Python環境を起動中... 初回のみ数秒かかります
Lesson 3 / 12

NumPyで逆伝播・自動微分

このレッスンで学ぶこと

  • 誤差逆伝播が「連鎖律で勾配をさかのぼる」ことだと、コードで確認できる
  • 全結合層の勾配 dW = Xᵀ·dz を導いて実装できる
  • ReLU の微分(通過/遮断のマスク)を実装できる
  • 数値微分による勾配チェックで、自分の勾配が正しいと確かめられる

1. 逆伝播(backpropagation)とは

📚 このレッスンの前提(必ず先に確認を): 本レッスンは、勾配・最適化の理論を前提に、それをコードへ落とし込みます。理論があいまいだと式の意味が追いにくいので、 E資格対策「深層モデルのための最適化」先に目を通しておくことを強くおすすめします。勾配降下・パラメータ更新の考え方を理解していないと、ここから登場する式(勾配の計算と更新)が腑に落ちにくくなります。

順伝播で出した予測と正解とのズレ(損失)を小さくするには、各パラメータを「どちらにどれだけ動かせば損失が減るか」を知る必要があります。 その情報が勾配(gradient)。逆伝播は、連鎖律(chain rule)を使って出力側から入力側へ勾配を順々に伝える計算です。

学習の1ステップは 順伝播 → 損失 → 逆伝播(勾配計算)→ パラメータ更新。 PyTorch ではこの「逆伝播」を loss.backward() が自動でやってくれますが、 ここで中身を手で書いておくと、autograd(自動微分)の挙動が腑に落ちます。

理論の確認: 連鎖律 dL/dW = (dL/dz)·(dz/dW)。損失から各層へ「微分のかけ算」でさかのぼります。 「勾配の形(shape)は、対応するパラメータの形と同じ」——これが実装時の強力なチェックになります。

2. 単層 + MSE の逆伝播

まず一番シンプルに、全結合1層 pred = X·W + b と平均二乗誤差(MSE)で勾配を求めます。 手で導いた式は次の3つだけです(N はサンプル数)。この3式は全結合層の逆伝播の「型」なので、ぜひ覚えてしまいましょう。

sample_1.py
Ctrl+Enter
出力

💡 勾配の形=パラメータの形: dW は必ず W と同じ形 (3, 1) になります。これは更新 W = W − lr·dW が要素ごとに行えるため必須。 勾配の形が合わない=どこかで転置や軸を間違えているサインです。

🔑 この3式は丸暗記でOK(コードの形):

  • dpred = 2 * (pred - y) / N … 損失 → 予測 の勾配
  • dW = X.T @ dpred … 重みの勾配(入力の転置 × 上流の勾配
  • db = dpred.sum(axis=0) … バイアスの勾配(行方向=サンプル方向に合計

全結合層が出てくるたびに、この形が繰り返し登場します。

3. 2層 + ReLU の逆伝播(連鎖律をさかのぼる)

次は中間層を1つ増やします。順伝播は z1 = X·W1+b1 → h = ReLU(z1) → pred = h·W2+b2。 逆伝播は出力側から1ステップずつさかのぼります。ポイントは ReLU の微分: 入力が正だった要素は勾配をそのまま通し、負だった要素は0で遮断します(z1 > 0 のマスク)。

sample_2.py
Ctrl+Enter
出力
理論の確認: dh = dpred @ W2.T は「重み行列の転置を掛けて勾配を前の層へ戻す」という逆伝播の定石。 dz1 = dh * (z1 > 0) は ReLU の微分(正で1・負で0)を掛けているだけです。 この「転置を掛けて戻す → 活性化の微分を掛ける」の繰り返しが誤差逆伝播の本体です。

🔑 ReLU の逆伝播はこの1行: dz1 = dh * (z1 > 0)

(z1 > 0) は「順伝播のとき正だった所が True(=1)、負だった所が False(=0)」のマスク。 これを上流の勾配 dh に掛けるだけで、正だった所はそのまま通し、負だった所は0で止める——これが ReLU の微分です。 短い式ですが非常に重要なので、形ごと覚えておきましょう。

4. 勾配チェック(数値微分と一致するか)

自分で連鎖律から導いた勾配(=解析的な勾配)が本当に正しいか、別のやり方で「答え合わせ」できます。それが 数値微分です。

そもそも微分とは「入力をほんの少し動かしたら、出力(ここでは損失)がどれだけ変化するか=傾き」のこと。 だから、あるパラメータを −ε だけ動かして損失を測り、その差を動かした幅で割れば、勾配の近似値が手に入ります((L(+ε) − L(−ε)) / 2ε)。 これは中学・高校で習った「(変化量)÷(動かした量)で傾きを出す」のと同じ発想です。

この数値微分の値と、連鎖律で求めた解析的な勾配がほぼ一致すれば、逆伝播の実装は正しいと確認できます。

sample_3.py
Ctrl+Enter
出力

💡 ここまでが「自動微分」の正体: 私たちは層ごとに勾配の式を手で書きました。PyTorch の autograd は、この勾配計算を計算グラフから自動で組み立てloss.backward() の一行で実行します。次のレッスンで、その PyTorch のやり方を見ます。

5. 練習問題

問題 1

重みの勾配 dW を求める

単層 pred = X·W + b で、dpred(損失から予測への勾配)が与えられているとき、重みの勾配 dW を計算してください(dWW と同じ形になるはず)。

exercise_1.py
出力
ヒントを見る(答え+解説)
import numpy as np
np.random.seed(2)

X = np.random.randn(6, 4)    # (N=6, D=4)
W = np.random.randn(4, 1)    # (4, 1)
dpred = np.random.randn(6, 1)  # 損失から予測への勾配(与えられているとする)

# 重みの勾配 dW を求めてください(形は (4, 1) になるはず)
dW = X.T @ dpred

print("dW.shape:", dW.shape)  # (4, 1)

Xᵀ(4, 6)dpred(6, 1) なので (4,6)@(6,1)=(4,1)「入力の転置 × 上流の勾配」が全結合層の重み勾配の形です。

問題 2

ReLU の逆伝播

順伝播で h = ReLU(z1) を計算しました。上流から来た勾配 dh を、ReLU を通して dz1 に変換してください(z1が正だった所だけ通す)。

exercise_2.py
出力
ヒントを見る(答え+解説)
import numpy as np

z1 = np.array([[ 1.0, -2.0],
               [-0.5,  3.0]])
dh = np.array([[ 0.7,  0.4],
               [ 0.2,  0.9]])

# z1 が正だった要素だけ dh を通し、負だった要素は0にしてください
dz1 = dh * (z1 > 0)

print("dz1 =\n", dz1)
# 期待: [[0.7 0. ]
#        [0.  0.9]]

(z1 > 0) は True/False の配列(1/0として計算される)。これを掛けることで、 正だった要素だけ勾配が通り、負だった要素は遮断されます。これが ReLU の微分です。

6. まとめ

このレッスンのポイント

  • 逆伝播は連鎖律で、出力側から入力側へ勾配を伝える計算
  • 全結合層の重み勾配は dW = Xᵀ · (上流の勾配)形は W と同じ
  • 勾配を前の層へ戻すときは重みの転置を掛ける(dh = dpred @ W2.T
  • ReLU の微分は (z > 0) のマスク。正だった所だけ通す
  • パラメータ更新(SGD)は W = W − lr · dW。勾配の逆向きに少し動かす
  • 勾配チェック(数値微分との比較)で実装の正しさを確認できる
  • PyTorch の loss.backward() は、この勾配計算を自動でやってくれる(次のレッスン)

学習ループを回して、損失が下がり続けるのを観察してみましょう。下のコードは「答え(本当の重み)が分かっている練習用データ」を用意し、勾配降下がその答えを当てられるかを試します。

free_practice.py
出力

完了するとコース一覧に進捗が記録されます