news 2026/9/28 7:07:25

MADDPG多智能体博弈对抗算法Python源码实战解析

作者头像

张小明

前端开发工程师

1.2k 24
文章封面图
MADDPG多智能体博弈对抗算法Python源码实战解析

简介:基于MADDPG的多智能体博弈对抗算法Python项目源码,为一份98分期末大作业项目,面向计算机专业正在完成课程设计或期末项目的学生,也适合需要强化学习实战的开发者。项目围绕多智能体在博弈对抗中的训练与决策,提供完整可运行的实验代码,可帮助理解MADDPG多智能体框架以及DDPG单智能体算法的实现原理,同时通过奖励设置、经验回放、网络更新等环节展示典型强化学习流程。压缩包共13个文件,除Python脚本外还有配置文件和文本说明,整体仅15KB,轻量而模块密集;核心脚本分别对应经验回放、网络结构、智能体训练、环境测试等部分,便于按需阅读和二次开发。项目经过严格调试,下载即可运行,省去环境适配与排错成本。目前已有385人学习,适合用于算法理解、课程报告或项目复现。

1. 当两个智能体互相拆台时,MADDPG项目源码为什么值得看

做过单智能体强化学习的人第一次把DDPG用在对抗环境里,通常会遇到一个很困惑的现象:两个智能体明明各自都收敛了,放到一起打却立刻崩掉。原因不难理解——对手的策略一直在变,环境对单个智能体来说已经不是稳定环境了,经验回放里存下来的“正确动作”很快过期。这个问题在博弈对抗场景里尤其致命,因为你要的不只是“会玩”,而是“能针对对手调整玩法”。基于MADDPG的多智能体博弈对抗算法Python实现项目源码,解决的就是这一类问题:它把训练过程拆成“集中训练、分散执行”,让每个智能体在学自己策略的同时,把对手行为也建模进来,从而在追逐、拦截、竞速、攻防这类博弈任务里训练出能对抗的策略。

这篇笔记适合三类人:做多智能体强化学习课程设计或毕业设计的学生,需要一套能快速跑通的基线代码;做游戏AI或仿真对抗方向的工程师,想看看MADDPG在自己的环境里能到什么程度;以及已经跑过PPO、DQN,想往多智能体方向走一步的强化学习新手。文章会从原理讲到跑通,再到把这份源码改成自己能用的项目,过程中会给出我会实际调整的参数和经验值,尽量让你少走弯路。

2. 为什么对抗博弈选MADDPG:从非平稳环境到集中训练分散执行

2.1 单智能体算法在多智能体对抗中失效的三个原因

先建立一个直觉:强化学习的核心假设是环境转移概率和奖励函数在训练过程中保持稳定。Q-learning和策略梯度方法都依赖这个假设来从历史经验里学习。多智能体对抗场景里这个假设直接被打破了——对手也在学习、也在更新策略,所以当前状态转移到下一个状态的概率,会因为对手策略的改变而改变。这就是所谓的非平稳环境。

非平稳带来的第一个问题是经验回放过期。智能体存了一批“上一版本对手”下的经验,等对手更新了策略,这些经验里标注的收益就不再准确,用它们更新价值网络会让Q值严重高估。第二个问题是梯度方向不稳定。对多个同时学习的智能体来说,某个智能体策略的微小变化会改变其他智能体的观测分布,而观测分布变化又反过来影响这个智能体的收益,形成循环干扰。第三个问题是缺乏对手建模。单智能体算法根本不知道“对面那个家伙”在干嘛,它把所有不确定性都归结为环境噪声,这在博弈里等于瞎打。

MADDPG的思路是:既然训练阶段可以开卷,那就不必让每个智能体自己猜对手。训练时让Critic看到所有智能体的状态和动作,相当于考试时大家一起对答案,学习阶段信息全共享;考试时(执行阶段)再各凭观测做决策。这样一来,非平稳性问题主要落在Critic的更新里,Actor面临的非平稳性大大降低。

2.2 中心化训练、去中心化执行的数学直觉与核心网络结构

MADDPG全称是Multi-Agent Deep Deterministic Policy Gradient,在DDPG框架上扩展而来。每个智能体i有自己的Actor网络μ_i,输入自己的局部观测o_i,输出确定性动作a_i。同时有一个中心化Critic网络Q_i,输入是所有智能体的观测和动作(即o_1, o_2, ..., o_N和a_1, a_2, ..., a_N),输出对智能体i的Q值估计。关键点在于:每个智能体都有自己的Critic,但每个Critic都能看到全局信息。

Critic的更新目标是用贝尔曼方程最小化时间差分误差。Actor的更新目标是最大化自己的Q值,但因为Q_i知道所有智能体的动作,所以Actor的梯度里包含了对其他智能体策略的隐式建模。这种设计让每个智能体在决策时不需要显式推断对手意图,Critic已经把这些信息编码进Q值里了。

我这里给一个最简的PyTorch版本网络结构,方便你对照源码理解。实际项目里Actor末尾会加tanh把动作压缩到[-1, 1]区间:

import torch import torch.nn as nn class Actor(nn.Module): def __init__(self, obs_dim, act_dim, hidden=64): super().__init__() self.net = nn.Sequential( nn.Linear(obs_dim, hidden), nn.ReLU(), nn.Linear(hidden, hidden), nn.ReLU(), nn.Linear(hidden, act_dim), nn.Tanh() ) def forward(self, obs): return self.net(obs) class Critic(nn.Module): def __init__(self, obs_dim_all, act_dim_all, hidden=64): super().__init__() # 输入是所有智能体的观测拼接 + 所有智能体的动作拼接 self.net = nn.Sequential( nn.Linear(obs_dim_all + act_dim_all, hidden), nn.ReLU(), nn.Linear(hidden, hidden), nn.ReLU(), nn.Linear(hidden, 1) ) def forward(self, obs_all, act_all): x = torch.cat([obs_all, act_all], dim=-1) return self.net(x)

这个结构本身不复杂,复杂的是训练循环里的数据流。训练时要把所有智能体的观测拼成一个长向量喂给Critic,更新时每个智能体单独算loss。执行阶段Actor只用自己的观测推理,不需要知道其他智能体的信息。这种“训练开卷、执行闭卷”的架构就是MADDPG的核心贡献,也是它区别于独立DDPG、独立PPO的地方。

2.3 三个关键设计细节:策略集成、经验回放与奖励设计视角

MADDPG原文里有几个容易被忽略的工程细节,理解了它们你才看得懂源码里的参数。第一个是策略集成(policy ensemble):每个智能体维护K个不同的Actor子策略,每次更新随机选一个子策略与环境交互,目标价值计算时取K个子策略输出动作的平均Q值。这样做的目的是缓解对手策略剧烈变化导致的价值估计震荡,等于给对手建模加了一个平滑项。

第二个是经验回放的博弈视角。在多智能体场景中,经验缓冲区的采样策略非常讲究。如果按时间顺序连续采样,相邻样本之间对手策略差异很小,智能体学不到应对多样策略的能力。更有效的做法是随机从整个缓冲区采样,让每一次更新面对的都是“历史不同版本对手”的混合样本。这就是为什么MADDPG项目里buffer容量通常设得比单智能体更大,我一般设到50000条以上。

第三个是奖励设计的博弈陷阱。MADDPG本身不解决奖励塑造问题,但对抗场景中零和奖励(一方收益等于另一方损失)会让训练非常不稳定,因为双方梯度方向完全相反。实际项目中常见做法是把赢家奖励设为+1、输家设为-1,同时加上小幅度动作惩罚项,防止策略退化成原地抖动。你也可以在环境里加入稀疏的“接近奖励”或“命中奖励”来引导探索,这部分后面会展开讲。

3. 把“下载即用”的源码跑起来:Python环境依赖与最小启动命令

3.1 依赖版本为什么经常装完就报错

这类源码包最常见的翻车点不是算法,而是环境依赖冲突。MADDPG项目普遍依赖PyTorch和OpenAI Gym,而这俩库的版本兼容性在Python 3.8到3.11之间差别很大。如果你直接用最新版PyTorch配最新版Gym去跑老项目,大概率会遇到gym.make接口变动或者np.float属性报错这类问题。

我的建议是固定一套能互相兼容的版本组合。经典MADDPG开源实现大多基于PyTorch 1.x和Gym 0.15到0.21之间,这两个版本的接口比较稳定。操作系统层面,Windows、Linux、macOS都能跑,但如果你用的是Windows,Gym的rendering模块偶尔会缺底层显示依赖,训练模式下关掉渲染就能绕过去。

推荐的环境创建命令如下,Python版本用3.8或3.9兼容性最好:

conda create -n maddpg python=3.9 conda activate maddpg pip install torch==1.13.1 pip install gym==0.21.0 pip install numpy==1.23.5 pip install matplotlib pyyaml

这里把numpy固定在1.23.5是为了避开numpy 2.x里移除np.float、np.int这类别名导致的兼容性错误。torch 1.13.1是1.x系列最后一个大版本,既能在老代码上跑,又对后续新硬件有基本支持。如果你电脑是Apple Silicon芯片,可以试试pip install torch装官方预编译版,一般能直接用。

3.2 从解压到看到第一条训练曲线的完整步骤

拿到压缩包后,第一步不是直接运行,而是先看目录结构。多数MADDPG项目源码的组织方式大体相似:主训练脚本、模型定义文件、环境文件、配置目录各司其职。你需要先确认入口文件名,通常是main.py、train.py或run_experiments.py。

接下来执行三步:安装依赖、确认入口、启动训练。假设入口文件是main.py:

# 第一步:在项目根目录安装依赖 pip install -r requirements.txt # 第二步:快速验证环境能不能初始化 python -c "from envs import make_env; env = make_env('simple_tag'); print(env.observation_space)" # 第三步:启动训练 python main.py --scenario simple_tag --episodes 3000 --seed 42

如果requirements.txt不存在,就用前面提到的固定版本逐个装。启动命令里的--scenario指定环境场景,simple_tag是MADDPG论文里经典的追捕-逃跑任务,一个追捕者对抗多个逃跑者,非常适合作为入手场景。训练过程中要观察三个指标:每个episode的累计奖励、actor的loss量级、critic的loss量级。如果奖励曲线持续上升且loss没有爆炸到NaN,说明训练方向正确。

第一次跑的时候建议把--episodes设小一点,比如500,先验证整个链路能跑通,再全量训练。训练时长取决于你的GPU。纯CPU跑simple_tag3000个episode可能要两三个小时,GPU则快得多。数据量不大,不需要分布式。

3.3 跑通之前先确认的三件事:文件结构、入口参数、保存路径

下载即用的项目最怕的是“代码没问题,但不知道去哪改参数、去哪看结果”。我每次拿到新源码都会先做三件事,花十分钟省后面好几个小时。

第一件事是确认训练配置从哪读。大多数项目支持命令行参数覆盖,但也有项目把超参数硬编码在脚本里。搜一下代码里的learning_rate、batch_size、buffer_size这些关键词,看它们定义在哪个文件。第二件事是确认模型保存路径和日志路径。训练完权重存成.pt还是.pth文件,存储在models/还是runs/目录,这决定了你中断训练后怎么续跑。第三件事是确认环境注册方式。Gym老版本需要gym.make('simple_tag')能直接找到环境类,如果找不到,说明环境文件没有被import进来,需要在入口文件里手动import envs。

这里给一个通用的文件结构参考表:

路径/文件作用跑通前要确认
main.py训练入口,解析命令行参数默认参数是否完整
maddpg.py智能体初始化与单步更新智能体数量与环境是否匹配
buffer.py经验回放缓冲buffer大小是否过小
envs/自定义环境gym.make能否初始化
config.yaml超参数配置学习率、噪声衰减是否合理

检查完这三件事,再启动训练,基本能避免跑了一半发现路径不存在、保存失败、参数没生效等等问题。

4. 核心代码模块拆解:MADDPG训练主循环里每一步在做什么

4.1 经验回放缓冲区:对抗场景下应该存什么、怎么采

MADDPG的经验回放和单智能体最大的区别在于:每一条经验必须包含所有智能体的观测和动作。也就是说,一条transition的格式是(obs_all, act_all, reward_all, next_obs_all, done_all),其中obs_all是N个观测向量的拼接,act_all是N个动作的拼接。这直接决定了你的replay buffer数据组织方式。

采样时,一次随机采一个batch,然后按智能体逐个更新。注意每个智能体的Critic更新用的是同一个batch的全局数据,只是奖励和done字段取自己那一份。这意味着训练时所有智能体的网络都要能访问到全局状态,这就是“中心化”的落地方式。

class ReplayBuffer: def __init__(self, capacity, obs_dim_all, act_dim_all): self.capacity = capacity self.obs_all = np.zeros((capacity, obs_dim_all), dtype=np.float32) self.act_all = np.zeros((capacity, act_dim_all), dtype=np.float32) self.rew_all = np.zeros((capacity, num_agents), dtype=np.float32) self.next_obs_all = np.zeros((capacity, obs_dim_all), dtype=np.float32) self.done_all = np.zeros((capacity, 1), dtype=np.float32) self.ptr = 0 self.size = 0 def store(self, obs_all, act_all, rew_all, next_obs_all, done): idx = self.ptr % self.capacity self.obs_all[idx] = obs_all self.act_all[idx] = act_all self.rew_all[idx] = rew_all self.next_obs_all[idx] = next_obs_all self.done_all[idx] = done self.ptr += 1 self.size = min(self.size + 1, self.capacity) def sample(self, batch_size): idx = np.random.choice(self.size, batch_size, replace=False) return (self.obs_all[idx], self.act_all[idx], self.rew_all[idx], self.next_obs_all[idx], self.done_all[idx])

这段代码有两点需要注意。第一,rew_all是二维数组,因为每个智能体的奖励不同,存的时候不能合成标量。第二,done字段在对抗任务里通常所有智能体共享同一个结束标志(比如某一方被抓住),但有些场景是部分结束,这时候要对每个智能体单独记录。采样用replace=False防止同一条经验被重复采到,如果buffer还很小就硬采,会导致模型反复看到同一条数据而过拟合。

4.2 集中式Critic的更新:目标网络、贝尔曼误差与soft update

Critic的更新是整个MADDPG里最核心的数学逻辑。每个智能体的Critic网络要学习一个Q函数:给定全局状态和所有智能体的动作,输出自己从此刻开始的期望回报。目标值计算公式为:

y_i = r_i + gamma * Q_i_target(obs_next_all, act_next_all)

其中act_next_all不是环境给的,而是所有智能体的target Actor网络基于各自下一时刻观测输出的动作。这就形成了一种“自举”:用target网络估算未来价值,再用当前网络逼近这个目标。

实际实现中,每步更新会做一次梯度下降,然后对target网络做软更新。软更新的参数tau通常取0.01,意思是target网络的权重每次往当前网络靠近1%。tau太小导致target网络更新太慢,模型学得慢;tau太大则target网络追踪太快,失去稳定目标的意义。我通常先把tau设为0.01,如果训练曲线震荡剧烈再降到0.005。

def update_critic(self, batch, agent_id, gamma=0.95, tau=0.01): obs_all, act_all, rew, next_obs_all, done = batch with torch.no_grad(): # 用target Actor输出所有智能体的下一动作 next_act_all = torch.cat([ self.target_actors[i](next_obs_all[:, obs_start[i]:obs_end[i]]) for i in range(num_agents) ], dim=-1) target_q = self.target_critics[agent_id](next_obs_all, next_act_all) y = rew[:, agent_id].unsqueeze(-1) + gamma * (1 - done) * target_q # 当前Critic对实际执行动作的Q值 q = self.critics[agent_id](obs_all, act_all) td_loss = nn.MSELoss()(q, y) self.critics_optimizer[agent_id].zero_grad() td_loss.backward() torch.nn.utils.clip_grad_norm_(self.critics[agent_id].parameters(), 0.5) self.critics_optimizer[agent_id].step()

这里有一个常被忽略的细节:计算target Q值时,next_act_all必须用target Actor网络输出,不能用当前Actor网络。原因是target网络的价值估计相对稳定,如果用当前网络,每一步更新都在追逐一个移动的靶子,训练容易发散。另外我在反向传播前加了梯度裁剪,max_norm=0.5,这个值应对奖励尺度大或loss爆炸的场景很有效,项目源码里如果没加,建议你自己补上。

4.3 Actor的更新:确定性策略梯度与对手建模的隐式表达

Actor的更新目标相对简单,但理解它需要绕过一个弯。每个智能体的Actor目标是最大化自己Critic给出的Q值。因为当前动作是通过Actor网络输出的,Critic又依赖这个动作,所以梯度可以从Critic的输出反传到Actor的参数上。数学形式是:

grad_mu_i = grad_{a_i} Q_i(obs_all, act_all) * grad_{theta_i} mu_i(obs_i)

对应到代码里,更新时先让所有Actor基于各自的观测输出当前动作,拼接成完整动作向量喂给Critic,然后用反向传播优化Actor参数。由于Critic输入中包含其他智能体的动作,Actor的梯度天然会受到其他智能体策略的间接影响,这就是对手建模的隐式表达——不需要显式预测对手下一步动作,Critic已经把这个知识编码在Q值里了。

def update_actor(self, batch, agent_id): obs_all, act_all, rew, next_obs_all, done = batch # 只更新当前智能体的Actor,其他Actor输出用stop_gradient固定? new_act = self.actors[agent_id](obs_all[:, obs_start[agent_id]:obs_end[agent_id]]) # 拼接时,其他智能体的动作用实际执行的动作(断开梯度) act_mixed = torch.cat([ act_all[:, :act_start[agent_id]], new_act, act_all[:, act_end[agent_id]:] ], dim=-1) actor_loss = -self.critics[agent_id](obs_all, act_mixed).mean() self.actors_optimizer[agent_id].zero_grad() actor_loss.backward() self.actors_optimizer[agent_id].step()

这里最微妙的地方在于:其他智能体的动作act_all在拼接时不能被梯度更新,否则反向传播会影响其他智能体的Actor参数。所以拼接后要确保act_mixed的梯度图里只有当前Actor的部分是活的。有些实现会在拼接前对act_all调用.detach(),意义就在这。用-Q.mean()作为loss是因为PyTorch只能做梯度下降,最大化Q值等价于最小化负Q值。

4.4 一次完整训练迭代的事件顺序

理解了三个更新函数之后,把整个训练循环串起来看一遍。每个episode开始,重置环境,拿到所有智能体的初始观测。每一步先通过噪声添加探索性动作,所有智能体执行动作后环境返回全局奖励和下一时刻观测,存入buffer。每积累一定步数(比如100步)后从buffer采样一个batch,依次更新每个智能体的Critic和Actor,再软更新所有target网络。

事件顺序的细节决定了项目的稳定训练行为。噪声添加是在Actor输出之后、环境执行之前;更新顺序是Critic先更新、Actor再更新,不能反过来。如果Actor先更新,它当前的输出被用于Critic的target计算,会在同一轮里产生依赖循环。软更新放在所有智能体更新完毕后做,保持target网络滞后一步,这是DDPG系列稳定训练的根本保证。

噪声参数也需要配套调整。训练早期用较大的噪声鼓励探索,之后逐步衰减。常见做法是noise_scale = 0.1 * (1 - episode / total_episodes),让噪声线性衰减到零。如果你发现智能体训练到后期还乱逛,多半是噪声衰减得太慢;如果只学到一个动作不变,那是衰减过快,探索不足就收敛到了局部最优。

5. 避坑排查:我从MADDPG项目里踩过的五个经典问题

5.1 训练中途loss变成NaN,模型直接废掉

现象:训练正常跑到几百个episode,突然Critic的loss变成NaN,Actor输出也全是NaN,后续训练彻底崩掉。

原因:最常见的是学习率偏高加奖励尺度偏大,导致Q值反向传播时梯度爆炸。其次可能是没有做梯度裁剪,或者网络权重的初始化范围太大。还有一个比较隐蔽的原因是传入Critic的观测里含有NaN——如果你在环境里计算了“距离”之类的量,初始状态下距离为无穷大,进入网络后直接炸掉。

解决:我给Critic和Actor都加上梯度裁剪,max_norm=0.5。同时在环境返回的观测里做一次数值检查,用np.isnan(obs).any()去排查。学习率方面,把Actor和Critic的学习率从默认的0.01降到0.001,训练更慢但从根上解决了发散问题。如果还没解决,检查奖励设计,把奖励除以一个常数缩放到[-1, 1]区间。

5.2 两个智能体都“躺平”,谁也不作为

现象:训练跑完,两个智能体的行为都是待在原地不动。奖励曲线早早收敛到一个不高不低的水平,push上去又掉下来。

原因:这是典型的奖励设计问题。如果环境给出的奖励是“赢者通吃”(赢了+10,输了-10),在策略还没成形时,智能体发现“不作为”比“瞎折腾”的平均收益更高,因为乱动的代价可能更大。博弈里这叫“安全策略”,它避免失败,但也放弃胜利。

解决:改用更平滑的奖励函数,外加一个小的“探索鼓励”项。比如追捕任务里,对追捕者每一步增加距离缩短的奖励——靠近目标就给小分,抓住目标再给大分。对逃跑者则相反。同时给每个动作加上一个微小的动作惩罚,例如-0.01 * ||a||,防止智能体通过高频抖动“刷”距离奖励。修改奖励后再训练,两个智能体才开始有实质对抗行为。

5.3 训练后期策略震荡:赢了又输、输了又赢

现象:训练曲线在前中期稳步上升,到后期开始剧烈震荡,智能体的胜率在90%和20%之间来回跳动。每个seed的结果都不太一样,有的seed训练效果好,有的seed直接废掉。

原因:这是非平稳环境的典型症状。对手策略更新太快,导致你的Agent刚适应了当前对手版本,对手已经换了新打法。MADDPG里target网络的存在缓解了一部分,但target更新率tau太大时,这个效应会被放大。另一个原因是buffer容量太小,存不下足够多样的“历史对手样本”,采样时多样性不够。

解决:把tau从0.01降到0.003,让target网络更慢地追踪。buffer容量加大,至少存到能覆盖几万条经验,保证每次采样能混合不同时期的对手策略。如果还震荡,就降低Critic的学习率,让价值估计更保守。我还有一个比较偏门的技巧:给“对手的移动轨迹”做一步预测,加入观测空间。这样智能体能区分“对手正在进攻还是逃跑”,策略更有分化。

5.4 追捕任务里智能体互相推挤,绕圈不捕

现象:追捕者明明已经贴近逃跑者了,就是不出手,两个智能体在一起绕圈,谁都逮不住谁。

原因:这个现象在MADDPG里非常经典,原因是“策略退化到局部最优”。如果距离奖励设计成“越近奖励越高”,但接近后的“捕捉动作”没有显著额外奖励,智能体学到的最优策略是保持近距离等待——因为捕捉动作一旦失败,反而拉开距离、损失奖励。它宁可绕圈也不愿意冒进。

解决:给“捕捉成功”设置一个远高于其他奖励的稀疏大奖励,比如+50,同时在捕捉失败时不给惩罚。这样智能体才能建立“接近-捕捉-成功”的完整因果链条。另一个缓解办法是给追捕者的动作空间加上“限制”,比如每次只能移动一步或攻击一次,避免它用连续的微小移动消磨时间。

5.5 “下载即用”却一运行就报错:路径问题与版本陷阱

现象:按照README里的命令启动训练,报ModuleNotFoundError: No module named 'envs',或者gym.make找不到环境名,又或者np.floathas no attribute之类的错误。

原因:第一类错误是PYTHONPATH没包含项目根目录,特别是从子目录启动脚本时Python找不到同级的envs包。第二类错误是Gym版本过高,老接口gym.make的行为变了。第三类是numpy 2.0移除了np.float等旧别名。

解决:在项目根目录执行export PYTHONPATH=$(pwd):$PYTHONPATH(Windows用set PYTHONPATH=%cd%),然后重新启动。依赖版本上,严格按前一章的固定版本表安装。另外给一个通用排查动作:用pip list检查torch、gym、numpy三个包的版本,只要跟我前面给的版本组合不一致,优先对齐再谈其他。

6. 从复现到改造:把MADDPG用到你自己的博弈场景

源码跑通只是第一步,实际业务里的对抗环境不可能跟simple_tag一模一样。我分享三个最实用的改造技巧。

第一个是自对弈机制。MADDPG本身就是自对弈的天然载体,但如果你发现训练后期胜率震荡,可以定期保存“冠军版本”的模型,作为对手池中的一员。每次训练时,当前智能体不仅跟“当前自己”打,还跟历史版本的自己打。这能有效缓解策略退化和循环克制问题。实现时只需要把其他智能体的Actor网络替换成加载的历史权重,其余的更新逻辑不变。

第二个是场景迁移时的观测与动作维度对齐。你自己的博弈环境可能不是“位置+速度”的粒子世界,而是更接近股票交易、无人机竞速、游戏对战之类。迁移时先做一件事:把观测和动作统一归一化到[-1, 1]区间。MADDPG的Actor输出层是tanh,输入层如果接受了尺度差异很大的原始特征,训练会非常吃力。我的经验是,归一化之后奖励函数的尺度统一到[-1, 1],学习率可以相对提高,收敛速度快一倍。

第三个是评估指标要分开看。很多项目源码里有evaluate.py或--eval参数,用它的结果来判断模型好坏是合理的。但要注意评估时关闭所有噪声,固定随机种子,让对手使用不同历史版本,分别统计胜率和平均奖励。单一对局的结果带很大的偶然性,我习惯至少跑20局取平均再做决策。评估完发现策略有漏洞,回去调奖励或者调tau,比盲目加训练轮数有效得多。

我这几年跑这种策略对抗项目,最深的一个教训是:不要等到训练结束才去评估。每个500个episode快速评估一次,把胜率曲线和训练曲线画在一起,能提前暴露“训练曲线上升但胜率不上升”的假收敛问题。靠这种高频评估,我堵掉了好几次项目后期推倒重来的风险。希望这篇笔记里的原理、参数和踩坑能让你拿到这份源码后少走弯路,也希望你能在这个基础上做出真正有对抗能力的智能体。

本文还有配套的精品资源,点击获取

版权声明: 本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若内容造成侵权/违法违规/事实不符,请联系邮箱:809451989@qq.com进行投诉反馈,一经查实,立即删除!
网站建设 2026/9/28 7:07:08

2026实测10款降AI率软件红黑榜:TaoToken统一Key接入与达标率验证

/* MD / 富文本中的 .toc(含博客园搬家等嵌套结构);.toc-box 在侧栏,不受影响 */#content_views .toc,/* 编辑器常在目录前后插入空 p(:empty 仍占 20px),一并去掉避免顶空隙 */#content_views.markdown_views > p:empty:has(+ .toc),#content_views.markdown_views …

作者头像 李华
网站建设 2026/9/28 7:06:54

2026年Visual Studio插件精选:效率、AI与调试实战指南

做 .NET、C 和桌面端开发这些年,Visual Studio 算是我每天打开次数最多的工具。2026 年再看,VS 的插件生态已经非常成熟,但问题也随之而来:插件市场里鱼龙混杂,很多项目标题把插件吹得天花乱坠,装上之后却发…

作者头像 李华
网站建设 2026/9/28 7:06:15

数据库程序操作优化:SQL、连接池与事务锁实战指南

数据库性能优化做到第三篇,聊点真正让DB同学“血压升高”的东西——程序操作优化。前两篇如果讲的是硬件选型、参数调优这些服务器侧的活儿,那这篇就完全是“人和代码”的战争了。我见过太多业务系统,硬件配置拉满、MySQL参数抄了一堆大厂模板…

作者头像 李华
网站建设 2026/9/28 7:06:14

数据库性能优化:从程序操作入手,根治N+1查询与连接池陷阱

做后端这几年,有个感受特别明显:一说数据库性能差,大家的直觉反应就是看索引、调参数、加机器,但很多时候真正把数据库拖垮的,恰恰是程序里那些不起眼的操作习惯——循环里发查询、事务包裹了远程调用、连接池配得过大…

作者头像 李华
网站建设 2026/9/28 7:05:33

SAM2高精度医疗图像分割算法:数据、微调与推理部署全流程解析

简介:基于SAM2的高精度医疗图像分割算法项目,面向医学影像分析研究人员、深度学习开发者及初学者,提供从模型训练、推理到GUI交互的完整实践方案。资源压缩包共77个文件、约31.86MB,涵盖39个Python源码脚本、9个Markdown说明文档、…

作者头像 李华