習實戰(zhàn):連續(xù)動作空間Python工具箱)
1. 從“數(shù)據(jù)”到“連續(xù)動作”到底在解決什么問題連續(xù)動作空間的強化學(xué)習一直是讓不少人頭疼的方向。離散動作還能用Q表格、DQN那一套枚舉但一旦動作變成機械臂關(guān)節(jié)角度、無人機推力、自動駕駛的油門剎車轉(zhuǎn)向動作維度變成連續(xù)實數(shù)傳統(tǒng)值函數(shù)方法立刻就不夠用了。這個系列走到第四篇核心要解決的就是怎么用基于模型的方法把一批離線數(shù)據(jù)變成能輸出連續(xù)動作的策略并且用Python工具箱把它跑通。我最初接觸這個方向的時候踩過一個很典型的坑直接拿離散動作的DQN改一改把輸出層換成連續(xù)值結(jié)果訓(xùn)練完全不收斂。后來才明白連續(xù)動作空間里策略梯度和基于模型的方法才是正路。而基于模型的強化學(xué)習MBRL最大的吸引力在于樣本效率——真實環(huán)境交互一次可能很貴機器人磨損、自動駕駛危險、工業(yè)設(shè)備損耗但如果能學(xué)一個環(huán)境動力學(xué)模型就可以在“想象”中大量采樣把數(shù)據(jù)利用率拉滿。這篇內(nèi)容適合誰如果你已經(jīng)寫過基礎(chǔ)的強化學(xué)習代碼知道什么是狀態(tài)、動作、獎勵但一到連續(xù)控制就卡殼或者你手頭有一批離線軌跡數(shù)據(jù)想把它變成可用的控制器再或者你只是想找一個能直接跑的Python工具箱把MBRL的流程走一遍——那這篇就是給你準備的。我會從整體設(shè)計思路講起然后拆核心細節(jié)、實操步驟、參數(shù)選擇最后把常見坑和排查方法整理成速查表。全程用從業(yè)者視角不繞彎子。2. 整體設(shè)計與思路拆解2.1 為什么選“基于模型”而不是純無模型先說清楚一個選擇邏輯。無模型方法比如PPO、SAC在連續(xù)動作上確實能work但它們對樣本的胃口極大。我實測過一個六自由度機械臂的到達任務(wù)SAC大概需要幾十萬到上百萬步交互才能穩(wěn)定而基于模型的方法在幾萬步量級就能看到像樣的策略。差距在哪無模型方法把“環(huán)境規(guī)律”隱式地編碼在策略網(wǎng)絡(luò)里每一步都要靠真實交互去糾偏基于模型的方法則顯式地學(xué)一個動力學(xué)模型然后讓策略在模型里反復(fù)試錯。這里有個關(guān)鍵權(quán)衡模型誤差會累積。你學(xué)的動力學(xué)模型不可能完美策略在模型里表現(xiàn)好放到真實環(huán)境可能崩。所以MBRL的工程重點一半在模型精度一半在怎么控制“模型利用”的度。常見做法是限制策略在模型里的 rollout 長度或者用模型集成ensemble來估計不確定性不確定性高的地方就少信模型、多信真實數(shù)據(jù)。我個人的經(jīng)驗是如果你的真實環(huán)境交互成本高、數(shù)據(jù)量有限MBRL的性價比明顯更高如果環(huán)境便宜、可以隨便采樣那無模型方法省心。這個判斷決定了你整個工具箱的架構(gòu)方向。2.2 工具箱的整體架構(gòu)分層這個Python工具箱我把它分成四層從下到上依次是數(shù)據(jù)層負責離線軌跡的加載、清洗、歸一化。連續(xù)動作對數(shù)據(jù)尺度非常敏感關(guān)節(jié)角度可能是[-π, π]推力可能是[0, 1000]不歸一化直接喂網(wǎng)絡(luò)梯度會炸。模型層動力學(xué)模型輸入是狀態(tài)動作輸出是下一狀態(tài)和獎勵。我一般用概率神經(jīng)網(wǎng)絡(luò)集成輸出均值和方差方差用來做不確定性懲罰。策略層連續(xù)動作策略網(wǎng)絡(luò)輸出高斯分布的均值和標準差或者直接用確定性策略加噪聲。訓(xùn)練層把模型rollout和真實數(shù)據(jù)混合做策略優(yōu)化同時定期用真實數(shù)據(jù)更新模型。這個分層的好處是每一層可以獨立替換。比如你動力學(xué)模型想換成高斯過程只動模型層策略想換成擴散策略只動策略層。工程上解耦調(diào)試起來省心。2.3 連續(xù)動作輸出的核心設(shè)計考量連續(xù)動作和離散動作最大的區(qū)別在于輸出形式。離散動作是softmax出一組概率連續(xù)動作通常有兩種高斯策略網(wǎng)絡(luò)輸出均值μ和標準差σ動作從N(μ, σ2)采樣。優(yōu)點是天然帶探索缺點是σ需要約束用softplus或exp否則容易塌縮或爆炸。確定性策略外部噪聲網(wǎng)絡(luò)直接輸出動作訓(xùn)練時加OU噪聲或高斯噪聲。優(yōu)點是推理時干凈缺點是探索靠外部噪聲調(diào)參麻煩。我在這個工具箱里默認用高斯策略因為它在MBRL里和模型的不確定性結(jié)合得更自然。標準差我用softplus加一個下界比如1e-3防止訓(xùn)練早期σ直接歸零導(dǎo)致完全不探索。這個細節(jié)后面實操部分會展開。3. 核心細節(jié)解析與實操要點3.1 動力學(xué)模型集成網(wǎng)絡(luò)與不確定性估計動力學(xué)模型是整個MBRL的心臟。我用的結(jié)構(gòu)是概率集成網(wǎng)絡(luò)訓(xùn)練N個通常5到7個獨立的神經(jīng)網(wǎng)絡(luò)每個網(wǎng)絡(luò)輸出下一狀態(tài)的均值和方差。預(yù)測時把所有網(wǎng)絡(luò)的均值平均作為預(yù)測把網(wǎng)絡(luò)之間的分歧作為不確定性。為什么用集成而不是單個貝葉斯網(wǎng)絡(luò)因為集成實現(xiàn)簡單、并行友好而且實測不確定性估計質(zhì)量夠用。單個貝葉斯網(wǎng)絡(luò)比如MC Dropout在連續(xù)控制任務(wù)上往往低估不確定性導(dǎo)致策略鉆空子。模型訓(xùn)練的損失函數(shù)是負對數(shù)似然# 概率集成網(wǎng)絡(luò)的單網(wǎng)損失 def nll_loss(mu, log_var, target): # mu, log_var: 網(wǎng)絡(luò)輸出 # target: 真實下一狀態(tài) precision torch.exp(-log_var) return torch.mean(0.5 * precision * (target - mu)**2 0.5 * log_var)這里有個實操要點log_var要裁剪。我一般把log_var限制在[-10, 2]之間防止方差過大導(dǎo)致?lián)p失被忽略或者方差過小導(dǎo)致數(shù)值不穩(wěn)定。這個范圍是試出來的太小模型不敢表達不確定性太大訓(xùn)練發(fā)散。3.2 數(shù)據(jù)預(yù)處理歸一化與軌跡切分連續(xù)動作的數(shù)據(jù)預(yù)處理我踩過最深的坑就是歸一化不一致。訓(xùn)練模型時用一套均值和方差訓(xùn)練策略時又用另一套結(jié)果策略在模型里表現(xiàn)好一放到真實環(huán)境就廢。正確做法是把歸一化參數(shù)存下來模型和策略共用同一套。具體步驟把所有狀態(tài)拼成矩陣算每個維度的均值和標準差。動作同理但注意動作的歸一化范圍要和策略輸出范圍對齊。獎勵一般不做歸一化但可以做一個縮放比如除以最大獎勵讓價值網(wǎng)絡(luò)好訓(xùn)。軌跡切分也很關(guān)鍵。離線數(shù)據(jù)往往是一條條長軌跡直接整條喂給模型梯度會受長序列影響。我一般切成固定長度比如20到50步的片段既能捕捉短期動態(tài)又不會讓模型負擔太重。切分時注意不要跨軌跡邊界否則模型會學(xué)到不存在的轉(zhuǎn)移。3.3 策略優(yōu)化在模型里rollout的正確姿勢策略優(yōu)化的核心循環(huán)是從真實數(shù)據(jù)里采一批初始狀態(tài)在模型里rollout H步用這H步的獎勵和做策略梯度。這里H的選擇很講究H太小比如1到3策略看不到長期后果學(xué)不到需要多步規(guī)劃的任務(wù)。H太大比如50以上模型誤差累積策略會利用模型錯誤真實表現(xiàn)崩。我的經(jīng)驗值是H取10到20具體看任務(wù)的時間尺度。如果是快速反應(yīng)任務(wù)比如平衡H可以小如果是規(guī)劃型任務(wù)比如導(dǎo)航H要大一些但要配合不確定性懲罰。不確定性懲罰怎么加在rollout時每一步都用集成網(wǎng)絡(luò)的分歧算一個懲罰項加到獎勵里# rollout時的不確定性懲罰 def penalized_reward(reward, ensemble_disagreement, beta1.0): return reward - beta * ensemble_disagreementbeta是懲罰系數(shù)我一般從0.1開始調(diào)太大策略會變得過于保守太小又壓不住模型利用。4. 實操過程與核心環(huán)節(jié)實現(xiàn)4.1 環(huán)境準備與依賴安裝先把環(huán)境搭起來。這個工具箱依賴PyTorch、NumPy、Gym或Gymnasium。我推薦用conda建一個干凈環(huán)境避免和系統(tǒng)Python打架。conda create -n mbrl python3.9 conda activate mbrl pip install torch numpy gymnasium matplotlib如果你要用MuJoCo環(huán)境做連續(xù)控制測試還需要額外裝mujoco和gymnasium的mujoco擴展。注意版本匹配PyTorch和CUDA的對應(yīng)關(guān)系去官網(wǎng)查別裝錯。提示Python 3.9是比較穩(wěn)的選擇3.10以上有些老庫會有兼容問題。如果你只是跑這個工具箱3.9足夠。4.2 數(shù)據(jù)加載與歸一化實現(xiàn)假設(shè)你有一批離線軌跡存成npz或者pickle。加載后先做統(tǒng)計import numpy as np def compute_normalizer(states, actions): state_mean states.mean(axis0) state_std states.std(axis0) 1e-6 action_mean actions.mean(axis0) action_std actions.std(axis0) 1e-6 return { state_mean: state_mean, state_std: state_std, action_mean: action_mean, action_std: action_std } def normalize(x, mean, std): return (x - mean) / std def denormalize(x, mean, std): return x * std mean這里1e-6是防止除零。歸一化參數(shù)一定要存盤推理時用同一套。我見過有人訓(xùn)練時歸一化、推理時忘了結(jié)果動作輸出完全不對排查了半天。4.3 動力學(xué)模型訓(xùn)練完整流程模型訓(xùn)練我一般跑200到500個epoch每個epoch隨機采一批片段。關(guān)鍵參數(shù)參數(shù)推薦值說明集成數(shù)量5-7太少不確定性不準太多訓(xùn)練慢隱藏層2層×256連續(xù)控制夠用再大容易過擬合學(xué)習率3e-4Adam默認實測穩(wěn)batch size256片段數(shù)×片段長度訓(xùn)練輪數(shù)300看驗證損失早停訓(xùn)練循環(huán)大致這樣for epoch in range(num_epochs): for batch in dataloader: states, actions, next_states batch total_loss 0 for model in ensemble: mu, log_var model(states, actions) log_var torch.clamp(log_var, -10, 2) loss nll_loss(mu, log_var, next_states) total_loss loss optimizer.zero_grad() total_loss.backward() optimizer.step()驗證集要單獨留看模型在沒見過的數(shù)據(jù)上的預(yù)測誤差。如果驗證損失一直降不下去說明模型容量不夠或者數(shù)據(jù)有問題。4.4 策略訓(xùn)練與模型交互策略訓(xùn)練是外層循環(huán)。每一輪從真實數(shù)據(jù)采初始狀態(tài)。在模型里rollout H步記錄獎勵和不確定性。算策略梯度更新策略網(wǎng)絡(luò)。每隔幾輪用真實環(huán)境采一批新數(shù)據(jù)更新模型。這里有個工程細節(jié)模型更新和策略更新的頻率。我一般策略更新5到10次模型更新1次。因為模型更新太頻繁策略剛適應(yīng)舊模型模型一變又得重來訓(xùn)練不穩(wěn)定。策略網(wǎng)絡(luò)輸出高斯分布采樣動作def sample_action(policy, state): mu, log_std policy(state) std torch.exp(log_std) dist torch.distributions.Normal(mu, std) action dist.rsample() # 重參數(shù)化采樣 return action, dist.log_prob(action).sum(dim-1)用rsample而不是sample是為了讓梯度能回傳。log_std同樣要裁剪我一般限制在[-5, 0]對應(yīng)std在[0.0067, 1]之間。5. 常見問題與排查技巧實錄5.1 訓(xùn)練不收斂的典型原因連續(xù)動作MBRL訓(xùn)練不收斂我總結(jié)下來主要是這幾類歸一化問題狀態(tài)或動作尺度差異大網(wǎng)絡(luò)學(xué)不動。排查方法打印歸一化后的數(shù)據(jù)統(tǒng)計均值接近0、標準差接近1才對。學(xué)習率過大模型或策略損失震蕩。先把學(xué)習率降到1e-4試試。不確定性懲罰系數(shù)不當beta太大策略不探索太小策略鉆模型空子。觀察rollout獎勵和真實獎勵的差距差距大就加大beta。rollout長度過長模型誤差累積。把H從20降到10看看。5.2 模型預(yù)測誤差大的排查模型預(yù)測不準先看是訓(xùn)練誤差大還是驗證誤差大訓(xùn)練誤差大模型容量不夠或訓(xùn)練不夠。加寬網(wǎng)絡(luò)、加訓(xùn)練輪數(shù)。驗證誤差大過擬合。加正則化、減網(wǎng)絡(luò)容量、加數(shù)據(jù)。兩者都大數(shù)據(jù)本身有問題。檢查數(shù)據(jù)里有沒有異常值、缺失值軌跡切分有沒有跨邊界。我遇到過一次模型怎么訓(xùn)都誤差大最后發(fā)現(xiàn)是數(shù)據(jù)采集時狀態(tài)記錄有延遲狀態(tài)和動作對不齊。這種問題只能靠檢查數(shù)據(jù)源頭解決。5.3 策略在模型里好、真實環(huán)境崩這是MBRL最經(jīng)典的坑。原因基本是模型利用策略找到了模型預(yù)測錯誤的地方在那里刷高獎勵。解決辦法加大不確定性懲罰beta。縮短rollout長度H。增加集成數(shù)量讓不確定性估計更準。混合真實數(shù)據(jù)和模型數(shù)據(jù)訓(xùn)練策略別純靠模型。我一般會監(jiān)控一個指標策略在模型里的平均獎勵 vs 在真實環(huán)境的平均獎勵。差距超過30%就要警惕了。5.4 常見問題速查表現(xiàn)象可能原因排查方向損失NaN學(xué)習率過大、log_var未裁剪降學(xué)習率、加clamp策略不探索log_std塌縮、beta過大檢查log_std下界、降beta模型誤差大數(shù)據(jù)未歸一化、容量不足檢查歸一化、加網(wǎng)絡(luò)寬度真實環(huán)境崩模型利用加beta、減H、加集成訓(xùn)練慢集成太多、rollout太長減集成數(shù)、減H注意調(diào)參時一次只動一個別同時改學(xué)習率、beta和H否則出了問題不知道是誰的鍋。6. 工具箱擴展與實戰(zhàn)建議6.1 從單任務(wù)到多任務(wù)的擴展思路這個工具箱目前是單任務(wù)設(shè)計但實際項目里經(jīng)常要處理多任務(wù)。擴展方向有兩個條件策略把任務(wù)ID作為額外輸入喂給策略和模型一個網(wǎng)絡(luò)處理多個任務(wù)。優(yōu)點是參數(shù)共享缺點是任務(wù)差異大時互相干擾。多模型多策略每個任務(wù)一套模型和策略互不干擾。優(yōu)點是干凈缺點是數(shù)據(jù)利用率低。我一般先用條件策略試如果任務(wù)間沖突明顯再拆開。判斷沖突的方法很簡單看多任務(wù)訓(xùn)練時單任務(wù)性能比單獨訓(xùn)練差多少差太多就拆。6.2 真實機器人部署的注意事項如果你要把訓(xùn)練好的策略部署到真實機器人有幾個坑必須提前防推理頻率策略網(wǎng)絡(luò)推理要夠快一般要求10Hz以上。如果太慢考慮模型量化或剪枝。安全邊界動作輸出一定要加限幅防止網(wǎng)絡(luò)輸出異常值把機器人搞壞。狀態(tài)估計誤差真實環(huán)境的狀態(tài)估計有噪聲訓(xùn)練時最好加噪聲增強讓策略對狀態(tài)噪聲魯棒。我部署過一次機械臂策略訓(xùn)練時狀態(tài)是完美的真實環(huán)境編碼器有噪聲策略抖動明顯。后來在訓(xùn)練時給狀態(tài)加了高斯噪聲問題解決。6.3 后續(xù)可以深入的方向這個工具箱跑通之后可以往幾個方向深挖一是把動力學(xué)模型換成更表達力強的結(jié)構(gòu)比如Transformer處理長序列依賴二是引入離線強化學(xué)習的保守估計讓策略在數(shù)據(jù)分布外更保守三是做模型和策略的聯(lián)合優(yōu)化而不是分開訓(xùn)練。每個方向都有不少論文和開源實現(xiàn)可以參考但核心流程和這個工具箱是一致的。我個人在實際操作中的體會是MBRL的難點不在算法本身而在工程細節(jié)歸一化、不確定性、rollout長度、更新頻率這些看起來不起眼的地方往往決定了訓(xùn)練能不能收斂。把這篇里的參數(shù)和排查表存下來遇到問題對著查能省不少時間。