入力層、隠れ層、出力層で構成される単純なニューラルネットワークで、
誤差逆伝播を計算します。
隠れ層の活性化関数はsigmoid、
出力層の活性化関数はsoftmaxとします。
誤差関数(損失関数)は、交差エントロピーを使用します。
それぞれの式は以下の通りです。
sigmoid:
softmax:
softmaxの交差エントロピー:
ここで、は各層の入力ベクトル、
は教師データのベクトル、
は出力層の出力ベクトルとします。
ネットワークのパラメータの行列は、
1層目を、
2層目をで表します。
行列の成分の添え字は、jが出力側のベクトル成分、iが入力側のベクトル成分を表します。
上付きの数字nは、何番目の層かを表します。
順伝播
順伝播は、入力から出力
を以下の式で計算します。
出力は、出力層の活性化関数にsoftmaxを使用しているので、ベクトルの各成分がクラスを表し、値がそのクラスに属する確率を示します。
入力が与えられたときにクラスを分類する識別器として機能します。
Pythonでは以下の通り記述します。
import numpy as np def sigmoid(u): return 1 / (1 + np.exp(-u)) def softmax(u): e = np.exp(u) return e / np.sum(e) def forward(x): global W1 global W2 u1 = x.dot(W1) z1 = sigmoid(u1) u2 = z1.dot(W2) y = softmax(u2) return y, z1
xはnumpyのarrayオブジェクトで、入力ベクトルの成分をx1,x2とすると、
x = np.array([[x1, x2]])
となります。
W1、W2はネットワークのパラメータの行列で、
W1 = np.array([[w1_11, w1_21], [w1_12, w1_22]])
のようになります。W2も同様です。
上の方で説明したを転置した形になっていることに注意してください。
これは入力ベクトルを横ベクトルで表しているため計算の都合上、転置しています。
x.dot(W1)は、xを横ベクトル、W1を転置した形で表しているので、
を計算しています。.dotは行列の積(ドット積)を表します。
同様に、z1.dot(W2)は、z1の成分を,
とすると、
を計算しています。
順伝播を使って、具体的な値で計算すると以下のようになります。
W1 = np.array([[0.1, 0.3], [0.2, 0.4]]) W2 = np.array([[0.1, 0.3], [0.2, 0.4]]) x = np.array([[1, 0.5]]) y, z1 = forward(x) print z1 print y
結果は以下の通りになります。
>>> print z1 [[ 0.549834 0.62245933]] >>> print y [[ 0.44165237 0.55834763]]
誤差逆伝播
次に誤差逆伝播を行い、ネットワークのパラメータを教師データに近づくように調整します。
教師データと順伝播の出力結果の差分を誤差関数(損失関数)で計ります。
誤差関数(損失関数)が0に近づくように調整します。
誤差関数(損失関数)は、誤差逆伝播を行うには微分が行いやすいことが条件となりますので、交差エントロピーを使います。
softmaxの交差エントロピーは、微分が引き算で計算できるという特性があります。
2層目のネットワークのパラメータの勾配
誤差関数Eをネットワークのパラメータで微分する式は、微分の連鎖則を使うと、以下の通りとなります。
式(1)の右辺の第1項は、softmaxの交差エントロピーは以下の式なので、
これを出力層の入力で微分すると、
となります。(途中の計算式は省略。詳しくは専門書を参照するなどしてください。)
式(1)の右辺の第2項は、
なので、
となります。(右辺でijが左辺と一致しない項は微分すると0になる。)
したがって、式(1)は、
となります。
行列で書き下すと以下の通りになります。
これで、2層目のネットワークのパラメータの勾配が計算できたことになります。
1層目のネットワークのパラメータの勾配
続いて、1層目のネットワークのパラメータの勾配を計算します。
先ほどの微分の連鎖則を1層目のネットワークのパラメータにも適用すると、
となります。
右辺の1項目は、がEに与える影響は、
の変化で表せることから、微分の連鎖則を使うと、
で表せます。
右辺の2項目は、であることから、
と表せます。(活性化関数をfで表している。)
ここで、
と置き換えると、
と表せます。
式(4)を一般化すると、
となります。
式(3)の右辺の第2項は、であることから、
となります。(右辺のjiが左辺のjiと一致しない項は微分すると0になる。)
したがって、式(3)は、
となり、式(4)からは、
を使って、
となります。
隠れ層の活性化関数はsigmoidで、
式(9)の微分は、
となります。
式(8)のf'を式(10)で置き換えると、式(8)は、
となります。
したがって、式(6)は、
となり、1層目のネットワークのパラメータの勾配が計算できます。
式(5)のように、前の層のから、勾配が計算できることから、誤差逆伝播と呼ばれています。
以上の通り計算した勾配に定数をかけた値で、ネットワークのパラメータを更新します。
数式で書くとややこしいですが、イメージとしては、教師データとの差分が大きければ、影響の大きかった前の層のパラメータをより多く調整するということを行っています。
Pythonでは以下の通り記述します。
def back_propagation(x, z1, y, d): global W1 global W2 delta2 = y - d grad_W2 = z1.T.dot(delta2) sigmoid_dash = z1 * (1 - z1) delta1 = delta2.dot(W2.T) * sigmoid_dash grad_W1 = x.T.dot(delta1) W2 -= learning_rate * grad_W2 W1 -= learning_rate * grad_W1
各行の解説
delta2 = y - d
は、上記の式(6)を計算しています。
grad_W2 = z1.T.dot(delta2)
は、上記の式(2)を計算しています。
z1、delta2はともに横ベクトルになっているので、z1を転置して、
を計算しています。
これで、2層目のネットワークのパラメータの勾配grad_W2が計算できました。
sigmoid_dash = z1 * (1 - z1)
は、式(10)を計算しています。
delta1 = delta2.dot(W2.T) * sigmoid_dash
は、式(11)を計算しています。
行列で書き下すと、
となります。(*は要素同士の積を計算します。)
grad_W1 = x.T.dot(delta1)
は、式(6)を計算しています。
xは横ベクトルなので転置を行っています。行列で書き下すと、
となります。
これで、1層目のネットワークのパラメータの勾配grad_W1が計算できました。
最後に、求めた勾配を使って、ネットワークのパラメータW2、W1を更新します。
W2 -= learning_rate * grad_W2 W1 -= learning_rate * grad_W1
learning_rateは、学習係数で通常は非常に小さい値(0.00001など)を使用します。
行列の演算のみで誤差逆伝播が計算できたことに注目してください。
これは演算にGPUを利用する際に重要になります。
誤差逆伝播を使って、具体的な値で計算すると以下のようになります。
W1 = np.array([[0.1, 0.3], [0.2, 0.4]]) W2 = np.array([[0.1, 0.3], [0.2, 0.4]]) learning_rate = 0.005 # 順伝播 x = np.array([[1, 0.5]]) y, z1 = forward(x) # 誤差逆伝播 d = np.array([[1, 0]]) # 教師データ back_propagation(x, z1, y, d) print W1 print W2
結果は以下の通りになります。
>>> print W1 [[ 0.0998618 0.29986879] [ 0.1999309 0.39993439]] >>> print W2 [[ 0.10153499 0.29846501] [ 0.20173774 0.39826226]]
以上で、説明は終わりです。
本来は、ネットワークのパラメータにバイアス項を加えますが、説明を簡単にするため省略しています。
数式の説明は、この本を参考にしました。
Pythonのコードは、この本を参考にしました。