1. ニューラルネットワークの学習 3
【簡略化と高速化】
この回では、前回(学習 2)で紹介出来なかったプログラムの簡略化と改良(速度改善)を紹介します。 前回のプログラム作成中に気が付いたところや、テキスト( 『ゼロから作るDeep Learning Pythonで学ぶディープラーニングの理論と実装』*1) )のプログラム(GitHub中)手法を勉強して改良した内容です。
後で行う誤差逆伝搬法(テキスト第5章:次々回の記事ぐらいに予定)での高速化とは違い、自己流で少しプログラム改善をした結果の紹介です。
1) おさらい
2) 数値微分式の変更による高速化
3) グラフプロットの消去による書き換え
4) プログラムのステップを考慮した簡略(高速)化
というの内容となります。
1) おさらい
まず前回作成したプログラムを紹介します。 四つのゲートロジック AND,OR,NAND,XOR のニューラルネットワークを実装しました。 プログラムは全体が長く、だいぶ無駄もあるようです。(まだまだ自己の学習不足を感じます。 )
まず、 以下のプログラムでは多すぎたのコメントを削除しました(修正の邪魔になってしまった。読みたい方は前回の記事を見てください。) 。 また、学習回数を200回に設定すると約2分(111秒)で、結果はこの様に[Fig.IX-0] なりました。
AND,OR,NAND はなんとかロジックになっていますが、明確な処理ではありません。XORでは全くロジックになっていませんでした。
この後は、学習回数を20,000回(100倍)に設定します。 上の従来プログラムだと、100倍、11,100秒で3時間はかかる計算になります。 このプログラムでは毎回グラフィックを更新していたので、変化の様子が良く分かるのですが、グラフィックの負荷が大きくなって遅くなっています(私のPCのスペックが低いのも原因) 。
まずは、グラフ表示を学習1000回に1回のプロットに変更。処理時間はtimeパッケージをインポートして測るようにしました。
【プログラム Ⅸ-1】
# 学習回数が設定値の倍数になった時(割った時の余り999)にプロットする様にしました。(MAINの学習ループ後半)
# パッケージのtimeもインポートして処理時間も判る様にします。 (head部、最終-1行目)
【結果 Ⅸ-1】
AND、OR、NAND は安定的になりました。 でもXORは正しい判断に至りませんでした。
処理時間は約160秒でした。 70倍ぐらいの早さです。 グラフィックに相当の時間が掛かっていたことになります。
2) 数値微分式の変更による高速化
プログラムをよく見ると、出力yのメソッド(value_y)呼び出しはクロスエントロピー誤差の計算とプロットの時だけです。 そのクロスエントロピー誤差の計算は学習ステップで各パラメータの偏微分計算のために+δと‐δでの計算しますが、その時点での出力yはクロスエントロピー誤差の計算では必要としていません。 プログラム作成中に少しに違和感を感じました。どうなっているのか?
ニューラルネットワークの学習はネットワークのパラメータ(W1,B1,W2,B2)を推計することを目的として誤差を最小にする修正を繰り返します。 学習効果をηとして偏微分値(gw1,gb1,gw2,gb2)によるの計算です。 プログラムでは次の計算になります。
gate.w1 -= eta * gw1
gate.b1 -= eta * gb1
gate.w2 -= eta * gw2
gate.b2 -= eta * gb2
[ Ⅸ-1 ]
出力yは知らなくても、偏微分値が分かれば学習が出来るということです。 ただし、数値微分値を求めるために出力yを求めるメッソドは必要となります。
数学的には微分は、
で、これを数値微分にしすると。
数値微分ではδを無限小には出来ないので僅かですが誤差が出ます。 それが ≒ です。 ならば、
でもいいはず。 この式は、B とAの関係をデルタの差として
となります。
[ Ⅸ - 4 〕では入力値Bから1/2dだけずれていると考えるだけで、 [ Ⅸ - 3 〕と同じです。 テキストでは、 正しい数値微分が[ Ⅸ - 3 〕だと説明をしています。 たしかに、 [ Ⅸ - 3 〕が正しいのですが、f(A) の値を使わないので、入力値が常に1/dずれているだけです。 もし、その時の正しい出力値または微分値を知りたければ、入力値に1/d(≂σ)足すことになります。 予測がほぼ収束した時には +σ変化させても微分値に変化はほとんどないし、その時のf(A)の値もほとんど変わりません。
[ Ⅸ - 4 〕を使用することにします。 各パラメータの-δ時のクロスエントロピー誤差を計算しないことになります。 元の各パラメータ値(W1,B1,W2,B2)での計算結果を繰り返し使えます。これは大きな利点でしょう。 微分メソッドの最初で基準となるクロスエントロピー誤差を計算(Error_nowとした)します。
【プログラム IX-2】
【結果 Fig.IX-2】 ほぼ先と同じ結果になりました。(ので、図はちょっと小さくします)
これでは 20000回の学習時間は 104秒 でした。 約35%の短縮になります。
3) グラフプロットの消去による書き換え
次に、処理の高速化は期待出来ませんが3Dプロット部分を書き直してみます。 これまでは、プロットする毎にcla()メソッドでプロット全体を消去していました。 出来れば、3軸のプロットを最初だけにしたいです。そこで、プロットのインスタンスをリスト収め、removeメソッドでリストに登録したプロットだけを消去します。(popメソッドなので、新しい方のプロットから順に取り出して、消去(remove)します。 ) プロットの関数に、引数として空のプロットリストをデフォルトで作ります。 最初の呼び出し(プロットリストが空の時、)にはグラフ軸を描きます。 次に、プロットリストに登録された前回のプロットを削除。 その後で、新しいプロットをすると同時にリストにプロットをインスタンスとして追加します。(プロット処理とリスト追加が同時に出来るのは、少し不思議です)。 プロット関数の呼び出しの時には引数を指定しなければ、前のプロットリストがそのまま残ります( ここも少し不思議なところ)。
【プログラム IX-3】
【結果 Fig.IX-3】 ここもほぼ先と同じ結果になりました。(ので、図はちょっと小さくします)
実行時間は約96秒でした。 少し早くなったのか、誤差のうちかもしれません。
4) パラメータの順次呼び出し
次に、nditer()コマンドを使って、for文ループしていた配列WやBのインデックスの記述を短くします。テキストのプログラム(GitHab上)で使っていますが、説明が無いので当初は良く解らなかった。検索して調べました。
it=np.nditer(W, flags=['multi_index']) で配列Wのインデックスを順に得ることが出来ます。 nditer()メソッドではインデックス変数にitを充てるネット例が多かったです。習慣的にitを使っているみたいですので、それに倣います。
今回のプログラムでは偏微分を行うw1,b1,w2,b2のインデックスを引き出します。 配列サイズが違うので、リストにして順次呼び出す様にします。 偏微分の計算結果も同じサイズのgrad変数に収めるので。同じサイズのリストにします。
【プログラム IX-4】
# w1,b1,w2,b2をall_arraysリストにして、それらの偏微分値をgrada_arraysリストに設定します。
# 偏微分計算のクラスメソッド名は def grad_multi に変えています( 変更前は def grad):
# 各配列をall_arraysリストのインデックスで指定して、各配列のインデックスをnp.nditer()で取得します。
# 偏微分値はgrada_arraysリストの同じインデックスで格納させます。
# リストのインデックス指定とNumpyのインデックス指定は記載方法が異なるので少し混乱しました。(まだまだ実力が付いていないですね)
【結果 Fig.IX-4】 ほぼ先と同じ結果になりました。(ので、図はちょっと小さくします)
実行時間は約99秒でした。 やはり誤差のうちかもしれません。時間短縮の効果はほぼありません。
プログラムは短くなりました。
4) プログラムのステップを考慮した簡略(高速)化
ニューラルネットワークを構成する式は
です。 学習では式[IX-6]のW(1),B(1),W(2),B(2)の偏微分を求めます。 偏微分の数値微分は各要素にδ値を加えた時の出力Yの変化分をδで除します(式[IX-4])。 ここで、、W(2),B(2)の偏微分に注目すると、A(1)、Zの結果に影響を与えません。 W(2)、B(2)の偏微分計算では、zの値を記録して置いて、A(2)計算から後を行えば済みます。 式[IX-6]をさらに分解して
として、各ステップでの計算値を保持。 偏微分で必要なところから+δの計算を始めればいいことになります。
ただし、どこかのステップで偏微分の計算を行うとその後の値が変化します。偏微分の計算は後の方にある関数から行うようにしました。
【プログラム IX-5】
【結果 Fig.IX-5】
実行時間は 約89秒 でした。
こんどはXORも正しく処理出来ています。 偶発的に出来ることがあります。初期値乱数に依って結果に影響するようです。
若干ですが早くなったようです。 ただ、W1やB1があまり大きな配列ではない為に、それほどの短縮にはなっていないようです。
なお、もし、20,000回の学習を途中でのグラフィック表示なしで行った場合は86秒でした。
5) 終わりに
今回は、簡単な改善活動をしてみました。 ディープラーニングに必須となる偏微分についての高速化を試行してみました。 計算ステップに分解して不要な計算を省略する考え方は、後で行う誤差逆伝搬法と共通する点だと思われます。 誤差逆伝搬法への導入になると思われます。
他に、今回も色々あるNumpy のメソッドの中から、その幾つかについて学習も出来ました。 また、Pythonの変数指定がアドレス指定をしているらしく、変数のコピーや、引数のデフォルト設定が微妙にこれまでに使ったことのある高級言語やBASICなどと異なることが解ってきました。(この辺り、まだ混乱しているところもあります。) そして、メソッドの終わりで戻り値が必要なければreturn文が不要なことに気が付きました(今頃になってです。BASICではReturnを忘れて・・・エラーが・・・ということが多かった)。
次回はもう一つ例題をつくって理解を深めることにします。
++++++++++++++++++++++++++++++++++++++
*1) このブログはテキストとして、ゼロから作るDeep Learning Pythonで学ぶディープラーニングの理論と実装 [ 斎藤 康毅 ]【出版社: オライリージャパン 発売日: 2016/9/24 】 (ISBN-10:4873117585 ISBN-13:978-4873117584) を使っています。
2) 線形代数での行列については各種の専門書が有ります。 基本的で分かり易い本として 高校数学でわかる線形代数 (ブルーバックス) [ 竹内 淳 ] などがありますA。
3) Pythonでの描画はmatplotlibを使用します。matplotlib サイト のチュートリアルは機能を調べるのには便利でした。 例えば、Tutorials » Annotations(注釈)辺りから調べます。https://matplotlib.org/tutorials/text/annotations.html#sphx-glr-tutorials-text-annotations-py
* ) 独学ではなかなか進まない方は、やはり有料になりますが、専門セミナーを受けられることをお勧めします。
0コメント