返回列表

11th Place Solution

600. HMS - Harmful Brain Activity Classification | hms-harmful-brain-activity-classification

开始: 2024-01-09 结束: 2024-04-08 临床决策支持 数据算法赛
第11名方案 - 有害脑活动分类竞赛

第11名方案 - 有害脑活动分类竞赛

团队:kansai-kaggler(@tomohiroh@ryushisa@shunyaiida@ekichi@t88take

首先,衷心感谢 Kaggle 工作人员和比赛主办方组织了如此精彩的竞赛。在这次比赛中,我们探索了多种 EEG 数据处理方法,收获颇丰。此外,团队中有三位成员已成为 Kaggle 竞赛 Master,我们感到非常高兴。

概述

我们的团队在比赛期间与 P‑SHA 和 TR 团队合并。我们将每个团队开发的模型进行集成。单模型训练采用两阶段策略:第一阶段使用全部 EEG ID,第二阶段仅使用投票数 ≥ 8 的数据。上述单模型随后通过堆叠(Stacking)方法进行集成。

提交方案详情

P‑SHA 团队单模型

iida 部分

模型架构

对原始 EEG 数据应用多种频谱图变换(STFT、MFCC、LFCC、RMS)。在频谱图变换后,包括 Kaggle 频谱图在内的数据被送入 Timm Backbone 或 GRU 进行训练。对于原始 EEG 数据,还先通过 1D CNN 再连接 Timm Backbone 进行训练。

1D CNN 结构图
1D CNN

在 Nischay Dhankhar 提供的 1D CNN 模型基础上做了改进。为保持全部卷积的序列长度为 10000,使用了多种核大小(kernels=[1,3,5,7,9,11,13,15,17,19,21,23,25,27,29,35,63,127]),并设置 padding=(kernel_size-1)//2。各核的输出在通道维度上拼接,随后再经过 1D CNN,随后使用 Timm Backbone 将二维张量 (特征数, 时间) 作为输入进行训练。

训练损失的 计算

在训练损失中同时考虑了各个频谱图的学习效果。

# 训练损失
with amp.autocast(cfg.enable_amp):
    if cfg.mixup_p > rand:
         y,h1,h2,h3,h4,h5,h6 = model(xs)
         loss = mixup_loss(loss_func, y, y_a, y_b, lam)
         loss_h1 = mixup_loss(loss_func, h1, y_a, y_b, lam)
         loss_h2 = mixup_loss(loss_func, h2, y_a, y_b, lam)
         loss_h3 = mixup_loss(loss_func, h3, y_a, y_b, lam)
         loss_h4 = mixup_loss(loss_func, h4, y_a, y_b, lam)
         loss_h5 = mixup_loss(loss_func, h5, y_a, y_b, lam)
         loss_h6 = mixup_loss(loss_func, h6, y_a, y_b, lam)
    else:
         y,h1,h2,h3,h4,h5,h6 = model(x)
         loss = loss_func(y, t)
         loss_h1 = loss_func(h1, t)
         loss_h2 = loss_func(h2, t)
         loss_h3 = loss_func(h3, t)
         loss_h4 = loss_func(h4, t)
         loss_h5 = loss_func(h5, t)
         loss_h6 = loss_func(h6, t)
    loss = loss+(loss_h1+loss_h2+loss_h3+loss_h4+loss_h5+loss_h6)/6.
数据增强
  • mixup(p=0.5,α=1)
  • 水平翻转(HorizontalFlip)
  • 乘法噪声(MultiplicativeNoise)
  • 粗dropout(CoarseDropout)
  • XY遮蔽(XYMasking)
Timm Backbone
  • tf_efficientnetv2_s.in21k
  • tf_efficientnet_b3.in1k
其他技巧

与 eikichi 部分类似,两阶段训练非常有效。

eikichi 部分

模型

构建了 5 个模型并通过全连接层进行融合。

  • model1:Timm 模型
    • backbone:tf_efficientnetv2_s.in21k
    • 输入:Kaggle 频谱图
  • model2:Timm 模型
    • backbone:tf_efficientnetv2_s.in21k
    • 输入:从 EEG 生成的频谱图
  • model3:EEG1D 模型
    • 扩展核大小从 [3,7,5,9] 至 [3,7,5,9,15,17,19,21],以强调长期特征
    • 输入:大脑前部 EEG(Fp1‑F7、F7‑T3、Fp1‑F3、F3‑C3、Fp2‑F8、F8‑T4、Fp2‑F4、F4‑C4)
  • model4:EEG1D 模型
    • 扩展核大小同上
    • 输入:大脑后部 EEG(T3‑T5、T5‑O1、C3‑P3、P3‑O1、T4‑T6、T6‑O2、C4‑P4、P4‑O2)
  • model5:EEGMegaNet 模型
数据增强
  • mixup(p=0.5,α=0.4)
  • 水平翻转(p=0.5)
技巧
  • 两阶段训练
    • 第一阶段:训练全部数据,验证仅使用全部数据中抽取的中间段
    • 第二阶段:训练投票数≥8,验证仅使用投票数≥8且≤20的中间段
  • 未使用 butter_lowpass_filter,因为不使用时 CV 更好。

TR 团队单模型

特征提取(波形 → 图像)

采用 CWT 或 MelSpectrogram 将波形转换为图像。

# CWT 参数
CWT(fmin=0.5, fmax=25, hop_length=200*50//self.size)

# MelSpec
nn.Sequential(
    T.MelSpectrogram(
        sample_rate=200,
        n_fft=1024,
        win_length=128,
        hop_length=(200*50)//self.size,
        f_min=0,
        f_max=20,
        n_mels=128,
    ),
    T.AmplitudeToDB(top_db=80),
)
Timm Backbone

maxvit_tiny(使用 CWT 或 MelSpec)、swinv2_base(CWT)、convnextv2_base(CWT)。其中 maxvit 效果最佳。

数据增强

TimeMaking、左右翻转(如下所示):

# 左右翻转
if self.training:
    if random.random() < 0.50:
        _x = x.clone()
        # LL <=> RL
        x[:, 0:4, :, :] = _x[:, 4:8, :, :]
        x[:, 4:8, :, :] = _x[:, 0:4, :, :]
        # LP <=> RP
        x[:, 8:12, :, :] = _x[:, 12:16, :, :]
        x[:, 12:16, :, :] = _x[:, 8:12, :, :]
其他技巧

模型训练 20 个 epoch(前 10 个 epoch 使用全部投票数据,后 10 个 epoch 切换为投票≥8 的数据)。我们没有使用 Kaggle 频谱图,仅使用 EEG 数据效果更好。

堆叠模型(T88 部分)

使用单模型 OOF 数据训练 2D‑CNN 堆叠模型,仅使用投票总数≥8 的数据进行训练和评估。堆叠模型取得了非常好的最终分数(公开: 0.23 / 私有: 0.28),而相同单模型配置的简单平均为(公开: 0.24 / 私有: 0.29),显示了堆叠的优势。

输入的单模型

共使用 9 个单模型:

模型 公开分数 私有分数
iida0620.270.33
iida1390.250.31
iida1640.250.31
iida1690.250.32
eikichi0380.280.34
tomo0890.260.31
tomo0920.270.31
tomo0940.280.34
tomo0950.290.34

模型结构

我们最终采用了 2D‑CNN 堆叠模型,是在尝试多种基于神经网络的堆叠模型后表现最好的。

输入数据格式:

(Channel, Height, Width) = (1, Class, Model) = (1, 6, 9)

class HMSStacking2DCNN(nn.Module):
    def __init__(self, num_classes):
        super().__init__()
        self.conv2d1 = nn.Conv2d(1, 8, kernel_size=(1,3), padding=0, stride=1)
        self.conv2d2 = nn.Conv2d(8, 16, kernel_size=(1,3), padding=0, stride=1)
        self.fc1 = nn.Linear(480, 480)
        self.dropout1 = nn.Dropout(p=0.5)
        self.fc2 = nn.Linear(480, num_classes)

    def forward(self, x):
        x = F.relu(self.conv2d1(x))
        x = F.relu(self.conv2d2(x))
        x = torch.flatten(x, start_dim=1)
        x = F.relu(self.fc1(x))
        x = self.dropout1(x)
        x = self.fc2(x)
        return x

模型优化

使用 Optuna 对单模型组合及 dropout 等超参数进行优化。为降低过拟合,使用 3 个不同种子的 CV 均值作为优化指标。

训练配置

  • 学习率:5e‑4
  • epoch:20
  • batch_size:16
  • 最终提交模型使用全部数据训练,不再划分折
  • 采用 5 次随机种子平均(5 SeedAvg)

其他

  • 私有数据集的分布是大家关注的焦点。为此,我们基于预测分布的相似性进行探测,发现整个测试数据与训练数据中投票≥8 的分布相似。基于此,我们确定了交叉验证策略和提交选择。
同比赛其他方案