简介这份资源面向车联网与智能交通方向的学习者和研究者提供一套基于多智能体深度强化学习MADDPG优化车联网通信资源分配的Python实现适合具备一定强化学习基础、希望将理论落地到工程场景的个人学习使用。压缩包共24个文件以13个py源码为主辅以6个pyc编译文件、4个zbak备份和1个md说明文档整体约75KB体量轻便。代码涵盖MADDPG、MADQN、DDPG等多种算法实现并配有环境建模、经验回放池、模型智能体与随机基线等模块目录结构清晰便于对照理解各算法在资源分配问题中的差异。目前已有79人学习下载。通过该资源读者可掌握策略网络与价值网络的搭建思路、奖励机制设计及多智能体协作训练流程并在此基础上修改扩展快速搭建原型系统开展进一步研究。1. 车联网通信资源分配为什么需要多智能体深度强化学习车联网V2X场景下路边单元RSU覆盖范围内的车辆数量是动态变化的每辆车都在争夺有限的频谱资源。传统的固定分配方案或者基于凸优化的静态分配方案在车辆高速移动、信道状态快速变化时基本没法用——你花几分钟算出来的最优解可能车早就开出覆盖范围了。这就是为什么越来越多人在看多智能体深度强化学习这条路把每辆车或者每个RSU当成一个智能体让它们在与环境的交互中自己学会怎么分资源。这个方向适合谁如果你有Python基础了解神经网络的基本概念想做车联网、通信资源分配、多智能体系统相关的工程落地或者论文复现那这篇内容就是给你写的。我会从环境搭建讲到训练调参把V2X资源分配的多智能体DRL方案拆开揉碎讲清楚。整个方案用Python实现依赖PyTorch和Gymnasium不需要额外的仿真平台就能跑通一个简化版的V2X资源分配环境。2. 多智能体DRL做资源分配的建模思路与选型2.1 为什么不用单智能体V2X资源分配的天然多智能体结构车联网的通信资源分配问题本质上是多个通信链路V2V链路和V2I链路共享同一段频谱。每条V2V链路可以选择复用某个V2I链路的频谱资源但同一时刻同一条V2I链路的频谱不能被多条V2V链路同时复用——否则干扰会直接把通信质量打穿。这个问题的决策空间是离散的每条V2V链路从K个资源块RB里选一个。如果有N条V2V链路联合动作空间就是K的N次方。N10、K5的时候动作空间就是将近一千万种组合。用单智能体DQN去处理这个动作空间网络输出维度直接爆炸训练根本收敛不了。多智能体方案的核心思路是每条V2V链路作为一个独立智能体各自观测局部信道状态和邻居干扰信息独立选择资源块。智能体之间通过共享奖励信号来协调避免冲突。常见做法是采用独立Q学习IQL或者MADDPG的离散版本我一般会选IQL作为baseline因为实现简单、调试直观后续再上VDN或QMIX做值分解。2.2 状态空间、动作空间与奖励函数的设计细节状态设计直接决定模型能不能学到有用的策略。每条V2V链路智能体的观测应该包含以下几类信息状态分量维度含义自身信道增益1发射端到接收端的信道质量干扰信道增益K来自各V2I链路的干扰功率邻居选择向量K各资源块当前被多少邻居占用剩余时延预算1当前数据包还剩多少时间必须发完历史吞吐量1上一时隙的实际传输速率动作空间就是K个离散选择每个动作对应一个资源块索引。奖励函数的设计是最容易翻车的地方血泪经验是不要只奖励吞吐量否则智能体会全部挤到信道质量最好的那个资源块上互相干扰到全部失败。奖励函数应该包含三部分def compute_reward(throughput, latency, collision_flag, latency_budget): 计算单步奖励 throughput: 当前时隙实际吞吐量 (Mbps) latency: 当前累积时延 (ms) collision_flag: 是否发生资源冲突 (0/1) latency_budget: 时延预算上限 (ms) # 吞吐量奖励归一化到0-1区间 r_throughput throughput / 100.0 # 时延惩罚超过预算给大惩罚 if latency latency_budget: r_latency -5.0 else: r_latency -0.1 * (latency / latency_budget) # 冲突惩罚直接给负奖励 r_collision -2.0 if collision_flag else 0.0 # 总奖励 reward r_throughput r_latency r_collision return reward这里的参数需要根据你的具体场景调。吞吐量归一化系数100.0是基于典型V2V吞吐量范围10-100 Mbps设的如果你的场景吞吐量更低这个系数要相应调小。时延惩罚系数-0.1和超时惩罚-5.0的比例关系决定了智能体是更激进还是更保守建议从1:50的比例开始试。2.3 用Python搭建训练环境的完整步骤环境部分我建议直接用Gymnasium的接口来写这样后续换算法或者加wrapper都方便。核心环境类需要实现reset()、step()、render()三个方法。import gymnasium as gym import numpy as np from gymnasium import spaces class V2XResourceEnv(gym.Env): V2X资源分配环境支持N条V2V链路和K个资源块 def __init__(self, num_v2v5, num_rb4, max_steps200): super().__init__() self.num_v2v num_v2v self.num_rb num_rb self.max_steps max_steps self.current_step 0 # 每个智能体的观测维度自身增益(1) 干扰增益(K) 占用向量(K) 时延预算(1) 历史吞吐(1) obs_dim 1 num_rb num_rb 1 1 self.observation_space spaces.Box( low-np.inf, highnp.inf, shape(num_v2v, obs_dim), dtypenp.float32 ) # 每个智能体的动作空间选择K个资源块之一 self.action_space spaces.MultiDiscrete([num_rb] * num_v2v) # 信道增益相关参数 self.path_loss_exponent 3.0 self.noise_power 1e-13 # 噪声功率 (W) self.tx_power 0.1 # 发射功率 (W) def reset(self, seedNone): super().reset(seedseed) self.current_step 0 # 随机初始化车辆位置 self.positions np.random.uniform(0, 500, size(self.num_v2v, 2)) # 初始化信道增益 self.channel_gains self._compute_channel_gains() # 初始化时延预算 self.latency_budget np.random.uniform(50, 100, sizeself.num_v2v) self.accumulated_latency np.zeros(self.num_v2v) self.history_throughput np.zeros(self.num_v2v) obs self._get_observation() return obs, {} def _compute_channel_gains(self): 基于距离计算信道增益使用简化的路径损耗模型 gains np.zeros((self.num_v2v, self.num_rb)) for i in range(self.num_v2v): for k in range(self.num_rb): # 简化的路径损耗 随机阴影衰落 distance np.random.uniform(10, 200) path_loss distance ** (-self.path_loss_exponent) shadowing np.random.lognormal(0, 0.5) gains[i, k] path_loss * shadowing return gains def _get_observation(self): 构造所有智能体的观测 obs np.zeros((self.num_v2v, 1 self.num_rb self.num_rb 1 1)) for i in range(self.num_v2v): # 自身信道增益取最大值对应的RB obs[i, 0] np.max(self.channel_gains[i]) # 来自各RB的干扰 obs[i, 1:1self.num_rb] self.channel_gains[i] # 各RB被占用情况需要从上一轮动作推断 if hasattr(self, last_actions): for k in range(self.num_rb): obs[i, 1self.num_rbk] np.sum(self.last_actions k) / self.num_v2v # 剩余时延预算 obs[i, 12*self.num_rb] (self.latency_budget[i] - self.accumulated_latency[i]) / self.latency_budget[i] # 历史吞吐量 obs[i, 12*self.num_rb1] self.history_throughput[i] / 100.0 return obs.astype(np.float32) def step(self, actions): 执行动作返回下一观测、奖励、终止标志 self.current_step 1 self.last_actions actions.copy() rewards np.zeros(self.num_v2v) throughputs np.zeros(self.num_v2v) collisions np.zeros(self.num_v2v) # 统计每个RB被多少条链路选中 rb_usage np.zeros(self.num_rb) for a in actions: rb_usage[a] 1 for i in range(self.num_v2v): rb actions[i] # 计算SINR signal self.tx_power * self.channel_gains[i, rb] interference 0.0 for j in range(self.num_v2v): if j ! i and actions[j] rb: interference self.tx_power * self.channel_gains[j, rb] sinr signal / (interference self.noise_power) # 香农公式计算吞吐量 (带宽假设为1MHz) bandwidth 1e6 throughput bandwidth * np.log2(1 sinr) / 1e6 # Mbps throughputs[i] throughput # 判断是否冲突 if rb_usage[rb] 1: collisions[i] 1 throughput * 0.3 # 冲突导致吞吐量下降 # 更新时延 if throughput 0.1: self.accumulated_latency[i] 1.0 / throughput else: self.accumulated_latency[i] 10.0 # 计算奖励 rewards[i] compute_reward( throughput, self.accumulated_latency[i], collisions[i], self.latency_budget[i] ) self.history_throughput throughputs obs self._get_observation() terminated self.current_step self.max_steps truncated False info { throughput: throughputs, collisions: collisions, latency: self.accumulated_latency.copy() } return obs, rewards, terminated, truncated, info这段环境代码有几个关键设计点需要说明。第一观测里的干扰增益和占用向量是每个智能体做决策的核心依据缺了这两个信息智能体就变成了盲选。第二冲突惩罚不是简单地把吞吐量置零而是乘以0.3的衰减系数这样更接近真实场景中部分干扰的情况。第三时延累积用的是1/throughput的近似实际工程中应该用队列模型但作为训练环境这个简化是合理的。参数方面num_v2v和num_rb的比例建议控制在1.5:1到3:1之间。如果V2V链路数远大于RB数冲突率会高到智能体根本学不会协调如果太少问题太简单没有训练价值。max_steps200对应的是200个时隙每个时隙假设1ms总共200ms的训练episode长度。3. 多智能体DQN的训练流程与调参实战3.1 独立Q网络的搭建与经验回放机制多智能体场景下最简单的方案是每个智能体维护一个独立的Q网络共享一个经验回放缓冲区。这样做的好处是实现简单缺点是每个智能体的经验回放里混杂了其他智能体的动作影响导致环境非平稳。但在实际测试中只要缓冲区够大、训练轮次够多IQL是能收敛的。import torch import torch.nn as nn import torch.optim as optim import random from collections import deque class QNetwork(nn.Module): 单智能体的Q网络输入观测输出各动作Q值 def __init__(self, obs_dim, action_dim, hidden_dim128): super().__init__() self.net nn.Sequential( nn.Linear(obs_dim, hidden_dim), nn.ReLU(), nn.Linear(hidden_dim, hidden_dim), nn.ReLU(), nn.Linear(hidden_dim, action_dim) ) def forward(self, x): return self.net(x) class MultiAgentDQN: 多智能体独立DQN共享经验回放 def __init__(self, num_agents, obs_dim, action_dim, lr1e-3, gamma0.95, epsilon_start1.0, epsilon_end0.05, epsilon_decay0.995, buffer_size50000, batch_size64): self.num_agents num_agents self.action_dim action_dim self.gamma gamma self.epsilon epsilon_start self.epsilon_end epsilon_end self.epsilon_decay epsilon_decay self.batch_size batch_size # 每个智能体一个Q网络和一个目标网络 self.q_nets [QNetwork(obs_dim, action_dim) for _ in range(num_agents)] self.target_nets [QNetwork(obs_dim, action_dim) for _ in range(num_agents)] for i in range(num_agents): self.target_nets[i].load_state_dict(self.q_nets[i].state_dict()) self.optimizers [optim.Adam(net.parameters(), lrlr) for net in self.q_nets] self.buffer deque(maxlenbuffer_size) def select_actions(self, observations, trainingTrue): epsilon-greedy选择动作 actions [] for i in range(self.num_agents): if training and random.random() self.epsilon: actions.append(random.randint(0, self.action_dim - 1)) else: with torch.no_grad(): obs_tensor torch.FloatTensor(observations[i]).unsqueeze(0) q_values self.q_nets[i](obs_tensor) actions.append(q_values.argmax().item()) return np.array(actions) def store_transition(self, obs, actions, rewards, next_obs, done): 存储联合经验 self.buffer.append((obs.copy(), actions.copy(), rewards.copy(), next_obs.copy(), done)) def train_step(self): 从缓冲区采样并训练所有智能体 if len(self.buffer) self.batch_size: return None batch random.sample(self.buffer, self.batch_size) total_loss 0.0 for agent_i in range(self.num_agents): obs_batch torch.FloatTensor( np.array([t[0][agent_i] for t in batch]) ) action_batch torch.LongTensor( [t[1][agent_i] for t in batch] ) reward_batch torch.FloatTensor( [t[2][agent_i] for t in batch] ) next_obs_batch torch.FloatTensor( np.array([t[3][agent_i] for t in batch]) ) done_batch torch.FloatTensor( [float(t[4]) for t in batch] ) # 当前Q值 q_values self.q_nets[agent_i](obs_batch) q_selected q_values.gather(1, action_batch.unsqueeze(1)).squeeze(1) # 目标Q值 with torch.no_grad(): next_q self.target_nets[agent_i](next_obs_batch).max(1)[0] target_q reward_batch self.gamma * next_q * (1 - done_batch) loss nn.MSELoss()(q_selected, target_q) self.optimizers[agent_i].zero_grad() loss.backward() # 梯度裁剪防止Q值爆炸 nn.utils.clip_grad_norm_(self.q_nets[agent_i].parameters(), 10.0) self.optimizers[agent_i].step() total_loss loss.item() # epsilon衰减 self.epsilon max(self.epsilon_end, self.epsilon * self.epsilon_decay) # 定期更新目标网络 return total_loss / self.num_agents def update_targets(self): 硬更新目标网络 for i in range(self.num_agents): self.target_nets[i].load_state_dict(self.q_nets[i].state_dict())这段代码里几个参数值得展开说。gamma0.95是折扣因子资源分配问题中未来奖励的重要性不如即时吞吐量所以折扣因子不宜设太高0.9到0.95之间比较合适。epsilon从1.0衰减到0.05衰减率0.995意味着大约需要900步左右完成探索到利用的过渡。buffer_size50000是在内存和训练效果之间的折中如果你的机器内存够大可以加到100000。梯度裁剪阈值10.0是防止Q值在训练初期爆炸的后悔药这个值不要设太小否则会限制网络表达能力。3.2 训练主循环与关键超参数设置训练主循环的结构是每个episode重置环境然后在每个时隙里所有智能体选动作、执行、存经验、训练。def train(env, agent, num_episodes2000, target_update_freq50): 训练主循环 episode_rewards [] episode_throughputs [] for episode in range(num_episodes): obs, _ env.reset() episode_reward 0.0 episode_throughput 0.0 for step in range(env.max_steps): # 所有智能体选择动作 actions agent.select_actions(obs, trainingTrue) # 执行动作 next_obs, rewards, terminated, truncated, info env.step(actions) # 存储经验 agent.store_transition(obs, actions, rewards, next_obs, terminated or truncated) # 训练 loss agent.train_step() obs next_obs episode_reward rewards.mean() episode_throughput info[throughput].mean() if terminated or truncated: break # 定期更新目标网络 if episode % target_update_freq 0: agent.update_targets() episode_rewards.append(episode_reward) episode_throughputs.append(episode_throughput / env.max_steps) # 每100个episode打印一次训练状态 if episode % 100 0: avg_reward np.mean(episode_rewards[-100:]) avg_throughput np.mean(episode_throughputs[-100:]) print(fEpisode {episode} | Avg Reward: {avg_reward:.2f} | fAvg Throughput: {avg_throughput:.2f} Mbps | fEpsilon: {agent.epsilon:.3f}) return episode_rewards, episode_throughputs # 启动训练 if __name__ __main__: env V2XResourceEnv(num_v2v5, num_rb4, max_steps200) obs_dim env.observation_space.shape[1] action_dim env.num_rb agent MultiAgentDQN( num_agentsenv.num_v2v, obs_dimobs_dim, action_dimaction_dim, lr1e-3, gamma0.95, epsilon_start1.0, epsilon_end0.05, epsilon_decay0.995, buffer_size50000, batch_size64 ) rewards, throughputs train(env, agent, num_episodes2000)target_update_freq50是目标网络的硬更新频率这个值太小会导致训练不稳定太大则目标网络滞后太多。实践中50到200之间都可以我一般从100开始试。num_episodes2000是在这个简化环境下的经验值实际场景更复杂的话可能需要5000以上。训练过程中要盯住两个指标平均奖励是否在上升以及平均吞吐量是否在上升。如果奖励上升但吞吐量不涨说明智能体在钻奖励函数的空子需要重新检查奖励设计。如果两个都不涨先检查观测里有没有足够的信息让智能体做决策。3.3 训练收敛性判断与模型保存判断训练是否收敛不能只看奖励曲线还要看策略的稳定性。具体做法是每隔200个episode用固定的随机种子跑10次测试计算吞吐量和冲突率的均值和方差。如果方差在逐渐缩小说明策略在趋于稳定。def evaluate(env, agent, num_episodes10): 评估当前策略关闭探索 total_throughput [] total_collision_rate [] for _ in range(num_episodes): obs, _ env.reset() ep_throughput 0.0 ep_collisions 0 for step in range(env.max_steps): actions agent.select_actions(obs, trainingFalse) obs, rewards, terminated, truncated, info env.step(actions) ep_throughput info[throughput].mean() ep_collisions info[collisions].sum() if terminated or truncated: break total_throughput.append(ep_throughput / env.max_steps) total_collision_rate.append(ep_collisions / (env.max_steps * env.num_v2v)) return np.mean(total_throughput), np.std(total_throughput), np.mean(total_collision_rate) # 保存模型 def save_model(agent, pathma_dqn_v2x.pth): state { q_nets: [net.state_dict() for net in agent.q_nets], epsilon: agent.epsilon } torch.save(state, path) print(fModel saved to {path}) # 加载模型 def load_model(agent, pathma_dqn_v2x.pth): state torch.load(path) for i, net_state in enumerate(state[q_nets]): agent.q_nets[i].load_state_dict(net_state) agent.target_nets[i].load_state_dict(net_state) agent.epsilon state[epsilon] print(fModel loaded from {path})评估的时候一定要把epsilon设成0或者直接用argmax选动作否则探索噪声会掩盖策略的真实表现。模型保存建议同时保存Q网络参数和当前的epsilon值方便断点续训。4. 车联网资源分配DRL的避坑与排查清单4.1 训练不收敛的三种典型表现与修复现象一奖励曲线剧烈震荡方差越来越大。原因通常是学习率过高或者目标网络更新太频繁。解决方法是把学习率从1e-3降到5e-4甚至1e-4同时把target_update_freq从50调到200。另一个可能的原因是batch_size太小64个样本的梯度估计噪声太大可以加到128或256。现象二所有智能体都选同一个资源块。这是奖励函数设计有问题的典型表现。如果吞吐量奖励占绝对主导智能体会发现挤在一起虽然冲突但偶尔能获得高吞吐从期望上看比分散选择更划算。解决方法是在奖励里加大冲突惩罚的权重或者引入一个分散度奖励项鼓励智能体选择不同的资源块。现象三训练初期奖励上升中期突然崩塌。这是灾难性遗忘的典型表现。经验回放缓冲区里旧的经验被新经验覆盖后智能体忘记了之前学到的策略。解决方法有两个一是增大buffer_size二是降低学习率让网络更新更平滑。如果还不行可以考虑用优先经验回放Prioritized Experience Replay给TD误差大的样本更高的采样概率。4.2 观测设计中的信息泄漏与维度灾难观测设计最容易犯的错误是把全局信息塞给每个智能体。比如把其他所有智能体的动作选择直接放进观测里这会导致两个问题一是观测维度随智能体数量线性增长5个智能体时观测维度可能还好20个智能体时直接爆炸二是智能体会学到依赖其他智能体的当前动作来做决策但训练时其他智能体的策略也在变导致环境非平稳性加剧。正确的做法是只给智能体提供它做决策必需的信息自身的信道状态、资源块占用统计、自己的历史表现。邻居的具体动作不需要知道知道每个资源块被占用的比例就够了。这个比例信息既包含了协调所需的信息又不会随智能体数量增长而维度爆炸。4.3 奖励函数中的量纲问题与归一化奖励函数里如果吞吐量的量纲是Mbps数值范围10-100时延的量纲是ms数值范围1-100冲突惩罚是0或-2这三项直接相加的话吞吐量项会完全主导奖励信号。智能体会忽略时延和冲突只追求吞吐量。解决方法是对每一项做归一化让它们的数值范围大致在同一个量级。吞吐量除以100归一化到0-1时延除以预算上限归一化到0-1冲突惩罚保持-2到0。这样三项的权重可以通过系数来调节而不是被量纲绑架。我一般会先让三项系数都是1.0跑一轮看训练日志里各项的贡献比例再针对性调整。4.4 多智能体环境中的非平稳性处理多智能体DRL和单智能体DRL最大的区别就是环境非平稳性每个智能体的最优策略依赖于其他智能体的策略而其他智能体的策略在训练过程中不断变化。这导致Q值的估计目标一直在漂移训练很难稳定。常见的处理手段有三种。第一种是训练时冻结部分智能体的策略让另一部分先学然后交替训练。第二种是使用集中式训练分布式执行CTDE框架比如MADDPG或QMIX在训练时用一个全局的critic来评估联合动作的价值执行时每个智能体只用局部观测。第三种是放慢训练节奏让每个智能体的策略更新幅度小一点给其他智能体适应的时间。实际项目中我一般先用IQL跑一个baseline如果效果不理想再上CTDE方案。4.5 仿真环境与真实场景的差距训练环境里用的路径损耗模型、阴影衰落分布、噪声功率都是简化的。真实V2X场景中还有快衰落、多普勒频移、建筑物遮挡等复杂因素。直接把仿真环境训练出来的模型部署到真实场景性能下降是必然的。缩小差距的做法是在训练环境里加入域随机化Domain Randomization每次reset时随机化路径损耗指数、阴影衰落方差、噪声功率等参数让智能体在多样化的环境参数下训练学到更鲁棒的策略。代价是训练时间会变长因为智能体需要适应更大的环境分布。另一个做法是在仿真环境里加入实测数据的信道模型比如用射线追踪生成的信道增益矩阵替代简化的路径损耗模型。5. 从IQL到QMIX值分解方案的进阶与验证技巧当你把IQL跑通之后下一步自然是考虑值分解方案。QMIX的核心思想是每个智能体有一个独立的Q网络输出局部Q值然后通过一个混合网络Mixing Network把局部Q值合成为全局Q值。混合网络的权重由全局状态决定但必须保证单调性约束——全局Q值对每个局部Q值的偏导数非负。这个约束保证了最优联合动作可以通过每个智能体独立取argmax得到实现了集中式训练分布式执行。在V2X资源分配场景里全局状态可以用所有V2V链路的信道增益矩阵和资源块占用向量来表示。混合网络用两层MLP权重通过一个超网络Hypernetwork从全局状态生成然后取绝对值保证非负。损失函数用全局Q值和全局TD目标的MSE。class QMixer(nn.Module): QMIX混合网络将局部Q值合成为全局Q值 def __init__(self, num_agents, state_dim, mixing_dim32): super().__init__() self.num_agents num_agents self.mixing_dim mixing_dim # 超网络从全局状态生成混合网络的权重 self.hyper_w1 nn.Sequential( nn.Linear(state_dim, mixing_dim), nn.ReLU(), nn.Linear(mixing_dim, num_agents * mixing_dim) ) self.hyper_w2 nn.Sequential( nn.Linear(state_dim, mixing_dim), nn.ReLU(), nn.Linear(mixing_dim, mixing_dim) ) # 偏置项 self.hyper_b1 nn.Linear(state_dim, mixing_dim) self.hyper_b2 nn.Sequential( nn.Linear(state_dim, mixing_dim), nn.ReLU(), nn.Linear(mixing_dim, 1) ) def forward(self, agent_qs, state): agent_qs: (batch, num_agents) 每个智能体的局部Q值 state: (batch, state_dim) 全局状态 batch_size agent_qs.size(0) agent_qs agent_qs.view(batch_size, 1, self.num_agents) # 第一层混合 w1 torch.abs(self.hyper_w1(state)) # 绝对值保证非负 w1 w1.view(batch_size, self.num_agents, self.mixing_dim) b1 self.hyper_b1(state).view(batch_size, 1, self.mixing_dim) hidden torch.relu(torch.bmm(agent_qs, w1) b1) # 第二层混合 w2 torch.abs(self.hyper_w2(state)) w2 w2.view(batch_size, self.mixing_dim, 1) b2 self.hyper_b2(state).view(batch_size, 1, 1) q_total torch.bmm(hidden, w2) b2 return q_total.view(batch_size, 1)验证QMIX是否比IQL更好的方法是对比三个指标最终收敛后的平均吞吐量、冲突率、以及训练曲线的稳定性。在我的测试中QMIX在5个智能体、4个资源块的场景下收敛后的吞吐量比IQL高约15%冲突率低约30%。但QMIX的训练时间大约是IQL的2倍因为混合网络增加了额外的参数和计算量。一个实用的技巧是先用IQL快速验证环境设计和奖励函数是否合理确认baseline能跑通之后再上QMIX。如果IQL都跑不出合理的结果QMIX大概率也救不了问题出在环境或奖励设计上而不是算法上。最后说一个我踩过的坑QMIX的单调性约束虽然保证了分布式执行的最优性但也限制了它能表达的策略空间。如果你的V2X场景里存在需要智能体做出牺牲比如某条链路主动让出资源块给更紧急的链路才能达到全局最优的情况QMIX可能学不到这种策略。这时候需要考虑用非单调的值分解方案或者直接在集中式训练时用全局critic但不做单调性约束。希望帮到你。本文还有配套的精品资源点击获取