ARTICLE DETAIL

资讯详情

深耕网站视觉设计与运营推广的一线实战洞察。

共享经验Actor-Critic:多智能体强化学习中的样本效率提升实战解析

共享经验Actor-Critic:多智能体强化学习中的样本效率提升实战解析 做多智能体强化学习实验的朋友十有八九都见过下面这个场景几个智能体在环境里各跑各的其中一个可能偶然踩中稀疏奖励效率立刻起飞但旁边的智能体还在原地随机试探完全不知道队友已经发现了“通关密码”。整个训练过程又慢又浪费算力每个人都经历过那种“明明环境不难为什么数据利用效率这么低”的抓狂感。NIPS 2020当年还经常叫NIPS后来统一叫NeurIPS上有一篇专门解决这个问题的论文缩写叫SEAC全称Shared Experience Actor-Critic中文一般翻译成“共享经验Actor-Critic”。它的核心思路说起来很简单让协作型智能体之间共享经验把其他智能体探索到的轨迹拿来当自己的训练数据再通过重要性采样修正策略差异。这篇文章我会结合复现这种方法时最容易踩的坑从问题背景、算法原理、实现流程到排查技巧完整过一遍。无论你是刚接触多智能体强化学习的研究生还是想在项目里提升训练效率的工程师都可以把这篇文章当作一份带注释的SEAC上手笔记。1. 从训练现场说起多智能体强化学习最大的痛点1.1 为什么每个智能体都在“闭门造车”先看一个很典型的独立学习Independent Learning场景每个智能体都有自己的一套策略网络各自与环境交互各自收集轨迹再各自更新参数。比如经典的多智能体粒子环境里几个agent要合作完成一个搬运任务reward只在最终目标达成时给出。这种情况下智能体A花了3000个episode学会先推箱子再移动这个经验对智能体B来说本来是极有价值的但因为两者完全独立B只能自己从零探索把同样的弯路再走一遍。这种“闭门造车”带来三个连锁问题。第一是非平稳性。每个智能体都在同时更新策略对单个智能体来说环境的转移概率和奖励分布在训练过程中不断变化就像在移动的沙地上盖楼。独立学习本质上假设其他智能体是环境的一部分但这一部分又是动态的这就导致单个智能体很难稳定收敛。第二是探索效率低下。尤其在稀疏奖励环境里随机探索找到有用反馈的概率极低。如果几个智能体的探索方向还互相重叠那算力基本等于浪费。第三是数据利用率低。每条轨迹只有一个智能体使用采集成本却要全部自己承担。在多智能体场景里环境交互的代价通常比单智能体更高因为要同时推进多个智能体的状态训练一轮的时间肉眼可见地变长。1.2 现有的“协作”方案为什么还不够针对多智能体训练难的问题社区里已经有了不少解法。我把主流方向归纳成三类中心化训练分散执行CTDE代表算法有MADDPG、QMIX、COMA。这类方法在训练时引入全局信息比如所有智能体的观测和动作用来辅助评论家网络学习。虽然效果不错但它们对通信和集中式信息的依赖比较重而且没有直接解决“单个智能体的探索经验如何被其他智能体复用”这个问题。课程学习与奖励塑形通过拆解任务、设计中间奖励来降低稀疏奖励带来的困难。但这需要不少任务先验知识换个环境就要重新设计。更高效的探索机制比如给策略加上多样性激励让不同智能体探索不同方向。这类方法有效但很多时候并没有充分利用已经采集到的数据。这些方法各有适用场景但在“提升样本效率”这件事上它们都还差一个关键机制经验复用。SEAC正是从这个角度切入的而且它切得很准——既然智能体之间是合作关系目标函数一致那为什么不能把彼此的经验直接拿来训练呢2. SEAC 的核心思路与数学原理2.1 为什么“共享经验”合理先回答一个基础问题为什么在合作型多智能体任务里共享经验在理论上站得住脚因为在完全合作的任务中所有智能体共享同一个总回报也就共享同一个优化目标。用强化学习的语言说我们关心的是一个联合策略下系统整体的期望回报。既然目标一致任何一条能够带来高回报的轨迹无论它是由哪个智能体生成的对整个系统都有学习价值。举个例子两个机器人协作搬箱子机器人A发现先触碰箱子再移动到目标点能触发奖励这条轨迹虽然只来自A但对机器人B也同样意味着“下一步应该尝试什么方向”。如果B能学到这段经验它的探索范围会大幅缩小。但这里有一个很微妙的区别经验值不值得借鉴和“能不能直接用来更新自己的策略”是两回事。A的行为是由A的策略产生的如果B直接拿A的轨迹去更新B的策略就会犯一个经典的off-policy错误——用别人的行为分布估计自己的策略梯度这在统计上是有偏的。2.2 核心机制共享Buffer 重要性采样修正SEAC的做法分两步走。第一步建立共享经验池。所有智能体在训练过程中产生的轨迹样本统一放入一个大的replay buffer里面每条样本除了常规的(state, action, reward, next_state, done)还要额外记录一个关键字段这条样本是哪个智能体产生的我习惯叫它source_agent_id。第二步更新阶段引入重要性采样比例。对于智能体i来说从buffer里抽样时可能会拿到智能体j产生的样本。为了把这个样本用于更新自己的策略需要计算一个比例$$\rho \frac{\pi_i(a_t | s_t)}{\pi_j(a_t | s_t)}$$这个式子里的分子是“当前智能体在状态s_t下采取动作a_t的概率”分母是“产生这条样本的行为智能体在同一状态下采取同一动作的概率”。如果样本本来就来自智能体i自己那么分子分母相同ρ 1退化成普通的on-policy更新。如果样本来自智能体j那么ρ就会衡量两条策略在这个样本上的差异程度。有了ρ以后再把它套进策略梯度的更新里。这里可以直接参考PPO的截断思路对ρ做clip操作限制在某个区间内比如[1-ε, 1ε]。这个设计既保留了PPO防止更新步长过大的优点也避免了其他智能体的经验因为策略差异过大而把当前策略带偏。用生活化一点的方式理解你在公司里看到隔壁组总结了一套高效工作方法你想借鉴但你不能直接照搬因为你和他的技能栈、习惯不一样。你需要先评估“这套方法按我的方式执行的话可行性有多高”再决定参考多少。重要性采样就是那个评估系数而clip就是给评估过程加了上限防止你因为过分高估这套方法而做出激进改变。2.3 SEAC和PPO的关系很多人第一次看SEAC会觉得“这不就是PPO加了个共享buffer吗”这个直觉方向是对的但并不完全准确。PPO的clip ratio是当前策略和旧策略的概率比处理的是同一个智能体在不同迭代版本之间的策略偏移问题。而SEAC里的clip ratio是当前智能体策略和另一个智能体行为策略的概率比处理的是“跨智能体策略差异”问题。二者都是用重要性采样控制修正幅度但修的对象不同。另外SEAC并不是一个和PPO互斥的算法更像是“在Actor-Critic框架上加了经验共享与修正”的通用插件。你完全可以把SEAC和PPO结合起来使用先由每个智能体分别采样并存储样本更新时统一从共享池里抽样配合clip机制做梯度更新。这也是多数开源实现里出现的形式。2.4 与主流MARL算法的差异对比我把SEAC和几个常见MARL算法放到同一张表里方便你在选型时心里有数算法经验是否共享解决的主要问题关键限制独立PPO/A2C否单一智能体策略稳定性非平稳、样本效率低MADDPG否仅集中评价评论家信息不足需要中心化信息训练复杂QMIX否联合动作价值分解只适用于特定值分解结构COMA否信用分配counterfactual baseline计算开销较大SEAC是探索经验复用、样本效率仅适用于合作任务需行为策略记录从这个表能看出来SEAC和这些方法并不是完全替代关系它更像是一种训练策略层面的增强手段。如果任务本身就是纯合作的SEAC的收益会非常明显。3. 复现SEAC环境、流程与超参3.1 环境选型我建议从哪里开始如果你想快速复现SEAC我建议先从**多智能体粒子环境MPE**入手。它配置简单可视性强而且有现成的PettingZoo接口动作空间和观测空间都比较小训练一版只需要几十分钟非常适合验证算法流程。有几个环境很适合用来测试SEAC的效果比如Spread多个agent需要各自走到不同目标点强调协作分配。Adversary虽然有对抗性质但合作方内部依然是共享奖励的可以用来观察算法在“半合作”场景下的表现。Transport两个agent合作搬运reward较稀疏SEAC在这里的提升通常很明显。如果要更复杂一些可以上SMAC星际争霸多智能体挑战环境但对初学者来说前期调试成本偏高不建议作为第一个SEAC实验。有一点需要提前提醒MPE这种环境里如果所有智能体的观测空间完全一致共享经验的效果会更直接如果观测是不同的局部视图共享经验依然有用但你需要留意每个智能体的状态空间差异对策略更新的影响。3.2 网络与共享Buffer实现要点按照SEAC的惯例每个智能体可以拥有独立的Actor网络和Critic网络。在没有额外通信要求的情况下我觉得这是最省心的结构。记住一个关键点共享的是经验数据不是网络参数。虽然有工作会把网络参数也共享来加速但会影响智能体的行为多样性和SEAC原始动机是有出入的。共享Buffer的存储字段建议这样设计{ source_agent_id: int, # 这条经验由哪个智能体产生 obs: np.ndarray, # 当前观测 action: int or np.ndarray, # 动作 reward: float, # 奖励 next_obs: np.ndarray, # 下一步观测 done: bool, # 是否终止 log_prob_source: float # 产生此经验时的log概率 }这里藏着一个非常容易踩的坑“动作在source策略下的概率”必须在采样当时就保存下来。你可能会想训练时直接用source agent当前的策略网络重新算一遍不就行了不行因为训练过程中策略一直在更新训练时的网络已经不是你采样时候的那个策略了。正确做法是采样时保存好源智能体的动作概率之后把它作为分母使用。Critic网络的更新和单智能体PPO类似用TD目标或者GAE计算目标值做回归即可。不同点是样本可能来自其他智能体因此在critic训练时不需要额外修正因为评论家学习的本来就是给定状态动作下的价值off-policy数据对价值函数估计没有本质障碍。3.3 训练循环核心伪代码下面是一段能跑通核心逻辑的伪代码我把关键部分都做了注释# 每个环境下各agent产生经验 for agent_id, agent in enumerate(agents): obs env.reset()[agent_id] for t in range(max_steps): action, log_prob, _ agent.choose_action(obs) next_obs, rewards, dones, _ env.step(action) buffer.push( source_agent_idagent_id, obsobs, actionaction, rewardrewards[agent_id], next_obsnext_obs[agent_id], donedones[agent_id], log_prob_sourcelog_prob # 核心字段 ) obs next_obs[agent_id] # 每个agent用共享buffer更新 for agent_id, agent in enumerate(agents): batch buffer.sample(batch_size) for item in batch: obs item[obs] action item[action] # 当前策略在样本上的概率 log_prob_current agent.actor.log_prob(obs, action) # 源策略概率采样时保存的那个 log_prob_source item[log_prob_source] # 重要性采样比例 ratio (log_prob_current - log_prob_source).exp() ratio_clip torch.clamp(ratio, 1 - clip_eps, 1 clip_eps) # 用当前agent的value网络估计advantage伪代码从简 adv estimate_gae(agent.critic, batch, item) # 策略损失PPO式截断 policy_loss -torch.min(ratio * adv, ratio_clip * adv).mean() # 价值损失 value_loss mse(agent.critic(obs), target) # 更新agent我在这里说明一下estimate_gae这一段在实际工程里会涉及GAE的lambda递推计算不同实现的细节略有不同但思想是一致的用当前智能体自己的critic估计状态价值然后对每个样本计算advantage。3.4 超参数推荐与调参思路按我复现SEAC的经验下面这组超参在不同环境里都能跑出不错的结果参数推荐值说明clip_eps0.2同PPO控制重要性采样比例截断范围batch_size256~1024太小则修正比例波动大太大则更新频率低learning_rate3e-4 ~ 1e-3Actor和Critic可分开设置GAE lambda0.95常规设定discount gamma0.99常规设定buffer容量越大越好但注意过时经验比重策略漂移后缓冲区中旧样本会失真调参有一个很有意思的规律如果你发现训练早期loss震荡得特别厉害优先减小learning_rate而不是怀疑算法不收敛如果你发现后期性能上不去、但训练过程又很平稳那大概率是clip_eps设得太小限制了策略从共享经验中获得有效信息的能力可以适当放开到0.3~0.4试试。还有一点值得强调如果环境中奖励尺度非常大比如动辄几百上千建议先做reward normalization。SEAC本身对奖励尺度没有额外处理但共享样本意味着不同智能体的reward尺度会混在一起数值范围过大很容易让价值网络震荡得更厉害。4. 常见问题与排查技巧实录4.1 重要性采样比例爆炸这是我复现SEAC时遇到的第一个问题。如果不用clip或者clip范围设置得太宽ratio很容易出现几十几百的极端值导致策略更新一步就彻底崩坏。原因在于当两个智能体策略差异较大时当前策略给某个动作的概率可能很接近0除以一个更小的分母就会产生巨大数值。排查方法很简单在训练日志里记录ratio的均值、最大值和分位数。如果max经常超过10说明样本中确实存在大量“别人做过但我现在不太会做”的动作。这时候除了clip还要检查buffer里的数据是不是太杂了。比较好的做法是设置一个上限比如ratio超过4的样本直接以较低权重参与更新或者干脆过滤掉。另外我建议在更新前对log_prob做一次数值稳定性处理防止exp的时候出现inf。4.2 忘记记录source_agent_id或log_prob很多人在自己写buffer的时候只记录obs、action、reward、done等到更新阶段才发现没有分母信息只能把ρ强行设为1等于没做修正SEAC就退化成了普通的共享经验PPO效果会退化得比较明显。解决办法是设计数据类时就把字段定死并且在push函数入口处校验字段完整性。一个非常土但有效的调试方式在采样阶段故意对同一批数据用“正确的source log_prob”和“错误的source log_prob”各跑一遍前向手工检查计算出的ratio是否符合预期。这个操作能帮你快速定位是存储问题还是计算问题。4.3 在对抗或者混合任务里使用SEACSEAC的核心前提是合作。如果你把SEAC直接用到对抗环境里共享经验不仅无益反而会成为噪声源。比如两个智能体下棋目标完全相反A的经验对B来说很可能就是反向示范。如果你非要用在混合场景至少要做分层只让同一团队的智能体共享经验团队之间保持独立。如果要应用到非对称任务中还需要额外设计reward重标定等机制否则我建议直接换别的方法。4.4 过时经验污染buffer越大能提供的数据越多样但也会积攒大量和当前策略不匹配的老旧样本。多智能体场景下这个问题比单智能体更严重因为其他智能体的策略也在不断变化一条半集成的老轨迹可能同时包含“过时的行为策略”和“过时的队友行为模式”。一种缓解办法是使用比例上限当某条样本的ρ超出阈值时直接丢弃不参与梯度计算。另一种更轻量但同样有效的做法是限制共享buffer的容量让它只保留最近若干轮的经验牺牲一部分数据利用率换取策略稳定性。我还尝试过给不同来源的样本加优先权重让“和自己当前策略更接近”的样本拥有更高的采样概率。这个思路工程上稍微复杂但在某些任务上确实能进一步改善稳定性。4.5 问题速查表现象可能原因处理方法训练前期loss爆炸ratio未clip / clip范围过大 / 学习率过高检查ratio统计调低学习率固定clip_eps0.2后期性能停滞clip过紧 / buffer过旧增大clip上限清理buffer缩短保留周期共享经验后提升不明显环境本身合作度不够 / 奖励太稠密换稀疏奖励环境观察差异验证任务确实需要协作多智能体行为趋同共享经验让策略方差变小可以引入策略熵正则项保持探索多样性Critic损失震荡reward scale不一致对reward做归一化或使用PopArt类方法5. 最后分享一点个人体会SEAC这篇工作给我的最大启发是多智能体强化学习并不一定非要靠复杂的中心化机制取胜把“数据”这个最朴素的资源用对也能带来非常可观的效率提升。尤其在后来的实际项目里我越来越强烈地感觉到很多MARL算法落地时最大的瓶颈根本不是模型表达力不够而是数据采集太慢、探索太盲。在算力受限的场景里像SEAC这样“让团队经验流动起来”的思路往往比单纯堆更多并行环境更直接有效。如果你正在做多智能体相关的实验花一个下午把SEAC在你的任务上跑出来再和独立PPO对比一下大概率会看到非常直观的差距。它实现成本不高踩坑点也相对集中是个很适合作为多智能体强化学习进阶练习的算法。后续如果你想把合作场景下的经验复用做得更极致还可以往“选择性共享”“价值加权共享”等方向延伸这其实就是另一篇论文的故事了。
返回列表