多智能体强化学习实战:gym框架下追逃博弈DQN实现与避坑指南
简介该资源是一套基于gym框架的多智能体追逃博弈强化学习平台完整呈现了从环境搭建、交互逻辑到策略训练与测试的实现流程。平台支持Env_2D与Env_3D两类仿真环境并整合FlightGear、Jsbsim及自定义dogFightEnv等多种场景覆盖不同维度的博弈任务适合计算机相关专业学生用于课程设计、期末大作业也适合希望深入理解强化学习多智能体建模的开发者参考。资源共40个文件以27个Python脚本为核心涵盖环境定义、算法调用、测试入口等模块另有少量pyc文件、依赖清单及README说明文档辅助理解整个压缩包约79KB结构清晰便于按需查阅。已有146人学习浏览该项目。源码经过导师指导且获得98分高分评价具备较好的规范性与可读性可帮助读者快速掌握gym环境下的多智能体交互设计思路也可作为后续算法改进或仿真实验的基座平台。1. 打开追逃博弈的 gym 世界多智能体强化学习项目先看清三个问题第一次接触“Python实现gym 框架下的多智能体追逃博弈强化学习平台源代码”这类项目的人多半是被“追逃博弈”四个字吸引来的一群追捕者围堵一个逃逸者像极了游戏里的 AI 对抗。反直觉的是真正卡住大多数人的不是 DQN 或者 PPO 这类深度强化学习算法而是 gym 环境本身的设计——状态怎么表示、奖励怎么给、多智能体之间怎么协调这些环节决定项目能否跑出“围捕”效果。本文按一条可复现的路径展开先讲清楚追逃博弈环境在 gym 中如何建模再实现一个参数共享 DQN 作为追捕者基线最后给出训练闭环、踩坑记录和进阶方向。适合课程设计、毕业设计以及第一次接触多智能体强化学习MARL的从业者照做。2. 从零写一个追逃博弈的 gym 环境状态动作奖励设计与环境类实现你当然可以直接用 ma-gym 或 PettingZoo 里的 Pursuit 环境但“高分项目”的分数往往来自你能独立完成环境建模并且能说清楚每个参数为什么这么设。自己实现环境的另一个好处是奖励函数、视野范围、智能体数量都能按答辩时想讲的故事来调。2.1 为什么不用现成的追捕环境而要自己写 gym 环境类现成的多智能体环境库有两个硬伤。第一个是版本兼容问题ma-gym 停在 gym 0.21 时代PettingZoo 的 API 又带一层 wrapper新手在环境安装上消耗的时间往往比写算法还多。第二个是“不可解释性”——答辩时老师问“你这个追逃博弈的奖励函数怎么设计的”你如果只回答“我用了现成库”这一问就掉了分。自己写 gym 环境类的工作量其实很小。一个标准的 gym.Env 子类只需要实现reset()和step()两个方法再加上observation_space和action_space两个属性。对追逃博弈来说网格世界是最容易讲清楚的状态表示方式也正好是 gym 官方文档里最经典的离散环境形态。2.2 追逃博弈环境的状态动作与奖励设计我把这个环境定义成 8×8 的网格默认 2 个追捕者、1 个逃逸者。每个智能体的动作空间是 Discrete(5)对应“原地不动、上、下、左、右”五个离散动作。观测状态是所有智能体的坐标展平成的向量长度为 2×(追捕者数逃逸者数)每个坐标值范围是 0 到 网格边长-1。奖励设计是整个环境最核心的部分我采用“稀疏大奖励 稠密小奖励”的组合追捕者每一步获得 -0.1 的时间惩罚逼它尽快完成围捕。追捕者获得一个距离 shaping 奖励全局最小“追捕者-逃逸者距离”相比上一步减少的量乘以 0.5距离缩小得越多奖励越大。捕获发生时所有追捕者获得 10.0逃逸者获得 -10.0。若超过最大步数默认 100仍未捕获追捕者获得 -5.0逃逸者获得 5.0。逃逸者本身不参与学习先用启发式策略计算每个逃逸者与最近追捕者的距离选择曼哈顿距离最大化的方向移动。这样能保证追捕者一开始面对的不是“靶子”而是会躲的对手。2.3 环境类实现PursuitEvasionEnv 完整代码下面这段代码可以直接保存为pursuit_env.py运行。注意我使用的是 gymnasium新版本 gym API 的继承者在避坑章节会专门说明和旧版 gym 的差异。import numpy as np import gymnasium as gym from gymnasium import spaces class PursuitEvasionEnv(gym.Env): 追逃博弈网格环境 - 追捕者接受外部动作DQN 控制 - 逃逸者内置启发式策略向最近的追捕者反方向移动 def __init__(self, grid_size8, n_pursuers2, n_evaders1, max_steps100, min_init_distance4): super().__init__() self.grid_size grid_size self.n_pursuers n_pursuers self.n_evaders n_evaders self.max_steps max_steps self.min_init_distance min_init_distance # 动作空间0 不动1 上2 下3 左4 右 self.action_space spaces.Discrete(5) # 观测空间所有智能体坐标展平形状为 2 * (n_pursuers n_evaders) obs_dim 2 * (n_pursuers n_evaders) self.observation_space spaces.Box( low0, highgrid_size - 1, shape(obs_dim,), dtypenp.int64 ) self.pursuer_pos [] self.evader_pos [] self.step_count 0 def reset(self, seedNone, optionsNone): super().reset(seedseed) self.pursuer_pos [] self.evader_pos [] for _ in range(self.n_pursuers): self.pursuer_pos.append(self._sample_free_pos()) for _ in range(self.n_evaders): self.evader_pos.append(self._sample_free_pos()) # 保证追捕者和逃逸者初始距离足够远避免开局秒抓 while self._min_distance() self.min_init_distance: self.pursuer_pos [] self.evader_pos [] for _ in range(self.n_pursuers): self.pursuer_pos.append(self._sample_free_pos()) for _ in range(self.n_evaders): self.evader_pos.append(self._sample_free_pos()) self.step_count 0 return self._get_obs(), {} def step(self, actions): actions: 长度为 n_pursuers 的列表/数组每个元素是 0~4 的整数 返回: obs, reward, terminated, truncated, info assert len(actions) self.n_pursuers, 追捕者动作数量不匹配 self.step_count 1 prev_min_dist self._min_distance() # 追捕者执行动作 for i in range(self.n_pursuers): self.pursuer_pos[i] self._move(self.pursuer_pos[i], actions[i]) # 逃逸者使用启发式策略移动 for i in range(self.n_evaders): self.evader_pos[i] self._move( self.evader_pos[i], self._flee_action(self.evader_pos[i]) ) # 检查捕获追捕者与逃逸者坐标重合 captured False for p in self.pursuer_pos: for e in self.evader_pos: if p e: captured True # 距离 shaping 奖励 cur_min_dist self._min_distance() distance_reward (prev_min_dist - cur_min_dist) * 0.5 # 组装奖励 rewards [] for _ in range(self.n_pursuers): r distance_reward - 0.1 # 时间惩罚 rewards.append(r) if captured: rewards [r 10.0 for r in rewards] # 逃逸者奖励用于后续交替训练这里不返回 evader_reward -5.0 if captured else 5.0 if self.step_count self.max_steps and not captured: rewards [r - 5.0 for r in rewards] terminated captured truncated self.step_count self.max_steps info { pursuer_pos: self.pursuer_pos.copy(), evader_pos: self.evader_pos.copy(), evader_reward: evader_reward } return self._get_obs(), rewards, terminated, truncated, info # ---- 内部工具方法 ---- def _get_obs(self): obs [] for x, y in self.pursuer_pos: obs.extend([x, y]) for x, y in self.evader_pos: obs.extend([x, y]) return np.array(obs, dtypenp.int64) def _move(self, pos, action): x, y pos if action 1: # 上 y min(self.grid_size - 1, y 1) elif action 2: # 下 y max(0, y - 1) elif action 3: # 左 x max(0, x - 1) elif action 4: # 右 x min(self.grid_size - 1, x 1) return (x, y) def _sample_free_pos(self): 拒绝采样保证不与已有智能体重叠 while True: x self.np_random.integers(0, self.grid_size) y self.np_random.integers(0, self.grid_size) pos (int(x), int(y)) if pos not in self.pursuer_pos self.evader_pos: return pos def _min_distance(self): min_dist self.grid_size * 2 for p in self.pursuer_pos: for e in self.evader_pos: dist abs(p[0] - e[0]) abs(p[1] - e[1]) min_dist min(min_dist, dist) return min_dist def _flee_action(self, evader_pos): 逃逸者启发式选择使曼哈顿距离最大的动作 ex, ey evader_pos best_action 0 best_dist -1 for action in range(5): nx, ny self._move(evader_pos, action) if (nx, ny) evader_pos: continue min_dist self.grid_size * 2 for p in self.pursuer_pos: dist abs(nx - p[0]) abs(ny - p[1]) min_dist min(min_dist, dist) if min_dist best_dist: best_dist min_dist best_action action return best_action这段环境的三个设计点容易在答辩中被追问。第一_sample_free_pos使用拒绝采样法它基于np_random这个由super().reset(seedseed)初始化的随机数生成器因此能保证固定的随机种子下整个环境完全可复现。第二_min_distance计算的是曼哈顿距离它决定了 shaping 奖励的梯度方向——追捕者向逃逸者靠近的每一步都会获得正向反馈。第三逃逸者的_flee_action是贪心的它枚举五个动作选择离所有追捕者最远的方向这个策略足够作为初期训练对手后期可以换成学习的策略。2.4 距离 shaping 奖励的参数边界距离 shaping 中的系数 0.5 和惩罚项 -0.1 是相互制约的。如果 shaping 系数大于时间惩罚系数追捕者会在追到接近逃逸者之后绕着它转圈因为“保持近距离”本身有收益如果 shaping 系数过小追捕者学习速度会明显变慢前几百局几乎看不到围捕轨迹。一个经验法则是shaping 系数设在时间惩罚的 3~8 倍之间且逃逸者移动速度与追捕者相同最小初始距离设为网格尺寸的一半。2.5 全局观测 vs 局部观测的选择这里的环境直接给每个追捕者完整坐标观测属于“全局观测 中心化训练”的简化版。真实场景中追捕者只能看到视野范围内的物体那就要把观测改成局部视野裁剪例如每个追捕者只能看到以自身为中心的 3×3 范围。这个改动会在第六章展开说明。对课程设计而言先用全局观测拿稳定结果再把局部观测作为“优化点”写进报告是性价比最高的路径。3. 追捕者策略从随机到 DQN贪心基线、参数共享和 MADDPG 的取舍环境写好后接下来解决“追捕者怎么决策”的问题。常见误用是一上来直接上 MADDPG 或者 PPO结果训练发散半天找不出原因。更稳妥的做法是先做随机基线和贪心基线确认环境本身没毛病再上深度强化学习算法。3.1 为什么单智能体 DQN 在这里也能工作参数共享的适用边界追逃博弈严格来说是一个多智能体问题标准的 DQN 是单智能体算法。但在默认的 2 追 1 逃场景里“2 个追捕者参数共享一个 DQN 网络”是一个工程上高度可行的近似——两个追捕者的目标相同捕捉同一个逃逸者观测空间结构相同策略网络自然可以共用同一套参数。共享参数的 DQN 训练起来比 MADDPG 稳定得多参数量少一个数量级Python 实现也短。这个近似成立的边界条件有两个一是追捕者之间不需要复杂的任务分工二是追捕者之间的协作可以通过共享网络隐式完成。当追捕者数量提升到 4 个以上或者场景要求“左侧包抄 右侧拦截”这种明确分工时参数共享就常常学出扎堆行为那时再考虑 MADDPG 这类真正意义上的多智能体算法。3.2 DQN 网络结构与双网络更新机制网络结构非常简单输入层是观测维度两个全连接层各 128 个隐藏单元输出层维度等于动作数 5。训练机制上必须做两件事经验回放Experience Replay和目标网络Target Network。经验回放用一个deque(maxlen50000)的缓冲区每次训练从缓冲区随机采样 64 条转移样本打破样本之间的时间相关性。目标网络每 100 步从当前网络复制一次参数计算 TD 目标时用目标网络的输出避免“用正在更新的网络来计算目标”导致的训练震荡。这两点缺一个DQN 在追逃环境里都会很快发散。3.3 DQN 智能体实现代码import random from collections import deque import numpy as np import torch import torch.nn as nn import torch.nn.functional as F class DQNNet(nn.Module): 三层全连接 Q 网络 def __init__(self, obs_dim, act_dim, hidden128): super().__init__() self.fc1 nn.Linear(obs_dim, hidden) self.fc2 nn.Linear(hidden, hidden) self.fc3 nn.Linear(hidden, act_dim) def forward(self, x): x F.relu(self.fc1(x)) x F.relu(self.fc2(x)) return self.fc3(x) class DQNAgent: 参数共享的 DQN 智能体追捕者共用同一个实例 def __init__(self, obs_dim, act_dim, lr1e-3, gamma0.99, epsilon1.0, epsilon_min0.05, epsilon_decay0.995, buffer_size50000, batch_size64, target_update100): self.net DQNNet(obs_dim, act_dim) self.target_net DQNNet(obs_dim, act_dim) self.target_net.load_state_dict(self.net.state_dict()) self.optimizer torch.optim.Adam(self.net.parameters(), lrlr) self.buffer deque(maxlenbuffer_size) self.gamma gamma self.epsilon epsilon self.epsilon_min epsilon_min self.epsilon_decay epsilon_decay self.batch_size batch_size self.target_update target_update self.learn_step 0 self.act_dim act_dim def act(self, obs, exploreTrue): obs 是 numpy 数组或 list if explore and np.random.rand() self.epsilon: return np.random.randint(self.act_dim) obs_tensor torch.FloatTensor(np.asarray(obs, dtypenp.float32)).unsqueeze(0) with torch.no_grad(): q_values self.net(obs_tensor) return int(q_values.argmax(dim1).item()) def remember(self, obs, act, reward, next_obs, done): self.buffer.append((np.asarray(obs, dtypenp.float32), act, float(reward), np.asarray(next_obs, dtypenp.float32), done)) def update(self): 从经验池采样更新一次网络返回 loss if len(self.buffer) self.batch_size: return 0.0 batch random.sample(self.buffer, self.batch_size) obs, act, reward, next_obs, done zip(*batch) obs torch.FloatTensor(np.array(obs)) next_obs torch.FloatTensor(np.array(next_obs)) reward torch.FloatTensor(np.array(reward)) done torch.FloatTensor(np.array(done)) # 当前网络计算 Q(s, a) q_values self.net(obs).gather(1, torch.LongTensor(act).unsqueeze(1)).squeeze(1) # 目标网络计算 max Q(s, a) with torch.no_grad(): next_q self.target_net(next_obs).max(dim1)[0] target reward self.gamma * next_q * (1.0 - done) loss F.mse_loss(q_values, target) self.optimizer.zero_grad() loss.backward() self.optimizer.step() self.learn_step 1 # 延迟更新目标网络 if self.learn_step % self.target_update 0: self.target_net.load_state_dict(self.net.state_dict()) # 贪婪率衰减 if self.epsilon self.epsilon_min: self.epsilon * self.epsilon_decay return loss.item()act()方法里有两个细节值得注意。第一判断explore and np.random.rand() self.epsilon这意味着评估阶段传入exploreFalse即可关闭随机探索直接拿 Q 值最高的动作。第二传入观测时统一做了np.asarray(obs, dtypenp.float32)避免环境返回的 int64 数组被 PyTorch 自动转成 float64 而报警告。update()方法里最关键的是目标值的计算reward gamma * next_q * (1.0 - done)。done 为 1 的回合样本其目标值直接等于即时奖励不加未来的 Q 估计这是站在终止状态边界上的标准处理。(1.0 - done)这个乘法保证该条件生效。经验回放缓冲区deque(maxlen50000)在缓冲区满时自动丢弃最早样本不用手动管理。3.4 从 DQN 到 MADDPG什么场景才需要换MADDPG 的核心是“中心化训练去中心化执行”CTDE每个智能体保留自己的 actor 网络训练时用一个能看到全局信息的 critic 网络来评估 actor 的 Q 值。在 2 追 1 逃的默认配置下MADDPG 相比参数共享 DQN 的优势并不明显反而要处理更多的调参问题——critic 输入维度过大、训练不稳定、需要给每个智能体单独维护 buffer 和网络。如果你决定在项目中展示 MADDPG比较合理的定位是“方案对比”同一环境下跑参数共享 DQN 和 MADDPG统计平均捕获步数说明为什么简化方案够用、什么时候必须上 MARL 算法。这样既避开 MADDPG 调参的黑匣子深渊又能在答辩中体现技术选型的完整思考。4. 多智能体追逃训练避坑四个让项目翻车的常见问题与排查写代码时最容易自我感觉良好训练一跑就原形毕露。以下四个问题都是追逃博弈训练里的高频事故按“现象 → 原因 → 解决”逐一排查。4.1 灾难一逃逸者开局就被抓逃逸者学不到任何东西现象追捕者和逃逸者的初始位置随机生成追捕者第一步就往逃逸者方向冲100 步上限内很快捕获。逃逸者的启发式策略根本来不及展开训练数据里全是“成功捕获”样本。原因初始位置约束太松。如果追捕者初始位置离逃逸者只有 1~2 格曼哈顿距离的 shaping 奖励让追捕者几乎没有探索成本就把逃逸者堵在角落里。逃逸者的启发式动作在高密度围堵下可选空间极小防御效果完全体现不出来。解决在reset()里增加初始距离检查强制追捕者与逃逸者的最小曼哈顿距离不低于网格边长的一半。环境代码中while self._min_distance() self.min_init_distance循环就是干这个的。将min_init_distance设为 4对应 8×8 网格追捕者有足够的空间展开包抄态势逃逸者的逃跑路线也才有区分度。4.2 灾难二loss 曲线先降后爆炸训练中段 reward 崩成负数现象前 500 局训练正常捕获率逐步上升到 1500 局左右loss 突然跳到上千随后捕获率直线下降最后追捕者开始原地打转。原因DQN 的目标 Q 值不稳定。当经验池里积累了足够多“捕获成功10”的样本后Q 值的绝对值被推高此时若目标网络没有及时更新TD 误差被放大更隐蔽的原因是 shaping 奖励和捕获奖励的尺度不匹配——shaping 奖励量级在 0.5 左右加 10 的稀疏大奖励后 Q 值出现数量级跳变反向传播的梯度也爆炸。解决把捕获奖励从 10 降到 5同时把 shaping 系数从 0.5 降到 0.2让稠密奖励和稀疏奖励的尺度接近将target_update从 100 步改成 200 步目标网络更新得更慢TD 目标更平稳最后给梯度加裁剪torch.nn.utils.clip_grad_norm_(self.net.parameters(), 1.0)防止单步更新幅度过大。这个组合在过去多个追逃项目中稳定有效。4.3 灾难三追捕者会追但不会围两个追捕者永远扎堆现象训练完成后单看捕获率还行但逐帧回放轨迹发现两个追捕者始终走同一条路线一前一后追着逃逸者跑完全没有任何包抄、堵截的分工。原因参数共享 DQN 天然存在探索对齐问题。两个网络权重完全相同面对同样观测时输出的动作也相同再加上观测是全局坐标两个追捕者看到的输入几乎一致于是动作趋同成为必然。解决最简单的办法是给观测加“智能体身份标记”——在每个追捕者的观测前拼接一个 one-hot 身份向量。这样即使網絡参数共享不同角色的输入不同策略也能分叉。另一种办法是在奖励函数中增加“拥挤惩罚”若两个追捕者处于同一格各扣 0.5 分。这两个手段可以叠加后者还能直接抑制扎堆行为。4.4 灾难四gym 0.21 的老代码迁移到 gymnasium 后直接报错现象按照老的 gym 教程写obs env.reset()、obs, reward, done, info env.step(action)在 gymnasium 下报错ValueError: too many values to unpack。原因gymnasium 完全换掉了环境返回值协议。reset()现在返回(obs, info)step()返回(obs, reward, terminated, truncated, info)其中terminated表示回合终止如捕获成功truncated表示因为步数上限等外部条件截断。老 gym 的done被拆成两个布尔变量语义上更精确但迁移时必须改调用协议。解决如果你在课程设计中没被要求必须用老 gym直接用 gymnasium 并统一新 API。环境代码在reset()返回obs, {}在step()返回五个返回值训练循环里对应改成obs, _ env.reset()和obs, reward, terminated, truncated, _ env.step(actions)。如果牵涉老项目写一个封装层把 gymnasium 的返回值转换成老格式但不要在新代码里兼容两套 API维护成本极高。5. 把环境、算法和日志放进同一套训练流程跑出一个能汇报的结果环境类和 DQN 智能体都完成后最后一公里是训练主循环。这一步看似简单但日志记录、模型保存、指标统计这三个细节直接决定项目答辩时能不能拿出可信的结果。5.1 训练主循环代码import numpy as np from pursuit_env import PursuitEvasionEnv from dqn_agent import DQNAgent def train(): # 环境与智能体初始化 env PursuitEvasionEnv(grid_size8, n_pursuers2, n_evaders1, max_steps100) obs_dim env.observation_space.shape[0] act_dim env.action_space.n # 5 # 参数共享两个追捕者共用一个 DQNAgent 实例 agent DQNAgent(obs_dimobs_dim, act_dimact_dim, lr1e-3, gamma0.99, epsilon1.0, epsilon_min0.05, epsilon_decay0.995, buffer_size50000, batch_size64, target_update200) total_episodes 5000 capture_count 0 evader_survive_steps [] for episode in range(total_episodes): obs, _ env.reset() terminated False truncated False episode_reward 0.0 episode_steps 0 while not (terminated or truncated): # 两个追捕者用同一网络各自决策 np_obs np.asarray(obs, dtypenp.float32) actions [] for _ in range(env.n_pursuers): actions.append(agent.act(np_obs, exploreTrue)) next_obs, rewards, terminated, truncated, info env.step(actions) # 简化处理多个追捕者共用一个学习信号 reward_mean np.mean(rewards) done terminated or truncated agent.remember(np_obs, actions[0], reward_mean, next_obs, done) obs next_obs episode_reward reward_mean episode_steps 1 env.render() # 可选可视化 if terminated: capture_count 1 evader_survive_steps.append(episode_steps) # 每局结束更新一次网络 loss agent.update() # 每 500 局打印一次训练指标 if (episode 1) % 500 0: recent_capture capture_count / 500 avg_steps np.mean(evader_survive_steps[-500:]) print(fEpisode {episode 1:5d} | fcapture_rate {recent_capture:.2f} | favg_steps {avg_steps:.1f} | floss {loss:.3f} | fepsilon {agent.epsilon:.2f}) # 每 1000 局保存一次模型 if (episode 1) % 1000 0: torch.save(agent.net.state_dict(), fpursuit_dqn_{episode1}.pt) env.close() if __name__ __main__: train()主循环中reward_mean的处理值得解释两个追捕者虽然各自决策但捕获奖励是同时给到两者的因此这里取均值作为共享学习信号。这是参数共享策略的自然延伸——既然网络共用一套参数奖励信号也没必要拆开。如果在第 4.3 节基础上加了身份 one-hot 或做了追捕者个性化这里就要改成按各自奖励分别调用remember()。5.2 超参数取值与调参对照表参数默认值调整方向影响grid_size8增大到 10~12训练难度提升逃逸者更容易拖延时间n_pursuers2增加到 3~4捕获更快但参数共享容易扎堆min_init_distance4网格一半经验值过小导致开局秒抓过大导致前 100 步摸不到对手epsilon_decay0.9950.99 ~ 0.999衰减太快探索不足太慢收敛变慢target_update200100 ~ 500与奖励尺度相关奖励越大该值越大lr1e-30.5e-3 ~ 3e-3大于 3e-3 大概率发散epsilon_decay0.995意味着每局更新后贪婪率乘以 0.995300 局后衰减到约 0.221000 局后接近下限 0.05。这个节奏对 5000 局训练总量是合理的前 1000 局主要靠随机探索收集多样本之后逐步转为利用策略。如果发现 2000 局后捕获率还提不上去把epsilon_decay调小到 0.99让探索更激进。5.3 训练过程的 5000 局行为曲线跑完 5000 局后日志文件本身就能看出三个阶段的特征。第一阶段约前 500 局捕获率低平均步数接近 100epsilon 大行为基本随机。第二阶段约 500~2500 局捕获率稳步爬升到 0.5 左右平均步数降到 60~70这是 Q 值开始拟合的正常过程。第三阶段2500 局以后捕获率在 0.6~0.8 之间波动平均步数稳定在 40~50。如果第三阶段出现剧烈波动大概率是采样随机性带来的用固定seed重新训练一次对比即可。5.4 录屏式可视化的轻量实现答辩时一段逐帧动画的演示效果远比折线图好。可视化不需要引入 pygame 这类重依赖用 matplotlib 的显示模式即可在训练时弹出逐帧画面。在env.render()中画网格和智能体位置主循环里每局调用plt.pause(0.1)刷新能直接看到追捕轨迹同时保存最后一局的坐标序列用于答辩材料制作。注意纯计算服务器上不要启用显示训练跑完再把保存的坐标序列画成静态轨迹图。6. 从默认项目到高分项目交替训练、对局验证与可视化如果你的目标是让这个项目“看起来不止于此”三个改动性价比最高把逃逸者从启发式换成可学习策略、用多次对局验证泛化性能、把结果轨迹做成可展示的对比图。6.1 追捕强化学习交替训练的思路固定逃逸者启发式策略训练出的追捕者只在“对手动作已知”的假设下有效。要让项目上一个层次可以在启发式训练完成后冻结追捕者网络给逃逸者接上另一个 DQN让逃逸者学习“如何在两个追捕者夹击下拖延更久”。训练若干局后再冻结逃逸者解冻追捕者继续训练。这种交替训练的思路能稳定提升双方策略强度也是多智能体博弈对抗的标准循环。实现时注意两个网络的缓冲区独立交替训练周期设置为 1000 局一组避免某一方过拟合。6.2 对局验证方法训练结束后用exploreFalse关闭探索分别在随机种子 0、1、2 下各跑 100 局记录三个指标捕获率、平均捕获步数、逃逸者平均存活步数。对比交替训练前后的数据如果逃逸者从“固定被捕获”变成“多拖了 20 步”说明对抗升级是有效的。6.3 轨迹可视化技巧把一局博弈的追捕者与逃逸者坐标分别记录训练结束后用不同颜色画出两条轨迹线追捕者用红色箭头、逃逸者用蓝色箭头在图上标注捕获点。这张图配上一段文字说明“参数共享 DQN 的包抄轨迹”比十页原理说明都更有说服力。我做这类项目的一个比较深的错觉是以为算法模型决定分数上限实际上环境设计和奖励函数才是决定“能不能跑出围捕效果”的第一要素。最初的奖励函数一次写死距离 shaping 系数给到 1.0结果训练到 600 局时 loss 直接爆炸被迫回退参数。现在我会先跑 200 局看行为曲线再决定要不要调奖励尺度。希望帮到你。本文还有配套的精品资源点击获取
上一篇/下一篇内容由系统自动关联
返回资讯列表 →