tsujimotterの下書きノート

このブログは「tsujimotterのノートブック」の下書きです。数学の勉強過程や日々思ったことなどをゆるーくメモしていきます。下書きなので適当です。

記事一覧はこちらです。このブログの趣旨はこちら

メインブログである「tsujimotterのノートブログ」はこちら

誤差逆伝播法の計算

昔計算したんだけど、また忘れたので計算してみた。

誤差逆伝播法は、多層のニューラルネットワークを考えたときに、その重みを学習するための方法の一つである。バックプロパゲーション(Backpropagation)とも言う。やってみるとわかるが、出力の誤差を計算し、出力側の方から順に誤差の情報を伝播させていくことで、入力に近いほうの重みを更新させることができる。だから逆伝播法という。

順伝播の計算

簡単化するために、3層の一番シンプルなモデルを用いる。

f:id:tsujimotter:20180531142304p:plain:w400

入力層には  (x_i) が入る。 入力の個数は変数がもったいないので書かない。中間層が  (y_i) で、出力層が  z_1 とする。

入力が与えられたときの出力の計算は、入力から順にだんだんと行われる。

 \displaystyle y_j = f\left(\sum_{i} w_{ij} x_i\right)
 \displaystyle z_1 = f\left(\sum_{j} u_{j1} y_j\right)

 f(X)活性化関数と呼ばれる。この関数はいろいろなタイプがある。層ごとに違うものを選んでもいいが、簡単化のため、今回はすべての層で共通のものを用いる。

ところで

 \displaystyle y_j = f\left(\sum_{i} w_{ij} x_i - h_i\right)

という形の計算式をみたことがある人もいるかもしれない。つまり、閾値  h_i が設定されていて、それを超えるような  \sum_{i} w_{ij} x_i が入ってきたときにニューロンが発火する、みたいな話だ。もちろんそんな風にモデリングしてもいいが、 w_{ij}, h_i を別々の式で更新する必要があってめんどくさい。そこで、 w_{i0} = h_i としておき、 w_{i0} に相当する入力  x_0 には常に  -1 が入るとしておくのだ。こうしておくと上のような簡潔な形でかけて大変都合が良い。


 w_{ij}, u_{j1} は重み係数であり、これを変化させることで、同じ入力に対しても異なる出力を与えるように出来る。狙った出力が出るように、重み係数を更新する操作のことを学習というわけだ。

余談になるが、重み係数の学習させ方は任意の方法でよい。神様が現れて「このニューラルネットワークの重みはこれじゃ!」といって与えてもいい。神係数である。

逆伝播の計算

今回は、経験上うまくいくとされる誤差逆伝播法に基づいて計算する。

まず「入力に対してこんな出力になってほしい」という願いをこめた教師データを用意する。入力の組  \boldsymbol{x}^{(d)} = (x_i^{(d)}) に対して出力  z^{(d)} を対応付ける二項関係を考えればよい。 d 番目の教師データを

 (\boldsymbol{x}^{(d)}, z^{(d)})

これが何個かある。

これを使うと、現在のニューラルネットワークが出した出力に対する誤差が計算できる。

 \displaystyle E(\boldsymbol{W}, \boldsymbol{U}) = \frac{1}{2}\sum_{d} (z^{(d)} - z_1(\boldsymbol{x}^{(d)}) )^2 = \sum_{d} E^{(d)}(\boldsymbol{W}, \boldsymbol{U})

教師データを固定しておけば、誤差は重み  \boldsymbol{W} = (w_{ij}), \boldsymbol{U} = (u_{j1}) の関数と考えてよい。

というわけで、この誤差  E^{(d)}(\boldsymbol{W}, \boldsymbol{U}) を最小化するような、 \boldsymbol{W}, \boldsymbol{U} を計算すればよいとわかる。


シンプルなソリューションは、勾配法を使って誤差が下がる方向に重みを少しずつ更新していけばいい。ここで偏微分が使える。

 \displaystyle u_{j1} \leftarrow u_{j1} - \alpha \frac{\partial E}{\partial u_{j1}}
 \displaystyle w_{ij} \leftarrow w_{ij} - \alpha \frac{\partial E}{\partial w_{ij}}

よって、誤差関数を重みで偏微分する計算さえできればよいとわかる。あとはゴリゴリ計算するのみ。


 u_{j1} について計算する。

 \displaystyle \begin{align} \frac{\partial E^{(d)}}{\partial u_{j1}} &= (z^{(d)} - z_1(\boldsymbol{x}^{(d)}) ) \cdot \frac{\partial z}{\partial u_{j1}} \\
&=  (z^{(d)} - z_1(\boldsymbol{x}^{(d)}) ) \cdot f'\left(\sum_{j} u_{j1} y_j\right)  \cdot y_j \end{align}

外の微分、中の微分を計算していくだけなので、見た目ほどゴツイ計算ではない。


 w_{ij} についても計算する。

 \displaystyle \begin{align} \frac{\partial E^{(d)}}{\partial w_{ij}} &= (z^{(d)} - z_1(\boldsymbol{x}^{(d)}) ) \cdot \frac{\partial z}{\partial w_{ij}} \\
&=  (z^{(d)} - z_1(\boldsymbol{x}^{(d)}) ) \cdot f'\left(\sum_{j} u_{j1} y_j\right)  \cdot u_{j1} \frac{\partial y_j}{\partial w_{ij}} \end{align}

あーなるほど。かなり似てるなーと気づく。そのまま

 \displaystyle \begin{align} &=  (z^{(d)} - z_1(\boldsymbol{x}^{(d)}) ) \cdot f'\left(\sum_{j} u_{j1} y_j\right)  \cdot u_{j1} \cdot f'\left(\sum_{i} w_{ij} x_i\right) \cdot x_i  \end{align}

と計算できる。


少し長かったのだが、これで計算完了だ。

まとめると

 \displaystyle \begin{align} \frac{\partial E^{(d)}}{\partial u_{j}} &=  (z^{(d)} - z_1(\boldsymbol{x}^{(d)}) ) \cdot f'\left(\sum_{j} u_{j1} y_j\right)  \cdot y_j \\
 \frac{\partial E^{(d)}}{\partial w_{ij}} &=  (z^{(d)} - z_1(\boldsymbol{x}^{(d)}) ) \cdot f'\left(\sum_{j} u_{j1} y_j\right)  \cdot u_{j1} \cdot f'\left(\sum_{i} w_{ij} x_i\right) \cdot x_{i} \end{align}

となる。

そこそこ似通った形の式になっていることに気づくだろう。ここで

 \displaystyle \begin{align} \delta_1^{(3)} &:= f'\left(\sum_{j} u_{j1} y_j\right) (z^{(d)} - z_1(\boldsymbol{x}^{(d)}) )  \\
\delta_{j}^{(2)} &:= f'\left(\sum_{i} w_{ij} x_i\right) u_{j1} \delta_1^{(3)} \end{align}

とおけば、

 \displaystyle \begin{align} \frac{\partial E^{(d)}}{\partial u_{j1}} &=  \delta_1^{(3)} y_j \\
 \frac{\partial E^{(d)}}{\partial w_{ij}} &= \delta_{j}^{(2)} x_i \end{align}

とかけるわけだ。 \delta_1^{(3)} は出力層(第3層)の誤差の情報を表していて、 \delta_{j}^{(2)} は中間層(第2層)の誤差情報(みたいなもの)を表していると言えるかもしれない。 \delta_1^{(3)}, \delta_j^{(2)} が計算できれば、それに  y_j, x_i をかければ勾配が計算できる。

これらを計算する方法だが、上の定義からわかるように漸化式的な定義になっている。

 \displaystyle \delta_j^{(2)} = f'\left( \sum_{i} w_{ij} x_i \right) u_{j1} \delta_1^{(3)}

つまり、出力側の誤差情報  \delta_1^{(3)} から、より入力側に近い情報  \delta_{j}^{(2)} が計算できるのだ。誤差の情報が、出力層から順に伝播して更新されているように見える。だから逆伝播法なのだ。

デルタという記号を使って表すからデルタルールともいわれる。

層を増やしたときの注意

4層に増やしたときは少し注意が必要だ。4層目の出力を  o_1 として、3層目の中間層を  z_k とする。

f:id:tsujimotter:20180531193535p:plain:w400

順伝播の計算はこうなる。

 \displaystyle y_j = f\left(\sum_{i} w_{ij} x_i\right)
 \displaystyle z_k = f\left(\sum_{j} u_{jk} y_j\right)
 \displaystyle o_1 = f\left(\sum_{k} v_{k1} z_k\right)

このとき、逆伝播法の計算は次のように得られる。

 \displaystyle \begin{align}  \frac{\partial E^{(d)}}{\partial v_{k1}} &=  (o^{(d)} - o_1(\boldsymbol{x}^{(d)}) ) \cdot f'\left(\sum_{k} v_{k1} z_k\right)  \cdot z_k \\
 \frac{\partial E^{(d)}}{\partial v_{jk}} &=  (o^{(d)} - o_1(\boldsymbol{x}^{(d)}) ) \cdot f'\left(\sum_{k} v_{k1} z_k\right)  \cdot v_{k1} \cdot f'\left(\sum_{j} u_{jk} y_j\right)  \cdot y_j \\
 \frac{\partial E^{(d)}}{\partial w_{ij}} &=  \sum_{k} \left[ (o^{(d)} - o_1(\boldsymbol{x}^{(d)}) ) \cdot f'\left(\sum_{k} v_{k1} z_k\right)  \cdot v_{k1} \cdot f'\left(\sum_{j} u_{jk} y_j\right)  \cdot u_{jk} \cdot f'\left(\sum_{i} w_{ij} x_i\right) \cdot x_{i} \right] \end{align}

「あれ? w_{ij} の計算に出てくるとこがおかしい」と思うかもしれないが、これが正解だ。


デルタを以下のように定義すると

 \displaystyle \begin{align} \delta_1^{(4)} &:= f'\left(\sum_{k} v_{k1} z_k\right) (o^{(d)} - o_1(\boldsymbol{x}^{(d)}) ) \\
\delta_{k}^{(3)} &:=  f'\left(\sum_{j} u_{jk} y_j\right) v_{k1} \delta^{(4)} \\
\delta_{j}^{(2)} &:=  f'\left(\sum_{i} w_{ij} x_i\right) \sum_{k} u_{jk} \delta_{k}^{(3)} \end{align}

以下のようにかける。

 \displaystyle \begin{align}  \frac{\partial E^{(d)}}{\partial v_{k}} &=  \delta_1^{(4)} z_k \\
 \frac{\partial E^{(d)}}{\partial u_{jk}} &= \delta_k^{(3)} y_j \\
 \frac{\partial E^{(d)}}{\partial w_{ij}} &= \delta_j^{(2)} x_i \end{align}


つまり、こういうことだ。 w_{ij} を計算するために、3層目の  \delta_k^{(3)} の情報が必要になるわけだが、ニューロン  j が接続するニューロン  k は複数ある。 j の更新則を考えるためには、 j に接続するすべての  k の影響を考える必要があるのだ。図にするとこういうことである:

f:id:tsujimotter:20180601090450p:plain:w400

層をさらに増やした場合も同様の注意が必要である。うーんややこしい。


ところで、デルタルールの重要な点に触れておく。

上で観察したように  1 層から  2 層への重みの更新式を計算する際に、 2 層のデルタ  \delta_j^{(2)} を計算する必要があり、漸化式から  3 層のデルタ  \{\delta_k^{(3)}\} が必要となった。これは一般の多層ニューラルネットワークを計算するときも同様である。

つまり、 (l-1) 層から  l 層への重み  w_{ij}^{(l)} の更新式を計算する際に、 l 層のデルタ  \delta_{j}^{(l)} を計算する必要があり、漸化式から  l+1 層のデルタ  \{ \delta_k^{(l+1)} \} が必要となる。これを使うと、デルタルールにより

 \displaystyle \delta_{j}^{(l)} = f'\left(\sum_{i} w_{ij}^{(l)} z_i^{(l-1)}\right) \sum_{k} w_{jk}^{(l+1)} \delta_{k}^{(l+1)}

と得られる。 z_i^{(l-1)} (l-1) 層のニューロンの出力である。これを使うと更新式は

 \displaystyle \frac{\partial E^{(d)}}{\partial w_{ij}^{(l)}} = \delta_j^{(l)} z_i^{(l-1)}

となる。 l = 2 とした結果が上の例である。

 l 層の前後 2 層の情報があれば計算できてしまうのである。これは重要な性質だ。アルゴリズムが簡単になる。


これなら最初から一般的なケースで考えたほうが良かったんじゃないかという気がしてくる。

一方で、一般的なケースで書いたらわけがわからなくて、結局本質的な理解に到達しない気もするのでこれはこれでよい気もしてくる。