← 機械学習テキスト 一覧

🎓 レベル:標準 | 重要度:A(必須)

📎 前提:パーセプトロンと多層パーセプトロン | 土台:勾配降下法(勾配で下る)・凸最適化の基礎(連鎖律の文脈)

要点(BLUF)

1. なぜ必要か — 素朴な微分は破綻する

ニューラルネットの学習は 勾配降下法 で見たとおり、損失 L(θ)L(\theta) を勾配の逆向きに下るだけです:

θθηθL(θ)\theta \leftarrow \theta - \eta\,\nabla_\theta L(\theta)

問題は「θL\nabla_\theta L をどう計算するか」です。θ\theta は全層の重み・バイアスをまとめたもので、現代のモデルでは数百万〜数十億個あります。

素朴にやるなら、各パラメータ θk\theta_k について有限差分

LθkL(θ+εek)L(θ)ε\frac{\partial L}{\partial \theta_k} \approx \frac{L(\theta + \varepsilon e_k) - L(\theta)}{\varepsilon}

を計算すればよさそうですが、これは破綻します。1個の偏微分にネットワークの順伝播(forward)が1回必要なので、パラメータが PP 個あれば順伝播 PPPP が百万なら、勾配を1ステップ出すのに順伝播を百万回——非現実的です。

そこで連鎖律を「出力側から1回だけ」流して、共通する計算を全パラメータで使い回す。 これが誤差逆伝播法の発想です。結論を先に言うと、これで**順伝播1回+逆伝播1回(≈順伝播と同コスト)**で全勾配がそろいます。PP 倍が定数倍に化けるわけです。

2. 計算グラフと順伝播(forward)

まず順伝播を定式化します。ネットワークを LL 層とし、第 ll 層を次のように書きます(ϕ\phi は活性化関数):

z(l)=W(l)h(l1)+b(l),h(l)=ϕ(z(l))z^{(l)} = W^{(l)} h^{(l-1)} + b^{(l)}, \qquad h^{(l)} = \phi\big(z^{(l)}\big)

最終層の出力 h(L)=y^h^{(L)} = \hat{y} から損失 L=(y^,y)L = \ell(\hat{y}, y) を計算します。これら一連の演算(行列積 → 加算 → 活性化 → … → 損失)を計算グラフ(computational graph)——各ノードが演算、各エッジが値の流れ——として並べたものが、順伝播です。

要するに:入力 xxW,b,ϕW,b,\phi で順に変換して損失 LL という1つのスカラーにたどり着く、その「変換の連なり」が計算グラフです。逆伝播はこのグラフを逆向きにたどります。

用語メモ:z(l)z^{(l)}(プレ活性化)と h(l)h^{(l)}(活性化後)を区別するのが導出の要です。誤差信号 δ\deltazz に関する微分として定義し、hh ではなく zz で再帰を回すと式がきれいになります。

3. 連鎖律(chain rule)の復習

逆伝播の数学的な中身は合成関数の微分(連鎖律)一本です。スカラー LLuu を介して vv に依存するとき、

Lv=Luuv\frac{\partial L}{\partial v} = \frac{\partial L}{\partial u}\,\frac{\partial u}{\partial v}

多変数で、LL が複数の中間変数 u1,,umu_1, \dots, u_m を経由して vv に依存するなら、すべての経路の寄与を足します

Lv=j=1mLujujv\frac{\partial L}{\partial v} = \sum_{j=1}^{m} \frac{\partial L}{\partial u_j}\,\frac{\partial u_j}{\partial v}

これが計算グラフ上では「そのノードに合流してくる全エッジの(上流の勾配)×(局所の勾配)を足し合わせる」という規則になります。

要するに:「ゴール(損失)の微分」は「経路上の微分の積」、経路が複数あればその和。逆伝播は各ノードでこの掛け算と足し算を機械的に繰り返すだけです。

各ノードは2つだけ知っていれば動きます——(1) 上流から流れてきた勾配(upstream gradient)、(2) 自分の出力を自分の入力で微分した局所勾配(local gradient)。この2つを掛けたものを下流へ流す。ネットワーク全体の構造をノードが知る必要はありません(モジュラリティ)。

4. 逆伝播(backward)の導出 — 誤差信号 δ

ここが本題です。各層の誤差信号を、プレ活性化 z(l)z^{(l)} に関する損失の勾配として定義します:

  δ(l)    Lz(l)  \boxed{\;\delta^{(l)} \;\equiv\; \frac{\partial L}{\partial z^{(l)}}\;}

「層 ll のプレ活性化を少し揺らすと損失がどれだけ動くか」という感度ベクトルです。これさえ各層で求まれば、後述のとおり重み・バイアスの勾配は一発で出ます。δ\delta出力層から入力層へ再帰的に求めるのが逆伝播です。

(BP1) 出力層の誤差信号

最終層 z(L)z^{(L)} は、活性化 h(L)=ϕ(z(L))h^{(L)} = \phi(z^{(L)}) を経て損失 LL に届きます。z(L)z^{(L)} の各成分 zi(L)z^{(L)}_i は活性化 hi(L)=ϕ(zi(L))h^{(L)}_i = \phi(z^{(L)}_i) だけに(要素ごとに)影響するので、連鎖律から

δi(L)=Lzi(L)=Lhi(L)hi(L)zi(L)=Lhi(L)ϕ(zi(L))\delta^{(L)}_i = \frac{\partial L}{\partial z^{(L)}_i} = \frac{\partial L}{\partial h^{(L)}_i}\,\frac{\partial h^{(L)}_i}{\partial z^{(L)}_i} = \frac{\partial L}{\partial h^{(L)}_i}\,\phi'\big(z^{(L)}_i\big)

全成分まとめてベクトルで書くと、

  δ(L)=hL    ϕ(z(L))  \boxed{\;\delta^{(L)} = \nabla_{h} L \;\odot\; \phi'\big(z^{(L)}\big)\;}

ここで \odotアダマール積(要素ごとの積)hL\nabla_h L は損失を出力 h(L)h^{(L)} で微分したベクトルです。

要するに:出力層の誤差は「損失が出力をどれだけ気にするか(hL\nabla_h L)」に「活性化がプレ活性化にどれだけ反応するか(ϕ\phi')」を要素ごとに掛けたもの。

補足:損失と出力活性化の相性が良いと hLϕ\nabla_h L \odot \phi' がきれいに簡約されます。たとえばソフトマックス出力+交差エントロピー損失恒等出力+二乗損失では δ(L)=y^y\delta^{(L)} = \hat{y} - y(予測と正解の差)という非常に簡潔な形になります。この「誤差=予測−正解」が δ\delta を「誤差信号」と呼ぶ由来です。

(BP2) 隠れ層の誤差信号(再帰式)

隠れ層 z(l)z^{(l)} は、次層のプレ活性化 z(l+1)=W(l+1)h(l)+b(l+1)z^{(l+1)} = W^{(l+1)} h^{(l)} + b^{(l+1)}経由してのみ損失に影響します。z(l)z^{(l)}h(l)h^{(l)}z(l+1)z^{(l+1)}LL という経路です。連鎖律で成分ごとに書くと、zi(l)z^{(l)}_ihi(l)=ϕ(zi(l))h^{(l)}_i = \phi(z^{(l)}_i) を通って、次層の全ニューロン zk(l+1)z^{(l+1)}_k に流れ込みます:

δi(l)=Lzi(l)=(kLzk(l+1)zk(l+1)hi(l))hi(l)経由で次層へhi(l)zi(l)\delta^{(l)}_i = \frac{\partial L}{\partial z^{(l)}_i} = \underbrace{\left(\sum_k \frac{\partial L}{\partial z^{(l+1)}_k}\,\frac{\partial z^{(l+1)}_k}{\partial h^{(l)}_i}\right)}_{h^{(l)}_i\,\text{経由で次層へ}}\,\frac{\partial h^{(l)}_i}{\partial z^{(l)}_i}

各部品を埋めます。Lzk(l+1)=δk(l+1)\dfrac{\partial L}{\partial z^{(l+1)}_k} = \delta^{(l+1)}_k(次層の誤差信号、もう求まっている)、zk(l+1)hi(l)=Wki(l+1)\dfrac{\partial z^{(l+1)}_k}{\partial h^{(l)}_i} = W^{(l+1)}_{ki}(次層の重み)、hi(l)zi(l)=ϕ(zi(l))\dfrac{\partial h^{(l)}_i}{\partial z^{(l)}_i} = \phi'(z^{(l)}_i)。代入すると、

δi(l)=(kWki(l+1)δk(l+1))ϕ(zi(l))\delta^{(l)}_i = \left(\sum_k W^{(l+1)}_{ki}\,\delta^{(l+1)}_k\right)\phi'\big(z^{(l)}_i\big)

括弧内は W(l+1)W^{(l+1)\top}δ(l+1)\delta^{(l+1)} の行列・ベクトル積(第 ii 成分)そのものです。よってベクトルで、

  δ(l)=(W(l+1)δ(l+1))    ϕ(z(l))  \boxed{\;\delta^{(l)} = \Big(W^{(l+1)\top}\,\delta^{(l+1)}\Big) \;\odot\; \phi'\big(z^{(l)}\big)\;}

要するに:層 ll の誤差は「次層の誤差 δ(l+1)\delta^{(l+1)} を、重みの転置 W(l+1)W^{(l+1)\top}逆向きに配り直し(=順伝播 W(l+1)h(l)W^{(l+1)}h^{(l)} の逆操作)」、さらに「この層の活性化の傾き ϕ\phi' を要素ごとに掛けた」もの。順伝播が「WW を掛けて前へ」なら、逆伝播は「WW^\top を掛けて後ろへ」です。

これが再帰式です。δ(L)\delta^{(L)} を (BP1) で求め、(BP2) で δ(L1),δ(L2),,δ(1)\delta^{(L-1)}, \delta^{(L-2)}, \dots, \delta^{(1)}出力から入力へドミノ式に降りていきます。同じ δ(l+1)\delta^{(l+1)} を使い回すので、層をまたいだ微分の重複計算が消えます——これが効率の源です。

(BP3)(BP4) パラメータの勾配

肝心の重み・バイアスの勾配は、δ\delta が手元にあればその層だけで即座に出ます。z(l)=W(l)h(l1)+b(l)z^{(l)} = W^{(l)} h^{(l-1)} + b^{(l)} を直接微分します。

重みzi(l)=jWij(l)hj(l1)+bi(l)z^{(l)}_i = \sum_j W^{(l)}_{ij} h^{(l-1)}_j + b^{(l)}_i なので zi(l)Wij(l)=hj(l1)\dfrac{\partial z^{(l)}_i}{\partial W^{(l)}_{ij}} = h^{(l-1)}_j。連鎖律で

LWij(l)=Lzi(l)zi(l)Wij(l)=δi(l)hj(l1)\frac{\partial L}{\partial W^{(l)}_{ij}} = \frac{\partial L}{\partial z^{(l)}_i}\,\frac{\partial z^{(l)}_i}{\partial W^{(l)}_{ij}} = \delta^{(l)}_i\,h^{(l-1)}_j

これは外積(列ベクトル×行ベクトル)の形なので、行列でまとめて

  LW(l)=δ(l)(h(l1))  \boxed{\;\frac{\partial L}{\partial W^{(l)}} = \delta^{(l)}\,\big(h^{(l-1)}\big)^\top\;}

バイアスzi(l)bi(l)=1\dfrac{\partial z^{(l)}_i}{\partial b^{(l)}_i} = 1 なので、

  Lb(l)=δ(l)  \boxed{\;\frac{\partial L}{\partial b^{(l)}} = \delta^{(l)}\;}

要するに:ある層の重みの勾配は「その層に流れ込んだ入力 h(l1)h^{(l-1)}」と「その層の誤差信号 δ(l)\delta^{(l)}」の外積。バイアスの勾配は誤差信号そのもの。δ\delta さえ伝播できれば、勾配は機械的に出ます。

計算グラフ上の順伝播 → 逆伝播

flowchart LR
    X["入力 x = h⁽⁰⁾"] -->|"forward →"| Z1["z⁽¹⁾ = W⁽¹⁾h⁽⁰⁾ + b⁽¹⁾"]
    Z1 -->|"φ"| H1["h⁽¹⁾ = φ(z⁽¹⁾)"]
    H1 -->|"forward →"| Z2["z⁽²⁾ = W⁽²⁾h⁽¹⁾ + b⁽²⁾"]
    Z2 -->|"φ"| H2["h⁽²⁾ = ŷ"]
    H2 --> Loss["損失 L = ℓ(ŷ, y)"]

    Loss -. "δ⁽²⁾ = ∇hL ⊙ φ'(z⁽²⁾)" .-> Z2
    Z2 -. "δ⁽¹⁾ = (W⁽²⁾ᵀδ⁽²⁾)⊙ φ'(z⁽¹⁾)" .-> Z1
    Z2 -. "← backward(W²ᵀで配り直す)" .-> Z1

    Z2 ==> G2["∂L/∂W⁽²⁾ = δ⁽²⁾h⁽¹⁾ᵀ"]
    Z1 ==> G1["∂L/∂W⁽¹⁾ = δ⁽¹⁾h⁽⁰⁾ᵀ"]

実線が順伝播(左→右で損失まで)、点線が逆伝播(損失→左へ δ\delta を伝播)、太矢印が各層で取り出すパラメータ勾配です。

手続きのまとめ

flowchart TD
    A["順伝播:x から各層の z⁽ˡ⁾・h⁽ˡ⁾ を計算し保存"] --> B["損失 L を計算"]
    B --> C["出力層の誤差信号<br/>δ⁽ᴸ⁾ = ∇hL ⊙ φ'(z⁽ᴸ⁾)"]
    C --> D{"l = L から 1 まで"}
    D --> E["パラメータ勾配を取り出す<br/>∂L/∂W⁽ˡ⁾ = δ⁽ˡ⁾h⁽ˡ⁻¹⁾ᵀ ,  ∂L/∂b⁽ˡ⁾ = δ⁽ˡ⁾"]
    E --> F["1つ前の層へ誤差を伝播<br/>δ⁽ˡ⁻¹⁾ = (W⁽ˡ⁾ᵀδ⁽ˡ⁾)⊙ φ'(z⁽ˡ⁻¹⁾)"]
    F --> D
    D -- "完了" --> G["全勾配 ∇θL がそろう<br/>→ θ ← θ − η∇θL で更新"]

5. 計算量 — 順伝播と同オーダー(効率の核心)

誤差逆伝播法が「効率的」と言われる理由を定量化します。

ll 層の主役は行列積です。

つまり逆伝播の各演算は、対応する順伝播の演算と同じ重みサイズの行列を1〜2回触るだけ。全層を合わせると、

逆伝播のコスト    (定数倍)×順伝播のコスト\text{逆伝播のコスト} \;\approx\; (\text{定数倍}) \times \text{順伝播のコスト}

となり、両者は同じオーダー O ⁣(lnlnl1)O\!\big(\sum_l n_l n_{l-1}\big) です。これは自動微分の言葉で言えば「スカラー出力に対するリバースモード自動微分は、関数評価と同じ漸近計算量で勾配を返す」という一般的事実の特別な場合です。

要するに:パラメータが PP 個あっても、勾配は PPの順伝播ではなく、順伝播1回ぶん+逆伝播1回ぶんでまるごとそろう。素朴な有限差分(O(P)O(P) 回の順伝播)に対する圧倒的な勝利が、深層学習を計算可能にしています。

注意(メモリとのトレードオフ):逆伝播は速い代わりにメモリを食いますδ(l)\delta^{(l)}L/W(l)\partial L/\partial W^{(l)} の計算に順伝播で求めた h(l1),z(l)h^{(l-1)}, z^{(l)} が必要なので、順伝播の中間値を全層ぶん保持しておかねばなりません。深いネットや長い系列でメモリが逼迫する原因はこれで、勾配チェックポインティング(一部を捨てて再計算)などで対処します。

6. 勾配消失・爆発への伏線

再帰式 (BP2) をよく見ると、δ\delta を1層後ろへ運ぶたびに W(l+1)W^{(l+1)\top}ϕ(z(l))\phi'(z^{(l)})掛け算で入ります。出力層から kk 層さかのぼると、

δ(Lk)(W(L)diagϕ)(W(L1)diagϕ)\delta^{(L-k)} \sim \Big(W^{(L)\top}\,\mathrm{diag}\,\phi'\Big)\Big(W^{(L-1)\top}\,\mathrm{diag}\,\phi'\Big)\cdots

のように、ϕ\phi' と重みの積が kk 回連なります。ここから深いネットの病が見えます。

要するに:逆伝播は「掛け算の連鎖」なので、ϕ\phi'WW の大きさが 1 からずれると深さに対して指数的に効いてくる。これが活性化関数の選び方(ReLU 系で ϕ1\phi' \approx 1 を保つ)や初期化・正規化が深層学習で死活的に重要になる理由です。詳細は 活性化関数重み初期化と正規化 で扱います。

7. 自動微分(autodiff)との関係

現代では誤差逆伝播法を手で実装することはまずありません。PyTorch / TensorFlow / JAX などのフレームワークが、順伝播のコードを実行する裏で計算グラフを自動構築し、各演算の局所勾配を知っているので、loss.backward() 一行で逆伝播(リバースモード自動微分)を走らせて全パラメータの .grad を埋めてくれます。

ポイントは、誤差逆伝播法はニューラルネット専用の特別な魔法ではなく、計算グラフ上のリバースモード自動微分の一適用だということです。スカラー(損失)への入力次元が出力次元より遥かに大きい状況(まさにパラメータ多数の学習)で、リバースモードが最も得をします。各演算ノードに「局所勾配の計算規則」さえ定義しておけば、任意の合成関数で同じ仕組みが回ります。

8. ⚠️ よくある誤解・落とし穴

9. まとめ

誤差逆伝播法は、(1) 損失を計算グラフとして並べ(順伝播で z(l),h(l)z^{(l)}, h^{(l)} を保存)、(2) 出力層の誤差 δ(L)=hLϕ(z(L))\delta^{(L)} = \nabla_h L \odot \phi'(z^{(L)}) を起点に、(3) 再帰式 δ(l)=(W(l+1)δ(l+1))ϕ(z(l))\delta^{(l)} = (W^{(l+1)\top}\delta^{(l+1)}) \odot \phi'(z^{(l)}) で誤差を後ろへ伝え、(4) 各層で L/W(l)=δ(l)(h(l1))\partial L/\partial W^{(l)} = \delta^{(l)}(h^{(l-1)})^\topL/b(l)=δ(l)\partial L/\partial b^{(l)} = \delta^{(l)} を取り出す——これだけの手続きです。連鎖律を出力側から使い回すことで計算量を順伝播と同オーダーに抑え、数百万パラメータの勾配を一括で得ます。この勾配を モーメンタムとAdam系最適化 に渡せば、深層学習の学習ループが完成します。次は再帰式に現れた ϕ\phi' の主役、活性化関数 へ進みます。

対応するシミュレーション

simulations/backprop_check.py:小さな MLP(2→3→1)で、誤差逆伝播で求めた解析的な勾配と、損失を ±ε\pm\varepsilon 動かして得る数値微分(有限差分)の勾配を突き合わせます。相対誤差が 10910^{-9} 前後とほぼ一致し、BP の連鎖律による勾配計算が正しいことを検証できます(gradient checking)。

関連ノート