ARTICLE DETAIL

资讯详情

深耕郑州网站建设与运营推广的一线实战洞察。

多智能体强化学习实战:gym追逃博弈环境搭建与DQN训练

多智能体强化学习实战:gym追逃博弈环境搭建与DQN训练 简介强化学习是近年人工智能领域的热门技术但许多学习者仅停留在单智能体环境跑通Demo的阶段。现实中的智能决策问题尤其是博弈对抗与协同围堵任务往往涉及多智能体之间的竞争与合作。本文从强化学习的基本概念出发以gym框架为基础系统拆解追逃博弈问题的建模方法、马尔可夫博弈原理与奖励函数设计技巧。通过实现一个可扩展的多智能体追逃环境结合DQN算法完成训练调优揭示非平稳性与信用分配等核心难点。该平台具备清晰的工程结构与标准接口可广泛应用于课程设计、毕业设计及多智能体对抗场景的算法原型验证。在实战调参过程中文章详细分析了探索策略、潜在奖励、超参设置等关键环节对收敛效果的影响为希望深入多智能体强化学习的开发者提供了一条从环境搭建到训练落地的完整路径。 做这个项目的起因其实挺朴素很多人学强化学习用gym跑CartPole、Pendulum这些单智能体演示环境跑通了就觉得自己入门了可一旦放到真实场景里就会发现现实问题几乎没有单打独斗的尤其是博弈对抗类的任务。我花了几周时间用Python在gym框架下做了一个多智能体追逃博弈的强化学习平台训练出了能完成围堵任务的追捕者也让逃跑者学会了躲避路线。整个平台源代码结构清晰、扩展性好放到课程设计或者毕业设计里属于那种一眼就能看出工作量和技术深度的项目。这篇文章会把追逃博弈的建模逻辑、gym环境实现、算法选型、训练调试中踩过的坑全部拆开讲尽量做到每一步都能照着落地。如果你正打算做一个多智能体强化学习项目或者需要一个能演示对抗协作场景的完整源码这篇内容应该能帮你省下不少时间。1. 追逃博弈问题建模从游戏规则到强化学习要素很多人在动手写代码之前会忽略一个问题追逃博弈到底要怎么转化成强化学习能解的框架如果你只是凭感觉把一群人放进一个二维地图里然后随机跑两步模型是很难学出东西的。所以我先花时间做了问题建模把博弈规则翻译成状态、动作、奖励三件套。1.1 追逃博弈的数学描述与核心难点追逃博弈是一个经典的零和或非零和博弈问题。假设地图是一个正方形的连续平面里面有m个追捕者和n个逃跑者追捕者的目标是在限定时间内接近逃跑者到一定距离内逃跑者的目标是尽可能长时间地避开追捕者。这个设定听起来像是在做游戏但它本质上是一个多智能体序贯决策问题可以用马尔可夫博弈来建模在每个时间步t环境处于联合状态s_t所有智能体根据各自策略选择动作a_1,...,a_N联合动作驱动环境转移到s_{t1}每个智能体获得自己的奖励r_i。每个智能体都要最大化自己的长期折扣回报。从数学上看这个建模并不算特别复杂。真正麻烦的是多智能体强化学习的两个核心难点非平稳性和信用分配。非平稳性是指对于一个追捕者来说它的环境包括地图边界也包括逃跑者和其它追捕者。而逃跑者自己的策略也在不断学习更新这就导致每个追捕者面对的环境转移概率始终在变。单智能体强化学习里固定环境的假设在这里直接失效了。这也是为什么你有时候会发现明明上个epoch训练得好好的下一个epoch的效果突然崩掉。信用分配则是协作追捕时的经典难题两个追捕者合力堵住了逃跑者最后抓到的那一下功劳该算在前面拦截的追捕者身上还是算在最后扑上去的追捕者身上如果奖励分配得不合理智能体就会只学习抢最后一下而不是学会真正的围堵配合。1.2 状态空间、动作空间与观测设计我在设计观测时没有做太复杂的东西。最开始用的是最直观的绝对坐标方案把所有智能体的坐标全部平铺成一个向量。比如2个追捕者、1个逃跑者那observation就是一个长度为6的向量pursuer0.x, pursuer0.y, pursuer1.x, pursuer1.y, evader.x, evader.y。这个方案有个问题模型对绝对坐标的泛化能力很差。把地图从10x10改成12x12后之前的模型几乎要重新训练。所以我后面改成了归一化坐标相对位置的组合。观测向量变成了每个追捕者的归一化坐标逃跑者的归一化坐标每个追捕者相对逃跑者的方向向量每个追捕者距离逃跑者的距离这样设计的好处是空间状态被压缩到一个固定范围而且模型更容易学到靠近逃跑者这种相对关系。代码里用gym.spaces.Box声明观测空间低维和高维都设置为0到1之间。动作空间我用的是离散动作4个方向上、下、左、右。每个智能体在每个时间步从4个动作里选一个。之所以用离散而不是连续是因为DQN这一系算法天然适合离散动作训练起来也比连续动作稳定得多。如果你想做连续动作版本后面再换成Pendulum那种连续控制环境即可但第一版建议直接用离散。多智能体的动作空间在gym里可以用spaces.MultiDiscrete([4, 4, 4])来声明分别对应每个智能体的动作。训练时每个智能体独立地从各自的动作空间中采样。1.3 奖励函数设计这个环节最容易摔跟头奖励函数是整个追逃博弈里最值得花时间的地方也是最容易被低估的地方。我第一次直接用了抓到给10逃跑者这局活着且没被追到给1的稀疏奖励结果训练了8000个epoch追捕者还是在地图里瞎晃。后来我重新设计了奖励分角色处理追捕者的奖励距离变近每步给一个微小正奖励数值是上一步距离 - 当前距离的正数相当于奖励缩小距离的行为距离变远给一个微小负奖励撞墙-1捕获成功20步数超过上限还没抓到-5逃跑者的奖励距离变远正奖励距离变近负奖励被捕获-20存活到时间结束5这种基于距离变化量的潜在奖励思路来自强化学习里的reward shaping。它解决了稀疏奖励的问题让智能体在每一步都能收到反馈信号。但要注意潜在奖励的系数不能设得太大否则智能体会钻漏洞比如追捕者学会跑进角落因为角落的边界让逃跑者无处可逃距离一下子缩小很多奖励也突然变大。这种捷径不是真实策略但算法觉得它挺好。所以我在惩罚撞墙的同时对逃进死角的策略也会产生隐式压制。多智能体的奖励分配上我采用了团队奖励只要任何一个追捕者抓到逃跑者所有追捕者都获得本次捕获的团队奖励。这个设计很重要它让追捕者从单打独斗模式切换成了协作围堵不然两个人永远会争抢同一个位置。2. gym环境实现把博弈规则变成标准接口建模方案确定之后接下来就是把它变成一个标准gym环境。这里不光是写一个类的问题还要考虑训练循环怎么和环境交互、渲染怎么可视化、环境怎么注册。我建议任何一个认真做强化学习项目的人都遵守gym规范来做即使你只跑通了一个简单场景后面的扩展也会省很多事。2.1 环境类结构与初始化逻辑gym环境的核心接口是reset和step。这两个函数是所有强化学习算法交互的基础。继承gym.Env之后必须定义好observation_space和action_space否则很多算法框架在初始化的时候就直接报错。我的环境类大概是这样的结构import gym from gym import spaces import numpy as np class PursuitEvasionEnv(gym.Env): metadata {render_modes: [human, rgb_array]} def __init__(self, size10.0, capture_dist0.5, max_steps200, n_pursuers2, n_evaders1): super().__init__() self.size size self.capture_dist capture_dist self.max_steps max_steps self.n_pursuers n_pursuers self.n_evaders n_evaders # 离散动作0上 1下 2左 3右 self.action_space spaces.MultiDiscrete( [4] * (n_pursuers n_evaders) ) # 观测每个智能体 [x_norm, y_norm] 追捕者到逃跑者的相对位置/距离 obs_dim 2 * (n_pursuers n_evaders) n_pursuers * 3 self.observation_space spaces.Box( low0.0, high1.0, shape(obs_dim,), dtypenp.float32 ) self.reset() def reset(self, seedNone, optionsNone): super().reset(seedseed) self.steps 0 # 随机初始化所有智能体位置避免固定起点的对称性 self.pursuers self.np_random.random((self.n_pursuers, 2)) * self.size self.evaders self.np_random.random((self.n_evaders, 2)) * self.size # 保证追捕者和逃跑者初始距离不至于太近 for p in self.pursuers: while np.min(np.linalg.norm(self.evaders - p, axis1)) self.capture_dist * 3: p[:] self.np_random.random(2) * self.size return self._get_obs(), {}初始化的几个细节值得注意。位置用self.np_random.random而不是直接np.random.random这是gym的新规范能确保环境可复现。初始化的时候必须保证追捕者不会直接出生在逃跑者脸上不然每轮开局就结束经验池里全是成功样本模型反而学不到追击过程。2.2 智能体决策循环与碰撞判定step函数是环境的核心。它接收每个智能体选择的动作更新位置判断是否捕获同时计算奖励和终止信号。def step(self, actions): self.steps 1 actions np.asarray(actions) # 根据动作更新追捕者 for i, p in enumerate(self.pursuers): p self._action_to_delta(actions[i]) p np.clip(p, 0, self.size) # 根据动作更新逃跑者 for i, e in enumerate(self.evaders): base_idx self.n_pursuers i e self._action_to_delta(actions[base_idx]) e np.clip(e, 0, self.size) # 捕获判定任何一个追捕者到任何一个逃跑者的距离小于阈值 caught_flags [] rewards np.zeros(self.n_pursuers self.n_evaders) for j, e in enumerate(self.evaders): dists np.linalg.norm(self.pursuers - e, axis1) caught np.any(dists self.capture_dist) caught_flags.append(caught) if caught: rewards[:self.n_pursuers] 20.0 rewards[self.n_pursuers j] - 20.0 terminated any(caught_flags) or self.steps self.max_steps # 每步的距离变化奖励在这里补充 ... return self._get_obs(), rewards, terminated, False, {}这个循环里最关键的是奖励是长度等于智能体数量的向量而不是单个标量。很多刚上手多智能体的同学容易忽略这点直接在环境里return一个float然后训练循环里所有智能体共用同一个奖励。这个在团队协作场景里其实不是不行但一旦你希望追捕者和逃跑者有不同的目标就必须分开计算奖励。碰撞判定这里用的是欧氏距离。地图是10x10的话capture_dist设0.5就可以。如果地图变成20x20这个数字也要跟着调不然追捕者会隔老远就捕获逃跑者。2.3 环境注册与渲染可视化为了让gym更加规范地管理环境可以在__init__.py里注册环境from gym.envs.registration import register register( idPursuitEvasion-v0, entry_pointpursuit_evasion_env:PursuitEvasionEnv, )注册之后可以直接用gym.make(PursuitEvasion-v0)创建环境。如果你只是在本地跑跑不注册也行直接实例化类即可。但注册有一个好处很多第三方算法库比如Stable-Baselines3、Ray RLlib会通过环境id来创建环境注册之后就能无缝对接。渲染我推荐用matplotlib简单粗暴。每步更新一下散点图def render(self, modehuman): import matplotlib.pyplot as plt plt.clf() px [p[0] for p in self.pursuers] py [p[1] for p in self.pursuers] ex [e[0] for e in self.evaders] ey [e[1] for e in self.evaders] plt.scatter(px, py, cblue, labelpursuers) plt.scatter(ex, ey, cred, labelevaders) plt.xlim(0, self.size) plt.ylim(0, self.size) plt.legend() plt.pause(0.01)渲染函数不用追求华丽重点是能直观看出智能体是不是在学东西。我在训练时通常每100个epoch渲染一局观察追捕者的行为模式。可视化对调试的帮助非常大甚至比你盯奖励曲线还直接。2.4 为什么用gym而不是自己写一个简单循环有些同学会问既然只要几个坐标和一个碰撞判定为什么非要用gym自己写一个simulator加上训练循环不也一样能跑吗表面上看确实可以但实际项目里gym规范带来的好处很快就能体现出来。一个是标准接口带来的算法复用价值你以后想试试PPO、A2C、MADDPG直接对接gym接口就行不需要重写环境。另一个是环境与训练算法的解耦训练代码写好后只依赖reset、step、render这三个接口环境怎么改都不影响训练主循环。这个解耦在课程设计、毕业设计的答辩环节非常加分因为老师会认为你已经具备了工程化的思维而不是只把代码堆在一起能跑就行。3. 多智能体强化学习算法选择与训练循环实现环境准备好之后算法选择就摆到台面上了。多智能体强化学习算法现在有很多从DQN的变体到PPO、MADDPG、QMIX每一类都有自己的适用场景。我一开始想过直接上MADDPG但后来决定先从DQN做起。原因很简单先跑通一个可用的东西再考虑效果。3.1 单智能体算法扩展到多智能体时的三个坑很多论文里讲DQN是单智能体算法但用在多智能体环境里也不完全违法。实际上只要处理得当DQN在追逃这种低维动作空间的场景里完全够用。不过直接拿单智能体DQN代码套到多智能体环境上你马上会碰到三个坑第一个坑是经验回放打乱了样本的时间相关性。多智能体场景里追捕者的经验不只取决于自己的动作还取决于逃跑者的动作。如果一条经验是追捕者往上移动导致距离变近另一条经验是逃跑者往上移动导致距离变远这两条经验里的奖励差异其实来自不同智能体但DQN很难区分这一点。这个问题的缓解办法是参数共享所有追捕者使用同一个深度网络输入自己的观测输出自己的动作价值。这样经验池里的样本可以被所有追捕者共用数据效率高很多。第二个坑是目标网络更新周期的选择。多智能体环境的非平稳性会导致Q值目标频繁跳动如果目标网络更新太快训练容易震荡。我测试下来目标网络每500步同步一次比较合适。单智能体里常见的1000步同步规则在这里反而会不稳定。第三个坑是经验池的采样分布。追逃博弈里开局阶段和后期阶段的状态分布差异很大如果经验池容量太小旧经验很快被冲掉模型会遗忘前半局学到的东西。我最后把经验池容量设置到了100000比单智能体环境里常用的50000大了一倍效果确实更稳定。3.2 DQN实现细节经验回放、目标网络、奖励裁剪我用的是Double DQNDDQN和标准DQN的区别在于计算TD target时用当前Q网络选择动作再用目标网络计算该动作的Q值。这样能有效缓解Q值高估的问题。在很多野外教程里DDQN只多了三四行代码收益却很明显值得加上。核心更新逻辑如下def train_step(self): if len(self.memory) self.batch_size: return batch random.sample(self.memory, self.batch_size) obs torch.tensor([t[0] for t in batch], dtypetorch.float32) actions torch.tensor([t[1] for t in batch], dtypetorch.long) rewards torch.tensor([t[2] for t in batch], dtypetorch.float32) next_obs torch.tensor([t[3] for t in batch], dtypetorch.float32) dones torch.tensor([t[4] for t in batch], dtypetorch.float32) q_values self.q_net(obs).gather(1, actions.unsqueeze(1)).squeeze(1) with torch.no_grad(): next_actions self.q_net(next_obs).argmax(dim1) next_q self.target_net(next_obs).gather(1, next_actions.unsqueeze(1)).squeeze(1) target rewards self.gamma * next_q * (1 - dones) loss nn.functional.mse_loss(q_values, target) self.optimizer.zero_grad() loss.backward() self.optimizer.step()这个训练步函数是需要所有追捕者共享经验的。我实现时每个追捕者采集到的经验都会统一丢进同一个ReplayBuffer然后每步从buffer里随机采样训练。这样做与集中式训练的思路一致虽然部署时每个智能体是个独立个体但训练时可以共享数据这在多智能体强化学习里是被广泛接受的trick。还有一个小细节奖励裁剪。我会把每步奖励clip到[-1, 1]范围。如果不裁剪极端情况下捕获奖励20和撞墙惩罚-1相差太大Q值梯度方向会被少数样本主导。裁剪之后模型更容易学到平滑的策略。3.3 训练主循环、日志记录与模型保存训练主循环比环境实现简单很多但有一些约定俗成的组织方式。我把它分成了两层循环外层是episode内层是单步决策。for epoch in range(epochs): obs, _ env.reset() ep_rewards np.zeros(n_agents) done False while not done: actions [] for agent_id in range(n_agents): action agent.choose_action(obs, epsilon) actions.append(action) next_obs, rewards, terminated, truncated, _ env.step(actions) done terminated or truncated for agent_id in range(n_agents): agent.memory.push((obs, actions[agent_id], rewards[agent_id], next_obs, done)) obs next_obs ep_rewards rewards agent.train_step() if global_step % target_update 0: agent.update_target() global_step 1这个循环里有几个细节需要注意。choose_action里用epsilon贪心前3000步epsilon基本保持0.5以上后面逐步衰减到0.05。epsilon衰减如果太快追捕者很快就锁定在一个固定的动作序列上根本不会尝试去堵截逃跑者的多变路线衰减太慢又浪费训练时间。我用的衰减函数是epsilon 0.95 ** (epoch // 50)也就是每50个epoch乘以0.95到大约1500个epoch左右降到一个很低的值。日志记录我用的是标准库logging每100个epoch打印一次平均奖励、捕获率、平均步数。同时把每个epoch的奖励曲线保存下来方便训练完以后直观分析。模型保存则用了PyTorch的torch.save每1000个epoch保存一次权重保留最近5个checkpoint。这样训练过程中如果某次崩溃可以从最近的checkpoint继续不至于全部重来。4. 训练实战从乱跑到围堵的调参全过程训练过程是最能反映一个项目真实成色的地方。我跑了三轮版本每一轮都有不同的问题把这些问题排查掉之后追捕者的表现才开始成熟。4.1 第一个版本跑出来追捕者原地打转怎么办第一版模型跑出来的效果可以用惨不忍睹来形容。追捕者在原地小幅移动偶尔转圈完全不往逃跑者方向靠。我第一反应是网络结构有问题检查了一遍发现网络结构、激活函数都没有明显bug。后来仔细看奖励分布才意识到问题出在探索策略上。epsilon初始值设成了0.1意味着开局就有90%的概率选择贪心动作。但此时Q网络还是随机初始化贪心动作其实是随机网络认为最好的动作而这种动作通常没什么规律。结果整个训练过程中智能体一直困在初始化的策略里走了很远才偶尔探索到新路径效果当然差。解决方法是把epsilon初始值设置为1.0并且在训练初期强制采用随机游走。我在前1000个epoch里让epsilon始终保持在0.9以上让智能体充分探索地图。等到它有了一定的随机经验Q网络的梯度方向才开始有意义。这个调整之后追捕者大约在第2000个epoch左右就开始有朝逃跑者移动的倾向了。4.2 奖励太稀疏用潜在奖励引导探索第一版训练稳定之后追捕者确实会朝逃跑者方向移动了但动作依然很笨拙。地图是10x10从一端跑到另一端需要几十步追捕者经常追着追着就跟丢了。问题出在奖励设置上之前只在成功捕获的时候给20平时没有反馈模型很难学会持续缩短距离这个优秀行为的链条。我后来引入了每步的相对距离变化量作为奖励修正prev_dist np.linalg.norm(self.pursuers - self.evaders, axis1) # 执行动作、更新位置 new_dist np.linalg.norm(self.pursuers - self.evaders, axis1) distance_shaping (prev_dist - new_dist) * 0.5这个公式直观理解就是如果追捕者这一步让两个角色之间的距离缩短了就给出一个正奖励幅度和缩短量成正比。对于逃跑者来说刚好反过来距离扩大给正奖励。这样每一步都有明确的优化方向整个梯度信号变得密了非常多。注意这个潜在奖励的权重0.5不能设太高。我把权重从0.2一路试到1.0发现0.5附近的训练最稳定。权重过了0.8之后模型开始出现原地转圈刷距离奖励的投机行为——它不追了站在原地转圈因为这样能让逃跑者误打误撞偶尔靠近自己刷一部分距离奖励。这个现象说明奖励塑造的权重过高时智能体确实会钻空子。4.3 超参调整记录学习率、batch、discount、epsilon超参数调优是整个项目里最耗时也最见功夫的部分。下面是我调整过程中比较重要的一组记录标注了数值和它带来的影响超参数初始值调整后影响学习率1e-31e-41e-3下Q值震荡剧烈降到1e-4后才稳定收敛batch size3264加大采样数量后训练曲线更加平滑gamma0.990.95追逃博弈是个有限步任务gamma太高会让远期奖励权重过大延迟收敛epsilon初始0.11.0增加前期探索epsilon最低值0.010.05给后期留一点探索空间防止策略完全固化网络隐藏层[128, 128][256, 256]增大网络容量后能记住更多状态-动作组合target update1000步500步多智能体非平稳目标网络同步频率提高更稳这套参数跑出来的效果在2追1逃场景下大约3000个epoch之后捕获率开始明显上升5000个epoch后能稳定在80%以上。后面我把逃跑者换成更强的策略之后捕获率会下降到60%左右这属于正常的对抗性变化。训练时还有一个非常值得关注的现象追捕者的奖励曲线并不是单调上升的它会有周期性的回落。我看到的最大一次回落在epoch 4200左右捕获率从80%突然掉到45%。排查之后发现是因为逃跑者也是强化学习智能体它在同一时间段内也学到了新的闪避策略导致追捕者暂时不适应。这种你涨我跌的循环在多智能体博弈里非常常见对应的处理方法是不要一看到曲线下降就停掉训练给它时间追上来。4.4 评估指标与结果可视化训练跑完不能光看一个reward曲线还需要一套能说明问题的评估指标。我最终选了四个指标来评估捕获率、平均捕获步数、逃跑者平均存活步数、追捕者平均回合奖励。前三个指标评估任务完成度最后一个指标评估学习效果。评估的时候需要把epsilon设成0也就是完全采用贪心策略跑100局取平均值。这个细节很容易漏掉如果你评估的时候还开着epsilon探索测出来的捕获率会偏低因为偶尔会随机选一个错误动作导致漏掉逃跑者。可视化方面我用matplotlib把每个智能体的运动轨迹画成一个折线图。训练前期的轨迹是乱七八糟的后期可以看到追捕者的轨迹逐渐形成左右包抄的形态。这里贴一个我后续绘制的轨迹示意逻辑for i, trace in enumerate(pursuer_traces): trace np.array(trace) plt.plot(trace[:, 0], trace[:, 1], b-, alpha0.7) for i, trace in enumerate(evader_traces): trace np.array(trace) plt.plot(trace[:, 0], trace[:, 1], r-, alpha0.7)轨迹图的作用不只是好看。我在观察轨迹时发现了一个有趣的策略两个追捕者会在训练后期自动分化出追击者和拦堵者两种角色一个从后方追另一个绕到逃跑者前方堵截。这个突现行为并没有在代码里显式设计完全是算法从奖励函数里学出来的。这种群体协作行为涌现在多智能体系统里是一种很迷人的现象也是老师问起项目亮点时可以重点讲的部分。5. 项目可扩展方向与常见问题答疑项目做成之后很多同学会问我还能怎么扩展。追逃博弈这个主题的扩展空间非常大选对方向能让项目档次再上一个台阶。5.1 从2v1扩展到n vs n现在的代码默认是2个追捕者、1个逃跑者。扩展到3v3或者5v5最直接的影响是观测维度、动作维度、奖励计算复杂度同步增长。环境代码里的n_pursuers和n_evaders都是可配置的理论上直接改参数就能跑n vs n但实际训练效果会差很多。n vs n场景下追捕者的协作难度急剧上升。3个追捕者面对2个逃跑者时必须学会分组和分工不然所有人追同一个逃跑者另一个会轻松跑掉。这种分工策略在简单的独立DQN架构下很难涌现。如果想把项目做成真正的多智能体协作项目建议从两个方向升级一是采用集中训练-分布执行的框架比如MADDPG的思想训练时给每个智能体一个包含全局信息的中心评论家执行时只用局部观测二是给智能体增加通信通道让追捕者之间能够共享目标分配信息。这两个方向写进毕设论文里工作量一下子就起来了。5.2 引入通信机制或合作奖励追捕者在真实场景中通常需要通信比如确定谁去封左路谁去封右路。这个在项目里可以简化为给追捕者的观测向量额外拼接队友的意图信息也就是队友上一时刻选择的动作。这样一来追捕者就能在决策时参考队友的下一步动向避免都往同一个方向跑。合作奖励方面可以考虑使用个体奖励 团队奖励的组合。团队奖励可以定义为队伍中最小的追捕者-逃跑者距离的变化量而不是所有追捕者距离的平均值。这样做的理由是围堵任务中最关键的是最优位置的那个追捕者而不是所有人的平均表现。这个设计能促使追捕者之间形成互补站位而不是各自为战。5.3 常见问题与排查思路最后整理几个我实际踩过、也是群里同学经常踩的坑。这些问题的排查思路比答案本身更重要因为你会反复遇到类似的问题。环境报了ValueError: Buffer dtype mismatch。通常是gym版本和numpy版本不兼容导致的用gym0.21.0配合numpy1.24可以解决或者直接把环境迁移到gymnasium。训练时reset()里用np.random.random会导致每次结果不可复现。应使用self.np_random.random这是gym规范的随机数接口。多智能体step返回的reward形状不一致。很多解析器会期待环境返回标量但多智能体环境下必须返回一个List或者Dict。如果你的训练框架不支持需要在包装器里做转换。训练很慢追捕者数量多导致每步都要算好几对距离。可以稍微优化一下碰撞判定比如先判断x和y坐标是否都在capture_dist范围内再做欧氏距离计算能省掉很多无用的sqrt运算。训练后期模型震荡严重。优先检查学习率是不是太大了其次检查target_update间隔。还不行可以试试减少潜在奖励的权重。做完整套项目之后我最大的体会是多智能体强化学习真正的难点不在于算法公式而在于对环境的理解和对奖励信号的设计。追逃博弈表面上是几个坐标点的移动但当你看到两个追捕者自动学会包抄的时候那种感觉和看到单智能体学会走迷宫完全不一样——你会觉得环境真的是一个生动的世界。最后再分享一个小技巧训练的时候先冻结逃跑者的策略用随机策略或者简单规则等追捕者学得差不多之后再让逃跑者参与学习。这个简单的课程学习思路能将追捕者的成长期缩短三分之一左右。等追捕者稳定了再放开逃跑者让两个队列在对抗中共同进化最终的效果比同时训练要稳定不少。本文还有配套的精品资源点击获取
返回列表