返回列表

8th place solution

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

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

作者:Konstantin Yakovlev (Grandmaster)

竞赛排名:第 8 名

发布时间:2025 年 9 月 4 日

第 8 名解决方案

竞赛 walkthrough

前方长文预警))) - 包含许多个人想法和情感。

以下所有内容仅代表我个人的观点和看法,当我说"你应该"或"你必须"时,当然这只是我的观点。

感谢 Kaggle 团队和 CMI 举办如此精彩的竞赛 - 数据非常出色,处理起来非常有趣!


当你开始参加竞赛时,从一开始就应该考虑以下几个方面:

  • 代码组织
  • 版本控制
  • 实验追踪
  • 结果可重复性

代码组织

通常我的机器学习实验遵循这样的结构(对我来说已经足够且良好)。

  • data/: 包含所有数据文件
    • raw/: 原始、未修改的数据文件
    • processed/: 经过各种处理步骤后的数据
      • base/: 基本处理后的数据(例如,清洗、过滤)
      • fe/: 特征工程后的数据
    • temp/: 不需要版本控制的临时数据文件
  • checkpoints/: 存储权重和运行记录
  • notebooks/: Jupyter notebooks
    • public/: 公开分享的 notebooks(当然你应该查看其他人做了什么)
    • eda/: 数据分析(复杂或单次数据检查)
    • old/: 我的垃圾文件夹,存放各种副本 notebooks
    • models/: 实验主文件夹,按模型类型分离
      • lgb/:
      • lstm/:
      • xxx/:
  • src/: 按功能组织的源代码
    • data/: 数据加载和处理代码
    • fe/: 特征工程代码
    • models/: 按模型类型隔离的代码
      • lgb/: LightGBM 模型
      • lstm/: LSTM 模型文件
      • pca/: PCA 模型文件
    • temp/: 临时代码文件和测试脚本
    • utils/: 工具函数和辅助模块
  • history/: 历史项目数据和记录 / 如果项目没有 git 就在这里做源码转储
  • tb/: Tensorboard 追踪日志
  • tests/: 单元测试和集成测试

我强烈建议你使用某种版本控制(git)。它将极大地帮助你在竞赛"马拉松"(通常是 3 个月)中梳理思路。

计划

所以你下载了数据 - 我也是。做了一些基本的数据检查后开始思考计划。
目标是金牌 - 任何其他结果都是失败)))。

  • 我们有 2 种类型的数据,所以我需要为每个分支(IMU 和 TOF)准备好的模型。IMU 很简单只有 7 个特征 - 所以从它开始。
  • 这是个人竞赛,所以我需要在最后用不同的架构和模型类型进行集成。
  • 肯定需要神经网络 - 任何梯度提升都不够。
  • 后处理?当然。几乎每个竞赛都需要后处理。而且 F1 指标很适合"hack"(一些阈值优化?)。
  • 推理时间限制。数据是逐序列的且很小 - 时间应该不是问题。
  • 模型类型 - 我们有时序成分(序列)应该被利用 - lstm / gru?
  • 我们有 18 个类别,但还有方向/阶段,以及主要目标可以按组件拆分"前额/区域" - "拉发际线/动作" - 肯定需要利用。
  • 只有 9k 序列 - 需要数据增强

第一步

我决定从 lgbm 开始(惊讶吧)))) 之前的 CMI 竞赛(我们与 @trasibulo 一起赢了)表明梯度模型在某些任务中可以与神经网络竞争。
做了一些实验,显示了一些有趣的细节。

  • 模型过拟合非常严重!
  • 预测手势/非手势相对容易
  • 15 步的历史窗口足以让模型跟上
  • 泄露的 std 意味着按受试者归一化有很大帮助 - 所以存在一些受试者的设备差异(校准/人口统计数据?)。
  • ROC AUC 非常接近 1 - 0.998+ - 所以需要更多数据 - 肯定需要数据增强(我们可以生成自己的数据吗?还是只增强已有的数据?还是不在序列级别预测而是在点级别 N 预测看过去?)

查看了竞赛预测流程并尝试提交 lgbm 非常基础的预测

CMI - Baseline v0
成功 · 2 个月前 
0.704 / 0.722

好的,它有效。思考吧兄弟,思考 - 我有什么可以在流程中改进的(不是说分数 - 很明显这甚至远不够好)。
我可以为每个目标使用不同的模型 - 1 个用于多分类,1 个用于二分类。

  • [添加到计划] - 制作两个不同的模型,如果推理时间和内存允许就都使用(并且如果二分类模型更好)

仍然是 LGBM 但处理多分类问题 - 我考虑切换到 catboost 或 xgb,但很明显梯度提升是死胡同。

我保留了 lgbm 流程来测试模型对特征工程的反应,以决定保留什么和扩展什么(最终没用,因为无论特征多好,NN 对待它的方式不同,
几乎每个自定义特征都让 NN 结果更差)

另外,曾考虑为梯度提升做 PCA / 自动编码器,但决定切换到纯 NN。

目标 | 我们在预测什么?

好的,我们有了一个几乎没有特征工程的基线;是时候深入探索了。

让我们检查我们的目标是什么。

df.drop_duplicates(subset=['sequence_id'])['gesture'].value_counts()
gesture
Forehead - pull hairline                      640
Neck - pinch skin                             640
Text on phone                                 640
Neck - scratch                                640
Forehead - scratch                            640
Eyelash - pull hair                           640
Above ear - pull hair                         638
Eyebrow - pull hair                           638
Cheek - pinch skin                            637
Wave hello                                    478
Write name in air                             477
Pull air toward your face                     477
Feel around in tray and pull out an object    161
Write name on leg                             161
Pinch knee/leg skin                           161
Scratch knee/leg skin                         161
Drink from bottle/cup                         161
Glasses on/off                                161
Name: count, dtype: int64

等等,什么?每个人做的动作完全一样吗?

SUBJ_000206  Above ear - pull hair                         8
             Cheek - pinch skin                            8
             Eyebrow - pull hair                           8
             Eyelash - pull hair                           8
             Forehead - pull hairline                      8
             Forehead - scratch                            8
             Neck - pinch skin                             8
             Neck - scratch                                8
             Text on phone                                 8
             Pull air toward your face                     6
             Wave hello                                    6
             Write name in air                             6
             Drink from bottle/cup                         2
             Feel around in tray and pull out an object    2
             Glasses on/off                                2
             Pinch knee/leg skin                           2
             Scratch knee/leg skin                         2
             Write name on leg                             2
SUBJ_001430  Above ear - pull hair                         8
             Cheek - pinch skin                            8
             Eyebrow - pull hair                           8
             Eyelash - pull hair                           8
             Forehead - pull hairline                      8
             Forehead - scratch                            8
             Neck - pinch skin                             8
             Neck - scratch                                8
             Text on phone                                 8
             Pull air toward your face                     6
             Wave hello                                    6
             Write name in air                             6
             Drink from bottle/cup                         2
             Feel around in tray and pull out an object    2
             Glasses on/off                                2
             Pinch knee/leg skin                           2
             Scratch knee/leg skin                         2
             Write name on leg                             2

好的 - 这将是我们的后处理!- 不知道具体是什么以及如何做,但这里是关键!

神经网络实验

尝试了不同的神经网络配置(多亏了 Junie 很快)。决定为每个模态使用不同的"分支"和可定制的头部。

Junie 在这里说明:

### 模型概述

我们的解决方案是一个多模态神经网络,专为从异构时间序列特征进行手势识别而定制。架构根据配置动态组装:对于每个特征组(例如,原始 IMU、去重力信号、工程特征、ToF 衍生的 2D 地图),模型实例化一个专用编码器并固定其输出维度以供后续融合。编码的特征被连接并通过两个带有模态 dropout 的独立融合块。第一个融合流为所有手势和非手势类别提供主多分类器,而第二个流提供辅助二分类检测器(手势 vs 非手势)。可选地,第二个流还可以产生一个小的辅助多分类头部和一个轻量级回归头部。

### 模态特定编码器

每个活动特征组使用由配置选择的处理器。支持的处理器包括用于 1D 序列的时间编码器、紧凑的 `dense` 处理器和几个 ToF 2D 骨干网络。只有 `use_feat=True` 的特征会被实例化。每个编码器将输入投影到固定的 `output_dim`,这些嵌入在融合前被连接。这种模块化设计允许在不改变训练循环的情况下轻松进行特征集的消融或扩展。

### 融合和分类头部

我们使用具有可配置宽度和 dropout 的统一融合模块,加上随机丢弃整个模态嵌入的模态 dropout 以提高鲁棒性。两个融合模块接收相同的连接表示但在训练期间专业化:第一个针对细粒度多分类判别,第二个提炼干净的手势信号。主头部输出所有类别的 logits;辅助头部输出单个 sigmoid 概率用于手势 vs 非手势。启用时,第二个流还可以驱动辅助多分类器和简单的回归头部作为归纳正则化器。

### 训练目标和优化

训练使用由配置选择的一系列组合损失将主多分类目标与辅助二分类目标耦合,支持可选的 mixup。当辅助头部激活时,我们添加辅助多分类目标的交叉熵项和回归头部的 MSE 项,两者权重都很小。循环支持每批次学习率调度、可选的模型权重 EMA,以及可选的 dropout 退火器以逐渐调整正则化。这种设置稳定了长序列和异构模态的优化。

### 推理和评估

在推理时,模型编码每个活动特征组,连接嵌入,并运行两个融合路径以产生预测。对于验证,我们计算与竞赛目标对齐的指标:手势 vs 非手势的二分类 ROC AUC 和 F1;前八个手势类别的平均 ROC AUC;完整标签空间的 macro-F1;以及裁剪的手势专用 macro-F1。报告的竞赛指标是二分类 F1 和手势 macro-F1 的平均值,平衡检测质量和类别判别。简单的 TTA 模式可以在序列级别聚合预测以增加鲁棒性。

### 实际优势

- 模块化编码器让我们可以集成不同的信号并为每个模态交换架构,而无需触碰核心训练循环。
- 双融合流将细粒度分类与鲁棒手势检测解耦,提高稳定性和整体性能。
- 辅助头部提供有针对性的正则化和更丰富的监督。
- 模态 dropout 增强了一些特征通道缺失或噪声时的弹性。

对于代码代理(Junie),我创建了一个特殊文件夹和一些指令。这将是他的个人沙盒,可以在这里玩耍而不会在其他地方"制造垃圾文件"。

## 实验结构和组织

`src/experiments` 目录组织用于追踪模型实验:

- `proposal/`: 包含 markdown 格式的实验提案
    - 每个文件代表一个实验提案
    - 命名约定:`exp_XXX_short_description.md` 其中 XXX 是序列号

- `running/`: 包含当前进行中的实验
    - 实验在实施时从 `proposal/` 移动到这里
    - 可能包括与实验实施相关的额外文件

- `results/`: 包含完成的实验结果
    - 最终结果、指标和实验分析
    - 应引用原始提案

每个实验提案遵循标准化格式,包含以下部分:

- 目标
- 假设
- 方法论
- 评估指标
- 预期结果
- 所需资源
- 参考文献

实验工作流程包括:

1. 在 `proposal/` 目录中创建提案
2. 实施期间移动到 `running/`
3. 在 `results/` 目录中记录结果
4. 使用发现为未来实验提供信息

最终我采用了这样的模态和特征(特征在不同模型中添加/关闭/开启):

# 1d 时间序列
raw_acc = [
    'raw__|acc_x', 'raw__|acc_y', 'raw__|acc_z', 'raw__|acc_magnitude_all',
    'raw__|acc_x__dif__|lag-1', 'raw__|acc_y__dif__|lag-1', 'raw__|acc_z__dif__|lag-1', 'raw__|acc_magnitude_all__dif__|lag-1',
    'raw__|acc_x__ratio__|lag-1', 'raw__|acc_y__ratio__|lag-1', 'raw__|acc_z__ratio__|lag-1', 'raw__|acc_magnitude_all__ratio__|lag-1',
]

# 1d 时间序列
raw_rot = [
    'raw__|rot_x', 'raw__|rot_y', 'raw__|rot_z', 'raw__|rot_w',
    'raw__|rot_x__dif__|lag-1', 'raw__|rot_y__dif__|lag-1', 'raw__|rot_z__dif__|lag-1', 'raw__|rot_w__dif__|lag-1',
]

# 1d 时间序列
raw_thm = [
    'thm_1', 'thm_2', 'thm_3', 'thm_4', 'thm_5',
    'thm__global__|mean', 'thm__global__|std', 'thm__global__|min', 'thm__global__|max',
    'tof__global__|mean', 'tof__global__|std', 'tof__global__|min', 'tof__global__|max', 
]

# 2d 时间序列
raw_tof = [c for c in list(base_df) if 'tof_v' in c]

# 1d 时间序列
raw_vel = [
    'ang__|velocity_acc_x', 'ang__|velocity_acc_y', 'ang__|velocity_acc_z',
    'ang__|velocity_acc_x__dif__|lag-1','ang__|velocity_acc_y__dif__|lag-1', 'ang__|velocity_acc_z__dif__|lag-1',
]

raw_custom = [
    'raw__|acc_magnitude_all', 'raw__|acc_magnitude_xy', 'raw__|acc_magnitude_zx', 'raw__|acc_magnitude_zy',
    'raw__|rot_angle', 'raw__|rot_angle__dif__|lag-1',
    'raw__|acc_xy_angle', 'raw__|acc_zx_angle', 'raw__|acc_zy_angle',
    'no_grav__|acc_magnitude_all', 'no_grav__|acc_magnitude_all__dif__|lag-1',
    ...
]

# Dense
raw_global = [
    'raw__|acc_x__global__|std', 'raw__|acc_y__global__|std', 'raw__|acc_z__global__|std',
    'raw__|rot_w__global__|std', 'raw__|rot_x__global__|std', 'raw__|rot_y__global__|std', 'raw__|rot_z__global__|std',
    ...
]

# Dense
raw_demo = [
    'adult_child', 'sex', 'handedness', 'age', 'height_cm', 'shoulder_to_wrist_cm', 'elbow_to_wrist_cm',
]

特征工程和数据清洗

我尝试了不同的方法,如翻转手/小旋转/TTA - 花了太多时间但没有成功。
简单的事情被保留为基础:

  • 左手翻转 'acc_x' 'rot_y' 'rot_z'] | *-1
  • 奇怪的 'SUBJ_045235', 'SUBJ_019262' 其中 acc_y 似乎被翻转 - *-1 略微提高了 cv

特征工程

  • 重力移除 - 2 个版本
  • 速度
  • 距离
  • 角度和投影
  • 幅度 **2 / **3 全部和仅成对
  • 滞后/差异/比率

这里没有花哨的东西

  • 为不同版本添加自定义命名以保持一致性,如 'raw__|rot_x__dif__|lag-1' - 添加新特征和实验很简单

暂停和回归

8 月 11 日,我的本地验证对于 IMU 数据是 0.805-0.815,在 3 周后停止了竞赛工作。完全停止了 kaggling,因为我在 JetBrains 开始了新工作,所有时间都 dedicated 给它(另一个原因是我认为我的模型对于金牌区根本不够好)。

8 月 29 日星期五晚上 - 一杯好的葡萄牙葡萄酒在我手中,工作日结束 / 20:00 时间 / 没有爱好 - 当然让我们检查我为竞赛做了什么并用清晰的眼光回顾。

什么!?错误?竞赛指标上的巨大错误。我在 TTA 上实验,我的最终指标有点奇怪(忘记改回正常))))

简化示例:
f1 on
mask = binary_predictions > 0.5
np.clip(np.argmax(values[mask], axis=1), 0, 7)
+
f1 on
np.argmax(values, axis=1)<=7

/ 2

当然,这不是竞赛指标 - 立即改为 np.clip(np.argmax(.values, axis=1), 0, 8)
然后 tadaaam 我有 0.82-0.824。
所以我有一个有竞争力的单模型 - THM 和 ToF 应该给 +0.03
0.85 + 集成 - 0.86 + 后处理 0.87 - 我可以挑战金牌区

配置

周末期间我重写了所有模型并准备了统一配置以开始进行集成

始终使用 Tensorboard(或等效工具)- 否则你将无法进行良好的比较。

统一配置如下所示

base_params = {
    'GLOBAL_VERBOSE': False,

    # 数据集参数
    'WINDOW_SIZE': 140,
    'OUT_WINDOW_SIZE': 386,  
    'CUR_FOLD': 0,
    'TRAIN_OFFSET': 0,
    'MAIN_TARGET': 'target_b', # 主多分类
    'AUX_TARGET': 'target_a', # 辅助二分类
    'AUX_TARGET_WEIGHT': 0.3,

    'AUX_TARGET_2': 'target_c', # 辅助多分类
    'AUX2_NUM_CLASSES': 4,

    'AUX_TARGET_3': True, # 回归目标
    'AUX3_WEIGHT': 0.05,

    'FEATURES': {}, # 动态添加
    'DIMS': {}, # 动态添加
    'NUM_CLASSES': 18,
    'FBFILL': False,
    'FILLNA_VALUE': 0,
    'USE_SCALER': True,

    # 批次参数
    'BATCH_SIZE': 64,
    'EVAL_BATCH_SIZE': 256 * 8,

    # 特征工程
    'MAKE_FE': False, # 运行时 fe

    # 模型参数
    'LEARNING_RATE': 0.0005, 
    'NUM_EPOCHS': 30, 
    'WEIGHT_DECAY': 0.0001,
    'EMA_DECAY': 0.999,

    # 调度器参数
    'SCHEDULER': 'cosine_warmup_decay',
    'WARMUP_EPOCHS': 5, 
    'WARMUP_LR': 0.0001,
    'MIN_LR': 0.00008,

    # 数据增强
    'USE_TTA': False,
    'DS_AUG': 10,
    'MIXUP': True,
    'MIXUP_PROB': 0.8,
    'MIXUP_ALFA': 0.5,
    'RAW_AUG': {},
    'TS_AUG': {
            'enable': True,
            'apply_to': ['raw_acc', 'raw_rot', 'raw_vel', 'raw_thm', 'raw_tof', 'raw_custom'],
            'time_stretch_range': (0.8, 1.2),
            'time_shift_range': 0.1,
            'noise_std': 0.02,
            'magnitude_scale_range': (0.9, 1.1),
            'rotation_angle_range': 0.1,
            'mask_ratio': 0.1,
            'freq_filter_range': (0.1, 0.9),
            'p_stretch': 0.,
            'p_shift': 0.,
            'p_noise': 0.5,
            'p_mag': 0.1,
            'p_rotate': 0.,
            'p_mask': 0.1,
            'p_freq': 0.,
        },

    # 其他
    'SEED': 18,

    # 损失
    'LOSS_NAME': 'v1',
    'NEGATIVE_PROBS': False,

    # 层参数
    'fusion_channels': 512,
    'fusion_dropout': 0.5,
    'fusion_modality_dropout': 0.2,

    'PROCESSOR_CONFIGS': {
        "raw_acc": {
            "processor_type": "temporal_v2",
            "use_feat": True,
            "output_dim": 512,
            "dropout": 0.5,
            "res_blocks": {
                "block1": {
                    "in_channels": None,
                    "channels": [64, 128],
                    "ks": [3, 5],
                    "dropout": 0.5,
                    "reduction": 8,
                    "pool_size": 2,
                    "core_type": "cnn2"
                },
                "block2": {
                    "in_channels": 256,
                    "channels": [128, 256, 256],
                    "ks": [3, 5, 5],
                    "dropout": 0.5,
                    "reduction": 8,
                    "pool_size": 2,
                    "core_type": "cnn3"
                }
            }
        },
        ...
        'raw_tof': {
            'use_feat': True,
            'processor_type': 'tof_conv2d',
            'output_dim': 64,
            'conv_channels': [8, 16, 32],
            'kernel_size': 3,
            'dropout': 0.5,
            'pool_size': 2,
            'temporal_dim': 512
        },
        'raw_global': {
            'processor_type': 'dense',
            'use_feat': False,
            'output_dim': 32,
            'hidden_dim': 64,
            'dropout': 0.7,
            'use_bn': True,
        },

    # Dropout 调度器
    'USE_DROPOUT_SCHEDULER': False,
}

这样的配置允许动态更改参数并追踪任何更改,在 Kaggle 上以稳定的结果重新运行模型推理。

好的模型被上传到 Kaggle 数据集,我做了几次提交。周一至周四我慢慢训练 IMU 模型,因为不完全相信会成功。
8 月 25 日晚提交 9/5 折 IMU 模型在 LB 上得到 0.838。

Junie 在这里说明:

### TemporalProcessor

`TemporalProcessor` 是一个紧凑的序列编码器,结合卷积特征提取、时间建模和基于注意力的池化,为每个序列产生固定大小的表示。它接受形状为 `[B, T, F]`(批次、时间、特征)的输入张量,用残差 1D CNN 提取局部时间模式,用双向 GRU 建模更长范围的依赖关系,并通过可学习的注意力将序列压缩为单个向量,然后投影到固定的 `output_dim`。

#### 卷积前端(局部模式提取)
- 两个残差 CNN 块(`ResidualCNNBlock`)在转置信号 `[B, F, T]` 上操作:
  - 块 1 从 `in_features` 调整通道宽度并使用 3 层或 2 层 1D CNN 核心。
  - 块 2 继续用自己的核心进行特征细化。
- 每个块包括:
  - 通过 Squeeze-and-Excitation `SEBlock` 进行通道注意力以重新加权特征图。
  - 残差加法、ReLU 激活、`MaxPool1d` 下采样和 dropout。
- 这个阶段增加感受野并减少序列长度(两个池化步骤),同时通过残差连接保持稳定的梯度。

#### 时间骨干(序列建模)
- 卷积输出被转置回 `[B, T', C]` 并送入双向 GRU:
  - `input_size = C` 等于最终 CNN 通道数(来自块 2)。
  - `hidden_size = C`, `bidirectional=True`, `batch_first=True`, `bias=False`。
  - GRU 后跟一个 dropout 层以正则化时间表示。

#### 注意力池化和投影
- 可学习的 `AttentionLayer` 消耗 GRU 输出 `[B, T', 2C]` 并返回加权上下文向量 `[B, 2C]`,将模型集中在信息最丰富的时间步上。
- 最终线性层将该向量映射到 `output_dim`,产生适合下游融合和分类的每个序列嵌入。

#### 可配置性和 I/O
- 关键参数:
  - `in_features`: 每个时间步的输入通道数。
  - `res_blocks`: 字典,配置每个块的通道、内核大小、池化、dropout、SE 缩减和 `core_type`。默认提供强大的通用设置:
    - 块 1: `channels=[64,128]`, `ks=[3,5]`, `core_type='cnn2'`。
    - 块 2: `channels=[128,128,256]`, `ks=[3,5,5]`, `core_type='cnn3'`。
  - `dropout`: GRU 输出 dropout;每个块有自己的 dropout。
  - `output_dim`: 导出到融合模块的最终嵌入大小。
- 形状:
  - 输入:`[B, T, in_features]`
  - CNN 块后:时间下采样,通道扩展
  - BiGRU 后:`[B, T', 2C]`
  - 输出:`[B, output_dim]`(序列级向量)

#### 设计原理
- 带有 SE 注意力的残差 CNN 捕捉多尺度局部动态并早期抑制噪声通道。
- 最大池化减少计算负载并鼓励对微小时间偏移的不变性。
- 双向 GRU 通过建模超出卷积感受野的更长上下文来补充 CNN。
- 注意力池化提供了一种灵活、数据驱动的方式来总结可变长度序列。
- 固定的 `output_dim` 使该处理器在多模态融合框架中即插即用,允许不同的特征组贡献可比大小的嵌入。

最后几天

好的,现在我有选项要么全力以赴要么就停止。Danila Savenkov(JB 方面,你可能认识他为 @daniel89)允许我花 3 天在 Kaggle 上(周五/周一/周二 + 周末,当然)。

决定做出 - 全力以赴

我需要什么:

  • GPU - lightning AI - 1 个自己的 gpu 不够
  • ToF 模型
  • 后处理
  • 更好的 IMU 模型
  • 运气)))
  • 小心的 LB 探测
  • 不要在 CV 上搞砸,因为没有机会修正任何东西

周四晚上/周五

  • 用集成设置进行 LB 探测 - 平均/加权平均/模型数量/全数据或 5 折 - 仍然是 0.838
  • 决定使用多个弱 5 折模型 - 这是唯一的机会,因为没有选项在全数据上探测单独的单模型
  • 让我们制作 ToF 模型 - 与 1 个 ToF 混合的 IMU 得到 0.855
  • 我需要更多 ToF 模型和更多 IMU - 开始大规模训练

周六

  • 我需要后处理 - 第 1 名不可能仅靠模型就有这样的分数差异
  • 第一个 PP 版本 - 0.862 - 好的开始,但我需要更多
  • 更多 ToF 模型 - 5 个准备好并验证 - 带 PP 为 0.871(没有提交可以不带 PP 探测)
  • 修复 EMA 模型 - 克隆可训练参数但没有缓冲区 - 修复使 EMA 工作

周日

  • 切换到仅 EMA 模型
  • 更改 LR 调度器
  • 重新训练 IMU 模型
  • 进行大规模探测(确保所有组件按预期工作)
  • 单 IMU 无 PP - (0.823 / 0.834)
  • 6 个 IMU 模型无 PP 加权平均 - (0.837 / 0.845)
  • 6 个 IMU 模型带 PP 加权平均 - (0.840 / 0.853)
  • 6 个 IMU + 旧 ToF 无 PP (0.851 / 0.868)
  • 6 个 IMU + 旧 ToF 带 PP (0.863 / 0.876)
  • 对后处理做了一些更改,但没能大幅改善输出,我的本地 CV 显示 +0.01 +/- 0.0015 std / 最大 - 最小 0.006

周一

  • 新 ToF 模型
  • 无 PP (0.853 / 0.872)
  • 带 PP (0.868 / 0.883)

周二 - 最后一天

  • 时间应该非常精确
  • 所有新模型分数应该在伦敦时间 22:00 前准备好以提交最终解决方案
  • 新 Imu 模型 6 次运行 - 每个在 3 个 gpu 上 1.5 小时,应该在 16:00 前准备好 - 2 小时推理 - 19:00 分数最大
  • 新 ToF 模型 - 每次运行 3 小时 - 能够再做 3 个模型 - 应该在 19:00 前准备好 - 21:00 分数最大
  • 1 小时准备最终混合带 pp 并在 22:00 前提交

CMI - Final 5.0 - 最终候选 2!
成功 · 2025 年 9 月 2 日星期二 21:58:40 GMT+0100 (0.860 / 0.877)

CMI - Final 4.0 - 最终候选 1!
成功 · 2025 年 9 月 2 日星期二 21:57:42 GMT+0100 (0.858 / 0.880)

  • 完成 - 选择候选 1 和之前最佳 lb 模型 - 可能 0.858 也足以获得金牌,但我们永远不会知道)))

模型更改实验

  • 特征
  • 不同的 CNN 块 - 'cnn', 'inception', 'gated_dilated', 'convnext', 'miniconformer', 'fft'
  • 卷积层
  • 输出维度
  • 外推窗口
  • LR
  • dropouts
  • 融合块
  • 辅助目标
  • 损失函数

没有选项测试我的权重是否正确 - 可能简单的平均混合带 cv 分数 cutoff 会表现更好。

混合是用 Dirichlet 搜索与 oof 预测进行的(如果 CV 错误且与 LB 不对齐则太冒险)- cutoff 低权重并重新归一化

IMU 最终指标(四舍五入 + 修剪):0.843523 | 带 pp 为 0.854

TOF 最终指标(四舍五入 + 修剪):0.891330 | 带 pp 为 0.903

#################################
# 模型权重
#################################

# IMU
'test_run_-_2025-09-02_102121': np.float64(0.04577114427860696),
'test_run_-_2025-09-02_104733': np.float64(0.07462686567164178),
'test_run_-_2025-09-02_100513': np.float64(0.13532338308457711),
'test_run_-_2025-09-02_100715': np.float64(0.04676616915422885),
'test_run_-_2025-09-01_232203': np.float64(0.07462686567164178),
'test_run_-_2025-08-31_182223': np.float64(0.16417910447761194),
'test_run_-_2025-08-31_182238': np.float64(0.04577114427860696),
'test_run_-_2025-08-31_182155': np.float64(0.055721393034825865),
'test_run_-_2025-08-30_144324': np.float64(0.04577114427860696),
'test_run_-_2025-08-26_125131': np.float64(0.04676616915422885),
'test_run_-_2025-08-26_095603': np.float64(0.1024875621890547),
'test_run_-_2025-08-26_055724': np.float64(0.05771144278606965),
'test_run_-_2025-08-26_055618': np.float64(0.10447761194029849)
}

# TOF
{'test_run_-_2025-09-01_174406': np.float64(0.08208884884305259),
'test_run_-_2025-09-01_095855': np.float64(0.027909755890227466),
'test_run_-_2025-09-01_022627': np.float64(0.15450960916030498),
'test_run_-_2025-08-30_112546': np.float64(0.029019711064357332),
'test_run_-_2025-08-29_151211': np.float64(0.028584738288600937),
'test_run_-_2025-08-28_124723': np.float64(0.009465796363042244),
'test_run_-_2025-08-28_124649': np.float64(0.07006594305155826),
'test_run_-_2025-08-29_091104': np.float64(0.08299200257345174),
'test_run_-_2025-08-28_154159': np.float64(0.13787604157789557),
'test_run_-_2025-08-27_205748': np.float64(0.028090284333857785),
'test_run_-_2025-08-27_141837': np.float64(0.12763658458693833),
'test_run_-_2025-08-31_182223': np.float64(0.11118294788297756),
'test_run_-_2025-08-31_182142': np.float64(0.005057595225800813),
'test_run_-_2025-08-31_182238': np.float64(0.06738590961562396),
'test_run_-_2025-08-31_182155': np.float64(0.00539168026876913),
'test_run_-_2025-08-26_170459': np.float64(0.010176717301051534),
'test_run_-_2025-08-26_095603': np.float64(0.010695373071077567),
'test_run_-_2025-08-26_055724': np.float64(0.01187046090141201)
}

后记

我加入 Kaggle 快 7 年了。从未想过我会成为三重大师 - 直到今天,我甚至羞于称自己为 GM,因为只有竞赛头衔才算数。
通往最后一枚奖牌的道路很长))) 这不是结束 - 我会继续竞赛(当有更多空闲时间时),并且肯定会一直竞赛,只要出现表格数据(不是股票市场))。
这么多美好的回忆涌上心头:

没有你们 - 你们的指导、长谈、你们的榜样,我不会取得所有这些成就。
当然还有 Kaggle 社区 - 支持、知识分享、温和的教学 - 没有其他地方像这样。

谢谢你们!

```
同比赛其他方案