3層ニューラルネットワークのバックプロパゲーションについて
「Python機械学習プログラミング 達人データサイエンティストによる理論と実践」
を読んで学んだことをまとめてみました。
初投稿ですが、何卒よろしくお願いいたします!
【この記事を書こうと思った動機】
バイアス項の重みの更新について, 数学的理論の説明の部分では触れられていない(ように思えた)のですが, コードを見ると実装上では, バイアスユニットの重みも更新しています。そのときに, どうして実装上はこのように重みを更新したのだろう?という疑問が生まれ、色々と考えてみました。一応, 自分なりに結論が出ましたのでこちらにまとめてみました。
【場面設定】
入力層(数式中ではinと表現), 隠れ層(数式中ではhと表現), 出力層(数式中ではoutと表現)の3層ニューラルネットワークのバックプロパゲーションについて説明します。
$A^{(l)}$:第$l$層のユニットベクトル
$a_{j}^{[i](l)}$:第$i$データ点に関する, 第$l$層の第$j$ユニットの総入力
$x^{[i]}$:第$i$データ点
$z_{j}^{[i](l)}$:第$i$データ点に関する, 第$l$層の第$j$ユニットの総入力
$m$:バイアス項を除いた入力層のユニット数
$d$:バイアス項を除いた出力層のユニット数
$t$:バイアス項を除いた出力層のユニット数
$W^{(h)}$:隠れ層の重み行列
$W^{(out)}$:隠れ層の重み行列
$w^{(l)}_{j,k}$: 第$l-1$層の第$k$ユニットから, 第$l$層の第$j$ユニットへの挿入力の計算に用いられる重み
$\sigma$:活性化関数、ここではシグモイド関数を採用
【交差エントロピー損失の定義】
出力層の1つ1つのユニットに対して交差エントロピー損失を計算して和をとっていることに注意してください。その上で, 入力したすべてのデータ点に関して和をとっています。
$\sum_{l,j,k}(w^{(l)}_{j,k})^{2}$は出力層, 隠れ層に登場する重み行列の成分を、すべて2乗して足しています。
【計算が複雑になっても迷子にならないために】
損失を最小になるように重みを更新したいので…$$\frac{\partial}{\partial w_{j,k}^{(out)}}J(W), \frac{\partial}{\partial w_{j,k}^{(h)}}J(W)$$を計算することが目標ということを, 意識してこの後の計算をみていくとよいかと思います。
始めは説明のしやすさの為に, 正則化項を無視して計算を行っていきます。
最後にはきちんと正則化項を加味して, 重みの更新を行いますのでご安心を。
【手順①】
途中計算は省略していますが、$\frac{\partial}{\partial z^{[i](out)}_{j,k}}$の計算は添え字が多くて複雑に見えますが, 単なる偏微分の計算なので,
$$\frac{\partial}{\partial z}(y\log a +(1-y)\log(1-a))$$
と同じ計算になります。ただし, $a=\sigma(z)$となるので合成関数の微分が必要となることに注意してください。
【手順②】
上記の画像に書かれている通り, ここで出力層の重みの更新を行うための勾配の計算が完了します。
その計算を行うために手順①で$\delta ^{[i](out)}$を計算していました。
【手順③】
ここからは$\frac{\partial}{\partial w_{j,k}^{(h)}}J(W)$を計算するのが目標です。
そのために手順③では, 以下の画像の$\delta ^{[i](h)}$を計算します。
$\frac{\partial J(W)}{\partial z^{[i](h)}_{k}}$を$k=1, 2$の2つだけ計算してみました。
この計算結果を見ると, 以下の画像のように$\frac{\partial J(W)}{\partial z^{[i](h)}}$を2つの行列の積で表現できることが分かります。
では, 次は$\frac{\partial z^{[i](out)}}{\partial z^{[i](h)}}$を計算してみます。
第(1, 1)成分を計算してみます。他の成分についても計算は同様にしてできます。
上の画像の最後の行のドットは内積を意味します。
内積で敢えて表現したのは, $\frac{\partial z^{[i](out)}}{\partial z^{[i](h)}}$を「行列で表現できそう!」という感覚をつかんでもらうためです。
実際に行列で表現すると以下の画像のようになります。
赤の四角と水色の四角が重なり, 分かりにくくなってしまっていますが, 先ほどの計算を踏まえて,
$$\frac{\partial z^{[i](out)}}{\partial z^{[i](h)}}=W^{(out)} \frac{\partial a^{[i](h)}}{\partial z^{[i](h)}}$$
と変形しています。次は$\frac{\partial a^{[i](h)}}{\partial z^{[i](h)}}$ を計算していきます。
様子をつかむために以下の画像のように, (2,2)成分, (2,3)成分, (1,1)成分の順に計算してみます。
ここで注目してほしいのは $a^{[i](h)}_1 = \sigma(z^{[i](h)}_1)$なので, これを$z^{[i](h)}_2$で偏微分すると結果は0になってしまいます。ただし, $\frac{\partial a_0^{[i](h)}}{\partial z_1^{[i](h)}}$(第(1,1)成分)は, $a_0^{[i](h)}=1$(バイアスユニット)なので0になってしまいます。
以上のことから$\frac{\partial a^{[i](h)}}{\partial z^{[i](h)}}$はほぼ対角行列となります。(対角行列と述べて問題ないですが(1,1)成分が0という事を強調して'ほぼ'と表現しました(笑))
ここでちょっと寄り道です。
Hadamard積というのは上の画像のような成分ごとの積を意味します。
Hadamard積を使うとほぼ対角行列の積を上のように表現できます。
このことを利用して, 計算の続きを行っていきましょう。
少し長かったですがこれで$\delta^{[i](h)}$の計算が完了です。
【手順④】
いよいよ隠れ層の勾配を求めていきます!
上のように勾配を計算できます。出力層の勾配を計算したときと同様の手順です。
これまでの計算を振り返ってみましょう!
このように図で見ると出力層から順に計算して, 戻っている感じがしますね。
この計算がバックプロパゲーション(誤差逆伝播法)です。
後は, 今求めた勾配をもとにした, 重みの更新式をのぞいて見ましょう!
【いよいよ重みの更新を行います】
出力層の重みの更新
バックプロパゲーションでの計算を踏まえると出力層の勾配は上のように
$$(\delta ^{[i](out)})^T (a_0^{[i](h)}, a_1^{[i](h)}, \cdots , a_d^{[i](h)})$$
で計算できます。
先ほどまでは, ある1つのデータ点$x^{[i]}$にのみ注目した話でした。
次の画像はデータ点をn個に増やした場合の, 出力層の勾配の計算式です。
データ点がn個に増えたとしても1つ1つのデータ点に行う計算は同じです。それ故, $\delta ^{(out)}, A^{(h)}$は, 上の画像のように単にn行になるだけです。
では, 最後に今までずっと無視していた正則項を加味して重みの更新を行いたいと思います。
実は, 正則化項の計算はそこまで難しくありません。上の画像でいうと「なぜなら」の下の部分です。
$W^{(out)}$の勾配の第$(s,t)$成分は
$$\frac{\partial }{\partial w _{s,t} ^{(out)}}(\frac{\lambda}{2} \sum _{l, j, k} (w_{j,k} ^{(l)} )^2) = \lambda w _{s,t} ^{(out)}$$
となります。要するに微分と関係ない変数は0となってしまい, 残るのは結局$\lambda w _{s,t} ^{(out)}$ということです。
隠れ層の重みを更新
最後に, 隠れ層の重みの更新を説明します。が, 基本的には出力層と変わりません。ただし, 1か所だけ気にしてほしいのはバイアスユニットの影響です。
バイアスユニットの影響で(「ほぼ対角行列」の話をしたところ), 本来$\delta ^{(h)}$には$\delta _0 ^{[i](h)} (i=1, 2, \cdots, n)$と対応する列(第0列とでもいうべきでしょうか)があります。しかしながら, $w _{0,k}^{(h)}$なる重みは存在しません。そのことを考慮して上の画像では$\delta ^{(h)}$から$\delta _0 ^{[i](h)} (i=1, 2, \cdots, n)$と対応する列を除いて書いています。これが出力層の重みの更新と異なる点です。
後は, 出力層のときと同様にして重みの更新を行います。
以上がニューラルネットワークの重みの更新の手順です。
最後まで読んでいただきありがとうございました!