无人机辅助边缘计算卸载:DDPG源码实战与消融实验

📅 发布时间:2026/9/23 22:56:13
无人机辅助边缘计算卸载:DDPG源码实战与消融实验
简介本资源面向通信、边缘计算与强化学习方向的学生及研究人员提供一套基于无人机辅助移动边缘计算UAV-MEC的计算卸载优化完整Python实现核心采用深度确定性策略梯度DDPG算法并附带DQN、Actor-Critic等对比方案适合毕业设计、课程大作业及科研复现使用。压缩包共17个文件以16个py源码与1个md说明文档为主整体约44KB涵盖UAV_env.py环境建模、ddpg_algo.py算法实现、state_normalization.py状态归一化及Edge_only、Local_only等基线脚本代码注释详尽新手也能读懂。目前已有130人学习下载。读者可据此掌握无人机轨迹与卸载决策的联合优化思路理解DDPG在连续动作空间中的训练流程并借助对比实验快速搭建自己的仿真环境具备较高的实际应用与二次开发价值。1. 无人机辅助边缘计算卸载这套 DDPG 源码到底能跑出什么做无人机辅助移动边缘计算方向的同学大概率都卡在同一个地方论文里的系统模型看懂了公式推导也跟下来了但真要自己写一套能收敛、能出对比曲线的计算卸载仿真就无从下手。这套源码解决的正是这个断层——它把「无人机 移动边缘计算 计算卸载」这个组合场景用深度确定性策略梯度DDPG完整实现了一遍并且把 Edge_only、Local_only、DQN、DDPG 四组基线都摆在一起方便你直接跑出对比结果。它适合正在做毕设、课程设计或期末大作业的本科生和低年级研究生也适合刚接触强化学习、想找一个连续动作空间实战案例的工程师。整套代码是纯 Python环境用 Gym 风格封装算法文件独立注释密度对新手友好部署门槛不高。2. 系统模型与算法选型为什么是 DDPG 而不是 DQN2.1 无人机辅助 MEC 的计算卸载问题长什么样先把场景说清楚。地面有一批终端设备比如传感器、摄像头、手持终端它们自己算力有限要么本地算要么把任务通过无线链路传给空中的无人机由无人机上的边缘服务器代算。无人机在覆盖区域内可以移动位置会影响信道质量和传输能耗。每个时隙系统要决定这个任务卸载多少比例、无人机往哪飞、发射功率给多大。这就是一个典型的连续控制问题。卸载比例是 [0,1] 的连续值飞行方向和速度是连续向量发射功率也是连续值。动作空间连续状态空间包含任务队列长度、信道增益、无人机位置、剩余电量等。目标是最小化长期加权开销——通常是时延和能耗的加权和。常见做法是把每个时隙建模成马尔可夫决策过程状态 s_t 是当前系统快照动作 a_t 是卸载决策加飞行控制奖励 r_t 是负的加权开销。智能体的任务就是学一个策略 π(a|s)让累计折扣奖励最大。2.2 连续动作空间下 DDPG 与 DQN 的分工DQN 只能处理离散动作。如果你把卸载比例离散成 10 档、飞行方向离散成 8 个方向动作组合会爆炸而且精度受离散粒度限制。DDPG 走的是 Actor-Critic 路线Actor 网络直接输出连续动作Critic 网络评估这个动作的价值两者交替更新。它用确定性策略加行为噪声来兼顾探索和利用在连续控制里比 DQN 自然得多。这套源码里 DQN 和 DDPG 是并列的不是让你二选一而是让你做对比实验。DQN 那组把动作离散化DDPG 那组保持连续跑出来的曲线差异正好能写进论文的实验分析章节。Edge_only 和 Local_only 是两个极端基线全部卸载到边缘、全部本地计算用来证明你的智能体确实学到了东西而不是随便乱动。2.3 源码目录结构与各文件职责拿到压缩包后先别急着跑。花五分钟把目录结构理清楚后面调参会省很多事。目录/文件职责Edge_only/Edge_only.py全卸载基线任务全部传给无人机边缘服务器Local_only/Local_only.py全本地基线任务全部在终端本地计算DQN/离散动作版本含dqn_algo.py、UAV_env.py、state_normalization.pyDDPG/主算法目录含ddpg_algo.py、UAV_env.py、state_normalization.pyDDPG_without_state_normalization/去掉状态归一化的消融版本DDPG_without_behavior_noise/去掉行为噪声的消融版本Actor Critc/Actor-Critic 基础版本含ac_algo.pyREADME.md运行说明和依赖列表这个结构的好处是消融实验已经帮你拆好了。写论文时「状态归一化对收敛的影响」「行为噪声对探索的作用」这两节直接跑对应目录就能出数据不用自己改代码。2.4 环境封装状态、动作、奖励三件套UAV_env.py是整个项目的地基。它定义了reset()和step(action)两个核心接口和 Gym 的约定一致。状态向量一般包含各终端的任务队列长度、当前信道增益、无人机三维位置、无人机剩余能量、上一时隙的卸载比例。动作向量包含卸载比例、飞行方向角、飞行速度、发射功率。奖励函数是调参的重灾区。常见写法是# UAV_env.py 中奖励计算的典型结构 def _compute_reward(self, offload_ratio, fly_action, power): # 本地计算时延与能耗 local_delay (1 - offload_ratio) * self.task_bits / self.local_cpu local_energy (1 - offload_ratio) * self.task_bits * self.local_energy_per_bit # 卸载时延 传输时延 边缘计算时延 trans_delay offload_ratio * self.task_bits / self.trans_rate edge_delay offload_ratio * self.task_bits / self.edge_cpu offload_delay trans_delay edge_delay offload_energy power * trans_delay offload_ratio * self.task_bits * self.edge_energy_per_bit # 飞行能耗与推进功耗 fly_energy self.fly_power * self.delta_t total_delay max(local_delay, offload_delay) # 并行执行取较大者 total_energy local_energy offload_energy fly_energy # 加权开销w1 w2 是可调权重 cost self.w1 * total_delay self.w2 * total_energy return -cost # 奖励取负开销这段逻辑里有两个参数最影响结果w1和w2。它们决定时延和能耗的相对重要性。如果你论文里侧重时延敏感场景就把w1调大侧重能耗受限场景就把w2调大。建议先跑一组w10.5, w20.5的对称配置作为基准再跑偏斜配置做敏感性分析。注意max(local_delay, offload_delay)这个写法假设本地和卸载两条路径并行执行。如果你的模型是串行或者部分并行这里要改否则奖励信号会偏乐观智能体会学到不真实的策略。3. 跑通 DDPG 训练从环境配置到收敛曲线3.1 依赖安装与 Python 环境准备这套代码依赖不算重主要是 numpy、torch、matplotlib、gym。建议用 conda 建一个独立环境避免和系统 Python 打架。# 创建并激活虚拟环境 conda create -n uav_mec python3.9 -y conda activate uav_mec # 安装核心依赖 pip install numpy matplotlib gym pip install torch --index-url https://download.pytorch.org/whl/cpu如果你有 CUDA 显卡把最后一行换成对应 CUDA 版本的 torch 安装命令。CPU 版本跑这个规模的网络也够用只是训练轮数多了会慢一些。Python 版本建议 3.8 到 3.10太新的版本有些老版本 gym 会报兼容性错误。装完之后进DDPG/目录先跑一个最小验证cd DDPG python -c import torch; import gym; import numpy; print(ok)没有报错就说明环境通了。如果提示No module named gym检查是不是 conda 环境没激活或者 pip 装到了系统 Python 里。3.2 DDPG 主循环Actor、Critic 与目标网络ddpg_algo.py是核心。DDPG 的结构是四个网络Actor 在线网络、Actor 目标网络、Critic 在线网络、Critic 目标网络。在线网络负责训练目标网络负责稳定 TD 目标。# ddpg_algo.py 中更新逻辑的简化版 class DDPG: def select_action(self, state): state torch.FloatTensor(state).unsqueeze(0) action self.actor(state).detach().numpy()[0] # 训练时加行为噪声测试时不加 if self.training: noise np.random.normal(0, self.noise_std, sizeaction.shape) action np.clip(action noise, self.action_low, self.action_high) return action def update(self, batch): states, actions, rewards, next_states, dones batch # Critic 更新最小化 TD 误差 with torch.no_grad(): next_actions self.actor_target(next_states) target_q rewards self.gamma * (1 - dones) * self.critic_target(next_states, next_actions) current_q self.critic(states, actions) critic_loss nn.MSELoss()(current_q, target_q) self.critic_optimizer.zero_grad() critic_loss.backward() self.critic_optimizer.step() # Actor 更新最大化 Critic 对当前动作的评分 actor_loss -self.critic(states, self.actor(states)).mean() self.actor_optimizer.zero_grad() actor_loss.backward() self.actor_optimizer.step() # 软更新目标网络 self._soft_update(self.actor, self.actor_target, self.tau) self._soft_update(self.critic, self.critic_target, self.tau)几个关键参数gamma是折扣因子一般 0.95 到 0.99tau是软更新系数常见 0.001 到 0.005noise_std是行为噪声标准差训练初期可以大一点帮助探索后期衰减。action_low和action_high要和环境的动作边界严格对齐否则 clip 之后动作分布会偏。3.3 状态归一化消融实验里最容易被忽略的一环state_normalization.py做的是把状态向量各维度缩放到相近量级。状态里既有队列长度可能几百上千又有信道增益可能 1e-6 量级不归一化的话网络很难学。# state_normalization.py 的典型实现 class StateNormalizer: def __init__(self, state_dim): self.mean np.zeros(state_dim) self.std np.ones(state_dim) self.count 1e-4 def update(self, state): # 在线更新均值和标准差Welford 风格的简化版 self.count 1 delta state - self.mean self.mean delta / self.count self.std np.sqrt((self.std ** 2 * (self.count - 1) delta * (state - self.mean)) / self.count) def normalize(self, state): return (state - self.mean) / (self.std 1e-8)DDPG_without_state_normalization/这个目录就是把这层去掉的版本。跑对比时你会发现去掉归一化后前期收敛明显更慢甚至在某些随机种子下不收敛。这个消融结果写进论文很有说服力。3.4 训练脚本运行与结果观察主训练入口一般在ddpg_algo.py底部或者单独一个main.py。运行方式cd DDPG python ddpg_algo.py训练过程中会打印每轮的累计奖励和平均开销。判断是否正常收敛看两个信号一是累计奖励曲线是否从低位震荡逐渐抬升并趋于平稳二是平均开销是否降到 Edge_only 和 Local_only 之间或更低。如果奖励一直不涨先检查动作 clip 范围是否合理再检查奖励尺度是不是太大导致梯度爆炸。常见做法是每跑 100 轮存一次模型权重方便后面加载做测试。测试时把training标志设为 False关掉行为噪声用确定性策略跑固定轮数统计平均时延和能耗。4. 避坑与排查跑这套代码最容易翻车的五个地方4.1 现象训练奖励剧烈震荡完全不收敛原因通常是行为噪声标准差设得太大或者 Critic 学习率远高于 Actor。DDPG 对超参敏感是出了名的玄学两个网络学习率不匹配时Critic 评估不准Actor 就会往错误方向更新。解决先把噪声标准差降到 0.1 左右Actor 学习率设 1e-4Critic 设 1e-3跑一轮看曲线。如果还是震荡把tau从 0.005 降到 0.001让目标网络更新更慢。还不行就减小网络隐藏层宽度从 256 降到 128降低过拟合风险。4.2 现象状态归一化后某些维度全是 NaN原因是标准差计算时除零或者某个维度的状态值恒定不变导致 std 为 0。state_normalization.py里如果没加1e-8这种小量保护就会出问题。解决检查normalize函数分母有没有加 epsilon。另外确认环境里每个状态维度确实在变化如果某个维度从头到尾是常数要么去掉它要么在归一化时跳过。4.3 现象DQN 版本跑得通DDPG 版本报动作维度不匹配原因是UAV_env.py里action_space定义和ddpg_algo.py里网络输出维度不一致。DQN 用的是离散动作数DDPG 用的是连续动作向量长度两者不能混用同一个环境配置。解决确认 DDPG 目录下的UAV_env.py的action_space是Box类型而不是Discrete。如果是从 DQN 目录复制过来的环境文件记得改动作空间定义和step函数里的动作解析逻辑。4.4 现象训练到一半 loss 突然变成 NaN原因是奖励尺度太大或者梯度爆炸。这套代码里能耗和时延的数值量级可能到几百上千直接喂给网络容易出问题。解决在奖励函数里做缩放比如把 cost 除以一个常数因子让奖励落在 [-10, 10] 区间。或者在优化器里加梯度裁剪torch.nn.utils.clip_grad_norm_(self.critic.parameters(), max_norm1.0) torch.nn.utils.clip_grad_norm_(self.actor.parameters(), max_norm1.0)这两行加在backward()之后、step()之前能挡掉大部分梯度爆炸导致的 NaN。4.5 现象四个基线跑出来的曲线几乎重合原因是环境随机种子没固定或者奖励函数里某个权重设成了 0导致不同策略的开销差异被淹没。解决在训练脚本开头固定 numpy 和 torch 的随机种子。然后检查奖励函数确认w1和w2都不为 0。如果还是重合把任务大小、信道条件等环境参数调得更极端一些让卸载决策的差异更明显。有时候场景太简单所有策略表现都差不多这时候需要加大任务负载或者拉长无人机到终端的距离。5. 进阶用法把消融实验做成论文里的加分项跑通主实验只是第一步。这套源码真正的价值在于它已经把消融实验的目录拆好了你可以直接拿来产出论文里「参数敏感性分析」和「模块有效性验证」两节内容。先说状态归一化的消融。分别跑DDPG/和DDPG_without_state_normalization/固定其他所有超参和随机种子各跑 500 轮记录每 50 轮的平均开销。把两条曲线画在同一张图上你会看到带归一化的版本在前 100 轮就明显下降不带归一化的版本可能到 300 轮还在高位震荡。这个对比直接说明归一化层对收敛速度的贡献。再说行为噪声的消融。DDPG_without_behavior_noise/去掉了探索噪声理论上会陷入局部最优。跑出来的曲线往往在前期上升很快但后期明显低于带噪声的版本因为它没有足够探索去发现更优的卸载策略。这个结果可以支撑你论文里「探索机制必要性」的论述。还有一个进阶玩法是改奖励权重做 Pareto 前沿。把w1从 0.1 到 0.9 扫一遍每个权重跑一次训练记录最终的平均时延和平均能耗画成散点图。这就是时延-能耗的 Pareto 前沿比单点对比更有说服力。我一般会跑 9 个权重点每个点跑 3 个随机种子取平均虽然费时间但曲线出来很稳。验证方法上除了看训练奖励一定要做测试阶段的确定性策略评估。加载训练好的模型关掉噪声跑 100 个 episode统计平均开销和标准差。如果测试开销远高于训练末期奖励对应的开销说明过拟合了需要加正则或者减少网络容量。最后说一个我踩过的坑不要用训练过程中的即时奖励直接当最终性能指标。DDPG 的训练奖励包含噪声带来的随机性波动很大。一定要单独跑测试脚本用确定性策略评估。从那以后我每次做强化学习实验都强制把训练和测试完全分开训练脚本只负责存模型测试脚本只负责加载模型跑评估两边不混。希望帮到你。本文还有配套的精品资源点击获取