ニューラルネットワークの学習では、損失関数を小さくする方向へパラメータを少しずつ動かします。そのためには 損失をすべてのパラメータで偏微分した値(勾配) が必要です。パラメータが数百万あっても、この勾配を順伝播と同程度の計算量で求められるのが バックプロパゲーション(誤差逆伝播法) です。

この記事では、記法を決めて連鎖律を適用し、4つの基本式を導きます。最後に、手計算できる数値例と NumPy による実装で確かめます。

1. 記法

LL 層の全結合ニューラルネットワークを考えます。第 ll 層(l=1,…,Ll = 1, \dots, L)では、次の計算を行います。

z(l)=W(l)a(l−1)+b(l)a(l)=σ(z(l))\begin{aligned} \mathbf{z}^{(l)} &= W^{(l)} \mathbf{a}^{(l-1)} + \mathbf{b}^{(l)} \\ \mathbf{a}^{(l)} &= \sigma\left(\mathbf{z}^{(l)}\right) \end{aligned}
記号意味
a(0)=x\mathbf{a}^{(0)} = \mathbf{x}入力
W(l),b(l)W^{(l)}, \mathbf{b}^{(l)}第 ll 層の重み行列とバイアス
z(l)\mathbf{z}^{(l)}活性化関数に入る前の値(重み付き入力)
σ\sigma活性化関数(成分ごとに適用)
a(L)=y^\mathbf{a}^{(L)} = \hat{\mathbf{y}}ネットワークの出力
L(y^,y)\mathcal{L}(\hat{\mathbf{y}}, \mathbf{y})損失関数(y\mathbf{y} は正解)

順伝播(実線)で信号を前へ送り、逆伝播(破線)で誤差 δ を後ろへ送る

成分で書くと zj(l)=∑kWjk(l)ak(l−1)+bj(l)z^{(l)}_j = \sum_k W^{(l)}_{jk} a^{(l-1)}_k + b^{(l)}_j です。Wjk(l)W^{(l)}_{jk} は「前の層のユニット kk から、この層のユニット jj への重み」を表します。

目標は、すべての ll について次の2つを求めることです。

∂L∂Wjk(l),∂L∂bj(l)\frac{\partial \mathcal{L}}{\partial W^{(l)}_{jk}}, \qquad \frac{\partial \mathcal{L}}{\partial b^{(l)}_j}

2. 鍵となる量:誤差 δ

δj(l)\delta^{(l)}_j は「ユニット jj の入力 zj(l)z^{(l)}_j を少し動かしたとき、損失がどれだけ変わるか」を表します。バックプロパゲーションは、この δ\boldsymbol{\delta} を 出力層から入力層へ向かって順番に 計算していく手法です。

3. 連鎖律

多変数の連鎖律を思い出しておきます。L\mathcal{L} が中間変数 u1,…,unu_1, \dots, u_n を通して vv に依存しているとき、次が成り立ちます。

∂L∂v=∑i=1n∂L∂ui∂ui∂v\frac{\partial \mathcal{L}}{\partial v} = \sum_{i=1}^{n} \frac{\partial \mathcal{L}}{\partial u_i} \frac{\partial u_i}{\partial v}

ニューラルネットワークでは、ある層の値は 次の層のすべてのユニットを通して 損失に影響します。そのため、和の形の連鎖律が自然に現れます。

4. 4つの基本式

(BP1) 出力層の誤差

aj(L)=σ(zj(L))a^{(L)}_j = \sigma(z^{(L)}_j) なので、zj(L)z^{(L)}_j は aj(L)a^{(L)}_j だけを通して損失に影響します。

δj(L)=∂L∂aj(L) σ′(zj(L))\delta^{(L)}_j = \frac{\partial \mathcal{L}}{\partial a^{(L)}_j} \, \sigma'\left(z^{(L)}_j\right)

ベクトルで書くと、⊙\odot を成分ごとの積(アダマール積)として次のようになります。

δ(L)=∇a(L)L⊙σ′(z(L))(BP1)\boldsymbol{\delta}^{(L)} = \nabla_{\mathbf{a}^{(L)}} \mathcal{L} \odot \sigma'\left(\mathbf{z}^{(L)}\right) \tag{BP1}

(BP2) 誤差を1つ前の層へ伝える

zj(l)z^{(l)}_j は、次の層のすべての zk(l+1)z^{(l+1)}_k を通して損失に影響します。連鎖律より、次が成り立ちます。

δj(l)=∑k∂L∂zk(l+1)∂zk(l+1)∂zj(l)=∑kδk(l+1)∂zk(l+1)∂zj(l)\delta^{(l)}_j = \sum_k \frac{\partial \mathcal{L}}{\partial z^{(l+1)}_k} \frac{\partial z^{(l+1)}_k}{\partial z^{(l)}_j} = \sum_k \delta^{(l+1)}_k \frac{\partial z^{(l+1)}_k}{\partial z^{(l)}_j}

ここで zk(l+1)=∑jWkj(l+1)σ(zj(l))+bk(l+1)z^{(l+1)}_k = \sum_j W^{(l+1)}_{kj} \sigma(z^{(l)}_j) + b^{(l+1)}_k なので、次のようになります。

∂zk(l+1)∂zj(l)=Wkj(l+1) σ′(zj(l))\frac{\partial z^{(l+1)}_k}{\partial z^{(l)}_j} = W^{(l+1)}_{kj} \, \sigma'\left(z^{(l)}_j\right)

これを代入すると、次が得られます。

δj(l)=(∑kWkj(l+1)δk(l+1))σ′(zj(l))\delta^{(l)}_j = \left( \sum_k W^{(l+1)}_{kj} \delta^{(l+1)}_k \right) \sigma'\left(z^{(l)}_j\right)

∑kWkj(l+1)δk(l+1)\sum_k W^{(l+1)}_{kj} \delta^{(l+1)}_k は、転置行列 (W(l+1))⊤\left(W^{(l+1)}\right)^\top とベクトル δ(l+1)\boldsymbol{\delta}^{(l+1)} の積の第 jj 成分です。したがって、次のようにまとめられます。

δ(l)=((W(l+1))⊤δ(l+1))⊙σ′(z(l))(BP2)\boldsymbol{\delta}^{(l)} = \left( \left(W^{(l+1)}\right)^\top \boldsymbol{\delta}^{(l+1)} \right) \odot \sigma'\left(\mathbf{z}^{(l)}\right) \tag{BP2}

順伝播では WW を掛けて信号を前へ送りました。逆伝播では W⊤W^\top を掛けて誤差を後ろへ送ります。これが「逆伝播」という名前の由来です。

(BP3) バイアスに関する勾配

∂zj(l)/∂bj(l)=1\partial z^{(l)}_j / \partial b^{(l)}_j = 1 なので、次が成り立ちます。

∂L∂bj(l)=δj(l)⟺∇b(l)L=δ(l)(BP3)\frac{\partial \mathcal{L}}{\partial b^{(l)}_j} = \delta^{(l)}_j \quad\Longleftrightarrow\quad \nabla_{\mathbf{b}^{(l)}} \mathcal{L} = \boldsymbol{\delta}^{(l)} \tag{BP3}

(BP4) 重みに関する勾配

∂zj(l)/∂Wjk(l)=ak(l−1)\partial z^{(l)}_j / \partial W^{(l)}_{jk} = a^{(l-1)}_k なので、次が成り立ちます。

∂L∂Wjk(l)=δj(l) ak(l−1)⟺∇W(l)L=δ(l)(a(l−1))⊤(BP4)\frac{\partial \mathcal{L}}{\partial W^{(l)}_{jk}} = \delta^{(l)}_j \, a^{(l-1)}_k \quad\Longleftrightarrow\quad \nabla_{W^{(l)}} \mathcal{L} = \boldsymbol{\delta}^{(l)} \left(\mathbf{a}^{(l-1)}\right)^\top \tag{BP4}

重みの勾配は「その重みの 出口側の誤差 × 入口側の活性」です。入力側のユニットがほとんど発火していなければ(ak(l−1)≈0a^{(l-1)}_k \approx 0)、その重みはほとんど学習されません。

5. よく使う組み合わせでの出力層の誤差

(BP1) は、出力層の活性化関数と損失関数の組み合わせによって簡単な形になります。

出力層損失関数δ(L)\boldsymbol{\delta}^{(L)}
恒等関数二乗誤差 12∥y^−y∥2\frac{1}{2}\lVert \hat{\mathbf{y}} - \mathbf{y} \rVert^2y^−y\hat{\mathbf{y}} - \mathbf{y}
シグモイド二値交差エントロピーy^−y\hat{\mathbf{y}} - \mathbf{y}
ソフトマックス交差エントロピー −∑iyilog⁡y^i-\sum_i y_i \log \hat{y}_iy^−y\hat{\mathbf{y}} - \mathbf{y}
シグモイド二乗誤差(y^−y)⊙σ′(z(L))(\hat{\mathbf{y}} - \mathbf{y}) \odot \sigma'(\mathbf{z}^{(L)})

ソフトマックスと交差エントロピーの組み合わせは、ソフトマックスが成分ごとの関数ではないので (BP1) をそのままは使えません。それでも、直接計算すると δ(L)=y^−y\boldsymbol{\delta}^{(L)} = \hat{\mathbf{y}} - \mathbf{y} という非常に簡単な形になります。y^i=ezi/∑mezm\hat{y}_i = e^{z_i} / \sum_m e^{z_m} より ∂y^i/∂zj=y^i(1[i=j]−y^j)\partial \hat{y}_i / \partial z_j = \hat{y}_i (\mathbb{1}[i=j] - \hat{y}_j) なので、次のように計算できます。

∂L∂zj=−∑iyiy^iy^i(1[i=j]−y^j)=−yj+y^j∑iyi=y^j−yj\frac{\partial \mathcal{L}}{\partial z_j} = -\sum_i \frac{y_i}{\hat{y}_i} \hat{y}_i \left(\mathbb{1}[i=j] - \hat{y}_j\right) = -y_j + \hat{y}_j \sum_i y_i = \hat{y}_j - y_j

最後の等号では、one-hot ベクトルなので ∑iyi=1\sum_i y_i = 1 であることを使いました。

6. アルゴリズム

ここまでの4つの式をまとめておきます。

バックプロパゲーションの基本式

δ(L)=∇a(L)L⊙σ′(z(L))(BP1)δ(l)=((W(l+1))⊤δ(l+1))⊙σ′(z(l))(BP2)∇b(l)L=δ(l)(BP3)∇W(l)L=δ(l)(a(l−1))⊤(BP4)\begin{aligned} \boldsymbol{\delta}^{(L)} &= \nabla_{\mathbf{a}^{(L)}} \mathcal{L} \odot \sigma'\left(\mathbf{z}^{(L)}\right) && \text{(BP1)} \\ \boldsymbol{\delta}^{(l)} &= \left( \left(W^{(l+1)}\right)^\top \boldsymbol{\delta}^{(l+1)} \right) \odot \sigma'\left(\mathbf{z}^{(l)}\right) && \text{(BP2)} \\ \nabla_{\mathbf{b}^{(l)}} \mathcal{L} &= \boldsymbol{\delta}^{(l)} && \text{(BP3)} \\ \nabla_{W^{(l)}} \mathcal{L} &= \boldsymbol{\delta}^{(l)} \left(\mathbf{a}^{(l-1)}\right)^\top && \text{(BP4)} \end{aligned}
  1. 順伝播:a(0)=x\mathbf{a}^{(0)} = \mathbf{x} から始め、各層の z(l),a(l)\mathbf{z}^{(l)}, \mathbf{a}^{(l)} を計算して 保存 しておく。
  2. 出力層の誤差:(BP1) で δ(L)\boldsymbol{\delta}^{(L)} を求める。
  3. 逆伝播:l=L−1,…,1l = L-1, \dots, 1 の順に (BP2) で δ(l)\boldsymbol{\delta}^{(l)} を求める。
  4. 勾配:(BP3) と (BP4) で各層の勾配を求める。
  5. 更新:学習率 η\eta で勾配降下する。
W(l)←W(l)−η ∇W(l)L,b(l)←b(l)−η ∇b(l)LW^{(l)} \leftarrow W^{(l)} - \eta \, \nabla_{W^{(l)}} \mathcal{L}, \qquad \mathbf{b}^{(l)} \leftarrow \mathbf{b}^{(l)} - \eta \, \nabla_{\mathbf{b}^{(l)}} \mathcal{L}

ミニバッチ(mm 個のサンプル)で損失を平均する場合は、各サンプルの勾配を平均します。

7. 手計算で確かめる

入力1つ、隠れユニット1つ、出力1つの最小のネットワークで計算してみます。

  • 隠れ層:z1=w1x+b1z_1 = w_1 x + b_1、a1=σ(z1)a_1 = \sigma(z_1)(シグモイド)
  • 出力層:y^=z2=w2a1+b2\hat{y} = z_2 = w_2 a_1 + b_2(恒等関数)
  • 損失:L=12(y^−y)2\mathcal{L} = \frac{1}{2}(\hat{y} - y)^2

値は x=1x = 1、y=1y = 1、w1=0.5w_1 = 0.5、b1=0b_1 = 0、w2=2w_2 = 2、b2=0b_2 = 0 とします。

順伝播

z1=0.5×1+0=0.5a1=σ(0.5)≈0.62246y^=2×0.62246+0≈1.24492L=12(1.24492−1)2≈0.029993\begin{aligned} z_1 &= 0.5 \times 1 + 0 = 0.5 \\ a_1 &= \sigma(0.5) \approx 0.62246 \\ \hat{y} &= 2 \times 0.62246 + 0 \approx 1.24492 \\ \mathcal{L} &= \tfrac{1}{2}(1.24492 - 1)^2 \approx 0.029993 \end{aligned}

逆伝播

出力層は恒等関数と二乗誤差の組み合わせなので、δ2=y^−y≈0.24492\delta_2 = \hat{y} - y \approx 0.24492 です。シグモイドの微分は σ′(z)=σ(z)(1−σ(z))\sigma'(z) = \sigma(z)\left(1 - \sigma(z)\right) なので、σ′(0.5)≈0.62246×0.37754≈0.23500\sigma'(0.5) \approx 0.62246 \times 0.37754 \approx 0.23500 となります。

∂L∂w2=δ2 a1≈0.24492×0.62246≈0.15245(BP4)∂L∂b2=δ2≈0.24492(BP3)δ1=w2 δ2 σ′(z1)≈2×0.24492×0.23500≈0.11511(BP2)∂L∂w1=δ1 x≈0.11511(BP4)∂L∂b1=δ1≈0.11511(BP3)\begin{aligned} \frac{\partial \mathcal{L}}{\partial w_2} &= \delta_2 \, a_1 \approx 0.24492 \times 0.62246 \approx 0.15245 && \text{(BP4)} \\ \frac{\partial \mathcal{L}}{\partial b_2} &= \delta_2 \approx 0.24492 && \text{(BP3)} \\ \delta_1 &= w_2 \, \delta_2 \, \sigma'(z_1) \approx 2 \times 0.24492 \times 0.23500 \approx 0.11511 && \text{(BP2)} \\ \frac{\partial \mathcal{L}}{\partial w_1} &= \delta_1 \, x \approx 0.11511 && \text{(BP4)} \\ \frac{\partial \mathcal{L}}{\partial b_1} &= \delta_1 \approx 0.11511 && \text{(BP3)} \end{aligned}

更新

学習率 η=0.5\eta = 0.5 で1回更新すると、パラメータは w1≈0.44244w_1 \approx 0.44244、b1≈−0.05756b_1 \approx -0.05756、w2≈1.92377w_2 \approx 1.92377、b2≈−0.12246b_2 \approx -0.12246 になります。損失は 0.0299930.029993 から約 0.0002480.000248 へ下がりました。

8. 正しさの確認:勾配チェック

実装した勾配が正しいかは、数値微分(中心差分) と比べて確かめられます。

∂L∂θ≈L(θ+ε)−L(θ−ε)2ε\frac{\partial \mathcal{L}}{\partial \theta} \approx \frac{\mathcal{L}(\theta + \varepsilon) - \mathcal{L}(\theta - \varepsilon)}{2\varepsilon}

上の例で ε=10−6\varepsilon = 10^{-6} として計算すると、∂L/∂w1≈0.115114\partial \mathcal{L} / \partial w_1 \approx 0.115114、∂L/∂w2≈0.152452\partial \mathcal{L} / \partial w_2 \approx 0.152452 となり、逆伝播の結果と一致します。

ただし、数値微分はパラメータ1つにつき損失を2回計算する必要があります。パラメータ数を PP とすると、順伝播を 2P2P 回行うことになります。一方バックプロパゲーションは、順伝播1回と逆伝播1回(順伝播の定数倍の計算量) で全パラメータの勾配が求まります。PP が数百万〜数十億になる現代のネットワークでは、この差が決定的です。

9. NumPy で実装する

XOR(2入力の排他的論理和)を、隠れ層4ユニット(シグモイド)と出力層(ソフトマックス + 交差エントロピー)で学習します。サンプルは列ベクトルとして横に並べています。

backprop_xor.py
import numpy as np

rng = np.random.default_rng(0)


def sigmoid(z):
    return 1.0 / (1.0 + np.exp(-z))


def softmax(z):
    z = z - z.max(axis=0, keepdims=True)  # オーバーフロー対策
    e = np.exp(z)
    return e / e.sum(axis=0, keepdims=True)


# データは列ベクトルを横に並べる:X は (入力次元, m)、Y は one-hot で (クラス数, m)
X = np.array([[0, 0, 1, 1],
              [0, 1, 0, 1]], dtype=float)
Y = np.array([[1, 0, 0, 1],    # クラス0:XOR が 0
              [0, 1, 1, 0]],   # クラス1:XOR が 1
             dtype=float)

n_in, n_hidden, n_out = 2, 4, 2
params = {
    "W1": rng.normal(0, 1, (n_hidden, n_in)),
    "b1": np.zeros((n_hidden, 1)),
    "W2": rng.normal(0, 1, (n_out, n_hidden)),
    "b2": np.zeros((n_out, 1)),
}


def forward(p, X):
    Z1 = p["W1"] @ X + p["b1"]
    A1 = sigmoid(Z1)
    Z2 = p["W2"] @ A1 + p["b2"]
    A2 = softmax(Z2)
    return A1, A2


def loss(A2, Y):
    m = Y.shape[1]
    return -np.sum(Y * np.log(A2 + 1e-12)) / m


def backward(p, X, Y, A1, A2):
    m = X.shape[1]
    d2 = (A2 - Y) / m                      # (BP1) softmax + 交差エントロピー
    d1 = (p["W2"].T @ d2) * A1 * (1 - A1)  # (BP2) σ'(z) = σ(z)(1 - σ(z))
    return {
        "W2": d2 @ A1.T,                   # (BP4)
        "b2": d2.sum(axis=1, keepdims=True),  # (BP3) バッチ方向に和をとる
        "W1": d1 @ X.T,
        "b1": d1.sum(axis=1, keepdims=True),
    }


eta = 1.0
for step in range(5001):
    A1, A2 = forward(params, X)
    grads = backward(params, X, Y, A1, A2)
    for name in params:
        params[name] -= eta * grads[name]
    if step % 1000 == 0:
        print(f"step {step:4d}  loss = {loss(A2, Y):.4f}")

_, A2 = forward(params, X)
print("予測クラス:", A2.argmax(axis=0))  # 正解は [0 1 1 0]

実行結果は次のとおりです(NumPy 2.5、乱数シード 0)。

実行結果
step    0  loss = 0.8358
step 1000  loss = 0.0085
step 2000  loss = 0.0034
step 3000  loss = 0.0021
step 4000  loss = 0.0015
step 5000  loss = 0.0012
予測クラス: [0 1 1 0]

同じコードで中心差分による勾配チェックを行うと、最大相対誤差は約 1.7×10−101.7 \times 10^{-10} でした。逆伝播の実装が正しいことが確認できます。

損失を mm 個の平均にしているので、d2 を mm で割っています。こうすると (BP3) と (BP4) の和が、自動的にサンプルごとの勾配の平均になります。

10. 少しだけ歴史

  • 1970年:リンナインマーが、今日のリバースモード自動微分にあたる方法を修士論文で示しました。
  • 1974年:ワーボスが、博士論文でこの方法をニューラルネットワークの学習に応用することを論じました。
  • 1986年:ラメルハート、ヒントン、ウィリアムズが Nature 誌の論文で、多層ネットワークが逆伝播によって有用な内部表現を学習できることを示しました。これにより手法が広く知られるようになります。

現在の PyTorch や JAX などのフレームワークは、ここで導いた計算を 計算グラフ上のリバースモード自動微分 として一般化し、自動で行っています。

まとめ

  • バックプロパゲーションは、連鎖律を 出力側から効率よく 適用する方法です。
  • 誤差 δ(l)=∂L/∂z(l)\boldsymbol{\delta}^{(l)} = \partial \mathcal{L} / \partial \mathbf{z}^{(l)} を導入すると、すべてが4つの式 (BP1)〜(BP4) にまとまります。
  • 逆伝播では W⊤W^\top を掛けて誤差を後ろへ送り、各重みの勾配は「出口の誤差 × 入口の活性」になります。
  • 計算量は順伝播の定数倍で、数値微分よりはるかに効率的です。実装したら勾配チェックで確かめましょう。