どうも、カタミチです。
さて、今日も「最短コースでわかる ディープラーニングの数学」のひとり読書会、やっていきたいと思います。余談ですが、3年前の今日、2019年4月11日が本書の発売日だったようなので、発売からちょうど3年が経った…ってことになりますね!(誰得情報)
さぁ、内容はいよいよ、10章「ディープラーニングモデル」のプログラム実装ですね。さぁ…いきましょう!
10-7. プログラム実装(その1)
まず今回も、ライブラリのインポートとデータセットの読み込みから始まるんですが、今回のデータセットは読み込むのに結構時間が掛かりました。それなりの容量のデータを落としてくるみたいなので、通信環境が通信容量に制限があるテザリングあたりでやると、ちょっとヨロシクナイかもしれません。
データセットを持ってくるライブラリは、またも「scikit-learn」ですね。ちなみに「scikit」ってどういう意味なのかなぁ、と思って英和辞書で調べても乗ってませんでした。だったら…ってことで「scikit-learn」の公式サイトに乗り込んでみたんですが、探り当てることができず…。英語がサラサラ読めるわけでなし、くまなく探したわけではないので、時間のある時にもう少し公式サイトを見てみますかね(気になる)。
ちなみに、どうやら本書発売当初からデータの読み込み方式が変わったらしく、コードを更新した旨のコメントが書かれていました。書籍発売後に情報をメンテするのは大変だと思いますが、私みたいに発売から3年経って本書を手にした人間にとっては、とても助かります(ありがたや)。
あと、今回「pandas」ってライブラリも読み込んでますね。どうやら、データセットの形式が、書籍発売後にpandasのDataFrameって形式に変わってしまった関係で、型変換の必要が発生してしまったようです。ちなみに、「pandas」が表形式のデータを扱うのが得意なライブラリ…ってことくらいは知ってますが、機械学習の現場では割と良く使うんですかねー。
さて、ライブラリとデータのインポートが終わったら、入力データの加工処理ですね。
まず入力データに、これまでの例題には無かった「正規化」の処理を噛ませるようですね。G検定の勉強でも出てきましたが「最小値を0に、最大値を1にする」って処理です。今回は、入力値が0〜255の値を取ることが分かっているので、入力データをそれぞれ255で割ることで正規化できるようです。
正解データも加工しますが、これはロジスティック回帰のときと同じで「OneHotベクトル」化する加工処理ですね。正解は「0〜9」の10種類なので、10個の成分を持ったベクトルになりますね。
で、今回も訓練データとテストデータに分割します。全体のデータ数が70,000ですが、訓練データ60,000、テストデータ10,000に割ります。今回は、分割する関数に「shuffle=False」って引数が付いてます。どうも、ランダムに取らないみたいですね。上から60,000個を訓練データにして、残り10,000個をテストデータにする感じですかね。それなら、関数を使わずにチカラワザで分割することもできそうですね(脳筋)。
今回どんな感じの画像データが入っているのかを確認するコードが書かれていたので実行してみました。折角なので、random関数のseedを変えて抽出するデータを変えてみました。入ってるのはこんなやつですね。
うん、結構ヘタクソですね(辛口)。私も悪筆ですが、この中だと上手な方かもしれません(ふむ)。
ちなみにこの図、これまでもグラフを書くのに愛用しているmatplotlibで書かれているんですが、工夫次第でこんなものも書けるのかぁ、と感心しました。いずれは、手足のように使いこなせるようになりたいですねー。
今回の活性化関数である「シグモイド関数」と「softmax関数」を定義。そしていつものように評価用に損失関数と正解率の式を用意したら、いざ勾配降下ループ発動!
…と、思いきや、まだ準備作業があるようです。というのも、今回は「ミニバッチ学習法」を採用するからです。G検定の勉強のときに、エポックとイテレーションの違いの理解に苦労したアイツですね。理解が深まるので非常にありがたいところです。
ここで、ミニバッチ学習法のためにPythonのクラスを定義してますね。プログラミングにおけるクラスの概念はちょっと理解に時間がかかると思うので、やはり本書を読む前に少しPythonの基本的なところは勉強しておいたほうが良さそうですかねー。私は、いちおう元プログラマー(約20年前)なので、とりあえず追っかけることはできそうです。
どうやら今回定義されたクラスは、「全体データの中からミニバッチで指定したサイズ分だけ、Index(データ系列番号)をランダムに取り出す」処理をしているようです。「全体データのIndexを使い切るまで、一度使ったIndexは使わない」ようになっていて、かつ「残りのIndexのストックがミニバッチ指定サイズに満たない場合は、再度全体をランダム化して取り直す」ってロジックになっていました。ミニバッチ学習法をやる場合にはよく使うやり方みたいなので、このクラスのロジックは覚えておこうかなぁ、と思いました。
ちなみに、本筋とは関係ないですが、Numpyの配列をNULLで初期化するとき「np.zeros(0)」って書いてありました。調べてみると「np.empty」を使った書き方もあるらしいんですが、使い分けはもう少し習熟しないと難しそう。
さて、続いては初期パラメータの設定です。これまでだと、「学習率」と「重みの初期値」を設定するだけでしたが、今回はもう少し設定する必要があります。
まずは「隠れノードの数」です。隠れ層に配置するノードの数は、事前に設定しておくパラメータになりますね。今回は128で行くようです。数値の目安ってあるのかなー(ありそう)。
さらに設定が必要なのが、ミニバッチ学習法で使うバッチのサイズですね。今回は512で行くようです。訓練データが60,000なので、結構ミニですね大丈夫なんでしょうか(感想)。ところで、先程のノード数の128といい、バッチサイズの512といい、2の累乗にしておいた方が計算が早かったりするんですかねー。
で、繰り返し回数であるエポック(Epoch)は100回。512を100回繰り返すってことは、51,200個のデータを使うことになりますね。…60,000個使い切らないんですね。使われなかった800個のデータにもお百姓さんの血と汗と涙が混じっていると考えると使いたくなってしまう、貧乏性な私です(お百姓さんとは)。
さて、ともあれこれで準備完了。前節までで準備した数式をコードに書いて…
あとは実行するだけです。
ついにラスボスを倒すときが来ました。ここまで長い冒険の旅でしたね…(回想シーン)。さぁ…とどめだ。必殺…
バックプロパゲーション!(お気に入り)
…はぁ…はぁ。ラスボスだけあって、かなり苦戦しました(実行時間が結構かかった)。
ですが、無事に倒すことができました!
さぁ、損失関数が減少していく様子をグラフで見てみましょう…
どん!
なん…だと!?
損失関数が減少していない…!?
いや、しかし精度の方は1.0に向かって上がっていっているはず…
効いてない…だと!?
…どうやら、戦いはまだ終わっていないようだ。(つづく)
ということで
今回は、数学的に総力戦だったのみならず、「入力データの正規化」「ミニバッチ学習法」という学習の工夫が登場した上で、損失関数が減らないという課題にも直面しました。実践の色が濃くなってきて、なかなか楽しいですね〜。しかし、まだこれで終わりというわけではないようなので、最後まで気を抜かずにやっていきたいと思います!
ではまた。