looyifan / ResAD: Normalized Residual Trajectory Modeling for End-to-End Autonomous Driving

Created Tue, 17 Mar 2026 00:00:00 +0000 Modified Fri, 15 May 2026 14:43:15 +0800

核心贡献: 提出了一种基于物理先验的残差轨迹建模方法,通过归一化技术解决了端到端自动驾驶中的训练难题


一、核心动机:为什么要用残差?

1.1 现有方法的问题

目前的端到端自动驾驶模型(E2EAD)大多试图回答同一个问题:"未来的轨迹是什么?" 它们直接从传感器数据预测车辆未来几秒钟的绝对坐标点 $(x, y)$。

作者指出这样做有两个大坑:

痛点一:虚假关联(Spurious Correlations) 就像考试死记硬背答案。比如模型看到前车刹车灯亮了,它学会了刹车,但它可能没理解是因为前面是红灯。如果只学绝对坐标,数据太复杂,模型容易"偷懒"学到错误的因果关系。

痛点二:规划视野困境(Planning Horizon Dilemma) 预测未来很远的地方(比如4秒后)是非常难的,误差会很大。这导致模型在训练时,为了降低远处的巨大误差,反而忽略了近处(0.5秒后)那些对安全至关重要的小调整。

1.2 ResAD 的核心思想:换个问法

ResAD 不再问"未来在哪里",而是问:"为什么要改变轨迹?"

形象理解:想象你在高速公路上开车。如果你什么都不做(不踩油门不打方向),车会顺着惯性继续滑行。这叫"物理先验"。

ResAD 的做法

  • 先计算出这个"惯性路径"
  • 然后只学习"由于路况(红灯、变道、避让)你需要做的修正量(Residual)"

好处:把很难的"画全图"任务,变成了简单的"找不同"任务。


二、方法论核心:三大关键模块

2.1 归一化残差轨迹建模 (Normalized Residual Trajectory Modeling)

这是整个算法的灵魂,分为三个步骤:

第一步:计算惯性参考 (Inertial Reference)

假设车辆保持当前的速度 $v_0$ 和方向不变,未来的位置在哪里?

$$ \mathbf{p}_{t_i} = \mathbf{p}_0 + \mathbf{v}_0 \cdot \Delta t_i $$
  • $\mathbf{p}_{t_i}$:未来 $t_i$ 时刻的预测位置
  • $\mathbf{p}_0$:当前位置
  • $\mathbf{v}_0$:当前速度向量
  • $\Delta t_i$:时间差

解读:这是初中物理公式 $S = Vt$。这一步不需要神经网络,纯物理计算,非常稳。

时间差的具体设定

  • 数据采样频率:2 Hz(每秒钟记录/预测两次数据)
  • 时间间隔 ($\Delta t$):0.5秒
  • 总预测步数 ($T_f$):8个时间步
  • 总预测时长:8步 × 0.5秒/步 = 4.0秒

具体运算中,时间差 $\Delta t_i$ 依次代入:

  • 第1个点(0.5秒后): $\Delta t_1 = 0.5$ 秒
  • 第2个点(1.0秒后): $\Delta t_2 = 1.0$ 秒
  • …以此类推…
  • 第8个点(4.0秒后): $\Delta t_8 = 4.0$ 秒

第二步:计算残差 (Residual)

神经网络只预测真实轨迹 $\tau_{gt}$ 和惯性轨迹 $\tau_{ref}$ 之间的差值:

$$ \boldsymbol{r} = \tau_{gt} - \tau_{ref} $$

解读:$\boldsymbol{r}$ 代表了人类驾驶员的"操作意图"(比如为了避让行人向左打方向)。模型只需要学这个意图,而不是学整个坐标。

第三步:逐点残差归一化 (Point-wise Residual Normalization, PRNorm)

问题:远处的坐标偏差通常很大(比如几米),近处的偏差很小(几厘米)。如果直接训练,模型会只关注远处的大数字,忽略近处的安全细节。

解决:把每个时间步的数值都缩放到同一个范围(比如 -1 到 1):

$$ \tilde{r}^d_t = 2\gamma \left( \frac{r^d_t - r^d_{\min}}{r^d_{\max} - r^d_{\min} + \epsilon_0} \right) - \gamma $$
  • $r^d_{\min}, r^d_{\max}$:该时间步在整个数据集里的最大最小值
  • $\gamma$:缩放系数(比如设为1)

解读:这是一个标准的 Min-Max 归一化,但关键在于它是**Point-wise(逐点)**的。也就是第1秒的数据只和第1秒的比,第4秒的和第4秒的比。这样,近处的小偏差在归一化后,权重就和远处一样大了,模型就不会忽略近处的安全了。


2.2 惯性参考微扰 (Inertial Reference Perturbation, IRP)

自动驾驶需要考虑多种可能性(Multimodal),比如前面有车,我可以左变道,也可以右变道。

做法:作者不在最终结果上加噪声,而是在初始速度上加噪声:

$$ \delta_{v,k} \sim \mathcal{N}(\mathbf{0}, \boldsymbol{\Sigma}) $$

$$ \mathbf{v}'_{0,k} = \mathbf{v}_0 + \delta_{v,k} $$
  • $\delta_{v,k}$:随机生成的微小速度/方向变化
  • $\mathbf{v}'_{0,k}$:扰动后的新速度

效果:通过改变初始速度,生成一堆略有不同的"惯性轨迹簇"。就像奇异博士看了未来的几种可能性,有的快一点,有的偏左一点。这迫使模型去学习不同初速度下的应对策略,增加了鲁棒性。


2.3 扩散解码器与打分 (Diffusion Decoder & Ranker)

生成轨迹

使用 Diffusion Model(扩散模型,类似于生成图片的 Stable Diffusion):

  • 输入:图像特征 + 扰动后的惯性参考 + 噪声
  • 输出:去噪后的归一化残差
  • 最终轨迹 = 惯性参考 + 去归一化后的残差

打分器 (Ranker)

模型生成了 $K$ 条可能的轨迹,哪条最好?

作者训练了一个 Transformer 来给每条轨迹打分(考虑安全性、舒适度等),选出分数最高的那条作为最终决策。


三、深入讨论

3.1 惯性参考为什么是直线?

基于公式 $\mathbf{p}_{t_i} = \mathbf{p}_0 + \mathbf{v}_0 \cdot \Delta t_i$ 算出来的这8个点,连起来确实是一条直线

这其实是对**牛顿第一定律(惯性定律)**的最直接应用:假设驾驶员在这一刻"双手离开方向盘,双脚离开踏板",车辆会保持当前的速度和方向,顺着切线方向一直往前滑行。

既然真实道路是弯的,一条直线怎么开车呢?

这恰恰是作者最聪明的设计。作者的逻辑是:“直线部分不需要 AI 学,物理定律已经帮你画好了;AI 只需要学怎么把这条直线’掰弯’。”

  • 直线(惯性参考):车辆的本能(顺着当前方向冲)
  • 残差(神经网络预测的偏移量):驾驶员打方向盘和踩踏板的动作
  • 最终轨迹 = 直线 + 残差

具体是怎么"掰弯"的?

  • 遇到弯道(打方向盘):如果前面是个左转弯,AI 就会在第1到第8个点上,预测出越来越大的横向残差(向左的偏移量)。把原本直线上点,一点点往左边"拉",连起来就成了一条完美的左转曲线。
  • 遇到红灯(踩刹车):此时方向不变(不需要横向偏移),但速度要减慢。AI 就会预测出纵向的负残差(向后的偏移量)。这样原本在直线远处的点,就会被"拉"回近处,表示车辆在4秒内走不了那么远,停下来了。

进阶理解:一把"扇形的直线"

在复杂的路口,只给AI一条直线的参考,它可能很难思考对策。所以,作者在初始速度 $\mathbf{v}_0$ 上加了随机噪声(微小扰动),生成了比如 10 个不同的初始速度。

这样一来,这 10 个速度就会画出 10 条指向不同角度的直线,看起来就像一把打开的折扇

  • 有的射线偏左,有的偏右,有的长(速度快),有的短(速度慢)
  • AI 看到这把"折扇",就会针对每一根"扇骨"(射线),预测出对应的残差,把它们分别"掰"成左转、直行、避障等多种可能的曲线轨迹
  • 最后,Ranker(打分器)出场,选出最安全、最平滑的那条曲线作为最终决定

3.2 扩散模型中的噪声是什么?

在算法的世界里,这里的"噪声"不是我们平时听到的噪音,它更像是一块**“未被雕琢的原材料”**。

形象理解:从"迷雾"中看清真相

想象你在清晨的大雾中开车,由于雾太大,你看不清前方的路,只能看到一片混沌(这就是噪声)。

  • 噪声(Noise):就是这团笼罩在未来轨迹上的"浓雾"
  • 图像特征 + 惯性参考:这是你脑子里的"地图"和"指南针"
  • 扩散解码器的任务:就是根据"地图"和"指南针",把这团"浓雾"一点点拨开,最后露出隐藏在雾里的、最合理的残差轨迹

技术细节:它到底长什么样?

在 ResAD 论文中,这个噪声在数学上被称为 高斯白噪声(Gaussian Noise)

  • 数学表达:$z \sim \mathcal{N}(0, \mathbf{I})$,意思是从一个均值为0、方差为1的标准正态分布中随机抽取出来的数值
  • 它的形状:和我们要预测的轨迹是一模一样的(8个时间步 × 2个坐标 = 16个数字组成的矩阵)
  • 起始状态:在生成的最开始(第 $T$ 步),模型手里只有这 16 个随机生成的乱码数字

为什么需要这个噪声?

A. 实现"多样性"(Multi-modality)

自动驾驶面对同一个路口,可能有多种走法(稍微偏左一点,或者稍微偏右一点)。

  • 如果模型是死板的回归,它可能只会给出一个"平均值",导致轨迹正好撞在路沿上
  • 有了噪声,每次我们换一个不同的噪声"种子"(Seed),扩散解码器就能从不同的角度"拨开迷雾",生成不同的合理轨迹

B. 逆向建模的需要

扩散模型的工作原理是**“去噪”**:

  • 训练时:我们把真实的轨迹残差(正确的答案)里慢慢加入噪声,直到它变成一团乱码
  • 推理时:我们给模型一团乱码,并告诉它当前的环境特征。模型会根据经验,一步步猜出:“这团乱码如果变回正确的轨迹,第一步该怎么减掉噪声?”

C. 提高容错率

直接预测一个精确的坐标点很难,但如果让模型去**“修正”**一个带有噪声的数值,它会有更多的容错空间。通过多次微调(去噪迭代),轨迹会变得越来越平滑、自然。


3.3 为什么不从惯性参考开始直接修正?

这是一个很自然的直觉:既然有了"惯性参考"这个物理先验,为什么不把它当做"初稿"直接修,非要从"随机噪声"开始白手起家?

这涉及到了 “隐变量生成”“确定性细化(Refinement)” 在数学建模上的本质差异。

原因一:“残差空间"已经非常"扁平"了

地平线 DiffusionDrive 等方案,很多时候是在绝对坐标空间(Trajectory Space)进行打磨。因为绝对坐标范围很大(可能几十米),直接从噪声生成很难,所以用 Anchor(锚点路径)作为起始位置可以大幅缩短搜索路径。

但请注意 ResAD 的神来之笔——PRNorm(归一化)

  • ResAD 预测的不是坐标,而是归一化后的残差 $\tilde{r}$
  • 通过 PRNorm,所有的预测目标都被强行压缩到了 $[-1, 1]$ 这个极小的、以 0 为中心的超立方体空间里
  • 标准高斯噪声的分布范围主要也在 $[-1, 1]$ 附近

这意味着:在 ResAD 的残差空间里,“随机噪声"和"最终答案"之间的距离,已经比绝对坐标空间近得多了!

原因二:解决"多模态"的数学严谨性

如果从惯性参考开始修(确定性细化): 这本质上是一个函数映射:$f(\text{惯性参考}, \text{环境特征}) = \text{修正量}$。对于同一个路口,这个映射往往只能给出一个确定的结果。

从噪声开始生成(生成式建模): 扩散模型是在学习整个概率分布 $P(\text{残差} | \text{环境, 惯性})$。从噪声 $z$ 开始,是因为 $z$ 是概率分布的"采样源”。噪声不是负担,而是"可能性"的源泉。

原因三:惯性参考是作为"条件约束"存在的

在 ResAD 的 Transformer Decoder 里:

  • 惯性参考(Inertial Reference)是作为"Condition(条件约束)“输入的
  • 这就像是一个经验丰富的老师站在 AI 旁边。AI 手里拿着一块乱糟糟的泥巴(噪声),老师不断告诉它:“根据现在的惯性和前面的红灯,你应该把泥巴捏成一个向后拉伸的形状。”

3.4 两次多模态:IRP 与 扩散噪声的关系

确实引入了两次"随机性”,但它们是**“物理层面的多模态”“决策层面的多模态”**的强强联手。

初始速度扰动 (IRP):解决"身体"的不确定性

  • 它在干什么? 模拟车辆状态观测的误差
  • 它的角色(物理先验):为模型提供多个**“赛道(Anchor Paths)”**
    • 扰动 A:假设我现在的速度比传感器测得的快一点,惯性参考线就长一点
    • 扰动 B:假设我现在的方向盘其实已经往左带了一点,惯性参考线就偏左一点
  • 总结:IRP 是在物理空间里撒网,它决定了 AI “起跑"的基础姿态

扩散随机噪声:解决"灵魂"的多模态(决策意图)

  • 它在干什么? 模拟面对复杂环境时的选择权
  • 它的角色(意图生成)
    • 在同一个惯性参考线上,噪声种子 1 可能让 AI 决定"左侧超车”
    • 噪声种子 2 可能让 AI 决定"减速跟车”
  • 总结:扩散噪声是在决策空间里探索,它决定了 AI 在既定物理基础上如何"起舞"

为什么要"双管齐下"?

方案 A:只有 IRP(没有扩散噪声): 你会有 10 条不同的射线,但每条射线上,AI 只能给出一个死板的修正。如果这 10 条射线都没能完美避开前方的障碍物,AI 就没招了。这叫**“物理多样,决策单一”**。

方案 B:只有扩散噪声(没有 IRP): 你只有 1 条笔直的参考线。虽然扩散模型可以把它变幻出无数花样,但由于起点太单一,模型需要耗费巨大的计算量去把这条直线"大幅度扭转"到侧面。这叫**“起点单一,决策费劲”**。

ResAD 的"黄金组合"逻辑

  1. 先用 IRP 铺路:用简单的物理扰动,快速占领未来的各种"物理阵地"
  2. 再用扩散模型雕琢:在每一个物理阵地上,利用随机噪声让 AI 灵活地思考"在这个位置我该怎么微调"

3.5 WTA Loss vs Diffusion:多模态策略对比

Winner-Take-All (WTA) 策略确实是目前解决"平均陷阱"最主流、最有效的手段之一。

什么是 WTA?(形象理解:赛马机制)

想象你带了 6 个徒弟(6 个预测头),让他们预测车该怎么走:

  • 训练时:如果真实的司机右转了。你发现徒弟 C 猜得最准。这时候,你只奖励/惩罚徒弟 C,让他以后更像真实司机。而另外 5 个徒弟这次的任务就是"闭嘴",不做任何更新
  • 结果:久而久之,徒弟 A 成了"左转专家",徒弟 B 成了"直行专家",徒弟 C 成了"避让专家"

为什么还要 Diffusion?

A. 离散 vs. 连续(菜单与厨师)

  • WTA 是"点菜制":你预先设定了 $K$ 个头。无论路况多复杂,你永远只能从这 6 个选项里挑。如果路况需要一个"微小的左转再加速",但你的 6 个头里只有"大左转"和"匀速直行",你就调不出来
  • Diffusion 是"大厨现做":只要噪声种子变一点,轨迹就变一点。它可以生成无数种、连续分布的轨迹

B. “死掉的头"问题(Mode Collapse)

  • WTA 的噩梦:如果某一个预测头运气不好,从来没被选中过,它就永远得不到训练梯度。最终,你虽然准备了 6 个头,但可能只有 3 个在干活,剩下的全成了"废头”
  • Diffusion:每一轮去噪过程,网络的所有参数都在参与计算。不存在"死掉的头"

C. “打磨"的深度

  • WTA 是"一锤子买卖”:神经网络一次性输出 6 条线
  • Diffusion 是"精雕细琢":它是迭代的。每一步去噪,模型都会结合环境特征微调轨迹。这种多步细化的能力,让 Diffusion 出来的轨迹在运动学上通常比 WTA 直接回归出来的要好得多

ResAD 的"神操作":把两者结合了

  1. Diffusion(负责生成):用噪声生成 64 条形状各异的轨迹
  2. Ranker(负责挑选):这个 Ranker 实际上就是在做类似 WTA 的筛选

所以,ResAD 的逻辑是

  • 不靠 WTA 来"产生"多模态(因为 WTA 产生的模态有限且容易坏死)
  • 靠 Diffusion “大规模制造"高质量的多模态候选项
  • 靠 Ranker(类似选秀)来做最终决策

四、实验结果

作者在 NAVSIM 数据集(目前最权威的端到端评测基准之一)上进行了测试。

4.1 核心指标

  • PDMS (PDM Score):综合分数,越高越好。包含不碰撞率(NC)、舒适度(C)等
  • EPDMS (Extended PDMS):NAVSIM v2 提出的更难的指标

4.2 战绩

  • SOTA:ResAD 在 NAVSIM v1 和 v2 上都拿到了第一梯队的成绩(PDMS 88.8 / 90.6)
  • 对比:比之前的明星模型 Transfuser、UniAD、DiffusionDrive 都要好
  • 效率:推理速度非常快,只需要 2 步去噪就能达到很好的效果

4.3 消融实验

  1. 即插即用:把"归一化残差建模(NRTM)“套用到旧模型上,旧模型效果也立刻提升
  2. 各组件的贡献
    • 只加 Ranker:提升一点点
    • 加 轨迹残差建模 (TRM):提升巨大!
    • 加 归一化 (PRNorm):收敛更快,近距离避障更准
    • 加 微扰 (IRP):解决多模态问题,提升复杂场景处理能力

五、伪代码详解

import torch
import torch.nn as nn

class ResAD(nn.Module):
    def __init__(self):
        super().__init__()
        # 1. 特征提取器 (比如 Vision Transformer),负责看路
        self.backbone = VisualBackbone()
        # 2. 扩散解码器,负责从噪声中"捏"出残差
        self.diffusion_decoder = ResidualDiffusionDecoder()
        # 3. 轨迹打分器,负责从一堆方案里选最好的
        self.ranker = TrajectoryRanker()

    def forward(self, sensor_data, ego_state):
        """
        sensor_data: 摄像头图像、雷达数据等
        ego_state:   当前车的状态 (位置 p0, 速度 v0, 朝向 theta)
        """

        # --- 第一阶段:感知与特征提取 ---
        # 提取环境特征(路口长啥样、红绿灯、其他车在哪里)
        env_features = self.backbone(sensor_data)

        # --- 第二阶段:物理先验(画"骨架") ---
        # 1. 惯性参考扰动 (IRP):生成 K 条略有不同的"初试射线条"
        # 我们不只画一条直线,我们给初始速度加点噪声,生成一把"扇形"射线
        ref_trajectories = self.generate_perturbed_refs(ego_state, num_samples=64)
        # ref_trajectories 形状: [64条, 8个时间步, (x, y)]

        # --- 第三阶段:生成式决策 (核心魔术) ---
        # 2. 准备初始"迷雾" (随机噪声)
        # 注意:这里的噪声是在"残差空间"里的,范围大概在 -1 到 1 之间
        # 形状与轨迹一致: [64, 8, 2]
        residual_noise = torch.randn(64, 8, 2)

        # 3. 扩散去噪循环 (Iterative Denoising)
        # ResAD 的优势在于只需要 2-5 步
        current_residual = residual_noise
        for t in reversed(range(num_steps)):
            # 这里的 diffusion_decoder 就像一个雕塑家
            # 输入:当前的乱码、环境特征、那一叠"物理射线"
            # 输出:预测的更清晰一点的"归一化残差"
            current_residual = self.diffusion_decoder(
                current_residual,
                env_features,
                ref_trajectories,
                t
            )

        # --- 第四阶段:物理还原 (从残差回到世界) ---
        # 4. 去归一化 (Inverse PRNorm)
        # 把预测出的 [-1, 1] 之间的数值,放大回真实的米(m)
        real_residuals = self.inverse_prnorm(current_residual)

        # 5. 最终合成轨迹 = 物理骨架 + AI 预测的偏差量
        # 这一步把直线"掰弯"成真正的避障/转弯曲线
        final_candidate_trajectories = ref_trajectories + real_residuals

        # --- 第五阶段:选秀 (Ranking) ---
        # 6. 打分器给这 64 条备选曲线打分
        # 考虑:是否撞车、是否越界、是否让乘客觉得晕车
        scores = self.ranker(final_candidate_trajectories, env_features)

        # 7. 选分数最高的一条发给方向盘和油门
        best_trajectory = final_candidate_trajectories[torch.argmax(scores)]

        return best_trajectory

    def generate_perturbed_refs(self, ego_state, num_samples):
        """
        计算惯性参考轨迹:p_t = p0 + (v0 + delta_v) * t
        """
        dt = 0.5  # 时间间隔 0.5 秒
        time_steps = torch.tensor([0.5, 1.0, 1.5, 2.0, 2.5, 3.0, 3.5, 4.0])

        # 给速度加扰动 (IRP)
        v0 = ego_state.v0
        # 产生一些微小的速度方向和大小的变化
        perturbed_v = v0 + torch.randn(num_samples, 2) * 0.1

        # 计算直线点:[num_samples, 8, 2]
        refs = v0.pos + perturbed_v.unsqueeze(1) * time_steps.unsqueeze(1)
        return refs

    def inverse_prnorm(self, normalized_residual):
        """
        逐点残差去归一化 (PRNorm 的逆操作)
        normalized_residual: 模型预测出来的 [-1, 1] 之间的数
        """
        # 从预先统计好的数据集分布里拿到每个时间步的最大最小值
        # 越远的点,min/max 范围越大
        res_min, res_max = self.dataset_stats.get_min_max()

        # 简单的线性拉伸:从 [-1, 1] 映射回 [min, max]
        real_res = (normalized_residual + 1) / 2 * (res_max - res_min) + res_min
        return real_res

六、总结

这篇论文解决了什么核心问题?

解决了端到端自动驾驶中,直接预测绝对坐标带来的训练难、远近权重不平衡、容易学坏的问题。

核心公式背后的逻辑

$$ \text{最终轨迹} = \underbrace{(\text{当前位置} + \text{速度} \times \text{时间})}_{\text{物理惯性,本来就会走的路}} + \underbrace{\text{神经网络预测的残差}}_{\text{AI根据路况做的智能微调}} $$

为什么叫 “Normalized” (归一化)?

因为远处偏差大,近处偏差小,不归一化会导致 AI 忽视近处更危险的偏差。

ResAD 的优势一句话总结

利用物理学定律(惯性)作为基准,让 AI 只需专注于学习"变化量”,并用归一化技术平衡远近视野,从而学得更快、更稳、更准。


相关论文

  • [[UAD - 无需3D标注的端到端自动驾驶]]
  • [[World4Drive - 无需感知标注的端到端世界模型]]
  • [[LAW - Latent World Model for E2E Driving]]