简介:这是一份基于多智能体深度强化学习解决车联网通信资源分配问题的高分毕业设计项目,适合计算机、通信相关专业学生及从业者用于毕设、课程大作业或期末项目参考。压缩包共20个文件,以Python源码为主,包括13个py脚本、6个编译生成的pyc文件及1个使用说明txt,整体仅84KB,轻量易部署。项目已通过严格调试,评审分97分,具备完整的环境交互模块与算法实现。通过预览可见涵盖SAMADDPG、MADDPG、MADQN及随机基线等多种方法,并包含环境搭建、经验回放与优先经验回放、网络模型及训练主程序等核心模块,能帮助读者深入理解多智能体强化学习在无线资源分配中的建模、训练与对比分析。当前已有188人学习下载,对于希望快速复现实验、获取可直接运行的毕设源码并参考文档说明撰写论文的研究者很有实用价值。
1. 多智能体深度强化学习的车联网资源分配优化:这个 Python 毕设到底在做什么
“基于多智能体深度强化学习的车联网通信资源分配优化”这个 Python 毕设选题,表面上是“多智能体深度强化学习 + 车联网仿真”的算法叠放,真正动手以后才会发现,难点集中在两处:一是车联网通信资源分配怎么抽象成多智能体问题,二是多智能体系统训练完以后,怎么向别人证明资源分配确实比基线更优。标题里出现的“源代码”和“文档说明”意味着交付物不只是一篇论文,还要有一份可运行、可复现的工程项目。这个方向适合准备把毕设做成完整工程的同学,也适合刚接触多智能体深度强化学习的工程师,用一个小场景快速跑通 MADDPG 或 MATD3 这类算法。下面按“建模 → 算法选型 → 仿真环境 → 验证指标”这条主线展开,给出的代码都可以直接作为源代码的一部分放进工程结构里。
2. 把车联网资源分配写成多智能体问题:状态、动作、奖励怎么定
如果只是想让车辆用最大功率发射,根本不需要深度学习。真正需要算法解决的,是每一辆 V2V 发射车自己决定占用哪个子带、用多大功率发送,同时不把旁边的 V2I 链路干扰到掉线。这个决策过程是天然分布式的:每辆车只能感知自己附近的环境,却要共同影响同一张频谱资源网格。所以在搭建网络之前,必须先确定三件事:智能体是谁、动作是什么、奖励函数怎么设计。
2.1 为什么单智能体深度强化学习在 V2X 资源分配里不够用
单智能体强化学习最舒服的场景是只有一个 Agent 在做决策,环境其余部分都视为固定分布。车联网里如果使用一个全局智能体,把所有车辆的观测拼成一个超长向量,动作空间会变成“子带数 × 功率档位数 × 车辆数”的笛卡尔积。一旦车辆数从 10 增加到 50,动作空间就会爆炸,训练无法收敛。
反过来,如果为每辆车独立训练一个 DQN 或 DDPG,又会出现另一个问题:每个智能体都在更新策略,导致每一个智能体面对的环境转移概率都在变化。对单智能体来说,这是典型的非平稳环境,经验回放机制甚至会失效。多智能体深度强化学习就是针对这种场景设计的:每个车端智能体只做局部决策,训练时允许 Critic 看到所有智能体的状态和动作,从而缓解非平稳性。
在多智能体系统里,车辆彼此既是干扰源又是协作对象。例如两个 V2V 链路如果复用同一个子带,会产生相互干扰;但如果它们保持一定距离或错开时隙,反而都能成功传输。这个关系适合用博弈或协作博弈来建模,单智能体算法很难表达这种“同时存在竞争与合作”的调度语义。
2.2 Dec-POMDP 建模:刻画智能体、观测与动作空间
在这个问题里,通信资源分配可以表述为一个带约束的部分可观测马尔可夫决策过程(Dec-POMDP)。真实状态 S 包含所有车辆的位置、速度、信道状态和队列长度;每个智能体 i 只能得到局部观测 O_i;每个智能体动作 A_i 在环境里同时生效,共同决定下一时刻状态和奖励 R。
实际编码时,我不建议直接用 dict 传递观测,因为接口不稳定,调试时很难定位字段。更常见的做法是定义一个 dataclass,把每个 V2V 链路的观测封装成结构化数据:
@dataclass class V2xObs: agent_id: int pos_x: float pos_y: float speed: float direction: float tx_buffer: float # 待发送数据量,单位 Mb delay_budget_ms: float # 当前数据包的剩余时延预算 interference_dbm: float # 目标子带上测到的干扰功率这里最关键的是delay_budget_ms:车联网对 V2V 链路有严格时延要求,智能体如果忽略这个值,就会为了吞吐量不管队列超时。interference_dbm是环境给智能体的“压缩观测”,代表它侦察到的同频干扰强度。如果仿真环境允许,还可以把附近 3 辆车的位置追加到观测里。
动作空间通常由两部分组成:频谱子带索引和发射功率。
| 场景 | 动作维度 | 取值范围 |
|---|---|---|
| V2V 链路选择频谱子带 | 离散 | 例如 0 ~ 15,对应 16 个子带 |
| V2V 链路选择发射功率 | 连续或离散 | 例如 -60 dBm ~ 23 dBm |
| V2I 链路 | 可固定功率 | 由基站统一调度,不作为智能体 |
如果功率用连续值,我会把网络输出限定到 [-1, 1],再线性映射到实际的 dBm 范围。这样做的好处是数值稳定,不会在训练初期出现离谱功率。
2.2.1 把约束写进奖励函数
车联网资源分配的奖励函数必须同时表达两个目标:V2I 链路频谱效率要高,V2V 链路时延服务质量要达标。如果只优化频谱效率,智能体很快就会把功率拉满,导致同频干扰失控;如果只惩罚时延,所有智能体又可能过度避让,把频谱白白空出来。
一个比较常见的紧凑写法是:
def compute_reward(v2i_sinr_list, delay_budget_list, latency_limit_ms=5): # 第一项:平均 V2I 频谱效率 v2i_rate = np.mean([np.log2(1 + sinr) for sinr in v2i_sinr_list]) # 第二项:V2V 时延预算越限惩罚 violation = np.mean([1.0 if budget < latency_limit_ms else 0.0 for budget in delay_budget_list]) return alpha * v2i_rate - beta * violationalpha权重用于鼓励系统频谱效率,beta用于惩罚 V2V 传输失败。实际调参时,如果训练很多回合后奖励持续为负,说明beta权重过大,智能体已经进入“少发少错”的保守状态;反之如果 V2V 超时比例始终高于 5%,基本上就是beta太小。
2.3 CTDE:中心化训练,分布式执行
很多第一次接触多智能体深度强化学习的人会问:既然 Critic 能看到全局,为什么执行时不用全局信息?这是因为车联网场景不允许车辆在毫秒级调度周期内完成全部状态同步。中心化训练,分布式执行(Centralized Training with Decentralized Execution,CTDE)是折中方案:训练时,全局 Critic 把所有智能体的观测和动作拼接起来,用于逼近联合价值函数;训练结束后,真正部署的只有每个智能体本地的 Actor,它只依赖自身观测输出动作。
这一个设计决定了后续所有算法实现结构。比如 MATD3、MADDPG 的核心类,基本都遵循“本地 Actor 网络 + 中心化 Critic 网络”的模板。能看见全局的 Critic 一旦被部署到推理阶段,所谓的多智能体分布式资源分配就名存实亡了。
3. 多智能体深度强化学习算法选择:MADDPG、MATD3 与 PPO 适配车联网的最小实现
算法选型是多智能体深度强化学习里绕不开的一步。很多人会直接拿 DDPG 改成多智能体版本,结果发现 Q 值过估计严重,训练曲线经常急上急下。下面先给出一组深度强化学习算法对比,再给一个可以直接落到 PyTorch 里的 MATD3 实现骨架。
3.1 深度强化学习算法列表对比,以及各自适配 V2X 的方式
| 算法类型 | 动作类型 | CTDE | 主要局限 | 适配 V2X 资源分配的建议 |
|---|---|---|---|---|
| DQN / Dueling DQN | 离散 | 不强制 | 无法输出连续功率 | 小规模场景,功率离散成 3~5 档 |
| DDPG | 连续 | 可用 MADDPG 扩展 | Q 过估计严重,采样效率一般 | 不建议直接使用原版 |
| TD3 / MATD3 | 连续 | 推荐 | 需要维护双 Q 和目标策略平滑 | 比 DDPG 稳定,适合功率分配 |
| PPO / IPPO / MAPPO | 离散或连续 | 可选 | 小批量数据下方差较大 | 多环境并行时效果更好 |
| QMIX / VDN | 离散 | 是 | 更适合纯协作场景 | 频谱子带分配可当作协作调度问题 |
在 V2X 车联网场景里,动作通常是“挑选子带 + 设置发射功率”。如果只关注离散子带选择,QMIX 这类价值分解算法也经常被选作毕设方案;如果希望同时优化连续功率,MATD3 比 MADDPG 更容易收敛。对于 5 年经验的工程师来说,这里最大的信息量在于:多智能体深度强化学习不是只能套 MADDPG,TD3 的双 Q 延迟更新机制在多智能体场景下收益会更明显。
3.2 用 PyTorch 实现 MATD3:本地 Actor 与全局 Critic
实现上,Actor 网络输入是单个智能体的观测,输出连续功率值。Critic 网络输入是所有智能体的观测和动作拼接结果,输出联合 Q 值。
import torch import torch.nn as nn import torch.nn.functional as F class Actor(nn.Module): def __init__(self, obs_dim: int, power_bound_max_dbms: float): super().__init__() self.net = nn.Sequential( nn.Linear(obs_dim, 256), nn.ReLU(), nn.Linear(256, 256), nn.ReLU(), nn.Linear(256, 1), nn.Sigmoid() ) self.power_bound = power_bound_max_dbms def forward(self, obs): # 网络输出 0~1,映射到 [-power_bound, power_bound] dBm p = self.net(obs) return p * 2 * self.power_bound - self.power_bound class CentralCritic(nn.Module): def __init__(self, total_obs_dim: int, total_act_dim: int): super().__init__() self.net = nn.Sequential( nn.Linear(total_obs_dim + total_act_dim, 256), nn.ReLU(), nn.Linear(256, 256), nn.ReLU(), nn.Linear(256, 256), nn.ReLU(), nn.Linear(256, 1) ) def forward(self, obs_all, act_all): # obs_all: [batch, total_obs_dim] # act_all: [batch, total_act_dim] return self.net(torch.cat([obs_all, act_all], dim=-1))这里Actor使用 Sigmoid 限制输出范围,比直接线性输出更稳定。在真实项目中,不仅要知道功率,还需要知道选择了哪个子带。一个简单方案是在 Actor 输出功率的同时,用另一个离散分支输出子带概率,再用 Gumbel-Softmax 近似梯度。毕设场景下,也可以把功率离散成若干档位,把子带索引和功率档位合并成一个大离散动作空间。
训练更新的核心逻辑可以简化为:
with torch.no_grad(): next_actions = get_target_actions(next_obs) # 所有 Actor 目标网络输出 noise = torch.clip(torch.randn_like(next_actions) * 0.2, -0.5, 0.5) next_actions = torch.clip(next_actions + noise, -1.0, 1.0) target_q = target_critic(next_obs_combined, next_actions) target_q_value = rewards + gamma * (1 - dones) * target_q critic_loss = F.mse_loss(critic(obs_combined, actions), target_q_value) action_pred = get_acts_from_main_actors(obs) actor_loss = -critic(obs_combined, action_pred).mean()Critic 的目标值里加入了随机噪声,这是 TD3 的 target policy smoothing,可以让 Q 函数在相似动作附近保持平滑。多智能体场景下,干扰功率会随动作组合突变,这种平滑机制能明显减少训练碰撞。
3.3 多智能体配置最容易踩的 3 个点
多智能体配置主要指网络连接结构、经验回放格式和更新频率。最容易出问题的三个点依次是:
第一,经验回放必须锁定同一时间步的所有智能体样本。如果从 Replay Buffer 里为每个智能体独立随机采样,Actor 和 Critic 训练时看到的状态就不是同一个时间点,联合 Q 值会被完全打乱。正确做法是每次采样一整条 transition,其中包含 N 个观测、N 个动作和一个全局奖励。
第二,Critic 的输入不是简单的“所有观测拼接”,要做特征归一化。车速、位置、信道增益三者量级完全不同,直接 Concate 会让 Critic 的梯度被量级较大的状态主导。常见做法是位置归一化到道路长度,速度归一化到最高限速。
第三,多智能体更新频率要比单智能体更保守。由于每个智能体都在变化,Actor 的学习率如果取 3e-4,在 sink 场景里很容易出现某个智能体策略突变,干扰所有队友。我一般会先把整体更新延迟调大,比如 Actor 每 3 个 Critic 更新才更新一次,稳定后再逐步放开。
4. 搭建可复现的车联网仿真环境:信道模型、移动性生成和训练主循环
算法代码写得再漂亮,也得落到仿真环境里跑。车联网资源分配的实验环境,需要同时包含三部分:仿真器选型、通信资源网格、训练主循环。
4.1 仿真器选型:为什么我建议从自建 Python 环境开始
常见的车联网仿真工具包括 SUMO(车辆移动性)、NS-3(网络协议)和 OMNeT++/Veins(V2X 通信)。这些工具很完整,但配置成本高,很多环境依赖会让人停在编译阶段。如果你做的是毕业设计或短期验证,我建议先自建一个纯 Python 轻量环境,把物理层信道模型和资源调度做进去,跑通多智能体深度强化学习训练;如果导师要求系统级仿真数据,再用 NS-3 或 OMNeT++ 做结果验证。
纯 Python 环境的优势不仅仅是“简单”。它还能自由控制训练速度:自动驾驶 Scene 可以按毫秒级推进,但神经网络训练可能需要几千个 Episode。如果每个 Step 都去调度外部仿真器,实验周期会被拖到无法接受。
4.2 信道模型与资源网格:用一张二维表决定频谱复用
信道模型负责计算每个智能体的接收信噪比。V2I 链路通常使用城市道路传播模型,路径损耗与距离的关系可以写作PL = 128.1 + 37.6 * log10(R),其中 R 是车到基站的距离,单位是 km。V2V 链路距离更短,路径损耗指数会低一些。
当多个智能体复用同一个子带时,SINR 的计算需要把同频干扰叠加进来:
import numpy as np def compute_sinr(rx_power_dbm, interference_dbm_list, noise_dbm=-104): rx_w = 10 ** ((rx_power_dbm - 30) / 10) interference_w = sum(10 ** ((i - 30) / 10) for i in interference_dbm_list) noise_w = 10 ** ((noise_dbm - 30) / 10) return 10 * np.log10(rx_w / (noise_w + interference_w))rx_power_dbm是接收到目标信号的功率;interference_dbm_list是所有同频发射机在该接收端造成的干扰功率;noise_dbm是接收机底噪。V2X 资源分配优化要做的事情,本质上就是让这个干扰列表里的信号源尽可能“分散”,避免多个发射机同时抢占同一个时频单元。
资源网格可以用一张二维表表示:横轴是子带索引,纵轴是时隙索引,每个单元格记录使用它的 V2V 链路编号。
| 子带/时隙 | 时隙 1 | 时隙 2 | 时隙 3 |
|---|---|---|---|
| 子带 1 | V2V 链路 3 | 空闲 | V2V 链路 7 |
| 子带 2 | V2V 链路 5 | V2V 链路 1 | 空闲 |
| 子带 3 | 空闲 | V2V 链路 2 | V2V 链路 4 |
这个表格是 Agent 动作的一种可视化形式。多智能体深度强化学习要做的是把链路合理地填充进表格,让同一时间、同一子带上的冲突尽量少。
4.3 训练主循环怎么和仿真时间对齐
在训练代码里,一个仿真 Step 需要完成车辆移动、信道抽样、资源分配、SINR 计算、奖励计算五个阶段。下面给一个最小的主循环骨架:
def train_episode(env, agents, memory, gamma=0.99, max_steps=200): obs = env.reset() for step in range(max_steps): actions = [] for i, agent in enumerate(agents): action = agent.select_action(obs[i]) actions.append(action) next_obs, reward, done, info = env.step(actions) memory.push(obs, actions, reward, next_obs, done) obs = next_obs if done: break # episode 结束后更新网络 for _ in range(100): update(td3_algo, memory, batch_size=128)env.step(actions)路径一般会先推进车辆位置,再根据新位置更新信道增益,最后计算资源网格上的 SINR 和时延违反情况。多智能体深度强化学习对异步不敏感,但要注意的一点是:环境返回的reward是全局奖励,所有智能体共享同一个值。如果场景中每个 V2V 链路有自己的时延目标,可以在奖励里加入个体项,形成带个性化奖励的多智能体系统。
5. 判断多智能体资源分配真的有效:指标验证、排错与热图
模型训练结束后,真正决定这个毕设质量的是验证方式。奖励曲线只能说明算法在某个随机种子下学会了最大化奖励,不能直接证明它完成了通信资源分配优化。下面三个指标足够把实验结果讲清楚。
5.1 三组指标代替奖励曲线
| 指标 | 计算方式 | 合格线参考 |
|---|---|---|
| V2V 传输成功率 | 时延预算内完成传输的包占比 | 90% 以上 |
| V2I 平均频谱效率 | 所有 V2I 链路log2(1+SINR)的均值 | 明显高于固定资源分配 |
| V2V 时延超越率 | 超过时延阈值的包数量 / 总包数量 | 低于 5% |
三种指标分别对应“链路可靠性”、“系统频谱利用率”和“端到端时延约束”。做对比实验时,至少要包含一个随机资源分配和一个固定复用方案的基线,否则无法说明多智能体深度强化学习带来的增益。
5.2 四个检查点判断模型是否在认真分配资源
第一,检查功率动作分布。如果所有智能体最后都选择最大功率,说明奖励函数没有足够地惩罚干扰,模型退化成了“大功率无脑发射”。第二,检查子带选择多样性。如果所有车辆都挤在同一两个子带上,说明智能体没有从碰撞惩罚中学到错峰策略。第三,检查 Critic 对同一观测、不同动作的 Q 值排序是否合理。可以构造一个明显差的动作组合,比如全部同频满功率,如果 Critic 给出的 Q 值比训练中的正常动作还高,说明价值函数还没学好。第四,做车辆数迁移测试。用 20 辆车训练的模型,放到 40 辆车的场景里观察指标,不用重训也能保持相对优势的模型,解释力更强。
5.3 用资源占用热图完成审计
训练完成后,我建议把资源网格渲染成热图,横轴是子带索引,纵轴是时隙,颜色代表 V2V 链路编号。用 Matplotlib 一行代码就能完成:
import matplotlib.pyplot as plt plt.imshow(resource_grid, aspect='auto', cmap='tab20') plt.colorbar() plt.xlabel('subband index') plt.ylabel('time slot') plt.show()如果热图上相同颜色集中成块没有明显交错,说明每个 V2V 链路倾向于长时间占用固定子带,这种策略更接近传统半静态调度;如果颜色在各个时频块间合理分散,说明智能体学会了对时频资源做动态复用。这张图配合 V2V 传输成功率和 V2I 平均频谱效率,就是车联网通信资源分配优化最直接的证据。出图之后,再把全部指标导成一张 CSV 审计表,作为源代码归档里“实验结果”部分的演示材料。
本文还有配套的精品资源,点击获取