返回列表

[EN/日本語] 19th solution writeup

665. CMI - Detect Behavior with Sensor Data | cmi-detect-behavior-with-sensor-data

开始: 2025-05-30 结束: 2025-09-02 健康管理与公共卫生 数据算法赛
第 19 名解决方案 Writeup - CMI Detect Behavior
竞赛: CMI Detect Behavior with Sensor Data
排名: 第 19 名
作者: @chimaki821 (观察与预处理), @tokkiwa (建模与深度学习)
协作者: @epopaca, @mckmckmck

第 19 名解决方案 Writeup

我们要向竞赛主办方表示最诚挚的感谢!在整个竞赛期间我们非常享受这个过程。
本解题报告分为两部分:由 @chimaki821 负责的观察、预处理和后处理,以及由 @tokkiwa 负责的建模和深度学习。

1. @chimaki821 部分 (观察 + 预处理)

1.1. 概要

像大多数参与者一样,我们实施了以下技术:

  • 修正左手数据:反转 acc_yrot_xrot_y 值,交换 THM 通道 3↔5,交换 ToF 通道 3↔5,并反转 8×8 正方形区域。
  • 实施多任务学习:添加 orientation(方向)和 behavior(行为)作为辅助损失项。
  • 在仅 IMU 和全传感器模型配置之间切换。
  • 对疑似安装异常的被试者应用修正(部分成功,详见下文)。
  • 使用加权集成方法(虽未包含在最终提交中,但在私有测试中证明有效)。

在实施这些基线方法后,我的队友 @tokkiwa 通过对管道和各种集成方法的多种增强,显著改进了模型。这非常有价值,因为我几乎没有机器学习背景。

下面,我描述了几项有助于上述方法的关键技术,以及我实施的其他方法:

  • 数据观察和安装修正
  • 使用绕 Y 轴旋转的数据增强 (DA) 和测试时增强 (TTA)
  • 创建软标签

我还总结了:

  • 竞赛期间尝试过但未成功或未实施的内容
  • 我想探索但没时间实施的其他方法

1.2. 数据观察和安装修正

这部分是与 @epopaca 合作开发的。

我们创建了一个 Notebook,当提供特定的被试者 (subject) 和手势 (gesture) 时,它会将所有相关数据合并到一张图像中。81 名被试者 × 18 种手势 = 1,458 张图像保存在我的智能手机上,一有时间我就不断观察它们。

例如 (SUBJ_000206, 手势:耳上 - 拉头发)

数据可视化示例

利用这种可视化,我们对左手修正和安装调整进行了分析。通过检查 acc_x, acc_y, acc_z 值,我们确定 SUBJ_019262SUBJ_045235 可能存在安装不当。考虑到 Herios 传感器附件的潜在问题,主要有四种可能的场景(包括正常情况):
(关于传感器 xyz 轴方向的参考,请参阅此 讨论帖)。

正常 (原始) 绕 Y 轴旋转 180°
正常 Y 轴 180 度
绕 Z 轴旋转 180° 绕 Y 轴 180° + 绕 Z 轴 180°
Z 轴 180 度 Y 轴 +Z 轴 180 度

考虑到测试数据也可能包含类似的安装错误,我们实施了一个二分类系统来检测安装异常并在推理时应用修正。然而,由于公共测试中相关案例数量有限,效果难以察觉。因此,在这次提交中,我们优先考虑减少误报,将阈值设置为 0.9,导致效果有限。在迟交提交 (Late Submission) 中将阈值降低到 0.5 时,我们的分数提高到了 0.862 😢

1.3. 绕 Y 轴旋转的 DA + TTA

基于传感器方向固定的假设,我们解决了“即使安装正确,绕 Y 轴也会发生轻微旋转”的情况。因此,我们实施了:

  • 训练期间:使用 -15° 到 +15° 之间的均匀分布进行绕 Y 轴随机旋转的 DA。
  • 推理期间:尝试多个绕 Y 轴的小旋转角度并平均结果的 TTA。

通过将推理角度设置为 [0, +5, -5, +15, -15],我们在多次实验中观察到公共 LB 平均提高了约 +0.002。私有 LB 也显示出类似的性能提升。

对于此实现,我们 carefully 应用了“旋转操作应从左侧作用于加速度数据,从右侧作用于旋转数据”的原则。

1.4. 创建软标签

这部分是与 @mckmckmck 合作开发的。

虽然之前的优化侧重于“防止遗漏困难的被试者”,但当我们按手势类型检查 OOF 分数时,我们发现某些手势特别难以预测。以下是仅 IMU 模型的示例:

-----------------------------------------------------
F1 score for class[0] = 0.666 tp=417, fp=197, fn=221
F1 score for class[1] = 0.643 tp=405, fp=214, fn=235
F1 score for class[2] = 0.824 tp=548, fp=142, fn=92
F1 score for class[3] = 0.442 tp=286, fp=369, fn=352
F1 score for class[4] = 0.609 tp=384, fp=238, fn=256
F1 score for class[5] = 0.538 tp=340, fp=284, fn=300
F1 score for class[6] = 0.693 tp=444, fp=198, fn=196
F1 score for class[7] = 0.517 tp=328, fp=305, fn=309
F1 score for class[8] = 0.985 tp=2998, fp=54, fn=40
-----------------------------------------------------
Macro F1 = 0.6573605192818119
BFRB Macro F1 = 0.6164599766386722
Score = (Binary F1 + Macro F1) / 2 = 0.824077831125434
    

※ Class 8 代表非 BFRB 手势。Class 0-7 对应:

0: 耳上 - 拉头发
1: 前额 - 拉发际线
2: 前额 - 抓挠
3: 眉毛 - 拉头发
4: 睫毛 - 拉头发
5: 颈部 - 捏皮肤
6: 颈部 - 抓挠
7: 脸颊 - 捏皮肤
    

此外,我们可视化了 BFRB 类别的混淆矩阵(其中元素 (i, j) 代表真实标签为 i 且预测标签为 j 的序列数量):

混淆矩阵

从这些观察中,我们确定:

  • “眉毛 - 拉头发”和“睫毛 - 拉头发”之间的区别特别困难。
  • 存在其他类似的手势类别。

然后我们在 One-hot 编码后引入了软标签,为类似的手势类别分配小的权重,这在整体上产生了 modest 但明显的改进。

1.5. 未成功的方法和其他考虑

改进领域

  • Stacking (堆叠)
    • 使用 Optuna 计算的 CV 最大化权重的加权平均被丢弃,因为它导致公共 leaderboard 分数降低(私有分数:0.860–0.861 😢)。
    • 尝试使用 LightGBM 进行 stacking,但未发现 CV 性能提升,因此放弃了这种方法。
  • 对齐修正
    • 由于不确定模型的鲁棒性并为了避免误报,我增加了阈值,这仅产生了有限的效果(见 1.2 节)。

我考虑过的其他方法

  • 自监督学习和对抗训练
    • 虽然鉴于被试者数量较多 (81),我想实验这些方法,但缺乏专业知识阻碍了实施。
  • 预训练模型利用
  • 绕 Y 轴旋转修正
    • 某些数据表现出绕 Y 轴旋转(可能 SUBJ_011323 需要 -90° 修正,SUBJ_032165 需要 +60° 修正)。
    • 由于省略此修正与其他被试者相比并未导致显著的精度下降,我优先处理了其他任务。
    • 如果有更多时间,我会开发一个模型来预测 Y 轴旋转角度并实施修正。

如果有人有关于这些领域的成功实施或见解,我将非常感谢您的反馈!!

2. @tokkiwa 部分 (深度学习与建模)

TL;DR

  • 逐通道卷积非常重要(单模型第 33 名!)
  • ToF 使用 3D CNN
  • 多任务训练

2.1 模型

我们的主模型由三部分组成:(1) 逐通道特征提取器,(2) BiGRU 和 (3) MLP 头。
后两部分没那么有趣,所以我们专注于特征提取器。我们首先注意到 这个 Notebook 中的通道独立 CNN 模型比其他模型表现更好。以下是代码片段:

self.branches = nn.ModuleList(
    [
        nn.Sequential(
            MultiScaleConv1d(1, 12, kernel_sizes=[3, 5, 7]),
            ResidualSEBlock(36, 48, 3, dropout=0.3),
            ResidualSEBlock(48, 48, 3, dropout=0.3),
        )
        for _ in range(num_channels)
    ]
)

该模型为每个通道独立准备 CNN 管道。由于传感器数据对于每个通道具有完全不同的模态,因此使用独立内核来提取丰富的特征是合理的。我们进一步使用分组卷积重写了分支,最初是为了加速。出乎意料的是,将通道独立卷积与通道间 SE 块相结合,比完全通道独立处理表现要好得多。

此外,我们的队友 @chimaki821 为 ToF 特征采用了 3D-CNN,以捕捉传感器的时空关系。

self.tof_branch = nn.Sequential(
     nn.Conv3d(tof_sensors, 24 * tof_sensors, (5,2,2), padding='same', groups=tof_sensors, bias=False),
     nn.BatchNorm3d(24 * tof_sensors), nn.ReLU(), nn.MaxPool3d((1,2,2)),
     nn.Conv3d(24 * tof_sensors, tof_out_channels * tof_sensors, (7,2,2), padding='same', groups=tof_sensors, bias=False),
     nn.BatchNorm3d(tof_out_channels * tof_sensors), nn.ReLU(),
     nn.AdaptiveMaxPool3d((None, 1, 1)),
 )

特征随后被送入 BiGRU 和 Attention Pooling,添加高斯噪声,最后输入到小型 MLP 头。请参阅我们的代码以获取更多细节。
对于提交,我们训练了该模型的一些变体(例如添加了 FFT 特征)以及在公共 Notebook 中看到的其他简单 CNN 模型。

2.2 训练

我们对 behavior、orientation 和 gesture 使用 separate losses 并将它们相加(最初由 @chimaki821 实施)。
具体来说,orientation 和 gesture 应用交叉熵(对于 gesture,label smoothing = 0.2 是我们的最佳实践),behavior 应用 nn.BCEWithLogitsLoss
我们最终使用了 Cosine Annealing LR、Adam 和以下数据增强:

  • 随机噪声 (random noise)
  • 随机缩放 (random scaling)
  • 人工漂移 (artificial drift)
  • 随机覆盖 (random overwrite)
  • Mixup

实施了 Focal loss、指数移动平均 (EMA)、radam 和归一化,但对我们的模型无效。
Wandb sweep 的结果表明,列的归一化对我们的模型影响很差。多任务训练的增益也有限,但我们认为它可能对集成有好效果,因为该方法使模型能够以不同方式学习。

同比赛其他方案