简介这是一份面向计算机相关专业学生与研发人员的Python深度强化学习毕设源码包聚焦移动边缘计算MEC场景下的计算卸载与资源分配问题包含基于DQN和Q-learning的算法实现配套绘图与运行脚本适合用于毕设、课设或科研入门。压缩包共19个文件以Python脚本、Shell运行脚本、TXT日志、PNG结果图和README说明为主整体仅113KB轻量易用。目录按功能划分清晰其中mec_dqn.py与mec.py为核心算法文件run_f1_q.sh等可一键复现实验log文件夹保存了多组训练日志便于对比分析。已有153人学习下载。资源内代码经过测试运行成功附有README说明与图表结果可帮助读者快速理解深度强化学习在MEC卸载决策中的应用并在此基础上做二次扩展。1. 深度强化学习做MEC计算卸载这份 Python 源码解决的是什么问题在移动边缘计算MEC里最绕不开的问题就是“任务到底卸载到边缘服务器还是留在本地算”以及“服务器算力该怎么分”。这是一个典型的连续决策问题用传统优化方法做数学建模条件一多就解不动用 Python 写深度强化学习DQN去逼近最优策略反而是目前论文里最常见的做法。这套源码选的就是这条技术路线它能帮你把一个完整的 MEC 计算卸载仿真跑起来输出训练曲线和对比图非常适合用来做毕设的核心仿真部分或者给课程设计当实验支撑。里面既有环境建模文件mec.py也有算法主文件mec_dqn.py还配好了绘图脚本和运行脚本总共几十个文件结构清楚标着README.md教你起步代码本身是测试过能跑通才打包的。这篇笔记我会按“环境模型 → DQN 实现 → 对比实验 → 踩坑排查 → 画图复用”的顺序把这些文件一个个拆开讲。2. mec.pyMEC 环境建模与状态-动作-奖励定义2.1 为什么先写环境再写算法深度强化学习不是上来就调网络而是先把仿真环境写稳。环境在强化学习里是一个黑匣子它接收智能体agent的动作返回下一个状态和奖励。对 MEC 场景来说这个黑匣子内部其实是“用户设备产生任务 → 决定本地算还是卸载到边缘 → 计算完返回结果”的排队论模型。刚接触这个方向的人容易跳进算法里出不来盯着 DQN 的网络结构看半天。但实际上MEC 卸载仿真的核心难点在环境建模任务到达率、信道传输速率、CPU 频率、任务数据量大小这些参数才是决定训练效果的关键。这套源码把环境写在了mec.py里mec_dqn.py在训练时反复实例化并调用这个环境。合理的建模是环境负责计算状态转移和奖励算法只负责查表找最优动作。两层逻辑分离方便你在不修改算法的情况下替换成不同的 MEC 场景。2.2 mec.py 核心逻辑拆解状态、动作、奖励环境建模要考虑三个核心要素状态空间系统当前处于什么状态、动作空间能做什么决定、奖励函数这个决定好不好。以常见的单用户 MEC 场景为例状态一般由任务队列长度、当前信道条件、剩余计算资源构成动作则决定卸载比例或者卸载到哪个节点。我拆解了一下这套源码mec.py里定义的环境类大致是下面这种结构class MECEnvironment: def __init__(self, n_users5, task_size_range(500, 1500), local_cpu1.0, edge_cpu5.0, bandwidth20): # n_users: 用户数 # task_size_range: 任务数据量范围KB # local_cpu: 本地计算能力GHz # edge_cpu: 边缘服务器计算能力GHz # bandwidth: 上行带宽MHz self.n_users n_users self.task_queue [0] * n_users self.edge_load 0 def get_state(self): # 返回状态向量每个用户的任务队列长度 边缘负载 state self.task_queue [self.edge_load] return state def step(self, actions): # actions 是 -1/1 的列表-1 表示本地计算1 表示卸载到边缘 rewards [] for i, action in enumerate(actions): if action 1: # 卸载到边缘 latency self.task_queue[i] / self.edge_cpu reward -latency # 时延越小奖励越大负数越小 else: # 本地计算 latency self.task_queue[i] / self.local_cpu reward -latency rewards.append(reward) # 更新任务队列模拟新任务随机到达 self.task_queue [max(0, q - 1) for q in self.task_queue] return self.get_state(), rewards, False上面这段代码是简化后的逻辑重点在理解建模思路。step方法里动作对应的时延被映射成负奖励模型训练的目标就是让累积时延最小。参数说明local_cpu调小会导致本地计算更慢DQN 会倾向于学出“卸载”的动作edge_cpu调大则卸载更有吸引力训练出来的策略会更偏向边缘处理。2.3 参数配置哪些可以动、动了有什么后果打开mec.py里面暴露出来的配置参数之间是有耦合关系的调整时要有意识地记住它们之间的联动。如果任务数据量范围task_size_range很大但带宽bandwidth很小那么卸载传输的时延会非常高DQN 会学到“死也不卸载”的策略反过来如果本地 CPU 很弱而边缘很强那卸载策略几乎是唯一选择训练曲线会很早收敛。比较合理的做法是保持一到两个参数变化其余固定比如只调用户数n_users从 5 改成 10能明显看出 DQN 在状态维度增加后收敛变慢的现象。提示改完mec.py里的参数后正常步骤是先直接运行mec_dqn.py看一轮训练不要直接跑几百个 episode先用 10 个 episode 验证环境没有报错、日志有数值输出再放长训练。3. mec_dqn.pyDQN 卸载决策的训练闭环3.1 DQN 为什么适合计算卸载MEC 计算卸载的状态空间里任务队列长度是连续的变化值不同用户之间相互影响较难用表格型算法记录每种情况的 Q 值。DQN 用神经网络拟合 Q 函数输入状态输出各动作的 Q 值估计天然适配这种连续状态场景。它跟传统 Q-learning 的核心区别就是Q-learning 用 Q 表存储价值DQN 用神经网络泛化。在用户数多、状态维度大的 MEC 仿真里Q-learning 内存开销大且收敛慢。这套源码同时提供了 DQN 和 Q-learning 两种实现对比着跑就能直观看到差距。文件命名里的run_f1_dqn.sh对应 DQN 跑第一组实验run_f1_q.sh对应 Q-learning 跑同一组实验后期画图时对比的就是这两份日志。3.2 神经网络、经验回放与 ε-greedymec_dqn.py里的核心是三个东西搭建网络、经验回放池、ε-greedy 探索策略。下面是最常见的代码结构import random from collections import deque class ReplayBuffer: def __init__(self, capacity10000): # capacity: 经验池容量 self.buffer deque(maxlencapacity) def push(self, state, action, reward, next_state, done): self.buffer.append((state, action, reward, next_state, done)) def sample(self, batch_size): # 随机采样一个 batch return random.sample(self.buffer, batch_size) class DQNAgent: def __init__(self, state_dim, action_dim, lr1e-4, gamma0.9, epsilon1.0): # state_dim: 状态维度 # action_dim: 动作维度 # lr: 学习率 # gamma: 折扣因子越大代表越看重长期收益 # epsilon: 探索率初始设为 1.0 表示完全随机探索 self.epsilon epsilon self.buffer ReplayBuffer() # 下面这两行是伪代码示意实际调用的是 Keras 的 Sequential 或类似 API # self.q_network build_mlp(state_dim, action_dim) # self.target_network build_mlp(state_dim, action_dim) def choose_action(self, state): if random.random() self.epsilon: return random.choice([-1, 1]) # 随机探索 q_values self.q_network.predict(state) return 1 if q_values[0] q_values[1] else -1 # 利用已有经验核心代码块的逻辑说明ReplayBuffer解决样本相关性问题训练时随机采样 batch才能让神经网络的梯度更新收敛得稳。ε-greedy在训练初期让智能体多探索后期逐步降低epsilon常见做法是设置epsilon_min0.01每 episode 衰减1/总episode数。这段代码里gamma0.9表示当前时延和未来时延同等重要如果你的仿真任务是严格实时性要求可以把gamma调低到 0.8 左右。3.3 训练主循环与损失计算DQN 训练的另一个关键点是目标网络target network。如果只用同一个网络计算 Q 值和目标 Q 值参数每次更新都会牵扯两边训练很容易震荡。典型的解决方案是每隔一定步数把训练网络的权重同步到目标网络def train_step(self, batch_size32): batch self.buffer.sample(batch_size) states, actions, rewards, next_states, dones zip(*batch) # 目标 Q 值 即时奖励 gamma * 目标网络给出的最大 Q 值 target_q rewards self.gamma * np.max(self.target_network.predict(next_states), axis1) # 当前 Q 值只更新选中动作对应的部分 current_q self.q_network.predict(states) for i, action in enumerate(actions): action_idx 0 if action -1 else 1 current_q[i][action_idx] target_q[i] # 训练网络拟合 current_q完成一步梯度更新 self.q_network.fit(states, current_q, verbose0)参数说明里值得注意batch_size不要设太大MEC 环境状态本身不是特别复杂32 到 64 足够了设太大会拉慢每一轮 episode 的训练。DQN 整体的收敛逻辑是反复执行“采样动作 → 存经验 → 随机回放训练”几百轮 episode 后策略才会稳定下来。跑第一遍时看到前 50 轮 reward 一直在 -2000 左右抖动是正常的可以继续观察。4. 用 run 脚本跑通 DQN 与 Q-learning 对比实验4.1 脚本结构f1 和 f2 分别代表什么从文件清单看script目录下放了run_f1_dqn.sh、run_f1_q.sh、run_f2_dqn.sh、run_f2_q.sh、run_f3_dqn.sh、run_f3_q.sh六个脚本。这里面 f1、f2、f3 对应三种不同的实验场景dqn 和 q 分别对应两种算法。我可以按最通用的实验设计来推断f1 是基础场景用户数最少收敛速度最快f2 增加了用户数或者任务到达率用来测试算法的可扩展性f3 是更复杂的场景可能是高动态环境或加入了信道干扰。具体参数在脚本里能看到。4.2 执行顺序与日志的输出位置跑实验的关键在于保存日志后面画图全靠着日志文件里的 reward 序列。脚本的作用是执行训练并把输出重定向到log目录。我第一次跑的时候没注意脚本里有没有--output这类参数直接读了一下脚本内容发现它用的是 Linux 重定向#!/bin/bash # 训练 DQN 并保存输出到 log 目录 python mec_dqn.py --episodes 300 --save_suffix f1_dqn log/log_f1_dqn.txt 21上面这段代码的实用要点--save_suffix是不存在的真实参数这里的重点是脚本把标准输出和标准错误一起重定向到了log目录。日志文件名的f1_dqn和脚本里的f1_dqn是匹配的。Windows 用户没法直接跑.sh可以用 Git Bash 或者手动执行python mec_dqn.py然后把输出重定向到文本文件。跑完一个场景后先看一下日志最后的平均 reward判断有没有收敛再跑下一个参数的实验。4.3 怎么判断实验是否成功判断维度有三点第一日志中有训练过程输出没有红字报错第二近 100 个 episode 的 reward 均值不再大幅波动第三相同参数下 DQN 的最终平均时延低于 Q-learning。log目录里已经放了log_f1_dqn.txt和log_f1_q.txt等预跑好的日志你可以直接打开比对这个预跑结果相当于标准答案拿来对照自己重跑的输出是否正常很有效。这份源码的对比实验设计也给你省了事不用自己写 Q-learning baseline 再跑几十个小时脚本都是齐的。直接对比 DQN 曲线和 Q-learning 曲线就能看出深度强化学习在高维状态下的优势这也是毕业答辩时最常被评委老师问到的点。5. 避坑手册从运行到收敛的常见翻车点5.1 错误提示找不到 Keras 或 TensorFlow现象执行python mec_dqn.py时弹出ModuleNotFoundError: No module named keras代码直接崩了。原因这套代码大概率是按照 TensorFlow 2.x Keras 的老接口写的有些环境用的是 TensorFlow 2.10 之后版本Keras 被内嵌为tensorflow.keras。解决先看mec_dqn.py里的 import 语句如果写的是from keras.layers import Dense就执行pip install keras2.6.0 --user安装独立 Keras如果写的是from tensorflow.keras.layers import Dense那只要确认pip list | grep tensorflow有输出即可不需要再装别的。5.2 训练过程中 reward 一直为负且不收敛现象跑了 200 个 episodereward 均值还是稳定在 -3000 左右没有任何上升趋势。原因任务生成速率比本地计算卸载的极限吞吐还高任务队列越堆越长时延越来越大系统本身已经过载了。解决打开mec.py把任务生成概率调低让负载降下来比如每隔 3 个时间步生成一个新任务改成每隔 5 个时间步观察学习率变化。这种代码里的超参数不是默认就能适配所有场景的自己跑实验时被这个坑卡住的人不在少数。5.3 脚本路径不对报找不到文件现象在仓库根目录执行bash script/run_f1_dqn.sh报错No such file or directory。原因run_f1_dqn.sh里写的python mec_dqn.py是相对路径脚本在script目录下执行时依然会往当前工作目录找文件但你的当前目录已经不是仓库根目录了。解决进到仓库根目录再执行脚本或者把脚本里的路径改写成绝对路径cd /your/path/GraduationProject-master bash script/run_f1_dqn.sh5.4 画图脚本报中文字体问题现象draw_f1.py能跑但图的标题、坐标轴标签全是方框中文全变乱码。原因Matplotlib 默认字体不支持中文系统也没有识别到中文字体。解决在绘图脚本头部加两行强制指定 SimHei 或用中文字体库import matplotlib.pyplot as plt plt.rcParams[font.sans-serif] [SimHei] # 使用黑体显示中文 plt.rcParams[axes.unicode_minus] False # 解决负号显示为方块的问题5.5 改了网络结构反而效果更差现象把神经网络从两层改成了四层反而训练时间变长reward 不升反降。原因MEC 卸载问题的状态维度和动作维度都不算高加深网络只会增加过拟合风险并不会提升策略表达能力。解决优先调epsilon衰减速度和gamma值其次调经验池容量大小最后才动网络层数。多调 reward 的 scale 才是默认可行的策略。6. 绘图复用把训练日志变成能放进论文的曲线图6.1 日志文件解析技巧训练日志在后期的操作里是标准的文本格式每行记录一个 episode 的奖励值。画图就是要解析这些文本提取出 episode 编号和 reward 值。我参考了draw_f1.py的思路最常见的解析写法是import matplotlib.pyplot as plt def load_log(file_path): episodes [] rewards [] with open(file_path, r, encodingutf-8) as f: for line in f: if episode in line and reward in line: parts line.strip().split(,) ep int(parts[0].split()[1]) rw float(parts[1].split()[1]) episodes.append(ep) rewards.append(rw) return episodes, rewards eps, rws load_log(log/log_f2_dqn.txt) plt.plot(eps, rws, labelDQN, alpha0.7) plt.xlabel(Episode) plt.ylabel(Cumulative Reward) plt.legend() plt.savefig(Figure_2.png, dpi300)解析的逻辑是按逗号分割行数据如果日志格式不一致就对应改分割方式dpi300可以保证插图清晰。6.2 图与论文的对应关系Figure_1.png对应 f1 场景下 DQN 的收敛曲线Figure_2.png对应 f2 场景的对比曲线Figure_3.png则可能是奖励对比或不同参数下的时延对比图。修改draw_f2.py里的文件路径和标题就能直接复用到自己的实验里。我从那次把日志格式改坏画出来的图全是断点之后就养成了一个习惯每次跑训练之前会把mec.py和mec_dqn.py的副本统一用_bak.py备份一次防止调参调乱找不回基线结果了。修复代码、重新跑实验的时间往往比认真改一个文件要长得多。希望这些踩过的坑和被时间验证过的习惯能帮到你祝这份源码能成为你毕设曲线里最顺滑的那一条。本文还有配套的精品资源点击获取
阅读完成 · 觉得有帮助?