俵言

しがない社会人が書く、勉強とかのこと。最近は機械学習や kaggle 関連がメイン。

kaggle で初めてメダル(silver) を取ったものの、お詫びせざるを得ない件

チーム(AgroDesign) で参加していた Human Protein Atlas Image Classification で22位になり、silver medal を獲得しました!

f:id:Tawara:20190112235803p:plain
Private Leaderboard

前参加した台風コンペ(@SIGNATE)では被災して 最下位に落ちたため、この結果は本当に嬉しかったです。正直言うと gold 取りたかったですが 「全部やる」努力が足りませんでした...。
kaggle 自体はずいぶん前に登録していたものの、過去に何度かやろうとしては放置するを繰り返していたので、ちゃんと諦めずに完走できたのは本当に良かったです。そしてそれが最後二日での「脳汁ドバア」(Public で 100位、Private では150位 程の更新) に繋がりました。

というわけで何をやったかと、今回は計算資源的にかなり支援を受けてたのでそこら辺の経緯を書こうと思います。毎度ながらダラダラ書いてしまったので適当に読んで頂ければと思います。

ことの始まり

chomeの誘い

"台風コンペで涙を飲み、土砂崩れコンペでのリベンジを誓った Tawara であったが、鳴かず飛ばずで締切一週間前を迎え虚無の感情を覚えていた。そんなとき、会社の先輩 chome から声がかかる。「君、タンパク質に興味ない?」"

というわけで、ひょんなきっかけから チーム Agro Design に(途中からですが)アサインされました。11/29のチームミーティング@東京 (kaggle meetup tokyo #5 の前日)から参加して、偶然にも前回同様 1月半(弱)位の取り組みです。
土砂崩れ終わったら画像から一旦足洗おうと思ってたので誘われなかったら参加しなかったと思うんですけど、それで初メダル獲得したので人生分からんもんです。

チーム 「AgroDesign」 の誕生

この名前はリーダー Nishigaya さんが代表を務めている農薬研究開発系スタートアップ (株)アグロデザイン・スタジオ に由来しています。 この会社は東大IPC起業支援プログラム の支援を受けており、その一環として AWS activateパッケージ10万ドル分(!!) を無料提供されていました。

ここで、何を思ったか社長は「計画中の農薬開発プロジェクトに使えそうなネタだし Human Protein Atlas Image Classification やるお」 とkaggle への投入を決意(※これ聞いたとき「マジかよ...」って正直思いました)。協力者を募って AgroDesign が生まれました。

詰まるところ僕は偶然お零れに預かった協力者(※念のため言っておくと(株)アグロデザイン・スタジオ さんと雇用関係はなく、金銭の授受もありません)だったという感じです。最後の一週間一番AWS使ってたけど。
計算資源という意味ではおそらく参加チーム中最も恵まれていたと思います。にも関わらず gold 取れなかったのは... 本当に申し訳ありません。 kaggler の皆様には「お前、金かけたにも関わらず gold 取れなかったんだってなww」 って煽られる覚悟はしてます。


そんなこんなで誕生した AgroDesignのチームメンバーは以下の5人。

Yuki Nishigaya: リーダー兼スポンサー。生命科学博士。
chome: 会社の先輩。Nishigaya さんとはとある新事業創出プログラムで知り合ったらしい。
Sheldon: AIエンジニア。chome さんと同様の経緯で Nishigaya さんと知り合った。
Agrwhy: Sheldon さんの同僚。要するに僕と同じパターンで参加。
Tawara: このブログの著者。趣味でGPU マシンを買って台風コンペに出たが最下位に終わった。

正直、社長の強力なドメイン知識を活かしきれなかったところとか、メンバー全員の力を出し切れなかったところとか反省点めっちゃあるのですがそこは一旦置いておきます。

役割分担(?)的には以下のような感じ。

Yuki Nishigaya: ドメイン知識担当。タスクの説明や論文の説明、画像の解釈など。
chome: 実は色々立て込んでいて終盤あまり参加できてない。「Tawara君を誘い込んだのが一番の仕事」(本人談)。
sheldon: AWS の管理やらデータの準備やらベースライン構築やら一杯お世話になりました。こちらも最後らへん仕事のせいで非常にしんどいことに。
Agrwhy: モデルをゴリゴリ改善。sampling の効果などを一番調査していた。
Tawara: 好きに動き回ってた。論文を頑張って実装したりとか。終盤はAgrwhy さんと競ってた。

タスク設定

手短に言うと

「与えられた画像(RGBではなくRGBY)が28クラスのうちどれに当てはまるか識別する、ただし当てはまるクラスは一つとは限らない」というもので、 機械学習的な意味では multi class かつ multi label かつ imbalanced class data での画像分類という難しいタスクでした。
あと画像サイズが512x512であり、個人的にはこの大きさの画像扱うのが初めてだったのでそこも少しつらかったです。

因みに生命科学的にはどういう意義があるの?

この点については社長が詳しく教えてくれたので少しだけ触れておきます。(間違った理解で書いてたらごめんなさい。)

細胞の中には様々なオルガネラ(有名どころは核とかミトコンドリアとか)があり、人間の持つタンパク質がどこに送られるのか?を知りたいという要望があります(「正常な送付先ではない => 遺伝病の発見」といった利用法もあるとか)。 そこで、送付先を知りたいタンパク質を人工的に着色して(慣例では緑色)細胞のどこに送られるかを確認します。今回のコンペは「対象のタンパク質(緑色に着色)が送付されたオルガネラが28種のうちどれなのか」を分類するというものでした。

緑以外の青、赤、黄 は行き先のわかっているタンパク質を着色して流し込むことで対応するオルガネラ(今回だと青: Nucleus, 赤: Microtubule, 黄: Endoplasmic reticulum)を発光させ、細胞の形を確認するためのもの。これらと照らし合わせることで目的のタンパク質(緑色)の行き先を判別します。(なのでRGBYは独立して撮像されたものであり配色そのものに意味はない。)

従来は人手で確認していたのですがそもそも人はRGBしか認識できないことから限界があり(今回のコンペの画像も本来は 4 channel だが、人が解釈する際には 無理矢理RGB画像に落とすことになる)、多チャネルから識別できるアルゴリズムの開発には非常に意義があるとのことです。

ここら辺の話(他にもどうラベル付けしているなど)を最初から分かってたのはかなり大きかったと思うのですが、結局あまり活かせませんでした。つらい。

最終提出までの道のり

本来なら4人(社長はドメイン知識担当なので画像のチェックなどは行っているがモデルは作ってない)のアンサンブルを提出するのですが、実は諸事情により最終提出は僕が作った single model のみに由来するものでした (K-fold での ensemble などは行っている)。
非常に勿体無い話なんですけど仕事の事情とかもあったので仕方なかった部分ではあります。もちろん組み込まれている知見はメンバーから得られたものであり、おそらく単独だと silver を取れてないです。

というわけで以下は最終的な提出に至るまでの single model の経過です(一部書きやすさのために時系列をゆがめてます)。色々条件を変えながら試しているので記憶があいまいですが、おおよその score を一緒に記載します。

0. ベース

public: 0.40611, private: 0.36909

使用モデル

chainerCV の pre-trained SEResNeXt50 を使用。ただし、上記のタスク設定で触れたようにRGBY の順番に意味は無いことから、 Conv1(ch: 3->64, kernel_size=7x7, stride=2, pad=3) の重み(shape:(64, 3, 7, 7)) を平均し(shape: (64, 1, 7, 7))、それらをコピーして入力が 4 channel の Conv層にしてます。
ついでに、効果のほどは微妙ですが max pooling で情報がつぶれるのが良くないみたいのを聞いたことがあったので、Conv1 の次の max pooling を Conv(ch: 64->64, kernel_size=3x3, stride=2, pad=1) に変更してました。

学習方法

kaggle meetup tokyo #5 で話を聞いた PFDet チームに影響を受け、cosine annealing schedule を使用 (あと step schedule も試したりしている)。
optimizer は SGD (lr =0.1) + Nesterov Momentum(momentum=0.9), Weight Decay は 1e-04 です (よくあるやつ)。 学習 epoch は 20 epoch にしてました。途中で data の sampling を変えたりして iteration 数が変わったりしますが epoch そのものはほぼ固定です。

損失関数にはクラスごとの sigmoid cross entropy loss を使用。focal loss とか、kernel で話が出ていた macro-f1 loss も試しましたがいまいち効果が出ませんでした。

使用するデータの扱い

画像サイズ
当初は計算時間や使用する計算資源に遠慮して 256x256 でやってました。まあすぐでかくするんですけど。「256x256 でも0.57 出せるよ」みたいなdiscussion がコンペ後半に出現してましたが僕には無理だった...

sampling の方法について
最初は何もせずランダムにミニバッチを生成して学習。後から変更。

external data
最初は使用せず。後から追加。

data augmentation
Left-Right flip, Up-Down flip, rotation(15 * n 度, 1 <= n < 12) 及び random erase を使用。これも後から変更が入ります。

推論時の処理

test time augmentation
Left-Right flip(2) * Up-Down flip(2) * 90 rotation(2) = 8 通り。これはコンペ中変更せず。メンバーでは15通り(ハイライト変更など)でやってる方もいました。

閾値の設定
当初は validation set に対する score が最大になる閾値を使っていました。後から変更してます。


ここからは、行った変更とそのときの(おおよその)スコアです。

1. 画像サイズの変更

public: 0.46200, private:0.44878

画像サイズを 256x256 => 512x512 に変更。目視確認でも明らかに分かりやすさが異なるクラスがあり、実際明らかに効果がありました。

2. external data の追加

public: 0.48666, private: 0.49559

今回のコンペの leakage が発覚した HPA18の一部を使用(全部使うと学習時間がめっちゃ長くなるので...)。この時点では、比較的希少なクラス((8, 9, 10, 12, 13, 15, 16, 17, 18, 20, 22, 24, 26, 27) を含むデータを追加。
僕が使う頃はもう leakage が公表されて leaderboard が reset された後でした。このときの reset が結構 rare class の分布をゆがめたんじゃないかなって思います。

因みにこのexternal data の処理の仕方の件で終了一週間前くらいにひと悶着あるのですが、僕が頑張るきっかけになりました。

終わってからわかったことですが、この提出 private の方が高いんですよね。 sampling 弄らない方が実は良かったのか...?それか閾値が偶然にもうまく噛み合ったとか? test data は見れないので永遠の謎になりそうです...

3. over & under sampling っぽいこと

public: 0.52706, private: 0.45834

f:id:Tawara:20190114042231p:plain

使用するデータは同じまま、mini batch 内のクラスの比率を弄っています。具体的には
* minor : 8, 9, 10, 15, 17, 20, 24, 26, 27
* middle: 1, 3, 4, 6, 11, 12, 13, 14, 16, 18, 19
* major: 0, 2, 5, 7, 21, 22, 23, 25
とグループ分けして、mini batch の 50% を minor, 30% を middle, 20 % を major から選択。数値とグループ分けは決め打ちなので論理的な根拠はありません。
何でこんなやり方してるのかというと、こうするのが楽だったからなんですが正直怠慢だったと思っています..

それにしてもこれ、public は上がって当時はかなり喜んでたんですけど private は 2. よりもかなり低いですね...

4. 閾値の調整

public: 0.54751, private: 0.48085

validation に対して最適な閾値を決めると特に rare class の閾値が高くなってしまうため、閾値の調整を行いました。色々試みたものの時間的な都合と自分で納得できる根拠が無かったことから、基本的に一律で決めるようにしています。このときの値は 0.2 です。

これでもまだ 2. に負けてるのか...

5. data augmentation の変更

public: 0.55454, private: 0.49894

この頃、discussion などで 「training data 中に類似画像沢山あるから validation うまく切らないとvalidation 良くても test は良くならない」みたいな議論が行われていました。ただクラスタリングするとかの対処がちょっと出来なかったので代わりに data augmentation の変更で出来るだけ「ほぼ同じ画像の存在」を無くそうとしました。

変更後は、「Brightness => Contrast => expand => 90 rotate => Left-Right Flip => Up-Down Flip => Random Erase」という順に適用します。 ※本当は Brightness => Contrast の部分は chainerCV の random_distort を使用したかったんですが、そのままだと3channel 専用だったのでこの時点では諦め、その一部であるBrightness, Contrast だけを使いました。

因みに rotate を 90度だけに変更したのは処理時間の都合です。密かな努力として、 mini batch を作る部分を如何に並列化・高速化してAWSの使用時間を短くするかめっちゃ頑張ってたのですが、これに関しては別の記事で書こうかと思います。いや思ったより data augmentation って時間かかるんですよね、画像が大きいと特に。

6. external data の 処理方法の修正

public: 0.56681, private: 0.50457

"それは、確か終了4日前(1/7) のこと。 discussion を眺めていた Agrwhy さんが言う, 、「external data の処理ってどうなってます?」"

公式の external data の discussionの中で、「external data はそのままグレースケール化するのではなく、対応する色のチャネルを取り出す方が良さそう」というコメントがありました。何でもっと早く気づけなかったのか...後悔してます。

実際それで学習し直したことでスコアが向上したんですけど、いやまさかそんな処理が必要だとは...。当時 score が停滞していたため、この 0.012 ほどの増加はかなりありがたかったです。

しかしながら、こうなってくると今まで使ってたデータの前提が変わってしまうため非常に困ったことになりました。残り時間も少なくて、でも現段階だとbronze すら危うい... (結果的にこのスコアはギリギリ bronze 位でした.) 更に言うと残り提出回数も少ない...

ここで初めて計算資源がいくらでも使えることにモノを言わせることにし、ensemble のための学習を仕込みます。ただ、今から別のモデルを調整するのは色々扱いきれないのであくまで今使っている single model に由来する ensemble を仕込んでました。
その時点でのsilver のラインが 0.58 とかだったので、なんとか+0.14 して欲しいという気持ちでした。single model での ensemble でなんとかそれくらい上げて、かつ他のモデルと組み合わて何とかもう一押し... もうただのお祈りででしたね。



7. snapshot ensemble(4cycle) × 4 fold ensemble

f:id:Tawara:20190114033531p:plain
25 hours to go

public: 0.60099, private: 0.54638

出勤前に提出したんですけど、正直言って嬉しすぎてちょっと泣きそうでした。cyclic cosine annealing + snapshot ensemble も 5 fold ensemble もこれより前に試していてそれぞれ上がり幅が 0.005と0.01 ほどだったので(だから0.58は行って欲しいと考えていた)、こんなに上がるのは想定外だったんですよね。
因みにタイトルの "4" fold は誤りではなくて、5 fold 学習させてたら 1fold だけ学習が間に合わなかったせいです。このタイミングで調子悪くなるのは勘弁してほしかった...

この snapshot ×K-fold の ensemble については色々検証したいことがあって、これ関連で Late Submit しまくってるのですが、別の記事で書こうと思います。参考にした塩コンペの discussion では「組み合わせてもそんなに効果無い」みたいな話だった気がするんですけどねー。今回のデータだから発生したのか単に学習 epoch が不足していたのか...

8. 最終提出

f:id:Tawara:20190114043305p:plain
pray
public: 0.60276, private: 0.54891

上のものは 20 epoch * 4 cycle を 5 fold 回したもののですが、6. でexternal data の処理方法を修正したために最適な epoch が不明になったので 40 epoch * 2 cycle も 5 fold 回していました。
これらすべての ensemble が最終提出です。微量の増加だったのですが、仕込まなないよりは良かったです。 今思うと違うモデルもっと仕込んどけやって話なんですけど、そこまでの精神的余裕が無かったというか慣れてなかったというか...、色々惜しまれる部分ではあります。

ってわけで後は銀から落ちないことを祈るだけになりました。

9. shake up

もう何か放心してた。

f:id:Tawara:20190114043700p:plain
11mins to go

f:id:Tawara:20190114043932p:plain
result

正直びっくりしました。shake down を恐れて祈りを捧げてたのに、逆にこんなに順位が上昇するとは... 最下位に落ちた台風のときと全く逆の状況でしたね。snapshot ensemble × k-fold ensemble で汎化性能が上がったのだとしたらやった甲斐はありました。

ただ嬉しかったけど、嬉しかったけどあと8位で gold だったのかあー!あー何てこったい。絶対まだできることたくさんあったでしょ!

まとめ

主に印象に残っているやったこと

  • SE-ResNeXt50 を fine-tuning
  • external data の追加
  • 不均衡データに対処するために mini batch 内のclass 比率を調整
  • data augmentation の追加
  • cyclic cosine annealing + snapshot ensemble × 5 fold ensemble

やったけどいまいちうまくいかなかった(効果がわからなかった)こと

得られたこと

  • kaggle でのコンペ完走
    kernel や discussion を読み漁る経験も今回が初めてでした。
  • AWS を使ったコンペ経験(ただし、依存症の恐れあり)
    AWS使ったこと無かったので。ただ今回使わせて頂けたのは超超特殊ケースなので、次回以降AWS使いたくなった時がこわい...
  • チームでの参加経験
    みんな kaggle 慣れしてないこともあって結構反省点がありますが、チームでメンバーと相談しながらできる環境はとてもありがたかったです。
  • silver medal 獲得
    これはいわずもがな。具体的な結果が得られたのはとても嬉しいです。

反省点

  • agrwhy さんが気付いてくれた discussion はその前から読んでいたのに、データの処理がうまく行ってないことに思い至らなかったこと
    処理自体は Sheldon さんにやって頂いていたのですが、discussion 見たときにすぐ確認するべきでした。本当に申し訳ない...
  • コンペの途中で思考停止していたこと
    いたずらに学習を回すマンになっていた気がします。もっと計画的に動けた気がする。やっぱり進捗が無いのが怖くてついつい学習回して提出するのですが、そのせいで計画性が無くなってましたし、このブログを書く際の振り返りにもめっちゃ苦労しました。
  • 社長のドメイン知識を有効活用できなかったこと
    クラスの関係性の知識などもあったのに本当に申し訳ない...(二度目)
  • 最後の ensemble でもっとモデルに多様性を持たせられなかったこと
    これは single model にこだわる気持ちを持ってしまったり kaggle 慣れしてないみたいなところがあったり色々原因ありますが、少なくともやれることはあったはずでした。本当にもったいないです。

終わりに

反省点色々ありましたが、このチームで参加させていただいたのは本当に幸運でした。皆様本当にありがとうございました。また生命系(?)のコンペでご一緒できたらと思います。
ただ、計算資源に余裕があったにも関わらずgold が取れなかったこと、本当に申し訳ありませんでした。お詫び申し上げます。

さて、次のコンペこそは画像系以外をしたい、どうしよっかなーと思っていたところ..おや通知が...?

f:id:Tawara:20190114051954p:plain

ん?

f:id:Tawara:20190114052045p:plain

んんん?

というわけで、何か引きずり込まれたので電線コンペにチャレンジするかもしれないです。

www.kaggle.com

暫く眠らせてた自宅マシンで頑張ります!



おまけ

1: チーム名の遷移

初期:Agro
AgroDesign から取った名前。まだわかる。

中期:Ham-Hams
僕を誘ったのに僕よりも team への参加が遅かった chome さんのプロフが何故かハム太郎だったため、全員のプロフがそれに倣ってハム太郎系列になり、気付けば名前も変更されていた。

f:id:Tawara:20190114053328p:plain

因みに、

やばいやつら認定されて「やばっ」ってちょっと思ってました。

後期:TAKI-GYOs
理由は後述しますが、「滝行ズ」の意です。

f:id:Tawara:20190114054344p:plain

終了時:AgroDesign
やっぱ最後は締めたいよね。

f:id:Tawara:20190112235803p:plain

2: AWS の代償

社長からのお言葉「いくらでも使ってくれていい、ただし gold medal を取れなかったときは...」 「滝行をやってもらう!」

f:id:Tawara:20190114060036p:plain

はい、gold とれませんでした、すみませんでした...。死なないか心配ですがやってきます。生きてたら感想ブログ書きます。

因みに、Agro Design のAWS利用料(10/20 - 1/10) は以下です。※グロ画像注意

f:id:Tawara:20190114060648p:plain
total: 20,000

今回のコンペでAWS の恐ろしさを知りました... やっぱり自前のマシン買って正解やったなって本当思います。 ただ、AWS で学習させると「このモデルは○○円」って思うようになるのはちょっと面白いと思いました。