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