Skip to article frontmatterSkip to article content
Site not loading correctly?

This may be due to an incorrect BASE_URL configuration. See the MyST Documentation for reference.

誤差逆伝播法

Open In Colab

これまでは、ニューラルネットワークの各パラメータについての目的関数の数値微分を計算することで勾配の計算を求める方法を説明しました。

勾配ベクトルの各成分は、各層の結合重みと各ユニットのバイアスでの損失関数の微分です。

しかし、ニューラルネットワークの層数が多くなると、数値微分の計算は膨大な時間がかかるでしょう。さらに、特に入力に近い深い層のパラメータほど、計算の手間が多くなります。

ここで、パラメータの勾配の計算を効率よく行う手法である「誤差逆伝播法」について学びます。

誤差逆伝播法 (backpropagation) の計算過程

1連鎖律

複数の関数によって構成される関数を合成関数と呼びます。

z=t2t=x+y\begin{align} z &= t^2 \\ t &= x + y \end{align}

合成関数の微分は、「ttに関するzzの微分zt\frac{\partial z}{\partial t}」と「xxに関するttの微分t1\frac{\partial t}{\partial 1}」の積のように、それぞれの関数の微分の積で求められます。

zx=zttx\frac{\partial z}{\partial x} = \frac{\partial z}{\partial t} \frac{\partial t}{\partial x}

2線形変換の逆伝播の導出

入力データx\mathbf{x}(N×D)(N \times D)の行列、W\mathbf{W}(D×H)(D \times H)の行列、b\mathbf{b}は要素数HHのベクトルと考え、線形変換の計算は以下の式で表します。

y=xW+b=(x0,0x0,1x0,D1x1,0x1,1x1,D1xN1,0xN1,1xN1,D1)(w0,0w0,1w0,H1w1,0w1,1w1,H1wD1,0wD1,1wD1,H1)+(b0b1bH1)=(d=0D1x0,dwd,0+b0d=0D1x0,dwd,1+b1d=0D1x0,dwd,H1+bH1d=0D1x1,dwd,0+b0d=0D1x1,dwd,1+b1d=0D1x1,dwd,H1+bH1d=0D1xN1,dwd,0+b0d=0D1xN1,dwd,1+b1d=0D1xN1,dwd,H1+bH1)=(y0,0y0,1y0,H1y1,0y1,1y1,H1yN1,0yN1,1yN1,H1)\begin{aligned} \mathbf{y} &= \mathbf{x} \mathbf{W} + \mathbf{b} \\ &= \begin{pmatrix} x_{0,0} & x_{0,1} & \cdots & x_{0,D-1} \\ x_{1,0} & x_{1,1} & \cdots & x_{1,D-1} \\ \vdots & \vdots & \ddots & \vdots \\ x_{N-1,0} & x_{N-1,1} & \cdots & x_{N-1,D-1} \end{pmatrix} \begin{pmatrix} w_{0,0} & w_{0,1} & \cdots & w_{0,H-1} \\ w_{1,0} & w_{1,1} & \cdots & w_{1,H-1} \\ \vdots & \vdots & \ddots & \vdots \\ w_{D-1,0} & w_{D-1,1} & \cdots & w_{D-1,H-1} \end{pmatrix} + \begin{pmatrix} b_0 & b_1 & \cdots & b_{H-1} \end{pmatrix} \\ &= \begin{pmatrix} \sum_{d=0}^{D-1} x_{0,d} w_{d,0} + b_0 & \sum_{d=0}^{D-1} x_{0,d} w_{d,1} + b_1 & \cdots & \sum_{d=0}^{D-1} x_{0,d} w_{d,H-1} + b_{H-1} \\ \sum_{d=0}^{D-1} x_{1,d} w_{d,0} + b_0 & \sum_{d=0}^{D-1} x_{1,d} w_{d,1} + b_1 & \cdots & \sum_{d=0}^{D-1} x_{1,d} w_{d,H-1} + b_{H-1} \\ \vdots & \vdots & \ddots & \vdots \\ \sum_{d=0}^{D-1} x_{N-1,d} w_{d,0} + b_0 & \sum_{d=0}^{D-1} x_{N-1,d} w_{d,1} + b_1 & \cdots & \sum_{d=0}^{D-1} x_{N-1,d} w_{d,H-1} + b_{H-1} \end{pmatrix} \\ &= \begin{pmatrix} y_{0,0} & y_{0,1} & \cdots & y_{0,H-1} \\ y_{1,0} & y_{1,1} & \cdots & y_{1,H-1} \\ \vdots & \vdots & \ddots & \vdots \\ y_{N-1,0} & y_{N-1,1} & \cdots & y_{N-1,H-1} \end{pmatrix} \end{aligned}

ここで、「nn番目の出力データのhh番目の項yn,hy_{n,h}」は、

yn,h=d=0D1xn,dwd,h+bhy_{n,h} = \sum_{d=0}^{D-1} x_{n,d} w_{d,h} + b_h

で計算できるのが分かります。

2.1重みの勾配

例えば、損失関数に二乗誤差

L=12y(x)d2L = \frac{1}{2}||y(x)-d||^2

を選んだとき、連鎖律より、Lwd,h\frac{\partial L}{\partial w_{d,h}}は次の式で求められます

Lwd,h=n=0N1Lyn,hyn,hwd,h\frac{\partial L}{\partial w_{d,h}} = \sum_{n=0}^{N-1} \frac{\partial L}{\partial y_{n,h}} \frac{\partial y_{n,h}}{\partial w_{d,h}}
  • Lyn,h\frac{\partial L}{\partial y_{n,h}}は、yn,hy_{n,h}に関するLLの微分です。

  • yn,hwd,h\frac{\partial y_{n,h}}{\partial w_{d,h}}は、wd,hw_{d,h}に関するyn,hy_{n,h}の微分です。

ここで、yn,hwd,h\frac{\partial y_{n,h}}{\partial w_{d,h}}は、

yn,hwd,h=wd,h{d=0D1xn,dwd,h+bh}=xn,d{xn,0w0,h++xn,dwd,h++xn,D1wD1,h+bh}=0++xn,d++0+0=xn,d\begin{aligned} \frac{\partial y_{n,h}}{\partial w_{d,h}} &= \frac{\partial}{\partial w_{d,h}} \left\{ \sum_{d=0}^{D-1} x_{n,d} w_{d,h} + b_h \right\} \\ &= \frac{\partial}{\partial x_{n,d}} \Bigl\{ x_{n,0} w_{0,h} + \cdots + x_{n,d} w_{d,h} + \cdots + x_{n,D-1} w_{D-1,h} + b_h \Bigr\} \\ &= 0 + \cdots + x_{n,d} + \cdots + 0 + 0 \\ &= x_{n,d} \end{aligned}

になりますため、

Lwd,h=n=0N1Lyn,hxn,d\frac{\partial L}{\partial w_{d,h}} = \sum_{n=0}^{N-1} \frac{\partial L}{\partial y_{n,h}} x_{n,d}

2.2バイアスの勾配

同じく連鎖律より、Lbh\frac{\partial L}{\partial b_h}は次の式で求められます。

Lbh=n=0N1Lyn,hyn,hbh\frac{\partial L}{\partial b_h} = \sum_{n=0}^{N-1} \frac{\partial L}{\partial y_{n,h}} \frac{\partial y_{n,h}}{\partial b_h}
yn,hbh=wd,h{d=0D1xn,dwd,h+bh}=0+1=1\begin{aligned} \frac{\partial y_{n,h}}{\partial b_h} &= \frac{\partial}{\partial w_{d,h}} \left\{ \sum_{d=0}^{D-1} x_{n,d} w_{d,h} + b_h \right\} \\ &= 0 + 1 \\ &= 1 \end{aligned}

まとめると、

Lbh=n=0N1Lyn,h\frac{\partial L}{\partial b_h} = \sum_{n=0}^{N-1} \frac{\partial L}{\partial y_{n,h}}

3ニューラルネットワークにおける誤差逆伝播法の計算例

連鎖律より勾配を計算する考え方をニューラルネットワークにも適用する計算例を見ましょう。

具体的には、ニューラルネットワークを構成する関数が持つパラメータについての目的関数の勾配を、順伝播で通った経路を逆向きにたどるようにして途中の関数の勾配の掛け算によって求めます。

ここから、手計算を通じて誤差逆伝播法の実装を理解しましよう。

  • 入力

i1=0.05,i2=0.10i_{1} = 0.05,i_{2} = 0.10
  • 初期パラメータ

w1=0.15,w2=0.20,w3=0.25,w4=0.30w_{1} = 0.15,w_{2} = 0.20,w_{3} = 0.25,w_{4} = 0.30
w5=0.40,w6=0.45,w7=0.50,w8=0.55w_{5} = 0.40,w_{6} = 0.45,w_{7} = 0.50,w_{8} = 0.55
  • 活性化関数: シグモイド関数

h(x)=11+exp(x)h(x) = \frac{1}{1 + \exp(-x)}
  • 教師データ

    o1=0.01,o2=0.99o_{1} = 0.01,o_{2} = 0.99
  • 目的関数は平均二乗誤差関数を用いることにします。

L=1Nn=1N(tnyn)2L = \dfrac{1}{N} \sum_{n=1}^{N} (t_{n} - y_{n})^2
ニューラルネットワークの実装例

ニューラルネットワークの実装例

3.1順伝播の流れ

import numpy as np
def sigmoid(x):
    return 1 / (1 + np.exp(-x))
net_h1= (0.15)*(0.05)+(0.2)*(0.1)+0.35
print("net_h1={}".format(net_h1))
net_h1=0.3775
net_h2= (0.25)*(0.05)+(0.3)*(0.1)+0.35
print("out_h2={}".format(net_h2))
out_h2=0.39249999999999996
net_o1 = (0.4)*net_h1+(0.45)*net_h2+0.6
out_o1= sigmoid(net_o1)
print("out_o1={}".format(out_o1))
net_o2 = (0.5)*net_h1+(0.55)*net_h2+0.6
out_o2= sigmoid(net_o2)
print("out_o2={}".format(out_o2))
out_o1=0.7165932011681534
out_o2=0.7319669364891265
L_1 = 0.5 * np.square(0.01-out_o1)
L_2 = 0.5 * np.square(0.99-out_o2)
L = L_1+L_2
print("Loss={}".format(L))
Loss=0.2829275069009325

例えば、w5w_5の勾配を計算する際には、

誤差逆伝播法でw_5の勾配を求める

誤差逆伝播法でw5w_5の勾配を求める

Lw5=Louto1outo1neto1neto1w5\frac{\partial L}{\partial w_5} = \frac{\partial L}{\partial out_{o1}}\frac{\partial out_{o1}}{\partial net_{o1}}\frac{\partial net_{o1}}{\partial w_5}

Louto1\frac{\partial L}{\partial out_{o1}}を計算する

L=12(targeto1outo1)2+12(targeto2outo2)2L= \frac{1}{2}(target_{o_{1}}-out_{o_{1}})^2+\frac{1}{2}(target_{o_{2}}-out_{o_{2}})^2

合成関数の微分g(f(x))=g(f(x))f(x)g(f(x))= g^{\prime}(f(x))f^{\prime}(x)によって

Louto1=212(targeto1outo1)1+0\frac{\partial L}{\partial out_{o1}}= 2*\frac{1}{2}(target_{o_{1}}-out_{o_{1}})*-1+0
d_out_o1 = -(0.01-out_o1)
print("d_out_o1={}".format(d_out_o1))
d_out_o1=0.7065932011681534

outo1neto1\frac{\partial out_{o1}}{\partial net_{o1}}を計算する

outo1=sigmod(neto1)out_{o1}= sigmod(net_{o_{1}})

Sigmoid関数の微分は f(x)=f(x)(1f(x))f^{\prime}(x)=f(x)(1-f(x)) なので

outo1neto1=outo1(1outo1)\frac{\partial out_{o1}}{\partial net_{o1}}= out_{o1}(1-out_{o1})
d_net_o1 = out_o1*(1-out_o1)
print("d_net_o1={}".format(d_net_o1))
d_net_o1=0.20308738520773184

neto1w5\frac{\partial net_{o1}}{\partial w_5}を計算する

neto1=w5neth1+w6neth2+b21net_{o_{1}}=w_{5}*net_{h_{1}}+w_{6}*net_{h_{2}}+b_{2}*1
neto1w5=neth1\frac{\partial net_{o1}}{\partial w_5}= net_{h_{1}}
d_w5= d_out_o1*d_net_o1*net_h1
print("d_w5={}".format(d_w5))
d_w5=0.05417131252562742

パラメータを更新する

w5+=w5ηLw5w_5^+ = w_{5}- \eta \frac{\partial {L}}{\partial w_5}

3.2多層ネットワークへの一般化

以上の計算例には、2層ネットワークの場合の損失関数の勾配を誤差逆伝播法で計算できました。各層での勾配計算がチェーンルールに従っているため、層数が増えても理論的には同じ流れで処理できますため、任意の層数のネットワークに拡張することができます。

  • 入力:学習サンプルxnx_nおよび目標出力dnd_nのベア

  • 出力:損失関数Ln(w)L_n(w)の各層llのパラメータについての微分Lwjil\frac{\partial {L}}{\partial w_{ji}^{l}}

  1. 順伝播: 入力データが層を通過して出力に達するまで順番に計算が行われます。

  2. 損失の計算: ネットワークの最終出力(予測値)と実際のラベルとの誤差(損失)を損失関数で計算します (δjL=zjdj\delta_j^{L}=z_j-d_j)

  3. 逆伝播: 各隠れ層では、l(=L-1, L-2,...)でのδ(l)\delta^{(l)}を、δ(l)=(z(l+1)z(l)δ(l+1))f(zl)\delta^{(l)}= (\frac{\partial z^{(l+1)}} {\partial z^{(l)}} \cdot \delta^{(l+1)}) \cdot f'(z^{l})のように、次の層からの勾配を逆伝播させ、チェーンルールを使って計算します。

  4. パラメータの更新:逆伝播によって計算された勾配δ(l)\delta^{(l)}を用いて、各層の重みとバイアスを更新します。