導入
順伝播で予測が出せました。では、その予測が外れていたとき、どの重みをどれだけ直せばよいのか。出力の誤差を入力側へさかのぼって各重みの勾配を求めるのが 逆伝播(backpropagation) です。NN 学習の心臓部です。
説明
考え方は「責任の分配」です。最終的な損失に対して、各重みが「どれだけ悪さをしたか」を、出力側から順に手前の層へ伝えていきます。数学的には**連鎖律(合成関数の微分)**そのものですが、直感は次の通りです。
flowchart RL loss["損失"] --> o["出力層の重みの<br/>責任を計算"] o --> h["1つ手前の層へ<br/>責任を伝播"] h --> i["さらに手前へ…"] i --> done["全重みの勾配が<br/>出そろう"]
順伝播が「入力 → 出力」の向きなら、逆伝播は「損失 → 入力」の逆向きに勾配を流します。勾配が出れば、あとは第2章と同じ 重み -= lr × 勾配 で更新するだけです。
PyTorch では、この逆伝播が loss.backward() のたった1行で自動実行されます(第4章)。ここで手計算の大変さを味わっておくと、その威力が分かります。
やってみよう
入力2 → 隠れ2 → 出力1 のネットワークで、順伝播 → 損失 → 逆伝播で勾配を求め、数値微分と一致するかを確かめます(勾配チェック)。逆伝播で求めた勾配が、愚直な数値微分とほぼ一致すれば、実装が正しい証拠です。
import numpy as np
def sigmoid(z):
return 1 / (1 + np.exp(-z))
np.random.seed(1)
x = np.array([0.5, -0.2])
target = np.array([1.0])
W1 = np.random.randn(2, 2) * 0.5
W2 = np.random.randn(1, 2) * 0.5
def forward(W1, W2):
a1 = sigmoid(W1 @ x)
y = sigmoid(W2 @ a1)
loss = np.mean((y - target) ** 2)
return a1, y, loss
# --- 逆伝播で W2 の勾配を解析的に求める ---
a1, y, loss = forward(W1, W2)
dloss_dy = 2 * (y - target) / 1
dy_dz2 = y * (1 - y) # sigmoidの微分
grad_W2 = (dloss_dy * dy_dz2)[:, None] * a1[None, :]
# --- 数値微分で W2 の勾配を確かめる ---
num = np.zeros_like(W2)
h = 1e-5
for i in range(W2.shape[0]):
for j in range(W2.shape[1]):
W2p = W2.copy(); W2p[i, j] += h
W2m = W2.copy(); W2m[i, j] -= h
num[i, j] = (forward(W1, W2p)[2] - forward(W1, W2m)[2]) / (2 * h)
print("逆伝播の勾配 :", np.round(grad_W2, 5))
print("数値微分の勾配:", np.round(num, 5))
print("ほぼ一致してれば逆伝播は正しい!")
演習
上のコードの target を 0.0 に変えて実行し、逆伝播と数値微分の勾配がやはり一致することを確かめましょう。正解を変えても勾配計算の仕組みは正しく動きます。
ヒント1を見る
target = np.array([0.0]) に書き換えるだけです。
ヒント2を見る
2つの勾配がほぼ同じ数値なら成功。逆伝播(連鎖律)は、どんな正解に対しても正しい勾配を出します。
まとめ
- 逆伝播は、出力の誤差を手前の層へさかのぼらせて各重みの勾配を求める仕組み。
- 正体は連鎖律(合成関数の微分)。勾配が出れば更新は第2章と同じ。
- 手計算は煩雑だが、PyTorch は
loss.backward()で全部自動化してくれる(次章)。