深層学習

誤差逆伝播法

出力側の誤差を連鎖律で入力側へ流し、全パラメータの勾配を順伝播1回分とほぼ同じコストで一度に求める手法。

  • A|中核
  • 深層学習

数式の記号で止まったら 記号の読み方 (∂・⊙・転置・上付き添字を、読み方から)

ひとことで言うと

誤差逆伝播法(backpropagation)は、損失を各パラメータで微分した値を、出力側から入力側へ連鎖律を適用しながら再利用して求める手続きです。学習アルゴリズムそのものではなく、勾配降下法に食わせる勾配を安く作る計算手順です。

工場のラインで不良品が出たとき、全工程を1つずつ止めて試すのではなく、最終検査の不良の内容から「直前の工程がどれだけ寄与したか」を割り出し、それを上流へ順に伝えていくようなものです。上流の責任配分は、下流ですでに計算した配分を使い回して求まる——この「使い回し」が逆伝播の正体です。

なぜ必要か

勾配さえ求まれば、パラメータ更新は勾配降下法に任せられます。問題は求め方のコストでした。

パラメータ数を PP、順伝播1回のコストを CC とすると:

方法勾配1セットのコスト精度
数値微分(中心差分)約 2PC2PC打ち切り誤差と桁落ちが乗る
解析的に手で導出ネットワークを変えるたび導出し直し正確だが人間が間違える
誤差逆伝播法約 CC の定数倍丸め誤差のみ

PP が数百万を超える現代のネットワークで、PP に比例する数値微分は最初から選択肢になりません。逆伝播は**PP に依存しない**——ここが決定的です。一方で、順伝播で計算した中間出力を保持しておく必要があるため、時間を稼ぐ代わりにメモリを払っています。

仕組み

LL を損失、第 ll 層の入力を a(l−1)\mathbf{a}^{(l-1)}、重みを W(l)W^{(l)}、活性化前の値を z(l)=W(l)a(l−1)+b(l)\mathbf{z}^{(l)} = W^{(l)}\mathbf{a}^{(l-1)} + \mathbf{b}^{(l)}、活性化関数を ff、その出力を a(l)=f(z(l))\mathbf{a}^{(l)} = f(\mathbf{z}^{(l)}) とします。

中心となるのは、活性化前の値に対する損失の勾配 δ(l)=∂L/∂z(l)\boldsymbol{\delta}^{(l)} = \partial L / \partial \mathbf{z}^{(l)} です。これが層をまたいで次のように伝わります。

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

⊙\odot は要素ごとの積、f′f' は活性化関数の導関数です。右辺に現れるのは1つ下流の δ\boldsymbol{\delta} だけで、そこから上流の δ\boldsymbol{\delta} が作れる。これが「使い回し」の中身です。δ\boldsymbol{\delta} さえ手に入れば、欲しかった勾配は掛け算1つで出ます。

∂L∂W(l)=δ(l)a(l−1)⊤,∂L∂b(l)=δ(l)\frac{\partial L}{\partial W^{(l)}} = \boldsymbol{\delta}^{(l)} \mathbf{a}^{(l-1)\top}, \qquad \frac{\partial L}{\partial \mathbf{b}^{(l)}} = \boldsymbol{\delta}^{(l)}
向き何を計算するか何を保存するか
順伝播z(l),a(l)\mathbf{z}^{(l)}, \mathbf{a}^{(l)} を出力側へ各層の a(l−1)\mathbf{a}^{(l-1)} と z(l)\mathbf{z}^{(l)}
逆伝播δ(l)\boldsymbol{\delta}^{(l)} を入力側へ勾配 ∂L/∂W(l)\partial L/\partial W^{(l)}

f′(z(l))f'(\mathbf{z}^{(l)}) が層ごとに掛け合わされる形になっている点に注意してください。この値が恒常的に1より小さければ積は指数的に縮み、大きければ発散します。勾配消失・勾配爆発はこの式から直接読める帰結であって、別の現象ではありません。

試験でどう問われるか

問われ方正解に寄る条件引っかけ
逆伝播の計算量の説明パラメータ数に依存せず、順伝播の定数倍で全勾配が求まる「パラメータ数に比例する」は数値微分の性質
δ\boldsymbol{\delta} の漸化式を選ばせる重みの転置を掛け、活性化の導関数と要素積を取る転置なし/行列積と要素積の入れ替え
勾配消失の原因層ごとの f′f' の積が0に近づく(sigmoid は最大 0.25)「学習率が小さすぎるから」は別の話
逆伝播と勾配降下法の関係逆伝播は勾配を求める手順、更新するのは最適化手法「逆伝播がパラメータを更新する」
順伝播で何を保持するか中間出力を保持するためメモリを消費する「メモリを使わないので効率的」

実装で確かめる

連鎖律で求めた勾配が、数値微分と一致することを見ます。逆伝播が正しく実装できているかを確かめる定番の手順(勾配確認)でもあります。

import numpy as np
rng = np.random.default_rng(0)
W1, b1 = rng.normal(size=(3, 4)), np.zeros(4)
W2, b2 = rng.normal(size=(4, 1)), np.zeros(1)
x, y = rng.normal(size=(5, 3)), rng.normal(size=(5, 1))

def forward(W2):                       # W2 以外は固定して損失を返す
    h = np.tanh(x @ W1 + b1)
    return h, ((h @ W2 + b2 - y) ** 2).mean()

h, loss = forward(W2)
g = 2 * (h @ W2 + b2 - y) / y.size     # まず出力に対する勾配を求め
gW2 = h.T @ g                          # 連鎖律で W2 まで一度に降ろす
num = np.zeros_like(W2)                # 同じものを数値微分でも作る
eps = 1e-6
for i in range(W2.size):
    Wp, Wm = W2.copy(), W2.copy()
    Wp.flat[i] += eps; Wm.flat[i] -= eps
    num.flat[i] = (forward(Wp)[1] - forward(Wm)[1]) / (2 * eps)
print("最大誤差:", np.abs(gW2 - num).max())

実行すると 最大誤差: 4.96277990924554e-10 になります。値は一致しますが、数値微分側はパラメータ1つにつき順伝播2回を回しているのに対し、逆伝播側は行列積1つで済んでいます。この差が PP 倍になって効いてきます。

勾配確認は差分幅 ε\varepsilon の取り方に敏感です。小さくしすぎると桁落ちで誤差が悪化するため、単調に改善するわけではありません。実装検査以外の目的で数値微分を使うことはまずありません。

取り違えやすいもの

用語誤差逆伝播法との関係
自動微分(リバースモード)逆伝播の一般形。逆伝播は「ニューラルネットに適用したリバースモード自動微分」にあたる
数値微分別の勾配計算手段。実装検査に使う。コストがパラメータ数に比例する
勾配降下法・Adam勾配を使う側。逆伝播は勾配を作る側で、役割が違う
誤差関数(損失関数)逆伝播の出発点となる量。逆伝播そのものではない
BPTT逆伝播を時間方向に展開したリカレントネット向けの適用形

想起チェック

逆伝播で全パラメータの勾配を求めるコストは、パラメータ数に対してどうスケールするか

PP に依存せず、順伝播1回の定数倍で済みます。中間出力を保持するぶんメモリは増えます。

ある層のデルタを、1つ下流の層のデルタから求める式は

δ(l)=(W(l+1)⊤δ(l+1))⊙f′(z(l))\boldsymbol{\delta}^{(l)} = (W^{(l+1)\top}\boldsymbol{\delta}^{(l+1)}) \odot f'(\mathbf{z}^{(l)})。重みは転置、活性化の導関数とは要素ごとの積です。

勾配消失が起きる理由を、上の式から説明すると

層をさかのぼるたびに f′(z(l))f'(\mathbf{z}^{(l)}) が掛け合わされるため、この値が1より小さいと積が指数的に0へ近づきます。sigmoid の導関数は最大でも 0.25 です。

「誤差逆伝播法がパラメータを更新する」は正しいか

正しくありません。逆伝播が作るのは勾配までで、更新するのは勾配降下法や Adam などの最適化手法です。

出典