「くずし字」識別チャレンジ 1位解法
概要
- 大きなモデル
- Augmentation各種
- Pseudo Labeling
- Ensemble
上記の通り、よく用いられる手法の組み合わせのみとなっています。
2位、3位の方と比較しても特別な手法を用いているわけではないので、今回の順位はシード値などの運要素も影響していると思います。
モデル
1段階目
- SE-ResNeXt50 (Input Size: 28)
- SE-Res2Net50 (Input Size: 28)
2段階目
- SE-ResNeXt101 (Input Size: 28)
- SE-ResNeXt101 (Input Size: 32)
※モデルのパラメータは、画像サイズに合わせて変更しています。
今回のコンペでモデルを選択する際に、まず決めたことは、「大きなモデルを使う」ことです。
個人的に、MNISTやCIFAR-10などの学習には、最大でも30層程度のモデルを使用することが多く、大きなモデルで試したことがなかったので、単純な興味本位と、宝くじをたくさん引こう(The Lottery Ticket Hypothesis)と考えたのが理由です。
1段階目の学習でSE-Res2Netを使用しているのは、最近趣味で実装したからという理由で、特に意味はありません。同程度のパラメータ数であれば、他のモデルでも大きな差は無かったと思います。
Augmentation
- 回転
- 拡大・縮小
- アスペクト比変換
- ランダムクロップ
- Mixup
- Cutout
Mixup以外の実装はalbumentationsを用いています(自作Augmentationモジュールから最近乗り換えました)。
学習
- Optimizer: SGDR (MomentumSGD + Cosine Annealing + Warm Restart)
- Learning Rate: 0.1
- Weight Decay: 1e-4
- Epoch: 140
- Batch Size: 100
以前、他のデータセットで行った実験のパラメータをそのまま流用しましたが、今回は、Snapshot Ensembleを用いなかったので、Warm Restartをかけないほうが無難でした。
次回以降は、どちらか(Warm Restart無し or Warm Restart有り + Snapshot Ensemble)にしようと思います。
1段階目
まず1段階目として、SE-ResNeXt50とSE-Res2Net50をモデルとして、それぞれ5-fold Cross Validation(以下CV)を行い、計10モデルのAverage Ensembleを1stサブミットとしました。
この時点でのCVのAccuracyは0.996~0.997程度でしたが、リーダーボードのスコアは0.990と出たため、僅かではありますが、共変量シフトの影響を疑いました。くずし字については、全く知識がありませんが、学習データとテストデータの間に、くずし字を書いた人や時代の偏りがあるのかもしれません。
2段階目
そこで、この結果を用いて以下のルールでPseudo Labelingを行い、さらに大きなモデルで学習を行うことにしました。
- テストデータのうち、予測値が0.9以上のサンプルを学習データに加える
- 対象データの予測ラベルを教師ラベルとし、その大きさを0.9とする
このルールは、学習のノイズとなる予測困難なデータを取り除くためと、1段階目の予測ラベルを丸暗記させないために設定しています。0.9という値は勘ですが、今回は既にAccuracyが高いため、厳しめの設定としました。
蛇足ですが、Residual構造のあるモデルでは、予測クラスのSoftmax値が1の近傍に張り付きやすく、予測値≠確信度となることが確認されているため、この時点でMC Dropoutのような手法を用いても良かったかもしれません(Ensembleによって、似たような効果を得られているとは思いますが)。
1のルールによって選択されたデータは、テストデータ全体の約98%となりました。既にほとんどのデータに対して正しく予測ができている(Accuracy 0.990)状態ですが、Pseudo Labelingを行ったテストデータを学習に加えることは、Batch Normalization層を中心に、共変量シフトの影響を抑制し、テストデータ全体の予測精度向上に寄与します。
手法は異なりますが、Domain Adaptationの分野では、(本来はソースドメインのパラメータを用いる)Batch Normalization層において、ターゲットドメインでパラメータを再計算するだけでも精度が向上するという報告もあります。
今回のコンペでは後述の通り、精度向上に大きく貢献した形となりましたが、個人的には、2ステージ制のコンペでない限り、多くの場合でPseudo Labelingは使い得と考えています。
もちろん、製品やサービスの開発においては別問題で、(コンペで使われている意味での)Pseudo Labelingは、学習時にテストデータを用いているため、テストデータを完全な未知データとして扱っていないことになります。そのため、ラベル付きの学習データが十分にある場合など、多くの場合では汎化性能の検証のために、使用しないことを推奨します。
学習データ + Pseudo Labelingデータを用いて、SE-ResNeXt101を28×28と32×32の2種類の解像度でそれぞれ5-fold CVし、計10モデルのAverage Ensembleをサブミットした結果、リーダーボードのスコアが最終スコアである0.995となりました(CVのAccuracyは0.997~0.998程度)。
最後にTTAも試してみましたが、スコアは0.995で、TTA無しと同様の結果となりました。
次回以降試したいこと
その他、今回は試す時間がありませんでしたが、今後試してみたい手法を挙げておきます。
Stacking
Ensembleするモデルの出力の相関が高い場合は、効果が薄いかもしれませんが、Average Ensembleより良くなることが多いという認識です。
AutoAugment系
モデルを変えた場合、結果も変わりそうですが、序盤に小規模なモデルを用いてAugmentationの探索を行い、その結果を参考にしてもいいかもしれません。
Pruning
学習済みのネットワーク内の重みの大部分は、予測に寄与しないノイズとなっているため、Pruning後にFineTuning or ランダム初期化から再学習することによって精度が向上することがあります。特に今回のように大きなモデルに対しては、効果的な手法だと思います。
終わりに
Twitterなどでお祝いをしてくださった皆様、ありがとうございました!
データサイエンスコンペティション初参加でしたが、非常に楽しく取り組むことができました。
Deep Learningを勉強し始めて5年程経ちましたが、まだまだ勉強することがたくさんある(むしろ増え続けている)ので、今後も精進していきたいと思います。
他のコンペティションなどでも交流できたらなと思いますので、よろしくお願いします。
今回実装したコードは、GitHubで公開しています。
解法を含め、質問等ありましたら、お気軽にコメントください。