Kaggleの網膜コンペで銅メダルをとったので振り返る
Kaggle で開催されたAPTOS 2019 Blindness Detection(網膜コンペ)にソロで参加したのでその振り返りです。
結果は174th/2943 (TOP6%)で銅メダルでした。このコンペで2枚目の銅メダルを獲得し、Kaggle Expertになることができました。
コンペ概要
網膜の画像から糖尿病網膜症の重症度を予測します。
ラベルは0〜4の5段階で数字が大きいほど重症を表しています。
糖尿病網膜症については以下のスライドが分かりやすいです。
評価指標はquadratic weighted kappaです。
またkernel only コンペだったので学習は手元でOKですが推論はkernel上で完結させる必要があり、以下にkernelの制限時間内に推論できるかもこのコンペのポイントでした。
自分の手法
Validation Strategy
2015年にも同様のコンペ (APTOS2015)が行われており、そのデータと現コンペ (APTOS2019)のデータを単純に結合して学習データとしました。discussionでは2015のデータでpretrainして2019のデータでfine-tuningする手法が出ていましたが、めんどくさかったので単純に結合して使いました。
検証データは訓練データと同じラベルの分布になるように分割。最初は5-foldでCVしていましたが、時間がかかるので中盤からはsigle fold で学習させました。
あとの祭りですが、external dataとしてAPTOS2015だけでなくIDRIDやMessidorのような他のデータもあったようでそちらも使うべきだったなあと反省…。
preprosessing
前回コンペの優勝者が使用していた前処理がkernelで公開されていたのでそちらのコードを丸コピして使わせてもらいました。
処理内容は黒の背景部分を自動でcropし、ガウシアンフィルタをかけるというものです。
ガウシアンフィルタの強さを3段階くらい試してその中で最もlocal CVのスコアが高いものを使用しました。
画像サイズは256, 340, 512でいろいろ試したが画像サイズを増やすとoverfitする傾向があったので256 or 340の小さめのサイズでやっていました。
Augmentation
以下を使用しました。
- Flip
- Rotate
- ShiftScaleRotate
- RandomBrightness
- RandomContrast
- RandomGamma
後で上位の解法を見ると相当heavyな拡張をしていたのでもっといろいろ試しても良かったかも。
Models
CNNのモデルはResnet34, Resnet50, SE-ResNext50, Efficient-Netなどいろいろ試しました。
Efficient-Netは最近GoogleがNeural Architecture Searchで発見したモデルの構造で既存モデルより少ないパラメータで精度が良いようです。
実際、上位の解法の多くがEfficient-Netを使用していました。
Efficient-Netはパラメータの数によってB0~B7の7段階あり、自分は学習済みモデルが公開されていたB0~B5まで試してローカルのスコアが最も高かったB3を使用しました。
Training
discussionではロスをMSEとして回帰問題として解いているものが多かったですが、自分は最初あえてCrossEntropyLossを使った分類問題として解いていました。最終的には回帰として解く方法も試しました。
optimizerはAdam+CosineAnnealingを使用。
また、discussionで同じ画像でも異なるラベルがついていることがわかったりとラベル付けが適当な感じあったのでミスラベリング&汎化性能向上のためにLabelSmoothingを試してみました。
Label Smoothing: An ingredient of higher model accuracy
実際LabelSmoothingによってスコアが0.05ほど上がりました。
inference
最終的には以下の4つのモデルのアンサンブルした結果で提出しました。
- Classificationとして解いたEfficient-NetB3で画像サイズを3種類で学習したモデル(size256, 340, 512)
- Regressionとして解いたEfficient-NetB3で画像サイズ320(しきい値はローカルスコアで最適化)
自分のkernelの単純なコードだと制限時間で4回推論するのが限界だったんですが他の解法を見るとさらに多くのアンサンブルをしているものもあったのでここは工夫してもっと短時間で大量に推論してアンサンブルできるようにするべきだったかもしれません。
結果
Public LB: 0.808829, Private LB 0.918672で無事銅メダル圏内に残ることができました。
QWKで0.9超えるってどういう問題なんだ…
上位ソリューションのまとめ
1st place solution
1st place solution summary | Kaggle
- 2015+2019のデータを学習データとして使用。Public LBをvalidationとして信頼した。
- 特別な前処理なく単純なresizeのみ ← !!
- 以下8つのアンサンブル
- 2 x inception_resnet_v2, input size 512
- 2 x inception_v4, input size 512
- 2 x seresnext50, input size 512
- 2 x seresnext101, input size 384
- nn.SmoothL1Loss()
- 最後のプーリング層をGeneralized mean poolingに変更
- pseudo labeing
2nd place solution
4th place solution (2nd after LB cleaning) | Kaggle
- APTOS2015でpretrained
- 以下3つのモデル
- Efficient-Net B3 image size 300
- Efficient-Net B4 image size 460
- Efficient-Net B5 image size 456
- 黒背景をクロップしてサイズを変えるだけの単純な前処理
- a lot of augmentation
- pseudo labeling
- Test Time Augmentation (TTA)
4th place solution
- 黒背景除去&学習データの画像サイズごと3種類に分け、それぞれに対応した前処理
- Effcient-Net B2~B7
- heavy augmentation
- APTOS2015で事前学習、2019で5-fold
- 5 model x 8 TTA
- 前処理を1回しかしないことで推論時間を短縮
5th place solution
7th place solution
10th place solution w/ code [Catalyst, Albumentations] | Kaggle
コードも公開されていました。
GitHub - BloodAxe/Kaggle-2019-Blindness-Detection
- APTOS2015でpretrained、2019でfine-tuning. 外部データとしてidrid&Messidor使用
- SeResNext50, SeResNext101, InceptionV4
- 特殊な回帰として解く
- 最終層でsigmoidを通した4要素のベクトルを予想し合計をとる
- MSE
- Loss function
- focal kappa > soft CE(label smoothing) > plain CE
- Regression: WingLoss
- Ordinal Regression: Huber loss (aka smooth L1 loss) -> Cauchy Loss (?)
- mixtured precision FP16 (Apex)
- pseudo labeling
- RAdam
- 4 model ensumble
反省
上位のソリューションを見るとほとんどがpseudo labeling を使用していました。このコンペはtrainとtestの分布がかなり違ったのでpseudo labelingでtestの情報を与えることが重要だったのだと思います。
まだ自分はコンペでpseudo labelingをしたことがないので次回はしっかり習得したい使ってみたい。
また、Grad-CAMのような手法をつかってデータセットや前処理の手法を分析をしているカーネルがあったりしてとても勉強になりました。今後は自分もそういう分析ができるようになりたいですね。
何はともあれKaggle Expertに上がれてよかったです。
ymicky | Kaggle
次はNIPSコンペ、鉄鋼コンペでやろうと思います。
参考
28thの方も手法を公開してくれています。
speakerdeck.com