🐶

【Kaggle】ISIC2024 240位🥉 振り返り

2024/09/08に公開

今回は、KaggleのISIC2024コンペに参加し、240位で銅メダルを取得することができたので、解法の振り返りを書いていこうと思います。

・Solution
https://www.kaggle.com/competitions/isic-2024-challenge/discussion/532640

・コード全体
https://github.com/yuto-m12/Kaggle_ISIC2024

0. コンペ概要

今回のコンペは、提供された皮膚の画像データから皮膚がんの患者である確率を予測する回帰問題のタスクでした。

・提供データ
画像: 皮膚の画像。患者がスマホで撮影した写真から判別できるようにするために、低解像度である。
テーブル: 病変と思われる部位の色や直径などの細かい情報、及び患者の年齢などの個人データ(test時にも提供される)

・提出ファイルのフォーマット

// target列が皮膚がんである確率
patient_id, target
ISIC_0015657, 0.3
ISIC_0015729, 0.3
ISIC_0015740, 0.3

・評価指標
評価指標はpartial area under the ROC curve (pAUC)で、Ture positive rate が80%以上であるAUCの面積の最大化が目的になります。(最大値2.0)

Kaggle ISIC 2024 - Skin Cancer Detection with 3D-TBP

・コンペの流れ
初めは画像モデルが中心でしたが、画像の解像度の低さと、提供された病変データの多さの影響で、中盤にはLGBMなどのテーブルモデルがPublic Notebookで上位でした。
そしてコンペの後半では、画像モデルが出力した値をテーブルモデルの入力特徴量として使用する、いわゆるスタッキングの手法が非常に高いスコアを出していました。

1. 解法の概要

概要
・テーブルモデルと画像モデルのアンサンブル(1:1の割合)
・テーブルモデルはパブリックのノートブックを使用: ISIC 2024 | Skin Cancer Prediction
・画像モデルはtimmのVITを使用
・正例のデータが非常に少なかった(0.098%)ため、過学習を抑えることを意識

イメージ:

df_subm['target'] = ((df_table['target'].to_numpy() * 0.5) + \
                     (df_vit_sub["target"].to_numpy() * 0.5))

2. 画像モデル

ここからは、画像モデルについて話していきます。

2.1 設定

model: "maxvit_rmlp_pico_rw_256.sw_in1k"
batch_size: 128
max_epoch: 9
n_folds: 5
optimizer: optim.AdamW
scheduler: OneCycleLR
lr: 1.0e-04
weight_decay: 1.0e-02
img_size: 256
interpolation: cv2.INTER_LINEAR
CV: StratifiedGroupKFold with "patient_id"

バリデーションには患者のidを考慮してfoldを分割できるようにStratifiedGroupKFoldを使用しました。

2.2 モデル

self.model = timm.create_model(
                model_name=model_name, 
                pretrained=pretrained, 
                in_chans=in_channels,
                num_classes=num_classes,
                global_pool=''
            )
        dim = CFG.output_dim_models[CFG.model_name]
        self.dropout = nn.ModuleList([
            nn.Dropout(0.5) for i in range(5)
        ])
        self.target=DynamicLinear(out_size=1)

過学習防止のため、Dropoutを多めに入れています。

2.3 上手く行ったこと

2.3.1 2-stage learning

2-stage learningを利用しました。
正例が少ない問題に対処するため、ISICの過去に蓄積された全てのデータを利用して1度目の学習を行い、そのモデルを事前学習モデルとして、2024年度のデータのみで学習を回しました。

2.3.2 Augmentations

2020年のコンペ解法と、自分の実験の結果から効果がありそうなデータ拡張を選択していました。
過学習を防ぐためにデータ拡張は重要であると思っていたので、多くのパターンで検証を行いました。

    augmentations_train = A.Compose([
    A.Transpose(p=0.5),
    A.VerticalFlip(p=0.5),
    A.HorizontalFlip(p=0.5),
    A.ColorJitter(brightness=0.2, contrast=0.2, p=0.3),
    A.OneOf([
            A.MotionBlur(blur_limit=5, p=0.5),
            A.MedianBlur(blur_limit=5, p=0.5),
            A.GaussianBlur(blur_limit=5, p=0.5),
        ], p=0.2),
    A.GaussNoise(var_limit=(5.0, 30.0), p=0.1),
    A.ShiftScaleRotate(shift_limit=0.1, scale_limit=0.1, rotate_limit=15, border_mode=0, p=0.3),
    A.CoarseDropout(max_holes=20, min_holes=10, p=0.3),
    A.Resize(CFG.img_size, CFG.img_size),
    ToTensorV2(p=1)
    ])

2.3.3 標準化

A.Normalizeによる正規化の代わりに、標準化を行いました。これはCVスコアを大きく向上させてくれました。

# A.Normalize(
        #                 mean=[0.485, 0.456, 0.406], 
        #                 std=[0.229, 0.224, 0.225], 
        #                 max_pixel_value=255.0, 
        #                 p=1.0
        #             ),

x, t = batch
if CFG.standardization:
    x = (x - x.min()) / (x.max() - x.min() +1e-6) * 255

これは、標準化が異なる位置の似た分布(平均値1の正規分布と平均値10の正規分布のような)を捉える力に優れていることと、入力の分布を統一することに適していたからだと考えています。

2.3.4 HSV入力

RGBに加えてHSVの6次元の入力を利用しました。
RGBの色のみに依存するよりも、彩度や明度などを考慮できた方が、過学習してしまう可能性が低いと考えて利用しました。

def F_rgb2hsv(rgb: torch.Tensor) -> torch.Tensor:
    cmax, cmax_idx = torch.max(rgb, dim=1, keepdim=True)
    cmin = torch.min(rgb, dim=1, keepdim=True)[0]
    delta = cmax - cmin
    hsv_h = torch.empty_like(rgb[:, 0:1, :, :])
    cmax_idx[delta == 0] = 3
    hsv_h[cmax_idx == 0] = (((rgb[:, 1:2] - rgb[:, 2:3]) / delta) % 6)[cmax_idx == 0]
    hsv_h[cmax_idx == 1] = (((rgb[:, 2:3] - rgb[:, 0:1]) / delta) + 2)[cmax_idx == 1]
    hsv_h[cmax_idx == 2] = (((rgb[:, 0:1] - rgb[:, 1:2]) / delta) + 4)[cmax_idx == 2]
    hsv_h[cmax_idx == 3] = 0.
    hsv_h /= 6.
    hsv_s = torch.where(cmax == 0, torch.tensor(0.).type_as(rgb), delta / cmax)
    hsv_v = cmax
    return torch.cat([hsv_h, hsv_s, hsv_v], dim=1)

2.3.5 学習途中でのデータセットの変更

学習のepochが60%を超えたところで、データ拡張のないデータで訓練するようにしていました。
途中でデータセットを高精度なものに変えるのは、より細かい2-stage learningのように機能すると考えていましたが、データ拡張がないとモデルの一般性が低下する可能性があるため、これは使用しなくてもよかったかもしれません。

if CFG.change_dataset & epoch >= ((CFG.max_epoch+1) * 0.6):
    train_loader = train_loader_noaugment

2.3.6 TTA(test time augmentation)

TTAは推論時の入力にデータ拡張を適用する方法で、推論の精度を高めるための手法です。
私はデータ拡張なしで推論した出力と、データ拡張ありで推論した出力を7:3の割合で統合するシンプルな手法を利用しました。
CVやLBに大きな変化はありませんでしたが、PBでは上手く機能しているようでした。

TTA_rate = {'None':0.7, 'with_train_aug':0.3}

# get_dataloader
val_transform_TTA, val_transform = get_transforms()
val_dataset = ISICDataset(df=train_meta[train_meta["fold"] == fold_id], fp_hdf=CFG.TRAIN_HDF5_COMBINED, transform=val_transform)
val_loader = torch.utils.data.DataLoader(val_dataset, batch_size=CFG.batch_size, num_workers=4, shuffle=False, drop_last=False)
if CFG.TTA:
    val_dataset_TTA = ISICDataset(df=train_meta[train_meta["fold"] == fold_id], fp_hdf=CFG.TRAIN_HDF5_COMBINED, transform=val_transform_TTA)
    val_loader_TTA = torch.utils.data.DataLoader(val_dataset_TTA, batch_size=CFG.batch_size, num_workers=4, shuffle=False, drop_last=False)

# prediction
oof_pred_arr_merged = (oof_pred_arr * CFG.TTA_rate['None']) + (oof_pred_arr_TTA * CFG.TTA_rate['with_train_aug'])

2.3.7 異常データの削除

このdiscussionで指摘されていた、布地のデータなど、明らかに関係のないデータを削除しました。

ids_to_drop = ['ISIC_0573025', 'ISIC_1443812', 'ISIC_5374420', 'ISIC_2611119', 'ISIC_2691718', 'ISIC_9689783', 'ISIC_9520696', 'ISIC_8651165', 'ISIC_9385142', 'ISIC_9680590', 'ISIC_2346081']

以上が主に行った手法になります。

3. 何が機能しなかったか

・オーバーサンプリング
・補助ロス
どちらも有効な手法だと思われるので、実装を間違えていた可能性もあります。

4. 環境

コンペ全体を通して、RTX4070 ti superを利用していました。
コンペ終盤では、runpod.ioやvast.aiといったクラウドGPUのサービスも利用していました。
あまり慣れておらず環境構築に手間取りましたが、どちらも使いやすいサービスでした。
特徴としては、vast.aiの方が価格が安いですが、runpodの方がスケールしやすく、UIなどが親切で利用しやすかったです。

5. まとめ

本解法ではテーブルモデルとVITによる画像モデルのアンサンブルを使用しました。
またコンペを通して、モデルが過学習しないように気をつけていました。

振り返りはここまでになります。読んでいただきありがとうございました!

参考

[1] ISIC 2024 - Skin Cancer Detection with 3D-TBP, Kaggle

Discussion