Deep Learning で重要な誤差逆伝播法に入ります。Neural Networks の偏微分に合成関数の微分公式を適用して高速化するアルゴリズムと理解しています。「合成関数の微分」は 英語で 'chain rule' 、 'chain rule' の訳が’連鎖律’(または’連鎖率’)となります。私の好きだった(古~い)数学の公式集(モノグラフ24公式集,科学振興社1976年改訂版)には「合成関数の微分」はありますが’連鎖律’ (’連鎖率’)はありません。「多変数関数に関する合成関数の微分公式」を’連鎖律’又は’連鎖率’と呼ぶ場合もあるようです。 ニュートラルネットワークでは、この偏微分の公式を総じて’連鎖律’とされることが多いようです。’chain rule’ の訳として’連鎖律’は正しいと思いますが、「合成関数の微分」の方が内容を少し説明するので個人的にはいいとは思います(頭が古~い)。この呼び方を使います。
Deep Learning の基本はニューラルネットワークの構築と誤差最小化による最適化にあると思っています。前回までは誤差の最小化に差分偏微分([d→0], (f(x+d)-f(x))/d) )で求める勾配法を使用しましたが、時間が必要でした。誤差逆伝播法は合成関数の微分公式(連鎖律)を利用した高速処理アルゴリズムです。高速化は、単なる時間短縮以上の効果が有る様です。実装しながら比較を試みました。
なお、本記事では数式による解法を行っています。 個人的には「合成関数の微分」に慣れが有り(大学入試でさんざん使ったように気がします)、新たな計算グラフの解法にはちょっと馴染めません。今回は長い内容なので、目次を作りました。
1. 差分偏微分によるDeep Learningプログラム
最初に、前回の記事までで作成した差分法による数値微分を用いたDeep Learningプログラムを整頓します。ネットワーク構成は隠れ1層で、以下の関数になります。なお、構成変更は自由に出来るようにします。
重みWとバイアスBは別の関数にしました。1つ1つの関数は出来ればシンプルにした方が良いと思います。各式の右側にはその関数が変数として使うパラメータを( )内で示した略式を表示しています。
Deep Learningプログラムは5つのユニットに分けました。
1)メインプログラム 2)データ取り込み関数 3)ニュートラルネットワークの構築と学習クラス 4)レーヤ関数のクラス 5)プロットのクラス
1)メインプログラム
メインプログラム構成は ①データの取り込み ②ニュートラルネットワークの構成設定 ③学習条件の設定 ④ニューラルネットワークの生成と学習です。それぞれの実行処理は関数やクラスで行います。ネットワークレーヤーの構成はテキストのリスト配列にします。重み関数'internal_product'、バイアス関数'add_bias'、シグモイド関数'sigmoid'、ソフトマックス関数'softmax、相互クロスエラー関数'cross_entropy_error'、平均'average_error' の組み合わせになります。重み関数の出力のノード数は入力ノード数と異なりますので、出力ノード数をパラメータに加えます。隠れ1層のノード数はL1_nodeとして設定します。
【プログラムXII_1: Main_7seg(7セグメント) 】
2)データ取り込み関数
データの生成・読み込みを関数にまとめます。①7セグメントLEDのデータ生成(前々記事[Neural Networksの7-segmentLED認識] )と、②MNISTデータ読み込み(前記事[Neural NetworksによるMNIST認識]) の関数をまとめてあります。
【プログラムXII-2: XII_data 】
3)ニュートラルネットワークの構築と学習クラス
ニュートラルネットワーククラスでは、関数レーヤをオブジェクトとしてリストにします。layer_forwardメソッドで、ネットワークのレーヤ処理を順番に行い、ネットワーク構成に従った処理結果を算出します。 studyメソッドではネットワークパラメータの偏微分値を求めて、勾配法によるパラメータ更新学習を行います。 学習状況を表示するプロットのオブジェクト(self.plotfig)も生成。学習の進捗はtestandplotメソッドで評価用データによるニューラルネットワークの推計と結果をプロット表示します。
【プログラムXII-3: XII_NeuralNetwork 】
4)レーヤのクラス
関数レーヤのクラスです。重み関数(内積)'internal_product'、バイアス関数(加算)'add_bias、シグモイド関数'sigmoid'、ソフトマックス関数'softmax'、エラー関数'cross_entropy_error'、平均’average_layer’ があります。 関数にはメソッドとして、パラメータ数(elements_num)、順処理(forward)、出力(value)、パラメータ更新(update_p) があります。重み関数とバイアス関数では初期値設定、偏微分パラメータの指定メソッド(set_elemnt)、加・減算(plus_d)メソッド、偏微分値のセットメソッド(set_grad)が追加されます。エラー関数には教師データの設定(set_t)メソッドがあります。
【 プログラムXII-4: XII_Class_layers_function 】
5)プロットのクラス
プロットは7segumentLEDとMNIST のイメージをプロットするものと、グラフのプロットの選択です。 プロット方法は前記事[Neural Networksの7-segmentLED認識] と[Neural NetworksによるMNIST認識] とほぼ同じです。
【 プログラムXII-5 XII_class_figs_plot 】
6) 実行_1
メインプログラムを実行します。
【FigXII-2】
200~300回の学習で100%の正答になります。時間は50秒程度でした。
メインプログラムのplot_typeを'graph'にしてプロット間隔を10に設定すると下の左グラフになります。さらに、学習終了時のプロットだけにする(プロット間隔400)と右グラフです。4~5秒程度で100%に到達します。 プロット処理の時間が占める割合が大きいことが分かりました。
【FigXII-3】 , 【FigXII-4】
7) 実行_2 MNIT文字の学習
MNIST での学習に移ります。使用するデータをMNISTに変更して、plot_typeを’MNIST100’にします。
隠れ層のノード数20、学習回数 100、バッチサイズ 100、δ値 0.0001、学習効率 0.1を設定しました。
【プログラムXII_6: Main_MNIST (手書き数字) 】
結果です。 ,MNIST の判別学習は画像データも大きく、時間が掛かります。 100回の学習で65%程度の正答率を得るのに35分ぐらいかかっています。やはり、くねくねとした数字の読み取りが難しいようです。プロットの処理時間の割合はまだ小さいです。
【FigXII-5】
2. 誤差逆伝播法
1) 合成関数の微分(連鎖律)
合成関数の微分(連鎖律)は
という公式です。 たぶん、高校の数IIIの学習内容です。(詳細説明は省略。習っていない方は数学の先生か数学の得意なお友達に聞いてください)
。 ’重み関数’や’バイアス関数’の係数は、通常、定数として扱われますがDeep Learningではこれを変数として係数(パラメータ)の最適化を行います。
式[XII-1]での簡略式を使い、最終の出力Lについての偏微分を合成関数の微分公式(連鎖律)に当てはめて展開します。式[XII-1]の最後の式(下の式)から遡っていくと下記の式です。同じ関数で変数が2種類あるものは横に並べて示しました。
Deep Learningで求める偏微分値は右側の式(5)(7)(10)(11)の値となります。 先の差分法によるDeep Learningではこの値だけを求めています。誤差逆伝播法では、左側の偏微分値も導出過程で必要となります。
なお、逆伝播の最初の値は出力 L の L 自体での偏微分になるので 1 です。 また、左側最下段に有るはずの最初の入力Xでの偏微分(書いていない)は、次の偏微分式(ネットワーク上の前の関数)が無いので不要となります。 式[XII-3]には多くの偏微分がありますが、順に求めた偏微分値を利用すれば11個の偏微分値を求める事で最終出力Lに対する偏微分値が求められます。同じ関数では同じ処理プロセスが出来ますので、クラス化すれば関数としては8個です。
2)合成関数の微分(連鎖律)の適用 (偏微分は差分法)
最初に整理したプログラムとモジュールを修正します。 ここでは差分微分法と誤差逆伝播法が選択できるように拡張していきます。 修正したプログラムモジュールの名前も変えていきます。 ここでの逆伝播法を適用したファイル名には’_r’ (レーヤクラスには’_grad’)を付けました。
(1)メインプログラム
モジュール名の変更と学習モードとして差分法と誤差逆伝播法の選択を追加します。
【プログラムXII_1_r : Main_7seg(7セグメント) 】
(2)データ取り込み関数 : ここは変更がないので、このまま。
(3)ニュートラルネットワークのクラス
studyメソッドの引数に学習モード(study_mode)が追加されますが、"reverse_numeric"モードでは、各レーヤ関数の偏微分値は差分法で求めます。
【プログラムXII-3_r: XII_NeuralNetwork_r 】
(4)レーヤのクラス
レーヤクラスに偏微分の為のメソッドを追加します(set_layerno メソッドとdiff_numeric メソッド)。
関数の偏微分値計算にはこれまでと同じ差分法を使用します。差分法はシンプルなので理解しやすいメリットがあります。また、導関数(微分式)が解らない関数につても適用が可能なのが大きなメリットでしょう。数値偏微分メソッド(diff_numeric)は、修正以前はニュートラルネットワーククラス内で行っていた差分偏微分をレーヤのメッソドで実行したものになります。
layerno メソッドは関数が何番目のレーヤであるかを記録します。 最初のレーヤ(0番目)では、パラメータの偏微分計算は行いますが、変数の偏微分計算は行いません。最初のレーヤでの計算量が特に多くなる場合があります(MNISTでは726ドット×隠れ1層目ノード数)ので効果があります。また、変数のデータタイプにも注意が必要なので無駄な処理が省けます。
偏微分メソッドをレーヤクラスに実装します。基本的には2つのステップを追加したものとなります。
a. レーヤ関数出力に対する入力(in_x)値での関数偏微分値(partialgrad)を求める 。
b . 引数で逆伝播した次レーヤの偏微分値(grad_y)と、関数偏微分値(partialgrad)を掛け合わてそのレーヤの偏微分値とする。
【 プログラムXII-4_r: XII_Class_layers_grad 】
(5)プロットのクラス : 変更なし
(6) 実行_1 (誤差逆伝播_差分法)
このプログラムを実行すると以下となります。100%到達まで30-40秒、400回学習に70秒となり、10秒程度の短縮です。 この場合、プロット処理時間の為に効果はあまり強く感じられません。
【FigXII-6】
plot_typeを’graph’に、plot_stepを10 (左グラフ).。終了時のプロットだけにする(右グラフ)と以下になります。 速さが格段に上がりました。400回の学習は2秒弱(さっきは4-5秒)で、3倍ぐらいの速さになります。
【FigXII-7】 【FigXII-8】
(7) 実行_2 (誤差逆伝播_差分法) MNIT文字の学習
MNIST での学習の場合(学習条件は前と同じ)です。
【FigXII-9】
1500秒ぐらいです。 処理速度としては1.5倍ぐらいに上がっています。
3)導関数と合成関数の微分(連鎖律)の適用
今回のニューラルネットワークを構成する関数はすべて微分が可能です。レーヤ関数の関数偏微分をすべて導関数に置き換えることが出来ます。 レーヤ関数のクラスに導関数による偏微分メソッドを追加するだけです。 メインとネットワークモジュールは少し修正して関数偏微分の計算方法を選択できるようにしておきます。 レーヤクラスのモジュールを別途’_deriv’ とします。他のプログラムファイル名は同じです。
(1) メインプログラム 【 プログラムXII-7: Main_MNIST (deriv) 】
(2) ニュートラルネットワーククラス 【 プログラムXII-8: XII_NeuralNetworks_deriv 】
(3)レーヤ関数クラス
レーヤ関数の微分値導出に導関数を使用するメソッドとして、deriv メソッドを追加します。
a) 重み関数'internal_product'
重み関数は下のように入力xと行列Wと内積で表現されます。
【XII-4】
まず、重み関数の係数である行列Wについて偏微分の導関数を求めます。
重み関数の入力Xを3要素、出力Aを4要素とすれば、重みWは(3×4)の行列になります。
【XII-5】
これを、例えば、w(2,3)について偏微分すれば、
【XII-6】
となります。 全部の要素に拡大していくと、次式のように w(i,j)についての偏微分は出力yと同じ配列で j 番目の要素が x(i)で、その他は0となります。
【XII-7】 [ ()内に配列のインデックスを示しています ]
一方、入力X の要素xについて偏微分については、
例えば入力Xの1要素x[3]についてば、
【XII-8】
となることがわかります。Xの全要素に拡張すると
【XII-9】
と置けます。x(i)での偏微分値は重み関数配列Wの[i]行と同じになるということになります。
この導関数を、internal_layerのderiv メソッドとして追加します。diff_numericメソッドの差分微分値の計算の所を導関数に置き換えたメソッドになります。
【 メソッドXII-9.a internal_layer.deriv 】
b) バイアス関数
【XII-10】
です。 この関数は、入力Xを4要素配列とすれば出力Cも4要素配列、バイアスも4要素の配列となります。
【XII-11】
これを、b3について偏微分すれば
【XII-12】
となり、バイアス関数の一般形式での偏微分値は次式のようになります。
【XII-13】 [ ()内に配列のインデックスを示しています ]
そして、入力Xの要素についての偏微分値についても次式になります。
【XII-14】 [ ()内に配列のインデックスを示しています ]
【 メソッドXII-9.b bias_layer.deriv 】
シグモイド関数では最適化するパラメータは無いので、入力xについて偏微分だけを求めるメソッドになります。 シグモイド関数とその導関数は、
【XII-15】
となります。この導関数をシグモイド関数のderiv メソッドとして追加します。
導関数の導出方法は数学的な解析で、紹介するなると長くなります。他サイトなどでも多く紹介されているので省略します。以降の関数でも数学的な解析部分は省略します。
【 メソッドXII-9.c sigmoid_layer.deriv 】
d) ソフトマックス関数
ソフトマックス関数では最適化するパラメータは有りません。 ソフトマックス関数とその導関数は
【XII-16】
となります。 偏微分の出力要素を(j)、偏微分の対象(i)によって条件分離が必要となります。
【 メソッドXII-9.d softmax_layer.deriv 】
e) loss関数
loss関数として相互エントロピーエラーを使っていますので、loss関数とその導関数は、
【XII-17】
となります。 tは教師データのラベルです。これは定数扱いとなります。 Σで合計するのはノードについてとなり、バッチデータとしては残ります。従って、loss関数では、入力Xは[バッチ数×ノード数]の2次元配列ですが、出力Yは[バッチ数]の1次元配列となって、次元が減少します。 反対に、誤差逆伝播法では、逆伝播してくる出力の偏微分[バッチ数]から入力の偏微分[バッチ数×ノード数]に次元を増やす処理が必要です。
【 メソッドXII-9.e ce_error_layer.deriv 】
f) 平均処理
平均処理の関数とその導関数です。
【XII-18】
簡単な関係式ですが、入力が[バッチ数]で出力が単数になります。 逆伝播では単数を配列に拡張する必要が有ります。
【 メソッドXII-9.f average_layer.deriv 】
(4) 実行
紹介したそれぞれの導関数メソッドを実装して出来るレーヤクラスの全体は以下になります。
【 プログラムXII-9 XII_Class_layers_deriv 】
実装が終了したので、メインプログラムから実行します。 処理速度が速くなったので7セグメントシグナルの分類学習では直ぐに終わってしまいます。 対象をMNIST の文字認識にします。 同じ学習条件です。
【FigXII-10】
早くなりました。 最初の差分法で約2000秒(FigXII-5)だったのが、90秒で終わりました。プロット処理時間を抑えてグラフ表示だけにすると、
【FigXII-11】
約40秒で100回の学習を終了しました。 50倍ぐらいの速さの処理が得られています。
3. 誤差逆伝搬法のまとめ
ここまでで、合成関数の微分(連鎖律)を適用して導関数による偏微分値の算出をしたところ、大変早い処理速度がえられました。 ここまでのプログラムには、3種類の偏微分法を残していましたが、導関数による誤差逆伝播法学習だけに整理をします。また、Python のブロードキャスト機能を有効に使える形式まとめます。
プログラム実装の計算処理は、最終の出力を L 、レーヤの関数を入力X(要素はx)、出力をY(要素はy)としてレーヤ関数の偏微分は以下の数式にできます。 数学的に対応するというよりは、プログラム処理の整理としての数式です。数学的な恒等性から導出したものではありません。
【XII-19】
入出力の配列次元が変化する場合や、処理中に次元の異なる配列を扱う場合には、バッチ配列でもPythonのブロードキャストの機能を利用出来るように、配列の構成変更(reshape)が必要でした。
上記の偏微分式を使ってプログラムに再実装しました(fine版)。 差分偏微分での処理も削除しています。
(1) メインプログラム 【 プログラムXII-10: Main_MNIST (fine版) 】
(2) ニュートラルネットワーククラス
【 プログラムXII-11: XII_NeuralNetworks_fine 】
(3) レーヤ関数クラス 【 プログラムXII-12: XII_Class_layers_fine 】
(4) 実行結果
【FigXII-12】
処理スピードはさらに速くなります。
これだけ早いと、さらに予測精度の高い学習結果を目指すことが出来ます。 学習回数500回、隠れ1層のノード数100, バッチサイズ200、Plots_tep20 での達成は以下となります。
【FigXII-13】
2分半程度の時間で正答率約 91%に達しました。 左側の結果からはやは判定が難しそうな文字の判定で間違い(青)が有ることがわかります。
4. まとめ
ニューラルネットワークの学習プログラムを機能別クラスに分けて作成。 学習の高速化には誤差逆伝搬法を使用すると大変高い効果を得ることが出来ます。 誤差逆伝搬法の処理をレーヤ層のクラスで実行させることで、導関数や差分法を混在して利用することも出来ます。これによって、導関数が不明または複雑なレーヤ関数の場合でも、差分偏微分法で設定すればよいことになります。
5.参考文献 / 参考リンク
[※] このブログは教本として、『ゼロから作るDeep Learning Pythonで学ぶディープラーニングの理論と実装』 [ 斎藤 康毅 ] 【出版社: オライリージャパン 発売日: 2016/9/24 】 (ISBN-10:4873117585 ISBN-13:978-4873117584) を使っています。
[※] pythonの教本として何かまとまった1冊を持っているといいです。 ネットで調べれば細かい所や、実例を沢山見ることができますが、導入として「何を」調べたいのかを知るにはまとまった教本が必要です。最近は『独学プログラマー Python言語の基本から仕事のやり方まで [ コーリー・アルソフ ] 』を参考にしています。
6.あとがき
前の記事から大変長い時間が空いてしまいました。逆伝搬法の処理プログラムは割と早い段階で一度は完成したんですが、原稿化の為に見直しをしていると、次から次へと改善点が出てきてしまいました。 改善のアイデアも実装してみると冗長だったり、ほかの計算との相性が悪かったりしてやり直しばかりでした。 しかし、誤差逆伝搬法の効果は絶大ですので、この方法を考えた方、素晴らしい、としか言いようがありません。
*🐭*🐍*🐭*🐍*🐭*🐍*🐭*🐍*🐭*
0コメント