はじめに
こんにちは。
Kaggle で2024/06/27~2024/09/07の間に開催された ISIC 2024 - Skin Cancer Detection with 3D-TBP というコンペティションに 羽鳥 、 阿内 、 福山 、 長妻 のチームで参加し、2,740チーム中5位で金メダル、賞金1万ドルを獲得しました。
予想通りシェイクが激しい(Public LeaderboardとPrivate Leaderboardでの順位変動が大きい)コンペだったため、大きくシェイクアップ(+1,100位)して金メダルを獲得しました。
この記事では本コンペのタスクと解法について解説します。
Kaggle上に投稿した解法の Discussion も合わせてご覧ください。
コンペティション詳細
タスク
本コンペでは、各地の医療施設で撮影された患者の皮膚の病変データに対して「皮膚がん」かどうかを判別する精度を競いました。
データ
病変部分の画像データと、病変部分の形状や大きさや色彩情報などが記載されたメタデータが利用可能でした。
目的変数
目的変数は病変が悪性か良性かのバイナリでした。 正例(=悪性のサンプル)が約400件に対して、負例(=良性のサンプル)が約40万件と、正例が少ないかつアンバランスな目的変数なのが本コンペの特徴でした。また、Public Leaderboardは全テストデータのうち28%の暫定評価であり、シェイクが激しいことが予想されていました。
画像データ
3D全身写真(TBP:Total Body Photos)から単一病変を取り出して作成された個別の病変画像が与えられました。専門家による診察や、高度な医療機器による病理検査にアクセスしづらいケースを想定して、スマホ等によって撮影された場合に近い品質の画像から診断を行いたいというモチベーションでした。
メタデータ
病変の診断に利用可能なメタデータも与えられました。
- isic_id: 病変ごとに割り振られたID
- patient_id: 患者ごとに割り振られたID
- age_approx: 画像撮影時のおおよその年齢
- sex: 患者の性別
- clin_size_long_diam_mm: 病変の最大直径(mm)
- mel_thick_mm: 病変の深さ
- attribution: データソースの医療施設
attributionについて
病院は複数の大陸に別れて分布しており、 こちらのdiscussion では学習データには存在しないattributionが存在することが示唆されており、より汎化性能を意識した改善が求められました。
評価指標
評価指標はpAUCでした。pAUCはpartial Area Under the Curveの略で、今回のコンペではTPR80%以上の領域におけるpAUCが用いられました。 この評価指標の選定理由は、がんの診断支援システムではTPR、すなわち実際に陽性であるサンプルのうち正しく陽性と予測できたサンプルの割合が高いことが要求されるためです。ソリューションの紹介
概要
以下がソリューションの全体像です。
キーとなった要素としては
- 複数の画像、テーブルのモデルによる多様性の確保
- attributionが未知/既知の場合で適用するモデルを分岐させる
- Trust CV
だったと考えています。
ソリューション詳細
画像モデル1(過去コンペのデータを使うモデル)
ここでは過去コンペのデータを使ったモデルについて説明します。モデルの学習方法の詳細などはDiscussionをご覧ください。ここではDiscussionの中ではあまり触れられなかった補助ロスと推論時の工夫について詳細を説明します。
今回のコンペで採用したロスの概要は以下のような形です。
loss = target loss + w_1 * has_lesion_id loss + w_2 * is_past_data loss
元々の目的変数であるtargetカラムのロスに加えて、ある画像がlesion_idを持っているかどうかを表すhas_lesion_id、ある画像が過去コンペの画像であるかを表すis_past_dataのロスを重みをつけて加えています。
補足:過去コンペについて
2020年にもKaggle上でISICが コンペティション(SIIM-ISIC Melanoma Classification) を開催しています。また、2018年と2019年についてのデータセットも公開されています。
画像の解像度やメタデータの形式は今回のコンペとは異なりますが、外部データの利用が大きなアドバンテージになるケースはKaggleでは多く、コンペ開始直後から利用を検討していました。
詳細についてはこちらの discussion をご覧ください。
lesion_idの有無について
今回のコンペにおけるlesion_idとtargetの関係を説明します。各画像を医師が診断する際に、悪性の可能性が一定あるものにはlesion_idが付与されます。lesion_idが付与された画像は生検などによって詳細な診断がなされ、そのうちの一部の画像についてtargetが1となります。( discussion )
lesion_idはその特性上、targetよりも件数が多く、今回のコンペではlesion_idを持っている画像は約2万件、target=1となる画像は約400件、特にtarget=1となる画像は必ずlesion_idを持っているという構造になっていました。今回はコンペの性質上正例がかなり少ないため、lesion_idが付与された画像であるかどうかという情報はモデルにとって重要であると考え、このラベルの情報も取り入れて学習しました。 どのくらいのweightで混ぜるかは0.0から0.2くらいの範囲でざっくりと探索し、最終的に0.1を採用しました。
過去データかどうか
今回のコンペのデータと過去のコンペのデータは取得方法や取得先の病院が異なるために、画像の見た目の印象が今回のコンペのものとはだいぶ異なっておりました。
上記画像は dataset より、ISIC_0000000.jpg、ISIC_0000001.jpg、ISIC_0000002.jpgを引用
そのため、ただ単に過去データを混ぜて学習するだけだとvalidation scoreが下がってしまうという状況でした。そこで、各サンプルについて、過去データかどうかをラベルとして明示的に与えることで今回のコンペの画像に似ている過去のコンペの画像をモデルが自動的に抽出し、役に立つ情報だけを学習することができるのではないかと考え、これを補助ロスとして採用してみました。こちらもweightをざっくり探索し、最終的には1e-4を採用しています。
上記の補助ロスを両方共に加えた際の結果が以下になります。かなり小さいモデルであるresnet18は補助ロス無しではfoldごとのpAUC 0.8のスコアが0.125とあまりよくありませんでしたが、2種類の補助ロスを入れることでcv scoreが大きく改善しました。
モデル | cv mean |
---|---|
補助ロス無し resnet18 | 0.125 |
補助ロスあり resnet18 | 0.151 |
推論時の工夫について
今回のコンペでは推論時間が厳しかったために、5fold分のモデルを全部利用するのは厳しい状態でした。そこで単純な考えですが、5つのモデルからランダムに2つを用いる戦略を取りました。
ただ、事前に使用するモデルを選んでtest set全体にそれを適用するような形式だと、選んだモデルのバイアスを強く受けてしまいます。 そこで、test dataloaderの各バッチごとに使うモデルを抽選するような実装を行いました。これにより全体で使うモデルが固定化されることを防げました。更に複数のbackboneで推論を行うときにはランダムシードを変えることで、同じバッチに対してもbackboneごとに選ばれるモデルが変わるように実装を行いました。
このような実装を行うことで、推論時間を抑え、後述するアンサンブルに推論時間を使うことができました。 ちなみに、LBで使うモデルの数を変えて実験を行ったところモデル1つの場合0.157、モデル2つの場合0.161、モデル3つの場合0.161となったため、最終提出の際には2つのモデルを使うことにしました。
画像モデル2(過去コンペのデータを使わないモデル)
過去コンペのデータを使わずに、今回のコンペで与えられた画像のみを使って学習したモデルを解説します。 こちらのモデルでは、前述したモデルとの多様性を考慮し、いくつかの工夫を凝らしています。
負例のダウンサンプリング
今回はコンペの性質上、正例が負例に対してかなり少なかったためそのまま学習することは難しいと考え、負例をダウンサンプリングして正例と負例の比率が1対1になるように調整して学習に用いました。また、過去データを使ったモデルでは画像の全量を使用したので、その差分によって多様性を生むという狙いもありました。ダウンサンプリングは他の上位解法でも有効性が語られており、今回のコンペでは有効だったようです。
実装に関しては、全epoch通してあらかじめ負例を事前に決定してしまうとモデルの汎化性能に悪影響が及ぶと考えました。そこで各epoch開始時に負例を再サンプリングし、epochごとにモデルに与える負例が変わるような実装を行いました。これにより、限られたサンプルサイズであっても適切な学習を行うことができたと考えています。
early stoppingについて
今回のコンペは前述の通り正例の比率もさることながら絶対的な数が限られているという状況でした。このような状況下でvalidation dataを見ながらearly stoppingを行うのはcvに過学習してしまうリスクがあると考えました。そこでコンペ後半からはearly_stoppingをあえてせずに、30epochを回し切るようにしています。これによってモデルがvalidationに対して過剰に適合することを防ぐことができました。 他の上位入賞者解法( 8th place solution )でもearly stoppingを意図的に導入しない手法が触れられており、今回のコンペでは有効だったと考えられます。
推論時の工夫について
今回のコンペでは推論時間の制約があったため、5fold分のモデルをすべて使うのは厳しい状態でした。そのため、全データを使って学習した1モデルのみで推論を行いました。今回はearly stoppingを取り入れていなかったためにスムーズにこの手法を採用することができました。
検証のために、5foldで学習したモデルのうちのいくつかを使って推論した場合と、全データで学習した1モデルで推論した場合でLBのスコアを計測し、悪影響がなさそうなことを確認しました。 この工夫により推論時間が1/5程度になり、後述する木のモデルのアンサンブルに十分時間を使うことができました。
テーブル1(過去コンペのデータを使わないモデル)
特徴量エンジニアリング
複数のグルーピング単位での集約特徴量を組み合わせました。患者ごと、病変が位置する体の部位ごと、病院ごとなどの複数の単位でレコードをグルーピングして平均や標準偏差などの統計量を算出して特徴量としました。また、それぞれのグルーピング単位で病変特徴量を正規化した値も特徴量として利用しました。
モデリング
モデルはLightGBMを使用しlearning_rate=0.05、 max_depth=4のみパラメータを指定し、それ以外はデフォルトの値を使用しました。Optuna等を使用して調整することも考えましたが、CVへのOverfittingの可能性をケアしてこのような設定で利用しました。
テーブル2(過去コンペのデータを特徴量生成だけに使うモデル)
特徴量エンジニアリング
今回のコンペでは約50万レコードが存在したため、データの処理はメモリ効率を考慮し、pandasではなくpolarsを使用しました。以下に、具体的な特徴量の作成方法を示します。
集計特徴量
patient_id、tbp_lv_location、attribution、tile_typeの様々なサブセットで数値特徴量を集計しました。具体的には、(patient_id), (patient_id, tbp_lv_location), (attribution), (attribution, tbp_lv_location), (tbp_lv_location, tile_type)の単位で集約し、それぞれの数値特徴量について平均、最大、最小、標準偏差、和を計算しました。また、それぞれの集約単位の平均値から対象の病変がどの程度かけ離れているか、を表現する特徴量も作成しました。
シフト系特徴量
patient_id、tbp_lb_location、age_approxで数値特徴量をグループ化し、各レコードに対して数値特徴量をage_approx単位でシフトしました。これは、例えば5年前の病変部位の数値がどうだったかを表すものです。シフトした値と元の数値特徴量の値の差分も特徴量として取り入れました。
過去のメタデータ
今回と前回のコンペティションの両方に参加した患者の情報を追加し、データの情報量を増やしました。
ソフトラベル
indeterminate sample(不確定な病変)を正例として扱い、さらにsample_weight=0.5とすることで補助的な目的変数を作成し、学習がスムーズに進むようにしました。
学習時と推論時のデータ分布を揃える
attribution(病院)の文字列を含む病院系特徴量を含むものと含まないもので2パターンの特徴量を作成し、推論時に病院が既知だった場合はattributionを含むモデルで推論し、未知だった場合はattributionを含まないモデルで推論しました。
スタッキング
リークに注意しながら画像のOOFを特徴量に組み込みました。画像のOOFに関しては、5種類のOOFを一つのモデルに加えるだけでなく、1種類のOOFを用いた5個のモデル、3種類のOOFを用いたn個のモデルを作成するなど、多様性を持たせました。また複数種類のOOFを用いる場合はconvnext系、swin系、resnet系の3種類のモデルを含むようにしました。
テーブル3(過去コンペのデータを使うモデル)
学習母集団の工夫
本コンペは正例が非常に少ないため、過去データを利用することで精度向上を狙いました。
スタッキング
過去データを用いた画像モデルのOOFを利用しました。 テーブル1、テーブル2と使う画像モデルが完全に一緒ではない点もモデルの多様性向上に寄与したと考えています。
モデリング
モデルはLightGBMとXGBoostのWeighted Averageで、attribution特徴量を含む場合と含まない場合のモデルを二つ作成し、推論時に分岐させました。
アンサンブル
画像とテーブル両方のモデルを含めてoptunaによってweightを最適化したアンサンブルが最終的にPrivateで最も良いパフォーマンスを示しました。CV overfitをケアしてweightを手動で決めたパターンもsubmitしていましたがスコアは劣っていました。
submitするコードはkaggleで与えられた環境上で12時間以内に実行が完了しなければいけないという制約があるためメンバー間でお互いのモデルの推論時間を共有しながら時間内に全てのテストデータに推論ができるよう相談をしながら進めました。
スコアまとめ
cv | Public | Private | |
---|---|---|---|
画像モデル1(過去データあり) | 0.1670 | 0.16221 | 0.15214 |
画像モデル2 (過去データなし) | 0.1592 | 未計測 | 未計測 |
テーブル1 | 0.172 | 0.177 | 0.15474 |
テーブル2 | 0.1832 | 0.17759 | 0.17205 |
テーブル3 | 0.1820 | 0.17590 | 0.16665 |
アンサンブル | 0.1839 | 0.18188 | 0.17210 |
その他
他の上位解法との比較
上位解法は、画像モデルによる病変の診断とGBDTによるメタデータによる診断の両方をうまく組み合わせて最終的な予測結果を出力している点が共通していました。
正例数が極端に少なく、CVやLBにoverfitしやすい状況の中でearly stopping等の過剰適合リスクが大きい手法を使わずに特徴量エンジニアリングやモデルの多様性による精度の改善に取り組んだチームが最終的に高い順位を取っているようでした。
Validation戦略の工夫では、 7th Place Solution が非常に参考になりました。このチームはvalidationのセットに必ず2個未知のattributionが出現する状況を作り信頼できるCV戦略をとっていました。
また、 1st Place Solution も参考になりました。 このチームは、過去の画像データにmelanoma(正例), nevus(負例), bkl(中間クラス)の三種類のラベルを付与し、クラス分類モデルを学習していました。その予測値を特徴量に追加することで、正例と負例の区別が難しい境界上の病変を見分け、精度を改善しました。特に、過去データの診断結果に独自でbklラベルを付与し、中間クラスとして扱う点がポイントでした。
おわりに
今回は普段同じ仕事をしているグループのみんなでチームを組み、最終的に金メダル&賞金を取ることができ大変嬉しく思います。業務と同じようにタスクに対して仮説構築→実装のプロセスをチーム全体で繰り返すことがKaggleでも有効であることが実証できて大変学びになりました。
一緒に働きませんか?
ここまで読んでいただいてありがとうございます。 弊社では、様々な職種のエンジニアを募集しています。興味のある方は、以下の採用ページをご覧ください。
人材領域でデータ分析を担当。シニアデータサイエンティスト
羽鳥 冬星
スマブラが好き
人材領域でデータ分析を担当。マネージャー
阿内 宏武
ギターとオセロが好き
人材領域でデータ分析を担当
福山 悠豪
辛いものに目がない
人材領域でデータ分析を担当
長妻 雄飛
めざせKaggle Master