ARTICLE DETAIL

资讯详情

深耕网站建设与运营推广的一线实战洞察。

深度强化学习实现无人机基站轨迹优化:Python源码实战

深度强化学习实现无人机基站轨迹优化:Python源码实战 简介面向无人机通信与强化学习研究者的实用源码包聚焦无人机基站的飞行轨迹优化问题。方案融合深度强化学习与动态规划算法内置MADQN、DyMADNQ、MADNQ等多智能体实现可同时考虑多机协同、信号覆盖、能耗与安全性等因素适合开展蜂窝网络吞吐量提升类仿真实验。压缩包共77个文件包含38个Python脚本、24个txt说明、6个xml配置、4个md文档、2个mat数据及ipynb示例等整体仅157KB结构清晰、便于快速部署。目前已有140人学习下载尤其适合正在研究深度强化学习应用或无人机基站部署的开发者参考。通过源码即可复现轨迹优化完整流程理解动态规划与多智能体DRL的结合逻辑还能依据注释与目录结构修改参数、扩展新场景为后续算法改进提供了可直接运行的基线。1. 深度强化学习设计无人机基站轨迹Python 源码落地前先把问题边界算清楚无人机背着基站飞到天上给地面用户补盲这个场景本身不新鲜真正让方案从“能飞”变成“能服务”的是它的飞行轨迹到底怎么规划。轨迹优化牵扯到通信覆盖、能耗约束和时隙分配三个目标常规的启发式算法只能做到“看起来合理”无法逼近最优。用 Python 做深度强化学习方法加动态规划来解无人机基站轨迹问题是目前源码实现成本最低、迭代速度最快的路径也是通信和 AI 两个方向交叉时最容易产生实际结果的切入点。这篇文章写给两类人刚接触强化学习的通信方向研究生以及需要在仿真里完整跑通一条轨迹优化链路的工程人员。下面按建模、环境搭建、算法实现、训练避坑、验证产出五个部分展开每个环节都给可照抄的代码和参数。2. 从动态规划到深度强化学习无人机基站轨迹问题的建模路径2.1 问题形式化轨迹优化的四个核心要素把无人机基站轨迹问题写成可以计算的形式需要四个要素任务时隙、无人机状态、用户分布和约束条件。经典做法是把任务时间 T 切成长度相等的时隙令 t 1,2,…,T 表示时隙序号无人机在二维水平面的位置记为 (x(t), y(t))。飞行高度 h 在典型部署中固定为 100 至 120 米这个高度既能避开低层建筑物遮挡又不至于让自由空间路径损耗过大。地面用户的位置可以在每一轮训练开始时随机撒点也可以按照某个真实地形分布预置。用户 i 在时隙 t 是否被无人机服务取决于它是否落在无人机覆盖半径 R 内。这里有个常见的建模分歧直接用理想圆形覆盖模型还是用带衰落的三维路径损耗模型。我的经验是第一版源码先用理想圆形把训练流程跑通后面再切换更精细的信道模型——因为奖励函数和算法调参的复杂度已经够高一开始引入太多物理细节只会让问题像黑匣子出了问题根本不知道是模型错还是算法错。目标函数通常是最大化加权吞吐量也就是让更多用户被服务且单用户速率不被拉低。用户速率用香农公式计算表现为 log2(1 SINR)。决策变量是无人机每个时隙的速度方向和位置序列但这个序列和奖励、能耗是强耦合的。换句话说这不是一个“选几个点飞过去”的静态问题而是一个动态时序决策问题。很多人一开始把它当路径规划做用 A* 或者遗传算法找一条最短路径结果发现覆盖率和能耗完全不对原因就在这里轨迹优化的本质是“每一刻都在权衡覆盖数量和移动成本”不是找一条静态路径。2.2 动态规划的模型原理网格离散与 Bellman 方程动态规划的模型原理在这个问题上非常直观。把空间切成网格状态定义为“无人机当前所在网格 剩余时隙数 用户位置快照”动作定义为“向四个相邻网格移动或者原地悬停”。在确定性运动模型下转移概率就是 1奖励函数等于该时隙被服务用户数减去飞行能耗。Bellman 方程写作 V(s) max_α [ r(s,α) γ·V(s′) ]从最后一时隙开始后向迭代最终得到初始状态对应的最优轨迹。这套在小规模下表现很好。我记得第一次实验用 10×10 网格、8 个时隙状态总数大概一万个迭代不到十轮就收敛了。但同样的问题把网格放大到 40×40、时隙拉到 30状态空间直接涨到百万级单次求解需要数分钟而且结果还是离线算出来的——用户位置一变就要重新跑一轮。更深层的瓶颈不在网格大小而在动态规划要求环境转移模型完全已知。用户一旦具备移动统计特性P(s′|s,α) 就得重新估计整个求解器又要重跑。这跟 01 背包问题动态规划解法不同——背包的物品种类和容量固定状态转移不会随时间改变而轨迹优化里每个时隙的“剩余电量”和“用户相对位置”都在变状态空间是一个随时间演化的决策树。对比下来DP 在这个问题里的角色更像“小规模基准答案生成器”而不是真正可部署的求解器。2.3 状态空间爆炸与连续动作需求为什么切换到深度强化学习当无人机位置从网格变成连续坐标动作从“四个方向”变成“在 [-v_max, v_max] 内选速度矢量”动态规划那套离散状态表示就彻底失效了。深度强化学习不存状态转移矩阵而是用神经网络去近似 Q 值或者策略。它接收的观测向量可以是无人机当前位置、剩余电量、所有用户相对坐标、已服务用户标志、当前时隙输出动作则取决于你选择离散控制还是连续控制。这里有一个关键技术选型动作空间离散且维度小时用 DQN动作连续时用 DDPG 或 TD3。飞行轨迹天然是连续控制问题因为无人机的转弯半径和能耗模型都要求动作具有连续性。如果你强行把动作离散成 9 个方向训练出来的轨迹会充满折线实际飞控很难执行。切换到深度强化学习之后还有一个隐性收益训练好的策略网络在每次做决策时只需要一次前向推理时延是毫秒级而 DP 或全局优化需要在每个决策点重新求解。这个差异在部署阶段会体现得非常明显——一个适合做实时在线轨迹调整另一个只能做离线规划。3. 用 Python 搭建无人机基站环境状态、动作与奖励的参数化实现3.1 通信模型与覆盖判断的工程取舍先交代运行环境Python 3.10 或 3.11 均可深度学习框架用 PyTorch 2.0 以上不需要额外安装 ROS 或复杂的通信仿真工具包。如果你还没把 Python 环境配好按官方安装包装完后记得把 pip 加入环境变量否则后面 import torch 时会莫名失败这个细节在新手那里出现频率极高。环境建模的第一步是通信模型。最省事的做法是假定理想圆形覆盖用户和无人机水平距离小于 R 就算可服务。但我在实际调试当中发现纯几何覆盖模型会导致一个典型问题无人机只关心“覆盖了多少用户”完全不在乎边缘用户的实际速率最终学出来的轨迹经常是在用户群中心画一个极小的圈运动范围比预设小得多。修正办法是给每个用户加一个信道衰落系数按三维空间距离计算路径损耗并折算到速率公式里。速率 r_i B·log2(1 P_tx·G / (N0 I))G 是信道增益距离越远越小。覆盖判据从“几何距离是否小于 R”改成“速率是否高于阈值”这样无人机必须靠近用户群中心才能获得更高奖励而不是在覆盖半径边缘试探。这个改动会让奖励稀疏度增加训练早期更慢但最终学出来的轨迹质量明显更合理。3.2 UAVBaseStationEnv一个最小可运行的 Python 环境类下面这个环境类覆盖 reset、step、状态拼接和奖励计算四个核心接口风格接近 gym可以直接对接后续的 DDPG 训练循环。import numpy as np class UAVBaseStationEnv: def __init__(self, n_users8, grid_size500, h100, r_cover80, max_steps50, v_max20): self.n_users n_users self.grid_size grid_size # 水平面边长单位米 self.h h # 固定飞行高度米 self.r_cover r_cover # 覆盖判定半径简化模型 self.max_steps max_steps self.v_max v_max # 每个时隙最大水平位移米 self.uav_pos np.array([grid_size / 2, grid_size / 2]) self.users np.zeros((n_users, 2)) self.time 0 def reset(self): self.time 0 self.users np.random.rand(self.n_users, 2) * self.grid_size self.uav_pos np.array([self.grid_size / 2, self.grid_size / 2]) return self._get_state() def _get_state(self): # 状态向量归一化无人机位置 相对用户坐标 时隙进度 relative (self.users - self.uav_pos).flatten() time_phase [self.time / self.max_steps] return np.concatenate([ [self.uav_pos[0] / self.grid_size, self.uav_pos[1] / self.grid_size], relative / self.grid_size, time_phase ]) def step(self, action): # 动作是 [-1,1] 的连续速度分量 dx float(action[0]) * self.v_max dy float(action[1]) * self.v_max self.uav_pos[0] np.clip(self.uav_pos[0] dx, 0, self.grid_size) self.uav_pos[1] np.clip(self.uav_pos[1] dy, 0, self.grid_size) self.time 1 reward self._compute_reward() done bool(self.time self.max_steps) return self._get_state(), reward, done, {}逻辑说明状态向量把无人机位置和所有用户的相对坐标拼接在一起各维度都做了归一化神经网络不用重新适配不同物理量纲。动作输入是两个 [-1,1] 的连续值乘以 v_max 后得到该时隙的实际位移。边界用 np.clip 限制在仿真区域内部防止无人机飞出地图导致奖励计算失效。time_phase 是容易被忽略的一维状态但它的作用很关键——网络需要知道“当前处于整个任务的前半段还是后半段”才能在最后几个时隙决定是继续覆盖新用户还是返回充电点。没有时隙进度网络学到的策略往往会忽略任务结束时间导致轨迹“有头无尾”。参数建议从 n_users8 起步太少问题过于简单学出来的轨迹没有参考价值太多则初期探索极其缓慢。grid_size500、r_cover80 时覆盖半径与区域边长的比值约 1/6训练难度适中。v_max20 配合 max_steps50无人机理论最大飞行距离为 1000 米而区域对角线约 700 米这意味着无人机有足够能力到达任何位置但必须做出取舍——它不可能在 50 个时隙里同时照顾所有角落这种约束正是训练出“智能轨迹”的前提。3.3 奖励函数的分阶段设计稀疏与密度怎么平衡奖励计算单独拆出来讲因为它是整套源码里对收敛影响最大的模块。我第一次实现的奖励是“覆盖用户数 0.1×剩余电量比例”结果训练 200 个 episode 后轨迹乱飞核心原因是每时隙奖励差异太小网络无法分辨哪个动作更优。修正方案是引入速率项和“阴影区”惩罚。每个时隙的奖励计算如下def _compute_reward(self): reward 0.0 served 0 for i in range(self.n_users): dist np.linalg.norm(self.users[i] - self.uav_pos) # 水平距离加固定高度构成三维空间距离 dist_3d np.sqrt(dist ** 2 self.h ** 2) if dist self.r_cover: served 1 rate np.log2(1 1.0 / dist_3d ** 2) reward rate # 覆盖半径外沿的用户产生微惩罚引导轨迹重心 elif dist self.r_cover * 1.5: reward - 0.05 # 能耗项先给一个极小的权重后期再放大 reward 0.01 * self.current_battery reward - 0.001 * (float(action[0]) ** 2 float(action[1]) ** 2) return reward这段代码里我用 1/d² 近似信噪比距离越近速率越高边缘用户贡献极低。这样无人机会自然向用户密度高的区域移动而不是待在中心不动。覆盖半径 1.5 倍以内的“阴影区”给予 -0.05 惩罚目的是防止轨迹反复在覆盖边界试探。能耗项在早期设置 0.001 的小权重几乎不影响训练等奖励曲线基本稳定之后再逐步加到 0.01——这个顺序很关键如果一开始就把能耗权重设高无人机会为了省电原地悬停这在后面避坑章节会具体展开。4. DDPG 实现与训练循环源码连续动作轨迹怎么收敛4.1 为什么选 DDPG连续动作空间与确定性策略的契合度轨迹问题里动作是飞行速度的两个分量本质上是连续变量。DQN 只能输出离散动作最多把方向切成 8 或 9 档训练出来的轨迹明显带折线实际飞控系统很难跟踪。DDPG 通过确定性策略网络直接输出连续动作配合目标网络和 OU 噪声做探索在单智能体连续控制问题里是相对稳妥的基础方案。TD3 在 DDPG 基础上加入了双 Q 网络和延迟更新稳定性更好但多出来的网络结构会增加调参负担。我的建议是第一版先跑 DDPG确认环境建模没问题之后再切换 TD3 做对比实验。PPO 也可以做连续控制但它本质是随机策略迭代在轨迹优化这种样本获取成本较高的问题上效率低于 DDPG 和 TD3。下面的实现全部基于 DDPG。4.2 Actor-Critic 网络定义与初始化import torch import torch.nn as nn class Actor(nn.Module): def __init__(self, state_dim, action_dim): super().__init__() self.net nn.Sequential( nn.Linear(state_dim, 256), nn.ReLU(), nn.Linear(256, 256), nn.ReLU(), nn.Linear(256, action_dim), nn.Tanh() ) def forward(self, x): return self.net(x) class Critic(nn.Module): def __init__(self, state_dim, action_dim): super().__init__() self.net nn.Sequential( nn.Linear(state_dim action_dim, 256), nn.ReLU(), nn.Linear(256, 256), nn.ReLU(), nn.Linear(256, 1) ) def forward(self, s, a): return self.net(torch.cat([s, a], dim-1))Actor 的输出层用 Tanh 把动作限制在 [-1,1]正好对应环境 step 接口的动作范围。Critic 把状态和动作拼接后输入输出一个 Q 值。隐藏层 256 维对这个场景足够用户数超过 20 之后再考虑拓宽到 512。网络深度不建议超过三层轨迹优化问题的状态维度通常不超过几十过深的网络容易过拟合训练分布导致验证阶段奖励曲线突然掉下去。4.3 回放缓冲区与主训练循环回放缓冲区用双端队列实现容量设为 20000。轨迹问题有个特点同一个 episode 内相邻时隙的样本相关性非常强如果样本缓冲区太小每次采样都会集中在最近几条轨迹上训练很容易被局部牵制。from collections import deque import random class ReplayBuffer: def __init__(self, capacity20000): self.buffer deque(maxlencapacity) def push(self, s, a, r, s2, d): self.buffer.append((s, a, r, s2, d)) def sample(self, batch_size): batch random.sample(self.buffer, batch_size) s, a, r, s2, d map(np.stack, zip(*batch)) return (torch.FloatTensor(s), torch.FloatTensor(a), torch.FloatTensor(r).unsqueeze(1), torch.FloatTensor(s2), torch.FloatTensor(d).unsqueeze(1))主训练循环里有一个关键参数——每 episode 更新次数与采样步数的比例。标准 DDPG 通常每一步做一次更新但轨迹问题里一个 episode 只有 50 步样本利用率太低所以我把更新比调到 1:5即每收集完一个 episode 的样本后连续做 5 次批量更新。def train(env, actor, critic, n_episodes500, batch_size128, gamma0.99, tau0.005, lr_a3e-4, lr_c3e-4, seed0): torch.manual_seed(seed) np.random.seed(seed) random.seed(seed) buffer ReplayBuffer() target_actor Actor(env.observation_space_dim, 2) target_critic Critic(env.observation_space_dim, 2) target_actor.load_state_dict(actor.state_dict()) target_critic.load_state_dict(critic.state_dict()) opt_a torch.optim.Adam(actor.parameters(), lrlr_a) opt_c torch.optim.Adam(critic.parameters(), lrlr_c) for ep in range(n_episodes): s env.reset() ep_reward 0.0 for _ in range(env.max_steps): with torch.no_grad(): a actor(torch.FloatTensor(s)).numpy() a np.random.normal(0, 0.1, size2) # 探索噪声 a np.clip(a, -1, 1) s2, r, done, _ env.step(a) buffer.push(s, a, r, s2, done) s s2 ep_reward r if done: break if len(buffer.buffer) 1000: for _ in range(5): # 更新比 1:5 s_b, a_b, r_b, s2_b, d_b buffer.sample(batch_size) with torch.no_grad(): next_a target_actor(s2_b) target_q target_critic(s2_b, next_a) y r_b gamma * (1 - d_b) * target_q q critic(s_b, a_b) loss_c nn.functional.mse_loss(q, y) opt_c.zero_grad() loss_c.backward() opt_c.step() policy_loss -critic(s_b, actor(s_b)).mean() opt_a.zero_grad() policy_loss.backward() opt_a.step() for tp, p in zip(target_actor.parameters(), actor.parameters()): tp.data.copy_(tau * p.data (1 - tau) * tp.data) if ep % 20 0: print(fep {ep}: reward{ep_reward:.2f})参数说明gamma0.99 适合 50 时隙的任务长度如果把 max_steps 拉到 100 以上建议改成 0.99 以下否则远距离奖励的衰减过慢。tau0.005 控制目标网络更新速度这个值一般不需要动。学习率 actor 和 critic 统一用 3e-4没有采用分层学习率因为实验结果显示在这个环境里调高 critic 学习率并不会带来更快收敛。噪声标准差 0.1 是初始值后面避坑章节会讲如何让噪声随训练进度衰减。另一个值得强调的细节是随机种子。轨迹优化任务对初始用户分布高度敏感如果不固定 seed每轮训练结果差异会非常大很难判断一个改动到底是有效还是运气好。所有实验都固定 seed0这是保证实验结果可复现的最低成本操作。4.4 训练曲线中值得看的三个信号第一平均 episode reward 是否收敛到稳定区间。正常情况会在 100 个 episode 内出现明显上升300 个 episode 以后波动收窄。如果 reward 前后不升优先检查奖励函数而不是调网络结构。第二actor 的 policy loss 是否持续增长但增速放缓——这是 DDPG 的正常行为如果 policy loss 变成剧烈振荡说明 critic 估值不稳定需要调低 critic 学习率。第三单条轨迹里用户覆盖时隙的分布。如果无人机在最后 20% 的时隙里飞行距离突然增大说明网络学到了“时间快结束前赶紧飞一圈覆盖更多用户”的投机策略这时候需要在奖励函数里加一个轨迹平滑项或者能耗惩罚来抑制。5. 无人机基站轨迹训练避坑五个翻车场景和处理记录5.1 奖励函数过早加入能耗项无人机原地悬停现象训练 200 个 episodereward 曲线一路下跌到负值输出的轨迹显示无人机始终停在初始点附近。原因能耗项权重过高网络发现“动一下奖励降得比覆盖收益还快原地不动则持平”于是选择了驻停策略。这是轨迹优化里最常见的奖励工程翻车。解决第一阶段把能耗项权重置 0只保留覆盖和速率奖励等平均 reward 稳定在正值区间后再以 0.001 的小系数引入能耗项每 50 个 episode 观察一次曲线走势逐步提高系数直到轨迹既覆盖用户又不过度飞行。改完之后原地悬停现象基本消失。5.2 状态归一化遗漏训练几乎不收敛现象reward 曲线波动剧烈同样的超参数重复跑两次结果差异非常大。原因状态向量里混入了未经归一化的绝对坐标和用户相对坐标不同维度的量纲差距让神经网络梯度在训练早期来回摆动收敛极不稳定。这在把多个物理量直接拼接进状态向量时非常容易发生。解决把所有距离量除以 grid_size时隙计数除以 max_steps让状态向量的每一维都在 [0,1] 区间。改完之后同一个 seed 下收敛曲线的可重复性大幅提升不同 seed 之间的曲线包络也变窄了。5.3 探索噪声固定方差策略陷入局部最优现象训练后期 reward 卡在某个上限不再增长轨迹只在区域的一小块范围内反复飞。原因高斯噪声的方差固定为 0.1训练后期策略已经收敛到某个精细动作范围0.1 的噪声幅度反而远大于策略本身的调整量网络无法走出当前的局部最优点。解决把噪声方差改为随 episode 线性衰减从 0.3 衰减到 0.02。前 50 个 episode 保持高噪声充分探索后面逐渐降低让策略精细化。如果衰减太慢会拉长训练时间太快则会提前收敛到局部最优。也可以直接切换到 TD3 算法用目标策略平滑正则来替代噪声衰减。5.4 Actor 输出 Tanh 之后未做二次限幅轨迹频繁触边现象无人机轨迹频繁在仿真区域边界来回往返看起来像“蹭墙”飞行。原因Actor 输出是 [-1,1] 的连续值在环境内部乘上 v_max 后虽然做了边界 clip但没有限制最小转弯半径。当无人机到达边界时前一帧动作让它往右飞后一帧为了让速度归零又得大反向动作于是产生了边界往返跳变。解决在环境 step 内部对 dx/dy 做二次限幅同时加入最小位移约束——位移量小于 0.5 米时直接视为悬停动作输出置零。这个改动对轨迹平滑性改善明显而且不影响覆盖收益因为悬停本身不产生额外能耗。5.5 验证集与训练环境分布不一致部署效果打对折现象训练环境固定用户在 500 米×500 米全域随机撒点验证时改成用户集中在角落 250 米×250 米区域发现 reward 掉到原来的一半左右。原因状态归一化参数是按训练全域计算的验证时用户集中在角落后相对坐标集中在 0.1 到 0.2 区间网络输入的分布已经完全偏移学到的策略自然失效。解决验证集沿用训练时的归一化参数但把用户分布半径从全域随机改成半域随机观察网络能否学到位置无关的覆盖策略。如果半域验证时 reward 显著下降说明网络过拟合到了“全域撒点”这种特定场景。应该在训练阶段就用不同撒点半径做混合采样提高策略的泛化能力。6. 闭环验证飞行轨迹可视化检查、基准对比与工程文件管理轨迹训练完成后第一步是画出学习到的轨迹与用户位置的关系图。把无人机每个时隙的位置按时间顺序连成折线叠加用户散点作为背景检查三个特征轨迹是否平滑是否优先经过用户密集区域末端是否回到任务初始点。第三步在很多文献里被忽略但如果任务是“连续服务型”而不是“单次巡航”无人机的回收位置必须在训练奖励里显式建模否则学出来的轨迹会忽略返航动作。第二步是和动态规划的基准对比。小规模场景网格 10×10、时隙 15下 DP 能算出近似最优解。用同一组用户分布分别跑 DP 和 DRL对比总吞吐量和平均时隙覆盖数。DRL 与 DP 的差距在 15% 以内属于可接受范围——DP 做的是全局离线优化DRL 做的是在线近似决策这点性能差换来了部署时的实时性是划算的置换。工程化落到源码层面我会做三件事把所有超参数和 reward 权重写进每次实验的配置字典并随结果一起存储训练结束后把轨迹保存成 JSON 文件字段包括时隙序号、位置、速度和覆盖用户 ID。这样后续做对比实验时脚本能直接读取历史轨迹计算覆盖率统计不必重新跑训练。另一个我反复犯过又改掉的教训是每次实验都要在输出文件名里带上 reward 权重、噪声衰减率、seed 这三个关键信息否则一周之后翻目录你会发现十个 exp_result_500 文件里根本分不清谁是谁。说出来有点丢人但这个习惯至少帮我省了三次重复实验的钱。这套方法的价值不在做到全网最优而在于给你一个确定性足够高的起点环境可以跑、算法收敛、轨迹可验证、坑有人替你踩过。希望帮到你。本文还有配套的精品资源点击获取
返回列表