俵言スマートフォン用の表示で見る俵言しがない社会人が書く、勉強とかのこと。第4回:3章(その3)ニューラルネットワークを試してみるはじめに今回も引き続き3章です。実装したニューラルネットワークを試してみるこの前作ったニューラルネットワークを試すだけなのでさらっと終わると思います。実装したニューラルネットワークを試してみる今回は前回作ったNNを用いて手書き数字(0~9)の認識を試みます。用いるデータセットはMNISTというモノクロ画像のデータセットで、とても有名なやつらしいです。これらの数字を認識させて、正解ラベルとどれくらい一致するか試してみます。データを取得する手段やNNのパラメータ(重みとバイアス)は著者の方が用意してくれているため、本当に何もする必要がありません。実際に認識を行わせる前にデータの取り込みなどを行います。その前に、現在作業しているディレクトリの構成を出しときます。現在の作業ディレクトリはDLFZという名前にしていて、その下の構成は以下の通りです。DLFZ(←ここがカレントディレクトリ)ch01ch02..ch08commondatasetmy_functions.pych01~datasetまでは著者の方が用意してくれているスクリプトなどが入っているディレクトリ、my_functions.pyは僕が今まで作った関数やクラスが入ってるpythonファイルです。今回は著者の方が作った物を使うことが多いので一応構成を出しときました。xxって出てきても「えっ?」ってなるかなって思ったので。データセットのダウンロードです。dataset下のmnist_for_py2.pyにある関数load_mnistを実行します。訓練データおよびそのラベル、テストデータおよびそのラベルを返すpickleはオブジェクトをそのまま保存できるモジュールです(公式に書かれてる直列化の意味がよくわからない..)。こいつはかなり便利なので頻繁につかってます。784L)となっています。28×28=784を一次元に直しており、それが10000枚あるわけですね。テストデータ画像の0番目を表示させます。Library)を使うとのこと(シラナカッタ..)。7画像ちっさ!いや28×28だしそりゃそうか..次に著者の方が用意してくれたNNの重み(重み行列とバイアスベクトル)をロードします。モジュールを使ってオブジェクトに戻します。ディレクトリch03の中にあるものです。pic.load(f)中身は辞書(dict)で、重み行列を表す二次元配列(W1,W2,W3)とバイアスベクトルを表す一次元配列(b1,b2,b3)が入ってるのが確認できます。'numpy.ndarray'>実装したNNを試すデータの取り込みも終わったところで本題です。前回作ったNNにロードした重みを食わせて、これを使って手書き文字認識をします。y前回作ったといいつつ実装変更しちゃいました。前回のやつ三層にしか対応してないんですもん。重み行列、バイアスベクトルをリストで受け取り、活性化関数も引数で受け取れるようにしています。それでは自作NNを初期化して実行してみます。重みを食わせましょう。softmax)ここで、重み行列を転置(.T)していることに注意です。)のが前提になってるので与えられた重みもそれに合わせて作られています。この時点では、これが悲劇につながるとは思いもしませんでした..さて、では実際にテストデータを認識させて見ます。ここでは、テストデータを一個ずつ認識させて正解ラベルと一致する数を数えます。%93.52%、なかなか高いですね。この本ではこれから更に工夫をして、最終的に99%を超えさせるまで行くそうです。先ほどは、テストデータであるベクトルを一個ずつ認識させていました。認識の処理って実装してきて分かった通り実際は行列演算です。これって入力を行列にしても一斉に処理できますよね?例えば100個のベクトルをまとめて行列にしてしまって一気に認識処理をさせることができるわけです。batch)を一度に認識処理させることをバッチ処理と言うそうです。多くの数値計算ライブラリでは大きな配列の計算を高度に最適化していて、小さな配列の計算を一個一個やるよりも効率的になるとのこと。バッチ学習とは意味が違うことに注意!さてこのバッチ処理を実際にやってみましょう。N)結果自体はさっきと同じになるはずですよね。..何故だ、計算は間違っていなかったはず...と思っていたのですが、実は大きな過ちを犯していたことにここで気づきました。ブロードキャストの罠まあ罠って言っても僕が仕様をちゃんと読んでなかっただけですが。※この節の話は本の通りにやってれば問題ないので読み飛ばしてOKです。numpy.ndarrayを使う上でめっちゃ便利な機能がブロードキャストです。配列に対して単一の値を足したりできるのはこの機能のおかげ。4]そして何より重要なのが、形状(要素数や次元)の異なる配列同士で計算できることです。10]]ブロードキャストは要素数が少ない方を勝手に補完してくれる非常に便利な機能なのですが、実は制約があります。片方が1なら計算が可能です。この後ろからというのが重要。勝手に90度回転して縦向きになって計算できそうじゃん!って思うんですよ。計算できないんですよね。同じことが僕の実装で起きた訳です。実装していたらこのエラーは起きなかったんですよね。縦ベクトルとして計算を行います。実装上は一次元配列なので、足し算が可能です。なるのでうまくいきません。ここでブロードキャストが働いてうまくいくって思ってたんですが、実際はさっきに示した例と同じ現象が起きてアウトとなるわけ。10]]このように一番後ろが揃うので問題なく計算できます。本の書き方が右からかけるやり方になってるのって、単に図の方向と合わせるためとか好みのためかと思ってたんですが、入力ベクトルを横ベクトルにすることでブロードキャストをスムーズに済ませるためだったのかもしれません。対処する方法としては以下が考えられます。バイアスの形を明確に与える(縦にする)もちろん2を選びます。さっきの例なら、bを二次元配列で表現した縦ベクトルにしてあげます。10]]ちゃんと計算できました!本題に戻るさてかなり寄り道しましたが、バッチ処理を再びやって見ましょう。さきほど述べたようにバイアスパラメータを明示的に縦ベクトルの形にすることでエラーが取れるはずです。%一個ずつやった場合と同じ結果だ!やったッ!第3章完!バッチ処理、いかほどのものか「完!」と言ったものの、バッチ処理でどれくらい効率化するのか気になりません?なので実行時間を比較してみます。(メモリ消費量は今回は無視で。)さっきのバッチ処理のコードをほぼそのままbatch_sizeを引数とする関数batch_testとして定義し、実行時間を計測します。計測にはtimeitというモジュールを使用します。引数の渡し方がややこしいのですが、複数のケースを試したい場合はこれがよさそう。計測については100回実行して最も速かった場合・遅かった場合を計測します。ばらつきがあるみたいなので、平均は見ません。max_time))表を出すだけでもいいんですが練習がてらグラフで可視化しましょう。楽なので一旦pandas.DataFrameに直してからプロットします。結構な差があるものの、共通して言えるのはバッチ処理した方が明らかに速いってことですね。今回はテストデータの数が10000枚なのでこれが限界ですが、テストデータ数が増えると、バッチのサイズが大き過ぎると逆に遅いってケースが出てきそうです。上の結果でもなんとなくそれっぽいですが、データ数もっと増えたほうがわかりやすそう。その分勉強になることは色々あったのでまあいいかなと。今回のポイントは以下。自作したニューラルネットワークでちゃんと画像認識ができた認識処理が行列演算であることを利用したバッチ処理で効率化が出来た(ブロードキャストの仕様を勉強できた)ブロードキャストの都合上、重み行列を右からかける実装の方が楽なんですよね。本の通りにするのもなんなので今後も今の方針で行こうと思います。勉強したかった学習の部分に入れますね。3章すら時間かかったのでこれから更に時間かかりそうですが、完走目指してがんばります!おまけ今回の記事で、さりげなくmnist_for_py2とかsample_weight_for_py2.pklとか出てきました。環境を前提として書かれているので、こんなものは用意してくれてません。使いは対処する必要がありました。この話をしようと思います。本当は移行した方がよさそうですけど、今のところそこまで困ってないし。load_mnist以下のようなエラーが出ます。3.x実はこのImportError、著者が用意したエラーです。スクリプト覗くと、urllib.requestがimport出来ないときに出るようになってます。3.x')..(以下コードが続く)..「オラ、エラー出したったぞ。「嫌や!僕はpyhon2使うんや!」てわけで対処するで。(指定したURLからファイルを取得して指定した場所に置く関数)を使うためだと判明しました。urllib.requestの代わりにurllibをimportして、元々のurllib.request.urlretrieveをurllib.urlretrieveに置き換えるだけでOK。モジュールの構成が異なっています。そもそもpython2ってurllibとurllib2があってややこしいんですよね。urllib.errorに分割されて組み込まれたらしい)そこらへんいいですよね。ちなみに、さっきの公式ページでも紹介されていますがrequestsというモジュールがあります。こいつが相当便利みたいなのでHTTP関連の処理したかったらこっちの方がいいかもしれません。pic.load(f)結果がこれ↓。公式にも載ってますし、以下のような記事もありました。オブジェクトに戻そうとするとエラーが起きたという訳です。