news 2026/10/23 6:38:16

自动驾驶:扩散模型作为规划器

作者头像

张小明

前端开发工程师

1.2k 24
文章封面图
自动驾驶:扩散模型作为规划器

端到端范式下,自动驾驶规划器几乎无一例外地被训练为"回归":输入场景特征,输出一条轨迹,损失函数是均方误差(MSE)。本文论证这一范式的结构性缺陷——在多模态驾驶场景中,MSE 的最优解是各模态的条件期望,而条件期望往往不在任何一条合理轨迹上。同一场景既可绕左也可绕右时,回归输出的均值轨迹恰好穿过正中间的障碍物。本文的演示复现了这一失败:回归规划器的最小间距为 0.00 m(碰撞),而一个仅 2.6 万参数、训练约 40 秒的扩散模型,通过"分布采样 + 代价打分",最小间距 2.58 m 且覆盖两个模态。

本文的核心论点有三:其一,规划的多模态性不是边缘情况,而是驾驶决策的常态;其二,扩散模型的价值不在于"生成得逼真",而在于把"输出一条轨迹"改为"从轨迹分布中采样",从而把模式覆盖与模式选择解耦为两个各自可控的阶段;其三,场景条件有三种注入方式(训练条件化、采样时引导、采样后重排),小模型场景下第三种最稳健。

扩散规划器是规划问题表述方式的更换;它的真实代价是多步去噪的算力,而蒸馏与截断式架构正在快速摊薄这一代价。

1.规划不是回归

过去数年,规划模块的神经网络化沿一条清晰的路线推进:从模仿学习直接回归轨迹,到端到端架构中规划头输出轨迹序列。形式上,它们共享同一个表述——给定场景,预测轨迹,训练目标为最小化。这个表述隐含了一个假设:给定场景,存在唯一"正确"的轨迹,数据中的其他轨迹只是围绕它的噪声。

驾驶场景不满足这个假设。前方慢车,绕左与绕右同样合理;四路口相遇,让与抢各有其理;同一条走廊,不同司机会开出不同的走法。专家数据天然是多模态分布——而多模态分布恰恰是回归的难题。

这一困境并非新发现。2019 年 Waymo 的 MultiPath 就放弃了直接回归,改用"锚轨迹分类 + 残差回归"来保留多模态[5];此后多假设、多头预测等工程变体层出不穷。但这些都是修补:模型的输出形式仍是"若干条固定候选",模态数量与位置由人工先验决定。扩散模型提供的是另一条路:不预设模态,直接建模轨迹的完整分布,从中采样。

2.均值坍缩

MSE 损失下,最优预测器是条件期望:

(对关于求导即得。)

若是双峰分布——例如 50% 的专家绕左(m)、50% 绕右(m)——则条件期望为:。两条合理轨迹的均值,是一条谁都不会走、且正好撞上障碍物的轨迹。这与分类任务不用 MSE 回归类别标签是同一个道理,只是在轨迹空间中,均值坍缩的后果不是"分类错误",而是物理碰撞。

3.扩散模型:把规划改成采样

扩散模型分两个过程[1]:

前向加噪(固定,不学习):对真实轨迹逐步添加高斯噪声,,经步后近似纯高斯。记,可一步采样:。

反向去噪(学习):训练网络预测加入的噪声,损失为

采样时从出发,按 ancestral 公式迭代:

去噪采样的模式选择由采样路径中的随机项决定:不同的初始噪声把轨迹"拉"向不同的模态盆地,去噪过程只是把样本搬回数据流形。因此次采样天然得到条分属不同模态的轨迹——多模态覆盖不需要任何专门设计,它是迭代去噪的固有性质。相比之下,回归网络的前向是确定性的,一次前向只能得到一个点;要多个候选,只能靠人工多假设机制。

对规划而言,这个性质改变的不是"输出质量",而是问题结构:生成(覆盖所有合理走法)与决策(选定一种走法)被拆成两个独立阶段,各自可用最合适的工具——前者用生成模型,后者用代价函数。

4.条件注入的三种方法

扩散规划器绕不开的问题:场景条件(障碍在哪、导航去哪)如何进入采样?文献中形成三条路线:

路线一:训练条件化。条件与噪声轨迹一并输入去噪网络[2][3],配合 classifier-free guidance 放大条件影响。表达能力强,但对模型容量与训练量有实质要求。

路线二:采样时引导。训练无条件模型,采样过程中沿代价函数的负梯度修正去噪方向[2][8](CTG++ 用此机制实现可控交通流生成)。灵活:换一个代价函数即换一种"条件",无需重训;但每步去噪需额外一次梯度反传。

路线三:采样后重排(best-of-N)。采样 $N$ 条候选,用代价函数(碰撞、间距、平顺、进度)打分取最优。最简单、最稳健,也是量产管线中最常见的形态——扩散头负责"给出多样的合理候选",安全与偏好判断交给显式打分器。

三条路线不是互斥的,量产系统中常见组合:粗粒度条件走训练,细粒度偏好走引导或重排。

5.代码演示

5.1场景与设置

自车沿轴以 10 m/s 行驶,前方 25 m 同车道有一辆慢车,左右相邻车道(车道宽 3.6 m)均可绕行。轨迹为 20 个路点(步长 0.2 s,时域约 4 s),用横向序列表示(纵向步长固定为 2 m)。专家数据 4096 条:50% 绕左、50% 绕右的 S 形变道,叠加 0.12 m 噪声——对称双模态,回归的最坏情况。

两个被试模型均为三层 MLP、规模相当(参数量约 1.9 万与 2.6 万):回归规划器以障碍状态为条件直接输出序列;扩散规划器为无条件 DDPM($T=200$ 步,线性噪声表)——场景条件不进入网络,而经代价打分注入,训练 8000 步、采样 200 步。

5.2结果

指标回归规划器扩散规划器
障碍居中:输出形态单条轨迹(条件期望)16 条采样,左 11 / 右 5
障碍居中:最小间距0.00 m(碰撞)选定轨迹 2.58 m
障碍左偏 1.2 m:Top-3 模态—(未评测)右 / 左 / 右(打分改选宽侧)
障碍左偏 1.2 m:选定间距—3.58 m
单次推理耗时<1 ms(一次前向)120 ms(200 步去噪)

(a) 专家数据的双模态分布;

(b) 回归输出平直穿过障碍,红色标记处最小间距 0.00 m——注意其训练是"成功"的;

(c) 扩散 16 次采样覆盖两个模态,绿色为打分选定;

(d) 障碍左偏 1.2 m 后,同一个模型、重新采样的 16 条候选(左 13 / 右 3),代价打分把选择翻转到间距更宽的绕右侧。

64 条轨迹从纯高斯噪声逐步组织成双模态云,直观展示"模式在去噪中分化"。

本文在实现过程中对"训练条件化"路线做了完整尝试,其失败模式有普遍参考价值:

1. 首先验证核心机制:无条件 DDPM 在标量双模态分布上左占比 0.52、在 20 维轨迹分布上 0.55(64 采样直方图双峰清晰)

2. 加入条件后,2.6 万参数的去噪网络始终欠拟合条件分布:以障碍横偏为自变量对采样做五点扫描,专家规则下左占比应从 0.9(障碍偏右,绕左让行)单调过渡到 0.1(障碍偏左)。四种条件化变体(加宽条件特征、FiLM 调制、余弦噪声表 + classifier-free guidance、线性噪声表 + classifier-free guidance)逐一尝试:加宽特征版完全平坦(左占比恒 0.22),FiLM 无改善,余弦表 + guidance 版倒向单侧(左占比全为 0),最好的线性表 + guidance 版方向单调正确、幅度严重不足——障碍居中(专家左右各半)时左占比仅 0.12,障碍偏右(应 0.9)时仅 0.36。小网络的容量被均匀时间步下的高噪声步主导,条件信号(集中在中等噪声区间)学不动;

3. classifier-free guidance 在条件欠拟合时放大的是残余偏差而非条件差,余弦噪声表则在序列末端引入数值放大步,二者叠加反而更差。均告失败。

这组阴性结果指向:条件扩散的表达能力红利,要大到一定模型规模才能兑现;小模型 + 小数据场景下,"无条件生成 + 采样后重排"是更稳定的起点。

5.3代码

# -*- coding: utf-8 -*- """ 扩散模型当规划器 | 演示:回归规划的"均值坍缩" vs 扩散采样的"模式覆盖" ========================================================================== 场景:自车沿 +x 行驶,前方同车道有慢车(障碍),左右相邻车道均可绕行。 专家数据为对称双模态分布(50% 绕左 / 50% 绕右)——这正是回归规划器的 最坏情况:多模态分布的条件期望不在数据流形上。 对照: 1) 回归规划器:MLP 直接回归 y 序列,MSE 最优解 = 条件期望 → 穿过障碍; 2) 扩散规划器:DDPM 学习轨迹分布 → 采样覆盖两个模态 → 代价打分选优。 场景条件(障碍位置)不经训练注入,而是通过代价函数在采样端生效 ========================================================================== """ import os import time import numpy as np import torch import torch.nn as nn import matplotlib.pyplot as plt import matplotlib.font_manager as fm from matplotlib import animation from matplotlib.patches import Rectangle # ---------- 中文字体 ---------- for _f in ("Microsoft YaHei", "SimHei"): if any(_f == f.name for f in fm.fontManager.ttflist): plt.rcParams["font.sans-serif"] = [_f] break plt.rcParams["axes.unicode_minus"] = False # ---------- 配色 ---------- C_A = "#2a78d6" # 蓝:绕左模态 C_B = "#eb6834" # 橙:绕右模态 C_DIST = "#1baf7a" # 绿:选定轨迹 C_LIMIT = "#d03b3b" # 红:回归失败/碰撞 C_GRID = "#e1e0d9"; C_AXIS = "#c3c2b7"; C_INK = "#0b0b0b"; C_INK2 = "#52514e" C_MUTED = "#898781"; C_ROAD = "#f0efec"; C_EDGE = "#c3c2b7"; C_SURF = "#fcfcfb" # ---------- 场景参数 ---------- LANE_W = 3.6 # 车道宽 [m] Y_L, Y_R = LANE_W, -LANE_W # 相邻车道中心(左 / 右) N_WP = 20 # 轨迹点数 DX_STEP = 2.0 # 每点纵向步长 = v·DT = 10×0.2 [m] X_GRID = np.arange(N_WP) * DX_STEP # 0,2,...,38 m X_SHIFT0, X_SHIFT1 = 6.0, 18.0 # 横向变道起止 [m] OBS_HALF_L, OBS_HALF_W = 2.4, 0.95 # 障碍包围盒半长/半宽 [m](含自车余量) SEED = 42 torch.manual_seed(SEED) rng = np.random.default_rng(SEED) OUT_DIR = os.path.dirname(os.path.abspath(__file__)) # ============================================================ # 1. 专家数据:对称双模态分布 # ============================================================ def smoothstep(x): u = np.clip((x - X_SHIFT0) / (X_SHIFT1 - X_SHIFT0), 0.0, 1.0) return 3 * u ** 2 - 2 * u ** 3 def make_expert(n, rng): """双模态专家轨迹:y = ±3.6 · S 形变道 + 噪声""" shape = smoothstep(X_GRID)[None, :] mode = (rng.uniform(0, 1, n) < 0.5).astype(np.float64) * 2 - 1 # +1 左 / -1 右 traj = mode[:, None] * Y_L * shape + rng.normal(0, 0.12, (n, N_WP)) return traj.astype(np.float32), mode def dmin_to_box(traj, x_obs, y_obs): """轨迹点到障碍 AABB 的最小距离(自车按中心线,余量并入盒宽)""" dx = np.maximum(np.abs(X_GRID[None, :] - x_obs) - OBS_HALF_L, 0.0) dy = np.maximum(np.abs(traj - y_obs) - OBS_HALF_W, 0.0) return np.hypot(dx, dy).min(axis=1) # ============================================================ # 2. 网络 # ============================================================ def time_embed(t, dim=32): half = dim // 2 freqs = torch.exp(torch.linspace(np.log(1e-3), np.log(1e3), half)) ang = t.float()[:, None] * freqs[None, :] return torch.cat([torch.sin(ang), torch.cos(ang)], dim=1) class Regressor(nn.Module): """回归基线:障碍状态 → y 序列(一次前向)""" def __init__(self, cond_dim=2, n_wp=N_WP, hid=128): super().__init__() self.net = nn.Sequential( nn.Linear(cond_dim, hid), nn.SiLU(), nn.Linear(hid, hid), nn.SiLU(), nn.Linear(hid, n_wp)) def forward(self, c): return self.net(c) class Denoiser(nn.Module): """ε 预测网络:输入 [y_t, 时间嵌入] → 噪声 ε""" def __init__(self, n_wp=N_WP, hid=128, tdim=32): super().__init__() self.tdim = tdim self.net = nn.Sequential( nn.Linear(n_wp + tdim, hid), nn.SiLU(), nn.Linear(hid, hid), nn.SiLU(), nn.Linear(hid, n_wp)) def forward(self, y_t, t): return self.net(torch.cat([y_t, time_embed(t, self.tdim)], dim=1)) # ============================================================ # 3. DDPM(前向加噪 / 反向去噪) # ============================================================ class DDPM: def __init__(self, T=200, beta0=1e-4, beta1=0.02): self.T = T beta = torch.linspace(beta0, beta1, T) alpha = 1.0 - beta abar = torch.cumprod(alpha, dim=0) self.beta, self.alpha, self.abar = beta, alpha, abar def add_noise(self, y0, t, eps): a = self.abar[t][:, None] return a.sqrt() * y0 + (1 - a).sqrt() * eps def sample(self, model, K): """ancestral 采样:纯高斯 → 轨迹;返回 [K,N_WP] 与各步快照""" y = torch.randn(K, N_WP) snaps = {} for t in range(self.T - 1, -1, -1): tt = torch.full((K,), t, dtype=torch.long) with torch.no_grad(): eps = model(y, tt) coef = self.beta[t] / (1 - self.abar[t]).sqrt() y = (y - coef * eps) / self.alpha[t].sqrt() if t > 0: y = y + self.beta[t].sqrt() * torch.randn_like(y) snaps[t] = y.clone() return y, snaps def train(model, steps, batch, lr, loss_fn): opt = torch.optim.AdamW(model.parameters(), lr=lr) for _ in range(steps): idx = torch.randint(0, N_DATA, (batch,)) opt.zero_grad() loss = loss_fn(idx) loss.backward() opt.step() return loss.item() # ============================================================ # 4. 代价打分与选定 # ============================================================ def score(traj, dmin_arr): """轨迹代价:硬安全 + 间距偏好 + 平顺性 + 终点回正""" c_clear = 10.0 * np.maximum(1.2 - dmin_arr, 0.0).max() # 硬安全 c_clear += 0.3 * max(0.0, 3.0 - dmin_arr.min()) # 软偏好:间距越宽越好 c_smooth = 0.5 * ((np.diff(traj, 2)) ** 2).sum() c_end = 0.1 * traj[-1] ** 2 return c_clear + c_smooth + c_end def pick_best(samples, x_obs, y_obs): dmins = dmin_to_box(samples, x_obs, y_obs) costs = np.array([score(s, d) for s, d in zip(samples, dmins)]) order = np.argsort(costs) return order, costs, dmins # ============================================================ # 5. 绘图 # ============================================================ def draw_car(ax, x, y, color=C_MUTED, label=None): ax.add_patch(Rectangle((x - OBS_HALF_L, y - OBS_HALF_W), 2 * OBS_HALF_L, 2 * OBS_HALF_W, fc=color, ec=C_EDGE, lw=1.0, zorder=4, label=label)) def style_ax(ax, title, ylab=False): ax.set_facecolor(C_ROAD) ax.axhline(0, color=C_EDGE, lw=1.4, zorder=1) # 本车道中心 for yl in (Y_L, Y_R): ax.axhline(yl, color=C_EDGE, lw=1.0, ls="--", zorder=1) ax.set_xlim(-2, 40) ax.set_ylim(-6.2, 6.2) ax.grid(True, color=C_GRID, lw=0.7) for s in ax.spines.values(): s.set_color(C_AXIS) ax.set_title(title, fontsize=11, color=C_INK2) if ylab: ax.set_ylabel("横向 y [m]", fontsize=10, color=C_INK2) ax.set_xlabel("纵向 x [m]", fontsize=10, color=C_INK2) def plot_traj(ax, traj, color, lw=1.6, alpha=0.9, zorder=3, label=None): ax.plot(X_GRID, traj, color=color, lw=lw, alpha=alpha, zorder=zorder, label=label) def make_figure(traj, mode, reg, ddpm, model_d, x1, y1, y2): fig, axes = plt.subplots(2, 2, figsize=(12.5, 9.0), dpi=180) fig.patch.set_facecolor(C_SURF) fig.suptitle("扩散模型当规划器 | 回归的均值坍缩 vs 扩散的模式覆盖", fontsize=14.5, fontweight="bold", color=C_INK) # (a) 专家数据:双模态 ax = axes[0, 0] style_ax(ax, "(a) 专家数据:同一场景、两种走法(对称双模态分布)", ylab=True) draw_car(ax, x1, y1) ax.text(x1, y1 - 2.1, "慢车", fontsize=9, color=C_INK2, ha="center", zorder=4) sel = rng.choice(len(traj), 60, replace=False) for i in sel: plot_traj(ax, traj[i], C_A if mode[i] > 0 else C_B, lw=1.1, alpha=0.55) plot_traj(ax, traj[sel[0]], C_A, lw=2.2, alpha=1.0, label="绕左模态") plot_traj(ax, traj[sel[1]], C_B, lw=2.2, alpha=1.0, label="绕右模态") ax.legend(loc="lower right", fontsize=8.5, framealpha=0.92) # (b) 回归规划器:均值坍缩 ax = axes[0, 1] style_ax(ax, "(b) 回归规划器:MSE 最优解 = 条件期望 → 穿过障碍", ylab=True) for i in rng.choice(len(traj), 40, replace=False): plot_traj(ax, traj[i], "#c9c8c0", lw=0.8, alpha=0.5) pred = reg(torch.tensor([[x1 / 30.0, y1 / 1.5]], dtype=torch.float32))[0].detach().numpy() plot_traj(ax, pred, C_LIMIT, lw=2.6, label="回归输出(条件期望)") dmin_reg = dmin_to_box(pred[None, :], x1, y1)[0] gaps = np.hypot(np.maximum(np.abs(X_GRID - x1) - OBS_HALF_L, 0), np.maximum(np.abs(pred - y1) - OBS_HALF_W, 0)) k_bad = int(np.argmin(gaps)) ax.plot(X_GRID[k_bad], pred[k_bad], "X", ms=13, color=C_LIMIT, zorder=6) ax.annotate(f"最小间距 {dmin_reg:.2f} m → 碰撞", xy=(X_GRID[k_bad], pred[k_bad]), xytext=(X_GRID[k_bad] - 16, pred[k_bad] + 2.6), fontsize=9.5, color=C_LIMIT, arrowprops=dict(arrowstyle="->", color=C_LIMIT, lw=1.2)) draw_car(ax, x1, y1) ax.legend(loc="lower right", fontsize=8.5, framealpha=0.92) # (c) 扩散采样:模式覆盖 + 打分选优(查询 1:障碍居中) ax = axes[1, 0] style_ax(ax, "(c) 扩散规划器:16 次采样覆盖两个模态 → 代价打分选优", ylab=True) s1, _ = ddpm.sample(model_d, 16) s1 = s1.numpy() is_left1 = s1.mean(axis=1) > 0 for i in range(16): plot_traj(ax, s1[i], C_A if is_left1[i] else C_B, lw=1.1, alpha=0.55) order1, costs1, dmins1 = pick_best(s1, x1, y1) k1 = int(order1[0]) plot_traj(ax, s1[k1], C_DIST, lw=2.8, label=f"打分选定(间距 {dmins1[k1]:.1f} m)") draw_car(ax, x1, y1) ax.text(0.5, Y_L + 1.6, f"左 {int(is_left1.sum())} / 右 {int((~is_left1).sum())}", fontsize=9.5, color=C_INK2) ax.legend(loc="lower right", fontsize=8.5, framealpha=0.92) # (d) 条件注入(代价打分):障碍左偏 → 同一模型,选优变为绕右 ax = axes[1, 1] style_ax(ax, "(d) 场景变化:障碍左偏 1.2 m → 打分改选更宽的一侧", ylab=True) s2, _ = ddpm.sample(model_d, 16) s2 = s2.numpy() is_left2 = s2.mean(axis=1) > 0 for i in range(16): plot_traj(ax, s2[i], C_A if is_left2[i] else C_B, lw=1.1, alpha=0.55) order2, costs2, dmins2 = pick_best(s2, x1, y2) k2 = int(order2[0]) plot_traj(ax, s2[k2], C_DIST, lw=2.8, label=f"打分选定(间距 {dmins2[k2]:.1f} m)") draw_car(ax, x1, y2) ax.text(x1, y2 - 2.2, "障碍左偏", fontsize=9, color=C_INK2, ha="center", zorder=4) ax.text(0.5, Y_L + 1.6, f"左 {int(is_left2.sum())} / 右 {int((~is_left2).sum())}", fontsize=9.5, color=C_INK2) ax.legend(loc="lower right", fontsize=8.5, framealpha=0.92) fig.tight_layout(rect=(0, 0, 1, 0.955)) out = os.path.join(OUT_DIR, "diffusion_planner.png") fig.savefig(out, facecolor=C_SURF) plt.close(fig) print(f"已保存: {out}") return pred, dmin_reg, (s1, is_left1, order1, costs1, dmins1), \ (s2, is_left2, order2, costs2, dmins2) def make_gif(ddpm, model_d, x1, y1): """去噪过程动画:64 条轨迹从纯高斯逐步组织成双模态云""" s_all, snaps = ddpm.sample(model_d, 64) s_all = s_all.numpy() order, costs, dmins = pick_best(s_all, x1, y1) k_best = int(order[0]) frames = list(np.unique(np.linspace(ddpm.T - 1, 0, 36).astype(int))) fig, ax = plt.subplots(figsize=(8.6, 4.4), dpi=110) fig.patch.set_facecolor(C_SURF) def update(i): ax.clear() t = frames[min(i, len(frames) - 1)] style_ax(ax, f"DDPM 反向去噪 | 步 t = {t}(共 {ddpm.T} 步)", ylab=True) yt = snaps[t].numpy() if t > 0 else s_all for j in range(len(yt)): plot_traj(ax, yt[j], "#b9b8b0", lw=0.8, alpha=0.6) if t == 0: for j in range(len(s_all)): col = C_A if s_all[j].mean() > 0 else C_B plot_traj(ax, s_all[j], col, lw=1.0, alpha=0.6) plot_traj(ax, s_all[k_best], C_DIST, lw=2.8, label=f"打分选定(间距 {dmins[k_best]:.1f} m)") ax.legend(loc="lower right", fontsize=9, framealpha=0.92) draw_car(ax, x1, y1) ani = animation.FuncAnimation(fig, update, frames=len(frames) + 4, interval=110) out = os.path.join(OUT_DIR, "diffusion_planner.gif") ani.save(out, writer=animation.PillowWriter(fps=9), savefig_kwargs={ "facecolor": C_SURF}) plt.close(fig) print(f"已保存: {out}") return k_best, dmins[k_best], is_left_frac(s_all) def is_left_frac(samples): return float((samples.mean(axis=1) > 0).mean()) # ============================================================ # 6. 主流程 # ============================================================ def main(): global N_DATA print("=" * 64) print("扩散模型当规划器 | DDPM(T=200)vs 回归基线 | PyTorch CPU") print("=" * 64) # ---- 数据 ---- N_DATA = 4096 traj, mode = make_expert(N_DATA, rng) x1, y1, y2 = 25.0, 0.0, 1.2 # 查询 1:障碍居中;查询 2:障碍左偏 1.2 m print(f"数据:{N_DATA} 条专家轨迹(对称双模态 ±{Y_L:.1f} m)") # ---- 回归基线(条件:障碍位置) ---- t0 = time.time() reg = Regressor() Y_t = torch.tensor(traj) C_t = torch.tensor(np.stack([np.full(N_DATA, x1 / 30.0), rng.uniform(-0.4, 0.4, N_DATA) / 1.5], axis=1).astype(np.float32)) def loss_reg(idx): return ((reg(C_t[idx]) - Y_t[idx]) ** 2).mean() train(reg, steps=3000, batch=256, lr=1e-3, loss_fn=loss_reg) t_reg = time.time() - t0 # ---- 扩散规划器(无条件学习轨迹分布) ---- t0 = time.time() ddpm = DDPM(T=200) model_d = Denoiser() def loss_diff(idx): y0 = Y_t[idx] t = torch.randint(0, ddpm.T, (len(idx),)) eps = torch.randn_like(y0) y_t = ddpm.add_noise(y0, t, eps) return ((model_d(y_t, t) - eps) ** 2).mean() train(model_d, steps=8000, batch=256, lr=1e-3, loss_fn=loss_diff) t_diff = time.time() - t0 print(f"训练:回归 3000 步({t_reg:.1f} s)| 扩散 8000 步({t_diff:.1f} s)") # ---- 采样耗时 ---- t0 = time.time() ddpm.sample(model_d, 16) t_sample = time.time() - t0 print(f"采样:16 条轨迹 × 200 步去噪 = {t_sample * 1000:.0f} ms" f"(回归一次前向 <1 ms)") # ---- 图 ---- (pred, dmin_reg, q1, q2) = make_figure(traj, mode, reg, ddpm, model_d, x1, y1, y2) s1, is_left1, order1, costs1, dmins1 = q1 s2, is_left2, order2, costs2, dmins2 = q2 # ---- 打分排序(查询 2:条件经代价函数注入) ---- sides1 = ["左" if is_left1[k] else "右" for k in order1[:3]] sides2 = ["左" if is_left2[k] else "右" for k in order2[:3]] # ---- GIF ---- k_gif, dmin_gif, frac_gif = make_gif(ddpm, model_d, x1, y1) # ---- 指标 ---- print() print(">>> 查询 1:障碍居中 (x=25 m, y=0)") print(f" 回归:最小间距 {dmin_reg:.2f} m → " f"{'碰撞(均值坍缩)' if dmin_reg < 1.0 else '安全'}") print(f" 扩散:左 {int(is_left1.sum())} / 右 {int((~is_left1).sum())}" f" | 代价 Top-3 模态:{'/'.join(sides1)}" f" | 选定间距 {dmins1[order1[0]]:.2f} m") print(f">>> 查询 2:障碍左偏 (y=+{y2} m)——同一模型,条件经代价注入") print(f" 扩散:左 {int(is_left2.sum())} / 右 {int((~is_left2).sum())}" f" | 代价 Top-3 模态:{'/'.join(sides2)}" f" | 选定间距 {dmins2[order2[0]]:.2f} m") print(f"(GIF 64 采样左占比 {frac_gif:.2f} | 选定间距 {dmin_gif:.2f} m)") if __name__ == "__main__": main()

6.产业现状与实时性

从 2022 年至今,扩散规划器完成了从论文到量产管线的跨越:

奠基(2022):Diffuser 首次把"轨迹作为生成对象、规划作为引导采样"表述为通用框架[2];Decision Diffuser 证明约束与奖励都能以条件生成的方式进入决策[3];Diffusion Policy 在机器人操作上以"多模态专家数据"为核心论据确立了生成式策略的优势[4]。

交通仿真:CTG++ 用引导扩散生成可控的交互交通流,为闭环评测提供场景供给[8]——与世界模型在评测端的角色互补。

端到端上车(2024–2025):DiffusionDrive 提出截断式扩散,把端到端规划的去噪步数压到 1–2 步[6];Diffusion Planner(2025)以灵活引导为卖点,支持多类约束即插即用[7]。

实时性是这一路线的真实代价:本文演示中 200 步去噪需 120 ms(CPU、16 条采样),而回归一次前向不足 1 ms。当前的工程答案是蒸馏——渐进蒸馏[9]与一致性模型[10]可以把数十步去噪压缩到 1–4 步,DiffusionDrive 即以此把扩散头做到端到端管线可接受的开销[6]。方向上,算力曲线站在扩散这一边:采样步数每降一档,相对回归的开销就小一个量级,而模式覆盖能力不随蒸馏消失。

7.局限与讨论

模式覆盖不等于模式正确。扩散模型学到的是专家数据的分布,包括专家的坏习惯;采出的轨迹也不保证运动学可行。可行性与安全性仍需显式层(打分、约束投影或安全滤波)兜底——生成式架构没有取消安全工程,只是移动了它的位置。

打分器成为新的规则之墙。路线三中,代价函数决定最终输出,这意味着规则与偏好从网络内部移到了网络外部。好处是透明可审计;代价是打分函数的设计质量重新成为系统上限——这与世界模型篇讨论的"评测循环性"同构:把智能移出黑盒的每一部分,都会在白盒里留下一个同样难写的新组件。

8.参考文献

[1] Ho J., Jain A., Abbeel P. Denoising Diffusion Probabilistic Models. arXiv:2006.11239, 2020.

[2] Janner M., Du Y., Tenenbaum J., Levine S. Planning with Diffusion for Flexible Behavior Synthesis (Diffuser). ICML, 2022. arXiv:2205.09991.

[3] Ajay A., Du Y., Gupta A., Tenenbaum J., Jaakkola T., Agrawal P. Is Conditional Generative Modeling all you need for Decision-Making? (Decision Diffuser). ICLR, 2023. arXiv:2211.15657.

[4] Chi C., Xu Z., Feng S., Cousineau E., Du Y., Burchfiel B., Russell S., Song S. Diffusion Policy: Visuomotor Policy Learning via Action Diffusion. RSS, 2023. arXiv:2303.04137.

[5] Chai Y., Sapp B., Bansal M., Anguelov D. MultiPath: Multiple Probabilistic Anchor Trajectory Hypotheses for Behavior Prediction. arXiv:1910.05449, 2019.

[6] Liao B., Chen S., Yin H. et al. DiffusionDrive: Truncated Diffusion Model for End-to-End Autonomous Driving. arXiv:2411.15139, 2024.

[7] Zheng Y., Liang R., Zheng K. et al. Diffusion-Based Planning for Autonomous Driving with Flexible Guidance. arXiv:2501.15564, 2025.

[8] Zhong Z., Rempe D., Xu D. et al. Guided Conditional Diffusion for Controllable Traffic Simulation (CTG++). arXiv:2210.17366, 2022.

[9] Salimans T., Ho J. Progressive Distillation for Fast Sampling of Diffusion Models. ICLR, 2022. arXiv:2202.00512.

[10] Song Y., Dhariwal P., Chen M., Sutskever I. Consistency Models. ICML, 2023. arXiv:2303.01469.

版权声明: 本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若内容造成侵权/违法违规/事实不符,请联系邮箱:809451989@qq.com进行投诉反馈,一经查实,立即删除!
网站建设 2026/10/23 6:36:08

OpenClaw 本地 AI Agent 搭建,隐私闭环存储,桌面自动化完整演示

&#x1f4d6; 前言 本文面向 Windows 系统用户&#xff0c;系统梳理 OpenClaw 的标准化部署流程。全程无需输入任何命令行&#xff0c;所有操作均依托可视化向导完成&#xff0c;即便是零基础用户也能独立走完整套部署。文中同时汇总了高频报错的对应解决方案&#xff0c;力求…

作者头像 李华
网站建设 2026/10/23 6:32:45

小白程序员必看!收藏这份医疗大模型数据集建设指南,从入门到精通

本文针对医疗数字化转型中AI预测性维护的痛点&#xff0c;提出了一套系统性数据集建设方法。内容涵盖行业现状与挑战、设备场景需求、数据标准化体系、质量控制流程、数据资产化路径等关键环节。通过多源异构数据整合、清洗增强技术、标注对齐方法等&#xff0c;实现数据价值转…

作者头像 李华
网站建设 2026/10/23 6:30:30

鼾声识别:嵌入式离线鼾声检测 SDK 与睡眠健康数据

鼾声识别的价值&#xff08;场景、流程&#xff09; 全球约三分之一的人口睡眠中打鼾&#xff0c;中年男性中比例超过 50%。鼾声不只是"吵"——它可能是单纯性打鼾、上气道阻力综合征&#xff0c;甚至睡眠呼吸暂停&#xff08;SAS/OSAS&#xff09;的预警信号&#…

作者头像 李华