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