目的:让模型学会"正常驾驶里的危险长什么样"
回顾三阶段分工:阶段①要学出一个通用的危险几何先验 H = {o, ρ, v},学成后永久冻结,后面阶段②的事故语义、阶段③的扩散生成都建立在它之上。
为什么必须先冻结?这是核心设计决策 3:先验一旦冻结,它就是一块"通用零件"——阶段②随便怎么注入事故语义,都不会反过来污染先验本身;而且下游每次出问题,你可以确定问题不在先验(它有冻结凭证和验收数字),排查范围瞬间缩小。后来阶段③能层层归因,靠的就是这套"冻结+验收闸门"制度。
输入输出:上一阶段的产物直接对接
| 项目 | 内容 | 形状 |
|---|---|---|
| 输入 X | 历史占用序列(任务 A 产物) | (10, 2, 200, 200, 20):10 帧 × 2 通道(占用+距离分层) |
| 监督 Y | 伪标签(任务 B 产物) | rho (10,1,·)、vel (10,3,·)、mask (10,1,·) |
| 模型输出 Ĥ | 三个头:ô 占用、ρ̂ 风险强度、v̂ 风险方向 | 与 Y 同网格 |
三个输出的语义对应"回答三个问题":
- o(占用):东西在哪——最基础的几何理解;
- ρ(风险强度):哪里危险、多危险——[0,1] 连续场;
- v(风险方向):危险从哪个方向来——BEV 单位向量场,只在 mask=1 的体素上被监督。
注意一个设计:模型看 10 帧历史,但预测的是当前帧的危险场。10 帧历史提供运动信息(哪个目标在逼近),输出落在"当下"。
模型设计:为什么是 Grid U-Net
U-Net 的选型逻辑:输入输出都是同分辨率的三维网格(200×200×20),这是典型的"稠密预测"任务——和图像分割同构。U-Net 的下采样路径压缩出大感受野(危险判断需要看到 40m 外的上下文),上采样路径恢复原分辨率,跳跃连接保住局部细节(行人的小核不能被抹掉)。
输出端是三个头:共享一个编码器-解码器主干,最后分叉成 o 头、ρ 头、v 头。共享主干的理由:三个任务是同一个物理现实的三面——"哪里有东西、哪里危险、危险从哪来"高度相关,共享特征互相借力(也埋下了后面多任务损失互相打架的伏笔,马上讲)。
ρ 头输出的是 logits,导出时才过 sigmoid 压到 [0,1]——这个细节当时没人在意,后来在阶段③引发了大地震(背景平台问题,见下面"事后暴露的缺陷")。
训练的核心难题:ρ 塌缩三轮攻关
背景:数据极度不平衡。回忆任务 B 的验收数字:ρ 场 99.3% 的体素是精确零,非零体素只有 0.71%。这是一个"极端稀疏连续场"。
第一轮:weighted L1 → 完全塌缩
最直觉的做法:L1 损失,正样本加权(w_pos=10)。结果:ρ 比值(mean(预测)/mean(GT))= 0.00005——模型干脆全部预测零。训练曲线显示 epoch 1 时比值还有 0.76,然后单调塌缩到零。
诊断(这就是"先诊断"的示范):L1 损失的数学性质是最优解收敛到条件中位数。99.3% 是零的分布,中位数就是 0——损失函数本身在奖励塌缩,给正样本加 10 倍权重也拧不过这个数学事实。而且 mini 集上同口径比值 0.039,和全量一个量级——说明与数据量无关,是损失函数的机制问题。这个对照实验排除了"数据不够"这个错误假设,改动才有依据。
第二轮:CenterNet focal heatmap loss → 延迟塌缩,但仍塌缩
换成检测领域的 focal loss(α=2, β=4),过拟合试金石上很漂亮:200 步内强度维持 0.27~0.83(旧 L1 只有 0.00005)。但全量训练还是塌了:比值 0.410(ep1)→ 0.279(ep2)→ 0.033(best@ep29),塌掉 92%。
更深的根因有两个:
- 全零预测时,99.3% 负样本的 focal 损失天然为 0,模型只承受 0.7% 正样本的有限惩罚——塌缩代价太小;
- 真正的主因是多任务损失失衡:epoch 29 的损失分解显示 velocity 损失 0.00775 占 99.6%,rho 损失占比 0%——velocity 的量级比 rho 大 775 倍。优化器全身心在修方向场,ρ 被彻底无视。
这条教训后来写进了项目方法论:多任务学习,先查损失量级分解,再谈调参。以及:L1 在 ŷ=0 处梯度恒定(立即塌缩),focal 在 ŷ→0 时梯度大(多撑了 1~2 个 epoch 再塌)——任何"点估计"损失(L1/L2/focal)都会被 99.3% 精确零的分布主导,换汤不换药。
第三轮:Distribution Matching Loss(最终方案)→ 通过验收
思路彻底转变:既然逐点监督会被稀疏分布淹没,那就不在逐点层面纠结,改为直接约束分布的统计量:
moment_loss = |mean(pred) − mean(gt)| + |std(pred) − std(gt)| ← 保总量,防塌缩
sparse_loss = focal(pred[gt>0.1], gt[gt>0.1]) ← 只在高强度区保精度- 矩匹配(moment):不管你每个点预测成什么样,整体均值和标准差必须对齐 GT——想全预测零?均值直接不合格。塌缩路径被数学上封死。
- 稀疏焦点(sparse):只在
GT > 0.1的高强度体素上算 focal,把负样本海彻底隔离在外。
调权也走了一轮:v1(λ_moment=10)完全防住了塌缩但比值稳定 3.0——过度放大 ρ 还压制了 velocity(多任务跷跷板);v2 定版 λ_moment=2.0, λ_sparse=5.0:ρ 比值回落进验收带 [0.5, 1.5],velocity cosine 恢复 ≥0.95。
这轮攻关沉淀的方法论(都进了经验教训清单):
- 极端稀疏连续场不能用点估计损失,分布匹配+稀疏焦点是出路;
- 过拟合 200 步是损失改动的试金石——强度能不能追平 GT,200 步内见分晓,不必等全量训练几天;
- 先 mini vs 全量对照排除数据量因素,再改损失。
训练流程与工程保障
- 模式设计:smoke(冒烟,确认管线通)→ overfit(200 步过拟合小样本,试金石)→ train(全量)→ validate(验收)。这个"先冒烟、先试金石、再全量"的流程后来贯穿所有阶段;
- NaN 防护:训练中带 NaN 哨兵,
skipped_steps=0是硬验收项(有 NaN 的 batch 会被跳过并计数,计数必须为零——不允许"悄悄丢数据"); - AMP 混合精度 + 断点恢复;
- 全量规模:train 4419 序列,最终 best@epoch27。
验收与冻结
验收闸门(全部满足才准冻结):
| 闸门 | 结果 |
|---|---|
| 占用 IoU | 0.9999 |
| velocity cosine(方向场对齐度) | 0.983(闸 ≥0.95) |
| ρ 强度比 mean(pred)/mean(gt) | 落入 [0.5, 1.5] |
| skipped_steps | 0 |
| 可视化三标准 | 人工确认通过 |
| 单元测试 | 19 项全过 |
通过后执行冻结仪式:权重另存为 frozen_best.pt,同时生成 frozen_manifest.json 登记验收数字——这份 manifest 是"冻结凭证",下游任何阶段接入先验时先读它。从这一刻起,阶段①的权重只许读、不许改。
事后暴露的缺陷:背景平台(诚实记录,很重要)
这是项目里非常经典的一个"验收盲区"案例,后来阶段③才被揪出来:
- 阶段①的 ρ 强度比闸门只在 GT mask 内部统计(
train.py:202),背景区域(99.3% 的零区)从未被任何损失和闸门约束; - 结果:ρ 头在背景区自由收敛到一个全局常数平台 ≈ 0.25(sigmoid 后的值),而 GT 背景是精确零。当时验收全绿,没人看得见它;
- 更扎心的是:当初"达标"的 in-mask 强度比,扣掉平台垫高后只有 0.52~0.58——验收数字有水分;
- 阶段③第一次真实消费先验输出时,这个平台把下游条件场灌饱和(H′ 覆盖率 ~100% vs 应有的 45.7%),问题才暴露;
- 修复:用
background_mask_balanced损失做了 pilot 重训(全局背景 BCE + hard negative 挖掘 + 矩匹配),3 个 epoch 把背景残余从 0.25 压到 0.063,再配 C3 校准和 velocity 门控打通下游链路。正式的"阶段①-v2 重训"被降级为可选质量优化(pilot 已验证路线有效)。
这条教训的普适版本(已入清单):mask 内部评估对全局背景是失明的;场量验收必须同时闸背景(全局均值/精确零占比);各阶段自证合格 ≠ 下游消费合格——冻结组件的"够用"是相对于消费方式的。
阶段①小结
阶段① = 用 Grid U-Net 从 10 帧占用历史预测当前帧的 H = {o, ρ, v};工程上是普通的多任务稠密预测,真正的硬仗是 99.3% 零分布下的 ρ 塌缩——三轮攻关(L1 → focal → 分布匹配)确立了"先诊断机制、再动损失、200 步试金石定生死"的项目方法论;最终以 velocity cosine 0.983、ρ 比值入带的成绩冻结为 frozen_best.pt,成为后续所有阶段赖以站立的地基。而背景平台缺陷的迟到暴露,则给"验收闸门怎么设计"上了最贵的一课。