戰(zhàn):MAPPO多智能體強(qiáng)化學(xué)習(xí)訓(xùn)練simple_spread)
1. 車聯(lián)網(wǎng)場(chǎng)景下的多智能體協(xié)作到底難在哪車聯(lián)網(wǎng)這個(gè)方向我斷斷續(xù)續(xù)跟了快三年從最早的單車ADAS仿真到后來做V2X消息廣播再到最近這一年扎進(jìn)多智能體強(qiáng)化學(xué)習(xí)踩的坑比走的路還多。這次要聊的是我最近在跑的一個(gè)實(shí)戰(zhàn)項(xiàng)目用RLlib訓(xùn)練MAPPO算法解決simple_spread這個(gè)經(jīng)典合作任務(wù)。說白了就是讓多個(gè)智能體在一個(gè)共享空間里學(xué)會(huì)分工協(xié)作各自跑到不同的目標(biāo)點(diǎn)同時(shí)還要避免互相碰撞。這個(gè)任務(wù)看起來簡(jiǎn)單但它幾乎是所有多智能體協(xié)作場(chǎng)景的最小可行原型——車聯(lián)網(wǎng)里的多車協(xié)同變道、交叉路口無信號(hào)燈通行、編隊(duì)行駛本質(zhì)上都是simple_spread的復(fù)雜化版本。為什么選RLlib而不是自己手寫訓(xùn)練循環(huán)因?yàn)槎嘀悄荏w強(qiáng)化學(xué)習(xí)的工程復(fù)雜度遠(yuǎn)超單智能體。你需要處理環(huán)境并行采樣、策略共享與獨(dú)立、信用分配、非平穩(wěn)性等一系列問題。RLlib作為Ray生態(tài)下的分布式強(qiáng)化學(xué)習(xí)框架原生支持多智能體環(huán)境接口內(nèi)置了PPO、IMPALA、QMIX等常用算法而且它的多智能體API設(shè)計(jì)得相對(duì)干凈不需要你從零搭建分布式采樣和梯度聚合的管道。MAPPO本身是PPO在多智能體場(chǎng)景下的適配版本核心思想是集中式訓(xùn)練、分布式執(zhí)行——訓(xùn)練時(shí)critic能看到全局狀態(tài)執(zhí)行時(shí)actor只依賴局部觀測(cè)。這個(gè)設(shè)計(jì)在合作型任務(wù)里非常實(shí)用因?yàn)楹献魅蝿?wù)中每個(gè)智能體都需要知道隊(duì)友在干什么但執(zhí)行階段又不能依賴全局信息。這篇文章適合誰(shuí)看如果你已經(jīng)了解PPO的基本原理知道什么是actor-critic架構(gòu)也跑過至少一個(gè)單智能體的強(qiáng)化學(xué)習(xí)demo那接下來的內(nèi)容你可以直接抄作業(yè)。如果你對(duì)多智能體完全沒概念也沒關(guān)系我會(huì)在關(guān)鍵節(jié)點(diǎn)補(bǔ)充必要的背景知識(shí)保證你能跟上節(jié)奏。整篇內(nèi)容會(huì)圍繞一個(gè)核心目標(biāo)展開讓你能在自己的機(jī)器上用RLlib跑通MAPPO訓(xùn)練simple_spread并且理解每一步為什么這么做。2. 環(huán)境搭建與RLlib多智能體接口解析2.1 為什么選simple_spread作為入門任務(wù)simple_spread來自多智能體粒子環(huán)境Multi-Agent Particle Environment簡(jiǎn)稱MPE是OpenAI早期開源的一套輕量級(jí)多智能體測(cè)試平臺(tái)。它的場(chǎng)景設(shè)置很直觀一個(gè)二維平面上有N個(gè)智能體通常N3和N個(gè)地標(biāo)landmark每個(gè)智能體需要移動(dòng)到一個(gè)地標(biāo)附近并保持覆蓋同時(shí)盡量不與其他智能體發(fā)生碰撞。獎(jiǎng)勵(lì)函數(shù)由三部分組成地標(biāo)覆蓋率每個(gè)地標(biāo)被至少一個(gè)智能體覆蓋時(shí)給正獎(jiǎng)勵(lì)、碰撞懲罰智能體之間距離過近時(shí)給負(fù)獎(jiǎng)勵(lì)、以及可選的移動(dòng)成本。這個(gè)任務(wù)之所以適合入門是因?yàn)樗臓顟B(tài)空間和動(dòng)作空間都是連續(xù)的但維度很低。每個(gè)智能體的觀測(cè)通常包括自身位置、速度、以及相對(duì)于其他智能體和地標(biāo)的相對(duì)位置。動(dòng)作是二維的力向量控制智能體在平面上的加速度。低維度意味著訓(xùn)練收斂快你不需要等幾個(gè)小時(shí)才能看到效果。同時(shí)simple_spread又保留了多智能體協(xié)作的核心難點(diǎn)信用分配哪個(gè)智能體的動(dòng)作導(dǎo)致了團(tuán)隊(duì)獎(jiǎng)勵(lì)的變化、非平穩(wěn)性其他智能體的策略在訓(xùn)練過程中不斷變化、以及探索與利用的平衡。我試過用其他環(huán)境入門比如星際爭(zhēng)霸的SMAC或者谷歌足球但這些環(huán)境的安裝配置太折騰而且訓(xùn)練周期長(zhǎng)不適合快速驗(yàn)證算法。simple_spread的另一個(gè)好處是它已經(jīng)被集成到了PettingZoo這個(gè)多智能體環(huán)境庫(kù)里而RLlib又支持PettingZoo接口所以整個(gè)鏈路是通的。2.2 RLlib的多智能體環(huán)境接口長(zhǎng)什么樣RLlib處理多智能體環(huán)境的方式和單智能體有本質(zhì)區(qū)別。在單智能體場(chǎng)景下環(huán)境返回一個(gè)觀測(cè)策略輸出一個(gè)動(dòng)作。但在多智能體場(chǎng)景下你需要告訴RLlib有哪些智能體、每個(gè)智能體的觀測(cè)空間和動(dòng)作空間是什么、它們是否共享策略、以及如何將環(huán)境返回的字典映射到各個(gè)策略上。RLlib的多智能體API核心是一個(gè)字典結(jié)構(gòu)通常叫policies它定義了策略ID到策略配置的映射。每個(gè)策略配置包含policy_class用哪個(gè)策略類、observation_space、action_space、以及config策略超參數(shù)。另一個(gè)關(guān)鍵參數(shù)是policy_mapping_fn它是一個(gè)函數(shù)輸入智能體ID輸出該智能體應(yīng)該使用哪個(gè)策略ID。如果你想讓所有智能體共享同一個(gè)策略就讓這個(gè)函數(shù)對(duì)所有智能體返回同一個(gè)策略ID如果你想讓每個(gè)智能體有獨(dú)立的策略就返回不同的ID。在simple_spread里我通常采用參數(shù)共享的策略也就是所有智能體共用一套網(wǎng)絡(luò)參數(shù)。這樣做的好處是樣本效率高因?yàn)樗兄悄荏w的經(jīng)驗(yàn)都用來更新同一個(gè)策略。壞處是如果智能體之間的角色差異很大共享策略可能會(huì)限制表達(dá)能力。不過在simple_spread這種同構(gòu)任務(wù)里參數(shù)共享是標(biāo)準(zhǔn)做法。環(huán)境返回的觀測(cè)通常是一個(gè)字典鍵是智能體ID值是觀測(cè)數(shù)組。RLlib會(huì)自動(dòng)根據(jù)policy_mapping_fn將每個(gè)智能體的觀測(cè)路由到對(duì)應(yīng)的策略。動(dòng)作的輸出也是一個(gè)字典鍵是智能體ID值是動(dòng)作數(shù)組。這里有一個(gè)容易踩的坑如果你的環(huán)境是PettingZoo風(fēng)格的它返回的觀測(cè)和動(dòng)作可能是嵌套字典需要做一層轉(zhuǎn)換。我建議在環(huán)境包裝器里統(tǒng)一處理成RLlib期望的格式避免在訓(xùn)練循環(huán)里做復(fù)雜的適配。2.3 安裝依賴與版本兼容性避坑RLlib的版本迭代很快不同版本之間的API差異不小。我目前用的是Ray 2.x系列具體是2.9.0這個(gè)版本對(duì)多智能體API的支持比較穩(wěn)定。安裝命令很簡(jiǎn)單pip install ray[rllib]2.9.0 pip install pettingzoo[mpe]1.24.0 pip install supersuit3.9.3這里有幾個(gè)版本兼容性的坑要提醒。第一PettingZoo的MPE環(huán)境在1.24.0版本里返回的觀測(cè)是字典形式但RLlib期望的是按智能體ID索引的字典所以需要寫一個(gè)包裝器做轉(zhuǎn)換。第二supersuit這個(gè)庫(kù)用來做觀測(cè)和動(dòng)作的預(yù)處理比如把離散動(dòng)作轉(zhuǎn)成連續(xù)動(dòng)作的格式或者對(duì)觀測(cè)做歸一化。第三如果你用的是Windows系統(tǒng)Ray的某些版本在安裝時(shí)可能會(huì)報(bào)編譯錯(cuò)誤建議用WSL2或者直接上Linux。還有一個(gè)隱藏的坑RLlib的MultiAgentEnv基類要求環(huán)境實(shí)現(xiàn)reset()和step()方法并且返回的字典必須包含__all__鍵表示所有智能體的信息。如果你直接用PettingZoo的環(huán)境它不滿足這個(gè)要求所以必須寫一個(gè)適配器。我當(dāng)時(shí)的做法是繼承MultiAgentEnv在reset()里調(diào)用PettingZoo環(huán)境的reset()然后把返回值整理成RLlib期望的格式。3. MAPPO算法核心邏輯與RLlib配置實(shí)戰(zhàn)3.1 MAPPO和普通PPO的本質(zhì)區(qū)別PPO大家應(yīng)該都熟核心是 clipped surrogate objective通過限制策略更新的幅度來保證訓(xùn)練穩(wěn)定性。MAPPO在PPO的基礎(chǔ)上做了兩個(gè)關(guān)鍵改動(dòng)。第一critic網(wǎng)絡(luò)接收全局狀態(tài)而不是局部觀測(cè)。在simple_spread里全局狀態(tài)可以是所有智能體位置的拼接而局部觀測(cè)只包含當(dāng)前智能體能看到的信息。這個(gè)改動(dòng)讓critic能夠更準(zhǔn)確地估計(jì)狀態(tài)價(jià)值因?yàn)楹献魅蝿?wù)中團(tuán)隊(duì)獎(jiǎng)勵(lì)取決于所有智能體的聯(lián)合動(dòng)作。第二MAPPO通常采用參數(shù)共享的actor網(wǎng)絡(luò)但critic可以是共享的也可以是獨(dú)立的。在我的實(shí)現(xiàn)里actor和critic都共享參數(shù)因?yàn)閟imple_spread的智能體是同構(gòu)的。為什么集中式critic有效打個(gè)比方你在踢足球每個(gè)球員只能看到自己周圍的情況局部觀測(cè)但教練在場(chǎng)邊能看到全場(chǎng)全局狀態(tài)。教練根據(jù)全場(chǎng)情況判斷這次進(jìn)攻值不值得然后告訴球員該怎么調(diào)整。MAPPO的critic就是那個(gè)教練actor是球員。訓(xùn)練時(shí)教練給反饋比賽時(shí)球員自己決策。這里有一個(gè)理論上的細(xì)節(jié)MAPPO的收斂性保證依賴于集中式critic能夠準(zhǔn)確估計(jì)聯(lián)合狀態(tài)價(jià)值。如果全局狀態(tài)設(shè)計(jì)得不好比如遺漏了關(guān)鍵信息critic的估計(jì)就會(huì)有偏差導(dǎo)致策略更新方向錯(cuò)誤。所以在simple_spread里我通常把全局狀態(tài)設(shè)計(jì)為所有智能體位置和速度的拼接再加上地標(biāo)位置。這個(gè)信息量足夠critic做出準(zhǔn)確判斷。3.2 RLlib中MAPPO的配置參數(shù)逐項(xiàng)拆解RLlib沒有直接叫“MAPPO”的算法但你可以通過配置PPO來實(shí)現(xiàn)MAPPO的效果。核心配置項(xiàng)如下from ray.rllib.algorithms.ppo import PPOConfig config ( PPOConfig() .environment(envsimple_spread_v2, env_config{N: 3}) .framework(torch) .training( train_batch_size4000, sgd_minibatch_size128, num_sgd_iter10, lr5e-4, gamma0.99, lambda_0.95, clip_param0.2, vf_clip_param10.0, entropy_coeff0.01, vf_loss_coeff1.0, ) .multi_agent( policies{shared_policy}, policy_mapping_fnlambda agent_id, episode, **kwargs: shared_policy, ) .resources(num_gpus1) )逐項(xiàng)解釋一下。train_batch_size4000表示每次迭代采樣4000個(gè)時(shí)間步。這個(gè)值不能太小否則梯度估計(jì)方差大也不能太大否則訓(xùn)練速度慢。4000是我在simple_spread上試出來的平衡點(diǎn)。sgd_minibatch_size128是每次SGD更新用的樣本數(shù)num_sgd_iter10表示對(duì)同一批數(shù)據(jù)重復(fù)訓(xùn)練10輪。lr5e-4是學(xué)習(xí)率比單智能體PPO常用的3e-4稍大因?yàn)槎嘀悄荏w任務(wù)的梯度噪聲更大。clip_param0.2是PPO的標(biāo)準(zhǔn)裁剪系數(shù)。entropy_coeff0.01控制探索強(qiáng)度太小會(huì)導(dǎo)致過早收斂到次優(yōu)策略太大則訓(xùn)練不穩(wěn)定。vf_clip_param10.0是價(jià)值函數(shù)裁剪防止value loss爆炸。multi_agent部分的配置是關(guān)鍵。policies{shared_policy}定義了一個(gè)策略ID叫shared_policy但沒有指定具體的策略類RLlib會(huì)用默認(rèn)的PPO策略。policy_mapping_fn是一個(gè)lambda函數(shù)對(duì)所有智能體返回同一個(gè)策略ID實(shí)現(xiàn)參數(shù)共享。這里有一個(gè)容易忽略的細(xì)節(jié)RLlib的PPO默認(rèn)使用GaussianTorchPolicy也就是連續(xù)動(dòng)作空間的高斯策略。simple_spread的動(dòng)作空間是連續(xù)的所以不需要改。如果你的任務(wù)是離散動(dòng)作需要顯式指定policy_class為PPOTorchPolicy并配置動(dòng)作分布。3.3 全局狀態(tài)與局部觀測(cè)的分離設(shè)計(jì)MAPPO的核心是critic用全局狀態(tài)actor用局部觀測(cè)。在RLlib里這個(gè)分離是通過model配置里的custom_model實(shí)現(xiàn)的。你需要自定義一個(gè)模型讓它的forward方法同時(shí)接收觀測(cè)和狀態(tài)然后分別輸出動(dòng)作分布和價(jià)值估計(jì)。具體做法是繼承TorchModelV2在__init__里定義actor網(wǎng)絡(luò)和critic網(wǎng)絡(luò)。actor網(wǎng)絡(luò)的輸入是局部觀測(cè)輸出是動(dòng)作分布的參數(shù)均值和方差。critic網(wǎng)絡(luò)的輸入是全局狀態(tài)輸出是一個(gè)標(biāo)量?jī)r(jià)值。然后在forward方法里根據(jù)input_dict里的obs和state分別前向傳播。這里有一個(gè)工程上的坑RLlib的forward方法簽名在不同版本里不一樣。在2.9.0里forward接收input_dict、state、seq_lens三個(gè)參數(shù)返回output和new_state。input_dict是一個(gè)字典包含obs、state等鍵。你需要從input_dict里取出全局狀態(tài)但RLlib默認(rèn)不會(huì)把全局狀態(tài)傳進(jìn)來所以需要在環(huán)境包裝器里把全局狀態(tài)塞到obs字典的一個(gè)特殊鍵里然后在模型里取出來。我當(dāng)時(shí)的做法是在環(huán)境包裝器的step()方法里把全局狀態(tài)拼成一個(gè)數(shù)組放到obs字典的global_state鍵下。然后在自定義模型的forward里從input_dict[obs][global_state]取出全局狀態(tài)。這個(gè)做法有點(diǎn)hack但實(shí)測(cè)有效。4. 訓(xùn)練過程監(jiān)控與調(diào)參經(jīng)驗(yàn)4.1 如何判斷訓(xùn)練是否在正常收斂RLlib自帶TensorBoard日志訓(xùn)練時(shí)可以用tensorboard --logdir ~/ray_results查看。關(guān)鍵指標(biāo)有三個(gè)episode_reward_mean、policy_loss、vf_loss。episode_reward_mean是團(tuán)隊(duì)平均獎(jiǎng)勵(lì)正常收斂的情況下應(yīng)該穩(wěn)步上升最終穩(wěn)定在一個(gè)較高的值。simple_spread的獎(jiǎng)勵(lì)范圍大概是-100到0之間因?yàn)榕鲎埠鸵苿?dòng)都有懲罰地標(biāo)覆蓋給正獎(jiǎng)勵(lì)但不足以抵消懲罰。如果訓(xùn)練正常最終獎(jiǎng)勵(lì)應(yīng)該在-20到-10左右。policy_loss應(yīng)該在一個(gè)小范圍內(nèi)波動(dòng)如果持續(xù)上升說明學(xué)習(xí)率太大或者優(yōu)勢(shì)估計(jì)有問題。vf_loss應(yīng)該逐漸下降如果震蕩劇烈說明value function過擬合或者全局狀態(tài)設(shè)計(jì)有問題。我踩過的一個(gè)坑是訓(xùn)練初期獎(jiǎng)勵(lì)上升很快但到一定程度就卡住了怎么調(diào)參都不動(dòng)。后來發(fā)現(xiàn)是探索不足智能體過早收斂到一個(gè)局部最優(yōu)策略比如所有智能體都往同一個(gè)地標(biāo)跑。解決辦法是增大entropy_coeff從0.01調(diào)到0.05同時(shí)增加train_batch_size讓每次更新看到更多樣的經(jīng)驗(yàn)。4.2 參數(shù)調(diào)優(yōu)的優(yōu)先級(jí)與經(jīng)驗(yàn)值多智能體RL的調(diào)參比單智能體復(fù)雜因?yàn)閰?shù)之間的耦合更強(qiáng)。我總結(jié)的調(diào)參優(yōu)先級(jí)是先調(diào)學(xué)習(xí)率和batch size再調(diào)entropy coefficient最后調(diào)網(wǎng)絡(luò)結(jié)構(gòu)。學(xué)習(xí)率方面5e-4是一個(gè)比較穩(wěn)的起點(diǎn)。如果訓(xùn)練不穩(wěn)定降到1e-4如果收斂太慢升到1e-3。batch size方面4000是下限如果機(jī)器內(nèi)存夠可以加到8000甚至16000訓(xùn)練會(huì)更穩(wěn)但速度會(huì)慢。entropy coefficient方面0.01是默認(rèn)值如果發(fā)現(xiàn)策略過早確定動(dòng)作方差很小就加到0.05如果訓(xùn)練太隨機(jī)就降到0.001。網(wǎng)絡(luò)結(jié)構(gòu)方面simple_spread的觀測(cè)維度很低通常小于20所以兩層64維的全連接網(wǎng)絡(luò)就夠了。我試過用更大的網(wǎng)絡(luò)比如256維結(jié)果訓(xùn)練更慢而且容易過擬合。critic網(wǎng)絡(luò)可以比actor稍大一點(diǎn)因?yàn)槿譅顟B(tài)維度更高但也不要超過128維。還有一個(gè)容易被忽略的參數(shù)是gamma。simple_spread的回合長(zhǎng)度通常是25步所以gamma0.99是合理的折扣因子在25步后衰減到0.78足夠讓智能體考慮中長(zhǎng)期回報(bào)。如果回合更長(zhǎng)比如100步gamma應(yīng)該調(diào)到0.995。4.3 訓(xùn)練日志解讀與異常排查訓(xùn)練過程中最常見的異常是獎(jiǎng)勵(lì)突然暴跌??赡艿脑蛴腥齻€(gè)一是策略更新步子太大導(dǎo)致策略崩潰解決辦法是減小學(xué)習(xí)率或增大clip_param二是環(huán)境隨機(jī)性太大比如地標(biāo)位置每次重置都變化很大解決辦法是固定隨機(jī)種子或者減小環(huán)境隨機(jī)性三是value function估計(jì)偏差太大導(dǎo)致優(yōu)勢(shì)估計(jì)錯(cuò)誤解決辦法是增大vf_loss_coeff或減小vf_clip_param。另一個(gè)常見問題是訓(xùn)練速度慢。RLlib默認(rèn)用單進(jìn)程采樣如果機(jī)器有多核CPU可以設(shè)置num_workers來并行采樣。在config.resources(num_cpus_per_worker1)和config.env_runners(num_env_runners4)里配置。注意num_env_runners不要超過CPU核數(shù)否則會(huì)互相搶資源。還有一個(gè)坑是GPU利用率低。RLlib默認(rèn)把整個(gè)訓(xùn)練圖放在GPU上但多智能體任務(wù)的網(wǎng)絡(luò)很小GPU利用率通常不到30%。如果GPU顯存夠可以增大batch size來提高利用率。如果GPU顯存不夠就老老實(shí)實(shí)用CPU訓(xùn)練simple_spread這種小任務(wù)CPU訓(xùn)練也很快。5. 常見問題與排查技巧實(shí)錄5.1 環(huán)境接口不兼容的典型報(bào)錯(cuò)與修復(fù)最常見的報(bào)錯(cuò)是ValueError: The observation space of the environment does not match the observation space of the policy。這個(gè)通常是因?yàn)镻ettingZoo返回的觀測(cè)空間是Dict類型而RLlib期望的是Box類型。解決辦法是在環(huán)境包裝器里把觀測(cè)空間轉(zhuǎn)換成Box具體做法是用gym.spaces.Box重新定義觀測(cè)空間把字典里的所有值拼接成一個(gè)扁平數(shù)組。另一個(gè)報(bào)錯(cuò)是KeyError: __all__。這是因?yàn)镽Llib的MultiAgentEnv要求step()返回的字典必須包含__all__鍵。解決辦法是在包裝器的step()方法里手動(dòng)添加__all__鍵值可以是任意信息比如一個(gè)空字典。還有一個(gè)報(bào)錯(cuò)是RuntimeError: Expected all tensors to be on the same device。這個(gè)通常是因?yàn)樽远x模型里有些張量在CPU上有些在GPU上。解決辦法是在模型的forward方法里用input_dict[obs].to(self.device)確保所有輸入都在同一個(gè)設(shè)備上。5.2 訓(xùn)練不收斂的排查清單如果訓(xùn)練完全不收斂按以下順序排查排查項(xiàng)可能原因解決辦法獎(jiǎng)勵(lì)曲線一直不上升檢查環(huán)境獎(jiǎng)勵(lì)函數(shù)是否正確打印每步獎(jiǎng)勵(lì)策略損失持續(xù)上升減小學(xué)習(xí)率增大clip_param價(jià)值損失震蕩劇烈檢查全局狀態(tài)設(shè)計(jì)增大vf_loss_coeff動(dòng)作分布方差趨近于0增大entropy_coeff采樣效率訓(xùn)練速度極慢增加num_env_runners檢查CPU利用率顯存占用OOM報(bào)錯(cuò)減小train_batch_size或sgd_minibatch_size我遇到過一次訓(xùn)練完全不收斂的情況排查了半天發(fā)現(xiàn)是環(huán)境包裝器里的觀測(cè)歸一化寫錯(cuò)了導(dǎo)致觀測(cè)值范圍在-1000到1000之間網(wǎng)絡(luò)根本學(xué)不動(dòng)。后來加了gym.wrappers.NormalizeObservation才解決。這個(gè)坑提醒我環(huán)境預(yù)處理比算法調(diào)參更重要。5.3 多智能體信用分配的實(shí)戰(zhàn)技巧信用分配是多智能體RL的核心難題。在simple_spread里團(tuán)隊(duì)獎(jiǎng)勵(lì)是所有智能體共享的但每個(gè)智能體的貢獻(xiàn)不同。MAPPO通過集中式critic來緩解這個(gè)問題因?yàn)閏ritic能看到全局狀態(tài)可以更準(zhǔn)確地估計(jì)每個(gè)狀態(tài)的價(jià)值。但即便如此如果智能體數(shù)量增多信用分配仍然困難。我的經(jīng)驗(yàn)是第一獎(jiǎng)勵(lì)函數(shù)設(shè)計(jì)要盡量稠密不要只給稀疏的最終獎(jiǎng)勵(lì)。simple_spread的獎(jiǎng)勵(lì)是每步都給這比只在回合結(jié)束時(shí)給獎(jiǎng)勵(lì)要好得多。第二可以嘗試給每個(gè)智能體加一個(gè)小的個(gè)體獎(jiǎng)勵(lì)比如距離最近地標(biāo)的距離來引導(dǎo)個(gè)體行為。第三如果智能體數(shù)量超過5個(gè)考慮用QMIX或者VDN這類專門做信用分配的算法MAPPO在智能體數(shù)量多時(shí)效果會(huì)下降。還有一個(gè)技巧是使用agent_id作為策略的額外輸入。在參數(shù)共享的策略里所有智能體共用一套網(wǎng)絡(luò)但如果你把a(bǔ)gent_id的one-hot編碼拼接到觀測(cè)里網(wǎng)絡(luò)就能區(qū)分不同智能體實(shí)現(xiàn)隱式的角色分化。這個(gè)技巧在異構(gòu)任務(wù)里特別有用。5.4 從simple_spread到車聯(lián)網(wǎng)場(chǎng)景的遷移思路simple_spread跑通之后下一步就是往車聯(lián)網(wǎng)場(chǎng)景遷移。車聯(lián)網(wǎng)的多車協(xié)同和simple_spread有幾個(gè)關(guān)鍵區(qū)別第一車輛有動(dòng)力學(xué)約束不能像粒子一樣瞬間改變速度方向第二車輛之間的通信有延遲和丟包第三交通規(guī)則約束比如不能逆行、不能闖紅燈。遷移的思路是先把車輛的運(yùn)動(dòng)學(xué)模型加到環(huán)境里用自行車模型或者阿克曼模型替代simple_spread的質(zhì)點(diǎn)模型。然后在觀測(cè)里加入通信延遲的模擬比如讓智能體看到的是其他車輛過去時(shí)刻的位置。最后在獎(jiǎng)勵(lì)函數(shù)里加入交通規(guī)則懲罰比如壓線扣分、闖紅燈重罰。我目前正在做的一個(gè)項(xiàng)目是把simple_spread改造成一個(gè)十字路口無信號(hào)燈通行場(chǎng)景三輛車從不同方向駛來需要協(xié)商誰(shuí)先通過。這個(gè)場(chǎng)景比simple_spread難很多因?yàn)檐囕v不能原地等待必須保持運(yùn)動(dòng)。但核心算法框架是一樣的MAPPO的集中式critic仍然適用只是全局狀態(tài)需要包含更多信息比如每輛車的速度和航向角。6. 我個(gè)人在實(shí)際操作中的幾點(diǎn)體會(huì)跑完這一整套流程我最大的感受是多智能體RL的工程復(fù)雜度遠(yuǎn)大于算法復(fù)雜度。MAPPO的數(shù)學(xué)推導(dǎo)其實(shí)不復(fù)雜無非是PPO加一個(gè)集中式critic。但真正讓你卡住的是環(huán)境接口、版本兼容、參數(shù)耦合這些工程問題。我建議新手不要一上來就追求SOTA算法先把simple_spread用最樸素的PPO跑通理解多智能體環(huán)境的數(shù)據(jù)流然后再加集中式critic最后再調(diào)參優(yōu)化。另一個(gè)體會(huì)是日志和可視化比調(diào)參更重要。我早期訓(xùn)練時(shí)只看最終獎(jiǎng)勵(lì)結(jié)果訓(xùn)練崩了都不知道為什么。后來養(yǎng)成了每50次迭代保存一次模型、每100次迭代跑一次可視化評(píng)估的習(xí)慣才發(fā)現(xiàn)很多問題在早期就有征兆。比如動(dòng)作方差在訓(xùn)練中期突然變小說明探索不足這時(shí)候就應(yīng)該調(diào)entropy_coeff而不是等到獎(jiǎng)勵(lì)卡住了才去排查。最后分享一個(gè)小技巧如果你發(fā)現(xiàn)訓(xùn)練速度慢可以把num_env_runners設(shè)成CPU核數(shù)的一半然后每個(gè)worker用兩個(gè)環(huán)境并行采樣。這樣既能充分利用CPU又不會(huì)因?yàn)檫M(jìn)程切換太頻繁導(dǎo)致效率下降。我在一臺(tái)16核的機(jī)器上用8個(gè)worker、每個(gè)worker兩個(gè)環(huán)境訓(xùn)練速度比單進(jìn)程快了將近6倍。