第 17 名解决方案
第 17 名解决方案
作者: Anil Ozturk & Optimo
发布日期: 2025-09-03
竞赛排名: 17th Place
热烈祝贺所有获奖者,感谢组织者!不幸的是,我们未能利用许多团队发现的针对特定主体(subject-wise)的提升技巧。但很高兴看到我们获得了一个鲁棒(robust)的集成模型!我们的解决方案是两个 Pipeline 的平等融合:
1. Anil 的 Pipeline
1.1. 建模
- 模式 1:仅 IMU - 当 THM/ToF 传感器不可用时,使用加速度计和旋转特征
- 模式 2:完整传感器套件 - 利用所有三种传感器模态(IMU + THM + ToF)
1.2. 特征
仅加速度计特征
- 原始加速度: acc_x, acc_y, acc_z
- 加加速度(加速度导数): acc_jerk_x, acc_jerk_y, acc_jerk_z
仅旋转特征
- 6D 旋转矩阵差异: rot_6d_diff_col1_x/y/z, rot_6d_diff_col2_x/y/z
- 绝对 6D 旋转差异: abs_rot_6d_diff_col1_x/y/z, abs_rot_6d_diff_col2_x/y/z
- 帧间相对旋转: rot_rel_6d_col1_x/y/z, rot_rel_6d_col2_x/y/z, abs_rot_rel_6d_col1_x/y/z, abs_rot_rel_6d_col2_x/y/z
ACC-ROT 融合特征
- 重力估计: gravity_est_x/y/z, gravity_est_jerk_x/y/z
- 线性加速度: linear_acc_x/y/z, abs_linear_acc_x/y/z
- 加加速度特征: jerk_x/y/z
- 主体对齐特征: acc_tangential/lateral/vertical, jerk_tangential/lateral/vertical
- 倾斜计算: tilt, tilt_diff
1.3. 架构
NaN 处理层
- IMU/THM:用于缺失值的可学习嵌入向量
- ToF:广播到所有缺失像素的单个可调参数
IMU 分支
- 两个带有 SE 的残差 CNN 块
- 卷积核大小:3 和 5
- 输出:每个时间步 128 个特征
- 特定模式 Dropout:0.3 (模式 1), 0.1 (模式 2)
THM 分支
- 两个带有 SE 的残差 CNN 块
- 输出:每个时间步 64 个特征
ToF 分支
- 空间注意力
- 深度可分离卷积
- 残差连接
- 多尺度特征提取
- 输入:(5, 8, 8) 每时间步 → 输出:128 个特征
三模态注意力
- 跨模态注意力 介于 IMU, ToF 和 THM 之间
- 双向
- 残差连接
BiLSTM + 注意力
- 128 隐藏单元,双向(总共 256)
- 用于序列级任务的注意力池化
- Dropout: 0.4
多任务辅助学习
- 手势 (主要): 20 类
- 方向: 4 类
- 惯用手: 2 类
- 行为 (每时间步): 4 类
2. Optimo 的 Pipeline
我将分享我的最终 Pipeline 看起来是什么样,并尝试解释我是如何做到的。最后我做了很多消融实验,表明一个相当简单的解决方案也可以具有竞争力。
2.1 数据标准化
这次竞赛的数据集相当小,因此似乎重要的是以更标准化的方式表示每个序列。
- ToF 对齐:由于我使用 2D CNN 进行 ToF 特征提取(见后续架构),感觉重要的是让传感器间的像素对应。因此按照设备方案,我将 ToF 传感器 3 和 5 旋转 90°。
- 左撇子对齐:我做了完全相同的事情,正如 @tatamikenn 在 这里 描述的那样,但他的绘图能力远比我好,所以我就不在这里竞争了!变换包括改变正确 acc 轴和 rot 轴的符号,以及切换传感器 3 和 5(thm 和 tof)以及翻转 tof 图像。想法是,当右撇子将手带到头部/身体时,加速度是从右到左,而左撇子则相反。
- 上下颠倒对齐:当我查看我的 OOF 分数时,很明显 "SUBJ_045235", "SUBJ_019262" 出了些问题。经过大量数据可视化,我得出结论,他们佩戴设备是上下颠倒的。当应用相应的变换(切换传感器,否定某些 acc 和 rot 轴)后,这两个主体的分数恢复正常。为了确保我的变换是正确的,我训练了一个“上下颠倒检测器”:随机对所有序列应用变换并预测是否使用了它。模型在这方面非常出色,并正确指出这两个主体确实是上下颠倒的。问题是你没有像“惯用手”那样的信息来知道某人佩戴设备是否正确。所以我尝试了一个提交,首先使用检测器,如果需要则在预测前应用变换:得到了相似的分数(那时还是两位数 LB 时代)。我想“测试集是干净的,每个人都以正确的方式佩戴设备”,所以我只是翻转了这两个主体,然后继续改进我的 CV。大错误:一些私有主体确实佩戴设备上下颠倒,检测器效果很好(LB: 863 - PLB: 857)。我应该以正确的方式去做:不要在自行更改数据后允许我的 CV 提高,我的解决方案应该包括检测器以提高 CV,我应该相信它!
- 序列长度:所有序列向右裁剪/填充到 96(64 或 128 也得到相似结果)
2.2 数据增强
我做了很多即时增强:
- 感知对齐的 MIXUP:在对齐手势开始后随机平均序列,这样平均更有意义
- 随机旋转:观看 Youtube 上女士表演手势的视频时,你可以看到设备并不总是佩戴得非常完美。所以我向四元数添加随机小旋转 (+-30°) 绕 Z 轴。
- 随机拉伸
- 随机忽略序列开头的一些点
2.3 损失函数
我觉得使用人口统计数据不太舒服,因为主体太少,对训练没有意义。尽管如此,我试图通过强迫模型预测尽可能多的事物(不同的头)来 incorporate 尽可能多的知识:
- 18 种手势
- 二元目标
- 方向
- 位置:手势自定义混合为 4 个位置(腿 - 脸 - 颈 - 前)
- 动作:手势自定义混合为 5 个动作(拉头发 - 捏 - 抓挠 - 写字 - 其他)
- 是否是儿童
位置、动作和儿童可能是无用的。
2.4 特征工程
我做了很少的特征工程:
- 加速度:原始加速度,时间差分 1, acc_mal_jerk, 序列中的归一化位置 (0 到 1)
- 四元数:原始四元数,旋转角和角速度 (x, y, z) 及距离,角速度范数和 10 窗口滚动均值
- thm:原始 thm
- rof:原始 tof
2.5 架构
我最终得到了一个相对简单的架构,我只使用了一个单一模型来学习忽略缺失信息。我没有为仅 IMU 和完整传感器设置特定模型:我只是在训练期间随机掩蔽 thm 和 tof 特征。
首先在时间级别编码信息
- 独立的 conv1D 用于 acc, quats 和 thm 进行时间级别编码(卷积核 3-5-7),每个具有 N_OUT 特征
- 2D CNN 具有 5 个通道,将 5x8x8 图像转换为 N_OUT(不共享时间序列信息)
然后每个流通过一个 gating 单元,决定每个传感器流的重要性:
class GatingUnit_v2(nn.Module):
def __init__(self, input_dim, hidden_dim=64):
super().__init__()
self.pool = nn.AdaptiveAvgPool1d(1)
self.gater = nn.Sequential(
nn.Linear(input_dim, hidden_dim),
nn.ReLU(),
nn.Linear(hidden_dim, 1),
nn.Sigmoid()
)
def forward(self, x):
# x: (batch, feat_dim, seq_len)
pooled_x = self.pool(x).squeeze(-1) # (batch, feat_dim)
gate = self.gater(pooled_x).unsqueeze(-1) # (batch, 1, 1)
gated_x = gate * x
return gated_x, gate
然后我通过求和组合所有特征,这迫使模型为每个数据流共享相同的表示:
numerator = accel_features + orient_features + temp_features + tof_features
denominator = gate_accel + gate_orient + gate_temp + gate_tof
combined_feats = numerator / (denominator + 1e-6)
然后我有一个注意力池化机制,从时间序列转到序列表示,然后是基本的 MLP 头。
2.6 CV 分数
遵循上述 Pipeline,我可以达到平均 CV 85.8(仅 IMU 82.9 - 全部 88.7),并正确翻转了上下颠倒的主体。
集成几个具有不同训练参数的模型可以达到 86.4 CV。
我的 LB 从未超过 86.6 使用我自己的模型,我从未能够过拟合公共 LB,不知道为什么。
简单地合并一个 86.5 的 Anil 提交和我自己的一个 86.5 单模型产生了 LB 87.6,所以我想这里方法的多样性很重要!
2.7 一些错误
过去一个月我未能改进我的 Pipeline 的主要原因是因为固执和 CV 蒸馏。如果使用模型的 OOF 从头训练模型,遵循相同的 CV 方案,我可以轻松达到 CV 0.88(LB 显著下降)。我花了将近一个月才接受这只是纯粹的泄漏。如果我随时间衰减 OOF 标签的重要性,CV 会下降但 LB 会上升。然后我像一个疯子一样尝试了 50 种 shades 的泄漏来降低我的 CV 并提高我的 LB。
我应该:
- 接受使用 OOF 进行蒸馏会产生强大的泄漏
- 计算我的 CV 时不允许任意将两个主体上下颠倒(我可能会尝试将我的上下颠倒检测器作为后期提交,但我怀疑 PLB 会有巨大改进)
- 可能利用 LB 来利用所有可用信息,就像一个真正的 Kaggle 冠军应该做的那样
无论如何!感谢阅读至此,再次祝贺获奖者,祝 Kaggle 愉快!