Ccmmutty logo
Commutty IT
0 pv29 min read

03 ニューラルネットワークの基礎

https://picsum.photos/seed/dd4230dc267a458ab53dc81c9776bde3/1200/630
ここでは,ニューラルネットワーク (Neural Network) についてその概要を紹介していきます.画像認識などに用いられる Convolutional Neural Network (CNN) や,自然言語処理などに用いられる Recurrent Neural Network (RNN) といった手法は,ニューラルネットワークの一種です.
ここではまず,最もシンプルな全結合型と呼ばれるニューラルネットワークの構造について説明を行ったあと,複数の入力データと望ましい出力の組からなる学習用データセットを準備したとき,どうやってニューラルネットワークを学習させればよいのか(教師あり学習の仕組み)について解説を行います.
ニューラルネットワークによって表現される複雑な関数を,現実的な時間で学習するための誤差逆伝播法(バックプロパゲーション)と呼ばれるアルゴリズムについても紹介します.
まずはニューラルネットワークをブラックボックスとして扱ってしまうのではなく,一つ一つ内部で行われる計算を丁寧に調べます.そして,パラメータで特徴づけられた関数で表される線形変換とそれに続く非線形変換を組み合わせて,全体として微分可能な一つの関数を表していることを理解していきます.

ニューラルネットワークの構造

まずはニューラルネットワークの構造を図式化して見てみましょう.入力変数が{年数,アルコール度数,色合い,匂い}の4変数,出力変数が{白ワイン,赤ワイン}の2変数の場合を示します.
ニューラルネットワークの基本構造
この図のひとつひとつの丸い部分のことをノードもしくはユニットと呼び,その縦方向の集まりをと呼びます.そして,一番初めの層を入力層(input layer),最後の層を出力層(output layer),そしてその間を**中間層(intermediate layer)もしくは隠れ層(hidden layer)**と呼びます.このモデルは入力層,中間層,出力層の3層の構造となっていますが,中間層の数を増やすことでさらに多層のニューラルネットワークを定義することもできます.この例では各層間の全てのノードが互いに結合されているため,全結合型のニューラルネットワークとも呼び,ニューラルネットワークの最も基礎的な構造です.
入力変数は前章までと同様ですが,出力変数の扱い方がこれまでと異なります.例えば,上図では出力層の各ノードがそれぞれ白ワインと赤ワインに対応しており,カテゴリの数だけ出力の変数があるということになります.なぜこのような構造となっているのでしょうか.
ニューラルネットワークの出力値
まず,最終層にどのような値が入るのか,具体例を見てみましょう.例えば,年数が3年物でアルコール度数が14度,色合いが0.2,匂いが0.8で表されるワインがあるとします.内部の計算は後述するとして,このようなデータをニューラルネットワークに与えたときに結果として得られる値に着目してみましょう.上図では,白ワイン y1=0.15y_{1} = 0.15, 赤ワイン y2=0.85y_{2}= 0.85 となっています.このとき,出力値の中で最も大きな値となっている変数に対応するクラス,すなわち今回の例では「赤ワイン」をこの分類問題におけるこのニューラルネットワークの予測結果とすることができます.
ここで出力層の全ての値を合計してみると,1になっていることに気づきます.これは偶然ではなく,そうなるように出力層の値を計算しているためです* .つまり,出力層のそれぞれのノードが持つ数値は,入力がそれぞれのクラスに属している確率を表していたのでした.そのため,カテゴリ数と同じ数だけ出力層にはノードが必要となります.
それでは,ここからニューラルネットワークの内部で行われる計算を詳しく見ていきましょう.ニューラルネットワークの各層は,前の層の値に線形変換と非線形変換を順番に施すことで計算されています.まずは,ここで言う線形変換とは何を表すのか,から見ていきましょう.
* 具体的には,Softmax関数という活性化関数(これも後述します)をニューラルネットワークの出力ベクトルに適用することで,出力層における全ノードの値の合計が1になるようにします.

線形変換

ここでは,ニューラルネットワークの各層で行われる線形変換について説明します.
ニューラルネットワークの線形変換
ここで言う線形変換* とは,重み行列 (W{\bf W}) ×\times 入力ベクトル (h{\bf h}) ++ バイアスベクトル (b{\bf b}) のような計算のことを指しています.このとき,この変換の入力がh{\bf h},パラメータがW{\bf W}b{\bf b}となります.ここでの掛け算(×\times)は行列の掛け算であることに注意してください.また,これからは,hh が文字としてよく登場しますが,これは隠れ層 (hidden layer) の頭文字である hh から来ています.ただし,表記を簡潔にするため以下では入力層(上図における x1,x2,x3,x4x_1, x_2, x_3, x_4)を0層目の隠れ層と考えることにして, h01,h02,h03,h04h_{01}, h_{02}, h_{03}, h_{04} と表記します.では上図で表される計算を数式で記述してみましょう.
(* 通常数学では線形変換とは w×h{\bf w} \times {\bf h} のことを指し,この変換は厳密には「アファイン変換(もしくは アフィン変換)」と呼ばれるものです.しかし,深層学習の文脈ではこの変換も線形変換と呼ぶことも多いです.)
u11=w11h01+w12h02+w13h03+w14h04+b1u12=w21h01+w22h02+w23h03+w24h04+b2u13=w31h01+w32h02+w33h03+w34h04+b3 \begin{aligned} u_{11}&=w_{11}h_{01}+w_{12}h_{02}+w_{13}h_{03}+w_{14}h_{04}+b_{1} \\ u_{12}&=w_{21}h_{01}+w_{22}h_{02}+w_{23}h_{03}+w_{24}h_{04}+b_{2} \\ u_{13}&=w_{31}h_{01}+w_{32}h_{02}+w_{33}h_{03}+w_{34}h_{04}+b_{3} \end{aligned}
バイアス(b1,b2,b3b_1, b_2, b_3)は上図では省略されていることに注意してください.さて,以上の4つの式は,ベクトルと行列の計算として以下のように書き直すことができます.
[u11u12u13]=[w11w12w13w14w21w22w23w24w31w32w33w34][h01h02h03h04]+[b1b2b3]u1=Wh0+b \begin{aligned} \begin{bmatrix} u_{11} \\ u_{12} \\ u_{13} \end{bmatrix}&=\begin{bmatrix} w_{11} & w_{12} & w_{13} & w_{14} \\ w_{21} & w_{22} & w_{23} & w_{24} \\ w_{31} & w_{32} & w_{33} & w_{34} \end{bmatrix}\begin{bmatrix} h_{01} \\ h_{02} \\ h_{03} \\ h_{04} \end{bmatrix}+\begin{bmatrix} b_{1} \\ b_{2} \\ b_{3} \end{bmatrix}\\ {\bf u}_{1}&={\bf W}{\bf h}_{0}+{\bf b} \end{aligned}
本来は W{\bf W}b{\bf b} にも,どの層とどの層の間の計算に用いられるものなのかを表す添え字をつけるべきですが,ここでは簡単のため省略しています.

非線形変換

次に,非線形変換について説明します.線形変換のみでは,下図右のように入力と出力の間が非線形な関係である場合,両者の間の関係を適切に表現することができません.
入力と出力の関係
そこで,ニューラルネットワークでは各層で線形変換に引き続いて非線形変換を施すことで,全体の関数が非線形性を持つようにしています.この非線形変換を行う関数を,ニューラルネットワークの文脈においては 活性化関数 と呼びます.
上図の線形変換の結果 u11,u12,u13u_{11}, u_{12}, u_{13} に活性化関数を使って非線形変換を行った結果を h11,h12,h13h_{11}, h_{12}, h_{13} と書き,これらを活性値(activation)と呼びます(下図参照).これが次の層への入力となります.
活性値
活性化関数の具体例としては,下図に示す ロジスティックシグモイド関数(以下シグモイド関数)
シグモイド関数
が従来,よく用いられてきました.しかし近年,層数が多いニューラルネットワークではシグモイド関数は活性化関数としてほとんど用いられていません.その理由の一つは,シグモイド関数を活性化関数に採用することで 勾配消失 という現象が起きやすくなり,学習が進行しなくなる問題が発生することがあったためです.これは後で詳述します.これを回避するために,Rectified Linear Unit (ReLU) という関数がよく用いられます.これは,以下のような形をした関数です.
ReLU関数
ここで, max(0,u){\rm max}(0, u) は,00uuを比べて大きな方を返す関数です.すなわち,ReLUは入力が負の値の場合には出力は0で一定であり,正の値の場合は入力をそのまま出力するという関数です.シグモイド関数では,入力が小さな,もしくは大きな値をとった際に,勾配がどんどん小さくなってしまうだろうことがプロットからも見て取れます.それに対し,ReLU関数は入力の値がいくら大きくなっても,一定の勾配が発生します.これがのちほど紹介する勾配消失という問題に有効に働きます.

数値を見ながら計算の流れを確認

ここで,下図に書き込まれた具体的な数値を使って,入力 x1,x2,x3x_1, x_2, x_3 から出力 yy が計算される過程を確認してみましょう.今は計算を簡略化するためバイアス b{\bf b} の計算は省略します(バイアスが全て0であるとします).数値例として,x=[231]T{\bf x} = \begin{bmatrix} 2 & 3 & 1 \end{bmatrix}^T が与えられた時の出力 yy の計算手順を一つ一つ追いかけてみましょう.
出力までの計算例
前章で解説した重回帰分析では,目的関数のパラメータについての導関数を0とおいて解析的に最適なパラメータを計算できましたが,ニューラルネットワークでは一般的に,解析的にパラメータを解くことはできません.その代わり,この導関数の値(勾配)を利用した別の方法でパラメータを逐次的に最適化していきます.
このため,ニューラルネットワークの場合は,まずパラメータを乱数で初期化し,ひとまずデータを入力して目的関数の値を計算します.次にその関数の勾配を計算して,それを利用してパラメータを更新し,その更新後の新しいパラメータを使って再度入力データを処理して目的関数の値を計算し…といったことを繰り返し行っていくことになります.
今,パラメータを初期化した結果,上の図のグラフの枝に与えられているような数値になった状態で,入力層の値に線形変換を施すところまでを考えてみましょう.この計算は,以下のようになります.
u11=3×2+1×3+2×1=11u12=2×23×31×1=14 \begin{aligned} u_{11}&=3\times 2+1\times 3+2\times 1=11\\ u_{12}&=-2\times 2-3\times 3-1\times 1=-14 \end{aligned}
次に非線形変換を行う活性化関数としてReLU関数を採用し,以下のように中間層の値を計算してみましょう.
h11=max(0,11)=11h12=max(0,14)=0 \begin{aligned} h_{11} &= \max(0, 11) = 11 \\ h_{12} &= \max(0, -14) = 0 \end{aligned}
同様に,出力層の yy の値までを計算すると,
y=3×11+2×0=33 y = 3 \times 11 + 2 \times 0 = 33
となります.
さて,次節からは,パラメータを,どうやって更新していくかを見てみましょう.

目的関数

ニューラルネットワークでも,微分可能でさえあれば解きたいタスクに合わせて様々な目的関数を利用することができます.

平均二乗誤差

例えば,出力層にNN個の値を持つニューラルネットワークで回帰問題を解く場合を考えてみましょう.NN個の出力それぞれ(yn(n=1,2,,N)y_n (n=1, 2, \dots, N))に対して望ましい出力(tn(n=1,2,,N)t_n (n=1, 2, \dots, N))が与えられたとき,目的関数をそれぞれの出力(yny_n)と対応する正解(tnt_n)の間の 平均二乗誤差(mean squared error) とすることで,回帰問題を解くことができます.
L=1Nn=1N(tnyn)2 \mathcal{L} = \dfrac{1}{N} \sum_{n=1}^{N}(t_{n} - y_{n})^{2}
これを最小にするようにニューラルネットワーク中のパラメータを決定するわけです.例えば,上図の例で正解として t=20t = 20 が与えられたときの目的関数の値は,
L=11(2033)2=169 \mathcal{L} = \dfrac{1}{1} (20 - 33)^2 = 169
です.これを小さくするような重み行列の値を探せばよいということです.

交差エントロピー

一方,分類問題の場合はしばしば 交差エントロピー(cross entropy) が目的関数として利用されます.
例として,NNクラスの分類問題を考えてみましょう.ある入力 xx が与えられたとき,ニューラルネットワークの出力層に NN 個のノードがあり,それぞれがこの入力が nn 番目のクラスに属する確率 yn=p(y=nx)y_n = p(y=n|x) を表しているとします.これは,入力 xx が与えられたという条件のもとで,予測クラスを意味する yynn であるような確率ということです.
ここで,xx が所属するクラスについての正解が, t=[t1t2tN]T{\bf t} = \begin{bmatrix} t_1 & t_2 & \dots & t_N \end{bmatrix}^T というベクトルで与えられているとします.ただし,このベクトルは tn(n=1,2,,N)t_n (n=1, 2, \dots, N) のいずれか1つだけが1であり,それ以外は0であるようなベクトルであるとします. これを 1-hotベクトル と呼びます.そして,この1つだけ値が1となっている要素は,その要素のインデックスに対応したクラスが正解であることを意味します.例えば,t3=1t_3 = 1であれば3というインデックスに対応するクラスが正解であるということになります.
さて,このような準備を行うと,交差エントロピーは以下のように計算できるものとして記述することができます.
L=1Nn=1Ntnlogyn \mathcal{L} = - \frac{1}{N} \sum_{n=1}^{N}t_{n}\log y_{n}
補足:交差エントロピーについて
以下は,交差エントロピーの定義について知りたい方だけ参考にしてください.情報理論などで交差エントロピーの定義を知っている方は上の式で表されるものが交差エントロピーとは違うようにみえるかもしれません.しかしこれは,以下のように説明できます.今,q(yx)q(y|x)をニューラルネットワークのモデルが定義する条件付き確率とし,p(yx)p(y|x)を実データの条件付き確率とします.ここで,p(yx)p(y|x)は実際には未知であるため,代わりに学習データの経験分布
p^(yx)=1Nn=1NI(x=xn,y=yn) \hat{p}(y|x) = \frac{1}{N} \sum_{n=1}^N I(x =x_n, y=y_n)
を用いることとします.ただし II はディラック関数とよばれ,その等号が成立する時,値が\infty,それ以外では00であるような関数で,その定義域全体にわたる積分は1になるものです.この時,確率分布 p^(yx)\hat{p}(y|x)q(yx)q(y|x) の間のKLダイバージェンス(確率分布間の距離を測り、確率分布が一致する時、またその時のみ00となり、それ以外は正の値をとる)は
KL(pq)=x,yp^(yx)logp^(yx)q(yx)dxdy KL(p||q) = \int_{x, y} \hat{p}(y|x) \log \frac{\hat{p}(y|x)}{q(y|x)} dx dy
と定義されます.ここでディラックのデルタ関数の定義を用い,またqqに依存する項だけを抜き出すと,先程の交差エントロピーの目的関数が導出されます.

ニューラルネットワークの最適化

目的関数の値を最小にするようなパラメータの値を決定することが,ニューラルネットワークの学習の目的であるとわかりました.では,どのようにしてそのパラメータを探し当てればよいのでしょうか.ある目的関数が与えられたもとで,その目的関数が望ましい値をとるようにニューラルネットワークのパラメータを決定することを,ニューラルネットワークの最適化といいます.
最適化の方法を考える前に,まず最適化の対象とはなんであったか,再度確認しましょう.「ニューラルネットワークを最適化する」とは,すなわち「ニューラルネットワークが内部で用いている全てのパラメータの値を適切に決定する」という意味です.では,ニューラルネットワークにおけるパラメータとは,何だったでしょうか.それは,ここまで紹介したシンプルな全結合型ニューラルネットワークの場合,各層の線形変換に用いられていた W{\bf W}b{\bf b} のことを指します.
ニューラルネットワークの各パラメータを,目的関数に対する勾配を0とおいて解析的に解くことは,一般的には困難です.しかし,実データをニューラルネットワークに入力すれば,その入力の値における目的関数の勾配を数値的に求めることは可能です.この値が分かれば,パラメータをどのように変化させれば目的関数の値を小さくすることができるのかが分かります.そこで,この勾配を使ってパラメータを繰り返し少しずつ更新していくことで,ニューラルネットワークの最適化を行うことができるのです.この方法について順を追って考えていきましょう.
まず,以下の図を見てください.図中の点線は,パラメータ ww を変化させた際の目的関数 L\mathcal{L} の値を表しています.この例では簡単のため二次関数の形になっていますが,ニューラルネットワークの目的関数は実際には多次元で,かつもっと複雑な形をしていることがほとんどでしょう.しかし,ここでは説明のためこのようにシンプルな形を想像してみましょう.さて,この目的関数が最小値を与えるような ww は,どのようにして発見できるでしょうか.
パラメータと目的関数の関係(イメージ)
前節で説明したように,ニューラルネットワークのパラメータはまず乱数で初期化されます.ここでは,例として w=3w=3 という初期化が行われたと考えてみましょう.そうすると,w=3w=3におけるL\mathcal{L}の勾配 Lw\frac{\partial \mathcal{L}}{\partial w} が求まります.ニューラルネットワークの目的関数は,全てのパラメータについて微分可能である* ためです.さて,ここでは仮に w=3w=3 における Lw\frac{\partial \mathcal{L}}{\partial w}33 であったとしましょう(このことをLww=3=3\frac{\partial \mathcal{L}}{\partial w} |_{w=3} = 3と書きます).すると,以下の図のように,この 33 という値は w=3w=3 における L(w)\mathcal{L}(w) という関数の接線の傾き(勾配; gradient)を表しています.
(* 厳密には損失関数に微分不可能な点が存在する可能性はあります.例えばReLUは x=0x=0 の点で微分不可能なため,ReLUを含んだニューラルネットワークには微分不可能な点があります.しかし,通常使うニューラルネットワークの場合,そのような微分不可能な点はわずかしかないため,以下に説明する最適化の方法の中では,無視できます.)
目的関数の接線の傾き
傾きとは,ww を増加させた際に L\mathcal{L} が増加する方向を意味しているので,今は L\mathcal{L} の値を小さくしたいわけですから,この傾きの逆方向へ ww を変化させる,すなわち ww から L/w\partial \mathcal{L} / \partial w を引けばよい ことになります.
これがニューラルネットワークのパラメータを目的関数の勾配を用いて更新していく際の基本的な考え方です.このときの ww のステップサイズ(更新量)のスケールを調整するために,勾配に 学習率 (learning rate) と呼ばれる値を乗じるのが一般的です.
例えば,今学習率を 0.50.5 に設定してみます.そうすると,wwの更新量は 学習率 ×\times 勾配 で決まるので,0.5×3=1.50.5 \times 3 = 1.5 となります.現在 w=3w=3 なので,この値を引いて ww1.5w \leftarrow w - 1.5 と更新した後は, w=1.5w=1.5 となります.上の図は,この1度の更新を行ったあとの状態を表しています.
1度目の更新を行って,www=1.5w = 1.5 の位置に移動しました.そこで,再度この点においても勾配を求めてみます.今度は 1-1 になっていたとしましょう.すると 学習率 ×\times 勾配0.5×1=0.50.5 \times -1 = -0.5 となります.これを再び用いて,ww(0.5)w \leftarrow w - (-0.5) と2度目の更新を行うと,今度は w=2w = 2 の位置にくるでしょう.このようにして,2回更新したあとは,以下の図のようになります.
パラメータの更新
徐々にL\mathcal{L}が最小値をとるときのwwの値に近づいていっていることが見て取れます.
こうして,学習率 ×\times 勾配 を更新量としてパラメータを変化させていくと,パラメータ ww を求めたい L\mathcal{L} の最小値を与える ww に徐々に近づけていくことができます.このような勾配を用いた目的関数の最小化手法を 勾配降下法 と呼びます.ニューラルネットワークは,基本的に 微分可能な関数のみを層間をつなぐ関数として用いて 設計されるため,登場する関数はすべて微分可能であり,学習データセットを用いて勾配降下法によってパラメータを最適化する方法が適用可能なのです.
ただし,通常ニューラルネットワークを勾配降下法で最適化する場合は,データを一つ一つ用いてパラメータを更新するのではなく,いくつかのデータをまとめて入力し,それぞれの勾配を計算したあと,その勾配の平均値を用いてパラメータの更新を行う方法がよく行われます.これを ミニバッチ学習 と呼びます.これは,学習データセットから一様ランダムに k(>0)k (>0) 個のデータを抽出し,その kk 個のデータに対する目的関数の平均の値を小さくするようパラメータを更新することを,異なる kk 個のデータの組み合わせに対して繰り返し行う方法です.結果的にはデータセットに含まれる全てのデータを使用していきますが,1度の更新に用いるデータは kk 個ずつということになります.実際の実装では,データセット内のサンプルのインデックスをまずランダムにシャッフルして並べた配列を作り,その配列の先頭から kk 個ずつインデックスを取り出し,対応するデータを使ってミニバッチを構成します.こうして,全てのインデックスを使い切ること,すなわちデータセット内のデータを1度ずつ,すべてパラメータ更新に用い終えることを 1エポックの学習 と呼びます.そして,この kk をバッチサイズもしくはミニバッチサイズと呼び,このような学習方法を指して,確率的勾配降下法 (SGD: Stocastic Gradient Descent) という名前が用いられます.現在ほとんど全てのニューラルネットワークの最適化手法はこのSGDをベースとした手法となっています.SGDを用いると,全体の計算時間が劇的に少なくできるだけでなく,下図のように目的関数が凸関数でなかったとしても,多くの場合うまくいくことが経験的に知られており,その理論的な裏付けをしようという試みが近年盛んに行われています.
局所最適解と大域最適解

パラメータ更新量の算出

それでは今,下図のような3層の全結合型ニューラルネットワークを考え,1層目と2層目の間の線形変換が w1,b1{\bf w}_1, {\bf b}_1,2層目と3層目の間の線形変換が w2,b2{\bf w}_2, {\bf b}_2 というパラメータによって表されているとします(図ではバイアス b1,b2{\bf b}_1, {\bf b}_2 は省略されています).また,これらをまとめて Θ\boldsymbol{\Theta} と表すことにします.
パラメータ更新の例
入力ベクトルは x{\bf x},ニューラルネットワークの出力は yRN{\bf y} \in \mathbb{R}^NNN 次元実数ベクトルという意味)とし,入力 x{\bf x} に対応した“望ましい出力”である教師ベクトルを t{\bf t} とします.ここで,目的関数には前述の平均二乗誤差関数を用いることとしましょう.
さて,パラメータをそれぞれ適当な乱数で初期化したあと,入力 x{\bf x} が与えられたときの目的関数の各パラメータについての勾配を計算して,それぞれのパラメータについて更新量を算出してみましょう.
まず,目的関数を改めてベクトル表記を用いて書き下すと,以下のようになります.
L(y,t)=1Nty22 \mathcal{L}({\bf y}, {\bf t}) = \frac{1}{N} || {\bf t} - {\bf y} ||_2^2
ty22|| {\bf t} - {\bf y} ||_2^2はここでは(ty)T(ty)({\bf t} - {\bf y})^T({\bf t} - {\bf y})と同等の意味となります.さらに,ニューラルネットワーク全体を ff と書くことにすると,出力 y{\bf y}
y=f(x;Θ)=a2(w2a1(w1x+b1)+b2) \begin{aligned} {\bf y} &= f({\bf x}; \boldsymbol{\Theta}) \\ &= a_2 ( {\bf w}_2 a_1({\bf w}_1 {\bf x} + {\bf b}_1) + {\bf b}_2 ) \end{aligned}
と書くことができます.ここで,a1,a2a_1, a_2 はそれぞれ,1層目と2層目の,および2層目と3層目の間で線形変換のあとに施される非線形変換(活性化関数)を意味しています.以下,簡単のために,各層間で行われた線形変換の結果を u1,u2{\bf u}_1, {\bf u}_2とし,中間層の値,すなわち u1{\bf u}_1 に活性化関数を適用した結果を h1{\bf h}_1 と書きます.ただし,u2{\bf u}_2 に活性化関数を適用した結果は y{\bf y} と表記します.すると,これらの関係は以下のように整理することができます.
y=a2(u2)u2=w2h1+b2h1=a1(u1)u1=w1x+b1 \begin{aligned} {\bf y} &= a_2({\bf u}_2) \\ {\bf u}_2 &= {\bf w}_2 {\bf h}_1 + {\bf b}_2 \\ {\bf h}_1 &= a_1({\bf u}_1) \\ {\bf u}_1 &= {\bf w}_1 {\bf x} + {\bf b}_1 \end{aligned}
パラメータ w2{\bf w}_2 の更新量
それではまず,出力層に近い方のパラメータ,w2{\bf w}_2 についての L\mathcal{L} の勾配を求めてみましょう.これは,合成関数の偏微分なので,連鎖律(chain rule)を用いて以下のように展開できます.
Lw2=Lyyw2=Lyyu2u2w2 \begin{aligned} \frac{\partial \mathcal{L}}{\partial {\bf w}_2} &= \frac{\partial \mathcal{L}}{\partial {\bf y}} \frac{\partial {\bf y}}{\partial {\bf w}_2} \\ &= \frac{\partial \mathcal{L}}{\partial {\bf y}} \frac{\partial {\bf y}}{\partial {\bf u}_2} \frac{\partial {\bf u}_2}{\partial {\bf w}_2} \end{aligned}
この3つの偏微分はそれぞれ,
Ly=2N(ty)yu2=a2(u2)u2u2w2=h1 \begin{aligned} \frac{\partial \mathcal{L}}{\partial {\bf y}} &= -\frac{2}{N} ({\bf t} - {\bf y}) \\ \frac{\partial {\bf y}}{\partial {\bf u}_2} &= \frac{\partial a_2({\bf u}_2)}{\partial {\bf u}_2} \\ \frac{\partial {\bf u}_2}{\partial {\bf w}_2} &= {\bf h}_1 \end{aligned}
と求まります.ここで,活性化関数の入力に関する出力の勾配
a2(u2)u2 \frac{\partial a_2({\bf u}_2)}{\partial {\bf u}_2}
が登場しました.これは,例えば活性化関数にシグモイド関数を用いる場合は,
a2(u2)=11+exp(u2) a_2({\bf u}_2) = \frac{1}{1 + \exp(-{\bf u}_2)}
の微分ですから,すなわち
a2(u2)u2=(exp(u2))(1+exp(u2))2=11+exp(u2)exp(u2)1+exp(u2)=11+exp(u2)1+exp(u2)11+exp(u2)=11+exp(u2)(111+exp(u2))=a2(u2)(1a2(u2)) \begin{aligned} \frac{\partial a_2({\bf u}_2)}{\partial {\bf u}_2} &= -\frac{-(\exp(-{\bf u}_2))}{(1 + \exp(-{\bf u}_2))^2} \\ &= \frac{1}{1 + \exp(-{\bf u}_2)} \cdot \frac{\exp(-{\bf u}_2)}{1 + \exp(-{\bf u}_2)} \\ &= \frac{1}{1 + \exp(-{\bf u}_2)} \cdot \frac{1 + \exp(-{\bf u}_2) - 1}{1 + \exp(-{\bf u}_2)} \\ &= \frac{1}{1 + \exp(-{\bf u}_2)} (1 - \frac{1}{1 + \exp(-{\bf u}_2)}) \\ &= a_2({\bf u}_2)(1 - a_2({\bf u}_2)) \end{aligned}
となります.シグモイド関数の勾配は,このようにシグモイド関数の出力値を使って簡単に計算することができます.
これで w2{\bf w}_2 の勾配を計算するのに必要な値は全て出揃いました.では実際にNumPyを使ってこれらを計算してみましょう.ここでは簡単のために,バイアスベクトルはすべて0で初期化されているとします.
python
import numpy as np

# 入力
x = np.array([2, 3, 1])

# 正解
t = np.array([20])
まず,NumPyモジュールを読み込んでから,入力の配列を定義します.ここでは,上図と同じになるように 2, 3, 1 の3つの値を持つ3次元ベクトルを定義しています.また,正解として仮に 20 を与えることにしました.次に,パラメータを定義します.
python
# 1-2層間のパラメータ
w1 = np.array([[3, 1, 2], [-2, -3, -1]])
b1 = np.array([0, 0])

# 2-3層間のパラメータ
w2 = np.array([[3, 2]])
b2 = np.array([0])
ここでは,以下の4つのパラメータを定義しました.
1層目と2層目の間の線形変換のパラメータ
w1R2×3{\bf w}_1 \in \mathbb{R}^{2 \times 3} : 3次元ベクトルを2次元ベクトルに変換する行列
b1R2{\bf b}_1 \in \mathbb{R}^2 : 2次元バイアスベクトル
2層目と3層目の間の線形変換のパラメータ
w2R1×2{\bf w}_2 \in \mathbb{R}^{1 \times 2} : 2次元ベクトルを1次元ベクトルに変換する行列
b2R1{\bf b}_2 \in \mathbb{R}^1 : 1次元バイアスベクトル
それでは,各層の計算を実際に実行してみましょう.
python
# 中間層の計算
u1 = w1.dot(x) + b1
h1 = 1. / (1 + np.exp(-u1))

# 出力の計算
u2 = w2.dot(h1) + b2
y = 1. / (1 + np.exp(-u2))

print(y)
[0.95257194]
出力は 0.952571940.95257194 と求まりました.つまり,f([2,3,1]T)=0.95257194f([2, 3, 1]^T) = 0.95257194 ということになります.次に,上で求めた
Lw2=Lyyu2u2w2 \frac{\partial \mathcal{L}}{\partial {\bf w}_2} = \frac{\partial \mathcal{L}}{\partial {\bf y}} \frac{\partial {\bf y}}{\partial {\bf u}_2} \frac{\partial {\bf u}_2}{\partial {\bf w}_2}
の右辺の3つの偏微分をそれぞれ計算してみましょう.
python
# dL / dy
g_Ly = -2 / 1 * (t - y)

# dy / du_2
g_yu2 = y * (1 - y)

# du_2 / dw_2
g_u2w2 = h1
これらを掛け合わせれば,求めたかったパラメータ w2{\bf w}_2 についての勾配を得ることができます.
python
# dL / dw_2: 求めたい勾配
g_Lw2 = g_Ly * g_yu2 * g_u2w2

print(g_Lw2)
[-1.72104507e+00 -1.43112111e-06]
勾配が求まりました.これが L/w2\partial \mathcal{L} / \partial {\bf w}_2 の値です.これを学習率でスケールさせたものを使えば,パラメータ w2{\bf w}_2 を更新することができます.更新式は,具体的には以下のようになります.
w2w2ηLw2 {\bf w}_2 \leftarrow {\bf w}_2 - \eta \frac{\partial \mathcal{L}}{\partial {\bf w}_2}
ここでは学習率を η\eta で表記しました.
パラメータ w1{\bf w}_1 の更新量
次に,w1{\bf w}_1 の更新量も求めてみましょう.そのためには,w1{\bf w}_1 で目的関数 L\mathcal{L} を偏微分した値が必要です.これは以下のように計算できます.
Lw1=Lyyw1=Lyyu2u2w1=Lyyu2u2h1h1w1=Lyyu2u2h1h1u1u1w1 \begin{aligned} \frac{\partial \mathcal{L}}{\partial {\bf w}_1} &= \frac{\partial \mathcal{L}}{\partial {\bf y}} \frac{\partial {\bf y}}{\partial {\bf w}_1} \\ &= \frac{\partial \mathcal{L}}{\partial {\bf y}} \frac{\partial {\bf y}}{\partial {\bf u}_2} \frac{\partial {\bf u}_2}{\partial {\bf w}_1} \\ &= \frac{\partial \mathcal{L}}{\partial {\bf y}} \frac{\partial {\bf y}}{\partial {\bf u}_2} \frac{\partial {\bf u}_2}{\partial {\bf h}_1} \frac{\partial {\bf h}_1}{\partial {\bf w}_1} \\ &= \frac{\partial \mathcal{L}}{\partial {\bf y}} \frac{\partial {\bf y}}{\partial {\bf u}_2} \frac{\partial {\bf u}_2}{\partial {\bf h}_1} \frac{\partial {\bf h}_1}{\partial {\bf u}_1} \frac{\partial {\bf u}_1}{\partial {\bf w}_1} \end{aligned}
この5つの偏微分のうち初めの2つはすでに求めました.残りの3つは,それぞれ,
u2h1=w2h1u1=h1(1h1)u1w1=x \begin{aligned} \frac{\partial {\bf u}_2}{\partial {\bf h}_1} &= {\bf w}_2 \\ \frac{\partial {\bf h}_1}{\partial {\bf u}_1} &= {\bf h}_1(1 - {\bf h}_1) \\ \frac{\partial {\bf u}_1}{\partial {\bf w}_1} &= {\bf x} \end{aligned}
と計算できます.では,さっそく実際にNumPyを用いて計算を実行してみましょう.
python
g_u2h1 = w2
g_h1u1 = h1 * (1 - h1)
g_u1w1 = x

# 上から du1 / dw1 の直前までを一旦計算
g_Lu1 = g_Ly * g_yu2 * g_u2h1 * g_h1u1

# g_u1w1は (3,) というshapeなので,g_u1w1[None]として(1, 3)に変形
g_u1w1 = g_u1w1[None]

# dL / dw_1: 求めたい勾配
g_Lw1 = g_Lu1.T.dot(g_u1w1)

print(g_Lw1)

Discussion

コメントにはログインが必要です。