化學(xué)習(xí)稀疏獎(jiǎng)勵(lì)困境:Hindsight Experience Replay原理與實(shí)現(xiàn))
“hindsight”這個(gè)詞做機(jī)器學(xué)習(xí)的同行看到的第一反應(yīng)多半是 Hindsight Experience Replay事后經(jīng)驗(yàn)回放HER但如果你只是個(gè)普通開發(fā)者可能更熟悉它“事后聰明”的字面意思。我最早接觸它是在訓(xùn)練機(jī)械臂抓取任務(wù)的時(shí)候——稀疏獎(jiǎng)勵(lì)下 agent 死活學(xué)不會(huì)換成 HER 之后同一個(gè)環(huán)境、同一份數(shù)據(jù)訓(xùn)練效率肉眼可見地漲了一個(gè)量級(jí)。這篇內(nèi)容就想把這個(gè)名字背后的原理、實(shí)現(xiàn)要點(diǎn)和實(shí)操坑位一次講透適合正在做強(qiáng)化學(xué)習(xí)落地、或者被 sparse reward 折磨得頭疼的朋友參考。1. “事后聰明”到底解決什么問(wèn)題1.1 稀疏獎(jiǎng)勵(lì)的困境先說(shuō)一個(gè)最直觀的場(chǎng)景你讓一個(gè)智能體控制機(jī)械臂去抓桌面上的一個(gè)杯子。如果杯子被抓住了給一個(gè) 1 的獎(jiǎng)勵(lì)沒抓住獎(jiǎng)勵(lì)為 0。聽起來(lái)很合理對(duì)吧但在訓(xùn)練剛開始機(jī)械臂的動(dòng)作完全是隨機(jī)的它可能幾萬(wàn)步之內(nèi)一次杯子都碰不到。整個(gè) reward 序列幾乎全是 0沒有任何梯度信號(hào)能告訴策略“往哪個(gè)方向調(diào)”。這就是稀疏獎(jiǎng)勵(lì)問(wèn)題的本質(zhì)——你給的信號(hào)太少了模型根本不知道該往哪兒走。我見過(guò)不少朋友在這個(gè)階段加各種花哨的 reward shaping比如計(jì)算機(jī)械臂末端與杯子之間的距離給一個(gè)負(fù)的懲罰項(xiàng)。這個(gè)方法本身沒問(wèn)題但難點(diǎn)在于距離接近獎(jiǎng)勵(lì)的“形狀”如果設(shè)計(jì)得不好agent 很快會(huì)學(xué)會(huì)“把手伸過(guò)去但就是不抓”因?yàn)樗l(fā)現(xiàn)這樣能積累更多中間獎(jiǎng)勵(lì)。Reward hacking 就是這么來(lái)的。1.2 HER 的名字本身就是答案Hindsight事后聰明。Human 有個(gè)很有意思的認(rèn)知特征我們會(huì)對(duì)已經(jīng)發(fā)生的事情做復(fù)盤并且把“如果我當(dāng)時(shí)那么做就好了”這種反事實(shí)思維內(nèi)化成經(jīng)驗(yàn)。HER 算法的核心假設(shè)就是讓 agent 也學(xué)會(huì)這種“事后聰明”。具體做法說(shuō)起來(lái)其實(shí)非常樸素——想象一下對(duì)于一條失敗的經(jīng)驗(yàn)軌跡比如 agent 本來(lái)想抓那個(gè)綠色杯子但沒抓到碰到了旁邊的紅色杯子。對(duì)于這條軌跡我們通常記錄的是“目標(biāo) 綠色杯子”所以每一步的 reward 都是 0。但 HER 說(shuō)我把這條軌跡的目標(biāo)改成“碰到紅色杯子”行不行你會(huì)發(fā)現(xiàn)如果目標(biāo)改成了紅色杯子那這條軌跡其實(shí)“完成了任務(wù)”最后一步就能拿到 1 的獎(jiǎng)勵(lì)于是這條原本毫無(wú)學(xué)習(xí)價(jià)值的失敗軌跡瞬間變成了一條“成功樣本”。這就是“事后聰明”——你不是站在當(dāng)下的目標(biāo)去看而是站在結(jié)果反推如果我的目標(biāo)本來(lái)是“達(dá)成這個(gè)實(shí)際結(jié)果”那我其實(shí)已經(jīng)成功了。這讓 agent 可以從每一次失敗中挖出學(xué)習(xí)信號(hào)而不是干等那一次罕見成功。2. 技術(shù)原理拆解目標(biāo)重標(biāo)注的完整邏輯2.1 goal-conditioned 與 universal value function要真正理解 HER你首先得知道它服務(wù)于哪一類問(wèn)題。HER 是給 goal-conditioned reinforcement learningGCRL設(shè)計(jì)的——也就是說(shuō)策略的輸入不止是狀態(tài)還包括一個(gè)目標(biāo)π(s, g) → a。目標(biāo)通常是一個(gè)向量比如機(jī)械臂要抓的物體的坐標(biāo)、迷宮終點(diǎn)坐標(biāo)、或者機(jī)器人要到達(dá)的姿態(tài)。當(dāng)你有目標(biāo)輸入時(shí)你就需要 value function 也帶上目標(biāo)維度即 Q(s, a, g)。這個(gè)被稱為 universal value function approximatorUVFA。有了這個(gè)設(shè)定你還得要求算法是 off-policy 的——因?yàn)?HER 會(huì)修改經(jīng)驗(yàn)軌跡的 goal 維度修改后的樣本和當(dāng)前 policy 采集的分布就不再一致只有 off-policy 的方法如 DDPG、TD3、SAC才能從這種“偽造的歷史”里學(xué)習(xí)。如果你用 A3C 這類 on-policy 方法硬套 HER效果會(huì)非常差甚至直接崩潰。這里有個(gè)容易忽略的點(diǎn)目標(biāo)重標(biāo)注后每個(gè) transition 的 reward 要重新計(jì)算。原本的軌跡是 (s_t, a_t, r_t, s_{t1}, g)。重新標(biāo)注為 g 之后你要用環(huán)境自帶的 reward 函數(shù)重新算一遍 r_t reward(s_t, a_t, s_{t1}, g)。注意這里必須調(diào)用真實(shí)的 reward 函數(shù)不能簡(jiǎn)單地把原來(lái)的 r_t 復(fù)制過(guò)來(lái)——否則就會(huì)出現(xiàn)“明明這個(gè) goal 沒達(dá)成卻給了成功獎(jiǎng)勵(lì)”的幻覺樣本。2.2 選什么樣的 hindsight goal這是 HER 實(shí)現(xiàn)里最值得琢磨的細(xì)節(jié)。一條軌跡里有很多個(gè)時(shí)間步每一步都有一個(gè)實(shí)際的狀態(tài) s_t。哪些狀態(tài)可以拿來(lái)做新的“事后目標(biāo)”常見的策略有這么幾種final把軌跡的最后一個(gè)狀態(tài)作為 goal。這是最經(jīng)典的方案比如機(jī)械臂最終碰到的位置。大部分場(chǎng)景下它已經(jīng)夠用而且實(shí)現(xiàn)最簡(jiǎn)單。random從軌跡里隨機(jī)抽取 k 個(gè)狀態(tài)作為候選 goal。適合目標(biāo)空間很大、只靠 final 一個(gè)點(diǎn)覆蓋不過(guò)來(lái)的場(chǎng)景。future從當(dāng)前時(shí)刻往后的狀態(tài)里隨機(jī)選一個(gè)。這個(gè)策略能提供一點(diǎn)“時(shí)序平滑性”policy 學(xué)到的不只是“終點(diǎn)目標(biāo)”還有“中間目標(biāo)”。實(shí)操中我一般建議先用 final如果任務(wù)的目標(biāo)分布特別分散再升級(jí)成 future 或 random。有一條經(jīng)驗(yàn)法則——你重標(biāo)注的 goal 數(shù)量 k 不要太多4 個(gè)左右通常就足夠。多了確實(shí)增加樣本量但也引入了大量不相關(guān)目標(biāo)Q 網(wǎng)絡(luò)擬合起來(lái)也會(huì)吃力。2.3 為什么能解決 sample efficiency 問(wèn)題如果你跑過(guò) RL 實(shí)驗(yàn)一定對(duì) sample efficiency 這個(gè)概念不陌生——它本質(zhì)上就是“一個(gè)樣本能被利用幾次”。普通 off-policy RL 里一個(gè) transition狀態(tài)、動(dòng)作、下一狀態(tài)、獎(jiǎng)勵(lì)、目標(biāo)只能參與一次 Q 更新學(xué)完就丟。HER 讓它參與的更新次數(shù)乘以 (k1)原始目標(biāo)一次重標(biāo)注的 k 個(gè)替代目標(biāo)各一次。有一次我在 FetchReach 上做過(guò)對(duì)比實(shí)驗(yàn)同一個(gè) DDPG序列長(zhǎng)度和網(wǎng)絡(luò)結(jié)構(gòu)完全一致只是加上了 HER 的 final 策略訓(xùn)練到相同成功率HER 的花費(fèi)時(shí)間只有原來(lái)的 1/10 左右。原因不難理解成功樣本不再稀缺梯度有東西可學(xué)。這其實(shí)就是把“失敗數(shù)據(jù)”變成了“隱形成功數(shù)據(jù)”等于讓 agent 的經(jīng)驗(yàn)利用率憑空多了好幾倍。3. 實(shí)操落地手寫一個(gè) HER 的核心模塊3.1 需要準(zhǔn)備的東西我們從一個(gè)最簡(jiǎn)可運(yùn)行的 FetchReach 場(chǎng)景開始它在 gymnasium 里可以直接用。環(huán)境里 agent 要控制機(jī)械臂末端去觸碰一個(gè)隨機(jī)位置的目標(biāo)點(diǎn)到達(dá)范圍內(nèi)即視為成功獎(jiǎng)勵(lì)為 1否則為 0。整個(gè)任務(wù)非常簡(jiǎn)單但它足以驗(yàn)證 HER 的收益。環(huán)境狀態(tài)有兩部分需要你特別留意observation機(jī)械臂自身狀態(tài) 末端位置 物體位置和 desired_goal當(dāng)前目標(biāo)坐標(biāo)。在 FetchReach 這類任務(wù)里不涉及物體抓取所以 observation 里有沒有物體位置其實(shí)影響不大。你構(gòu)建 transition 時(shí)需要保存的是stateobservationaction這一步執(zhí)行的動(dòng)作reward環(huán)境返回的即時(shí)獎(jiǎng)勵(lì)next_state執(zhí)行完動(dòng)作后的 observationgoal當(dāng)前的 desired_goal注意 FetchReach 環(huán)境里有個(gè) achieved_goal 字段它直接告訴你在每個(gè)時(shí)間步機(jī)械臂末端“實(shí)際上”達(dá)到了什么位置。在 HER 的 final 策略下軌跡最后一個(gè) achieved_goal 就是我們最想要的重標(biāo)注目標(biāo)來(lái)源。3.2 核心重標(biāo)注邏輯的代碼實(shí)現(xiàn)HER 本身不復(fù)雜核心就一個(gè)結(jié)構(gòu)化遍歷邏輯。下面這個(gè)代碼是我平時(shí)在用的簡(jiǎn)化版目標(biāo)是邏輯清晰你可以直接照著改造import numpy as np from collections import deque class HERBuffer: def __init__(self, capacity, k_future4): self.buffer deque(maxlencapacity) self.k_future k_future # 每條軌跡生成幾個(gè)替代目標(biāo) def store_episode(self, episode): # episode: list of dict包含 state / action / reward / next_state / goal / achieved_goal # 1. 先取出原始目標(biāo)對(duì)應(yīng)的 data存入 buffer for t, trans in enumerate(episode): self.buffer.append({ state: trans[state], action: trans[action], reward: trans[reward], next_state: trans[next_state], goal: trans[goal], }) # 2. 對(duì)同一條軌跡做目標(biāo)重標(biāo)注 for t, trans in enumerate(episode): # 從當(dāng)前時(shí)刻之后的時(shí)間步里隨機(jī)抽 k 個(gè)狀態(tài)作為替代目標(biāo) future_goals [] future_indices np.random.randint( t 1, len(episode), sizemin(self.k_future, len(episode) - t - 1) ) for idx in future_indices: future_goals.append(episode[idx][achieved_goal]) for new_goal in future_goals: # 用真實(shí) reward 函數(shù)重算獎(jiǎng)勵(lì) new_reward self.compute_reward(trans[next_state], new_goal) self.buffer.append({ state: trans[state], action: trans[action], reward: new_reward, next_state: trans[next_state], goal: new_goal, }) def compute_reward(self, next_state, goal): # 這是一般的歐氏距離判斷FetchReach 的目標(biāo)范圍是 0.05 achieved next_state[:3] # 假設(shè)前 3 維是末端位置 distance np.linalg.norm(achieved - goal) return 1.0 if distance 0.05 else 0.0 def sample(self, batch_size): indices np.random.choice(len(self.buffer), batch_size, replaceFalse) return [self.buffer[i] for i in indices]這段代碼里有三個(gè)地方值得注意一是future_indices的取值。原論文里推薦從 t1 到軌跡末尾隨機(jī)選而不是從整個(gè)軌跡選。原因是如果重標(biāo)注的 goal 來(lái)自過(guò)去的某個(gè)狀態(tài)那這個(gè) goal 在當(dāng)前時(shí)刻之前就已經(jīng)被“達(dá)成過(guò)”對(duì)當(dāng)前策略來(lái)說(shuō)它是“歷史成功目標(biāo)”時(shí)序上反而會(huì)產(chǎn)生矛盾信號(hào)。從未來(lái)選意味著“之后某個(gè)時(shí)刻我到達(dá)了某個(gè)位置”這符合因果邏輯。二是compute_reward必須用真實(shí)環(huán)境邏輯重算。我最開始偷懶直接把原獎(jiǎng)勵(lì)復(fù)制過(guò)去結(jié)果 Q 值嚴(yán)重高估agent 在測(cè)試時(shí)經(jīng)?!翱雌饋?lái)在動(dòng)”但成功率卡在 30% 上不去。用真 reward 函數(shù)重算是 HER 的一條鐵律。三是 buffer 的 organization。store_episode時(shí)先存原始樣本再存重標(biāo)注樣本。它們會(huì)在抽樣時(shí)混合在一起。這里需要保證一個(gè) mini-batch 里兩種樣本比例適中。我用下來(lái)的經(jīng)驗(yàn)是 1:1 左右即每個(gè) transition 額外生成 2~4 個(gè)重標(biāo)注樣本比較穩(wěn)。3.3 算法外的幾個(gè)關(guān)鍵參數(shù)HER 不是一個(gè)獨(dú)立的算法它是寄生在 off-policy RL 上的“改造插件”。我用得最多的搭配是 DDPG HER結(jié)構(gòu)簡(jiǎn)單、調(diào)試直觀。以下是幾個(gè)必須盯好的參數(shù)k_future替代目標(biāo)數(shù)。建議 4抓取類任務(wù)可以降到 2因?yàn)樽ト〉哪繕?biāo)本身就是離散的樣本多樣性不需要太多。reward thresholdFetchReach 里是 0.05 米。這個(gè)值要和你的動(dòng)作步長(zhǎng)匹配。如果步長(zhǎng)是 0.01 而閾值是 0.001agent 幾乎無(wú)法達(dá)到獎(jiǎng)勵(lì)信號(hào)依然稀疏。exploration noiseDDPG 里的探索噪聲一開始給大一點(diǎn)如 0.3到了訓(xùn)練中后期再衰減。HER 雖然能緩解稀疏獎(jiǎng)勵(lì)但如果探索力度太小軌跡連“接近目標(biāo)”都做不到重標(biāo)注出來(lái)的也都是遠(yuǎn)距離目標(biāo)。我踩過(guò)這個(gè)坑——在環(huán)境復(fù)雜度較高的任務(wù)里HER 并不能替代探索。4. 常見問(wèn)題與排查技巧實(shí)錄4.1 訓(xùn)練時(shí) Q 值持續(xù)震蕩這是我在實(shí)戰(zhàn)中遇到最多的問(wèn)題?,F(xiàn)象是訓(xùn)練曲線前期上升很快但到 20 萬(wàn)步左右開始劇烈波動(dòng)成功率不升反降。排查思路很直接——先檢查重標(biāo)注樣本的獎(jiǎng)勵(lì)有沒有算錯(cuò)。用一個(gè)簡(jiǎn)單腳本抽取 1000 條重標(biāo)注樣本手動(dòng)跑一遍環(huán)境里的compute_reward對(duì)比 buffer 里的 reward。只要不一致問(wèn)題一定出在重標(biāo)注重算上。如果獎(jiǎng)勵(lì)沒問(wèn)題再檢查 value function 是否對(duì)“近端目標(biāo)”過(guò)度樂觀。HER 里大量重標(biāo)注樣本的成功獎(jiǎng)勵(lì)來(lái)自“距離較近”的目標(biāo)Q 網(wǎng)絡(luò)很容易對(duì)這類樣本產(chǎn)生正反饋。我的做法是在 loss 里給 Q 加一個(gè)小的 L2 懲罰項(xiàng)讓 Q 值不要膨脹過(guò)快或者改用 TD3 的 clipped double-Q它會(huì)同時(shí)保留兩個(gè) Q 估計(jì)的最小值對(duì)虛假高估天然有抑制作用。4.2 重標(biāo)注出來(lái)的目標(biāo)全部無(wú)效有一種情況很隱蔽目標(biāo)空間很大而軌跡里的achieved_goal幾乎都在一個(gè)狹小區(qū)域內(nèi)變化。比如多自由度機(jī)械臂末端能到達(dá)的空間很大但一條軌跡里它只在小范圍內(nèi)抖動(dòng)。這時(shí)候你用 final 策略重標(biāo)注出的新目標(biāo)和原目標(biāo)差距不大樣本多樣性反而下降了。我的解決方案是混合策略一部分樣本用future另一部分從整個(gè) episode 的achieved_goal里隨機(jī)抽也就是random策略。這樣能盡量覆蓋更多目標(biāo)區(qū)域。這個(gè)方法在多物體抓取任務(wù)里效果尤其明顯。受限于篇幅我把最常碰到的幾個(gè)問(wèn)題整理成了速查表方便你對(duì)照現(xiàn)象可能原因排查方法訓(xùn)練早期 loss 不下降重標(biāo)注樣本比例太低檢查 store_episode 里重標(biāo)注樣本的寫入數(shù)量成功率震蕩劇烈Q 值高估換 TD3 / 加 Q 懲罰agent 學(xué)會(huì)了“刷中間目標(biāo)”卻不完成最終任務(wù)reward threshold 過(guò)松增大閾值或者修改 distance 計(jì)算方式需要大量 epoch 才收斂探索噪聲過(guò)小調(diào)大 exploration noise或改用 OU 噪聲測(cè)試時(shí)目標(biāo)稍微變化就失敗訓(xùn)練目標(biāo)分布單一在 store_episode 中加 random goal 策略4.3 “成功”之后馬上崩潰怎么辦這個(gè)現(xiàn)象很經(jīng)典訓(xùn)練集的目標(biāo)全部是“接近且有正獎(jiǎng)勵(lì)”一旦目標(biāo)稍微遠(yuǎn)一點(diǎn)策略瞬間失靈。這其實(shí)暴露了 HER 的一個(gè)內(nèi)在弱點(diǎn)——它依賴“目標(biāo)與實(shí)際結(jié)果”之間的分布一致性。如果訓(xùn)練時(shí)目標(biāo)分布和測(cè)試時(shí)分布差異太大HER 的優(yōu)勢(shì)反而變成了陷阱。我做的一個(gè)有效應(yīng)對(duì)是在訓(xùn)練中定期加入一些“完全隨機(jī)”的原始目標(biāo)不做重標(biāo)注。這些目標(biāo)不一定是可解的只為了讓 agent 見過(guò)更多目標(biāo)空間。實(shí)驗(yàn)結(jié)果表明這個(gè)方法讓最終測(cè)試成功率提高了 15% 左右。代價(jià)是訓(xùn)練時(shí)間拉長(zhǎng)了一些但換來(lái)的是更好的泛化能力值得。5. 從算法到工程HER 的遷移思路5.1 在真實(shí)系統(tǒng)里怎么選型如果你只是跑跑 gym 教程那 HER 怎么用都沒問(wèn)題。但一旦面對(duì)真實(shí)系統(tǒng)選型就要謹(jǐn)慎得多。我接觸過(guò)不少做工業(yè)機(jī)械臂抓取的朋友他們告訴我HER 真正發(fā)揮作用的場(chǎng)景是那些“有明確 goal 且可量化判斷成功與否”的任務(wù)比如點(diǎn)到點(diǎn)抓取、擺放、碼垛。而在那些 goal 難以定義的任務(wù)里比如“把零件裝配到合適的力度”HER 的價(jià)值會(huì)大打折扣。因?yàn)檠b配任務(wù)的成功往往取決于力/位混合控制目標(biāo)向量很難用一個(gè)簡(jiǎn)單的坐標(biāo)表示。我的建議是判斷一個(gè)任務(wù)適不適合用 HER可以問(wèn)三個(gè)問(wèn)題——有沒有辦法定義 achieved_goal即“當(dāng)前實(shí)際達(dá)成了什么狀態(tài)”能否被觀測(cè)到。有沒有辦法寫清楚的 success detector即“這個(gè)狀態(tài)下目標(biāo)達(dá)成了沒有”。這個(gè) goal 狀態(tài)是否是“軌跡中可到達(dá)的”而不是全局空間的任意點(diǎn)只有這三個(gè)問(wèn)題都是“是”的時(shí)候HER 才能放心引入。5.2 在其他領(lǐng)域里的啟發(fā)可能有人覺得 HER 只能用在強(qiáng)化學(xué)習(xí)但其實(shí)它背后的思路——從失敗數(shù)據(jù)里挖出“事后正確”在很多工程場(chǎng)景都有遷移價(jià)值。比如搜廣推場(chǎng)景里的 CTR 模型訓(xùn)練。傳統(tǒng)方法只把“用戶點(diǎn)擊”作為正樣本點(diǎn)擊率低的曝光樣本一律當(dāng)成負(fù)樣本。但 HER 的思路提示我們?nèi)绻粋€(gè)用戶沒點(diǎn)擊但他停留時(shí)間長(zhǎng)、或者點(diǎn)擊了相似物品其實(shí)也可以重標(biāo)定為“對(duì)這個(gè)物品的偏好”從而把原本的負(fù)樣本盤活成正樣本。這就是“用行為結(jié)果重寫標(biāo)簽”的遷移思路。同樣在運(yùn)維故障復(fù)盤里也有類似的邏輯事后復(fù)盤的時(shí)候我們常常會(huì)問(wèn)“如果當(dāng)時(shí)我換了另外一種策略是不是就能避免這個(gè)故障”。這種反事實(shí)分析在強(qiáng)化學(xué)習(xí)里的對(duì)應(yīng)實(shí)現(xiàn)就是讓 agent 在失敗軌跡里換一個(gè)目標(biāo)去重新學(xué)習(xí)。一旦你習(xí)慣了這個(gè)思維范式會(huì)發(fā)現(xiàn)在數(shù)據(jù)驅(qū)動(dòng)的工作里HER 的遷移價(jià)值幾乎無(wú)處不在。最后分享一個(gè)我調(diào) HER 時(shí)的個(gè)人體會(huì)遇到不收斂的問(wèn)題別急著改網(wǎng)絡(luò)結(jié)構(gòu)、調(diào)學(xué)習(xí)率先檢查你的樣本重標(biāo)注邏輯是否 100% 正確。因?yàn)?HER 是個(gè)容易“靜默失敗”的算法——如果你的 reward 算錯(cuò)了它表面上還在訓(xùn)練loss 也在下降但最終策略一定是個(gè)廢品。我后來(lái)給所有 HER 實(shí)驗(yàn)都加了一條自動(dòng)化檢查每 5000 個(gè) episode 抽樣驗(yàn)證一次 buffer 里所有重標(biāo)注樣本的 reward 是否與真實(shí)環(huán)境一致。有了這一條我調(diào)參效率提升了一倍都不止。