
做离线强化学习久了你会意识到一个很拧巴的事实扩散策略拟合数据分布的能力极强但它本质上是在模仿行为策略。你用一批次优混合数据训练出来的扩散模型采出来的动作大概率只是“像数据”而不是“值钱”。我最早看到 CFGRL 这个标题时第一反应是它把 diffusion guidance 重新框成了一个可控的策略提升算子——也就是说引导强度不再只是图像生成里调风格的一个参数而是策略改进力度本身。这篇文章我会把它掰开揉碎讲清楚并且给出我实际跑实验时能直接上手的工程实现。1. 为什么说 diffusion guidance 可以当作策略提升算子1.1 先把两个概念对齐扩散策略与策略提升先说扩散策略。常规的策略网络输入状态 $s$直接输出动作 $a$ 或其分布参数。扩散策略的思路是换一种生成方式把动作 $a$ 看成一个需要“去噪”的样本网络预测的是噪声通过多步去噪得到最终动作。这样做的好处是能表达多模态的动作分布——真实数据集里往往存在“同一个状态多种最优做法”高斯分布根本拟合不了扩散模型可以。再说策略提升算子。这个词听起来学术其实道理很朴素强化学习里的策略迭代就是反复执行两步——用当前策略评估价值然后根据价值改进策略。改进的那一步在数学上可以写成一个算子 $T$它把旧策略 $\pi_{\text{old}}$ 映射到新策略 $\pi_{\text{new}} T(\pi_{\text{old}})$。经典的有 Bellman 最优算子有自然策略梯度也有各种保守类算法自己定义的改进规则。CFGRL 的核心贡献是把这个“改进”动作放到了扩散模型的采样阶段一次带引导的采样就等价于执行了一次可控的策略提升。这个视角让我觉得很值钱因为它把离线强化学习中“如何超越行为策略”的难题转成了“如何设计引导信号与引导强度”的问题。1.2 策略提升算子的通用形式以及它和分数引导的对应关系如果把策略提升写成最通用的形式几乎所有方法都是在做同一件事[ \pi_{\text{new}}(a|s) \propto \pi_{\text{old}}(a|s) \cdot \exp\left(\beta \cdot A(s,a)\right) ]意思是新的策略在旧策略的基础上给高优势的动作分配更多概率密度。$\beta$ 控制这个“偏向”有多强——$\beta$ 大提升激进$\beta$ 小保守贴近旧策略。对这个式子两边取梯度可以得到[ \nabla \log \pi_{\text{new}}(a|s) \nabla \log \pi_{\text{old}}(a|s) \beta , \nabla A(s,a) ]左边是新策略的分数score右边第一项是旧策略的分数第二项是优势函数的梯度。熟悉扩散模型的朋友到这里应该已经嗅到熟悉的味道了这正是 classifier guidance 的分数组合形式。扩散模型采样时最终决定生成方向的也是分数。无条件扩散模型的分数对应 $\nabla \log \pi_{\text{old}}$而 classifier guidance 会额外加上一个分类器对数概率的梯度。在 CFGRL 的语境里这个梯度被替换成了价值函数或优势函数的梯度。于是不同强度的引导就对应了不同强度的策略提升。1.3 CFGRL 的“可控”到底可控在哪里这个“可控”是全文的关键词也是它区别于 Diffusion-QL 那一类方法的地方。Diffusion-QL 是在扩散策略的训练损失里加一个 Q 值项通过加权梯度把策略往高价值方向推这种做法的问题是提升力度隐含在损失权重里而损失权重和数据分布、奖励尺度耦合在一起很难直观调节。CFGRL 的做法是走 classifier-free guidance 的路线。训练时让同一个扩散模型同时学会“无条件生成”和“有条件生成”采样时用两者的噪声预测之差作为引导方向再乘上一个 scale 因子 $w$[ \epsilon_{\text{guided}} \epsilon_{\theta}(z | s, \emptyset) w \cdot \left(\epsilon_{\theta}(z | s, c) - \epsilon_{\theta}(z | s, \emptyset)\right) ]这里的 $c$ 是条件信号可以是回报、价值函数或优势函数。$w$ 就是那个“可控”的旋钮$w0$ 时是纯行为克隆$w1$ 时是利用条件信号的标准强度$w1$ 时则是对条件信号的外推——生成的样本会比数据集里的高回报样本更激进。我在实践中最大的感受是这种把“提升强度”独立成采样参数的设计让调参过程变得异常清爽。你不用为了提升策略表现去动训练损失、改网络结构只需要在采样端调一个数。2. 核心思路拆解从行为克隆到可控外推2.1 条件信号的选择回报、价值函数还是优势函数CFGRL 可以落地成很多变体最关键的差异在于条件信号 $c$ 的设计。第一种是直接用 return-to-goRTG也就是把一段轨迹的累计回报作为条件。训练时给模型看“高回报轨迹对应的动作”和“低回报轨迹对应的动作”采样时指定一个高于数据均值的回报目标让模型生成与之匹配的动作。这个设计的优点是简单、稳定不需要额外训练 critic缺点是 RTG 是轨迹级信息给到单步动作时会有 credit assignment 问题只能说“这段轨迹整体回报高”但说不清是哪几步的功劳。第二种是训练一个价值函数 $Q(s,a)$ 或优势函数 $A(s,a)$把它作为条件。CFGRL 标题里的 policy improvement operator 更贴近这个路线提升算子的输入就是一个价值估计。这个做法理论上更干净但工程上多了一个 critic 要训critic 训不好会把噪声传进引导信号。第三种是混合方案用 RTG 做条件但采样前用 critic 对生成样本做筛选。我没有在原文看到这种做法的依据但实操中见过有人这样提高质量代价是多一次前向推理。如果让我给一句经验第一版系统先把 RTG 条件跑通再考虑价值函数条件。RTG 让你快速验证“引导方向对不对”价值函数让你验证“引导上限高不高”。2.2 无条件分支为什么是必需品Classifier-free guidance 最反直觉的地方在于你明明训练的是条件模型为什么要让它在 10%~20% 的情况下看不到条件原因是引导方向需要“基准线”。采样时要用条件模型的输出减去无条件模型的输出这个差才是条件带来的增量如果模型永远看到条件你无法剥离出“条件到底改变了什么”。更准确地说classifier-free guidance 的分数形式是[ \nabla \log p(c|z) \approx \epsilon_{\theta}(z|c) - \epsilon_{\theta}(z|\emptyset) ]这个差是对条件对数概率梯度的近似。没有无条件分支这个差分就无从谈起。训练上实现也很简单每个 batch 里随机挑 10%~15% 的样本把条件置为特殊的空值比如全 0 的 embedding其余样本正常带条件训练。损失函数不区分这两种情况模型需要学会在条件缺失时依然能生成合理动作。我在实验中发现这个 dropout 比例太小时无条件分支会退化——模型即使看到空条件也会隐隐“脑补”出条件导致引导增量被削弱。2.3 CFGRL 与 RLHF、DPO 这类偏好优化方法的关系这里多扯一句。CFGRL 的算子视角和最近大模型领域流行的 RLHF、DPO 有异曲同工的地方——它们本质上都是让生成模型往“被偏好”的方向偏移而偏移的强度由一个超参控制。在 RLHF 里这个超参是 KL 惩罚系数在 DPO 里它对应对数概率比的温度在 CFGRL 里它就是 guidance scale $w$。区别在于RLHF 显式训练一个奖励模型再通过强化学习优化策略CFGRL 则是把价值信号直接揉进了扩散采样过程不需要额外的策略优化器。这个视角对我帮助很大你可以把 CFGRL 看成“RL 版的 classifier-free guidance”也可以把它看成“采样时做策略提升的 RLHF”。既然都是同一个思想在不同领域的投影那它们的调参经验也可以互相借鉴。比如引导强度过大导致分布坍缩这个问题在图像生成里叫 mode collapse在 RLHF 里叫 reward hacking在 CFGRL 里叫动作越界——本质都是同一个病。3. 落地实现训练流程、损失函数与采样配置3.1 数据准备与归一化我在 D4RL 的 MuJoCo 任务上做验证以 medium-replay 这类混合质量数据集为例。数据处理有几个小细节动作和状态都要归一化。扩散模型对输入尺度非常敏感动作不归一化会导致扩散过程的学习信号被大数值维度主导。RTG 条件信号用 min-max 归一化到 $[0,1]$ 区间。注意这里的 min 和 max 要基于训练集统计不要用全局未知的最大回报。轨迹切分时我习惯把一条完整轨迹切成长度为 64 的短段。段太短条件信号包含的未来信息不够段太长训练样本数变少而且长程依赖未必是单步动作生成需要的。数据准备好之后你会得到若干三元组$(s_{t:tH}, a_{t:tH}, R_{t:tH})$。这里 $H$ 是轨迹段长度。如果不想做轨迹级生成也可以退化成单步 $(s_t, a_t, R_t)$但那样会丢掉时序一致性动起来肌肉感会差一些。3.2 网络结构与训练要点网络结构取决于你要生成的是轨迹还是单步动作。生成单步动作时用一个 MLP 把状态编码成条件向量再用一个 MLP 做去噪网络就够了。生成轨迹段时我建议用 1D U-Net 或者带 causal attention 的 transformer这样能捕捉动作之间的时序相关性。我用的配置大致如下扩散步数 $T1000$噪声调度用 cosine schedule。优化器 AdamW学习率 $3 \times 10^{-4}$weight decay 设得很小大概 $1 \times 10^{-5}$。Batch size 256训练 100 万步左右或者按验证集上的扩散损失收敛为准。EMA 指数移动平均系数 0.995采样时用 EMA 权重而不是最新权重。条件 dropout 比例 0.15。条件用单层 MLP 嵌入成 256 维向量空条件用全 0 张量。训练损失就是标准的 DDPM 噪声预测损失没有什么特殊处理[ \mathcal{L} \mathbb{E}{t, z_0, \epsilon}\left[\left|\epsilon - \epsilon\theta(z_t, t, s, c)\right|^2\right] ]唯一要注意的是dropout 条件时$c$ 的位置传入空张量但状态条件 $s$ 永远保留。这一点很重要因为无条件分支指的是“无回报条件”不是“无状态条件”。3.3 采样端配置怎么做 classifier-free guidance采样时每个去噪步骤都要跑两次网络一次用真实条件一次用空条件。两者的 noise prediction 做线性组合得到引导后的噪声估计然后按 DDIM 的更新公式走一步。实际代码骨架大概是这样的def guided_sample(model, state, condition, w, sampling_steps50): # 初始化纯噪声 z torch.randn(sequence_length, action_dim, devicedevice) # 用 DDIM 步长序列 timesteps torch.linspace(1000, 0, sampling_steps 1)[:-1] dt 1000 / sampling_steps for t in timesteps: t_batch torch.full((1,), t, devicedevice) # 条件分支 eps_cond model(z, t_batch, state, condition) # 无条件分支 eps_uncond model(z, t_batch, state, empty_condition) # classifier-free guidance 组合 eps_guided eps_uncond w * (eps_cond - eps_uncond) # DDIM 更新 alpha_t get_alpha(t) alpha_next get_alpha(t - dt) z ddim_update(z, eps_guided, alpha_t, alpha_next) return z我采样时一般用 $50$ 步 DDIM$w$ 从 $0.5$ 到 $5.0$ 之间扫。训练用了 1000 步采样只用 50 步这中间的信息损失是真实的但 DDIM 的确定性性质让它对引导分数组合仍然稳定。如果你发现 50 步之后动作质量明显下降可以提升到 100 步代价是单次决策的延迟翻倍。3.4 评估协议与指标选择评估时我固定 10 个随机种子每个种子跑 100 个 episode取平均归一化回报。这里有一个容易踩的坑扩散模型的采样带随机性即使固定种子结果也会有方差。所以评估次数不能太少至少 50 个 episode 起步否则两个方法之间的差异会被噪声淹没。另外一个非常实用的辅助指标是“采样动作与数据集动作的分布距离”。我会计算生成动作与训练集动作的 MMD 或者简单统计动作范数、动作差分均值。如果 $w$ 调大之后动作分布明显偏离数据分布离线评估得分却上升你要警惕是不是环境模型给了不真实的过渡态。纯离线评估下这个现象很难察觉但只要在真实环境或模拟器里回放一遍原形毕露。4. 调参经验与常见问题排查4.1 guidance scale 不是越大越好它和任务难度强相关我在 D4RL 的 hopper、walker2d、halfcheetah 上做过完整扫描。结论是简单任务比如 hopper-medium-expert最优 $w$ 在 2.0 附近困难任务比如 antmaze最优 $w$ 会掉到 0.5~1.0。原因是困难任务的数据分布覆盖度低行为策略本身不稳定这时候强行外推高回报条件生成的动作很容易跳出状态空间的合法区域。我的建议是写一个小的超参扫描脚本在 $w [0.5, 1.0, 2.0, 3.0, 5.0]$ 里先粗扫再在最优值附近细扫。每档跑 20 个 episode 就能看出大致趋势不要一开始就在每个档位上跑满 100 个 episode。4.2 RTG 分桶 vs 连续回归谁更稳定把 RTG 作为条件时可以做连续回归也可以离散化成桶。连续回归实现简单但模型容易把中间值模糊化——因为高回报轨迹和低回报轨迹在动作空间上可能高度重叠回归目标含糊。分桶相当于把连续信号离散成几个类别模型学的是一个“高回报类别”的清晰边界生成的区分度反而更好。我的经验是分成 5 个桶用交叉熵损失训练条件 embedding。实际采样时指定“最高回报桶”作为条件相当于隐式地给了一个比数据集平均水平更高的目标。这个做法和标题里的 controllable 思想是对得上的你想多激进就选多高的桶。4.3 引导失效的诊断方法检查增量而不是只看结果如果你调了半天 $w$发现结果和 $w0$ 几乎没有区别不要急着怀疑算法先做两个检查。第一检查条件分支和无条件分支的输出差。把同一个噪声输入分别传入两个分支统计 $\epsilon_{\text{cond}}$ 与 $\epsilon_{\text{uncond}}$ 的差异范数。如果这个差异范数趋近于零说明条件信息没有进入网络常见原因是条件 dropout 太低或者条件 embedding 没有参与计算图。第二检查条件预测的准确率。如果你用的是 RTG 分桶条件可以在验证集上算一下给定一个带真实 RTG 桶标签的状态模型的分类头能不能预测对。如果训练集上准确率都不到 80%说明条件信号本身就没有被模型利用引导自然失败。这个问题我遇到过两次一次是 RTG 归一化出问题另一次是条件 embedding 的维度设得太小被状态特征掩盖了。4.4 采样步数与计算开销的权衡CFGRL 使用引导会导致计算开销翻倍因为每个去噪步跑两次网络。50 步 DDIM 意味着 100 次网络前向这在离线评估里不是问题但部署到实时控制系统里会比较吃力。我试过的折中方案先无条件采样 30 步最后 20 步加入引导。原理是扩散过程前期决定动作的宏观结构后期决定细节纹理引导前期对方向影响大后期对精细度影响大。如果只能在有限步数里用引导把它放在后期性价比更高。另一个折中是把无条件分支的输出缓存住固定状态不变化时复用同一份无条件输出可以省一半计算。4.5 和 Diffusion-QL、IQL 等基线对比时容易踩的坑CFGRL 和 Diffusion-QL 的对比是最容易出公平性问题的因为 Diffusion-QL 在训练时用 Q 加权CFGRL 在采样时用引导。两者为了使实验公平应该保持去噪网络架构、训练步数、采样步数完全一致。我在复现时发现一个隐蔽的偏差Diffusion-QL 如果加了条件 dropout性能会下降因为它的目标函数里没有“无条件生成”这一项而 CFGRL 倚仗条件 dropout 才能做引导。所以对比时要明确说明“条件 dropout 是 CFGRL 的一部分”不要为了让基线也享受这个组件而强行给 Diffusion-QL 加上否则基线会变差对比不公平。和 IQL 这类非扩散模型方法对比时要特别小心评估协议。IQL 输出的是确定性动作评估方差小扩散采样动作有随机性评估方差大。不能只看均值要看均值加减标准误否则 CFGRL 可能因为方差大而被误判为“不稳定”。5. 写在最后一点个人体会我最初接触 CFGRL 时以为它只是给扩散策略加了个 classifier-free guidance属于“技术拼接”。真正跑完实验之后才发现把 diffusion guidance 重新解读为可控策略提升算子改变的不仅是公式形式而是整个调参心智模型。之前我优化扩散策略总在训练损失里加各种正则项小心翼翼地平衡拟合与提升换了 CFGRL 之后训练阶段干干净净只做行为克隆所有“提升”动作都放到采样阶段用一个标量控制。这个分离让我少了很多头疼的时刻。如果让我给准备入手的同学一个建议第一版实现要克制。先用单步动作生成 RTG 分桶条件 50 步 DDIM把整个链路跑通再逐步加上轨迹生成、价值函数条件、动态采样步数这些复杂选项。CFGRL 的框架很灵活但它最核心的价值——引导强度与策略提升力度的对应关系——不需要复杂的工程就能验证。等你看到 w 从 0 调到 2 时策略行为肉眼可见地从“模仿”变成“激进”你就真正理解这个算子在做什么了。