高卒でもわかる機械学習 (5) 誤差逆伝播法 その1

これはITエンジニア向けの記事です。前置きはこちら
記事中に間違いなどがあれば何卒ご指摘お願いします。

はじめに

多層パーセプトロンの重みを更新する理論について解説します。
更新すべき重みがたくさんあるので単純パーセプトロンより難しいですが、ここがわかると近年流行したディープラーニングを理解するための基本ができあがります。
ただし、結構長いので、「理論はざっくりでいいから最終的に使える重み更新式が知りたい」という人は、別のサイトや本を読むのをお勧めします。

なお、記事中で使われる各種記号の定義は前回やりましたので、わからなくなったらそちらを参照してください。

数学の前知識

今回は数式がけっこう出てきます。
そこで必要な数学知識について、いくつか説明を書いておきます。
簡単のため前提条件を少し省略しているので、数学的に厳密ではない箇所はご容赦ください。

偏微分

偏微分は以前の記事でも出てきましたが、僕が誤解してまして、高校数学の範囲には含まれていないようでした。
そんなに難しいことではなく、複数の変数による関数があるとして、その中の1つの変数のみに関する微分のことです。
例えば g(a, b, c) = a^2 + b^2 + c^2 + 1 として、ga に関する偏微分は \frac {\partial g}{\partial a} = 2a です。
a以外の変数はすべて定数と見なすので、aを含まない項は消えています。
微分の記号は d の代わりに \partial を使います。

合成関数の微分公式

こちらは高校数学の範囲内だと思いますが、復習しておきます。

ab の関数で、さらに bc の関数とします。
このとき、 ac で微分した \frac {da}{dc} について、下記のような公式があります。

    \[\frac {da}{dc} = \frac {da}{db} \frac {db}{dc}\]

合成関数の偏微分公式

これは大学数学の範囲だと思います。

ab_1, b_2, \dots, b_n の関数(複数変数を持つ)で、b_1, b_2, \dots, b_n がそれぞれ c の関数である場合、ac で偏微分すると下記のようになります。

    \[\frac {\partial a}{\partial c}  = \sum_{k=1}^n \frac {\partial a}{\partial b_k} \frac {\partial b_k}{\partial c}\]

これをこの記事では合成関数の偏微分公式の「難しい方」と呼ぶことにします。
(他所で言うと笑われるのでご注意ください。)

さらにこれの特殊ケースとして、「簡単な方」があります。
b_1, b_2, \dots, b_n のうち、実は m 番目のみが c の関数で、ほかは c の影響を受けない場合を考えます。
b_k を表す式が c を含まないのであれば、b_kc で偏微分すると 0 になりますね。
つまり、 \sum の中の \frac {\partial b_k}{\partial c} は、k \neq m の場合はすべて 0 になるので、k = mの場合だけを考慮すればよくなります。

    \[\frac {\partial a}{\partial c}  = \frac {\partial a}{\partial b_m} \frac {\partial b_m}{\partial c}\]

高校で習う方の公式とほぼ同じ形をしています。
繰り返しますが、偏微分において上記は特殊なケースです。
が、今回は何度か出てきます。

使い方のポイントは下記です。

  • 関数aの変数b_1b_nのうち、cが影響するのが1つだけだったら「簡単な方」を使う
  • 関数aの変数b_1b_nのうち、cが影響するのが複数だったら「難しい方」を使う
  • 難しい方には \sum がつく

勾配降下法の式

ここから本題です。

単純パーセプトロンの説明で、勾配降下法について説明しました。
多層パーセプトロンにおいても、その理論を用いて重みを更新します。
前回新たに定義した記号を用いて勾配降下法の式を書き直すと、下記のようになります。

    \[w_{i,j}^{(l)} \gets w_{i,j}^{(l)} - \rho \frac{\partial E}{\partial w_{i,j}^{(l)}}\]

lは任意の層番号、iは層内でのユニット番号、jはユニット内での重みの番号(= 1つ前の層のユニット番号)です。
Eは誤差関数ですが、単純パーセプトロンと同じ誤差関数が使えないため、新しく定義します。

多層パーセプトロンの誤差関数

多層パーセプトロンはネットワーク全体で1つの学習機械ですから、誤差関数も全体で1つだけ定義します。
誤差関数は次のように定義されます。1

    \[E = \frac{1}{2} \sum_k (y_k - t_k)^2\]

y_k は出力層k番目のユニットの出力値ですから、(y_k - t_k) はそのユニットの「理想の出力値と実際の出力値の差」です。
まずそれを二乗しています。二乗しているのは後の計算で都合がいいからです。負の値にならないメリットもありますね。
\sum_k (y_k - t_k)^2 は、出力層の全ユニットについてその計算をして足し合わせるということです。2
そしてさらに \frac{1}{2}をかけています3。これも後の計算で都合がいいからという理由です。

誤差を二乗した値の和(の定数倍)なので、このE二乗誤差と呼びます。
n番目のユニットのみに注目した場合の (y_n - t_n)^2 も二乗誤差と言ったりしますが、あまり気にしないでください。

二乗誤差ビジュアル

y_k = o_k^{(L)} ですから、次のようにも書けます。

    \[E = \frac{1}{2} \sum_k (o_k^{(L)} - t_k)^2\]

これは、Eo_1^{(L)}, o_2^{(L)}, \dots の関数であることを意味しています。

出力層の重み更新

誤差関数が決まったので、勾配降下法を適用することを考えます。
まず、出力層i番目のユニットのj番目の重み、w_{i,j}^{(L)} に注目します。

出力層i番目のユニットのj番目の重み

更新式の展開

この重みの勾配降下法による更新式は

    \[w_{i, j}^{(L)} \gets w_{i, j}^{(L)} - \rho \frac{\partial E}{\partial w_{i, j}^{(L)}}\]

です。
この中の\frac{\partial E}{\partial w_{i, j}^{(L)}} を解いていきます。

Eo_i^{(L)} の関数、 o_i^{(L)}u_i^{(L)} の関数、 u_i^{(L)}w_{i, j}^{(L)} の関数と見なせます。
また、Eは出力層i番目以外のユニット出力値も変数として持ちますが、上図を見ると分かるとおり、w_{i, j}^{(L)} の影響は i番目のユニット以外には及びません。
よって、合成関数の偏微分公式の「簡単な方」を使い、下記が成り立ちます。

    \[\frac{\partial E}{\partial w_{i, j}^{(L)}}    & = \frac{\partial E}{\partial o_i^{(L)}}        \frac{\partial o_i^{(L)}}{\partial u_i^{(L)}}        \frac{\partial u_i^{(L)}}{\partial w_{i, j}^{(L)}}\]

3項に分解できました。1項ずつ見てみましょう。
後ろの項の方が今までの知識で理解できるので、後ろから順にいきます。

第3項: \frac{\partial u_i^{(L)}}{\partial w_{i, j}^{(L)}}

u_i^{(L)} は、出力層の一つ前、つまり第(L-1)層の各ユニット出力に重みをかけて合計したものなので、下記のようになります。

    \[u_i^{(L)} = w_{i, 0}^{(L)} + w_{i, 1}^{(L)} o_1^{(L-1)} + w_{i, 2}^{(L)} o_2^{(L-1)} + \dots + w_{i, j}^{(L)} o_{j}^{(L-1)} + \dots\]

    \[\therefore \frac{\partial u_i^{(L)}}{\partial w_{i, j}^{(L)}} = o_j^{(L-1)}\]

第2項: \frac{\partial o_i^{(L)}}{\partial u_i^{(L)}}

o_i^{(L)} は出力層i番目のユニットの出力ですから、o_i^{(L)} = f(u_i^{(L)}) です。

    \[\therefore \frac{\partial o_i^{(L)}}{\partial u_i^{(L)}} = \frac{\partial f(u_i^{(L)})}{\partial u_i^{(L)}} = f'(u_i^{(L)})\]

つまりこれは活性化関数の微分です。4
実際の式は活性化関数の定義によって決まります。

第1項: \frac{\partial E}{\partial o_i^{(L)}}

出力層 i番目のユニット出力値 o_i^{(L)}=y_i)で誤差関数Eを偏微分するということです。
下図をご覧ください。

i番目の出力値で誤差関数を偏微分したときのイメージ

誤差関数Eは、出力層すべてのユニットの二乗誤差を合計して \frac {1}{2} したものでした。
これをo_i^{(L)}=y_i)で偏微分すると o_i^{(L)} 以外の変数はすべて定数と見なして消えるので、i 番目以外のユニットの二乗誤差はすべて無視することができます。
よって、

    \[\begin{align*}   \frac{\partial E}{\partial o_i^{(L)}}      & = \frac{\partial \frac{1}{2} \sum_k (o_k^{(L)} - t_k)^2}{\partial o_i^{(L)}} \\     & = \frac{\partial \frac{1}{2} (o_i^{(L)} - t_i)^2}{\partial o_i^{(L)}} \\     & = \frac{\partial \frac{1}{2} \left( \left( o_i^{(L)} \right) ^2 - 2 o_i^{(L)} t_i + \left(t_i\right)^2 \right)}{\partial o_i^{(L)}} \\     & = o_i^{(L)} - t_i \end{align*}\]

となります。5
2行目でi番目以外の項を消しています。

出力層ユニットの重み更新式

以上より、

    \[\begin{align*} \frac{\partial E}{\partial w_{i, j}^{(L)}}     & = \frac{\partial E}{\partial o_i^{(L)}}         \frac{\partial o_i^{(L)}}{\partial u_i^{(L)}}         \frac{\partial u_i^{(L)}}{\partial w_{i, j}^{(L)}} \\     & = \left( o_i^{(L)} - t_i \right) f'(u_i^{(L)}) o_j^{(L-1)} \\ \end{align*}\]

なので、これを勾配降下法の式に当てはめると、w_{i, j}^{(L)} の更新式は下記になります。

    \[w_{i, j}^{(L)} \gets w_{i, j}^{(L)} - \rho \Biggl( \left( o_i^{(L)} - t_i \right) f'(u_i^{(L)}) o_j^{(L-1)} \Biggr)\]

複雑に見えますが、プログラムとして実装すると大したことはありません。
i, j の値を変えていくと、出力層ユニットにひもづく重みはすべて更新できます。

出力層すべての重み

出力層より1つ前の層の重み更新

上記の式で多層パーセプトロン内のすべての重みが更新できるといいのですが、出力層以外の重みには別の更新式が必要になります。

出力層より一つ前の層、つまり第(L-1)層のユニットに紐付く重みについて考えます。
ここでも前出の誤差関数 E を使って勾配降下法を適用します。
(L-1)層向けに誤差関数を新しく定義したりはしません。

(L-1)i番目のユニットのj番目の重み、w_{i, j}^{(L-1)} に注目します。
なお、前の節でも i, j という記号を使いましたが、今回の i, j はそれとは別だと思ってください。
新たに記号を定義するのが大変なので使い回しています。

第(L-1)層i番目のユニットのj番目の重み

更新式の展開

この重みの勾配降下法による更新式は

    \[w_{i, j}^{(L-1)} \gets w_{i, j}^{(L-1)} - \rho \frac{\partial E}{\partial w_{i, j}^{(L-1)}}\]

です。

前回少し触れましたが、出力層の出力値は、第(L-1)層の出力値を使った式に展開することができます。
つまり誤差関数中に現れる o_1^{(L)}, o_2^{(L)}, \dots もすべて o_1^{(L-1)}, o_2^{(L-1)}, \dots を含む式に展開できるということです。
その状態のEで考えてみます。
展開後の式はとても長くなるのでどんな式かはハッキリわからなくていいですが、o_1^{(L)}, o_2^{(L)}, \dots が消えて o_1^{(L-1)}, o_2^{(L-1)}, \dots の関数になっていることだけ意識しておいてください。

Eo_i^{(L-1)} の関数であり、また、 o_i^{(L-1)}u_i^{(L-1)} の関数で、u_i^{(L-1)}w_{i, j}^{(L-1)} の関数です。
そしてこの E は第(L-1)i番目以外の出力値も変数として持ちますが、 上の図を見るとわかるように、w_{i, j}^{(L-1)} が影響を与えるのは、第(L-1)層だと i 番目のユニットだけです。(第L層まで伝播すると複数のユニットに影響しますが、o_1^{(L)}, o_2^{(L)}, \dots は今Eから消えているのでした。下記では、微分のパラメータとしても 第L層に関する変数を使っていません。)
よって、合成関数の偏微分公式の「簡単な方」を使って下記のように展開できます。

    \[\frac{\partial E}{\partial w_{i, j}^{(L-1)}}    & = \frac{\partial E}{\partial o_i^{(L-1)}}        \frac{\partial o_i^{(L-1)}}{\partial u_i^{(L-1)}}        \frac{\partial u_i^{(L-1)}}{\partial w_{i, j}^{(L-1)}}\]

これも後ろから一項ずつ見ていきます。

第3項: \frac{\partial u_i^{(L-1)}}{\partial w_{i, j}^{(L-1)}}

これは層番号が違うだけで、出力層の時と同じパターンです。

    \[u_i^{(L-1)} = w_{i, 0}^{(L-1)} + w_{i, 1}^{(L-1)} o_1^{(L-2)} + w_{i, 2}^{(L-1)} o_2^{(L-2)} + \dots + w_{i, j}^{(L-1)} o_{j}^{(L-2)} + \dots\]

    \[\therefore \frac{\partial u_i^{(L-1)}}{\partial w_{i, j}^{(L-1)}} = o_j^{(L-2)}\]

第2項: \frac{\partial o_i^{(L-1)}}{\partial u_i^{(L-1)}}

これも同じですね。層番号だけが違います。

    \[o_i^{(L-1)} = f(u_i^{(L-1)})\]

    \[\therefore \frac{\partial o_i^{(L-1)}}{\partial u_i^{(L-1)}} = \frac{\partial f(u_i^{(L-1)})}{\partial u_i^{(L-1)}} = f'(u_i^{(L-1)})\]

第1項: \frac{\partial E}{\partial o_i^{(L-1)}}

これが出力層より少し厄介です。
(L-1)i番目のユニット出力値で誤差関数を偏微分しています。
Eo_1^{(L-1)}, o_2^{(L-1)}, \dots の関数と見なしているのでしたが、この関数はよくわからないくらい長いのでした。
このままだと計算がしにくいので、改めて E を元の定義(o_1^{(L)}, o_2^{(L)}, \dots の関数)に戻し、合成関数の偏微分公式でさらに分解します。
使うのは難しい方です。

    \[\begin{align*}     \frac {\partial E} {\partial o_i^{(L-1)}}     & = \sum_k         \frac {\partial E} {\partial o_k^{(L)}}         \frac {\partial o_k^{(L)}} {\partial u_k^{(L)}}         \frac {\partial u_k^{(L)}} {\partial o_i^{(L-1)}} \end{align*}\]

「難しい方」を使ったのは、o_i^{(L-1)} が第L層のユニットすべてに影響を与えるからです。

第(L-1)層i番目のユニット出力値の影響範囲

もう少し解いてみる

上記の式、\sum中の第三項、\frac{\partial u_k^{(L)}}{\partial o_{i}^{(L-1)}} を解きます。

u_k^{(L)} は第(L-1)層の各ユニット出力に第(L)k番目のユニットの重みを掛けて合計したものなので、

    \[u_k^{(L)} = w_{k, 0}^{(L)} + w_{k, 1}^{(L)} o_1^{(L-1)} + w_{k, 2}^{(L)} o_2^{(L-1)} + \dots + w_{k, i}^{(L)} o_{i}^{(L-1)} + \dots\]

    \[\therefore \frac{\partial u_k^{(L)}}{\partial o_{i}^{(L-1)}} = w_{k, i}^{(L)}\]

です。
これを前述の式に代入すると下記のようになります。

    \[\begin{align*}     \frac {\partial E} {\partial o_i^{(L-1)}}     & = \sum_k         \frac {\partial E} {\partial o_k^{(L)}}         \frac {\partial o_k^{(L)}} {\partial u_k^{(L)}}         \frac {\partial u_k^{(L)}} {\partial o_i^{(L-1)}} \\     & = \sum_k         \frac {\partial E} {\partial o_k^{(L)}}         \frac {\partial o_k^{(L)}} {\partial u_k^{(L)}}         w_{k, i}^{(L)} \end{align*}\]

\sumの中に、偏微分の記号を使った項があと2つ残っていますね。
この2項の中の各変数を見てみると、すべて層番号が (L) となっています。
実はこの2項、第L層の重み更新を計算する際にすでに算出していることにお気づきでしょうか?
それがポイントになってきます。

誤差逆伝播法の発想

流れでいくと第(L-1)層の重み更新式を記載したいところですが、ここであえて多層パーセプトロンの学習法の概要を書きます。

L層の重み更新を先に計算すると、その計算結果の一部を第(L-1)層の重み更新の際に利用できそうでした。
プログラム上では一時変数に保持しておけば使えますね。
それを使って第(L-1)層の重み更新の計算をし、さらにその計算結果の一部を第(L-2)層の重み更新に使い…ということができると、第1層の重みまですべてを更新することができます。
これが誤差逆伝播法という重み更新法の考え方です。

順方向と逆方向のイメージ

多層パーセプトロンの入力層に \bm{x} を入力した時は、重みを掛けながら第1層、第2層…と伝播していき、出力層から \bm {y} が出力されます。
重み更新の際には、まず出力層の重み更新を計算し、その計算結果の一部を伝播させながら 第(L-1)層、第(L-2)層…という “逆方向” の順番に計算していくことになります。
誤差 “逆” 伝播法 というのはそういう意味です。

重み更新は、プログラムだと出力層から順にループで処理していく形になります。
前の節で第(L-1)層の更新について考えたとき、層番号以外は第L層の時とほとんど同じ形の式が現れたことを思い出してください。
これもループに適していそうですよね。

更新式の決定版は次回

ここまでは第L層と第(L-1)層に注目しましたが、全ての層に適用できる更新式を導くため、任意の第l層で一般化して考えます。
長くなったのでまた次回。


  1. ベクトル\bm{y}, \bm{t}L^2ノルム(詳細は割愛)を使うと次のようにも書けます。
    E = \frac{1}{2} \| \bm{y} - \bm{t} \|^2
    また、簡単のため、この定義はオンライン学習でベクトル \bm {x} を1件だけ入力した場合の誤差として記述しています。
    一括学習やバッチ学習で、複数の教師データを入力して全ての誤差の和(または平均)を計算するという場合は、この誤差関数にさらに \sum がもう1つつきます。 
  2. \sum の真下に k が表示されているのと \sum の右下に k が表示されているのは同じ意味です。
    この記事で使用している数式表示のシステムの仕様です。 
  3. 定数倍しても相対的な大小は変わりません。 
  4. 多層パーセプトロンの活性化関数としてステップ関数が使えない理由はこれです。
    ステップ関数では微分値が0になるので、重みの更新量も0になり、つまり更新されないのです。 
  5. (o_i^{(L)} - t_i)^2o_i^{(L)} で偏微分したときに現れる係数 2 が、誤差関数の定義で付与した \frac {1} {2} と打ち消し合っているので、結果的に係数が消えてシンプルになっています。
    誤差関数で \frac {1} {2} をつけたのはこのためです。 
Pocket

Comments

comments