返回列表

10th Place Solution

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

开始: 2025-05-30 结束: 2025-09-02 健康管理与公共卫生 数据算法赛
第 10 名解决方案

第 10 名解决方案

作者: ohkawa3 (Grandmaster)

发布日期: 2025 年 9 月 6 日

竞赛排名: 第 10 名

首先,我想对组织这场激动人心的比赛表示感激。对我来说,这是近两年来获得的第一枚金牌。此外,由于我之前曾在同一场 Child Mind Institute 竞赛中通过排名跃升(shakeup)获得过第 10 名,这次的结果对我而言显得尤为有意义。


训练策略

  • Epochs & 优化器: 200 个 epoch,使用 AdamW 优化器和 EMA(衰减=0.999),无学习率调度。
  • 验证策略: StratifiedGroupKFold,设置 groups="subject"k=5。CV 分割使用三个不同的种子值生成:0、1 和 2。
  • 实验管理:
    • 对于明显表现不佳的实验,仅运行初始种子(seed 0)。所有其他实验均在三个种子下进行评估。
    • F1 指标显示出显著的不稳定性;在某些情况下,seed=0 的折叠表现很高,但 seed 1 和 2 的表现较低。
  • 模型选择:
    • 基于所有 15 个实验(5 折 x 3 种子)的平均性能选择最佳 epoch。
    • 此外,如果 F1 指标未变但 loss 有所改善,则选择该模型。

预处理

NaN 值的处理

  • 旋转(rot: 用单位四元数替换缺失值。
  • 热成像(thm:
    • 对于每个序列,如果任何传感器值低于 18 度,我们将该传感器对应的值替换为 NaN。这是为了屏蔽 potentially 损坏的读数。
    • 计算 NaN 值的行均值并用于插补。
    • 对于仅包含 NaN 值的行,我们分配全局均值 27.03

用手习惯校正

将所有传感器值调整为右手 orientation。

# Imu
df['acc_x'] *= -1.0
df['rot_y'] *= -1.0
df['rot_z'] *= -1.0

# Thm
thm_3 = df['thm_3'].copy()
thm_5 = df['thm_5'].copy()
df['thm_5'] = thm_3
df['thm_3'] = thm_5

# Tof
tof_names = [f'tof_{i}' for i in range(200)]
tof = df[tof_names].values.copy()
tof = tof.reshape(-1, 5, 8, 8)
tof = tof[:, [0, 1, 4, 3, 2]]
tof = np.flip(tof, 3)
tof = tof.reshape(-1, 5 * 8 * 8)
df[tof_names] = tof.copy()

Z 轴旋转

确定 SUBJ_019262SUBJ_045235 的传感器围绕 Z 轴旋转了 180 度。对其数据应用了以下校正。

# acc
df['acc_x'] *= -1.0
df['acc_y'] *= -1.0

# rot
w = df['rot_w'].copy()
x = df['rot_x'].copy()
y = df['rot_y'].copy()
z = df['rot_z'].copy()
df['rot_w'] = -z
df['rot_x'] = y
df['rot_y'] = -x
df['rot_z'] = w

# thm
thm_2 = df['thm_2'].copy()
thm_3 = df['thm_3'].copy()
thm_4 = df['thm_4'].copy()
thm_5 = df['thm_5'].copy()
df['thm_2'] = thm_4
df['thm_4'] = thm_2
df['thm_3'] = thm_5
df['thm_5'] = thm_3

# tof
tof_names = [f'tof_{i}' for i in range(200)]
tof = df[tof_names].values.copy()
tof = tof.reshape(-1, 5, 8, 8)
tof = np.rot90(tof, k=2, axes=(2, 3))
tof = tof[:, [0, 3, 4, 1, 2]]
tof = tof.reshape(-1, 5 * 8 * 8)
df[tof_names] = tof.copy()

数据增强

由于数据集大小有限,数据增强对模型性能至关重要。

时间序列拉伸

  • 四元数 (rot):
    • 问题: 四元数可以用反转的符号($q$ 和 $-q$)表示相同的方向。这会导致序列中出现“反转点”,基于插值的拉伸会产生无意义的数据。
    • 解决方案: 我们预处理序列以确保所有成对内积为正,创建平滑的方向路径。
    • 方法: 使用 SlerpNearest 插值方法。
  • 加速度 (acc):
    • 问题: 简单的插值(例如拉伸 1.5 倍)会错误地缩放从加速度 derived 的积分速度和位置。
    • 解决方案: 我们实施了一种校正,以在拉伸后保留起始和结束坐标。
    • 过程:
      1. 使用方向(rot)数据将每一帧的加速度分离为重力线性分量。
      2. 仅对线性加速度分量应用拉伸,然后将其除以拉伸因子的平方。
      3. 使用拉伸后的 rot 值重新计算重力分量。

旋转

  • 旋转数据以模拟腕带错位非常有效。
  • 我们沿 Y、X 和 Z 轴对整个序列应用随机旋转偏差。
  • 偏差的最佳标准差为:std_Y=10std_X=7,和 std_Z=0.1

MIXUP

  • MIXUP 被证明是一种极其有效的技术。
  • 实现:
    • 我们在特征工程之后应用 MIXUP。
    • 为了防止过拟合并提高长期训练的性能,我们分多个阶段应用 MIXUP:z = Mixup(Mixup(Mixup(x)))
    • 为了防止过度平均,原始数据 x 与混合数据 Mixup(x) 沿 batch 维度连接。
  • 参数: 第一阶段使用 beta=0.5,而第二和第三阶段使用 beta=16
  • 我们还尝试了通过从 Dirichlet 分布采样来混合三个或更多数据样本,但多阶段方法产生了更好的结果。

模型架构

  • 输入: 模型处理128 帧的输入序列。
  • 感受野: 此任务不需要长期时间依赖。模型的感受野跨越15 帧(约 1.5 秒),这已足够。这是通过 7 个卷积层(kernel size 3)without pooling 实现的。
  • Attention: 特征使用additive attention加权,合并为单一表示,然后传递给分类器。
  • 两阶段模型:
    • 阶段 1: 初始模型估计 4 个姿势类别,并将这些概率值馈送到第二阶段。
    • 阶段 2: 第二个模型输入原始传感器数据和姿势概率,以预测最终的 18 类和 9 类目标。
  • 集成: 我们创建并使用了单独的模型:一个用于仅 IMU 数据,另一个用于所有传感器数据。

特征工程

  • IMU (12 特征):
    • 加速度,加速度范数,线性加速度,线性加速度范数,角速度,角速度范数。
  • THM (10 特征):
    • 温度,温度的时间导数。
  • TOF (5 特征):
    • 传感器wise 平均值。

性能显著提升的原因("Shake Up")

  • 我们实现了从第 24 名(公有榜)到第 10 名(私有榜)的显著提升。
  • 主要原因: 这种提升可能源于预处理步骤,我们检测并校正了围绕 Z 轴翻转 180 度的传感器数据。
  • 分数影响:
    • 公有 leaderboard: 0.874(无变化)
    • 私有 leaderboard: 0.857 → 0.867(0.01 提升)
  • 我们没有进行任何私有 probing。
  • 鉴于传感器翻转案例发生在训练数据 82 个主体中的 2 个,私有测试主体中至少存在一个的概率约为 40%。因此,我们认为这个结果是幸运的。
同比赛其他方案