JDeeplearningのプログラム概要(1)

https://github.com/toyowa/jautoencoder
で公開しているプログラムの覚書を書いておこうと思う。これから、ロボットのセンサーを増やす作業に入るので、どういうプログラムだったか忘れてしまいそうなので。この(1)では、全体的なことを書いておく。
1。データに関して
データのフォーマットは、
https://github.com/huangzehao/SimpleNeuralNetwork
のサイトのプログラムを参考にさせてもらった。(プログラムは、独立に私が自分で書いたものだ。プログラムは参考にするほど理解できなかったが、自分で書かないと使えないだろうと思ったから)
データの最初の1行は、
topology: 784 400 10
のようにネットワーク構造を書く。ここで、784が入力レイヤーのニューロン数。以下出力ユニットまで、にゅう論数を書いていく。レイヤーの数にもニューロン数にも制限はない。
その後に続いて、in:とout:の接頭語に続いて、交互に、入力データと出力データを空白を区切って書いていく。
MNISTの手書き数字データに関しては、データの読み取りは、
http://nonbiri-tereka.hatenablog.com/entry/2014/09/18/100439
を参考にさせていただいて、それを上記のデータフォーマットに書き直したものだ。
https://github.com/toyowa/jautoencoder/tree/master/MNIST
に変換用のプログラムをおいてある。
データプログラムは、実行時オプション -dataで指定できる。
2。ウェイトファイル
学習後にウェイトデータを吐き出し、テストの時はそのファイルを実行時オプション -weights で指定して読み込むことになる。ただし、ウェイトファイル名は、wgtのさフィックスがないと拒否するようにしている。データと間違わないように。データのさフィックスは特にチェックせずに読み込む。
吐き出されたウェイトファイル名には、そのファイルが作成された日時が秒までつくので、識別できると思う。ウェイトファイルの頭の部分に、その学習のネットワーク構造やパラメータや、繰り返し数などが書かれている。
-------------------
File name: [ weight_170603192205.wgt ]
Iteration: 179997
InputData: trainingData.txt
Topology: 784 300 150 10
Adjusting: 0.15
Label: Pre_neuron No. ->  Layer No. : Neuron No. = Weight
Weight: 0 -> 1 : 0 =    0.0003876341
Weight: 1 -> 1 : 0 =    0.0011203298
Weight: 2 -> 1 : 0 =    0.0004425993
Weight: 3 -> 1 : 0 =    0.0007930749
...........
...........
---------------------------------
なお、テストじゃない時にウェイトファイルを指定すると、そのウェイトを読み込んで、そこに書かれているそれまでの学習の実行の続きをやることになる。何回かに分けてやりたいとか、中間状態を見たい時には、そのような方法もできる。プログラムのデバッグの時は、一回だけ学習させるということもやった。実行時ぷションの、-maxiterで1を指定すると一回だけやる。
なお、-maxiterを指定しないと、データがある限り学習を続ける。
3。テスト
テストの時は、できたウェイトファイルとテスト用データの両方を実行時オプションで指定して、-test オプションを付け加えれば良い。当たり前だが、ウェイトファイルとテスト用データは、ネットワーク構造に関して整合的でなければならない。-weightsでウェイトを指定しないと、当然だが、ランダムに与えたウェイトでテストしてしまうので、テストは無意味だ。チェックして排除するようにすべきだったかもしれない。テスト結果はコンソールに出力される。
4。オートエンコーダー(Autoencoder):深層学習
実行時オプションで -auto をつけると、オートエンコーダーとバックプロパゲーションで学習する。オートエンコーダーとは、隠れ層へのウェイトを1層ずつ、一つの圧縮符号化器のように作っていくことだ。例えば、MNISTで、入力784ニューロン、300と150の隠れ層、出力層が10ニューロンだとしよう。それぞれのレイヤー(層)にA,B,C,Dという名前をつけよう。
まず、ABにA'というAと同じニューロン数のレイヤーをつけて、A'の学習データとして、Aと同じものを使うという作業をする。そして、ウェイトをバックプロパゲーションで形成するという作業をする。一見無意味なようだが、よく考えてみると重要な意味を持っている。AがA'で再現されるんだが、BはAよりもニューロン数が少ない。それが再現されるということは、Bの出力は、Aから入力されるデータの特色をすでになんらかの形で組み込んでいるということである。つまり、ニュー力データだけで、すでに学習してしまっているのである。これでAからBへのウェイトをまず作る。次にそこで最終的に得た出力を利用して、BCB'というネットワークを作成し、その先のネットワーク出力で、自己学習させるのである。Bの入力と、学習用のB'のデータはまた同じである。もちろんニューロン数も同じである。そうすると、BC間のウェイトがまたCで特徴が凝縮されるように形成される。最終的にこのようにできたAB間のウェイトBC間のウェイトを使って、元のABCDのネットワークを元々の学習用出力で学習させる。この時,CD間のウェイトは、私の場合、ランダムに作成したものにしているが、正解かどうかの確信はない。でも多分正しいだろう。これが、オートエンコーダーである。
少なくとも、MNISTのデータについては、うまく機能している。
-autoを指定すると、元のネットワークトポロジーが、5層でもそれ以上でも、同じようにやってくれる。試してないが、はずである。

ディープラーニング(Autoencoder)のJavaプログラムをMNISTテストする

ニューラルネットのC++プログラムをJAVAに書き換えたら、すさまじく速くなったということは先に書いた。
これでディープラーニングの入り口である、Autoencoder(自動符号化器)のプログラムを作成した。バグもほぼ取れているような感じなので、
https://github.com/toyowa/jautoencoder
に公開している。
MNISTデータは、入力は、28X28=784ニューロンで、出力は、数字ラベルの10ニューロン。そこで、隠れ層を、300ニューロンと150ニューロン挟んで、4層にした。これまでと同様に、MNISTの6万個の手書き数字データと10000個のテストデータを実行した。
結果は、
<正解数 = 9413 不正解数 = 587 正解率 = 0.9413>
だ。同じプログラムで、改めて実施した、隠れ層400ニューロンだけの、Autoencoder抜きの結果は、
<正解数 = 9310 不正解数 = 690 正解率 = 0.931  >
なので、明らかに、正解率は上昇した。
しかも、隠れ層が300-150で、Autoencoderなしにニューラルネットを実施した場合は、全くダメ、というか収束しないので、Autoencoderの効果は確かめられた。
正解率のヒストグラムは、以下のようなものである。400の方が、0.999以上の頻度は多いのだが、総合的パフォーマンスは300-150のAutoencoderの方が高い。理論的に予測されているように、Autoencoderを入れた方が、柔軟に認識ている感じだ。

隠れ層のニューロンを400にしたら正解率が落ちた(笑)

MNISTを利用した他の研究を見ていると、私のように隠れ層が100ニューロンというのは例外的に小さい。そこで、隠れ層を400にして60000個の学習データでウェイトを学習させ、同じように10000個のテストをやって見た。
結果、
<正解数 = 9201 不正解数 = 799 正解率 = 0.9201>
で、正解率が1%落ちた(笑)
ただ、正解の時に、どのようなユニット出力になっているかを見ると、さすが隠れ層を増やした結果だなと思わせる。

図を見てもらえば明らかなように、先の2つのケースに比べて、その数字のユニット出力が0.999以上、つまり、ほぼ1になっているのだ。認識した数字に対する確信が抜群に高いということだ。「これは間違いなく3だ」とか、ネットワークが言い放っているような感じだ。そのためか、若干汚い手書き文字に対して、厳密な態度を取っている「こんな数字4じゃないだろう!」みたいな上から目線といっても良い。しかし、認識率もそれほど落ちたわけではない。ただ、上昇しなかったのが悔しいだけだ。

MNIST手書き数字データで93%の識別率

作成した汎用ニューラルネットワークで、その辺りのパフォーマンスを図る標準データとなっているMNISTの手書き数字データをテストしてみた。
MNISTについては、
http://yann.lecun.com/exdb/mnist/
にデータそのものと解説がある。
手書き数字は、
こんな感じのもので、数字の一つ一つがデータ化されている。1ピクセルが1バイト(0-255)の値が与えられ、1文字、28X28ピクセルからできている。
データ数は、60000文字の学習用データと10000文字のテスト用データがある。それぞれ、ピクセルデータとそれが幾つの数字を表しているかというラベルデータがある。60000字でニューラルネットを学習させ、ネットワークウェイトを作成し、そのウェイトが、テスト用10000字を正しく認識するかどうかを調べるのである。
ニューラルネットワークは、入力レイヤーが、28X28の784ニューロン、隠れ層(中間レイヤー)は100ニューロン、出力は0から9までの値を出すので、10ニューロンにした。出力は、ネットワークがその画像について判定した値のニューロンだけが発火する(値1になる)ことを見越しているわけである。

78万4千個のウェイトからなる、従って、訓練手法であるバックプロパゲーション(誤差逆伝搬)で、6万個の文字について、毎回これだけのウェイトを微調整しながら最終的に望ましいウェイトを見つけるわけであるから、相当大きめのネットワークである。
データについては、それぞれのピクセルの値を、正ならば1層でないならば0に、ビット化したものと、(0-255)それぞれの値を0から1の間の数に正規化したものと2種類用意した。元のデータのままをネットワークに入れたら、途中で破綻している。正規化したものに全ての情報が入っているので、生データを使う必要はない。
予測的には、正規化して0から1の間の数字にした方が、情報を多く持っているので、良いパフォーマンスを示すのではないかと思われた。0と1にビット化すれば、情報を単純なものにしてしまうのだから。
学習に、3Gヘルツ、8コアの最高スペックのMac Proでも1時間以上かかった。と言ってもこのマックは16スレッド動かせるのだが、プログラムそのものがほとんど1スレッドで動かしているので、相当無駄にしているのだが。逆に、同時にいくつもの学習を同時にさせることはできる。
10000個のテスト用データのテスト結果は、以下のようである。
<正規化(0.0-1.0の間の値)されたデータを用いた場合>
正解数 = 9283 不正解数 = 717 正解率 = 0.9283
<ビット化(0.0か01.0の値)されたデータを用いた場合>
正解数 = 9300 不正解数 = 700 正解率 = 0.93
微妙に正解率がビット化した方がいい。誤差の範囲といってもいいが、何よりも、ビット化して情報を削ったにもかかわらず、正規化したものに匹敵するパフォーマンスを出していることが驚きである。MNISTに掲載されているパフォーマンスと比べるとやや低いが、何の微調整もしていない、ただ作成したものでいきなりテストしただけで、これだけのパフォーマンスを出せれば、私としては合格だ。
正解率は、最大出力を出したユニット番号が、その画像の数字に一致する場合に正解としているのだ、これだけでは、どこまで明確にその数字と判断しているのかがわかりにくい。そこで、正解の出力ユニットがどれくらいの値を出したのかをヒストグラムで示す。基本的にユニットは0から1の間の数字しか出さず、通常、関係ないときはほぼ0に近い値しか出さないことに注意されたい。
つまり、そのユニットが正解だという場合、ほぼ0.9以上の値を突然、出している(ニューロンが発火している)ということだ。このグラフを見ると、このヒストグラムで示されたパフォーマンスは、正規化されたデータの方が、より1に近い数字で、ヒストグラムがより高く立っているので、パフォーマンスが良いといってもいい。ビット化されたデータをそれよりやや低いところで、パフォーマンスを稼いでいる。
次は、ディープラーニングを組み込む。