Kaggle CIFAR-10の話
以前、Kaggle CIFAR-10 に参加していると書きましたが、これが2週間ほど前に終わりました。コンペはまだ Validating Final Results の状態なのですが、2週間たっても終わらず、いつ終わるのか謎なのと、多分結果は変わらないと思うので先に書きます。
CIFAR-10は、次のような32x32の小さな画像にネコ、犬、鳥など10種類の物体が写っているので、与えられた画像に何が写っているか当てる問題です。
(Kaggle CIFAR-10のデータセットは、通常のCIFAR-10と結果の互換性がありますが、チート防止に画像のハッシュ値が変わるように改変されているのと、テストセットに29万枚のジャンクイメージが含まれています。)
自分の結果は、0.9415 (正解率94.15%)で、Classification datasets results によると、state-of-the-artが91.78%なので、それを上回って人間による精度である94%に達しているのですが、このスコアでなんと5位でした。1位はDeepCNetで、95.53%という驚異の精度を出しています。2位もDeepCNetとDropConnectの結果を合わせたものなので、DeepCNet最強だったという感じです(DeepCNetのコードはコンペ終了前に公開されていました)。3、4位は手法を公開していないので不明です。
自分の手法
kaggle-cifar10-torch7 - Github
でコードを公開しています。Torch7で実装しています。
オリジナル性が高いものはなく、よくある手法をいくつか組み合わせたのと、VGG(University of OxfordのVisual Geometry Group)がILSVRC2014で使ったモデルをCIFAR-10に調節しただけものです。
やったことは
- 学習データを36倍に増化(Data Augmentation)
- GCN + ZCA Whiteningで正規化
- VGGのモデルをベースにしたConvolutional Neural Network(CNN)を学習
- 上記のモデルを重みの初期値とMini-Batch-SGDの更新順を変えて6個学習し、各分類器の平均を予測として出力
です。
学習データを36倍に増化(Data Augmentation)
ニューラルネットワークは、経験的にはオーバーフィッティングしなければ層数や素子数が多いほどよいというのがあって、よりDeeeepしたいという思いはありますがデータが少ないとオーバーフィッティングしてしまうので、データを増して複雑なモデルでもオーバーフィッティングしにくいようにしました。
コツとしては、できるだけ"あり得る範囲"の変換により増やすということです。画像の場合、人工的なノイズや歪めたりでいくらでもパターンが増やせますが、テストセットに出てこないようなパターンで学習データの分布を歪めてしまうとよくないので、元のデータとは違うけどテストには出てくるパターンに変換できるのが理想です。
今回は次の3つのメソッドを使いました。
- Cropping
- CIFAR-10の学習画像は32x32ですが、これを24x24の部分画像に分解します。4px置きに切り出すと、3x3の9パターンが切り出せるのでデータを9倍に増やせます。(学習画像は小さくなります)
- Scaling
- Croppingでの切り出しサイズを28x28にして2px置きに切り出すと3x3の9パターンが切り出せます。この部分画像を24x24にリサイズ(ズームアウト)して学習画像とします。
- Horizontal reflection
- 左右反転です。これまで増やした画像を左右逆の2のパターンに分けて2倍に増やします。
これで(9 + 9) * 2 = 36倍になります。学習画像は32x32ではなく、24x24になります。
予測時は、予測対象の画像を同じ方法で36倍に増やして、各画像に対して予測を行い、それらを平均して予測結果としています。当然、予測にかかる時間も増えます。
この処理によって、2〜3%くらい精度がよくなりました。
1行目がCropping、2行目がCropping+Scaling、3、4行目がHorizontal reflectionです。
GCN + ZCA Whiteningで正規化
GCN(Global Contrast Normalization)は、standardizeとかz-scoreとか言われるものと同じで、データ全体から各要素の平均と標準偏差を求めて、平均を引いて標準偏差で割ります。入力の値域が-2〜2くらいに正規化されて、スケールの異なる軸があった場合でもその範囲にそろえられます。また、よく出る値は平均に近くなり、あまり出ない値は大きな絶対値を持つようになります。スケールの大きな軸の影響を抑えるのと、学習時の収束が速くなる効果があります。
2014/12/20追記
この説明は間違っていました。GCNは、画像をまたがずに、画像内での平均と分散を求めて平均を引いて分散で割るようです。これは、"An Analysis of Single-Layer Networks"の実装では、local contrast normalizationと書かれていた処理で、z-scoreはglobal standardizationと書かれていて、Maxoutの論文では、このlocal contrast normalizationがglobal contrast normalizationと書かれていたので、globalとlocalの概念がどこにあるのか混乱して勘違いしていました。ただ自分の実装では、このブログ通りのz-scoreを使っています。
ZCA Whiteningは、データ全体の分散共分散行列の固有ベクトルで主軸変換を行なって、変換後の空間でstandardizeを行なって元の空間に戻すというものです。自然画像は、あるピクセルはその近隣のピクセルと相関が強いという特徴があるので、この相関を消すことで色の情報を持ったままエッジ検出を行ったような結果が得られます。元の空間に戻すのは、CNNが元画像の構造を前提としているからです。
ZCA Whiteningは、An Analysis of Single-Layer Networks in Unsupervised Feature Learning - Andrew Ngですごくよい結果を出した前処理で、僕もこの手法を実装したことがあるのですが、この手法においてはZCA Whiteningをするかしないかで、CIFAR-10の精度が15%くらい変わります。ただ、最近のDeep CNNではほとんど差がでないので、必要なかったのではないかと思っています。その前のモデル(Network In Network)ではほんの少しだけ精度が改善できていたのと、外して変わらない精度が出るか試している余裕がなかったので、そのまま慣性で入れています。
VGGのモデルをベースにしたDeep Convolutional Neural Networkを学習
[1409.1556] Very Deep Convolutional Networks for Large-Scale Image Recognition で提案されているものをベースにしたDeep Convolutional Neural Networkで分類器を作りました。
伝統的なCNNでCIFAR-10用のアーキテクチャを作ると、conv 5x5 -> maxpool -> conv 5x5 -> maxpool -> conv 5x5 -> maxpool -> fc(fully connected) -> softmaxのようになるのですが、このconv 5x5の部分を3x3カーネルを2つか3つ並べたものに置き換えます。
これで
- 層数が増える
- 線形の大きなカーネルを非線形(convごとにReLUを挟んでいるため)の3x3を並べたものに置き換えるので表現力が上がる
- 大きなカーネルで一回畳み込むよりも小さなカーネルで複数回畳み込んだほうが計算量が少ない(5x5 > 3x3x2, 7x7 > 3x3x3)
というような効果があります。また3x3カーネルに1pxのpaddingを加えると、畳み込み層によって画像サイズが縮小されなくなるので、理論上は無限に層数を増やせるようになります。これはCIFAR-10のような入力画像が小さい場合に嬉しいです(畳込みでサイズが減っていくと増やせる層数に限界があるため)。
最終的に使ったアーキテクチャは、
conv 3x3 -> conv 3x3 -> maxpool -> conv 3x3 -> conv 3x3 -> maxpool -> conv 3x3 -> conv 3x3 -> conv 3x3 -> conv 3x3 -> maxpool -> fc -> fc -> softmax
というDeeeepなものです。詳しくはソースコードのページに表を書いているので参照してください。
上記のモデルを重みの初期値とMini-Batch-SGDの更新順を変えて6個学習し、各識別器の平均を予測として出力
ニューラルネットワークを使ったモデルで簡単に精度を上げる方法として、いくつかのモデルを学習して平均を取るというのがあります。ニューラルネットワークは初期値依存があるのと大域最適化はされないので、乱数のseedが異なると(微妙に)異なる結果を出力するモデルが学習されます。なので、いくつか学習して平均を取ると結果が安定します。
よくやるのはBagging(Committee Network)ですが、Baggingはサンプリングの割合など調節しなければならないのと今回はこれをやろうと思ったのが終了3日前で、調節する余裕がなく一発勝負だったため、良くなることはあっても悪くなることはないだろうという考えで以下の設定で行いました。
- 同じ学習データ
- 異なる初期重み
- 異なる更新順
また今回使ったモデルは学習に20時間程度かかり、単体マシンで学習していては2つしか学習できないので、EC2のSpot InstanceでGPU Instanceを6個たち上げて並行して学習しました。
結果的には、シングルモデルだと93.33%、6モデルの平均で94.15%だったので、この処理で0.85%改善できていました。
その他の話
VGGの論文が発表される前は、Network In Networkを使っていました。これは最終的には92.4%の精度を出せたので、悪くはなかったと思います。
最後の方では、このままではどうやってもDeepCNetに勝てないと思ったので、GoogLeNetを実装してみたのですが、学習がクッソ遅い上validationで88%しか出なかったので諦めました(調節が足りないのか、問題に合っていないのか、なにか間違っているのか分かっていない)。
この2つのモデルは、参考としてソースコードのディレクトリに置いてあります。(nin_model.luaとinception_model.lua)