到實(shí)現(xiàn)與調(diào)試)
最近在啃強(qiáng)化學(xué)習(xí)翻到第八章第18頁 Policy Gradient 的時(shí)候我明顯感覺到自己撞在了一堵墻上。前面 Q-learning、DQN 這些內(nèi)容好歹還有“表格”“近似函數(shù)”這種看得見摸得著的抓手可策略梯度一上來就給你滿頁的期望符號、對數(shù)概率、梯度展開第一遍讀下來基本是滑過去的。后來我拿著紙筆把每一步推導(dǎo)拆開重寫又動(dòng)手寫了一個(gè)最樸素的 REINFORCE 跑在經(jīng)典控制環(huán)境上才算把這條技術(shù)線真正理順。這篇就把我對 Policy Gradient 的完整理解倒出來從“它到底在解決什么問題”講起到那條核心公式的推導(dǎo)直覺再到一份能跑通的極簡實(shí)現(xiàn)以及我實(shí)際調(diào)試中踩過的坑。適合已經(jīng)了解 Q-learning、正在往策略梯度方法過渡的人也適合想在項(xiàng)目里快速上手決策控制算法的工程師。1. 為什么價(jià)值函數(shù)方法不夠用策略梯度的出場邏輯1.1 價(jià)值函數(shù)方法的兩道天花板價(jià)值函數(shù)方法包括 DQN 這類深度強(qiáng)化學(xué)習(xí)算法核心思路是先估計(jì)每個(gè)狀態(tài)-動(dòng)作對的價(jià)值然后從價(jià)值函數(shù)里“挑”出最優(yōu)動(dòng)作。這套路在游戲場景里確實(shí)很能打但你往現(xiàn)實(shí)問題里放就會(huì)發(fā)現(xiàn)它有兩個(gè)很普通的限制。第一個(gè)限制是連續(xù)動(dòng)作空間。假設(shè)你要控制一個(gè)機(jī)械臂的關(guān)節(jié)力矩每個(gè)關(guān)節(jié)的輸出就是一個(gè)連續(xù)實(shí)數(shù)比如 -1 到 1 牛米。價(jià)值函數(shù)方法需要比較所有動(dòng)作的 Q 值大小在連續(xù)空間里你沒法枚舉所有動(dòng)作——總不能把力矩按 0.01 間隔全部遍歷一遍關(guān)節(jié)一多維度直接爆炸?,F(xiàn)實(shí)中的機(jī)器人控制、自動(dòng)駕駛、能源調(diào)度動(dòng)作空間幾乎都是連續(xù)的這是價(jià)值函數(shù)方法一道實(shí)打?qū)嵉奶旎ò?。第二個(gè)限制更隱蔽價(jià)值函數(shù)方法天然輸出確定性策略也就是“在某個(gè)狀態(tài)最優(yōu)動(dòng)作是固定的那一個(gè)”。但很多問題的最優(yōu)策略本來就是隨機(jī)策略。最經(jīng)典的例子是石頭剪刀布如果策略是確定性的對手摸到規(guī)律之后你就一把都贏不了。再比如不完全信息博弈、信息受限的感知決策場景隨機(jī)性本身就是策略的一部分。Q-learning 里的 epsilon 貪心只能算一個(gè)臨時(shí)補(bǔ)丁它讓策略偶爾隨機(jī)一下是為了探索但本質(zhì)上還是把最優(yōu)動(dòng)作找出來而不是真正學(xué)會(huì)一個(gè)帶概率分布的決策方式。1.2 把策略本身參數(shù)化成一條可調(diào)的曲線Policy Gradient 換了一個(gè)完全不同的思路既然價(jià)值函數(shù)方法繞了一道“先估價(jià)值、再選動(dòng)作”的彎那還不如直接把策略本身參數(shù)化讓策略輸出每個(gè)動(dòng)作的概率。假設(shè)我們用 θ 表示策略參數(shù)那么策略就是一個(gè)概率分布 π_θ(a|s)這個(gè)分布在狀態(tài) s 下給出選擇動(dòng)作 a 的概率。目標(biāo)也隨之改變。價(jià)值函數(shù)方法追求的是讓 Q 值盡可能逼近真實(shí)價(jià)值而 Policy Gradient 直接優(yōu)化一個(gè)更原始的指標(biāo)——策略的期望累積回報(bào)J(θ) E_{τ~π_θ}[R(τ)]這里 τ 表示一條完整軌跡也就是從初始狀態(tài)開始策略與環(huán)境交互到結(jié)束的全過程R(τ) 是這條軌跡上所有折扣獎(jiǎng)勵(lì)的總和。整個(gè)目標(biāo)的含義非常直覺我們希望找到一組參數(shù) θ讓“按這個(gè)策略跑出來的軌跡平均回報(bào)”最大。到這里問題變成了一個(gè)標(biāo)準(zhǔn)的“黑箱優(yōu)化”問題決策過程內(nèi)部包含環(huán)境轉(zhuǎn)移、狀態(tài)反饋我們往往不知道環(huán)境的完整動(dòng)態(tài)模型但可以通過采樣來估計(jì)這條軌跡的回報(bào)。剩下的核心就是在不知道環(huán)境模型的情況下如何求出 J(θ) 對參數(shù) θ 的梯度這正是策略梯度方法整條推導(dǎo)線的起點(diǎn)。1.3 為什么梯度方向比隨機(jī)搜索靠譜有人可能會(huì)問不就是讓平均回報(bào)變大嗎我隨機(jī)給參數(shù)加噪聲、跑一批軌跡看哪個(gè)參數(shù)表現(xiàn)好保留好的參數(shù)不就行了這確實(shí)是進(jìn)化策略一族干的事情簡單直接但樣本效率低到令人發(fā)指。參數(shù)維度一高隨機(jī)搜索在龐大參數(shù)空間里漫無目的地試探絕大多數(shù)擾動(dòng)都踩在無用的方向上。梯度方法的優(yōu)勢在于它通過策略概率對參數(shù)的導(dǎo)數(shù)告訴你“在當(dāng)前參數(shù)附近往哪個(gè)方向調(diào)整能讓期望回報(bào)增長得最快”。即使每次采樣的梯度估計(jì)有噪聲平均下來它依然指向真實(shí)梯度的方向。這就相當(dāng)于你站在山坡上有根拐杖每次往前試探一下腳底傳來的坡度信息能引導(dǎo)你往上爬而不是閉上眼睛瞎跑。策略梯度的整個(gè)理論框架其實(shí)就是為了把這根拐杖造出來。2. 策略梯度核心公式的推導(dǎo)直覺2.1 log-trick把梯度挪進(jìn)期望里面推導(dǎo)的核心難點(diǎn)在于軌跡的分布由當(dāng)前策略決定采樣過程本身不可微。你無法直接對“樣本”求梯度但可以對“采樣概率”求梯度。這里用到一個(gè)在統(tǒng)計(jì)里非常常見的技巧叫做對數(shù)概率梯度恒等式?_θ π_θ(a|s) π_θ(a|s) · ?_θ log π_θ(a|s)這個(gè)恒等式看著簡單但它解決了策略梯度的大半問題。把左邊那個(gè)“對概率求梯度”改寫為右邊“概率值乘上對對數(shù)概率求梯度”之后我們就可以把梯度搬進(jìn)期望號里面。整個(gè)過程大致是?_θ J(θ) ?_θ ∫ P(τ|θ)R(τ)dτ ∫ P(τ|θ)?_θ log P(τ|θ)R(τ)dτ E_{τ~π_θ}[?_θ log P(τ|θ)·R(τ)]然后進(jìn)一步展開 log P(τ|θ)軌跡的概率由策略和環(huán)境動(dòng)態(tài)共同決定。展開后可以發(fā)現(xiàn)環(huán)境動(dòng)態(tài)部分不依賴于參數(shù) θ求梯度時(shí)直接歸零真正留下來的只有策略項(xiàng)?_θ J(θ) E_{τ~π_θ}[ Σ_{t1}^T ?_θ log π_θ(a_t|s_t) · R(τ) ]這個(gè)公式漂亮就漂亮在環(huán)境模型完全不需要出現(xiàn)在表達(dá)式里。哪怕你不知道狀態(tài)怎么轉(zhuǎn)移只要你能采樣軌跡就能用觀測到的軌跡去估計(jì)這個(gè)梯度。2.2 REINFORCE整條軌跡的成績單基于上面這個(gè)公式最簡單的策略梯度算法就是 REINFORCE它的執(zhí)行流程非常樸素用當(dāng)前策略 π_θ 采樣一條完整軌跡計(jì)算這條軌跡的折扣總回報(bào) R(τ)對軌跡上的每一個(gè)時(shí)間步計(jì)算 ?_θ log π_θ(a_t|s_t) 并乘以 R(τ)把所有時(shí)間步的梯度加總?cè)∑骄鳛檫@次采樣的梯度估計(jì)沿梯度方向更新參數(shù)θ ← θ α·梯度回到第 1 步循環(huán)迭代。把公式翻譯成人話每條軌跡就像一張成績單。如果某條軌跡的總回報(bào)是正數(shù)我們就獎(jiǎng)勵(lì)這條軌跡上出現(xiàn)過的所有動(dòng)作讓它們未來的出現(xiàn)概率都升高如果總回報(bào)是負(fù)數(shù)我們就懲罰這些動(dòng)作讓它們的概率降低。所有動(dòng)作共享同一張成績單一榮俱榮、一損俱損。2.3 高方差的根源用少數(shù)樣本來猜全局理解了流程你就能理解為什么策略梯度方法有一個(gè)臭名昭著的毛病——方差大。因?yàn)槲覀冃枰貌蓸榆壽E的回報(bào)來估計(jì)期望梯度而每條軌跡的回報(bào)本身波動(dòng)就非常大。同一個(gè)策略跑兩條軌跡一條可能拿到了高分另一條可能早早失敗兩者的回報(bào)差異巨大。REINFORCE 把整條軌跡的總回報(bào)平均地分給每一個(gè)時(shí)間步這里其實(shí)存在兩個(gè)問題第一軌跡之間的隨機(jī)性沒有做任何平滑第二某個(gè)動(dòng)作明明只產(chǎn)生了 3 步之后的效果卻要承擔(dān)整條軌跡所有獎(jiǎng)勵(lì)的功勞或責(zé)任這一點(diǎn)在技術(shù)上叫做 credit assignment 問題。方差大帶來的直接后果是訓(xùn)練不穩(wěn)定明明策略在變好某一次采樣運(yùn)氣差了梯度方向就可能完全跑偏運(yùn)氣好了又可能讓策略被一次偶然的高分軌跡裹挾。所以策略梯度算法的全部演進(jìn)史基本上就是在跟“方差”這兩個(gè)字做斗爭。3. 從零實(shí)現(xiàn)一個(gè)能跑的 REINFORCE3.1 實(shí)驗(yàn)設(shè)定與網(wǎng)絡(luò)結(jié)構(gòu)我建議第一次實(shí)操選一個(gè)輕量級的控制任務(wù)比如經(jīng)典的倒立擺環(huán)境CartPole。這個(gè)環(huán)境狀態(tài)是連續(xù)的動(dòng)作是離散的很適合用來驗(yàn)證策略梯度基礎(chǔ)實(shí)現(xiàn)。網(wǎng)絡(luò)不需要復(fù)雜兩層全連接就夠輸入層接狀態(tài)向量中間層用 ReLU輸出層給兩個(gè)動(dòng)作的 logits再接一個(gè) softmax 得到動(dòng)作概率分布。以下是我用的簡化結(jié)構(gòu)import torch import torch.nn as nn from torch.distributions import Categorical class PolicyNet(nn.Module): def __init__(self, obs_dim4, hidden_dim128, act_dim2): super().__init__() self.net nn.Sequential( nn.Linear(obs_dim, hidden_dim), nn.ReLU(), nn.Linear(hidden_dim, act_dim) ) def forward(self, obs): logits self.net(obs) return Categorical(logitslogits)這里的輸出是一個(gè) Categorical 分布對象它會(huì)根據(jù) logits 自動(dòng)算出各個(gè)動(dòng)作的概率同時(shí)提供 sample() 方法和 log_prob() 方法正好對應(yīng) REINFORCE 里需要的“采樣”和“算對數(shù)概率梯度”。3.2 訓(xùn)練閉環(huán)采樣、算回報(bào)、更新訓(xùn)練循環(huán)就是采樣一條軌跡保存每一步的動(dòng)作對數(shù)概率和獎(jiǎng)勵(lì)然后反向傳播更新。核心在于獎(jiǎng)勵(lì)的處理這一步也是新手最容易寫錯(cuò)的地方。折扣回報(bào)需要從軌跡末尾往前逆推def compute_returns(rewards, gamma0.99): returns [] G 0 for r in reversed(rewards): G r gamma * G returns.insert(0, G) return torch.tensor(returns, dtypetorch.float32)損失函數(shù)看起來非常反直覺——為什么最大化回報(bào)要寫成“最小化某個(gè)帶負(fù)號的式子”原因是我們習(xí)慣用深度學(xué)習(xí)框架的 loss.backward() 做梯度下降所以要取負(fù)號把“上升”變成“下降”loss -(log_probs * returns).mean() loss.backward() optimizer.step()其中 log_probs 是整條軌跡上每個(gè)動(dòng)作的對數(shù)概率張量returns 是對應(yīng)的折扣回報(bào)。用乘法對齊之后取平均就是這個(gè) batch 的近似梯度。3.3 關(guān)鍵的一步回報(bào)歸一化我強(qiáng)烈建議在算 loss 之前把 returns 做一次標(biāo)準(zhǔn)化也就是減均值除以標(biāo)準(zhǔn)差returns (returns - returns.mean()) / (returns.std() 1e-8)這一步不在原始 REINFORCE 的推導(dǎo)里但屬于實(shí)踐中的標(biāo)準(zhǔn)做法。因?yàn)榛貓?bào)的絕對值大小會(huì)影響梯度更新的幅度不同任務(wù)、不同環(huán)境的回報(bào)尺度差異很大直接乘上去很容易讓更新步子時(shí)大時(shí)小。歸一化之后訓(xùn)練收斂明顯更穩(wěn)。這個(gè)操作可以看做一個(gè)最簡單的 baseline 技巧它不會(huì)改變梯度的無偏性但能顯著壓低方差。3.4 三個(gè)我踩過的坑第一個(gè)坑是用同一條軌跡數(shù)據(jù)跑多次梯度更新。REINFORCE 的梯度估計(jì)依賴當(dāng)前策略的采樣分布軌跡是從舊策略采出來的數(shù)據(jù)和策略已經(jīng)對不上了。你多更新幾步就是在拿過期的數(shù)據(jù)訓(xùn)練新策略很容易發(fā)散。正確做法是一條軌跡采完更新一次扔掉重新采樣。第二個(gè)坑是學(xué)習(xí)率貪心。REINFORCE 的梯度估計(jì)方差本來就大學(xué)習(xí)率稍微調(diào)高一點(diǎn)就會(huì)劇烈震蕩。我第一次跑的時(shí)候設(shè)了 0.01曲線像心電圖一樣瘋狂跳動(dòng)。后來降到 1e-3 才穩(wěn)定。這個(gè)算法真的吃不了大補(bǔ)藥。第三個(gè)坑是不看策略熵。策略訓(xùn)練到后期可能退化成確定性策略輸出概率變成 1 和 0熵歸零。這種情況下訓(xùn)練曲線可能還在漲但策略已經(jīng)失去了探索能力一旦環(huán)境有擾動(dòng)就會(huì)脆斷。每次訓(xùn)練循環(huán)里順手打印一下策略熵能幫你及時(shí)發(fā)現(xiàn)這個(gè)苗頭。4. 常見問題排查與調(diào)參實(shí)戰(zhàn)4.1 訓(xùn)練不收斂先查這幾個(gè)方向如果獎(jiǎng)勵(lì)曲線連續(xù)很多個(gè) episode 都沒有爬升趨勢我一般先查 gamma 和學(xué)習(xí)率。gamma 太大會(huì)讓長期回報(bào)的方差不斷累積太小又會(huì)讓算法只盯著眼前獎(jiǎng)勵(lì)學(xué)不到長程決策。倒立擺這類短 horizon 任務(wù)0.99 是個(gè)比較穩(wěn)的起點(diǎn)但真正合適的值還是得自己掃幾輪。另一個(gè)容易忽略的問題是環(huán)境本身的終止條件。倒立擺的獎(jiǎng)勵(lì)到 500 步會(huì)截?cái)嗳绻悴惶幚磉@種“截?cái)唷焙汀罢嬲 钡膮^(qū)別算法很容易把 500 步截?cái)喈?dāng)成高分回報(bào)產(chǎn)生虛假的梯度信號。處理辦法是在計(jì)算回報(bào)時(shí)對截?cái)酄顟B(tài)做特殊標(biāo)記或者簡單一點(diǎn)把環(huán)境的最大步數(shù)設(shè)短一點(diǎn)讓算法更頻繁地看到完整的失敗軌跡。4.2 方差壓縮的三板斧第一板斧是用 reward-to-go 替代整條軌跡回報(bào)。整條軌跡的總回報(bào)把早期獎(jiǎng)勵(lì)也算進(jìn)了當(dāng)前動(dòng)作的功勞簿但從因果上講當(dāng)前動(dòng)作只影響當(dāng)前及未來的獎(jiǎng)勵(lì)。所以更合理的做法是讓每個(gè)動(dòng)作只對“從當(dāng)前時(shí)刻開始到軌跡結(jié)束”的累積回報(bào)負(fù)責(zé)環(huán)境回饋的過往獎(jiǎng)勵(lì)不該算進(jìn)來。第二板斧就是 baseline。最省事的 baseline 是回報(bào)均值因?yàn)閿?shù)學(xué)上可以證明減去一個(gè)不依賴動(dòng)作的常數(shù)不會(huì)改變梯度的期望但能顯著降低方差。我前面說的歸一化操作就是它的一種實(shí)現(xiàn)。第三板斧是往損失里加策略熵正則項(xiàng)損失函數(shù)變成loss -(log_probs * returns).mean() - beta * entropy.mean()entropy 是當(dāng)前策略分布的熵beta 是一個(gè)很小的系數(shù)比如 0.01。加這項(xiàng)的目的是懲罰“過于確定的策略”強(qiáng)迫策略保留一定的隨機(jī)性。這不會(huì)直接壓縮方差但能防止策略過早收斂到局部確定性策略間接提升穩(wěn)定性。4.3 真正有用的幾個(gè) Debug 手段比起整天盯著平均獎(jiǎng)勵(lì)曲線我更推薦打印三個(gè)量每個(gè) episode 的策略熵、動(dòng)作對數(shù)概率的梯度的范數(shù)、以及回報(bào)歸一化之后的均值標(biāo)準(zhǔn)差。策略熵如果快速掉到接近 0先看是不是學(xué)習(xí)率太大梯度范數(shù)如果忽大忽小看是不是回報(bào)尺度沒處理好。還有一個(gè)經(jīng)驗(yàn)把每次 episode 的步數(shù)畫成散點(diǎn)圖比畫 loss 曲線直觀得多。在倒立擺環(huán)境里episode 步數(shù)就是最直接的學(xué)習(xí)進(jìn)度條。你會(huì)發(fā)現(xiàn)這個(gè)曲線不是平滑上升的而是充滿毛刺但那不代表算法壞了——策略梯度本來就允許表現(xiàn)有波動(dòng)只要整體趨勢往上就說明方向是對的。我第一次看到這種鋸齒狀曲線時(shí)差點(diǎn)放棄調(diào)參后來才意識到這是策略梯度的工作方式。5. 寫在最后的個(gè)人體會(huì)我實(shí)操完 REINFORCE 之后最大的收獲不是看到獎(jiǎng)勵(lì)曲線終于爬上去那一刻的興奮而是終于明白了為什么策略梯度之后還會(huì)演化出 PPO、A3C 這一大堆方法。所有這些改進(jìn)本質(zhì)上都是在回答同一個(gè)問題怎么在保持梯度無偏的前提下把方差壓得更低。理解了 REINFORCE 的缺陷再去看后續(xù)那些算法會(huì)順暢很多。所以如果你也正卡在這個(gè)章節(jié)我的建議很簡單第一動(dòng)手把那個(gè)梯度公式從目標(biāo)函數(shù)開始推一遍別只看結(jié)論第二把最樸素的 REINFORCE 寫在紙上、跑在機(jī)器上哪怕它跑得并不漂亮。這兩件事做完你再看策略梯度這家人的其他方法心態(tài)會(huì)完全不一樣。最后再分享一個(gè)小技巧打印日志時(shí)把“當(dāng)前策略熵”帶上這是我排查訓(xùn)練異常時(shí)最先看的指標(biāo)比獎(jiǎng)勵(lì)值可靠得多。