機器人領域的專家軌跡、互聯網上的文本圖像視頻,這些數據讓生成模型在機器人操控、語言生成與規劃、視覺理解等任務上取得了驚人效果。但問題來了:換到具體任務上這些模型往往不太行。這是因為LLM 需要微調才能遵守安全約束或符合人類偏好,機器人策略也得繼續訓練才能彌補演示數據的不足。
擴散模型和流模型已經成為生成任務的主流方法,強化學習則是任務層面追求最優性能的老路子。兩者結合就有了 DDPO、DPPO、FPO、Flow-GRPO 這些工作。這類方法普遍在數十億參數、圖像文本這種高維環境下運行,所以我們換個思路:在一個二維簡單環境里研究訓練細節,只優化單條去噪軌跡。
這個環境訓練不到一分鐘,計算資源幾乎可以忽略。狀態空間和動作空間都簡單到指標沒什么意義,不過真正有意思的是不同微調策略下涌現出來的視覺行為。雖然這里聚焦于 DPPO 和擴散策略(把數據當作"動作"),但微調動態完全可以推廣到其他基于 RL 的擴散應用場景。

環境
定義一個"環形"高獎勵區域,模型要學會把樣本去噪到這個環的任意位置。觀察點在于:模型會收斂到環上的某個模式,還是把樣本均勻分布開?對環寬度的敏感程度如何?下面是一條去噪軌跡的例子:

期望行為:從隨機初始化(噪聲狀態)走向高獎勵區域,最后一步就是去噪完成的樣本。
不過在開始之前我們先解釋 DPPO 和相關術語,再嘗試用這個算法優化擴散模型來生成高獎勵樣本。
DPPO 算法概述
DPPO 是 PPO 的變體,屬于 on-policy 方法。核心思路是更新擴散模型參數讓生成樣本獲得更高獎勵。它把擴散過程建模成 MDP:每個擴散時間步是一個狀態,動作就是"去噪",獎勵來自最終的去噪狀態。獎勵通過蒙特卡洛估計傳播回有噪聲的時間步——也就是對完整回合的折扣回報求平均來估計期望累積獎勵。DPPO 論文里這張圖講得很清楚:
![]()
外循環先做回合采樣,存下每個擴散時間步的動作對數似然,加上狀態、動作、獎勵這些標配。內循環跑 K 個 epoch,用 PPO 風格的目標更新擴散模型參數。PPO 的細節網上講得很多,下面只展開相關部分。內循環結束后,用新策略再采樣一批回合。損失包含信任域策略更新、價值函數損失和探索用的熵項。為簡化起見,這里只看上圖中的"t=0"這一步,對應單條擴散軌跡。
![]()
算法 1. DDPO + DDIM 實現的偽代碼。第 5 行的"動作"就是去噪一個樣本。
步驟 1:回合采樣
state = env.reset()
# (aside: action variance is learned)
action_var = nn.Parameter(torch.full((2,), action_std_init * action_std_init))
current_pos = state[:2]
# in the rollout...
with torch.no_grad():
# conditional noise prediction
pred_noise = policy.actor(current_pos, t)
# the "T-1" prediction is the next position in the denoising trajectory
action_mean = policy.ddim_step(pred_noise, t, current_pos)
dist = Normal(action_mean, action_var.sqrt())
# sample from distribution with learned noise
action = dist.sample()
action_log_prob = dist.log_prob(action).sum(dim=-1)
next_state, reward, done = env.step(action)
# store in Buffer
buffer.states.append(state, action, action_log_prob, reward, done)
這段代碼對應 DPPO 論文公式 4.3。目前微調整個 DDIM 軌跡,后面會比較只微調最后幾步的效果:
![]()
代碼里去噪過程的每一步都被當作動作,這是 DPPO 內部 MDP 的關鍵。動作方差設為可學習參數,因為 DDIM 本身是確定性的,需要加探索噪聲(公式里的 sigma)。
DDIM 步驟方法如下,求解概率流 ODE 得到去噪過程的前一步(參考偽代碼里的公式)。這個操作必須可微,梯度才能流過去噪過程:loss → logprobs → dist → action → ddim_step → pred_noise → actor weights。噪聲調度參數是預設好的。
# DPPO differentiates through diffusion steps, so this needs to be differentiable
def ddim_step(self, model_output, timestep, sample):
# Handle t-1 (if t=0, prev=0, but alpha_prev=1.0)
prev_timestep = torch.clamp(timestep - 1, min=0)
alpha_prod_t_prev = alphas_cumprod[prev_timestep].view(-1, 1)
alpha_prod_t = alphas_cumprod[timestep].view(-1, 1)
beta_prod_t = 1 - alpha_prod_t
# DDIM Formula
pred_original_sample = (sample - torch.sqrt(beta_prod_t) * model_output) / torch.sqrt(alpha_prod_t)
pred_sample_direction = torch.sqrt(1 - alpha_prod_t_prev) * model_output
prev_sample = torch.sqrt(alpha_prod_t_prev) * pred_original_sample + pred_sample_direction
return prev_sample
步驟 2:獎勵縮放和 GAE
這部分基本是標準 PPO。跟蹤運行統計量來歸一化獎勵,因為獎勵方差太大會讓價值函數訓練不穩定。然后對緩沖區里所有狀態跑一遍價值函數前向(不算梯度),用 GAE 從回報計算優勢值,平衡偏差和方差。GAE 做的事情是給去噪過程中的"動作"分配功勞,價值函數則是為帶噪聲的狀態建模這個功勞(注意輸入里也帶了擴散時間步)。
# get values from buffer
old_states = torch.cat(buffer.states, dim=0)
# scale rewards using running statistics
rewards_np = np.array(buffer.rewards)
rewards_norm = (rewards_np - reward_scaler.mean) / (np.sqrt(reward_scaler.var) + 1e-8)
# compute Values for GAE
with torch.no_grad():
x_t = old_states[:, :2]
t_long = old_states[:, 2].long()
# Get values from critic
values = policy.critic(x_t, t_long)
advantages = []
last_gae_lam = 0
# iterate backwards through the buffer
# buffer.is_terminals tells us if the episode ended at that step
for step in reversed(range(len(buffer.rewards))):
if step == len(buffer.rewards) - 1:
next_non_terminal = 1.0 - float(buffer.is_terminals[step])
next_val = next_value
else:
next_non_terminal = 1.0 - float(buffer.is_terminals[step])
next_val = values[step + 1].item()
# Delta = r + gamma * V(s') * mask - V(s)
delta = rewards_norm[step] + gamma * next_val * next_non_terminal - values[step]
# Advantage = Delta + gamma * lambda * Advantage_next * mask
last_gae_lam = delta + gamma * gae_lambda * next_non_terminal * last_gae_lam
advantages.insert(0, last_gae_lam)
# Compute Returns: Return = Advantage + Value
# This is the target for the Value Function
returns = advantages + values
# Normalize Advantages (Standard PPO trick)
advantages = (advantages - advantages.mean()) / (advantages.std() + 1e-8)
步驟 3:PPO 更新
優化策略,降低低優勢動作的概率,提高高優勢動作的概率。這個過程跑多個 epoch 以充分利用緩沖區數據,不是更新一次就扔掉。每次迭代策略都在變,所以要在新策略下重新計算舊動作的對數概率,用新舊比率控制策略改進幅度。后面會實驗不同的裁剪參數(eps clip)。
# next state (from previous cell)
state = next_state
old_actions = torch.cat(buffer.actions, dim=0)
old_logprobs = torch.cat(buffer.logprobs, dim=0)
# K epochs define how
for _ in range(K_epochs):
x_t = old_states[:, :2]
t_long = old_states[:, 2].long()
pred_noise = policy.actor(x_t, t_long)
mean_action = policy.ddim_step(pred_noise, t_long, x_t)
# learnable action variance (defined during rollout)
dist = Normal(mean_action, action_var.sqrt())
# recalculate log probs under new policy for policy ratio
logprobs = dist.log_prob(old_actions).sum(dim=-1)
ratios = torch.exp(logprobs - old_logprobs)
surr1 = ratios * advantages
surr2 = torch.clamp(ratios, 1-eps_clip, 1+eps_clip) * advantages
policy_loss = -torch.min(surr1, surr2)
# compute V(s) with gradients this time, to train value function
state_values = policy.critic(x_t, t_long)
value_loss = 0.5 * nn.MSELoss()(state_values, returns)
# there can also be an entropy term and KL term here, but omit for now
loss = policy_loss + value_loss
DPPO 和 DDPO 的區別
網上幾乎沒有對比這兩個名字容易混淆的方法:Denoising Diffusion Policy Optimization(DDPO)和 Diffusion Policy Policy Optimization(DPPO)。DDPO 針對文本生成圖像,DPPO 針對擴散策略優化。動機差異之外,DDPO 用按 prompt 的獎勵歸一化,"類似于價值函數基線";DPPO 用更成熟的 GAE 加上顯式學習的價值函數。概念上真的很難分清楚,不過這里的實現因為用了 GAE,技術上算 DPPO。
從頭訓練 DPPO(失敗)
理論上跟 PPO 一樣,給夠回合數就能最大化獎勵。但 RL 和實際訓練里,樣本效率才是命門。模擬環境確實降低了采樣成本,可靈巧操控這種 sim2real 效果差的任務,還是得靠真實演示用盡量少的回合搞定。所以先試試只用 300 個回合從頭訓練,看看性能曲線。下圖和后續圖里,藍點是去噪后的樣本,訓練過程中定期評估。

300 個回合后 DPPO 完全沒收斂,樣本壓根沒往高獎勵區域靠。擴散模型本身就需要大量樣本,再疊上 RL 臭名昭著的樣本低效,失敗并不意外。就算加到 5000 個回合,超參不仔細調也收斂不了。
從專家演示微調
現實場景里不可能有無限回合,所以專家演示通常夠引導獎勵最大化。要模擬"專家演示",需要一個分布:接近高獎勵區域的多個模式,大體形狀像那么回事,但又留有 RL 優化空間。于是選了一個半徑 1.0 的圓形分布,用監督學習訓練擴散模型去噪到這個區域——可以類比從演示學習或在互聯網數據上預訓練。30k epoch 后幾百個樣本的可視化效果如下:
![]()
微調前的預訓練動作分布(去噪軌跡未畫出)。
加載預訓練 checkpoint 再跑 DPPO,性能提升明顯行為也符合預期:大約 150 個回合后粒子開始收斂到高獎勵區域。不過通常是找到第一個被探索到的高獎勵模式,而不是均勻分布在 radius=1.5 的環上。

DPPO 微調把動作從 radius=1 的預訓練分布引導到 radius=1.5 的高獎勵區域,比從頭訓效果好太多
添加 KL 約束
Flow-GRPO 等工作在 PPO 目標之外加了 KL 約束。對 LLM 和圖像生成模型(Flow-GRPO 的主要場景),偏向有效文本和語義正確的圖像是有道理的。機器人領域不太在意行為克隆的真實分布,只是借它引導通常稀疏且初次難以成功的高獎勵區域(比如到底拿沒拿起咖啡杯)。但如果用的是可能被"利用"的密集獎勵,KL 約束就有用了——比如"杯子舉多高"這種獎勵,很容易被往上拋的動作鉆空子。
DPPO 和 Flow-GRPO 目標對比如下:
![]()
DPPO 目標
![]()
Flow-GRPO 目標
Flow-GRPO 的組大小 G 可以替代 GAE 做優勢估計。KL 約束確保新策略不會偏離原策略太多,能防止收斂時的發散行為。策略比率則保證更新幅度不要太大。加上 KL 后損失變成:
![]()
帶 KL 約束的新 DPPO 目標

觀察到的現象是:動作沒有像之前那樣收斂到一兩個模式,而是保留了更多圓形分布的形狀,分散在多個獎勵模式周圍。總獎勵偏低,但這在預期之內。Flow-GRPO 論文也有類似發現:
…我們發現省略 KL 會導致視覺多樣性崩潰,這是一種獎勵利用的形式…(第 5.2 節)
分布在多個高獎勵模式上,對應現實中完成任務有多種方式的情況(可以抓杯身也可以抓杯把)。KL 約束還能增加泛化性,防止只收斂到單一高獎勵模式。比如普通 DPPO 可能只學會抓杯身,碰到杯子燙得不行的分布外場景就傻了;加了 KL 約束的 DPPO 還知道可以抓杯把。
即便抓杯把本來不在演示分布里,這個好處也可能成立。值得后續研究的問題是:PPO 更新中的 KL 約束到底保持的是預訓練分布的形狀,還是分布本身?在這個玩具環境里,如果帶 KL 的 DPPO 最優策略確實收斂到均勻分布在 radius=1.5 圓上,就可以定性地說形狀被保留了,只是低獎勵特性被替換。如果 KL 只是把動作值鎖在 radius=1.0,那就不成立了。
消融實驗
微調跑通之后,就可以看看 PPO 各組件對性能的影響。
只微調最后幾個擴散步驟
DPPO 論文建議提高效率的做法是:預訓練后復制兩份模型,一份凍結用于去噪前面的時間步,另一份微調用于去噪最后幾步(附錄 C.2 建議 10% 來平衡效率)。但在這個環境里,30% 到 50% 似乎更合適(總共 50 個去噪步驟)。

只微調最后 10%、30%、50% 的步驟(從左到右)。30% 到 50% 之間效果明顯更好
這個環境還可以對比不同設置下的擴散軌跡,訓練結束后可視化 20 個樣本:
![]()
只微調最后 10%、30%、50% 步驟的采樣軌跡(從左到右)
放大 10% 微調步驟產生的軌跡(最左邊),可以看到樣本越過預訓練流形后有個向高獎勵區域的急劇"轉向"。轉向后面的去噪步驟就是被微調的模型。有意思的是,微調步驟越多,這個轉向越平滑,但預期還是會在某個地方出現——最左邊軌跡在第 90 百分位步驟能看到,中間軌跡偶爾在第 70 百分位出現,最右邊第 50 百分位已經是平滑過渡了。如果轉向的急劇程度和微調效果差相關(急劇可能意味著最后幾步過度補償),那可以考慮用軌跡急劇程度作為擬合質量的指標,尤其是高維場景下不容易定位問題的時候。
跟微調整個軌跡的結果對比:
![]()
線條顏色更深,說明顯著地把樣本推向了高獎勵區域,初始擴散步驟的重要性可見一斑。
策略比率 eps clip 和學習率的交互
直覺上這兩個東西作用類似。策略比率控制策略變化速度,actor 學習率也決定這一點。

低/高 clip 與學習率的對比(clip = 0.1/0.4,lr = 1e-4, 5e-3)。高 clip 意味著允許更大的策略偏移
學習率的影響壓過了策略裁剪,這說得通——參數空間偏移太大的話,策略比率會變得巨大。跑了幾個實驗后結論很清楚:學習率最需要調,策略裁剪擋不住過激更新(哪怕 clip 設到 0.01 配上高學習率也沒用)。有意思的是,低學習率似乎有助于保留獎勵區域的多個模式。
移除策略比率
完全去掉策略比率,min() 兩邊都設成優勢值乘對數概率(注意如果只用 A 不帶 log prob,梯度就斷了)。動作收斂到比不加 KL 項更緊密的分布(跟上一節高 clip 結果很像),不過獎勵依然挺高。這也暴露了環境復雜度的局限——沒有性能掉下去就回不來的區域,而策略比率本來就是為防這個設計的。不過有趣的是,這又是一個能防止獎勵模式崩潰的因素。另一個有趣現象是訓練久了會在多個獎勵模式之間跳——像是高學習率行為的稍微穩定版。
![]()

其他超參的一些觀察
優化 epoch 數確實和 eps clip 成反比,兩個超參都在權衡每次更新的策略改進幅度。
DPPO 更新間隔的時間步數和學習率成反比,兩者都在權衡策略改進的速度。
總結
這篇文章解釋了如何為單步環境中的擴散模型實現 DPPO,希望能提供一個比典型機器人環境更容易理解訓練動態的平臺。跑了只微調最后幾個去噪步驟、調各種 PPO 超參的實驗。大家可以自己從這些結果里得出結論,也可以動手改改環境,看能不能提升樣本效率——這仍然是 PPO 的關鍵瓶頸。
代碼在這里:https://gist.github.com/nsortur/ec9660a0026598e54f1e4fb583e077ed
作者: Neel
特別聲明:以上內容(如有圖片或視頻亦包括在內)為自媒體平臺“網易號”用戶上傳并發布,本平臺僅提供信息存儲服務。
Notice: The content above (including the pictures and videos if any) is uploaded and posted by a user of NetEase Hao, which is a social media platform and only provides information storage services.