画像系初心者による kaggle 細胞コンペ 2019 まとめ

本記事の概要

Recursion Cellular Image Classification (以降細胞コンペや本コンペと略します) という kaggle の画像コンペに参加したのでまとめます。筆者は一年ほど kaggle をやっていますがまともに画像コンペに参加したのは初なので、幾つかの気付きも書き残しておこうと思います。

コンペ概要

細胞コンペではある実験に使用された細胞画像を 1,108 クラスに分類するというタスクを解き、その際の multi class accracy の高さを競います。それぞれのクラスは実験において細胞に適用される small interfering RNA (siRNA) に対応しており、siRNA とは下記に説明するメカニズムで生物の細胞を生成する過程で特定の機能を持つ細胞の生成を阻害する効果のある RNA です。

多くの生物は自身を構成するタンパク質の設計図である DNA を持っており、これをもとに体細胞を生成します。タンパク質の生成過程では fig 1 のようにまず二重螺旋構造の DNA をほどいて相補的に転写した一本鎖の messenger RNA (mRNA) が作られ、その後 mRNA の各要素に対応するアミノ酸がくっつくことでタンパク質が生成されます。siRNA は mRNA に比べると短い RNA であり、その塩基配列を特定のパターンにすることにより mRNA の特定の箇所と結合することができます。この結合によりその後のアミノ酸との結合を阻害することで、特定の mRNA へののタンパク質の合成を阻害でき、結果として一部の機能を持つ細胞の生成を狙って抑制できます。(筆者がコンペのために付け焼き刃でインプットした知識なので間違い、情報の不足等あるかもしれません...)

コンペの背景についてより詳しく知りたい方は、公式にデータの説明のためにも使われている RxRx.ai が非常に参考になるため、そちらをご参照ください。

f:id:guchio3:20191007192331p:plain
fig 1: DNA からタンパク質が生成される過程

また、このコンペの結果作成されたモデルが実ビジネス上どのような価値を生み出すのかというのも気になる点です。冷静に考えてみると実験において siRNA の適用は実験者によって行われるため、各細胞にどの siRNA が適用されたかというのは予測するまでもなく明確なはずであり、コンペ中も discussion でこれに関する議論を幾度か見かけました*1。実は筆者もこれに対する明確な回答を持ち合わせていないのですが、コンペの overview に下記の記述があることや RxRx.ai の内容を見るにこのコンペのタスクを解くためには Batch Effects や Plate Effects と呼ばれる実験条件の影響をうまくキャンセルして各クラスの普遍的な特徴を抜き出す必要があり、モデル構築の過程で行われるこの操作等が実ビジネスに使われる部分なのではないかと推測しています。

This competition will have you disentangling experimental noise from real biological signals. Your entry will classify images of cells under one of 1,108 different genetic perturbations. You can help eliminate the noise introduced by technical execution and environmental variation between experiments.

その他、以下に筆者が本コンペで重要だと感じた幾つかのテーマについて記述します。

画像特性

細胞コンペの画像データは通常の 3 チャネル (RGB) ではなく、 6 チャネルで配布されています。各チャネルは fig 2 のようにそれぞれ細胞核小胞体アクチン核小体ミトコンドリアゴルジ体に対応しています。

公式に配布されている util ツールを使うと fig 3 のように無理やり*2 6 チャネル画像から 3 チャネル (RGB) 画像へと加工でき、コンペ中も 6 チャネル 3 チャネルどちらで学習をすべきかという議論が幾つか上がっていました*3が、当然情報量が欠落しない 6 チャネルを使用する方が精度がでるため上位陣は基本的に 6 チャネルを使用していました。

f:id:guchio3:20191007195206p:plain
fig 2: 各チャネルの可視化例
f:id:guchio3:20191007195433p:plain
fig 3: fig 2 の例を 1 つの RGB 画像に合成した例

また、画像サイズが 512 * 512 と比較的大きく、当然この画像サイズのまま使用したほうが精度は出ますが、反面時空間計算量は増すことになるため、高速に PDCA を回すためや大きいモデルを使用するために画像を小さく resize して使う等の試行錯誤も行われていました。*4データサイズは train 36515 * 2 * 6 枚・test 19897 * 2 * 6 枚で (* 2 されているのは後に説明するように site が 2 つあることに起因しています) 計 46 GB *5 となっています。

一方、大きな画像サイズへのより単純な対応としてよりスペックの高いマシンリソースを使うという手もあります。実は細胞コンペには 7/22 の team merger deadline までにフォームを提出すると 1 team あたり $300 の GCP クレジットがもらえ、TPU を利用して参加することができるという特殊なルールがありました。筆者はこのコンペに参加した時点ですでにこの期限後だったため利用できませんでしたが、公式にチュートリアルコードも提供されており TPU の導入には非常に良いコンペだったのかもしれません。他コンペで TPU を使った筆者の同僚から少なくとも今時点では GPU を使うより安くて早いという話を聞きますし、また notebook で TPU が使えるようになるみたいな話もあるようなので、今後 kaggle でも TPU の利用者が増えてくるかもしれません。

実験構造

前述の通り細胞コンペでは幾つかの実験の結果得られた画像の分類をするのですが、この実験の構造が少々ややこしく、この実験の構造を理解することが良いスコアを出す上で非常に重要でした。

各画像は experiment, plate, well, site により構成される実験で生成されており、これらの組み合わせ 1 つ対して 1 つの画像が紐づきます。それぞれは fig 4 のような関係にあり、以下のような情報を表しています。

  • experiment : どのような細胞に対して行われた何番目の実験か
    • 細胞は HEPG2, HUVEC, RPE, U2OS の 4 種類存在
    • 基本的に*6 1 experiment に 1,108 種類の各 siRNA が 1 つずつ存在
    • ex. HEPG2-04 は HEPG2 に対して行われた 4 番目の実験
  • plate : 何番目の plate で行われた実験か
    • 実験は実際には 1 度に 1 plate ずつ行われ、毎度微妙に実験設定 (部屋の湿度等) が異なる
    • 各 experiment は 4 つの plate から成る
  • well : plate のどの位置で行われた実験か
    • 1 plate において実験可能な位置は 308 箇所存在
  • site : 2 つの内どちらの site から撮影された画像か
    • ある well に対して 2 種類の位置から撮影が行われる

f:id:guchio3:20191008031507p:plain
fig 4: 実験構造

siRNA の細胞への影響は細胞の種類や実験環境によってかなり大きく異なります。例えば fig 5 は class 100 の HEPG2-01 ~ HEPG2-06 の画像を羅列したものですが、同種の細胞に対して同じ siRNA を適用したのにも関わらず HEPG2-01 が他よりかなり明るい等見た目が大きく違うことがわかると思います。また、配布されるデータの中に書く画像の画素値に関する統計情報が格納された pixel_stats.csv というもの (fig 6 参照) があるのですが、experiment についてこれを集計すると統計値上もかなり大きく値が異なることがわかります。この構造に関して、細胞コンペでスコアを伸ばすためには主に以下の 3 点が重要でした。

f:id:guchio3:20191007203207p:plain
fig 5: class 100 の HEPG2-01 ~ HEPG2-06 の画像
f:id:guchio3:20191008010516p:plain
fig 6: pixel_stats の集計

normalization
experiment 毎の各チャネルの画素値の mean, std を使って normalize することで experiment 間の実験環境の差を埋めることができます。細胞コンペで使われた normalization のやり方には画像毎の mean, std を使うものや experiment * plate 毎の mean, std を使うもの等何種類かが存在し、normalization は discussion でもよく話題に上がっていたようです。*7

validation 戦略
train と test はそれぞれ異なる experiment によって構成されているため、この構造を反映するために validation set も train set と experiment が混ざらないように作る必要があります。discussion を眺めている感じ、多くの参加者が幾つかの experiment を validation set として、それ以外を train set として使うという validation の切り方を採用しているようでした。*8

sampling 戦略
experiment 間で大きく画像の性質が異なるため、ミニバッチの作り方によって BatchNorm 等の効果が大きく変化します。筆者はコンペ中にこれをうまく扱うことはできなかったのですが、上位解法ではこれを利用しているものも散見されました。また、コンペ中に pytorch の .eval() を使わないほうがスコアが高くなるという現象が報告されており、これも experiment 間の差異をキャンセルできることが原因だと考えられます。(.eval() モードでは BatchNorm に使われる統計量が batch 内ではなくそれまでの全体平均となる)

control 画像

前述の通り、実験毎の設定の微妙な違い等により同じ siRNA を同種の細胞に適用したとしてもその影響の見え方は異なります。この実験毎の差をできるだけキャンセルするため、siRNA の適用実験では普通各 plate 毎に control 用の実験を行い、この control 画像との差分から siRNA の影響を評価します。細胞コンペでは各 plate につき 1 個の negative control と 30 個の positive control が用意されていました。negative control とは siRNA を加えていない細胞のこと、positive control とは siRNA が適用されており、また適用された siRNA が何かという情報がわかってる細胞のことで、どちらも train にも test にも存在します。また、この計 31 個の control には分類対象の 1,108 種類の siRNA とは別の siRNA としてクラスラベルが振られており、よって細胞コンペには 1,108 + 31 = 1,139 クラスが存在することになります。control についての説明はこの discussion topic 上の説明が非常にわかりやすかったので是非ご参照ください。

discussion ではこの control 画像の使い方が盛んに議論されていました*9が、筆者の印象では結局これをうまく扱えたチームはなく、1,108 クラス分類モデルの pretrain 用につかったり追加の 31 クラスとして 1,108 クラスの画像と一緒に学習して数 % 程 accuracy を伸ばすのに使っているチームがほとんどでした。

リーク

細胞コンペには実験構造に基づいた 3 種類のリークがありました。

plate に含まれる siRNA の組に規則性が存在する
公式アナウンスにもあるように、plate 内に存在する siRNA の組に一定の規則性が有り、この法則に沿ってモデルの推論結果を後処理することでより正確な出力を作成できます。これを行う notebook も公開されており、多くの参加者がこのリークを利用していました。

各の siRNA が 1 つの experiment 内に一度しか現れない
各種 siRNA は 1 つの experiment 内 (更にいうと plate 内) に一度しか現れないという実験設定になっており、この性質を利用するとモデルの出力確率をもとに割当問題を解くことでより正確な予測を行うことができます。notebook にもハンガリー法を用いてこれを行うコードが挙げられており、多くの参加者がこのリークを利用していました*10

well と siRNA の紐づきが完全に同じ experiment が幾つか存在
公式アナウンスにもあるように、well と siRNA の対応が全く同じ experiment が幾つか存在し、その一つである HUVEC-18 が private test set に含まれているというリークがありました。これはあるユーザによって発見され、その後公式に HUVEC-18 を test set から抜くというリーダーボードのアップデート処理が行われたようです。HUVEC-18 は test set からは抜かれますが siRNA がわかった状態でデータとして配布はされ続けているため追加の train データとして使用でき、これを利用している方も多くいました。筆者は今本記事の執筆中にこの事実を知ったので、コンペ中にこのリークを使うことはできませんでした ... orz

metric learning

細胞コンペでは、最も upvote を集めた phalanx san の discussion topic で紹介されていたことや、過去に開催された細胞コンペの 1st place である bestfitting の解法でも使用されていたことが主な要因だと思いますが、多くの参加者が metric learning に取り組んでいました。中でも ArcFace を利用するかたが非常に多かったのですが、discussion ではロスがうまく収束しないという意見を非常に多く見かけ、終了直前に 細胞コンペで ArcFace をうまくワークさせるための tips が discussion にあがったりもしましたが、結局最終的に使用しない人の方が多かったように見えます。ArcFace の仕組みに関する情報は幾つか見かけますが、これを使用する上での試行錯誤に関する情報はあまり見かけないので本コンペの discussion はこの意味での価値が高いのではないかと思います。筆者はネット上に落ちている pytorch 実装の ArcMarginProduct を easy_mergin モードで test 時にはすべての label を 0 にするようにして使用したところ CrossEntropyLoss よりは性能が良かったのでこの設定で使用しました。なお、metric learning の仕組みについては良い日本語記事が沢山あるため割愛します*11

上位チームの解法

コンペ終了後、多くのチームが多様な解法を共有してくれていますが、その中でも筆者の目に止まった幾つかの解法の主要な部分のみ記述します。解法の詳細が知りたい方はコードとともに上位解法をまとめてくれている discussion topicがあるので、是非そちらをご参照ください。

1st place

モデルは DenseNet161 ベースのものを使い、Optimizer には Adam を、ロスは Loss = ArcFaceLoss / 2 * 0.2 + SoftmaxCrossEntropyLoss * 0.8 を使用しています。ArcFaceLoss と SoftmaxCrossEntropyLoss それぞれを計算する際、後者を算出する FC 層の入力のみに対して BatchNorm を使用しないことが重要だったらしいです。学習時の Data Augmentation には多くのチームが利用している Flip, Rotate90, Crop に加えて、各チャネルに対して channel = channel * a + b, where a ~ N(1, 0.1), b ~ N(0, 0.1) という処理行い (ノイズの追加が目的...?)、さらに CutMix を使用しています。また、TTA (Test Time Augmentation) としては flip と rotate90 の計 8 パターンを使用しているようです。加えて、多くの上位チームが行っていた各 control 画像も学習対象として使う (結果として 1,108 クラス分類問題が 1,139 クラス分類問題になります) 手法をとっており、また各入力画像が 4 種類の細胞の内どれかという信号も入力に使用しています。

また、他の多くの上位チームと同様に pseudo labeling を行っていますが、このチームは epoch 毎に、また自信のある top-k 個ずつのサンプルを pseudo labeling するという点で他のチームと異なります。自信のある top-k 個のサンプルは最も自信のあるクラスと 2 番目に自信のあるクラスの自信度の差を基準に選定し、また予測ラベルが同じ plate 内に複数個ある場合はより自信のある方を採用するというアプローチをとったようです。

control 画像についてもかなり多くの実験を行ったようですが、結局うまく扱えず最終スコアはこれを使わないものになったとのことです。

その他の細かい知見として以下が述べられています。

  • mixup は cutmix より性能が良いが収束に時間がかかった
  • ネットワークを大きくすればするほど性能が良くなった (DenseNet121 < DenseNet169 < DenseNet201 < DenseNet161)
  • EfficientNets および ResNeXts は性能向上に寄与しなかった

2nd place

基本的な部分は 1st place の解法と被る部分も大きいですが、このチームは model に関して多く手を加えたという点で他と異なります。このチームは画像全体というより局所的な情報、つまりそこに写っている一つ一つの細胞とその直ぐ側にある細胞が重要な情報を保持しているという仮定に基づきコンペを戦っていたようです。具体的には vanilla ResNet に対して以下のような変更を加えたモデルを作成しました。

  • ResNet は 通常 4 つのブロックを保持するが、これを 2 つにしたモデルを幾つか作成
  • 1 * 1 conv を増やした
  • 浅いブロックの Average Pooling と深いブロックの Average Pooling を concat して使用
  • shortcut layer の 11 conv を 33 conv に置き換えた (これによって validation の accuracy がよりスムーズになったらしい)

また、問題を control 画像を加えた 1,139 クラス分類としてではなく、各細胞の同じ siRNA を別のクラスとして扱うように 1,139 * 4 クラス分類として問題を解いていたようです。

自分は最初から torchvisionefficientnet_pytorch の ImageNet で pretrained されたネットワークを使用していたためあまりネットワーク構造をいじるという発想に至りませんでしたが、以後参加するコンペではこれも試すようにしようと思いました。

3rd place

mean teacherを用いて End2End で半教師あり学習を行っています。mean teacher とは teacher model と student model の 2 つのモデルの出力に一貫性をもたせるように学習する手法であり、teacher model を step 毎の student model の各重みの Exponential Moving Average で作成する手法です。ラベルのあるデータについては分類のロスと出力の一貫性を保つロスを、ラベルのないデータについては一貫性に関するロスを用いて学習します。

4th place

LabelSmoothingCrossEntropy を使っていることに加え、チャネルに着目したアプローチを取っていることが特徴的な解法です。6 チャネルの内異なる 5 つを使った場合に予測する上で得意・不得意な siRNA が異なる (ex. siRNA = 1 は 1, 2, 3 番目のチャネルに影響を与えるが siRNA = 2 は 4, 5, 6 番目のチャネルに影響を与える等) という知見を起点とし、チャネルを絞って学習を行った後アンサンブルをするというアプローチをとっています。

5th place

AdaBNExempler Memory という 2 つの手法を使っているのが特徴的な解法です。

AdaBN は非常にシンプルな Domain Adaptation 手法で、各最終的に予測したい対象について BatchNorm に使う統計量をその対象が所属するドメインから算出するというものです。この解法においては幾つかのドメインから統計量の算出を行っており、ランダムに算出した場合が 40% 代の accuracy なのに対して細胞の種類をドメインとして使った場合が 50% 代、experiment を使うと 60% 代になり、plate まで絞ると 70% 代になったそうです。pytorch での AdaBN 実装も共有してくれています。

Exempler Memory に関しては筆者の理解がかなり浅いのですが、明示的に設定したメモリにミニバッチをまたいでドメイン特徴を格納・更新していき、これを使って普遍的な特徴を学習する手法のようです。

7th place

このチームは pytorch/XLA というツールを使い、pytorch から TPU を使っていたようです。使った感想として I think Pytorch/XLA is just not quite production ready と述べており、主な問題は数時間 TPU を使った後 hangs unexpectedly するというものらしいです。

学習戦略として、phalanx san の discussion topic でも述べられているように 4 種類の細胞すべての画像を使って学習を行ったのち、学習済みの weight を使って 4 種類の細胞毎にモデルを作るという方針を取っています。また、このチームの特徴として比較的小さいモデル (ResNet50 や DenseNet121) を使っている点が挙げられます。予測時にも工夫を行っており、通常モデルの最終層の出力に softmax 関数を通して予測値を出すところを、最終層の一つ前の層の出力を使って類似度ベースの予測を行うという方法を使っています。

このチームは LB probing (複数回 submit してその際の learder board 上でのスコアが上がるように解法を調整していくこと) によって public leader board 上で accuracy 1.000 を出すことでコンペを非常に盛り上げてくれました。probing の進め方も詳細に書いてくれているので、興味のある方は是非彼らの discussion topic を読んでみてください。

9th place

EDA から一つ一つの細胞の大きさが小さすぎるという仮説を立て、画像サイズを大きくするという独自のアプローチを取ってスコアを伸ばしています。また、ArcFace はうまく行かなかったが CosFace はうまく行ったとの記述があります。

また、今回のように各クラス数が均等な状況では以前 Quick, Draw! Doodle Recognition Challenge コンペで使用されたテクニックが有用なようです。幾つかのチーム同様、このチームもこのテクニックを利用しています。

16th place

他にも幾つかのチームが使っているとの報告をしていましたが、Contrast Limited Adaptive Histogram Equalization (CLAHE) という方法で画像を加工しています。CLAHE は fig 7 のようにヒストグラム平坦化を行う技術であり、画素値の偏りをキャンセルすることができるため、細胞コンペでは experiment 間の明るさの違いを除去することなどが期待できます。筆者はこの技術を知らなかったのですが、割と色々なケースで使えそうなので以降は是非使っていきたいです。

f:id:guchio3:20191007224813p:plain
fig 7: CLAHE の適用例 (CLAHE を適用したものの方が画素値の分布がまんべんなく広がっている)

細胞コンペに参加してみて

やったこと

筆者はこのコンペに終了 1 ヶ月前ほどから参加し、最終順位は 35 位で銀メダルを獲得することができました。ここにコードを置いていますが (関係ないコードも結構混ざっちゃってます) やったことは画像コンペの基礎パイプラインを作って (ここに一番時間がかかった) kernel & discussion 上に乗っている情報を適用したくらいですが、具体的なものを以下に羅列します。

  • 512 * 512 images
  • efficientnet b5 w/ easy_margin (途中までは b2 で実験してました)
  • adam w/ cosine scheduler (0.001 -> 0.00001)
  • hold out (validation set : HEPG2-07, HUVEC-15, HUVEC-16, RPE-07, U2OS-03)
  • 40 epoch (all cell type data) -> 40 epoch * 4 (each cell type data) -> 20 * 4 epoch w/ pseudo labels -> 10 * 4 epochs w/ 2nd pseudo labels and all train (no validation)
  • plate leak と hungarian algorithm の使用
  • tta (8 patterns)
    • rotate90
    • flip

f:id:guchio3:20191007213905p:plain
fig 8: 最終順位

躓き・気付き

画像コンペ初心者目線での躓きや知見としては以下がありました。

  • 画像のロード時間がかなり長いが SSD を使うと爆速になる
  • docker で pytorch を使う際、 --ipc=host もしくは --shm-size を大きく設定しないと sampler の num_workers が 0 以外設定できない
    • error 例 : RuntimeError: DataLoader worker (pid 17517) is killed by signal: Bus error.
  • GPU の種類による速度の向上はかかるお金ほど大きくない
  • GPU 使用率を 100% に保てられれば batch size を大きくしても計算速度はほぼ変わらない (考えてみれば当たり前だけど...。)
  • GCP 上の GPU が枯渇することはよくある (枯渇した場合は GPU の種類を変えるとインスタンスが立つことがある)
  • resize と transpose の使い分けでバグを踏むことがある (これで 3 週間くらい飛んでいった... T_T)
  • (albumentations を使う場合は) data augmentation が sampling 時に確率的に適用されるという形式で表現される
  • TTA の実装の仕方 (筆者は各 augmentation を for 文で回して p = 1. で適用した)
  • 画像サイズ 512 * 512 はでかい
  • 1 つの実験に数日かかるとか割とある
    • debug 用コードとか味見用の小さいネットワークをうまく使うのが非常に大事
  • 画像自体の可視化も重要だが、pixel_stats を使うと統計量として扱えるので便利
  • 画像コンペで工夫する箇所
    • validation の切り方
    • batch の作り方 (ここが工夫できるのがかなり衝撃的だった)
    • モデルの構造・種類
    • augmentation
    • parameter tune (特に learning rate がかなり大事)
    • 論文実装 (parameter 設定とか論文が参考になったりする)
    • etc ...
  • backward でグラフを作らなかったら使用メモリがかなり小さい
    • test とか valid のときのみ batch size を大きくするとかもできる

次コンペではやりたいこと

以下が今回やりたいと思ってたけど時間や知識の関係上できなかったことです。

  • 実験管理
    • 大事なことはわかっているけど参加時から手数を逆算して切った
    • ML Flow とか使ってみたい
  • Apex
    • いい感じにデータ量を削ってくれて時空間計算量を節約できるらしい
    • 存在を知らなかった...
  • pipeline の整理
    • 画像コンペではコンペ間のコードの再利用が非常に効きやすいので今回の知見をもとによりよい pipeline を作っていきたい
  • 論文読む
    • 幾つか参考に読みはしましたが、中身をちゃんと分からずにとりあえず使うという場面も多かったので都度知識を蓄積することを意識したい
  • gold medal :D

まとめ

本記事では kaggle の細胞コンペについての説明に加え、画像系コンペ初心者の筆者がこのコンペで得た知見を共有させて頂きました。細胞コンペは何種類かのリークはありましたが、それも含めコンペとしては非常に面白くかつ学びのあるものだったと思います。次は金圏に食い込みたい...

以上!

*1:例えばこれとか

*2:おそらく細胞核・小胞体・アクチン・核小体・ミトコンドリア・ゴルジ体のそれぞれが青・緑・赤・シアン・マゼンタ・黄をベースとした画像としてうまい具合に合成されているのだと思います。

*3:これとかこれとか

*4:これとか

*5:正確には後に説明する control 画像も含めてこのデータサイズです

*6:実験上の操作ミスにより、experiment に存在する siRNA 数が 1,108 種類に満たない場合もあります。一方、1 experiment に同じ siRNA が 2 度あらわれることはありません。

*7:これとか

*8:例えばこれとか

*9:これとかこれとか

*10:ここにある程度このリークの利用アプローチがまとまってます。

*11:これとかこれとか。今回特に多く使われた ArcFace については uchida san の記事が非常にわかりやすくて参考になりました。