15 Deep Learning 連鎖律 計算グラフの逆伝播

15.1  計算グラフの逆伝播

上図に示すように、逆伝播の計算手順は信号Eに対して、ノードの局所的な微分dy/dxを乗算し、それを次のノードへ伝達していく。
ここで言う、局所的な微分とは順伝播でのy=f(x)の微分dy/dxを求めることになる。
y=f(x)=x^2 のとき dy/dx = 2xになる。
そして、この局所的な微分を上流から伝達された値(上図ではE)に乗算して、前のノードへ渡していく。

これが、逆伝播で行う計算手順だが、この計算を行うことで、目的とする微分の値を効率よく求めることができる。これは後述する連鎖律の原理から説明できる。

15.2 連鎖律

合成関数についてまず説明する。合成関数とは複数の関数によって、構成される関数のことである。たとえば、

\[z = (x+y)^2 \]

上式は式(15.1)のように、2つの式で構成される。

\[ \begin{array}{l} z = t^2 \\ t = x+y \tag{15.1} \end{array} \]

連鎖律とは合成関数の微分についての性質であり、次のように定義される。

ある関数が合成関数で表される場合、その合成関数の微分は、合成関数を構成するそれぞれの関数の微分の積によって表すことができる。

難しく見えるが、単純に言うと分数の積と同様に計算ができるということである。

dz/dx(xに関するzの微分)はdz/dt(tに関するzの微分)とdt/dx(xに関するtの微分)の積によって表すことができる。

\[ \frac{\delta z}{\delta x} = \frac{\delta z}{\delta t} \frac{\delta t}{\delta x} \tag{15.2} \]

上図のように、分数の積の計算のようにできる。

式(15.2)の微分dz/dxを求めるときは、まず局所的な微分(偏微分)を求める。

\[ \begin{array}{l} \frac{\delta z}{\delta t} = 2t \\ \frac{\delta t}{\delta x} = 1 \tag{15.3} \end{array} \]

よって、最終的に求めたいdz/dxは式(15.3)で求めた微分の積によって計算できる。

\[ \frac{\delta z}{\delta x} = \frac{\delta z}{\delta t} \frac{\delta t}{\delta x} = 2t・1 = 2(x + y) \tag{15.4} \]

15.3  連鎖律と計算グラフ

**2というノードは2乗を表すとする。x+y=tとして順方向に伝播する。さらにt^2 = z と順方向に伝播する。dz/dz は先ほどは省略していたが、連鎖律の原理により、dz/dz=1である。上図で注目すべきは、一番左の逆伝播の結果である。

dz/dz dz/dt dt/dx = dz/dx となり、「xに関するzの微分」に対応する。

上図に式(15.3)を代入すると下図のようになる。

dz/dx = 2t = 2(x+y) となる。

14 Deep Learning 計算グラフ

「馬鹿よ貴方は」のネタで、算数の問題がある。
例えば、以下のようである。

問 太郎君はスーパーでリンゴを2個、みかんを3個買いました。リンゴは1個100円、みかんは1個150円です。消費税が10%かかるものとして、支払う金額を求めなさい。

この問は「金額の数値」を問うているが、何故問うているかは不明である。
たとえば、教室で算数の時間で先生が問いを発しているならわかる。生徒に計算能力をつけるため、国の施策に従い、給料をえるために発している問いである。

しかし、「馬鹿よ貴方は」のファラオさんが、いきなりこの問いを発すると途端に訳が分からなくなる。
太郎君は何歳なのか? スーパーでフルーツだけ買って帰ることがあるのか? 金額を求めなさいと言われているのは、おそらくレジの人であろう。
レジの人はバーコードを打つだけだろうし、手計算や暗算などはしないだろう。ではレジのコンピュータに向かってレジの人が求めなさいといっているのか?
音声認識システムのレジをわざわざ作る必要はまったくないので、このレジの人は独語癖があると考えられる。しかもなぜレジの人は、太郎君だと名前を知っているのか?
そもそも日本円で消費税が10%とは未来の話をしているのか?などと、掘り下げるといくらでもできるが、問い自体に意味はない。
これが人生に意味がないというハイデガーやニーチェのニヒリズムや仏教の空と同一であるとするのはいささか強引に思われるかもしれないが、ほとんど同義だと私は考える。

誤差伝播法で使用する計算グラフで問いを解くと下図のようになる。

分かりやすい問題を難しくすることは、このように容易であるが、問題を簡潔にするのは難しい。

今回は簡単なグラフの説明なので、余談が多めであった。

13 Deep Learning 学習アルゴリズムの実装

ニューラルネットワークの学習は以下の4つの手順で行う。

ステップ1 ミニバッチ

訓練データの中からランダムに一部のデータを選びだす。その選ばれたデータをミニバッチと言い、ここでは、このミニバッチの損失関数の値を減らすことを目的とする。

ステップ2 勾配の算出

ミニバッチの損失関数を減らすために、各重みパラメータの勾配をもとめる。勾配は、損失関数の値を最も減らす方向を示す。

ステップ3  パラメータの更新

重みパラメータを勾配方向に微小量だけ更新する。

繰り返す

ステップ1,2,3を繰り返す。

ここで使用するデータはミニバッチとして無作為に選ばれたデータを使用していることから、確率的勾配降下法(stochastic gradient descent)と呼ばれる。
省略してSGDと呼ばれることもある。

13.1  2層ニューラルネットワークのクラス

2層(隠れ層が1層)のネットワークを一つのクラスとして実装する。

実装は以下。

13.2 ミニバッチ学習の実装

下図のグラフが出力される。

12.2 ニューラルネットワークに対する勾配

今更ながら、外部ファイルの関数を使用する方法を見つけたので記しておく。

他のファイルの中で定義された関数は、import文で読み込んでから使います。たとえば、「myhelper.py」というPythonコードの中で定義されたmyfunc1という関数を使いたい場合は

と書くことで使うことができます。構文は「from [ファイル名] import [関数名]」です。

他のコードの中で定義された関数をまとめてimportしたい場合は

とすることで一括でimportできます。ただ、このように何をimportするのかを明示しない場合には名前が衝突してしまう可能性もあるので注意が必要です。

ファイル全体をimportする場合には、fromは使わず、importの対象に直接ファイル名を指定する次のような使い方も可能です。

引用元:LIFE WITH PYTHON Python関数の使い方

ニューラルネットワークの学習においても、勾配を求める必要がある。ここで言う勾配は、重みパラメータに関する損失関数の勾配である。たとえば、形状が 2 × 3 (2行3列) の重みWだけを持つニューラルネットワークがあり、損失関数をLで表す場合を考えると、この場合、勾配はδL/δW と表すことができる。実際に数式で表すと次のようになる。

\begin{equation}\begin{array}{c} W = \left( \begin{array}{ccc} w_{11} & w_{21} & w_{31} \\ w_{12} & w_{22} & w_{32} \end{array} \right) \\
\frac{\partial L}{\partial W} = \left( \begin{array}{ccc} \frac{\partial L}{\partial w_{11}} & \frac{\partial L}{\partial w_{21}} & \frac{\partial L}{\partial w_{31}}\\
\frac{\partial L}{\partial w_{12}} & \frac{\partial L}{\partial w_{22}} & \frac{\partial L}{\partial w_{32}} \end{array} \right) \tag{12.2} \end{array}
\end{equation}

δL/δWの各要素は、それぞれの要素に関する偏微分から構成される。
まず、重みのWを初期化し、入力xと重みWの内積predict()の出力から損失関数の値を求める。

実装すると以下のようになり、重みの勾配dWは次のように出力される。

12.1 Deep Learning 勾配降下法

12.1 勾配法

機械学習の問題の多くは、学習の最適なパラメータを探索する。ニューラルネットワークも同様に最適なパラメータ(重みとバイアス)を学習時に見つけなければならない。ここで、最適なパラメータというのは、損失関数が最小値をとるときのパラメータの値である。損失関数は複雑なので、勾配をうまく利用して、関数の最小値を探そうというのが勾配法である。注意点は勾配が指す先が本当に関数の最小値なのかどうか、実際、複雑な関数においては、最小値ではない場合がほとんどである。

勾配法は現在の場所から勾配方向に、一定の距離だけ移動し、さらに勾配を求め、勾配方向へ一定の距離を進むことを繰り返す。このようにして、関数の値を減らすのが勾配法(gradient method)である。一般的にニューラルネットワークでは勾配降下法(gradient descent method)と呼ばれる。(ディープラーニングにおいては、勾配降下法と誤差逆伝播法(後述)の理解が必須であるため、章をたくさん使って説明している。)

勾配法を数式で表すと、次の式(12.1)のように書くことができる。
\begin{equation} \begin{array}{cc} x_0 = x_0 – \eta \frac{\partial f}{\partial x_0} \\ x_1 = x_1 – \eta \frac{\partial f}{\partial x_1} \tag{12.1} \end{array} \end{equation}

式(12.1)のηはへ更新料を表し、学習率(learning rate)と呼ばれる。一回の学習で、どれだけ学習すべきか、すなわち、どれだけパラメータを更新するかを決めるのが学習率である。
式(12.1)は一回の更新式を示しており、この処理を繰り返し行う。つまり、この処理ごとに変数の値を更新していき、処理を何度か繰り返すことによって、徐々に関数の値を減らしていく。
ここでは、変数が2つの場合を示しているが、変数の数が増えても、同じような式—それぞれぞれの変数の偏微分の値—によって更新されることになる。
学習率の値は0.01や0.001など、前もって何らかの値に決める必要がある。
学習率は一般的に大きすぎても、小さすぎても「良い場所」にたどり着くことができない。
勾配降下法をPythonで実装すると、下記のようになる。

勾配を求めるnumerical_gradient()は以下のようであった。

f(x0,x1) = x0^2 + x1^2 を勾配法で求めてみる。

出力が微小な値(-6.111..e-10, 8,184e-10)に対して、真の最小値は(0,0)であるため、勾配法を実装した関数が正しく動作していることが確認できる。

勾配法による学習のプロセスは下図のように表される。

原点が最も低い場所であるが、徐々に近づいていることがわかる。

学習率は小さすぎても、大きすぎてもよい結果にならないと述べたが、具体例を下図に示す。

この結果が示す通り、学習率が大きすぎると、大きな値へと発散し、小さすぎると、ほとんど更新されずに終わる。学習率のようなパラメータはハイパーパラメータと言い、カッコいい名前に反して、人力で探す必要がある手動パラメータである。