Tech カテゴリのイメージ

重み・バイアスとは?ニューラルネットワークは何を学習しているのか

ニューラルネットワークの「学習」は、入力に掛ける重みと、結果に加えるバイアスを、データに合う値へ調整することから始まる。重みは入力ごとの効き方を変え、バイアスは入力がゼロでも残る基準値を決める。

たとえば「気温が1℃上がると予測値がどれだけ動くか」と「気温が0℃でも予測値をいくつにするか」は別の問題だ。前者を重み、後者をバイアスとして分けると、モデルが何を変えているのか見えやすくなる。

1つのニューロンで何を計算するか

2つの入力を受け取る最小の例なら、活性化関数を通す前の値は次のように書ける。

z=w1x1+w2x2+bz = w_1x_1 + w_2x_2 + b

x1x_1 と x2x_2 は入力、w1w_1 と w2w_2 は対応する重み、bb はバイアス、zz は計算結果である。重みは入力ごとに1つずつあるが、この出力に対するバイアスは1つだ。出力が複数ある層では、通常、出力ごとに重みの組とバイアスを持つ。

重みが正なら、その入力が大きいほど zz は増える。負なら減る。ただし「重みが大きい入力ほど重要」と単純には言えない。入力の単位やスケールが違えば、重みの数字だけを比べても効果は分からない。1℃単位の気温と1円単位の金額を同列に扱うときは特に注意が必要だ。

バイアスは式全体を上下にずらす。x1=x2=0x_1=x_2=0 のとき、z=bz=b になる。したがって、バイアスをなくしたモデルでは、入力がすべてゼロなら出力も必ずゼロになる。この制約が問題に合わない場合、バイアスが必要になる。

NumPyで「何を学習したか」を見る

次のコードは、答えが y=2x1−3x2+1y=2x_1-3x_2+1 になる小さなデータを作り、重み2つとバイアス1つをゼロから学習する。外部データは使わず、NumPyだけで実行できる。

import numpy as np
x = np.array([
[0.0, 0.0],
[1.0, 0.0],
[0.0, 1.0],
[1.0, 1.0],
])
y = np.array([1.0, 3.0, -2.0, 0.0])
w = np.zeros(2)
b = 0.0
learning_rate = 0.2
for _ in range(200):
prediction = x @ w + b
error = prediction - y
gradient_w = (2.0 / len(x)) * (x.T @ error)
gradient_b = 2.0 * np.mean(error)
w -= learning_rate * gradient_w
b -= learning_rate * gradient_b
print('weights:', np.round(w, 3))
print('bias:', round(b, 3))
print('predictions:', np.round(x @ w + b, 3))

この例では平均二乗誤差を小さくするよう更新している。gradient_w と gradient_b は、誤差を各パラメータで微分した値だ。勾配や損失関数の導出は後続の記事に譲り、ここでは「重みとバイアスの両方が更新対象」という点に注目する。

実行結果は次のとおり。

weights: [ 2. -3.]
bias: 1.0
predictions: [ 1. 3. -2. 0.]

学習後の重みは、x1x_1 に対しておよそ 22、x2x_2 に対しておよそ −3-3。バイアスはおよそ 11 になった。最初から正解の係数をコードに設定したわけではない。データの予測誤差を小さくする更新を繰り返した結果である。

データが少なく、しかも理想的な直線関係なので、このようにきれいに戻る。実務のデータでは雑音や相関があり、係数が一意に決まらないこともある。重みを読めば常に「その特徴の因果的な重要度」が分かるわけではない。

PyTorchのLinear層ではどう見えるか

PyTorchの torch.nn.Linear は、入力に重みを掛けてバイアスを加える層である。公式ドキュメントでは y=xAT+by=xA^T+b と定義され、重みの形は「出力数 × 入力数」、バイアスの形は「出力数」とされている。たとえば nn.Linear(2, 1) なら、入力2個から出力1個を作るため、重みは2個、バイアスは1個だ。

この例のNumPyコードでは x @ w + b と書いた。PyTorchの式で転置が出てくるのは、重みを「出力数 × 入力数」の行列として保存しているためである。計算内容が違うわけではない。

PyTorchではこれらを学習可能なパラメータとして保持し、自動微分で勾配を計算できる。一方、bias=False を指定すればバイアスを持たない層も作れる。すべての層に必ずバイアスがある、と覚えないほうがよい。

LLMとの接続

LLMも、入力を別の表現へ変換する多数の行列計算を使う。その行列に含まれる重みは、学習で調整されるパラメータだ。ただし、先ほどの2入力の例から「LLMの1つの重みには日本語の意味が1つ割り当てられている」と考えるのは飛躍である。大量の層とパラメータが組み合わさって働くため、個々の数字を単独で読むだけではモデルの知識や判断を説明できない。

ここで押さえたいのは、LLMの「学習」も最初は具体的な数値の更新だということだ。重みとバイアスの役割が分かれば、次に出てくる活性化関数、損失関数、勾配降下法を、それぞれ別の仕事として理解しやすくなる。

よくある誤解

「バイアス」は機械学習では文脈によって意味が変わる。この記事の bb は層の出力に加えるパラメータであり、社会的偏りや、統計でいう推定量のバイアスと同じ概念ではない。

また、「学習するのは重みだけ」でもない。バイアスを持つ層では、通常それも誤差に基づいて更新される。逆に、パラメータを一切更新せずに入力を変換する処理もニューラルネットワークにはある。何が学習対象かは、層や実装を見て確かめる必要がある。

参考資料:

講座の前後記事