Sansan Tech Blog

Sansanのものづくりを支えるメンバーの技術やデザイン、プロダクトマネジメントの情報を発信

歴史をたどってディープラーニングを学ぶ第九回 ついにAlexNetを実装して学ぶ

こんにちは、ニューラルネット老人こと糟谷勇児です。

今回はついに!!!第二回で紹介した2012年の技術、AlexNetを学習させます。
ここまで長かったですね。これができればディープラーニングできたと言って過言ではないでしょう。

これまでC#で作ってきたニューラルネットはある程度汎用的に作ってきたのですが、そうは言ってもAlexNetとして動かすといろいろ問題が出てくるだろうなと思っていました。
そして、いくつか問題が出てきましたが、何とか学習することができました。

正直ここまで続くとは思ってなかったです。奇跡的です。
実際のところ、フルスクラッチで作るのは、次こそ作れないんじゃないかとか、本当に合ってるのかとか、プレッシャーとの戦いです。今のところ月一で続けられていてラッキーです。

AlexNetとCifar10

AlexNetは最初のコンボリューション層に11×11のフィルタを持っています。
f:id:kasuya_ug:20200729220831p:plain
しかし、これは32×32のCifar10の画像には大きすぎるということで縮小版のネットワークが提案されています。
qiita.com

これまで拡張してきたLeNetと比較して、各層でのフィルタの数が圧倒的に多いことと、これまで2回だったコンボリューション層+MaxPooling層に加えて、三連続のコンボリューション層+1回のMaxPooling層がついたことが特徴的です。


今回はCifar10の中でも、飛行機と船のみ学習するので、フィルタ数を少なめにしてみました。

  1. ConvolutionalLayer 48平面, フィルタサイズ3*3, ストライド1
  2. MaxPoolingLayer フィルタサイズ2*2, ストライド2
  3. ConvolutionalLayer 128平面, フィルタサイズ5*5, ストライド1
  4. MaxPoolingLayer フィルタサイズ2*2, ストライド2
  5. ConvolutionalMnLayer 128平面, フィルタサイズ3*3, ストライド1
  6. ConvolutionalMnLayer 128平面, フィルタサイズ3*3, ストライド1
  7. ConvolutionalMnLayer 128平面, フィルタサイズ3*3, ストライド1
  8. MaxPoolingLayer フィルタサイズ2*2, ストライド
  9. FullyConnectionLayer ニューロン数 200, ReLU
  10. FullyConnectionLayer ニューロン数 30, ReLU
  11. SoftMaxLayer ニューロン数 2

padding:DeepLearningの端っこ事情

早速学習してみましたが、誤差が減っていかない・・・。いろいろ調べてみるとpaddingという画像の周りに仮想の画素を置く操作が必要なことがわかりました。

これまで、LeNetを拡張する形で進めてきたのですが、LeNetにはpaddingが使われていなかったので未実装でした。
こちらが以前紹介したLeNetの図です。
f:id:kasuya_ug:20200214183657p:plain
32*32の画像が5*5のフィルターを通すことで28*28になっているのがわかると思います。
画像の左端から右端までフィルターをはみ出ないように操作していくと、縦横28の画像ができるわけです。
しかしこれには問題があります。端っこの画素はフィルタの端っこに一回使われるだけで、あまり考慮されません。
(真ん中の画素は5*5のフィルタなら25回使ってもらえるが、角の画素は1回だけ)
層が浅ければそれでも大きな問題はないですが、深いと何度も端っこがないがしろにされていき、結構広い範囲があまり考慮されなくなります。
LeNetは層が浅いのと数字の認識を目的にしていたので、一番端っこまで線が来ることが稀なため重要視していなかったのかもしれません。あるいは入力画像にすでにpaddingが入っていたのかもしれません。

そんなわけで、画像の周りをpaddingという画素で埋めてあげます。
paddingについてはこちらのブログの図がわかりやすいです。
deepage.net


paddingの画素の値はゼロでいいようです(本当にいいのかな。隣の値を延長するとかしたほうがいい気もするけど・・・)。

さて、前回、下のほうにある飛行機を判定できないという話がありましたが、paddingを入れると一部改善します。
前回と同じネットワーク構造でも90.6%まで精度が上がりました。


ソフトマックス層と初期値問題

さて、これで誤差が減っていくかと思いましたが、勾配がほぼゼロ。
デバッガで値を見てみると、重みの初期値が大きめだったので、何層も経るうちに、ソフトマックス層に20と80のような大きな入力が来るようになっていました。
20と80にソフトマックス関数をかけると、ほぼ0とほぼ1になります。
ソフトマックスは第七回で紹介しましたが、微分すると下記のようになるので、このような値を入れると微分値がほぼゼロになってしまいます。
f:id:kasuya_ug:20200601173040p:plain
なので、初期値が小さくなるように全体的に調整しました。

遅い

AlexNetの学習の実行時間の7割ぐらいは、コンボリューション層のバックプロパゲーションの計算に使われていたので、そのあたりをチューニングしました。例えばリストを使っていた部分があったので配列に直したりしました。
(ここから先の高速化は、3*3でpaddingありで、stride=1だったらとか条件を付けて、ループをアンローリングするとかしていかないと速くならなそう)
今回は何とか土日で収まるぐらいの学習時間になりました。

AlexNetの学習と精度

今回は、学習率0.0002、バッチサイズ512、8並列で学習しました。学習率は75エポックから、5エポックごとに0.9倍するステップディケイを採用しました。
初回なので、データオーギュメンテーションはせずに4500枚ずつの飛行機と船の画像を入れました。
学習結果はこんな感じです。
f:id:kasuya_ug:20200730231331p:plain


精度は最大でも90.0%、これまで使ってきた7層のネットワークにpaddingを入れた場合は90.6まで行ったので、ちょっと微妙な結果な気もします。
ちなみに、いろいろなブログを見るとAlexNetでCifar10を学習する際の精度は86%とかみたいです。
もちろんこれは10カテゴリー全部を学習させる際の話なので、難易度は全然違いますが、AlexNetを使ったとしてもどうしても誤判定するものは出てきそうです。
とはいえ、正直まだAlexNetの能力を完全に引き出せているわけでもないので、もう少しチューニングしてみたいです。

まとめと今後

とりあえずAlexNetを作るところまで来ました。
次回はパラメーターを変えたり、学習された重みを調べてみたり、データオーギュメンテーションを入れてみたりいろいろ試して理解を深めていこうと思います。
せっかくだから同じように学習している人向けにコードを公開するのもいいかもしれないですね。



▼本シリーズのほかの記事はこちら

buildersbox.corp-sansan.com

© Sansan, Inc.