網膜コンペ振り返り

kaggleのAPTOS 2019 Blindness Detectionコンペ(通称:網膜コンペ)に参戦したので、振り返ります。

www.kaggle.com

はじめに

結果から言うと、素晴らしいチームのおかげで無事金メダルを獲得することができました。

solutionは以下にあります。

www.kaggle.com

 cheaterがbanされて最終順位は10thでした。(持ちsubのbestは0.932503でした)

f:id:NmaViv:20190915023108p:plain

今回のコンペは正しいCVを構築できるようなコンペではなかったので、ほとんどLBを頼りにやっていきました(Private LBはCVに近い結果になりましたが)。なのでもちろんLBへのoverfittingの恐怖が大きかったです。対策としてはモデル数増やしてpreprocessも多様性持たせればある程度ロバストにならないかなという感じやっていました。あとはとにかくなんでも実験をたくさん回すのも大事だったのかなと思います。

コンペの概要

糖尿病網膜症の重症度を検出するコンペ

糖尿病網膜症については以下のスライドを参照してください(up主には感謝です)

www.slideshare.net

ラベルは0~4まで5種類あり、値が大きいほど重症

評価指標は quadratic weighted kappa

Synchronous Kernel Only Competition

データ

  • APTOS2019(今回のコンペ)
    APTOS2019のデータのみだとデータ数が少ないというのと、img sizeごとのcropのされ方とラベルの分布が極端でoverfittingしてしまうという問題から外部データをうまく活用してあげることが重要だったため、以下のデータもよく使用されていました。
  • APTOS2015(前回のコンペのデータ)
  • 他 external data (IDRIDやMessidor)

コンペを始めた理由

壺コンペに参戦したときのコードを流用してすぐできそうだと思ったので参戦してみました(ベースラインはすぐできました)

コンペ初期

f:id:NmaViv:20190909235448p:plain

コンペ初期、submissionエラーをかましまくって原因が突き止められずにいたのですが、カーネルで使用するデータセットに新しくファイルを上げた場合、そのデータセットカーネル上で再読み込みしないとPrivateではデータセットの更新がなされず、当然そこにあげたファイルをロードできなくなるので、このようなエラーが発生するということでした。

ここで、壺コンペでチームを組んでいたmhiro2さんがAPTOSをやりたそうな発言をしていらっしゃったので、また一緒に画像コンペどうでしょうかということでお誘いして、チームが結成されました。

そしてなんやかんやでいい順位でスタートダッシュを切ることができました。

(APTOS2015でpretrainしてからAPTOS2019でtrainしたもの)

ちなみにモデルはEfficientnet-b5でregressionでlossはMSEで解くやり方で最後までいきます。

(当時のEfficientnetのpretrained weightsはb5が最新でした)

  • preprocess検討

そしてtrain時のpreprocessを以下のようにいじってLB0.809まであげました。

# model = Efficientnet-b5
# img_size = 256
# 2015pretrain(2015valid) -> 2019train(2019valid)
Compose([ Resize(img_size), HorizontalFlip(), VerticalFlip(), Rotate(), RandomContrast(), IAAAdditiveGaussianNoise(), ])

網膜の画像なのでflipとrotateが効くのはなんとなくわかりますが(rotateは変な感じになるやつが実はあったりするんですが、それはそれでoverfitting抑える要因になっていたのかなというお気持ちで、とりあえず入れた方がLB良くなりました)、画像ごとに照明の明るさがバラついていたりするので、contrastなんかも効きました(brigtnessや他のものも試したけど少なくともCVは上がらなかった、cutoutは安定性はなかったが効くこともあったので後々一部のみ使った)

上記に加えて、少しCentercropしたもので学習させて、推論時の画像は特にCentercropしないというのでsubmitしたところLB0.811まで上がりましたが、別の網膜画像の黒いエリアは重症度に関係ないのでうまくCropして消そうという試みがうまくいってなかったという背景と、学習と推論で揃えないのもPrivateがどうなるかわからず気持ち悪かったので、採用しませんでした(Privateスコアは0.0004ほど上がってた)

  • 重複画像の処理

ちなみに、APTOS2019には重複画像が存在していましたが、重複画像についてはMD5で検出し、重複画像が同じラベルであれば一枚にするだけでいいのですが、重複画像でも異なるラベルの場合は、自分たちのモデルを使用してre-labelingしました(regressoinで解く場合はラベルの平均値を取るので良さそうですが、classificationのモデルも作成する場合その方がいいと思ったので)(ここを気にしすぎて足踏みするのも勿体無かったのでとりあえず暫定的にみたいな感じだったが、結局最後まで同じものを使用した)(というか、trainとtestで重複画像があって、それをリークとして扱ったものをサブしたところLBが下がったので、ラベルあまり信用できないじゃんwとなったのもあります)

  • img_sizeとcropのされ方のばらつき

img_sizeとcropのされ方のばらつきの問題があったので、cropの仕方をimg_sizeごとに決めてあげようみたいなことにもトライしてみていて、public-testに640*480が多かったので(一方で640*480はtrainには少なかった)、同じアスペクト比の2048*1536をアスペクト比保ったままcropしてあげて(cropしてあげないと黒いエリアが多い状態になっている)640*480に似たサンプルを増やしてあげれば...みたいなのが効果がなかったので(2048*1536は全てラベル0だったのもあったのかもしれませんが)、ここを深掘りするのはやめました。

コンペ中期

スコアが近かったかまろさんとマージして、[kaggler-ja] CMY が誕生しました。

メンバーのハンドルネームの頭文字をくっつけたら色の三原色になったのでそれを採用しました笑

かまろさんも同様にAPTOS2015でpretrainしてからAPTOS2019でtrainするというtraining methodで、モデルはSeResNext101で、解き方はregressionで、lossはRMSEで、preprocessはPublic kernelに上がっていたBensCropというのを使ってやっていて、僕らとは異なるアプローチでした。(アンサンブルしたら効きそう👀)

しかし、アンサンブルでスコアが上がらなかったり、しばらくの間「何もわからない」状態が続きます。

とりあえずここで一旦可視化をしてみようとclassificationのモデルを作成してGrad-CAMでモデルがどこを見てクラスを判断しているかをチェックしてみました。

とりあえず一番データ数の多いラベル0の例

【APTOS2015でpretrainしていない画像(LB低い方)】

f:id:NmaViv:20190912022216p:plain

APTOS2015でpretrainしていない画像は四隅と真ん中でラベル0と判断しているように見えます。

【APTOS2015でpretrainした画像(LB高い方)】

f:id:NmaViv:20190912022230p:plain

一方でAPTOS2015でpretrainした画像は上記の一部が緩和された結果として下隅でラベル0と判断しているように見えます。(つまり上に比べたら疎らになった印象)

APTOS2019は画像数も少なかったですし、img sizeごとのラベルの偏りやcropの違いもすごかったりしたので、こうしてAPTOS2015でpretrainしてからAPTOS2019でtrainしてあげてoverfittingを防ぐことでLBも上げることができたように見えます。

ただまだ下隅の暗闇の部分で判断しちゃっているようなので、もう一工夫してあげればまだスコアは伸びそう?

そうこうしているうちにかまろさんが活路を開きます。

 ちなみにこの後のチームslack

f:id:NmaViv:20190912030557p:plain

こうしてワイワイできるのもチームでコンペに取り組む醍醐味だなあと思いました笑

かまろさんが具体的に何をしたのかというと、pretrainは特にせずに、APTOS2015のデータセットとAPTOS2019のデータセットで同時にtrainしてAPTOS2019でvalidationしてあげたとのことでした。

そしてdefault coef = [0.5,1.5,2.5,3.5]を使ってLB0.820、optimized coefを使ってLB0.827になったとのことでした。

これまでの実験で、default coefとoptimized coefのどちらがいいか問題がありましたが、ここで明確にoptimized coefの方が高いスコアが出たのと、以降の僕の方のモデルでの比較でもoptimized coefの方が高いスコアが出たので、optimized coefを使う方針で固まりました。

ということでこのtraining methodに似たもの(色々なパターンで効果を検証したかったので)をEfficientnet-b5の方でも実験してみました。やり方としては、APTOS2015を5foldsに切ってその内の4foldsでpretrainして、残りの1foldはAPTOS2019の方と一緒にtrainしてあげるというのでやってみました。

trainの方のvalidationをAPTOS2019でやるのは当然ですが、pretrainの方のvalidationをAPTOS2019でやるのとAPTOS2015でやるのとではどちらがいいのか比較してみたところ、LB0.809のやつ(transforms: HFlip, VFlip, Rotate, RandomContrast, IAAAdditiveGaussianNoise,CV0.936)をベースとして、

  • 2015(4fold分)pretrain(validation=2015) -> 2015(1fold分)+2019train(validation=2019):LB0.814(CV0.932)
  • 2015(4fold分)pretrain(validation=2019) -> 2015(1fold分)+2019train(validation=2019):LB0.819(CV0.931)

この結果から

  • pretrainもvalidationは2015よりは2019でやる方がいい
  • 2015と一緒に学習させることでCV下がってるのはよりoverfitting防げてて,それでスコア上がった?

みたいなことがわかりました。

このtraining方法のもとであらためてBensCropではない方のCircleCropも試してみたり、IDRIDやMessidorも一緒に使ってみたりを試しますが、ダメでした。

なんとかCVとLBの相関も取れないかと、ここでCVをimg sizeごとにチェックしてみたりしましたがダメでした。

またかまろさんのものと同様にAPTOS2015のデータセットとAPTOS2019のデータセットで同時にtrainしてAPTOS2019でvalidationというのでやってみますが、LB0.807に下がるなどして、何もわからなかったので、とりあえず上記のLB0.819の方法で進めていきます。

一方でかまろさんはpseudo labelingで自身のモデルのスコアをLB0.835まで伸ばします。

ここでかまろさんが旅行に行くとのことだったので、その間になんとかEfficientnet-b5の方もスコアを上げたいなと思っていたところ、かまろさんがimage sizeを320から256にしたところLB0.827からLB0.813まで下がったという情報を共有してくださっていたので、今のEfficientnet-b5のimage sizeを256から320にして伸びてくれないかなーという淡い希望を抱いて回してみたところ・・・

f:id:NmaViv:20190912043859p:plain

この伸びには驚きましたが、image sizeを上げてスコアが上がること自体は納得できることなので、素直に喜びました。その後efficientnet-b5のdefalut sizeである456でも試してみましたが、LB0.820ということで256のときのLB0.819よりは上がったものの320の方が時間的にもスコア的にもよかったので320を採用しました。

(またこれは後から教えてもらったことですが、現在のimagenetのSOTAのfacebook aiのFixResNextも小さいimage sizeでpretrainしてから大きいimage sizeでtrainするみたいなことをしているらしく、それと似た効果があったのかはわからない)

pretrainのimage sizeも探ろうとしましたが、他の実験もあったのでそちらを優先して取りかかれませんでした。

そしてLB0.835のse_resnext101とLB0.830のefficientnet-b5をアンサンブルして

 LB0.842まで上がって、この時点でPrivate LBが0.930でした。

この後はpseudo labelingでスコアを少しずつ伸ばしていった感じになります。solutionの方に表と一緒にまとめてあるので残りはそちらをご覧ください。

学んだこと

  • データセットの分析も大事
  • Grad-CAM使えるようになってよかった
  • pseudo labeling強かった(今回のコンペでは特に)

終わりに

念願の金メダルとても嬉しいです!

おかげさまで無事Masterになることができました...!

チーム組んでくださったmhiro2さん、かまろさん、本当にありがとうございました!

 また何かあれば追記します