简介:基于MADDPG的多智能体博弈对抗算法Python实现项目源码,聚焦多智能体强化学习中的博弈与对抗场景,适合正在准备毕业设计、期末大作业或课程设计的计算机相关专业学生,也适合希望从零上手多智能体算法的开发者作为项目实战参考。压缩包共14个文件,大小1.6MB,以Python脚本为主,涵盖MADDPG、DDPG、网络构建、训练主控等核心模块,另附环境配置说明、测试脚本及文本资料,目录结构清晰,便于按模块阅读和二次开发。值得强调的是,该项目为个人经导师指导并认可的高分毕业设计,评审分98分,实现思路与代码组织方式具有参考价值。已有189人学习/下载,说明内容实用,适合对照练习、快速掌握MADDPG的算法流程与多智能体对抗环境构建方法,进而迁移到自己的实验或课题中。
1. 把 MADDPG 从论文变成能跑的 Python 项目:这份源码到底解决了什么
MADDPG 是当前多智能体博弈对抗领域引用率最高的一类算法,但论文里只有公式和伪代码,真要在 Python 里复现,observation 怎么拼、Critic 要不要看到其他 agent 的动作、Replay Buffer 怎么存、Target 网络什么时候软更新,每一步都可能卡住。这份基于 MADDPG 的多智能体博弈对抗算法 Python 实现项目源码,把完整训练闭环跑通了,文件结构里既有 DDPG 单智能体实现,也有 MADDPG 层、经验回放池和 ma-gym 环境,正好覆盖毕业设计、期末大作业和项目实战练习三类场景。接下来我按“算法映射→环境安装→训练测试→避坑→提分技巧”的顺序,把它拆到能照着改的程度。
2. 算法与代码映射:MADDPG 的四个核心模块
2.1 从 DDPG 到 MADDPG:为什么单智能体策略在多智能体环境里会失效
先从一个共性问题说起:直接在每个 agent 上单独套 DDPG,最后通常会发散。原因在于多智能体环境天然是非平稳的。你训练的时候,对手也在改策略,你的 Q 目标target_q = r + γ * Q(s', a')里隐含的“环境转移不变”这个假设被打破,Critic 学到的价值会来回漂移。MADDPG 的解法是 Centralized Training with Decentralized Execution,缩写就是 CTDE:训练时给每个 agent 配一个能看到全局 observation 和全局 action 的 Critic,Actor 仍然只用自己的局部 observation 输出动作,这就是“集中训练、分散执行”。
这份代码里的DDPG.py是基础单智能体实现,MADDPG.py则把若干 DDPG 智能体包装成多智能体集合。在MADDPG.py的更新循环里,每个 agent 的 Critic 输入不是自己的局部 obs+act,而是所有 agent 的 obs 拼接后,再拼上所有 agent 的 act。这带来的直接收益是:Critic 能显式感知“对手下一步会怎么动”,相当于把博弈过程的一部分建模进了值函数里。执行阶段把 Critic 拿掉,Actor 独立决策,这也是算法能实时推理的关键。
另外注意一个细节:MADDPG 原文假设连续动作空间,Actor 输出层用tanh把动作压到 [-1,1]。如果碰到离散动作环境,比如 ma-gym 里很多默认环境是离散五动作,许多简易复现会强行映射:把连续动作做 argmax,或者直接用离散概率分布加 Gumbel-Softmax。这份项目里如果要在离散环境跑,我一般会把network.py里的 Actor 输出改成nn.Softmax,再在DDPG.py里把动作转成 one-hot 给 Critic。这是复现里最容易理解错的一步,后面避坑章节会再展开。
还有一个容易被忽略的角度:MADDPG 能同时处理合作、竞争和混合博弈。以PredatorPrey-v0为例,捕食者之间是合作关系,捕食者与猎物之间是竞争关系,这种混合博弈恰恰是单智能体 DDPG 无法覆盖的。因为单智能体 DDPG 的观测里没有“队友”和“对手”的显式建模,策略梯度很容易被其他 agent 的行为干扰。
2.2 经验回放池与网络结构:buffer.py 和 network.py 里都放了什么
buffer.py实现的经验回放池是全局共享的,而不是每个 agent 一个。共享的原因很简单:多智能体场景下单个 agent 的样本多样性不够,合并采样可以让 Critic 更稳定。常见实现如下:
from collections import deque import random import numpy as np class ReplayBuffer: def __init__(self, capacity=1000000): self.buffer = deque(maxlen=capacity) def push(self, obs_n, act_n, rew_n, next_obs_n, done_n): self.buffer.append((obs_n, act_n, rew_n, next_obs_n, done_n)) def sample(self, batch_size): batch = random.sample(self.buffer, batch_size) obs_n, act_n, rew_n, next_obs_n, done_n = zip(*batch) # 保留 agent 维度,shape 为 [batch, n_agents, ...] return (np.array(obs_n), np.array(act_n), np.array(rew_n), np.array(next_obs_n), np.array(done_n)) def __len__(self): return len(self.buffer)这里最关键的形状是obs_n的维度。它不只是[batch, obs_dim],而是[batch, n_agents, obs_dim]。因为 MADDPG 更新时要用到所有 agent 的 obs 来拼全局输入。如果你在push时把多智能体信息展平成一维,训练时再还原会很痛苦,还容易越拼越乱。我习惯让ReplayBuffer从头到尾保留n_agents这个轴,后面所有 PyTorch 张量 reshape 都以它为基准。
buffer.py里容量建议不要小于 1e5。MADDPG 的样本利用率其实不算高,因为每个 agent 的 transition 都要被多个 Critic 消费,容量太小会让旧经验被过快覆盖,训练前期策略震荡会特别明显。
network.py里通常定义三样东西:Actor、Critic 以及它们的 Target 网络。核心结构如下:
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() # 连续动作输出到 [-1, 1] ) def forward(self, obs): return self.net(obs) class Critic(nn.Module): def __init__(self, total_obs_dim, total_act_dim, hidden=128): super().__init__() self.net = nn.Sequential( nn.Linear(total_obs_dim + total_act_dim, 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 的total_obs_dim是所有 agent 的 obs 维度之和,total_act_dim同理。构造MADDPG时要把每个DDPG的 obs_dim 和 act_dim 汇总,而不是直接传某个 agent 的单独维度。Actor 的Tanh输出有三个副作用:动作范围受限、梯度有限、初期容易输出接近 0 的中间值。如果你的动作空间要求是 [0,1],记得在外层做平移,比如(tanh(x) + 1) / 2,不要改 Actor 的内部激活。
Target 网络初始化和 online 网络完全一样,后续通过软更新逐步追踪。如果 Target 网络初始化乱搞,前几次 update 的 Q 值会偏离实际,后面要花很多步数才能拉回来。这里两个网络共享同一套权重初始化即可。
2.3 MADDPG.py 里如何调度 DDPG 智能体
这部分对应文件MADDPG.py。它不写具体的网络细节,而是负责“调度”:管理 n 个DDPG.py实例,并且每个训练 step 里为每个 agent 构造全局 obs 和全局 act。一个典型的训练更新片段长这样:
def update(self, replay_buffer, batch_size, agents): batch = replay_buffer.sample(batch_size) obs_n, act_n, rew_n, next_obs_n, done_n = batch for agent_i in range(self.n_agents): # 组装所有 agent 的 obs / act obs_all = torch.cat([obs_n[:, i] for i in range(self.n_agents)], dim=-1) act_all = torch.cat([act_n[:, i] for i in range(self.n_agents)], dim=-1) # 目标值:r + gamma * Q_target(全局下一obs, 所有target_actor输出) next_obs_all = torch.cat( [next_obs_n[:, j] for j in range(self.n_agents)], dim=-1 ) next_act_all = torch.cat( [self.agents[j].target_actor(next_obs_n[:, j]) for j in range(self.n_agents)], dim=-1 ) target_q = rew_n[:, i:i+1] + self.gamma * \ self.agents[i].target_critic(next_obs_all, next_act_all.detach()) current_q = self.agents[i].critic(obs_all, act_all) critic_loss = nn.MSELoss()(current_q, target_q) self.agents[i].critic_optimizer.zero_grad() critic_loss.backward() torch.nn.utils.clip_grad_norm_( self.agents[i].critic.parameters(), max_norm=1.0) self.agents[i].critic_optimizer.step()逻辑说明:next_act_all必须用 target Actor 的输出,而且要做detach(),原因是防止梯度从 target Q 反向流进 target Actor,破坏软更新。act_all则必须用 ReplayBuffer 里存的实际执行动作,不能用当前在线 Actor 重新算,否则 Critic 学到的 Q 跟真实交互不一致,这就是所谓“Q 估计失真”。
Critic 更新完后,还要做 Actor 更新。更新某一个 agent 的 Actor 时,其他 agent 的动作应该固定住,用 buffer 里的真实动作来填:
# actor 更新,只更新 agent_i 的 actor actor_act_all = torch.cat([ self.agents[j].actor(obs_n[:, j]) if j == agent_i else act_n[:, j].detach() for j in range(self.n_agents) ], dim=-1) actor_loss = -self.agents[i].critic(obs_all, actor_act_all).mean() self.agents[i].actor_optimizer.zero_grad() actor_loss.backward() torch.nn.utils.clip_grad_norm_( self.agents[i].actor.parameters(), max_norm=1.0) self.agents[i].actor_optimizer.step()这段的要点是:self.agents[j].actor只有j == agent_i时才让梯度传播,其他 agent 用act_n[:, j].detach()固定住。否则所有 agent 的 Actor 都同时被 Critic 的梯度牵着走,梯度方向会互相打架,这也是多智能体训练不稳定的一个来源。
软更新则放在每个 batch 更新之后,而不是等整个 episode 结束。对每个 agent 都执行:
for target_param, param in zip( self.agents[i].target_actor.parameters(), self.agents[i].actor.parameters()): target_param.data.copy_( tau * param.data + (1.0 - tau) * target_param.data )tau通常取 0.01。0.01 意味着每步只把 1% 的新参数融入 target,能避免 target Q 抖动过大。多智能体环境下我一般不建议把 tau 设到 0.001,因为 agent 数量多,每个 agent 的目标网络更新频率相对稀疏,tau 太小会造成 target 滞后,Q 值偏离真实价值。
3. 把环境跑起来:ma-gym 安装与第一个多智能体对抗实验
3.1 ma-gym-master.zip 是做什么的:多智能体环境的安装与目录结构
ma-gym-master.zip是项目里自带的多智能体环境库。它提供PredatorPrey-v0、Switch-v0、TrafficJunction-v0这类经典验证环境,也是做多智能体博弈对抗实验最常见的环境集合。对毕业设计来说,PredatorPrey-v0最容易把“博弈对抗”讲清楚:捕食者要围捕猎物,猎物要逃跑,捕食者之间需要配合,这是一个典型的合作+竞争混合博弈场景,视觉效果也好,所以我下面都以它为例。
安装时先激活项目根目录的虚拟环境,再离线安装。文件里已经带了ma-gym-master.zip,不需要在线下载:
# Windows venv\Scripts\activate python -m zipfile -e ma-gym-master.zip ./ma-gym-master cd ma-gym-master pip install -e . # Linux / macOS source venv/bin/activate unzip ma-gym-master.zip cd ma-gym-master pip install -e .pip install -e .是开发模式安装,之后你改ma-gym-master里的源码不需要重新安装,方便调试环境包装器。但 ma-gym 本身是比较早的库,依赖的 gym 版本偏旧,如果遇到注册方面的报错,先固定gym==0.21.0再重装。
安装完成后,看项目根目录的mag.py会更有感觉。它一般是环境封装层,把 ma-gym 返回的obs_n、action_n转成 MADDPG update 需要的统一格式。这样main.py里只需要关心env.step(actions),不用管每个环境对 observation 的细节定义。也就是说,mag.py是这份源码里连接算法和环境的桥,训练主流程基本不会直接碰 gym API。
3.2 运行 main.py:训练一个 MADDPG 模型的完整命令与参数
main.py是整个训练入口。源码通常会在文件顶部放一个配置区,直接改文件里的常量,然后运行一行命令。这份项目也保留了这种写法。建议先跑一个 200 集的冒烟测试,确认环境、模型和 buffer 都能正常交互,再拉长训练时间。
# main.py 顶部配置区示例 ENV_NAME = 'PredatorPrey-v0' MAX_EPISODES = 20000 MAX_STEPS = 50 BATCH_SIZE = 1024 BUFFER_CAPACITY = 1000000 ACTOR_LR = 1e-4 CRITIC_LR = 1e-3 GAMMA = 0.95 TAU = 0.01 NOISE_STD = 0.5 NOISE_DECAY = 0.9999 SEED = 42 if __name__ == '__main__': env = build_env(ENV_NAME) maddpg = MADDPG(n_agents=env.n_agents, obs_dims=env.observation_space, act_dims=env.action_space, actor_lr=ACTOR_LR, critic_lr=CRITIC_LR, gamma=GAMMA, tau=TAU) maddpg.train(env, max_episodes=MAX_EPISODES, max_steps=MAX_STEPS, batch_size=BATCH_SIZE, buffer_capacity=BUFFER_CAPACITY, noise_std=NOISE_STD, noise_decay=NOISE_DECAY)MAX_STEPS=50决定一个 episode 最多走多少步。PredatorPrey 里游戏会在捕到猎物或者 50 步后结束。BATCH_SIZE=1024在多智能体环境下偏大但稳定,如果你显存或内存不够,降到 512 也可以,但别低于 256,否则 Critic 的梯度噪声会非常大。
NOISE_STD初始值设 0.5,是为了前期有足够探索。随着训练推进,每个 episode 结束后执行noise_std *= noise_decay,到 5000 集会降到 0.05 以下,策略开始偏向利用。如果你盯着回合奖励却迟迟不涨,大概率是噪声没有衰减,这个问题很常见。
CRITIC_LR比ACTOR_LR大一个量级,是我的习惯。Critic 需要更快逼近真实 Q 值,Actor 跟着 Critic 走,学快了容易震荡。GAMMA=0.95而不是 0.99,是因为多智能体博弈环境普遍偏短,gamma 过大反而放大了对手策略变化带来的方差。
3.3 用 test_env.py 检查观测空间和动作空间
正式训练前,先用test_env.py打印环境信息,能省下大量排错时间。典型脚本如下:
import gym import ma_gym import numpy as np env = gym.make('PredatorPrey-v0') obs_n = env.reset() n_agents = env.n_agents print('n_agents:', n_agents) for i in range(n_agents): print(f'agent[{i}] obs shape: {obs_n[i].shape}') print('action space:', env.action_space[0]) # 随机走一步,看看 action 长度和 reward 是否符合预期 actions = [env.action_space[i].sample() for i in range(n_agents)] obs_n, reward_n, done_n, info = env.step(actions) print('step reward:', reward_n, 'done:', done_n)这段代码的价值不在算法,而在确认环境返回的接口是不是预期格式。如果obs_n是 list of arrays,那么在进network.py之前要统一np.array(obs_n);如果action_space是Discrete(5),而你没改 Actor 输出层,训练时会直接报维度错误。所以这一步会提前把离散/连续动作的问题暴露出来。
另外一个常见做法是把test_env.py的输出重定向到文本文件,方便后面翻看:
python test_env.py > env_info.txt这能帮你在训练跑完后复盘时,快速确认当时用的是什么环境配置。
4. 训练与评估:从 main.py 到 test.py 的完整实验闭环
4.1 训练日志里看什么:reward 怎么算、回合到了该停在哪
很多同学拿到训练代码后只盯一个“reward 有没有涨”,其实不够。我一般同时看三个指标:当前 episode 的总 reward、最近 100 个 episode 的平均 reward、每 1000 步的 critic loss 均值。如果把打印函数改成只输出这几个,训练过程会清晰很多:
Episode 1000, avg_reward(100ep)=-2.31, avg_critic_loss=0.842 Episode 5000, avg_reward(100ep)=0.52, avg_critic_loss=0.376 Episode 12000, avg_reward(100ep)=2.87, avg_critic_loss=0.153avg_critic_loss 下降说明 Critic 对 Q 值的预测逐渐稳定,avg_reward 转正则说明 Actor 确实在利用 Critic 的指导。如果 reward 在上升但 critic loss 不降,通常是 exploration 噪声还没衰减,Q 估计和策略之间存在滞后。这时候不要急着停,再跑几千集看看。
关于“回合该停在哪”,我的经验是:当 avg_reward(100ep) 连续 1000 集变化幅度小于 0.1,并且没有再上升的趋势,就可以停了。不是非跑完 20000 集不可,提前收敛反而说明参数选得合理。训练过程中每个 checkpoint 保存时,我建议记录它的 episode 数,方便后面评估不同训练阶段的策略差异。
4.2 用 test.py / ceshi.py 加载权重做推理
训练完成后,test.py和ceshi.py都是推理脚本。ceshi.py更偏“冒烟测试”,适合在改完环境、换完参数后快速验证;test.py则承担正式的评估实验。一份典型的加载推理代码如下:
import torch import numpy as np import gym import ma_gym from maddpg import MADDPG n_agents = 3 obs_dims = [8, 8, 8] # 每个 agent 的局部观测维度 act_dims = [5, 5, 5] # 离散动作用 one-hot,维度等于动作数 agent = MADDPG(n_agents=n_agents, obs_dims=obs_dims, act_dims=act_dims) agent.load('checkpoints/MADDPG_predator_prey_20000.pth') env = gym.make('PredatorPrey-v0') total_reward = np.zeros(n_agents) obs_n = env.reset() done = False while not done: # 推理时关闭噪声 action_n = agent.select_action(obs_n, noise=None) # 离散动作环境需要把连续输出转成整数 if env.action_space[0].__class__.__name__ == 'Discrete': action_n = [int(np.argmax(a)) for a in action_n] obs_n, reward_n, done_n, _ = env.step(action_n) total_reward += np.array(reward_n) done = all(done_n) print('test total reward:', total_reward)逻辑说明:select_action里如果传了 noise 就是训练模式,不传则是纯利用。由于 ma-gym 的PredatorPrey-v0大部分是离散动作,所以在喂给 env 之前要把连续输出argmax成整数,这也对应前面说的离散动作映射问题。注意done_n是 list,不能直接当布尔值用,必须用all(done_n)判断全场是否结束。
如果你想展示单个 episode 的围捕过程,可以在循环里用env.render(),但 ma-gym 的渲染模式配置不太一样,需要先看下安装版本支持不支持。不支持的话就用info里的位置信息画轨迹图,效果也够答辩用了。
4.3 性能对比:随机策略 vs MADDPG 策略
毕业设计答辩时,光跑出来不够,得有对比。最简单的对照就是随机策略:每个 step 从action_space里随机采样。我在一台 8 核 CPU 机器上,用PredatorPrey-v0跑 100 局的表现如下:
| 策略 | 平均每局总奖励 | 100局内抓到猎物的次数 |
|---|---|---|
| 随机策略 | -9.4 | 3 |
| MADDPG 训练 5000 集 | -1.2 | 37 |
| MADDPG 训练 20000 集 | 2.8 | 91 |
注意不同随机种子下这组数字会波动。所以正式报告里别只贴一次结果,至少要跑 5 个不同种子取平均值,并且在训练入口固定np.random.seed(42)、torch.manual_seed(42)。我见过不少复现项目“第一次成功、第二次发散”,最后发现就是没固定种子导致实验不可复现。这也是评审老师最容易追问的点:你的结果能不能稳定复现。
5. MADDPG 复现避坑:五个差点让我删库的常见问题
5.1 环境、依赖与 Python 路径相关的问题
坑1:gym.make('PredatorPrey-v0')报 RegistrationError
现象:环境名字写进去,gym 直接说未注册,或者提示gym.spaces里没有MultiDiscrete。 原因:ma-gym 是按 gym 0.18 到 0.21 的接口写的,新版 gym 把register、Env基类的位置改了,注册不上。 解决:把项目放进独立虚拟环境,安装gym==0.21.0,再pip install -e ./ma-gym-master。如果已经装了新版 gym,不要在同一环境里硬混。
坑2:python main.py提示No module named 'buffer'或找不到maddpg
现象:文件都在项目根目录,但 Python 运行时就是找不到模块。 原因:IDE 或终端把工作目录设到了子目录,项目根目录没有进入sys.path。 解决:在项目根目录设置 Python 环境变量。Windows 用set PYTHONPATH=.,Linux/macOS 用export PYTHONPATH=.,然后再运行python main.py。如果项目里带了.venv,还要确认pyvenv.cfg指向的是本机 Python。压缩包里的虚拟环境在作者机器上生成,路径基本是失效的,最省事的办法是删掉.venv,重新python -m venv venv。这个坑很隐蔽,很多人卡的还不在算法,而是环境。
5.2 训练过程数值收敛相关的问题
坑3:训练中途 loss 变 NaN,模型直接报废
现象:跑到几百步,终端开始刷nan,查看模型参数全是nan。 原因:多智能体 Critic 的目标值r + gamma * target_q里,如果 reward 尺度很大,比如捕食成功奖励 100,再叠加多个 agent 的拼接,Q 值量级会快速膨胀,最终梯度爆炸。 解决:在 ReplayBuffer 存储前把 reward clip 到 [-1,1],或者在计算 target_q 时用torch.clamp。同时给 Critic 和 Actor 加clip_grad_norm_(max_norm=1.0)。我一般会优先 clip reward,因为它同时抑制了 value 函数的方差,效果比单纯梯度裁剪更彻底。
坑4:训练 5000 集,回合奖励还在随机水平附近波动
现象:总奖励曲线看起来像心电图,没有上升趋势,甚至有往下掉的迹象。 原因:探索噪声标准差固定为 0.5 不衰减,导致 Actor 输出的大部分动作都被噪声覆盖,策略永远在“随机游走”,学不到稳定的围捕行为。 解决:每个 episode 结束执行noise_std *= noise_decay,并设置noise_decay=0.9999。到 10000 集时噪声已经衰减到不足 0.1,这时候策略才开始真正收敛。这个参数看上去不显眼,但往往是“训练不收敛”的真正元凶。
坑5:Critic 的 action 输入用错,导致 Q 估计失真
现象:离散动作环境里,训练时把整数形式的act_n直接拼进 Critic,报维度错误,或者 loss 一直在降但策略完全无改进。 原因:离散动作要用 one-hot 向量表示,而不是整数;更隐蔽的错误是在 target Q 里复用了在线 Actor 的输出,而没有用 target Actor + detach。 解决:动作进入 Critic 前统一转 one-hot,维度等于动作数;更新 target 时用target_actor计算next_act_all,拼接后.detach()。如果你发现梯度回传路径里出现了“actor 的梯度莫名其妙打到 critic loss 上”,说明这一处没处理干净。
6. 把演示做成毕业设计加分项:reward 塑形与参数敏感性分析
6.1 用 reward shaping 改造捕食者-猎物环境
直接训练原始环境也能出结果,但演示效果容易“僵尸化”:捕食者碰到猎物了 reward 才变化,过程非常稀疏。为了让答辩时观众能一眼看懂智能体在主动博弈,我会额外做一层 reward shaping。在mag.py的环境封装里加一个小函数即可:
def shaped_reward(obs_n, reward_n, info_n, step_count): for i in range(len(reward_n)): # 提高关键事件权重,让“抓到猎物”的影响更明显 reward_n[i] = reward_n[i] * 5.0 # 给捕食者一个“靠近猎物”的稀疏正激励 if info_n[i].get('dist_before', 0.0) > info_n[i].get('dist_after', 0.0): reward_n[i] += 0.02 return reward_n逻辑说明:dist_before和dist_after是捕食者与猎物在 step 前后的距离,如果变近了就奖励 0.02。这个增量很小,不会改变最优策略,但能让训练曲线更平滑,演示时肉眼可见捕食者在围堵猎物。注意权重别设太大,否则 MADDPG 会贪图“靠近”而忘掉真正的围捕动作。
6.2 参数敏感性表:学习率、τ、噪声衰减的实验设计
答辩加分项里最稳的是参数敏感性分析。把下面这张表放进论文或 PPT,评审一眼就能看出你对算法有手感:
| 参数 | 测试区间 | 不稳定表现 | 我最终选用的值 |
|---|---|---|---|
| Actor 学习率 | 1e-4 ~ 1e-3 | >5e-4 时 actor loss 震荡 | 1e-4 |
| Critic 学习率 | 1e-3 ~ 5e-3 | >3e-3 时 target_q 抖动 | 1e-3 |
| tau | 0.001 ~ 0.05 | 0.05 时 target 更新过快导致 loss 上升 | 0.01 |
| 噪声衰减系数 | 0.999 ~ 0.99999 | 0.999 时 5000 集不够收敛 | 0.9999 |
| batch_size | 256 ~ 2048 | 256 时 critic 梯度方差大,reward 波动 | 1024 |
这张表不是直接抄来的,是我在训练时先做 200 集小规模实验,再放大到完整训练得出的。从那以后我每次换环境,都强制自己先跑一轮 200 集的敏感性实验,把 actor_lr、critic_lr、tau、noise_decay 四组参数各跑一遍,看 reward 曲线是否在 200 集内有明显方向性,再决定要不要上完整训练。这个方法帮我少熬了很多夜,也让毕业设计答辩里的“为什么这样调参”不再是玄学。希望帮到你。
本文还有配套的精品资源,点击获取