news 2026/10/5 8:18:13

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

作者头像

张小明

前端开发工程师

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

简介:这份资源是面向计算机、人工智能、通信工程等专业学生与教师的毕业设计/课程设计参考包,聚焦移动边缘计算(MEC)场景下的计算卸载与资源分配问题,采用深度强化学习(DQN)方法实现。包内共19个文件,以Python源码、Shell运行脚本、txt日志、png结果图和md说明文档为主,压缩包约112KB,结构清晰,便于快速复现实验。核心代码包含MEC环境建模与DQN算法实现,配套多个运行脚本可分别对比不同策略,日志与图表文件则记录了训练过程和性能曲线,方便读者理解算法收敛与卸载决策效果。目前已有291人学习下载,适合作为毕设、课设或大作业的完整方案,也可在此基础上修改扩展,用于项目初期立项演示或强化学习入门实践。

1. 从一份毕设源码说起:MEC 计算卸载到底在解决什么问题

移动边缘计算(MEC)把算力从云端下沉到基站侧,目的是让手机、车机、AR 眼镜这类终端不必把每个任务都往远端送。但边缘节点的算力、带宽、能量都是有限的,一个基站下挂几十个终端,谁先算、在哪算、分多少资源,直接决定系统吞吐和时延。计算卸载要回答的就是「任务放本地还是放边缘」,资源分配要回答「放边缘的那部分,带宽和算力怎么切」。这两件事耦合在一起,用手写规则很难调好,于是深度强化学习(DRL)成了主流解法:把卸载决策和资源分配建模成马尔可夫决策过程,让智能体在和环境交互中自己学策略。

这份毕业设计源码的价值不在于算法多新,而在于它把「环境建模 → 状态/动作/奖励设计 → DRL 训练 → 对比基线」这条链路完整跑通了。适合两类人:一是做毕设或课程设计、需要一份能跑起来再改的学生;二是刚转强化学习、想找一个有明确物理背景(时延、能耗、信道)的落地场景练手的工程师。下面我按自己复现这类项目的顺序,把关键环节拆开讲。

2. 环境建模与 MDP 三要素:状态、动作、奖励怎么定

2.1 为什么 MEC 卸载天然适合写成 MDP

MEC 场景里,每个时隙终端会产生若干任务,任务有数据量、计算量、最大容忍时延。终端可以选择本地执行,也可以卸载到边缘服务器。边缘服务器的算力被多个终端共享,所以某个终端的选择会影响其他终端的体验——这就是典型的序贯决策加资源竞争。用 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^N,N 大了会爆炸,所以常见做法是每个终端独立决策,或者用连续松弛再取阈值。

奖励函数直接决定学出来的策略是否可用。我一般会加一个惩罚项:任务超时未完成给大负奖励,这样智能体会主动避开会导致超时的动作。

2.3 用 Python 搭一个最小环境

下面这段代码定义一个简化版 MEC 环境,只保留最核心的时延和能耗计算,方便先跑通再逐步加细节。

import numpy as np class MECEnv: def __init__(self, n_terminals=5, edge_cpu=10e9, bw=20e6): 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_terminals=5) state_dim = env.N * 3 action_dim = env.N model = ActorCritic(state_dim, action_dim) optimizer = torch.optim.Adam(model.parameters(), lr=3e-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 项,限制新旧策略比值不要偏离太远。参数说明:lr=3e-4是 PPO 常用学习率,太大容易崩,太小收敛慢;clip_eps=0.2是默认裁剪范围,任务奖励波动大时可以调到 0.1 更稳;gamma=0.99适合单 episode 20 步的场景,如果改成多时隙长任务,可以保持 0.99 或降到 0.95。epochs=10表示每轮采样数据重复训练 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(seed=42): 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,确认物理量算得对,再开始训练。很多人一上来就调网络,结果训练不收敛,查了半天发现是环境里时延算错了。先信环境,再信算法。希望帮到你。

本文还有配套的精品资源,点击获取

版权声明: 本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若内容造成侵权/违法违规/事实不符,请联系邮箱:809451989@qq.com进行投诉反馈,一经查实,立即删除!
网站建设 2026/10/5 8:18:10

Java命令模式实战:从接口设计到撤销重做与事务补偿

1. 一次重构让我彻底理解了“处理行为”为什么要设计成可变的做Java开发这些年,最头疼的不是技术不会,而是需求天天改。你花三天写好的业务逻辑,产品经理一句话就要换个处理方式。我印象最深的是一个订单通知模块的改造:最开始只需…

作者头像 李华
网站建设 2026/10/5 8:17:55

SpringBoot配置文件敏感信息加密:Jasypt、自定义AES与KMS方案详解

接手过不少SpringBoot项目,最让我头皮发麻的不是业务代码写得多烂,而是打开 application.yml ,数据库密码、Redis密码、第三方接口密钥一字排开,全是明文。更夸张的是,很多项目直接把这个文件提交进了Git仓库&#x…

作者头像 李华
网站建设 2026/10/5 8:17:11

九款AI论文写作工具实测:从选题到查重的全流程指南

毕业季的图书馆里,永远坐着一排盯着空白文档发愁的本科生。毕业论文这道坎儿,说难不难,说简单也不简单——难在没人告诉你一套完整的操作流程,烦在文献、大纲、格式、查重这些琐碎环节能把你最后一点耐心磨光。导师当时丢给我一句…

作者头像 李华
网站建设 2026/10/5 8:17:07

插件加载失败排查指南:plugin.json与TypeScript SDK实战

1. 从“plugins”这个标题说起:一个被低估的工程话题“plugins”这个词看起来平平无奇,但如果你最近在折腾 Cursor、Codex CLI、ZCode CLI 这类工具,或者被failed to load plugins、plugin.json、TypeScript SDK这些词反复折磨过,…

作者头像 李华
网站建设 2026/10/5 8:16:05

2026软考高级备考指南:系统分析师与系统架构设计师如何选

1. 先别急着买书:2026年软考高级到底该报“系分”还是“架构” 我隔三差五就会在后台收到这类私信:“博主,我准备26年考系分架构,有什么推荐资料?”每次看到这个问法,我都得先帮对方捋清楚一件事—— 系统…

作者头像 李华
网站建设 2026/10/5 8:16:04

魔术公式轮胎模型Matlab实现与参数标定实战指南

做车辆动力学仿真时,轮胎力算不准是最让人头大的问题。车身参数再精确,悬架模型再细致,只要轮胎模型不给力,整车操稳仿真结果基本就是看个乐子。几年前我刚开始做操纵稳定性研究时,第一件事就是搭一套能复现经典结果的…

作者头像 李华