600. HMS - Harmful Brain Activity Classification | hms-harmful-brain-activity-classification
团队:kansai-kaggler(@tomohiroh、@ryushisa、@shunyaiida、@ekichi、@t88take)
首先,衷心感谢 Kaggle 工作人员和比赛主办方组织了如此精彩的竞赛。在这次比赛中,我们探索了多种 EEG 数据处理方法,收获颇丰。此外,团队中有三位成员已成为 Kaggle 竞赛 Master,我们感到非常高兴。
我们的团队在比赛期间与 P‑SHA 和 TR 团队合并。我们将每个团队开发的模型进行集成。单模型训练采用两阶段策略:第一阶段使用全部 EEG ID,第二阶段仅使用投票数 ≥ 8 的数据。上述单模型随后通过堆叠(Stacking)方法进行集成。
对原始 EEG 数据应用多种频谱图变换(STFT、MFCC、LFCC、RMS)。在频谱图变换后,包括 Kaggle 频谱图在内的数据被送入 Timm Backbone 或 GRU 进行训练。对于原始 EEG 数据,还先通过 1D CNN 再连接 Timm Backbone 进行训练。
在 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.
与 eikichi 部分类似,两阶段训练非常有效。
构建了 5 个模型并通过全连接层进行融合。
采用 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),
)
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 数据效果更好。
使用单模型 OOF 数据训练 2D‑CNN 堆叠模型,仅使用投票总数≥8 的数据进行训练和评估。堆叠模型取得了非常好的最终分数(公开: 0.23 / 私有: 0.28),而相同单模型配置的简单平均为(公开: 0.24 / 私有: 0.29),显示了堆叠的优势。
共使用 9 个单模型:
| 模型 | 公开分数 | 私有分数 |
|---|---|---|
| iida062 | 0.27 | 0.33 |
| iida139 | 0.25 | 0.31 |
| iida164 | 0.25 | 0.31 |
| iida169 | 0.25 | 0.32 |
| eikichi038 | 0.28 | 0.34 |
| tomo089 | 0.26 | 0.31 |
| tomo092 | 0.27 | 0.31 |
| tomo094 | 0.28 | 0.34 |
| tomo095 | 0.29 | 0.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 均值作为优化指标。