TReNDSコンペ振り返り

kaggle の TReNDS Neuroimaging コンペに参戦したので、振り返ります。

www.kaggle.com

はじめに

結果から言うと、無事金メダルを獲得することができました。

solution は以下にあります。

www.kaggle.com

コンペの概要

予測対象:年齢とその他4つの数値(数値の内容については非公開)

評価指標:feature-weighted, normalized absolute errors

注意事項:

個人情報保護などの観点から train data の予測対象は少し加工されているとのこと

The scores (see train_scores.csv) are not the original age and raw assessment values. They have been transformed and de-identified to help protect subject identity and minimize the risk of unethical usage of the data. Nonetheless, they are directly derived from the original assessment values and, thus, associations with the provided features is equally likely.

Before transformation, the age in the training set is rounded to nearest year for privacy reasons. However, age is not rounded to year (higher precision) in the test set. Thus, heavily overfitting to the training set age will very likely have a negative impact on your submissions.

また train data には site1 のデータしかないが、test data には site2 のデータもあるとのこと

Models are expected to generalize on data from a different scanner/site (site 2). All subjects from site 2 were assigned to the test set, so their scores are not available. While there are fewer site 2 subjects than site 1 subjects in the test set, the total number of subjects from site 2 will not be revealed until after the end of the competition. To make it more interesting, the IDs of some site 2 subjects have been revealed below. Use this to inform your models about site effects. Site effects are a form of bias. To generalize well, models should learn features that are not related to or driven by site effects.

データ

  • fMRI_train - a folder containing 53 3D spatial maps for train samples in .mat format
  • fMRI_test - a folder containing 53 3D spatial maps for test samples in .mat format
  • fnc.csv - static FNC correlation features for both train and test samples
  • loading.csv - sMRI SBM loadings for both train and test samples
  • train_scores.csv - age and assessment values for train samples

  • sample_submission.csv - a sample submission file in the correct format

  • reveal_ID_site2.csv - a list of subject IDs whose data was collected with a different scanner than the train samples
  • fMRI_mask.nii - a 3D binary spatial map
  • ICN_numbers.txt - intrinsic connectivity network numbers for each fMRI spatial map; matches FNC names

チームマージ前

とりあえず LGB でベースライン作成

www.kaggle.com

CV: 0.1628 - LB: 0.162 と初期は CV と LB が一致するコンペだとか思っていました。

そして段々とハイスコアカーネルが出てきたので、それらをもとにブレンドしたものをサブミットしてみたところ、CV: 0.1581 - LB: 0.1593 という結果が出て、データ概要欄を改めて確認すると、train data の予測対象は少し加工されていると知りました。

この CV と LB の乖離の原因は、test data には train data にはない site2 が存在することの他に、この train data の予測対象の加工が少し関わっているのかなと思いつつ、ここで 3D map を使っている方々が上位にいるということに改めて気づき、ここで 3D map を使った NN を準備だけしてブレンドしていなかったので(CVが低かった)、ブレンドしてみると微動だにしなかった LB がようやく更新されます。

(チームマージありかもなんて呟いてますがこのあとにソロで行くとか呟きます)

そして NN 単体の精度をあげて NN の精度を確認してみると、CV: 0.1596 - LB: 0.1591と、3D map を使うことで CV と LB の乖離が抑えられることがわかりました。

NN は少しだけ時間がかかるので検証モデルを Ridge に乗り換え、CV: 0.1583 - LB: 0.1583 と LB が CV についていく形で順調にスコアが上がります。

(ソロで行くなんて呟いてますがこのあとにチームマージします)

チームマージ後

海外勢の Lightさん とチームを組むことにしました。

というのも、今回チームを組んでくださった Lightさん は 3D map 以外のところでスコアを上げているらしく、External Data Thread を見て 3D map で成功しているっぽい僕に声をかけたということでした。こちらとしては 3D map 以外のところでスコアを上げる糸口が見つかっていなかったので、お互いに win-win なチームマージになりました。

あとはお互いのアプローチを組み合わせて、改善できるところは改善して、以下のような solution になりました。最終的に NN を post-process なしで CV: 0.1578, LB: 0.1578 辺りまで改善出来たのが個人的に嬉しかったです。

f:id:NmaViv:20200702073439p:plain

最後に pseudo labeling も試しましたが、CV は改善するものの、train data に overfitting したのか LB は改善しませんでした。 

学んだこと

  • データの説明を読むのは大事(当たり前)
  • train と test で一癖あるとき post-process は大事
  • チームマージは偉大

終わりに

Bengaliコンペとionコンペで残念な結果に終わっていたので、ここで挽回して金メダルを取ることができて良かったです!

kaggleランクも自己ベスト更新してました(嬉しい)。

f:id:NmaViv:20200702063042p:plain

 

Google QUESTコンペ振り返り

kaggleのGoogle QUEST Q&A Labelingコンペに参戦したので、振り返ります。

www.kaggle.com

はじめに

結果から言うと、個人的に目標にしていた銀メダルを獲得することができました。

コードの一部をGitHubに上げています。

github.com

コンペの概要

f:id:NmaViv:20200213214138p:plain

コンペ概要

質問タイトル・質問文・回答文から30種類のTARGETを予測するコンペで、TARGETには、例えば「その質問が他人の興味を引くものか」や「回答が質問に対して満足なものか」などがありました。

評価指標はmean column-wise Spearman's correlation coefficient(各TARGETの順位相関係数の平均)でした。

今回のコンペでやりたかったこと

  • オンプレ初陣
  • BERTに慣れる
  • データ量が少なく比較的実験の回しやすいコンペなので、Jigsawコンペの解法で気になっていたBERT+LGBを試す
  • 銀メダルを取る

解法に加えて、これらの結果についても振り返っていきます。

オンプレ初陣

オンプレ、kaggleのGPUと比較しても、とても高速で快適でした。

Google Colaboratoryも使おうとしていたのですが、途中で落ちたりしたので結局モデル訓練はオンプレで回して、kaggleのGPUは推論カーネルを作成するためのGPU Quota Limitを確保しておきたかったので、訓練には使いませんでした。

オンプレ、これからも積極的に使っていきたいです。

BERTに慣れる

こちらのPyTorch BERTのカーネルをベースラインに進めていきました。

(TFのやつは再現性取れないのが嫌なので...) 

www.kaggle.com

このカーネルモデリングではスコアが他のカーネルに比べてまだ改善の余地があったので、他のカーネルを参考にしつつ、最終的には以下のようなモデリングになりました。

 

f:id:NmaViv:20200213225925p:plain

モデリング

モデルはBert-base-uncasedとBert-base-casedとBert-large-uncasedを回してみましたが、Bert-largeはパラメータの調整が難しすぎてまともな精度が出ず(ここらへんのお気持ちを知りたい)、また自身が終盤から参加したため実験を回す時間も確保したかったので、Bert-largeは捨てる方針で進めていきました。

結果的に、Bert-base-uncasedのモデルを5種類、Bert-base-casedのモデルを1種類、そして後述するBert-base-uncased+LGBのモデルを1種類作成してアンサンブルしました。

Bert-base-uncasedのモデル5種類の内訳としては

  1. [Q_title+Q_body]と[Answer]をinputとしたもの
  2. [Q_title+Q_body]と[Q_title+Answer]をinputとしたもの
  3. 2からMAX_LENを小さくしたもの
  4. 2からTARGETをランク化してそのmaxで割った値に置き換えたもの
  5. 2からいくつかのTARGETをnp.log1pしたもの

みたいな感じです(head_tailの割合とdrop_rateもちょっといじったりした)。時間も少なかったので、実験的に回したものを混ぜる感じになりました。

Bert-base-casedは1と同じ条件で回したものだったはずです。

コンペ参加前に比べると(取っ付きやすさという観点において)Bertに少しは慣れた気がしますが、上位の方のsolutionを見ると、Bertのこと何もわかっていないことを実感させられるので、上位の方のsolutionはちゃんと確認して次回のコンペに活かしたいです。

BERT+LGB

過去コンペの解法を活かすというところでは、Jigsawコンペの解法で見たBERT+LGBを試すことができたのはとてもよかったです。

www.kaggle.com

MultilabelStratifiedKFoldでのCVの比較になりますが(なぜGroupKFoldにしなかったのかはこの際置いておいて)、ClassifierをLGBにする前と後で、CV0.39754 → CV0.40056と向上し、単純なアンサンブルでは、Bert+LGBのモデルが加わることでCV0.41593 → CV0.41848と、単純な差分をとったBertを混ぜるよりも伸び幅が大きかったです。

後処理

最初はQWKコンペでよく使われるOptimizedRounderを使っていましたが、最終的には小さい値(または大きい値)からどこで閾値を設定すればスコアが最大になるか探索して、それを繰り返すような形で閾値を求めていきました。閾値ごとのサンプル数とかも見ないとCVにフィットしすぎた閾値になっていた気もしましたが、結局そこはそんなに詰めませんでした。

試したけど上手くいかなかったこと

  • Focal loss
  • Bert-large
  • 特定TARGETに対してunder-samplingして学習

終わりに

短い期間でのチャレンジでしたが目標としていた銀メダルに届いたのと、過去コンペの気になっていた解法を試すことができたのでよかったです。

kaggleランクも自己ベスト更新してました(嬉しい)。

f:id:NmaViv:20200214001458p:plain

 

 

 

 

網膜コンペ振り返り

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さん、かまろさん、本当にありがとうございました!

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

 

壺コンペ振り返り

kaggleのiMet Collection 2019 - FGVC6コンペに参戦したので、振り返ります。

www.kaggle.com

コンペの概要

FGVC6 のコンペで、美術品のラベル付けの精度を競う

ラベルは1103種類存在する

その中でも398種類がcultureに関するラベル(どの文化の美術品なのか)

残りの705種類がtagに関するラベル(どういう美術品なのか)

一つの美術品に対するラベルは当然複数存在しうる(いわゆるマルチラベル

評価指標は F2 score

データ

学習用の画像(/trainフォルダ)

学習用の画像に対応するラベル(train.csv

推論用の画像(/testフォルダ)

ラベルのidとnameを対応させた表(labels.csv

コンペを始めるにあたって

このコンペを始めようと思ったきっかけはPyTorchを書けるようになりたいなと思ったからです。一応PyTorchの本を読んではいましたが、コーディングは全然していなかったので実践するのに丁度いいコンペが来てくれました。

以下はコンペ初期のPyTorch何もわかっていない時の僕です。懐かしい。

 

また今回のコンペはマルチラベルのタスクで、僕はFashionMNISTでマルチクラス分類をやったことはありますが、マルチラベルの場合どうすればいいのか当時よくわかっていませんでした。

同じような方がDiscussionにもいて、ここでようやくマルチラベルのタスクに対するアプローチを理解しました。

www.kaggle.com

コンペ初期

自分でベースラインを実装する実力は当然なかったので、コンペ初期は以下のカーネルを参考にさせていただきました。

www.kaggle.com

このカーネルはpretrainedモデルを使って特徴抽出を行い、得られたtensorをinputとしてMultiLayerPerceptronで学習させるようなカーネルで、PyTorch初心者にも理解しやすい内容になっています。

このカーネルがあったおかげでスムーズにPyTorchに入門できたと思うのでとても感謝しています。

コンペ中期

上記のカーネルをもとに自分用のベースラインを作成したものの、まだ書き方が冗長でepochを全然回すことができていませんでした(カーネルGPUは9時間まで)。

そんなとき、fastaiでモデルを作成していたカーネルが自分のベースラインよりも高速にepochを回していることに気づき、ここでfastaiに移行しました。

www.kaggle.com

fastaiがその名の通り簡単にモデルを組むことができることは聞いたことがありましたが、このカーネルを見ても分かる通り、本当にお手軽にモデルが組めました。

fastaiで色々試行錯誤したところ、結局上記のカーネルのようにモデルを学習させて、ResNet50のモデルをアンサンブルしてようやく公開カーネルを超えるスコアが出せました。

チーム mhiro2 & Y.Nakama 

当時LBで0.63台につけていたmhiro2さんがチームメイトを募集していて、スコアがそこまで近くない身分でお声がけしたところ、とても丁寧なDMをいただきチームマージする運びとなりました。

mhiro2さんは以下のカーネルを参考にしていました。

www.kaggle.com

このカーネルは初期の頃からありましたが、僕は何をやっているか詳しく見ずにスルーしていました。

このカーネルが何をしているかと言うと、.pyファイルのソースコードbase64を使ってエンコードしたものをカーネル環境でデコードして、カーネルでそれぞれの.pyファイルを実行するというようなことをやっています。

これ、詳しく見てみたらソースコードとかめちゃくちゃ参考になるもので、作者さんには感謝しかないです。

しかもこのカーネルは僕が参考にしていたfastaiよりも多くepochを回せるようだったので、チームマージ後はこのカーネルとmhiro2さんによる追加実装を参考に作業を進めていきました。

チームマージしてから具体的にやったことは以下になります。

  • モデルの検討
  1. Discussionにもあった通りSEResNeXt101_32x4dが一番良かった
  2. アンサンブル時に多様性を出すために、ResNet101、SEResNet101、SEResNet152、DenseNet161なども用意した
  3. pretrainedが出たEfficientNet-b3も試したが、精度があまり出なかったので不採用
  • 2nd-trainの検討
  1. GPUは9時間までしか回せないので、9時間だけでは十分にモデルを学習させることができないモデルもあります
  2. そこで9時間学習させたモデル(1st-train)のweightを引き継いで、lrを調整して2nd-trainさせることで十分に学習させました
  • lossの検討
  1. BCELoss、FocalLoss、F2Lossから検討
  2. 上記の組み合わせを試したものの、最終的にはBCELossを使用
  • schedulerの検討
  1. CosineAnnealingLRも試したが、ReduceLROnPlateau方式を採用
  • batch sizeの検討
  1. batch sizeは基本的に大きい方が精度が出るイメージがありますが、32と64で比較してみたところ、32の方が精度が良かったので32を採用しました
  • transformの検討
  1. 縦長の画像もあれば横長の画像もあったので、transformを色々検討しました
  2. 90度回転させてみたりもしましたが、結局以下に落ち着きました
    # size == 320
    RandomResizedCrop(320, scale=(0.6, 1.0), ratio=(9/16, 16/9)),
    RandomHorizontalFlip()
    # size == 288
    RandomCrop(288),
    RandomHorizontalFlip()
  3. size=320のときのtransformの方がCVが高く出ましたが、LBではsize=288のときのtransformとほとんど変わらなかったので、もともと学習を回していたsize=288に追加する形で、size=320でtransformを変えてまたいくつかモデルを作ったという感じです
  4. TTAのときにもこれらを採用しました(TTA 4)
  • post-processの検討
  1. 予測結果も合わせてEDAしてみて、手元のvalidationでもスコアが改善されて根拠としても十分な以下を導入しました

 

  • アンサンブル
  1. 各モデルの予測値を等weightでブレンディング
  2. 閾値も各モデルでのベストな閾値を等weightでブレンディング

反省点

  1. cultureラベルとtagラベルでの閾値はそれぞれで検討すべきだった
  2. コンペ序盤では、閾値を一つに決めるのではなくclass-wiseな閾値も検討したが、精度が下がったので閾値は一つでいいかと思ったまま最後を迎えてしまった
  • stacking

終わりに

壺コンペはカーネルも充実していて本当に勉強になりました。

CVとLBで大きな差が出ることもなかったので良コンペだったと思います。

X5?知らない子ですね・・・

Discussionで公開されている解法も時間がある時に追いたいです。

最後に、mhiro2さんとチーム組ませていただいたおかげで様々な取り組みが出来るようになったので本当に感謝しています!ありがとうございました!

 

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

 

 

 

 

 

ペットコンペ振り返り

kaggleのPetFinder.my Adoption Predictionコンペに参戦したので、振り返ります。

www.kaggle.com

コンペの概要

マレーシアのPetFinder.myという動物福祉プラットフォームがホスト

マレーシアで里親を探しているペット(犬、猫)が、どれくらいのスピードで引き取られるか(AdoptioinSpeed)を予測するコンペ

AdoptioinSpeedは以下の5通り

0 - Pet was adopted on the same day as it was listed. 
1 - Pet was adopted between 1 and 7 days (1st week) after being listed. 
2 - Pet was adopted between 8 and 30 days (1st month) after being listed. 
3 - Pet was adopted between 31 and 90 days (2nd & 3rd month) after being listed. 
4 - No adoption after 100 days of being listed. (There are no pets in this dataset that waited between 90 and 100 days).

評価指標はquadratic weighted kappa

データ

以下のように、表形式のデータ、テキストデータ、画像データなどが用意されている

  1. Tabular Data
  2. Text
  3. Images
  4. Image MetaData(JSON形式)
  5. Sentiment Data(JSON形式)

 コンペ初期

コンペ終了まで残り1ヶ月くらいの時期に参戦したので、ベースラインとなるモデルがいくつか公開されていて、僕は以下のカーネルをベースにしてコンペに参戦しました。 www.kaggle.com

まずはこのカーネルのモデルのパラメータをOptunaを使ってチューニングしました。

このカーネルは0.427のスコアを出していますが、僕の最初のサブミットは0.417(Stratified5KFold lgb cv mean QWK score : 0.4448、手動閾値)からのスタートになりました。

このベースラインとなるモデルにさらに

  • Discussionで上がっているマレーシアの人口や地理やGDPの統計情報
  • Densenet121によるimage features extraction
  • ['pure_breed', 'Description_length']など他のカーネルの特徴量

を加え、スコアを0.448(StratifiedKFold lgb cv mean QWK score : 0.4751、手動閾値)まで伸ばします。

この時外部データのcat-and-dog-breeds-parameters(コンペのBreed名とは異なるBreed名で登録されていたりするので、コード中で修正を加えたりした)がどうにも上手く活用できていないまま突っ込んでいました。

そこで以下の作業をして

  • cat-and-dog-breeds-parametersを除く
  • ["breed_prefer"]という["Breed1", "Breed2"]によるTarget Mean Encoding特徴量を追加(過学習を避ける為に['breed_prefer_count']<100 or ['breed_prefer_var']>1.5 の場合-1を代入)

スコアを0.458(StratifiedKFold lgb cv mean QWK score : 0.4718、手動閾値)まで伸ばします。

コンペ中期

このあたりからスコアが伸び悩んだので、あらためてEDAをすることにしました。

ここまでのモデルで、RescuerID_COUNTのimportanceが上位だったので、RescuerID関連の特徴量を探ります。RescuerID_COUNTが上位にくるということはRescuerIDは何らかの時系列的要素を含んでいるんじゃないかなと思いながらEDAしていました。というのも、このRescuerIDはtrainとtestで重複するものがなく、また同じRescuerIDであってもDescriptionを見比べると必ずしも同一人物がRescuerしたという訳でもなさそうだったので、例えばこれがPetFinder.myサイトへの掲載タイミングだったとしたら、時系列を被せない為にRescuerIDがtrainとtestで重複するものがないという点にも納得がいくなとか思っていました。

ただそれは多分間違いで、後々RescuerIDというのは組織IDみたいなもので、必ずしも同一人物がRescuerする訳ではなく、掲載タイミングもそのRescuerID内で共通する訳ではないという考えに変わりました。

何はともあれ、RescuerIDによっては同じ掲載タイミングのものを含んでいるものもあるだろうという考えをもとに、そのRescuerID内のあるペットは優先的に選ばれやすいペットであるかを特徴量として組み込むことを試みました。

  • RescuerIDをもとに['Age', 'MaturitySize', ...]等のvarのaggregation(EDAをしてそのRescuerIDがどういう分布を持つかを表すには何となくvarがいいかなという気がしたのでとりあえずvarのみでaggregationしました。df[df['RescuerID_COUNT'] < 10]に関してはサンプルが少ないということで-1を代入することにしました)
  • df['health1'] = ((df[ ['Health', 'Vaccinated', 'Dewormed', 'Sterilized'] ]==1)*1).sum(axis=1)(健康指標で1が多いペットほど健康的なので引き取られやすい?)また一番上の特徴量と掛け算した['RescuerID_Age_var_health1', ...]みたいなものも作成
  • df['health3'] = ((df[ ['Vaccinated', 'Dewormed', 'Sterilized'] ]==3)*1).sum(axis=1)(健康指標で3が多いペットほど健康状態が不明なので引き取られにくい?)また一番上の特徴量と掛け算した['RescuerID_Age_var_health3', ...]みたいなものも作成
  • df['Age_better'] = df['Age'] / (df['RescuerID_Age_var'] + 2)、df.loc[df['RescuerID_Age_var'] < 0, 'Age_better'] = -1(正直この作り方がいいのかはわかりませんでしたがそこそこimportanceは出てたはずなのでひとまずこれを採用しました。のちにチームを組む@FujitaAtsunoriさんはdf['RescuerID_Age_MEAN_DIFF'] = df['Age'] - df['RescuerID_Age_MEAN']みたいにしてました。分母の+2はエラー対策です)

以上の特徴量を加えたところ、スコアを0.463(StratifiedKFold lgb cv mean QWK score : 0.4881、手動閾値)まで伸ばします。

しかし、ここで卒業旅行が連続で入るため、どうしても作業時間が足りなくなるのでチームメイトを探していたところ、@FujitaAtsunoriさんもチームメイトを募集していてスコアが近かったのでチームマージすることになりました。

チーム atfujita & Y.Nakama

ということで、僕がベトナムで慈愛を積んでいる間にatfujitaさんがマージ作業を進めてくれました。本当にありがたかったです...! 

ここでvalidationについて、今までのStratifiedKFoldはAdoptionSpeedに対してStratifiedしていましたが、ここからのStratifiedKFoldはDiscussionに上がっていたRescuerID_Adoption_MEANに対してStratifiedするようなやり方を採用しました。

そしてマージしたところ、fujita_lgb, fujita_xgb, nakama_lgbのridge_stackでスコアが0.465 (Counter({2: 1074, 4: 1156, 3: 666, 1: 864, 0: 188}))まで伸びました。

ここでどうにも手動閾値が使い物にならなくなってきているようだったので(StratifiedKFoldではどうにもリークがあり、trainデータを学習させた時の閾値に汎化性能がなくなっていたのだと思う)、自動閾値というものを実装することにしました。

  • 自動閾値(trainデータを["State", "Type", "AdoptionSpeed"]でgroupbyしてAdoptionSpeedの分布を["State", "Type"]のグループごとに求め、その分布をtestデータに当てはめてtestデータでのAdoptionSpeedの分布の期待値を算出し、それをもとに閾値を求める)

これにより、スコアが0.470(Counter({4: 1103, 2: 1061, 3: 838, 1: 831, 0: 115}))まで伸びます。

 またどういう層の人が住んでいる地域なのかも特徴量に追加しました。

  • state_HDI(Human Development Index)

これにより、スコアが0.473(StratifiedKFold Final OOF QWK = 0.51325、自動閾値)まで伸びます。

ただ、これはシングルモデルで試しても効果がなかったので、乱数的な要素かもしれません。

そして次にモデルの多様性を増やすことを試みます。

まだlgbとxgbしか使っていないので、ペットコンペということもありcatboostを新たに追加し、fujita_lgb, fujita_xgb, nakama_lgb, nakama_xgb, nakama_catのridge_stackを試みます。すると

スコアが0.477(StratifiedKFold Final OOF QWK = 0.51453、自動閾値)まで伸びました。

[kaggler-ja] NAKAMA

copypasteさん(@copypaste_ds)、カレーさん(@currypurin)とチームマージし、[kaggler-ja] NAKAMAが誕生します。

お二人のモデルの内容については割愛します。

チームマージ後に新しく試したこととしては以下があります。

  • 個人的なモデルの部分で['Fee', 'RescuerID_COUNT', 'Quantity', ...]といった特徴量をnp.log1pで裾を短くする
  • NIMA特徴量(美しさ的なスコア1~10を目的変数にした10クラス分類モデルで、美しさ的なスコアは人間の直感でラベリングされたもの)
  • モデルの多様性として0~4のAdoptionSpeedを0~1に変換して、lossをxentropyにしたモデルを追加
  • カーネルで公開されたStratifiedGroupKFoldを使う

www.kaggle.com

  • GroupKFoldにおけるitrをもとに、StratifiedKFoldのitrを決定する(カレーさんによる実験)

 

f:id:NmaViv:20190401210934p:plain

GroupKの平均itrから0.05倍刻みで増加させたitrをStratifiedKに適用した時のQWK推移

カレーさんがvalidationをGroupKFoldで行っていて、CVにリークがなく、OptimizedRounderをそのまま使用しても汎化性能のある閾値が取れていました。

またカレーさんのモデルが加わったことで、チーム atfujita & Y.Nakamaの時は上手くいかなかったStratifiedKFold+OptimizedRounderも安定したスコアが出るようになりました。Counterを比較してみても、atfujita & Y.Nakamaの時よりもいい分布になっていそうです。

  • StratifiedKFold(OptimizedRounder)スコア0.479(Final OOF QWK = 0.5084, Counter({1: 1002, 4: 984, 2: 947, 3: 945, 0: 70}))

そして組み合わせを試したところ以下のような結果になりました。

  • StratifiedGroupKFold(OptimizedRounder)
    LB: 0.477 CV: 0.46563
  • StratifiedGroupKFold(自動閾値
    LB: 0.481 CV: 0.46607
  • StratifiedKFold(round数)(自動閾値
    LB: 0.481 CV: 0.50999
  • StratifiedKFold(round数*1.3)(自動閾値
    LB: 0.484 CV: 0.51380

最終的に、

  1. 攻めのStratifiedKFold(round数*1.35)(自動閾値
  2. 守りのStratifiedGroupKFold(OptimizedRounder)

の二つを最終サブとして選択しました。

LBは以下の通りです。

f:id:NmaViv:20190401220308p:plain

Public Leaderboard

試したけど取り入れてないもの

  • 自分のモデルでxlearnを試したもののStratifiedKFoldでcv mean QWK score : 0.4397だったので結局取り入れなかった、調整が下手だったのかも
  • ['refundable_in_Description', 'chinese_in_Description','RM_in_Description']みたいな特徴量を作ったものの、あまり効かなかったので結局使っていない

反省点

  • RescuerID_COUNTのimportanceが上位で、自動閾値を実装するときもTypeを意識していたのに、どうしてRescuerID_COUNTをRescuerID_dog_COUNT、RescuerID_cat_COUNTに分断しなかったんだろう(['only_dog_RescuerID ', 'only_cat_RescuerID']みたいな特徴量を作っていたがあまり効かなかったので、こちらに気づくべきだった)
  • 僕のモデルでIMG特徴量が重複するものが34個、IMG特徴量+AdoptionSpeedが重複するものが29個あり(カーネルでも同じ写真があると話題に上がっていましたが)、これは「IMG特徴量が一致するときAdoptionSpeedも一致しがちな根拠としては,その写真に2匹のペットが写っていて,別々にリストアップされる(つまり異なるレコードでIMG特徴量が一致する)が引き取りは2匹でないとダメみたいなものが存在するため」みたいな仮説を立てたものの、結局上手く使えなかった、リークの一因になっていたりするのかな
  • shake対策として、ks_sampling、aucでtrain, testの2値分類のimportance比較により、shake要因となる特徴量を除くことを試みたが、微妙な感じで終わってしまった
  • 上記のshake対策をやっておいて、train/testを分類するモデルを作成してtestっぽくないtrainを何割か削除みたいなことをアイデアとして思いつかなかった

【追記(4/10)】

4/10にコンペの結果が発表されました。

金メダル圏→銀メダル圏と残念な結果となりましたが、一緒に闘ってくださったチームメイトには感謝しかありません。

上位のソリューションが公開されているので、確認してみると本当に色々なことをやっているので、しっかりと反省して次に活かしていきたいです。

反省点(追記)

  • PetFinder.myのサイトを調べてはいたが、登録まではしていなかった。上位チームの中にはサイトに登録して、そこで写真に対するレーティングがあることを知り、そのレーティングを特徴量として追加していた、やれることはやっておくべきだった 
  • RescuerIDに時系列的要素があるのではないかと疑っていたが、RescuerIDは自動生成されたもので、最初の4, 5文字に対してソートをかけることで、古参ユーザーたちの類似を見出せたらしい
  • Chinese Descriptionに対しては、['chinese_in_Description']しか試していなかったが、English DescriptionとChinese Descriptionで分けて、['Description_length', 'Description_word_length', 'Description_word_unique']など検討するべきだった
  • NNとgloveとFastText取り入れることできなかった(経験値不足)

ペットコンペ反省会でいっぱい反省します。