ARTICLE DETAIL

资讯详情

深耕编程入门与网站建设的一线实战洞察。

移动边缘计算卸载与资源分配:基于深度强化学习的毕设源码复现与避坑指南

移动边缘计算卸载与资源分配:基于深度强化学习的毕设源码复现与避坑指南 简介这份资源是面向计算机、人工智能、通信工程等专业学生与教师的毕业设计/课程设计参考包聚焦移动边缘计算MEC场景下的计算卸载与资源分配问题采用深度强化学习DQN方法实现。包内共19个文件以Python源码、Shell运行脚本、txt日志、png结果图和md说明文档为主压缩包约112KB结构清晰便于快速复现实验。核心代码包含MEC环境建模与DQN算法实现配套多个运行脚本可分别对比不同策略日志与图表文件则记录了训练过程和性能曲线方便读者理解算法收敛与卸载决策效果。目前已有291人学习下载适合作为毕设、课设或大作业的完整方案也可在此基础上修改扩展用于项目初期立项演示或强化学习入门实践。1. 从一份毕设源码说起MEC 计算卸载到底在解决什么问题移动边缘计算MEC把算力从云端下沉到基站侧目的是让手机、车机、AR 眼镜这类终端不必把每个任务都往远端送。但边缘节点的算力、带宽、能量都是有限的一个基站下挂几十个终端谁先算、在哪算、分多少资源直接决定系统吞吐和时延。计算卸载要回答的就是「任务放本地还是放边缘」资源分配要回答「放边缘的那部分带宽和算力怎么切」。这两件事耦合在一起用手写规则很难调好于是深度强化学习DRL成了主流解法把卸载决策和资源分配建模成马尔可夫决策过程让智能体在和环境交互中自己学策略。这份毕业设计源码的价值不在于算法多新而在于它把「环境建模 → 状态/动作/奖励设计 → DRL 训练 → 对比基线」这条链路完整跑通了。适合两类人一是做毕设或课程设计、需要一份能跑起来再改的学生二是刚转强化学习、想找一个有明确物理背景时延、能耗、信道的落地场景练手的工程师。下面我按自己复现这类项目的顺序把关键环节拆开讲。2. 环境建模与 MDP 三要素状态、动作、奖励怎么定2.1 为什么 MEC 卸载天然适合写成 MDPMEC 场景里每个时隙终端会产生若干任务任务有数据量、计算量、最大容忍时延。终端可以选择本地执行也可以卸载到边缘服务器。边缘服务器的算力被多个终端共享所以某个终端的选择会影响其他终端的体验——这就是典型的序贯决策加资源竞争。用 MDP 描述时状态要能反映当前信道质量、任务队列长度、边缘剩余算力动作要同时包含「卸载与否」和「分配多少带宽/算力」奖励要把时延和能耗加权成一个标量。只要这三样定清楚剩下的就是选算法。常见做法是把时延和能耗做归一化后加权reward -(w_t * delay_norm w_e * energy_norm)。权重 w_t、w_e 决定策略偏向省电还是低时延是调参时第一个要动的地方。2.2 状态、动作、奖励的具体定义以典型的「多终端单边缘服务器」场景为例状态向量一般包含状态分量含义维度信道增益终端到基站的上行信道质量N任务数据量当前时隙各终端任务大小N任务计算量需要的 CPU 周期数N本地队列终端本地待处理任务积压N边缘剩余算力边缘服务器可用计算资源1动作空间有两种设计离散动作每个终端选本地/卸载共 2^N 种组合和连续动作卸载比例 带宽分配比例。离散动作适合 DQN 系列连续动作适合 DDPG、TD3、PPO。源码里如果用的是 DQN动作维度就是 2^NN 大了会爆炸所以常见做法是每个终端独立决策或者用连续松弛再取阈值。奖励函数直接决定学出来的策略是否可用。我一般会加一个惩罚项任务超时未完成给大负奖励这样智能体会主动避开会导致超时的动作。2.3 用 Python 搭一个最小环境下面这段代码定义一个简化版 MEC 环境只保留最核心的时延和能耗计算方便先跑通再逐步加细节。import numpy as np class MECEnv: def __init__(self, n_terminals5, edge_cpu10e9, bw20e6): self.N n_terminals self.edge_cpu edge_cpu # 边缘服务器总算力 Hz self.bw bw # 总带宽 Hz self.local_cpu 1e9 # 单终端本地算力 self.task_size None self.task_cpu None def reset(self): # 每个终端随机生成任务数据量 0.5~2 Mbit计算量 0.5~2 Gcycles self.task_size np.random.uniform(0.5e6, 2e6, self.N) self.task_cpu np.random.uniform(0.5e9, 2e9, self.N) self.channel np.random.uniform(0.5, 1.0, self.N) return self._get_state() def _get_state(self): return np.concatenate([self.task_size, self.task_cpu, self.channel]) def step(self, action): # action: 每个终端的卸载比例 0~1 offload np.clip(action, 0, 1) local_part 1 - offload # 本地执行时延 local_delay local_part * self.task_cpu / self.local_cpu # 卸载传输时延数据量 / 速率速率由香农公式简化 rate self.bw / self.N * np.log2(1 self.channel * 10) trans_delay offload * self.task_size / rate # 边缘执行时延共享算力按卸载量比例分 edge_share offload / (np.sum(offload) 1e-9) edge_delay offload * self.task_cpu / (self.edge_cpu * edge_share 1e-9) delay local_delay trans_delay edge_delay energy local_part * self.task_cpu * 1e-9 offload * self.task_size * 1e-3 reward -np.mean(0.5 * delay / 1.0 0.5 * energy / 1.0) done True # 单步任务简化处理 return self._get_state(), reward, done, {}逻辑说明reset随机生成任务和信道step接收卸载比例分别算本地、传输、边缘三段时延再算能耗最后加权成负奖励。参数说明edge_cpu和bw是边缘侧资源上限改小会让竞争更激烈适合测试算法在资源紧张时的表现local_cpu决定本地执行快慢调大相当于终端更强最优策略会偏向本地。这段代码故意写成单步任务是为了先验证奖励函数是否合理跑通后再改成多时隙队列模型。3. 选 DQN 还是 PPO算法选型与训练脚本落地3.1 离散动作和连续动作的分水岭动作空间是选算法的第一判断。如果每个终端只做「本地/卸载」二选一动作是离散的DQN、Double DQN、Dueling DQN 都能用实现简单、调参少。如果要同时输出卸载比例和带宽分配比例动作是连续的DQN 就不合适了得换 DDPG、TD3 或 PPO。PPO 在连续控制里稳定性好、对超参不敏感是我在 MEC 场景里最常用的默认选择。源码如果用的是 DQN通常会把动作空间设计成每个终端独立一个 Q 网络或者用因子化动作。前者实现简单但忽略了终端间的耦合后者更合理但代码复杂。复现时先跑通独立决策版本再考虑加耦合。3.2 训练脚本的关键参数下面是一个 PPO 训练循环的骨架重点看参数怎么设。import torch import torch.nn as nn import numpy as np from mec_env import MECEnv class ActorCritic(nn.Module): def __init__(self, state_dim, action_dim): super().__init__() self.actor nn.Sequential( nn.Linear(state_dim, 128), nn.ReLU(), nn.Linear(128, 128), nn.ReLU(), nn.Linear(128, action_dim), nn.Sigmoid() # 输出 0~1 卸载比例 ) self.critic nn.Sequential( nn.Linear(state_dim, 128), nn.ReLU(), nn.Linear(128, 128), nn.ReLU(), nn.Linear(128, 1) ) def forward(self, x): return self.actor(x), self.critic(x) env MECEnv(n_terminals5) state_dim env.N * 3 action_dim env.N model ActorCritic(state_dim, action_dim) optimizer torch.optim.Adam(model.parameters(), lr3e-4) gamma 0.99 clip_eps 0.2 epochs 10 for episode in range(2000): state env.reset() log_probs, rewards, states, actions [], [], [], [] for t in range(20): # 每个 episode 20 步 state_t torch.FloatTensor(state) action_mean, value model(state_t) dist torch.distributions.Normal(action_mean, 0.1) action dist.sample() action_clamped torch.clamp(action, 0, 1) next_state, reward, done, _ env.step(action_clamped.detach().numpy()) log_probs.append(dist.log_prob(action).sum()) rewards.append(reward) states.append(state_t) actions.append(action) state next_state if done: break # 计算回报和优势做 PPO 更新 returns [] G 0 for r in reversed(rewards): G r gamma * G returns.insert(0, G) returns torch.FloatTensor(returns) states torch.stack(states) old_log_probs torch.stack(log_probs).detach() for _ in range(epochs): action_mean, values model(states) dist torch.distributions.Normal(action_mean, 0.1) new_log_probs dist.log_prob(torch.stack(actions)).sum(dim-1) ratio torch.exp(new_log_probs - old_log_probs) adv returns - values.squeeze().detach() surr1 ratio * adv surr2 torch.clamp(ratio, 1 - clip_eps, 1 clip_eps) * adv loss -torch.min(surr1, surr2).mean() 0.5 * (returns - values.squeeze()).pow(2).mean() optimizer.zero_grad() loss.backward() optimizer.step()逻辑说明Actor 输出每个终端的卸载比例均值用正态分布采样保证探索再 clamp 到 0~1。Critic 估计状态价值用来算优势。PPO 的核心是 clip 项限制新旧策略比值不要偏离太远。参数说明lr3e-4是 PPO 常用学习率太大容易崩太小收敛慢clip_eps0.2是默认裁剪范围任务奖励波动大时可以调到 0.1 更稳gamma0.99适合单 episode 20 步的场景如果改成多时隙长任务可以保持 0.99 或降到 0.95。epochs10表示每轮采样数据重复训练 10 次数据少时可以调大但别超过 20否则过拟合当前批次。3.3 训练不收敛时先查这三处第一奖励尺度。如果时延是毫秒级、能耗是焦耳级两者数值差几个数量级加权后一方会被淹没。做法是先各自归一化到 0~1 再加权。第二动作范围。连续动作如果没做 clamp采样出负值或大于 1 的值物理上无意义会导致环境返回异常奖励。第三状态归一化。信道增益、任务量这些量纲不同直接喂网络会让训练极不稳定建议在环境里就做 min-max 归一化。4. 避坑与排查复现这类源码最容易翻车的五个地方4.1 现象训练奖励一直震荡不上升原因学习率过大或者优势估计没有做标准化。PPO 里如果 adv 没减均值除标准差梯度方向会被大数值主导。解决把学习率降到 1e-4并在算优势后加adv (adv - adv.mean()) / (adv.std() 1e-8)。4.2 现象智能体学会「全部本地执行」或「全部卸载」原因奖励函数里某一项权重过大或者边缘算力共享模型写错导致极端策略反而奖励高。比如边缘时延计算时没有除以共享终端的数量卸载越多边缘越快智能体自然全卸载。解决检查边缘算力分配公式确保卸载终端越多、单个终端分到的算力越少同时把时延和能耗权重都设成 0.5 先跑基线。4.3 现象换了终端数量 N 之后代码报维度错误原因状态维度和动作维度写死在网络里N 变了但网络没重建。解决把state_dim和action_dim都写成env.N * k的形式网络初始化时从环境读取不要硬编码数字。4.4 现象GPU 上训练比 CPU 还慢原因MEC 环境是纯 NumPy 计算每步都在 CPU 上网络又小数据传输开销超过计算收益。解决这种小规模场景直接用 CPU 训练或者把环境也向量化放到 GPU 上但后者改动大毕设阶段没必要。4.5 现象对比基线结果好得不真实原因基线算法比如全本地、全卸载、随机卸载的实现里时延或能耗算漏了一项。常见的是全本地时忘了算本地能耗或者全卸载时没算传输时延。解决把三种基线的时延和能耗分别打印出来和理论值手算对比确认每一项都算进去了再画图。5. 让毕设结果更可信基线对比与消融实验的具体做法5.1 基线怎么选才有说服力只跟「全本地」「全卸载」比是不够的审阅老师通常会问「为什么不跟其他 DRL 算法比」。建议至少加两个基线一个是传统优化方法比如基于贪心的卸载信道好的优先卸载另一个是同类 DRL 但不同算法比如 DQN 对 PPO。这样能说明你的算法在同类里也有优势而不只是欺负规则方法。对比指标一般看三个平均时延、平均能耗、任务完成率。任务完成率是容易被忽略但很关键的指标尤其在有最大容忍时延的场景里一个策略可能时延低但完不成任务那就没意义。5.2 消融实验怎么做消融的目的是证明你加的每个模块都有用。比如你的奖励函数里加了超时惩罚项那就跑一组去掉惩罚的实验看任务完成率掉多少。如果你的状态里加了边缘剩余算力那就跑一组去掉这个分量的实验看收敛速度变慢多少。每组实验固定随机种子跑 3~5 次取均值和方差不要只跑一次就下结论。# 固定随机种子保证可复现 import random, numpy as np, torch def set_seed(seed42): random.seed(seed) np.random.seed(seed) torch.manual_seed(seed) torch.cuda.manual_seed_all(seed)逻辑说明强化学习对随机性极敏感不固定种子的话两次跑出来的曲线可能差很多没法做对比。参数说明seed一般选 42 或 0跑多组时依次用 42、43、44最后报告均值±标准差。5.3 画图时横坐标太密集怎么办训练曲线动辄几千个 episode直接画横坐标会糊成一片。做法是每 50 或 100 个 episode 取一次滑动平均横坐标用 episode 编号但只显示稀疏刻度。Matplotlib 里用plt.xticks(np.arange(0, 2000, 200))控制刻度间隔再用plt.plot画平滑后的曲线。这个细节不影响算法但直接影响论文图能不能看。5.4 我自己的习惯我复现任何 DRL 项目第一步永远是把环境单独跑 100 步打印每步的 state、action、reward确认物理量算得对再开始训练。很多人一上来就调网络结果训练不收敛查了半天发现是环境里时延算错了。先信环境再信算法。希望帮到你。本文还有配套的精品资源点击获取
返回列表