CHAPTER 2

逆伝播の仕組み

ニューラルネットワークと深層学習

What this book is about

On the exercises and problems

ニューラルネットワークを用いた手書き文字認識

逆伝播の仕組み

ニューラルネットワークの学習の改善

ニューラルネットワークが任意の関数を表現できることの視覚的証明

ニューラルネットワークを訓練するのはなぜ難しいのか

深層学習

Appendix: 知性のある シンプルな アルゴリズムはあるか?

Acknowledgements

Frequently Asked Questions


Sponsors


Resources

前章では、勾配降下法を用いてニューラルネットワークが重みとバイアスをどのように学習するかを説明しました。 しかし、その説明にはギャップがありました。具体的には、コスト関数の勾配をどのように計算するかを議論していません。これはとても大きなギャップです! 本章では、逆伝播と呼ばれる、コスト関数の勾配を高速に計算するアルゴリズムを説明します。

逆伝播アルゴリズムはもともと1970年代に導入されました。 しかし逆伝播が評価されたのは、 David RumelhartGeoffrey HintonRonald Williams による1986年の著名な論文が登場してからでした。 その論文では、逆伝播を用いると既存の学習方法よりもずっと早く学習できる事をいくつかのニューラルネットワークに対して示し、それまでニューラルネットワークでは解けなかった問題が解ける事を示しました。 今日では、逆伝播はニューラルネットワークを学習させる便利なアルゴリズムです。

本章は他の章に比べて数学的に難解です。 よほど数学に対し熱心でなければ、本章を飛ばして、逆伝播を中身を無視できるブラックボックスとして扱いたくなるかもしれません。 では、なぜ時間をかけて逆伝播の詳細を勉強するのでしょうか?

その理由はもちろん理解のためです。 逆伝播の本質はコスト関数Cのネットワークの重みw(もしくはバイアスb)に関する偏微分C/wC/b)です。 偏微分をみると、重みとバイアスを変化させた時のコスト関数の変化の度合いがわかります。 偏微分の式は若干複雑ですが、そこには美しい構造があり、式の各要素には自然で直感的な解釈を与える事ができます。 そうです、逆伝播は単なる高速な学習アルゴリズムではありません。 逆伝播をみることで、重みやバイアスを変化させた時のニューラルネットワーク全体の挙動の変化に関して深い洞察が得られます。 逆伝播を勉強する価値はそこにあるのです。

そうは言うものの、本章をざっと読んだり、読み飛ばして次の章に進んでも大丈夫です。 この本は逆伝播をブラックボックスとして扱っても他の章を理解できるように書いています。 もちろん次章以降で本章の結果を参照する部分はあります。 しかし、その参照部分の議論をすべて追わなくても、主な結論は理解できるはずです。

ウォーミングアップ:ニューラルネットワークの出力の行列を用いた高速な計算

逆伝播を議論する前に、ニューラルネットワークの出力を高速に計算する行列を用いたアルゴリズムでウォーミングアップしましょう。 私達は 前章の最後のあたり で既にこのアルゴリズムを簡単に見ています。 しかしその時はざっと書いていたので、ここで立ち戻って詳しく説明しようと思います。 特にこれまで説明して慣れた文脈で逆伝播で使用する記号に慣れるのに、このウォーミングアップは良い方法です。

ニューラルネットワーク中の重みを曖昧性なく指定する表記方法からまず始めましょう。 wjklで、(l1)番目の層のk番目のニューロンから、l番目の層のj番目のニューロンへの接続に対する重みを表します。 例えば下図は、2番目の層の4番目のニューロンから、3番目の層の2番目のニューロンへの接続の重みを表します。

この表記方法は最初は面倒で、使いこなすのにある程度の練習が必要かもしれません。 しかし、少し頑張ればこの表記方法は簡単で自然だと感じるようになるはずです。 この表記方法で若干曲者なのが、jkの順番です。 jを入力ニューロン、kを出力ニューロンとする方が理にかなっていると思うかもしれませんが、 実際には逆にしています。 奇妙なこの記述方法の理由は後程説明します。

ニューラルネットワークのバイアスと活性についても似た表記方法を導入します。 具体的には、bjll番目の層のj番目のニューロンのバイアスを表します。 また、ajll番目の層のj番目のニューロンの活性を表します。 下図はこれらの表記方法の利用例です。

これらの表記を用いると、l番目の層のj番目のニューロンの活性ajlは、(l1)番目の層の活性と以下の式で関係付けられます(式 (4) と前章の周辺の議論を比較してください)
(23)ajl=σ(kwjklakl1+bjl).
ここで、和は(l1)番目の層の全てのニューロンkについて足しています。 この式を行列で書き直すため、各層lに対し重み行列wlを定義します。 重み行列wlの各要素はl番目の層のニューロンを終点とする接続の重みです。 すなわち、j行目k列目の要素をwjklとします。 同様に、各層lに対し、バイアスベクトルblを定義します。 おそらく想像できると思いますが、バイアスベクトルの要素はbjl達で、 l番目の層の各ニューロンに対し1つの行列要素が伴います。 最後に、活性ベクトルalを活性ajl達で定義します。

(23)

ajl=σ(kwjklakl1+bjl)
を行列形式に書き直すのに必要な最後の要素は、σなどの関数のベクトル化です。 ベクトル化は既に前章で簡単に見ました。 要点をまとめると、σのような関数をベクトルvの各要素に適用したいというのがアイデアです。 このような各要素への関数適用にはσ(v)という自然な表記を用います。 つまり、σ(v)の各要素はσ(v)j=σ(vj)です。 例えばf(x)=x2とすると、次のようになります。
(24)f([23])=[f(2)f(3)]=[49].
すなわち、ベクトル化したfはベクトルの各要素を2乗します。

この表記方法を用いると、式 (23)

ajl=σ(kwjklakl1+bjl)
は次のような美しくコンパクトなベクトル形式で書けます。
(25)al=σ(wlal1+bl).
この表現を用いると、ある層の活性とその前の層の活性との関係を俯瞰できます。 我々が行っているのは活性に対し重み行列を掛け、バイアスベクトルを足し、最後にσ関数を適用するだけです。 *ところで、先ほどのwjklという奇妙な表記を用いる動機はこの式に由来します。 もし、jを入力ニューロンに用い、kを出力ニューロンに用いたとすると、式 (25) は重み行列をそれの転置行列に置き換えなければなりません。 些細な変更ですが、煩わしい上に「重み行列を掛ける」と簡単に言ったり(もしくは考えたり)できなくなってしまいます。 この見方はこれまでのニューロン単位での見方よりも簡潔で、添字も少なくて済みます。 議論の正確性を失う事なく添字地獄から抜け出せる方法と考えると良いでしょう。 さらに、この表現方法は実用上も有用です。 というのも、多くの行列ライブラリでは高速な行列掛算・ベクトル足し算・関数のベクトル化の実装が提供されているからです。 実際、前章のコード では、ネットワークの挙動の計算にこの表式を暗に利用していました。

alの計算のために式 (25)

al=σ(wlal1+bl)
を利用する時には、途中でzlwlal1+blを計算しています。 この値は後の議論で有用なので名前をつけておく価値があります。 zll番目の層に対する重みつき入力と呼ぶことにします。 本章の以降の議論では重みつき入力zlを頻繁に利用します。 (25) をしばしば重み付き入力を用いてal=σ(zl)とも書きます。 zlの要素はzjl=kwjklakl1+bjlと書ける事にも注意してください。 つまり、zjll番目の層のj番目のニューロンが持つ活性関数へ与える重みつき入力です。

コスト関数に必要な2つの仮定

逆伝播の目標はニューラルネットワーク中の任意の重みwまたはバイアスbに関するコスト関数Cの偏微分、すなわちC/wC/bの計算です。 逆伝播が機能するには、コスト関数の形について2つの仮定を置く必要があります。 それらの仮定を述べる前に、コスト関数の例を念頭に置くのが良いでしょう。 前章でも出てきた2乗コスト関数(参考:式(6)

C(w,b)12nxy(x)a2
)をここでも考えます。 前章の記法では、2乗コスト関数は以下の様な形をしていました
(26)C=12nxy(x)aL(x)2.
ここで、nは訓練例の総数、和は個々の訓練例xについて足しあわせたもの、y=y(x)は対応する目標の出力、Lはニューラルネットワークの層数、aL=aL(x)xを入力した時のニューラルネットワークの出力のベクトルです。

では、逆伝播を適用するために、コスト関数Cに置く仮定はどのようなものでしょうか。 1つ目の仮定はコスト関数は個々の訓練例xに対するコスト関数Cxの平均 C=1nxCxで書かれているという事です。 2乗コスト関数ではこの仮定が成立しています。 それには1つの訓練例に対するコスト関数をCx=12yaL2とすれば良いです。 この仮定はこの本で登場する他のコスト関数でも成立しています。

この仮定が必要となる理由は、逆伝播によって計算できるのは個々の訓練例に対する偏微分Cx/wCx/bだからです。 コスト関数の偏微分C/wC/bは全訓練例についての平均を取ることで得られます。 この仮定を念頭に置き、私達は訓練例xを1つ固定していると仮定し、コストCxを添字xを除いてCと書くことにします。最終的に除いたxは元に戻しますが、当面は記法が煩わしいので暗にxが書かれていると考えます。

コスト関数に課す2つ目の仮定は、コスト関数はニューラルネットワークの出力の関数で書かれているという仮定です。

例えば、2乗誤差関数はこの要求を満たしています、それは1つの訓練例xに対する誤差は以下のように書かれるためです
(27)C=12yaL2=12j(yjajL)2.
もちろんこのコスト関数は目標とする出力yにも依存しています。 コスト関数をyの関数とみなさない事を不思議に思うかもしれません。 しかし、訓練例xを固定する事で、出力yも固定している事に注意してください。 つまり、出力yは重みやバイアスをどのように変化させた所で変化させられる量ではなく、ニューラルネットが学習するものではありません。 ですので、Cを出力の活性aL単独の関数とみなし、yは関数を定義するための単なるパラメータとみなすのは意味のある問題設定です。

アダマール積 st

逆伝播アルゴリズムは、ベクトルの足し算やベクトルと行列の掛け算など、一般的な代数操作に基づいています。 しかし、その中で1つあまり一般的ではない操作があります。 stが同じ次元のベクトルとした時、stを2つのベクトルの要素ごとの積とします。つまり、stの要素は(st)j=sjtjです。 例えば、

(28)[12][34]=[1324]=[38]
です。 この種の要素ごとの積はしばしばアダマール積、もしくはシューア積と呼ばれます。 私達はアダマール積と呼ぶことにします。 よく出来た行列ライブラリにはアダマール積の高速な実装が用意されており、逆伝播を実装する際に手軽に利用できます。

逆伝播の基礎となる4つの式

逆伝播は重みとバイアスの値を変えた時にコスト関数がどのように変化するかを把握する方法です。 これは究極的にはC/wjklC/bjlとを計算する事を意味します。 これらの偏微分を計算する為にまずは中間的な値δjlを導入します。 この値はl番目の層のj番目のニューロンの誤差と呼びます。 逆伝播の仕組みを見るとδjlを計算手順とδjlC/wjklC/bjlと関連づける方法が得られます。

誤差の定義方法を理解する為にニューラルネットワークの中にいる悪魔を想像してみましょう。

悪魔はl番目の層のj番目のニューロンに座っているとします。 ニューロンに入力が入ってきた時、悪魔はニューロンをいじって、重みつき入力に小さな変更Δzjlを加えます。 従って、ニューロンはσ(zjl)の代わりに、σ(zjl+Δzjl)を出力します。 この変化はニューラルネット中の後段の層に伝播し、最終的に全体のコスト関数の値はCzjlΔzjlだけ変化します。

ここで、この悪魔は善良な悪魔で、コスト関数を改善する、つまりコストを小さくするようなΔzjlを探そうとするとします。 Czjlが大きな値(正でも負も良いです)であるとします。 すると、Czjlと逆の符号のΔzjlを選ぶことで、この悪魔はコストをかなり改善させられます。 逆に、もしCzjl0に近いと悪魔は重みつき入力zjlを摂動させてもコストをそれほどは改善できません。 悪魔が判断できる範囲においてはニューロンは既に最適に近い状態だと言えます* *もちろんこれが正しいのはΔzjlが小さい場合に限ってです。悪魔は微小な変化しか起こせないと仮定しています。 つまり、ヒューリスティックには、Czjlはニューラルネットワークの誤差を測定しているという意味を与える事ができます。

この話を動機として、l番目の層のj番目のニューロンの誤差δjlを以下のように定義します

(29)δjlCzjl.
. 慣習に沿って、δll番目の層の誤差からなるベクトルを表します。 逆伝播により、各層でのδlを計算し、これらを真に興味のあるC/wjklC/bjlと関連付けることができます。

悪魔はなぜ重みつき入力zjlを変えようとするのかを疑問に思うかもしれません。 確かに、出力活性ajlを変化させ、その結果のCajlを誤差の指標として用いる方が自然かもしれません。 実際そのようにしても、以下の議論は同じように進められます。 しかし、やってみるとわかるのですが、誤差逆伝播の表示が数学的に若干複雑になってしまいます。 ですので、我々は誤差の指標としてδjl=Czjlを用いることにします* *MNISTのような分類問題では、誤差(error)という言葉はしばしば誤分類の割合を意味します。 例えばニューラルネットが96.0%の数字を正しく分類できたとしたら、"error"は4.0%です。 もちろん、これはδベクトルとは全く異なる意味です。 実際の文脈ではどちらの意味かで迷うことはないでしょう。

攻略計画 逆伝播は4つの基本的な式を基礎とします。 これらを組み合わせると、誤差δlとコスト関数の勾配を計算ができます。 以下でその4つの式を挙げていきますが、1点注意があります:これらの式の意味をすぐに消化できると期待しない方が良いでしょう。 そのように期待するとがっかりするかもしれません。 逆伝播は内容が豊富であり、これらの式は相当の時間と忍耐がかけて徐々に理解できていくものです。 幸いなことに、ここで辛抱しておくと後々何度も報われることになります。 この節の議論はスタート地点に過ぎませんが、逆伝播の式を深く理解する過程の中で役に立つもののはずです。

誤差逆伝播の式をより深く理解する方法の概略は以下の通りです。 まず、 これらの式の手短な証明を示します。 この証明を見ればなぜこれらの式が正しいのかを理解しやすくなります。 その後、これらの式を擬似コードで書き直し、 その擬似コードをどのように実装できるかを実際のPythonのコードで示します。 本章の最後の節では、誤差逆伝播の式の意味を直感的な図で示し、ゼロからスタートしてどのように誤差逆伝播を発見するかを見ていきます。 その道中で、我々は何度も4つの基本的な式に立ち戻ります。 理解が深まるにつれ、これらの式が快適で、美しく自然なものとさえ思えるようになるはずです。

出力層での誤差δLに関する式: δLの各要素は

(BP1)δjL=CajLσ(zjL).
です。 これはとても自然な表式です。右辺の第1項のC/ajLはコストがj番目の出力活性の関数としてどの程度敏感に変化するかの度合いを測っています。 例えば、Cが出力層の特定のニューロン(例えばj番目とします)にそれほど依存していなければ、我々の期待通りδjLは小さくなります。 一方、右辺の第2項のσ(zjL)は活性関数σzjLの変化にどの程度敏感に反応するかの度合いを表しています。

ここで注目すべきなのは (BP1)

δjL=CajLσ(zjL)
中の全ての項が簡単に計算できる事です。 ニューラルネットワークの挙動を計算する間にzjLを計算でき、さらに若干のオーバーヘッドを加えればσ(zjL)も計算できます。従って、第2項は計算できます。 第1項に関してですが、C/ajLの具体的な表式はもちろんコスト関数の形に依存します。しかし、コスト関数が既知ならばC/ajLを計算するのは難しくありません。 例えば、2乗誤差コスト関数を用いた場合、C=12j(yjaj)2なので、C/ajL=(ajyj)という簡単に計算できる式が得られます。

(BP1)

δjL=CajLσ(zjL)
δLの各要素に対する表式です。 この表式自体は悪くはないのですが、逆伝播で欲しい行列を用いた表式ではありません。 この式を行列として書き直すのは容易で、以下の様に書けます
(BP1a)δL=aCσ(zL).
ここで、aCは偏微分C/ajLを並べたベクトルです。 aCは出力活性に対するCの変化率とみなせます。 (BP1a)(BP1) は同値である事はすぐにわかります。ですので、以下では両者の式を参照するのに (BP1) を用いる事にします。 例として、2乗誤差コスト関数の例ではaC=(aLy)です。 従って行列形式の (BP1) は以下のようになります。
(30)δL=(aLy)σ(zL).
見ての通り、この表式内の全ての項がベクトル形式の表式となっており、Numpyなどのライブラリで簡単に計算できます。

誤差δlの次層での誤差δl+1に関する表式: これは以下の通りです

(BP2)δl=((wl+1)Tδl+1)σ(zl).
ここで、(wl+1)T(l+1)番目の層の重み行列wl+1の転置です。 この式は一見複雑ですが、各要素はきちんとした解釈を持ちます。 (l+1)番目の層の誤差δl+1番目が既知だとします。 重み行列の転置(wl+1)Tを掛ける操作は、直感的には誤差をネットワークとは逆方向に伝播させていると考える事ができます。 従って、この値はl番目の層の出力の誤差を測る指標の一種とみなすことができます。 転置行列を掛けた後、σ(zl)とのアダマール積を取っています。 これによりl番目の層の活性関数を通してエラーを更に逆方向に伝播しています。 その結果、l番目の層の重みつき入力についての誤差δlが得られます。

(BP2)

δl=((wl+1)Tδl+1)σ(zl)
(BP1) と組み合わせる事で、ニューラルネットワークの任意の層lでの誤差δlを計算できます。 まず、δLを式 (BP1) で計算します。 次に、式 (BP2) を適用してδL1を計算します。 その後、再び (BP2) を適用して、δL2を計算します。以下これを繰り返してニューラルネットワークを逆向きに辿る事ができます。

任意のバイアスに関するコストの変化率の式: 具体的には以下の通りです

(BP3)Cbjl=δjl.
すなわち、誤差δjlはコスト関数の変化率C/bjl完全に同一です。 (BP1)(BP2) からこの値の計算方法は既にわかっているので、この事実はは好都合です。 (BP3) を簡潔に
(31)Cb=δ
と書くことができます。ここで、δの各成分は同じニューロンのバイアスbで評価した値と解釈します。

任意の重みについてのコストの変化率: 具体的には以下の通りです。

(BP4)Cwjkl=akl1δjl.
この式を見ると、偏微分C/wjklを計算方法が既知のδlal1を用いて計算できることがわかります。 この式はもう少し添字の軽い式で
(32)Cw=ainδout,
と書き直せます。ここで、ainは重みwを持つ枝に対する入力ニューロンの活性で、δoutは同じ枝に対する出力ニューロンの持つ誤差です。 重みwとそれに接続する2つのニューロンだけに焦点を絞ると、この式は以下のように見ることができます:

(32) からainが小さい(ain0)時には、勾配C/wも小さくなる傾向があるという結論が得られます。 このような状態を重みの学習が遅いと表現します。その意味は、勾配降下法を行っている間、値が大きく変化しないという事です。 同じ事の言い換えですが、式 (BP4) の帰結の1つとして、活性の低いニューロンから入力を受けとる重みは学習が遅いとわかります。

(BP1)

δjL=CajLσ(zjL)
- (BP4) からわかる事は他にもあります。 出力層から見てみましょう。 (BP1) 内のσ(zjL)の項に注目します。 前章のシグモイド関数のグラフを思い出すと、σ(zjL)01に近づくとき関数σはとても平坦になっていました。 これはσ(zjL)0の状態です。 従って、出力ニューロンの活性が低かったり(0)、高かったり(1)すると、最終層の学習は遅い事がわかります。 このような状況を、出力ニューロンは飽和し、重みの学習が終了している(もしくは重みの学習が遅い)と表現するのが一般的です。 同様の事は出力ニューロンのバイアスに対しても成立します。

出力層より前の層でも似た考察ができます。特に (BP2)

δl=((wl+1)Tδl+1)σ(zl)
内のσ(zl)の項に注目します。 この式は、ニューロンが飽和状態だとδjlは小さくなる傾向がある事を意味します。また、さらに飽和状態のニューロンに入力される重みの学習も遅くなることを意味します*。 *(wl+1)Tδl+1が十分大きく、σ(zjl)が小さくてもその埋め合わせができるならば、この推論は成り立ちません。ここでは一般的な傾向について述べています。

まとめると、入力ニューロンが低活性状態であるか、出力ニューロンが飽和状態(低活性もしくは高活性状態)の時には、重みの学習が遅いという事がわかりました。

これらの知見はどれも極端に驚くべき事ではありません。 しかし、これらの考察を通じてニューラルネットワークの学習過程に関するメンタルモデルの精緻にできます。 さらに、以上の考察を逆向きに利用する事ができます。 これら4つの基本方程式は任意の活性化関数について成立します (後述のように、証明に関数σの特別な性質を用いていないからです)。 従って、これらの式を利用して好きな学習特性を持つ活性化関数を設計する事が可能です。 アイデアを示すために例を挙げると、例えば(シグモイドではない)活性化関数σとしてσが常に正で、0に漸近しないものを選んだとします。 すると、通常のシグモイド関数を用いたニューロンが飽和した際に起こってしまう学習の減速を防ぐ事が可能です。 この本の後ろでは、この種の修正を活性化関数に対して施します。 (BP1)

δjL=CajLσ(zjL)
- (BP4) の4つの式を覚えておくと、なぜこのような修正を行うのか、修正でどのような影響が起こるかを説明するのに役立ちます。

問題