はじめに
PythonとTensorflowを使ってAIを学びます。この記事では、ニューラルネットワークの基本的な概念を理解し、実装できることを目指します。Pythonは、その読みやすい構文と豊富なライブラリで、AIの分野で人気があります。TensorFlowはオープンソースの深層学習ライブラリで、機械学習モデルの開発とトレーニングに広く用いられています。サンプルコードはこちらにありますので、理論と照らし合わせながらご活用ください。この記事ではコードには触れません。
用語整理
AI(人工知能)
コンピューターが、人間のようになにかを考えてなにかをすることです。幅広い概念です。
機械学習(Machine Learning)
AIの一種です。データから学習し、新しい入力に対して予測や判断を行うことです。
深層学習(Deep Learning)
機械学習の一種です。厳密な定義はありませんが、複数の中間層を持つニューラルネットワークによる学習を指すことが多いです。
機械学習の種類
-
教師あり学習(Supervised Learning):
データとそれに対応するラベルを使用し、データからラベルを予測します。
-
教師なし学習(Unsupervised Learning):
ラベルのないデータを使用し、データの構造やパターンを発見します。
-
強化学習(Reinforcement Learning):
試行錯誤をして、報酬を最大化するような行動を学習します。
このスクラップでは、教師あり学習のみ扱います。
全結合層
最も基本的なニューラルネットワークが全結合層です。前の層のすべてのノードが次の層のすべてのノードに接続されている層です。
以下では、全結合層がL層重なっているニューラルネットワークを考えます。
変数の定義
スカラー
x_i : i 番目の入力
z_i^l : l 層目の i 番目の加重総和
a_i^l : l 層目の i 番目の出力
w_{ij}^l : l - 1 層目の j 番目の出力から l 層目の i 番目の加重総和への重み
b_i^l : l 層目の i 番目のバイアス
f^l : l 層目の活性化関数
y_i : 教師データ
E : 損失関数
行列とベクトル
\boldsymbol{x} : 入力ベクトル
\boldsymbol{z}^l : l 層目の加重総和ベクトル
\boldsymbol{a}^l : l 層目の出力ベクトル
\boldsymbol{W}^l : l 層目の重み行列
\boldsymbol{b}^l : l 層目のバイアスベクトル
\boldsymbol{y} : 教師データベクトル
順伝播(フォワードプロパゲーション)
1層目
1層目(入力層)の計算をベクトルと行列で表すと以下のようになります。ベクトルはすべて列ベクトルとし、重みを左からかけます。
\begin{aligned}
\boldsymbol{z}^1 &= \boldsymbol{W}^1 \boldsymbol{x} + \boldsymbol{b}^1\\
\boldsymbol{a}^1 &= f^1(\boldsymbol{z}^1)
\end{aligned}
ベクトルの各要素を個別に計算すると以下のようになります。
\begin{aligned}
\left[\begin{array}{c}
z_1^1\\
z_2^1\\
\vdots\\
\end{array}\right]
&=
\left[\begin{array}{ccc}
w_{11}^1 & w_{12}^1 & \cdots\\
w_{21}^1 & w_{22}^1 & \cdots\\
\vdots & \vdots & \ddots
\end{array}\right]
\left[\begin{array}{c}
x_1\\
x_2\\
\vdots\\
\end{array}\right]
+
\left[\begin{array}{c}
b_1^1\\
b_2^1\\
\vdots\\
\end{array}\right]
\end{aligned}
\begin{aligned}
\left[\begin{array}{c}
a_1^1\\
a_2^1\\
\vdots\\
\end{array}\right]
&=
\left[\begin{array}{c}
f^1(z_1^1)\\
f^1(z_2^1)\\
\vdots\\
\end{array}\right]
\end{aligned}
\begin{aligned}
z_i^1 &= \sum_j w_{ij}^1 x_j + b_i^1\\
a_i^1 &= f^1(z_i^1)
\end{aligned}
例えば、入力を5次元ベクトル、1層目の出力を3次元ベクトルとすると、以下のようになります。
\begin{aligned}
\left[\begin{array}{c}
z_1^1\\
z_2^1\\
z_3^1\\
\end{array}\right]
&=
\left[\begin{array}{ccc}
w_{11}^1 & w_{12}^1 & w_{13}^1 & w_{14}^1 & w_{15}^1\\
w_{21}^1 & w_{22}^1 & w_{23}^1 & w_{24}^1 & w_{25}^1\\
w_{31}^1 & w_{32}^1 & w_{33}^1 & w_{34}^1 & w_{35}^1
\end{array}\right]
\left[\begin{array}{c}
x_1\\
x_2\\
x_3\\
x_4\\
x_5
\end{array}\right]
+
\left[\begin{array}{c}
b_1^1\\
b_2^1\\
b_3^1
\end{array}\right]
\end{aligned}
\begin{aligned}
z_1^1 &= w_{11}^1 x_1 + w_{12}^1 x_2 + w_{13}^1 x_3 + w_{14}^1 x_4 + w_{15}^1 x_5 + b_1^1\\
z_2^1 &= w_{21}^1 x_1 + w_{22}^1 x_2 + w_{23}^1 x_3 + w_{24}^1 x_4 + w_{25}^1 x_5 + b_2^1\\
z_3^1 &= w_{31}^1 x_1 + w_{32}^1 x_2 + w_{33}^1 x_3 + w_{34}^1 x_4 + w_{35}^1 x_5 + b_3^1
\end{aligned}
\begin{aligned}
a_1^1 &= f^1(z_1^1)\\
a_2^1 &= f^1(z_2^1)\\
a_3^1 &= f^1(z_3^1)
\end{aligned}
l 層目 (2層目以降)
ベクトルと行列で表すと以下のようになります。1層目との違いは、入力が前の層の出力になっていることだけですね。
\begin{aligned}
\boldsymbol{z}^l &= \boldsymbol{W}^l \boldsymbol{a}^{l - 1} + \boldsymbol{b}^l\\
\boldsymbol{a}^l &= f^l(\boldsymbol{z}^l)
\end{aligned}
ベクトルの各要素を個別に計算すると以下のようになります。
\begin{aligned}
\left[\begin{array}{c}
z_1^l\\
z_2^l\\
\vdots\\
\end{array}\right]
&=
\left[\begin{array}{ccc}
w_{11}^l & w_{12}^l & \cdots\\
w_{21}^l & w_{22}^l & \cdots\\
\vdots & \vdots & \ddots
\end{array}\right]
\left[\begin{array}{c}
a_1^{l - 1}\\
a_2^{l - 1}\\
\vdots\\
\end{array}\right]
+
\left[\begin{array}{c}
b_1^l\\
b_2^l\\
\vdots\\
\end{array}\right]
\end{aligned}
\begin{aligned}
\left[\begin{array}{c}
a_1^l\\
a_2^l\\
\vdots\\
\end{array}\right]
&=
\left[\begin{array}{c}
f^l(z_1^l)\\
f^l(z_2^l)\\
\vdots\\
\end{array}\right]
\end{aligned}
\begin{aligned}
z_i^l &= \sum_j w_{ij}^l a_j^{l - 1} + b_i^l\\
a_i^l &= f^l(z_i^l)
\end{aligned}
これを最後の層まで繰り返すことで、ニューラルネットワーク全体の出力が得られます。
活性化関数
活性化関数の役割
活性化関数は、ニューラルネットワークの各層での出力を決定する重要な関数です。この関数がなければ、ネットワークは単なる一次変換の連続となり、複雑な問題を解決する能力を持ちません。
ReLU関数
今回用いる活性化関数は、ReLU(Rectified Linear Unit)関数です。ReLU関数は、以下のように定義されます。
f^l(z_i^l) = \max(0, z_i^l) =
\begin{cases}
z_i^l & (z_i^l \geq 0)\\
0 & (z_i^l < 0)
\end{cases}
この関数は、入力が0以上の場合はその値をそのまま出力し、0未満の場合は0を出力します。
他の活性化関数
他にもステップ関数やシグモイド関数などがありますが、それぞれ以下の図に示される特徴を持ちます。
ReLU関数だけ出力の範囲が異なることに注意してください。
活性化関数の比較グラフ
各活性化関数の入力と出力の関係を以下の棒グラフで比較します。ここでは、-10以上10以下の一様乱数を入力としています。
左上が入力、右上がステップ関数の出力、左下がシグモイド関数の出力、右下がReLU関数の出力です。それぞれの特徴をよく観察してみてください。
活性化関数がない場合のニューラルネットワーク
活性化関数がない場合の順伝播の式を考えてみましょう。以下は、2層のニューラルネットワークの順伝播の式です。
\begin{aligned}
a^1 &= z^1 = W^1 x + b^1\\
a^2 &= z^2 = W^2 a^1 + b^2
\end{aligned}
a^2の式にa^1を代入すると以下のようになります。
\begin{aligned}
a^2 &= W^2 (W^1 x + b^1) + b^2\\
&= (W^2 W^1) x + (W^2 b^1 + b^2)
\end{aligned}
ここで、W^\prime = W^2 W^1、b^{\prime} = W^2 b^1 + b^2 とすると、以下のようになります。
a^2 = W^\prime x + b^{\prime}
活性化関数がなければ、どんなに層を重ねても、ニューラルネットワークは一つの行列とバイアスの組み合わせに過ぎず、複雑な関数を表現できません。
誤差逆伝播法(バックプロパゲーション)
順伝播で入力に対する出力が得られましたが、最初の重みはランダムな値に設定されているので、出力もランダムになっています。そのため、重みを調整して正解に近づける必要があります。重みを調整するステップは以下のようになります。
- 順伝播で出力を計算する
- 出力と教師データの誤差を計算する
- 誤差を重みで偏微分する
- 偏微分した値を使って重みを更新する
これは、最適化問題を解くときによく使われる勾配降下法と同じです。しかし、中間層の重みに関する誤差の微分(重みを変化させたときに誤差がどれだけ変化するか)を計算するためには、後ろの層から前の層に向かって偏微分を計算する必要があります。順伝播の計算を逆向きに辿っていくため、誤差逆伝播法と呼ばれます。
以下の動画が参考になります。
損失関数
現在のニューラルネットワークがどれだけ正解から離れているかを表す関数を損失関数と呼びます。以下では、代表的な損失関数を2つ紹介します。
二乗和誤差
出力の各要素と教師データの各要素の差の二乗を合計したものです。回帰問題においてよく使用されるこの手法は、空間距離の計算に似ており、直感的に理解しやすいです。1/2 をかけているのは、微分したときに 2 が打ち消されるようにするためです。
E = \frac{1}{2} \sum_i (y_i - a_i^L)^2
交差エントロピー誤差
予測された確率分布と実際の確率分布がどの程度異なるかを測る尺度です。分類問題では、教師データは「ワンホットベクトル」として表されます。ワンホットベクトルは、正解ラベルのインデックスのみ1で、それ以外は0であるようなベクトルです。このベクトルは、正解ラベルが100%の確率で正しいという確率分布を示しています。
交差エントロピー誤差は、以下のように定義されます。
E = - \sum_i y_i \ln{a_i^L}
見慣れない数式かと思いますが、この式はy_iを重み、-\ln{a_i^L}をペナルティと解釈できます。a_i^Lは0以上1以下の値をとりますので、\ln{a_i^L}は負の値をとります。そして、その絶対値はa_i^Lが0に近づくほど大きくなります。つまり、教師データのあるラベル(y_i)が高い確率であるにも関わらず、予測の確率(a_i^L)が低い場合、大きなペナルティが加算されます。
教師の確率分布をワンホットベクトルに限ってしまえば、この式は正解ラベルの予測確率の対数を取るだけの簡単な式になります。例えばi番目が正解ラベルの場合、以下のようになります。
以下では、分類問題を例に説明するため、損失関数は交差エントロピー誤差を使います。
出力層の活性化関数
分類問題において、出力層の活性化関数は特別な役割を担います。分類問題では、入力データが特定のクラスに属する確率を予測することが目的です。出力を確率分布として解釈するためには、以下の二つの性質が必要です。
- 各要素の出力が0以上であること。
- 出力の総和が1であること。
これらの条件を満たす代表的な関数がソフトマックス関数です。今回、出力層の活性化関数には、ソフトマックス関数を使うことにします。ソフトマックス関数は、ベクトルを入力とし、各要素に対して以下の式を適用します。
a_i^L = \frac{\exp{z_i^L}}{\sum_j \exp{z_j^L}}
ソフトマックス関数の定義
ソフトマックス関数はベクトル全体に適用され、以下のように定義されます。
softmax(\bold{z}^L) = \left[\begin{array}{c}
\frac{\exp{z_1^L}}{Z}\\
\frac{\exp{z_2^L}}{Z}\\
\vdots\\
\end{array}\right]
ここで、Zはすべての要素の指数関数の和で、以下のように計算されます。
\begin{aligned}
Z &= \sum_j \exp{z_j^L} \\
&= \exp{z_1^L} + \exp{z_2^L} + \cdots + \exp{z_i^L} + \cdots
\end{aligned}
ソフトマックス関数の特徴
ソフトマックス関数は、各要素の指数関数を取り、それらの総和で割ることで、出力を正規化します。以下のグラフは、-10以上10以下の一様乱数を入力とした場合のソフトマックス関数の出力を示しています。また、すべての入力に100を加えた場合の出力も示しています。
このグラフから、ソフトマックス関数が大きい値をさらに大きくし、小さい値をさらに小さくする性質が見て取れます。また、すべての出力の総和が1になるため、これを確率分布として解釈することができます。
定数の加算とソフトマックス関数
興味深い点として、すべての入力に定数を加えてもソフトマックス関数の出力は変わりません。これは指数関数の性質から導くことができます。
\begin{aligned}
\frac{\exp{(z_i^L + c)}}{\sum_j \exp{(z_j^L + c)}} &= \frac{\exp{z_i^L} \exp{c}}{\sum_j \exp{z_j^L} \exp{c}}\\
&= \frac{\exp{z_i^L}}{\sum_j \exp{z_j^L}}\\
\end{aligned}
この性質は、ソフトマックス関数が入力のスケールに依存せず、相対的な大小関係にのみ影響を受けることを意味します。
出力に関する損失関数の偏微分
計算式が揃ったので、後ろから順番に偏微分を計算していきます。まず、交差エントロピーを i 番目の出力で偏微分すると以下のようになります。
\frac{\partial E}{\partial a_i^L} = - \frac{y_i}{a_i^L}
ソフトマックス関数の偏微分
ソフトマックス関数は、ひとつの加重総和がすべての出力に影響を与えます。加重総和の添え字と出力の添え字が一致する場合と一致しない場合で結果が異なるため、場合分けして計算します。
i \neq j のとき
\begin{aligned}
\frac{\partial a_i^L}{\partial z_j^L}
&= \frac{0 \cdot Z - \exp{z_i^L} \cdot \exp{z_j^L}}{Z^2}\\
&= - \frac{\exp{z_i^L}}{Z} \cdot \frac{\exp{z_j^L}}{Z}\\
&= - a_i^L a_j^L
\end{aligned}
i = j のとき
\begin{aligned}
\frac{\partial a_i^L}{\partial z_i^L}
&= \frac{\exp{z_i^L} \cdot Z - \exp{z_i^L} \cdot \exp{z_i^L}}{Z^2}\\
&= \frac{\exp{z_i^L}}{Z} \cdot \frac{Z - \exp{z_i^L}}{Z}\\
&= a_i^L (1 - a_i^L)
\end{aligned}
これらをまとめると以下の式で表せます。
\frac{\partial a_i^L}{\partial z_j^L} = a_i^L (\delta_{ij} - a_j^L)
ここで、\delta_{ij} はクロネッカーのデルタと呼ばれ、i = j のとき 1、それ以外のとき 0 となる関数です。
\delta_{ij} = \begin{cases}
1 & (i = j)\\
0 & (i \neq j)
\end{cases}
出力層の加重総和に関する損失関数の偏微分
連鎖率を使うことで、i 番目の加重総和に関する損失関数の偏微分を計算できます。ソフトマックス関数の特徴から、z_i はすべての出力に影響を与えるため、a_1を介した偏微分、a_2を介した偏微分、\cdotsをすべて足し合わせます。
\begin{aligned}
\frac{\partial E}{\partial z_i^L}
&= \sum_j \frac{\partial E}{\partial a_j^L} \frac{\partial a_j^L}{\partial z_i^L}\\
&= \frac{\partial E}{\partial a_i^L} \frac{\partial a_i^L}{\partial z_i^L} + \sum_{\substack{j\\j \neq i}} \frac{\partial E}{\partial a_j^L}\frac{\partial a_j^L}{\partial z_i^L}\\
&= - \frac{y_i}{a_i^L} a_i^L (1 - a_i^L) + \sum_{\substack{j\\j \neq i}} - \frac{y_j}{a_j^L} (- a_i^L a_j^L)\\
&= - y_i (1 - a_i^L) + \sum_{\substack{j\\j \neq i}} y_j a_i^L\\
&= - y_i + y_i a_i^L + a_i^L \sum_{\substack{j\\j \neq i}} y_j\\
&= - y_i + a_i^L \sum_j y_j\\
&= a_i^L - y_i
\end{aligned}
ここで、\sum_j y_j = 1 であることを利用しました。これは、教師データがワンホットベクトルであることから導かれます。複雑に見えた式がシンプルになりましたね。
これをベクトルで表すと以下のようになります。
\begin{aligned}
\frac{\partial E}{\partial \boldsymbol{z}^L}
&=
\left[\begin{array}{c}
\frac{\partial E}{\partial z_1^L}\\
\frac{\partial E}{\partial z_2^L}\\
\vdots\\
\end{array}\right]
=
\left[\begin{array}{c}
a_1^L - y_1\\
a_2^L - y_2\\
\vdots\\
\end{array}\right]\\
&= \boldsymbol{a}^L - \boldsymbol{y}
\end{aligned}
出力層の重み、バイアス、入力に関する加重総和の偏微分
最終的に求めたいのは、重みとバイアスに関する損失関数の偏微分です。しかし、重みとバイアスは損失関数に直接影響を与えないため、先に加重総和に関する偏微分を計算します。順伝播の計算式を思い出してください。
\begin{aligned}
\frac{\partial z_i^L}{\partial w_{ij}^L}
&= \frac{\partial}{\partial w_{ij}^L} \left( \sum_k w_{ik}^L a_k^{L - 1} + b_i^L \right)\\
&= a_j^{L - 1}\\
\frac{\partial z_i^L}{\partial b_i^L}
&= \frac{\partial}{\partial b_i^L} \left( \sum_k w_{ik}^L a_k^{L - 1} + b_i^L \right)\\
&= 1
\end{aligned}
出力層の入力(ひとつ前の層の出力)に関する偏微分も計算しておきます。これは、中間層の偏微分を計算するときに使います。
\begin{aligned}
\frac{\partial z_i^L}{\partial a_j^{L - 1}}
&= \frac{\partial}{\partial a_j^{L - 1}} \left( \sum_k w_{ik}^L a_k^{L - 1} + b_i^L \right)\\
&= w_{ij}^L
\end{aligned}
出力層の重み、バイアス、入力に関する損失関数の偏微分
ようやく重みに関する損失関数の偏微分を計算できるようになりました。さらに連鎖率を用いて計算します。このとき、w_{ij}^L は z_i^L にのみ影響を与えるため、z_i^L を介した偏微分のみを計算します。
\begin{aligned}
\frac{\partial E}{\partial w_{ij}^L}
&= \frac{\partial E}{\partial z_i^L} \frac{\partial z_i^L}{\partial w_{ij}^L}\\
&= (a_i^L - y_i) a_j^{L - 1}
\end{aligned}
バイアスに関する偏微分も同様に計算できます。
\begin{aligned}
\frac{\partial E}{\partial b_i^L}
&= \frac{\partial E}{\partial z_i^L} \frac{\partial z_i^L}{\partial b_i^L}\\
&= a_i^L - y_i
\end{aligned}
これで出力層の重みとバイアスに関する損失関数の偏微分がわかったので、出力層の重みとバイアスを更新できます。
しかし、中間層の重みとバイアスも更新する必要があります。後に必要になるので、ひとつ前の層の出力に関する偏微分も計算しておきます。各出力はすべての加重総和に影響を与えるため、z_1^L を介した偏微分、z_2^L を介した偏微分、\cdotsをすべて足し合わせます。
\begin{aligned}
\frac{\partial E}{\partial a_j^{L - 1}}
&= \sum_i \frac{\partial E}{\partial z_i^L} \frac{\partial z_i^L}{\partial a_j^{L - 1}}\\
&= \sum_i (a_i^L - y_i) w_{ij}^L
\end{aligned}
上記の重み、バイアス、入力に関する偏微分を、それぞれベクトルと行列で表すと以下のようになります。
プログラムでは行列演算を使うことで、簡潔に書くことができます。
\begin{aligned}
\frac{\partial E}{\partial \boldsymbol{W}^L}
&=
\left[\begin{array}{ccc}
\frac{\partial E}{\partial w_{11}^L} & \frac{\partial E}{\partial w_{12}^L} & \cdots\\
\frac{\partial E}{\partial w_{21}^L} & \frac{\partial E}{\partial w_{22}^L} & \cdots\\
\vdots & \vdots & \ddots
\end{array}\right]
=
\left[\begin{array}{c}
a_1^L - y_1\\
a_2^L - y_2\\
\vdots\\
\end{array}\right]
\left[\begin{array}{ccc}
a_1^{L - 1} & a_2^{L - 1} & \cdots\\
\end{array}\right]\\
&= \frac {\partial E}{\partial \boldsymbol{z}^L} (\boldsymbol{a}^{L - 1})^T\\
\frac{\partial E}{\partial \boldsymbol{b}^L}
&=
\left[\begin{array}{c}
\frac{\partial E}{\partial b_1^L}\\
\frac{\partial E}{\partial b_2^L}\\
\vdots\\
\end{array}\right]
=
\left[\begin{array}{c}
a_1^L - y_1\\
a_2^L - y_2\\
\vdots\\
\end{array}\right]\\
&= \frac {\partial E}{\partial \boldsymbol{z}^L}\\
\frac{\partial E}{\partial \boldsymbol{a}^{L - 1}}
&=
\left[\begin{array}{c}
\frac{\partial E}{\partial a_1^{L - 1}}\\
\frac{\partial E}{\partial a_2^{L - 1}}\\
\vdots\\
\end{array}\right]
=
\left[\begin{array}{ccc}
w_{11}^L & w_{21}^L & \cdots\\
w_{12}^L & w_{22}^L & \cdots\\
\vdots & \vdots & \ddots
\end{array}\right]
\left[\begin{array}{c}
a_1^L - y_1\\
a_2^L - y_2\\
\vdots\\
\end{array}\right]\\
&=
(\boldsymbol{W}^L)^T \frac {\partial E}{\partial \boldsymbol{z}^L}
\end{aligned}
ReLU関数の偏微分
ここから中間層の偏微分を計算していきます。活性化関数にはReLU関数を使うので、ReLU関数の偏微分を計算します。
\frac{\partial a_i^l}{\partial z_i^l} = \begin{cases}
1 & (z_i^l > 0)\\
0 & (z_i^l \leq 0)
\end{cases}
z_i^l = 0 では本来微分は定義できませんが、今回は 0 とします。z_i^l が 0 になることはほとんどないので、実装上の影響は気にしなくても大丈夫です。
条件分岐が式に含まれると煩雑になるので、活性化関数の微分f^\prime(z_i^l)を以下のように定義しておきます。
f^\prime(z_i^l) = \begin{cases}
1 & (z_i^l > 0)\\
0 & (z_i^l \leq 0)
\end{cases}
ベクトルの場合は以下のように定義します。各要素にf^\primeを適用しているだけです。
{f}^\prime(\boldsymbol{z}^l) = \left[\begin{array}{c}
f^\prime(z_1^l)\\
f^\prime(z_2^l)\\
\vdots\\
\end{array}\right]
中間層の加重総和に関する損失関数の偏微分
ReLU関数はソフトマックス関数と異なり、ある加重総和に対して、ひとつの出力にしか影響を与えません。そのため、連鎖率で偏微分を計算するときは、対応する出力のみを考慮すればよいです。
\begin{aligned}
\frac{\partial E}{\partial z_i^l} &= \frac{\partial E}{\partial a_i^l} \frac{\partial a_i^l}{\partial z_i^l}\\
&= \frac{\partial E}{\partial a_i^l} f^\prime(z_i^l)
\end{aligned}
ベクトルで表すと以下のようになります。\odot は要素ごとの積を表します。
\begin{aligned}
\frac{\partial E}{\partial \boldsymbol{z}^l} &= \left[\begin{array}{c}
\frac{\partial E}{\partial z_1^l}\\
\frac{\partial E}{\partial z_2^l}\\
\vdots\\
\end{array}\right] = \left[\begin{array}{c}
\frac{\partial E}{\partial a_1^l} f^\prime(z_1^l)\\
\frac{\partial E}{\partial a_2^l} f^\prime(z_2^l)\\
\vdots\\
\end{array}\right]\\
&= \frac{\partial E}{\partial \boldsymbol{a}^l} \odot f^\prime(\boldsymbol{z}^l)
\end{aligned}
中間層の重み、バイアス、入力に関する損失関数の偏微分
連鎖率を使うと、中間層の重みとバイアスの偏微分は以下のようになります。
\begin{aligned}
\frac{\partial E}{\partial w_{ij}^l}
&= \frac{\partial E}{\partial z_i^l} \frac{\partial z_i^l}{\partial w_{ij}^l}\\
&= \frac{\partial E}{\partial a_i^l} f^\prime(z_i^l) a_j^{l - 1}\\
\frac{\partial E}{\partial b_i^l}
&= \frac{\partial E}{\partial z_i^l} \frac{\partial z_i^l}{\partial b_i^l}\\
&= \frac{\partial E}{\partial a_i^l} f^\prime(z_i^l)
\end{aligned}
ひとつ前の層の出力に関する偏微分は以下のようになります。連鎖率を使うときは、どの変数を介して損失関数に影響を与えるかに注意してください。
\begin{aligned}
\frac{\partial E}{\partial a_j^{l - 1}}
&= \sum_i \frac{\partial E}{\partial z_i^l} \frac{\partial z_i^l}{\partial a_j^{l - 1}}\\
&= \sum_i \frac{\partial E}{\partial a_i^l} f^\prime(z_i^l) w_{ij}^l
\end{aligned}
ここで、\frac{\partial E}{\partial a_i^{L-1}} が計算済みであることを思い出してください。この式は、l - 1 層目の出力の偏微分が、l 層目の出力の偏微分から計算できることを示しています。後ろから順番に計算していくことで、すべての層の出力に関する偏微分を計算できます。これを使用して、すべての層の重みとバイアスに関する偏微分も計算できます。
行列とベクトルを使って表すと以下のようになります。
\begin{aligned}
\frac{\partial E}{\partial \boldsymbol{W}^l}
&=
\left[\begin{array}{ccc}
\frac{\partial E}{\partial w_{11}^l} & \frac{\partial E}{\partial w_{12}^l} & \cdots\\
\frac{\partial E}{\partial w_{21}^l} & \frac{\partial E}{\partial w_{22}^l} & \cdots\\
\vdots & \vdots & \ddots
\end{array}\right]
=
\left[\begin{array}{c}
\frac{\partial E}{\partial z_1^l}\\
\frac{\partial E}{\partial z_2^l}\\
\vdots\\
\end{array}\right]
\left[\begin{array}{ccc}
a_1^{l - 1} & a_2^{l - 1} & \cdots\\
\end{array}\right]\\
&= \frac {\partial E}{\partial \boldsymbol{z}^l} (\boldsymbol{a}^{l - 1})^T\\
&= \left(\frac{\partial E}{\partial \boldsymbol{a}^l} \odot f^\prime(\boldsymbol{z}^l)\right) (\boldsymbol{a}^{l - 1})^T\\
\frac{\partial E}{\partial \boldsymbol{b}^l}
&=
\left[\begin{array}{c}
\frac{\partial E}{\partial b_1^l}\\
\frac{\partial E}{\partial b_2^l}\\
\vdots\\
\end{array}\right]
=
\left[\begin{array}{c}
\frac{\partial E}{\partial z_1^l}\\
\frac{\partial E}{\partial z_2^l}\\
\vdots\\
\end{array}\right]\\
&= \frac {\partial E}{\partial \boldsymbol{z}^l}\\
&= \frac{\partial E}{\partial \boldsymbol{a}^l} \odot f^\prime(\boldsymbol{z}^l)\\
\frac{\partial E}{\partial \boldsymbol{a}^{l - 1}}
&=
\left[\begin{array}{c}
\frac{\partial E}{\partial a_1^{l - 1}}\\
\frac{\partial E}{\partial a_2^{l - 1}}\\
\vdots\\
\end{array}\right]
=
\left[\begin{array}{ccc}
w_{11}^l & w_{21}^l & \cdots\\
w_{12}^l & w_{22}^l & \cdots\\
\vdots & \vdots & \ddots
\end{array}\right]
\left[\begin{array}{c}
\frac{\partial E}{\partial z_1^l}\\
\frac{\partial E}{\partial z_2^l}\\
\vdots\\
\end{array}\right]\\
&=
(\boldsymbol{W}^l)^T \frac {\partial E}{\partial \boldsymbol{z}^l}\\
&= (\boldsymbol{W}^l)^T \left(\frac{\partial E}{\partial \boldsymbol{a}^l} \odot f^\prime(\boldsymbol{z}^l)\right)
\end{aligned}
まとめ
この記事では、全結合層の概要と、誤差逆伝播法の理屈を長々と説明しました。抽象的な数式が多く、分かりづらかったかもしれません。肝心のパラメータを更新する方法が抜けてますが、以下の記事を参考にしていただけると幸いです。
また、サンプルコードには勾配降下法とAdamを使ったコードが含まれていますので、こちらもぜひ参考にしてください。
Discussion