Neural Networkの学習(1) (Deep Learnig StepVII)

 これまでで、ニューラルネットワークの基本となるプログラム部分のほとんど導入が出来たようです。 今回は、ディープラーニングの実装において重要な「損失」に関する処理です。 ディープラーニングは繰り返して学習して賢くなります。このためには学習成果を評価する必要があります。 学習成果を評価して、更に学習を進めることになります。  この学習成果の評価(テスト)として、損失(誤差、Error などとも呼んでいます)関数を導入します。 

 通常、ディープラーニングは不完全な問題も扱うので間違えることが有るのが前提でしょう。 猫と犬の写真を分類する問題で猫だか犬だか解らない写真(本物を見ても判らない)では、ディープラーニングがよく学習していても間違える(判らない)ということはあります。 

 今回、第一段として、不完全な問題を解く前に、明確な回答が得られる数学的な関数を取り上げてニューラルネットワークを構築します。 プログラミング初心者にとって、バグは当然にあります。文法エラーはエラー表示してくれるので直せばいいですが、アルゴリズムエラーは直すの難しいです。 ディープラーニングが不正確性を容認するので、まずは、明確な回答が有る関数問題をサンプルにします。  また、ニュラルネットワークの学習の様子をビジュアル化することも試みました。 画像、特に動画は挙動を見分けることが出来ます。 アルゴリズム上の間違いを直感的に疑うような挙動を見つけることが出来ます。  

 なお、この連載を書きながら、プログラムが高度になるにつれてプログラム説明が簡素になって来ていたように思われます。再度、初心者らしく、詳細にするようにしてみます。プログラム中級クラス以上の方は、読むのも怠いかもしれません。その場合、細かい所は読み飛ばして下さい。 また、 クラスオブジェクトの説明もしますが、なにせ、まだ勉強中です。用語としての「オブジェクト」と「インスタンス」の使い分けも明確には出来ていません。ほぼ同じものとして混同して使っています。ご容赦ください。 


1.学習する関数の設定   

 ニューラルネットワーク学習のサンプルとして、2次元関数

  を考えることにしました。  ここで A,B,Cは係数で X,Yが変数です。   ニューラルネットワーク学習のプログラムでは、係数A,B,Cを任意に決めて、そのA,B,Cを予測することが目標となります。 係数A,B,Cでの複数個のX,Yとその関数値F(X,Y) の組を教師データとして、ニューラルネットワークの学習用に提供。 係数A,B,Cに対応する予測値a,b,cを決めます。ここでは、目標とする関数の係数は大文字、学習中の関数の係数は小文字にして区別しています。 教師データの作成と目標関数の3D表示ににはA,B,Cを使いますが、学習には特定の教師データと予測係数のa,b,cだけを使います。

1) 位置 x , y に於ける f(x、y) を3次元で表現するプログラム  

最初のプログラムとして、関数の設定と3D表示を行うプログラムをつくりました。

Program[VII-1]

2) プログラムの説明 

PythonのIDLE エディタでは、プログラム行番号をOption→show Line Number で表示できます。 行数位置は空白行などでズレますので目安と思ってください。

# 利用パッケージ設定(1-3行目): 利用するパッケージとして、数値計算のnumpy、図表のmatplotlib.pyplot をインポートします。短縮がnpとplt。

# 3D表示用のオブジェクト(4-6行目) 3D描画には matplotlib のfiger をfig オブジェクトして、subplotに3dプロジェクションを追加設定します。オブジェクトはaxになる。 この辺りの設定は、初心者としては、「こうすれば3D表示が出来るのだ」ぐらいの感じでやっています。なお、以前はmpl_toolkits.mplot3d.axes3d ライブラリのインポートが必要(ネット記事でよく説明のある設定)だったけど、Matplotlib 3.2.0からは要らないようです。(詳しくは、チュートリアル[※1]などを参照)。

# 数式用フォントの設定(7-9行目)  数式用フォントにComputer Moren(cm)を使い、標準をイタリック(it)に設定しました。グラフなどの数式表示にTexを使います[※2]。Computer Moren(cm)はTexで数式表示する時にイメージに合う文字でした。

# 3D表示の関数設定(11-19行目) 3次元プロットをする時の関数としてplot_fxy を定義ました。引数はx,yとz 。 グラフタイトルは文字列title_textに定義しておきます(後で度々変更します)。 各軸ラベルの設定。平面x,yと値zをメッシュ状にax.plot_wireframe(x,y,z) で描画します。wireframe ではパラメータX,Y,Zは2次元配列にする必要があります[※3](meshgridで生成した配列はそのままで使える)。 plt.show()で表示します。

# *** main ***(22-31行目)  メインでは、まず3D表示用に2D配列設定をします。軸の要素数をPoint_N とし、20個に設定。x,y軸の始まりと終わりの値を決めて、x_pointとy_pointとしlinspace で生成します。meshgrid で 2次元配列(20×20≂400ポイント)が作れます。グラフのx, y範囲を自由に設定出来る様になります。 wireframe を使用する時には、x,yの数が同じでないとワーニング発生します。これはwireframe仕様によるものらしいです。linspace の引数(3番目)で、要素の個数を同じPoint_Nで指定することwornigを防止できます。 arenge 関数での配列生成では要素数を考えないといけないので linspace 関数の方が便利です。  次に、目標となる関数の係数A,B,Cを設定し、fxy で関数値配列Fを計算しています。Fもx,yと同じサイズの2D配列となります。x,yとFでwireframe を描きます。

3) 実行結果

これが、目標とする関数の3D表示ということです。


2. ニューラルネットワーク関数のClass化  

 ニューラルネットワークの関数計算の部分はクラスを作って、オブジェクトにした方が良さそうです。 最初は単なる関数オブジェクトですが、後でニューラルネットワークのオブジェクトとして繰り返しオブジェクトを作ります。少し早いですが、インスタンス名の頭にNWを付けることにしました。  また次の改良で、3D表示を2つ重ねられるようにしました。

1) ニューラルネットワークのクラス追加

上記で作成したプログラム[VII-1 ]の関数.[VII-1]の部分をクラス定義します。 

Program[VII-2]

2) プログラムの説明 (修正と追加の部分)

# ネットワークのクラス定義(11-23行目) クラス名NetWork 引数はslfと関数のパラメータa,b,c、平面位置のx, y データ。インスタンスが生成された初期(__init__)で引数をインスタンス変数( self.a,self.b,… )にしました。クラス内のどの変数をインスタンス変数にする必要があるのかよく判判らないけど(勉強不足です)、全部インスタンス変数にして於きました。そして、計算結果の関数値もインスタンス変数( self.z )にします。計算結果 z はオブジェクトの f() メソッドで回答されます。

# 3D表示の関数設定の変更(24-36行目) 3D表示に、同じ x, y 位置で、2つの z の表示を可能にします。z2 を引数に追加します。z2 が無い場合には z2 のデフォルト値= None を設定ます。後の、処理判断(34行目のif 文)で z2 が None 以外の場合に赤い(color='red') wireframe を重ね表示するように修正しました。 z2 が有る場合、z2 は配列データなので、判断ではnp.allを使い、すべての配列要素で確認しています。 タイトルと縦軸の表記も修正しています。

# メインプログラムの変更[1](43-45行目) クラス化した関数式は削除して、NetWork クラスのオブジェク NW_Goal を生成します。予測が目標とする関数なので’ゴール’としました。予測値のスタートとして、係数のa,b,c を初期設定して、オブジェクト NW_Now を生成します。ここでは、初期値a , b , c = -0.5, 3.0, 2.0 は目標からある程度離れていて、グラフ表示で差が分かり易いものを選んでみました。

# メインプログラムの変更[2](50行目) NW_GoalとNW_Nowの計算結果であるNW_Goal.f(), NW_Now.f()をPlotの関数に引き渡し、ワイヤー図を重ねて描きます。 NW_Goalはデフォルトの色で、予測NW_Nowは赤とします。

3) 実行結果

 学習目標のNW_Goalは青。 予測のNW_Nowは初期状態で赤です。


3.エラー関数の設定

 次に、エラー関数を設定します。 ここでは2乗誤差を計算します。2乗誤差はよく使われる割と単純な誤差関数で、誤差は0が最低でマイナスにはなりません。エラー関数を小さくすることが誤差が少なくすることになります。 誤差をエラー値 E として、係数A,B,C(目標値)でのF(x,y)と、現在の定数a,b,c(予測値)でのf(x,y)との差分を2乗 しす。

1) エラー関数の追加

 [VII-2]で、インスタンスNW_Nowのf()メソッド値が予測値で、インスタンスNW_Goalのf()メソッド値が目標となる関数値なりますので、

をプログラムに追加します。

 Program[ VII-3 ]

    

2) プログラムの説明 (修正と追加の部分)

# NetWork にerror メソッドの追加(23-25行目) NetWorkクラスに差分を2乗して返すメソッドを追加します。目標とする値を教師データ T として引数で受けて、オブジェクト自身(予測係数でのオブジェクト)の結果 self.z との差を2乗したものを返します。このメソッドは、self.z と T が1変数の場合は1変数を返し、zとTが配列(サイズは同じ)の場合は同じサイズの配列で返します。 ニューラルネットワークの学習では z、T は1変数ですが、 3D表示をする場合には配列として処理をしています。 

# 3D表示の関数の変更(27-33行目) タイトルと縦軸の表記を修正。 

# メインプログラムの変更(52-53行目) 予測中の誤差値 Error_Now を得るために、NW_Now の errorメソッドで目標データの値 NW_Goal.f() を引数にします。 3D描画の関数 plot_fxy でNW_Goal.f()とErrro_Nowを表示します。

3) 実行結果

 学習目標のNW_Goalは青。 予測係数の初期状態での誤差値Errro_Nowが赤です。 2次関数なので、原点から離れたところで大きく乖離しているのが分かります。


4.偏微分メソッドとループ処理の追加  

 予測係数a,b,cをエラー値が最小になるように繰り返し修正します。勾配法と呼ばれる解法を使用します。修正量を関数値 f(x, y) の予測係数値での偏微分(傾き)と、学習効果という係数ηを掛け合わせた量にします。

 この傾きとして用いる偏微分値は数式としては

となりますが、δが十分に小さい値であれば、数値解析的には近似式として

を使えます。 プログラムでの偏微分の数値的算出実装ではδ≂0.0001としておきます。

係数b,cそれぞれについても同じ数値解析的式となります。

この偏微分値が求まれば、 次の各係数を学習率ηを適用して次の式で求めます。  

この式でsは学習の回数を示します。 また、f値を求めるには位置 x、y と 教師データとしての値が必要です。位置 x、y は先に作った平面配列 x,y の組み合わせの中からランダムに選ぶことにします。これによって、Graphで描いた NW_Goal のどこかのメッシュ上の1点が教師データに選ばれます。1回の学習では教師データを1つ選んで少し(ηの値分)学習をします。

 s回の学習をした時の予測の係数a,b,cを使って、[VII-5]、[VII-6]、[VII-7]をプログラム実装します。 先ずは、aだけをa+δ と a-δに置き換えて b, c, x, y は置き換えのない同じ値での f(a+δ) と f(a-δ) というネットワークのインスタンス(NW_plusとNW_minus)を作り、それぞれのError値を計算して、係数aの偏微分値grad_aを得ます。  同じようにすると b, c による偏微分値を求めることが出来ます。 偏微分値が求まれば、[VII-8]、[VII-9]、[VII-10]で 次の係数を求めます。これを繰り返せば徐々に目標の係数に近くなるという事です。

1) 偏微分メソッドの追加

 プログラムにNW_Now の係数a,b,c偏微分値の計算メソッドを加えます。 さらに、学習による係数 a,b,c の更新も行って、学習をN回繰り返すところまで修正したプログラムです。Nとして200回を設定ます。これはGoalデータが(x、y)それぞれ20ポイントですので、400点データの内から約1/2の200点を教師データとして選択したことになります。 (実際には乱数設定なので若干は重複して使用したポイントも有るかもしません)

Program[ VII-4 ]

2) プログラムの説明 (修正と追加の部分)

# delta値の追加(20行目): 数値微分のため、微小変化量delta値を定数0.001として設定。

# クラスに微分値(grad)メソッドの追加(27-40行目) 係数a, b, c それぞれで誤差(Error値)の偏微分値(傾斜)を求めるメソッドとしてgradを追加します。 誤差は教師データと予測データの差分なので、引数は講師データ T となります。NetWork クラスの新しいオブジェクトNW_plus とNW_minusを生成します。偏微分する係数以外の引数は親インスタンスNW_Nowの変数をそのまま引き継いでいます。変数aの偏微分は self.a に delta値を足した場合(plus)と、引いた場合(minus)を設定します。 NWインスタンスである plus と minus の Error値の差分を計算して delta の2倍で割ることで [VII-5]の偏微分値をもとめます。 同じ処理を係数 b, c でも行い、係数 a, b, c による偏微分値 grad_a, grad_b, grad_c を求めて返します。

# 3D表示の関数設定の変更(44-48行目) タイトルと縦軸の表記を修正。 

# パラメータの最適化(65‐78行目) 学習効率となるη(eta)を設定(0.005)。 繰り返しの学習回数としてN値を設定(200回)。 forループでN回の学習をさせます。回数はsでカウントしています。 毎回、教師データの組、平面(x,y)のポイントと値Tを1つ決めるます。 平面(x,y)のポイントはPoint_Nの範囲(20に設定している)中からランダムに選んで 対応する位置 ( x, y )=( i, j ) でのインスタンスNW_Pinを生成させます。インスタンスNW_PinのgradメソッドでGoalの値T を引数にすれば、偏微分値grad_a,grad_b,grad_cを得ることが出来る。 次に[VII-8]、[VII-9]、[VII-10]の更新式でa,b,cを更新する。 N回の繰り返しが終了したら、予測の結果a, b, cを print表示しています。

#3D表示の設定の変更(82-83行目) 学習回数Nもテキストとしてグラフ上に追加記載するようにした。 3Dグラフに2Dのコメントを記載するには、text2Dメソッドを使ってオプション設定を transform=ax.transAxes とする(と出来る)。 アニメーション化の前段階として目標のGoalデータと、学習した結果データを重ねて表示するようにした。 

3) 実行結果

 N= 200 a= 1.7574410174401403 b= 2.9897931920950658 c= 3.218983451810292 


まだ目標値 A , B , C = 3.0, 2.0, 3.0 からは隔たりが有ります。 グラフも一致しているとは言えません。でも、初期のGraph[VII-4]に比べると大分近くなっていることが判りました。 プログラムからはバグがほぼとれたということになります。(ここまでに、沢山のバグ取りが有りました。)


5.処理状態のアニメーション化

  学習効率ηは、結果に大きく影響をするようですが、その設定は少し難しいようです。 収束の様子を観察出来るようにすると、プログラムの実施がかなり遅くなりますがパラメータの設定は分かり易くなります。 

 1) 3Dグラフのアニメーション化

Program[ VII-5 ]  

2)プログラムの説明(修正と追加の部分)

# 3D表示の関数設定の変更[1]1(45行目) タイトルと縦軸の表記を修正。 

# 3D表示の関数設定の変更[2](54行目) アニメーションはプロットの途中を確認するようにして行います。pause()メソッドで一時的に止める(0.01秒)とその時のプロット状態を表示します。pauseの時間適当な時間に調整ます。

# メインにアニメーション処理の追加(78-85行目) 繰り返し学習の途中にアニメーションの処理を挿入します。 まず、一度プロットしたグラフをaxのcla()メソッドで消してます。 ηの設定値と、その時の学習回数 s 、エラー値E を3Dプロット中に記載すように設定しています。 s回学習したネットワークインスタンスNW_Nowを生成させて、目標の値NW_Goal.f()と予測値NW_Now.f()を3D表示します。

#最後に結果表示(87-88行目)  結果も3Dグラフに重ねるようにしました。 アニメーションも終わるので、show()メソッドを実施して終了です。

3) 実行結果

200回学習後の結果です。 変化に様子は以下のビデオを見てみて下さい。

η≂0.005 の場合  : なかなか収束せずに、目標の数値からも離れていました。  

η=0.05 の場合  : ほぼ一致して、赤いネットは青の後ろに隠れてみえません。。誤差Eも小さいです。 

η=0.5 の場合  : 途中で、赤いネットが大きく上下に暴れて収束しません。  途中で3Dアニメには変化が無くなります。

 ηの設定が不適切だとこうなるという事です。 なお、x、yの範囲を-1.5から+1.5にしているのでこの結果でしたが、範囲を広げると収束しにくくなります。 固まってしまった(η=0.5)理由は、各係数が大きくなりすぎて、偏微分のδ値が相対的に小さくなり過ぎてしまい変化しなくなったと思われます。


5.まとめ  

 ニューラルネットワークの実装の基本プログラムが出来ました。 特定の関数(ゴールのある関数)に対する数学的解を予測するケースでのプログラム作成ですが、これを元に、数値解析では難しいデータ分類の解析に適用することが出来るようになりました。(と思っています)。

 そして、学習効率ηが小さいと、なかなか収束しない(遅すぎるのも困りもの)。ηが大きすぎると、過度に変化が発生して発散してしまうことが判った。

 今回の記事はビデオにしました。 アニメーションはビデオで確認して下さい。

 ( 公開してから半年以上、ビデオのリンクが有りませんでした。申し訳ありません)

6.終わりに  

  実際、今回の実装には時間がかかりました。Pythonのプログラミングテクニックや、次回に紹介の正解のない分類問題、などを試行錯誤での練習を行ってから、今回の初歩的な実装例を説明するための資料の作成に入りました。 それでも、記事内容を整理する間に新たな問題やミスが見つかって数回のやり直しがあります。 すなわち、上記のようなプログラムとその結果を一気に達成出来たいのではないということです。 例えば、当初は全ての教師データについて予測値とのエラー関数値を求め、平均化すると思い込んでプログラムを作りました。これでは目標に達しないで収束するケースが発生しまた。 実際は、1つの教師データを選んでは、その度にエラー値を小さくする学習を行います。  今の段階でも、上記のプログラムやアルゴリズム、考え方に致命的なミスがないかは不安です。 一方、思わぬ収穫は、NW_Nowのクラスの中で偏微分のためのインスタンスを再帰的に作ったことです。 再帰呼び出しはいくつかのPython説明を読んで知っていましたが、使い方がピンと来ませんでした。特殊な関数を使う時だけだの話と思っていたのです。ところが、今回は結果的にクラスの中で同じクラスのインスタンス化をするプログラムになりました。このプログラミングが、良い方法なのか?悪い手なのか?まだよく分かっていません。 この先、「あれは間違えだった」と気が付いて、こっそりと修正することもあるかもしれません。 

 他の人が作ったプログラムをコピー&ペーストで使って使っていると、苦労なく正しい結論に辿り着きます。 でも、特に初学者は、間違いを繰り返しながら新しいことを調べて、取り込みながらプログラムの精度を高めていくのは勉強になります。(苦難は有ります)

 ほんと、今回は時間がかかりましたが、いい勉強になりました。


7.参考文献 / 参考リンク 

[※1]madplotlibの3dプロットのチュートリアルサイトです。

https://matplotlib.org/stable/tutorials/toolkits/mplot3d.html#sphx-glr-tutorials-toolkits-mplot3d-py

[※2] Texによる数式表示は、記事6『Matplotlibで図表と数式(TeX)』で紹介しています。 https://python-learnig.amebaownd.com/posts/7732623

[※3] madplotlibの3dプロットのチュートリアル[※1]のwireframe-plots(以下)です。wireframe ではx,y,zを2D arraysにします。

https://matplotlib.org/stable/tutorials/toolkits/mplot3d.html#wireframe-plots

[※] Pythonの日本語チュートリアルサイトは 

https://docs.python.org/ja/3/contents.html

[※]  このブログは教本として、『ゼロから作るDeep Learning Pythonで学ぶディープラーニングの理論と実装』 [ 斎藤 康毅 ] 【出版社: オライリージャパン 発売日: 2016/9/24 】 (ISBN-10:4873117585 ISBN-13:978-4873117584) を使っています。

[※]  pythonの教本として何かまとまった1冊を持っているといいです。 ネットで調べれば細かい所や、実例を沢山見ることができますが、導入として「何を」調べたいのかを知るにはまとまった教本が必要です。最近は『独学プログラマー Python言語の基本から仕事のやり方まで [ コーリー・アルソフ ] 』を参考にしています。

[※]  線形代数での行列については各種の専門書が有ります。 基本的で分かり易い本として 『高校数学でわかる線形代数 』(ブルーバックス) [ 竹内 淳 ]  などがあります。


*🐭*🐍*🐭*🐍*🐭*🐍*🐭*🐍*🐭*

[※] プログラムの勉強は、独学、インターネット教師よりもプロの育成プログラムが格段に速くて確実です。 費用に余裕があって時間に余裕のない方にお勧めです。

 

🐭*🐭*🐭(つぶやき) そろそろ旅行にも行きたいですねー

初心者がPythonでプログラミング入門。初歩からデープラーニングを体験的に学習する記録

プログラム初心者でも解るように説明をしながら、これからの時代に必要なPython(パイソン)を使って、プログラミング入門から初めてAIデープラーニングを体験的に学習するサイトです。主の教科書に「ゼロから作るDeep Learning 」(オライリージャパン)を使って、多くの情報サイトを使います。自主学習の記録として留めておきたいこと、これから学習する人が知りたいだろうことをブログ式にまとめています

0コメント

  • 1000 / 1000