news 2026/10/7 4:46:16

深度强化学习求解无人机辅助旅行商问题:从建模到PyTorch实战

作者头像

张小明

前端开发工程师

1.2k 24
文章封面图
深度强化学习求解无人机辅助旅行商问题:从建模到PyTorch实战

简介:这份PDF文献聚焦无人机与卡车协同配送的旅行商问题(TSP-D),面向物流优化、最后一公里配送方案设计的研究人员与工程师。针对传统注意力模型难以协调异质车辆动作序列的痛点,作者提出融合注意力编码器与LSTM解码器的混合模型,通过隐藏状态记录多车动作序列,实现卡车与无人机的等待与路径协同,并在随机数据集与真实场景中验证了其求解质量与计算效率优于纯注意力模型及部分运筹学基线。资源包共1个PDF文件,约2.7MB,内容为完整论文,含摘要、引言、方法、实验与参考文献,便于读者系统理解模型结构、实验设置与对比结论。目前已有103人学习,适合希望将深度强化学习落地于车辆路径规划与无人机调度场景的读者参考复现。

1. 无人机辅助旅行商问题:为什么值得用深度强化学习啃这块硬骨头

地面车辆从仓库出发,把散落在城市各处的客户点全部访问一遍再回到仓库,要求总行驶距离最短——这就是经典的旅行商问题(TSP)。当客户点分布在山区、海岛、灾区或大片农田时,纯靠车辆跑会非常吃力,于是我们给车辆配一架无人机:车辆负责在主干道上行驶并充当移动起降平台,无人机从车辆上起飞,去访问那些车辆难以直达的客户点,再飞回车辆。这就是无人机辅助旅行商问题(Drone-assisted TSP,工程里常写作 TSP-D 或 min-max 变体)。

它的难点在于:车辆路径和无人机架次是耦合的,无人机续航有限,起降点必须落在车辆轨迹上,解空间比经典 TSP 大得多。传统精确求解器在几十个点上就开始吃力,启发式方法又要针对场景反复调参。深度强化学习(DRL)的价值在于:把路径构造建模成序列决策,训练一次,推理时对同分布的新算例可以毫秒级给出高质量解。这篇笔记面向想复现这条技术路线的同学,从建模、环境搭建、训练到排错,一步步讲清楚,代码可以直接抄。

2. 把 TSP-D 建模成马尔可夫决策过程:状态、动作与奖励怎么定

2.1 为什么选编码器-解码器 + 强化学习,而不是纯启发式

经典 TSP 的 DRL 解法(如指针网络、Attention Model)已经比较成熟,核心思路是用一个编码器把节点坐标编码成向量,再用解码器逐步选择下一个访问节点,用 REINFORCE 或 PPO 训练。TSP-D 不能直接套用,因为多了一个"车辆-无人机协同"的结构:无人机不是独立飞行,它必须从车辆当前位置起飞、回到车辆(可能是移动后的)位置。

我一般会采用"分层决策"的建模方式:外层决策车辆下一个访问的节点(或路段),内层决策在当前车辆位置上是否放飞无人机、放飞后访问哪个客户点。这样状态里需要同时包含:所有未访问客户点的坐标、车辆当前位置、无人机当前是否在机上、无人机剩余续航。动作空间是"车辆移动 + 无人机任务"的组合,比纯 TSP 大一截。

选 DRL 而不是遗传算法/模拟退火的理由很实际:一旦客户点分布规律固定(比如农村电商配送、灾区巡检),训练好的策略对新算例的推理时间是常数级,而元启发式每次都要重新迭代。代价是训练成本高、泛化到差异很大的分布会掉点,这个边界后面会讲。

2.2 状态表示与特征归一化

状态设计直接决定能不能收敛。我踩过的坑是:坐标不归一化,网络对尺度极其敏感,换一个地图范围就崩。做法是把所有坐标减去车辆起点,再除以最大坐标跨度,落到 [0,1]。

import torch def build_state(coords, vehicle_pos, drone_onboard, drone_budget, visited_mask): """ coords: [N, 2] 所有客户点坐标(已归一化到 [0,1]) vehicle_pos: [2] 车辆当前位置 drone_onboard: bool 无人机是否在车上 drone_budget: float 无人机剩余续航(归一化到 [0,1]) visited_mask: [N] 0/1,1 表示已访问 """ # 未访问点的相对坐标,突出"还能去哪" rel = coords - vehicle_pos.unsqueeze(0) # 拼接静态特征与动态特征 node_feat = torch.cat([ coords, # 绝对位置 rel, # 相对车辆位置 visited_mask.unsqueeze(-1), # 是否已访问 ], dim=-1) # [N, 5] # 全局特征:车辆位置、无人机状态、剩余续航 global_feat = torch.cat([ vehicle_pos, torch.tensor([float(drone_onboard), drone_budget]) ]) return node_feat, global_feat

逻辑说明:node_feat是每个客户点的特征,喂给编码器;global_feat是全局上下文,喂给解码器的 query。参数上,drone_budget一定要归一化,否则续航数值(比如 5000 米)和坐标量级差太多,梯度会被带偏。visited_mask用 0/1 而不是布尔,方便直接拼进张量。

2.3 动作掩码:把不可行动作提前屏蔽

TSP-D 里大量动作是非法或明显劣的:已访问的点不能再选、无人机续航不够飞不到的点不能派、无人机不在车上时不能起飞。如果不做掩码,网络会把概率分到非法动作上,训练极不稳定。

def action_mask(coords, vehicle_pos, drone_onboard, drone_budget, visited_mask): N = coords.shape[0] # 车辆可去的点:所有未访问点 vehicle_mask = (visited_mask == 0) # 无人机可派的点:未访问 且 往返距离在续航内 dist = torch.norm(coords - vehicle_pos.unsqueeze(0), dim=-1) drone_reachable = (dist * 2 <= drone_budget) & (visited_mask == 0) # 无人机不在车上时,禁止放飞 if not drone_onboard: drone_reachable = torch.zeros_like(drone_reachable) return vehicle_mask, drone_reachable

逻辑说明:vehicle_mask控制车辆动作,drone_reachable控制无人机动作。注意往返距离用dist * 2是保守估计,实际如果无人机要回到移动后的车辆位置,得用更精确的几何计算,这里先用保守值保证可行性。参数drone_budget是归一化后的续航,和dist同量纲才能比较。

3. 用 PyTorch 搭一个能跑通的 Attention 策略网络

3.1 编码器:把客户点坐标压成上下文向量

编码器用标准的多头注意力堆叠即可,和 Attention Model 一致。关键是输入维度要对上:node_feat是 5 维,先线性投影到embed_dim(我一般取 128),再走 3 层注意力。

import torch.nn as nn class Encoder(nn.Module): def __init__(self, in_dim=5, embed_dim=128, n_heads=8, n_layers=3): super().__init__() self.proj = nn.Linear(in_dim, embed_dim) self.layers = nn.ModuleList([ nn.TransformerEncoderLayer( d_model=embed_dim, nhead=n_heads, dim_feedforward=512, batch_first=True) for _ in range(n_layers) ]) def forward(self, node_feat): h = self.proj(node_feat) # [B, N, embed_dim] for layer in self.layers: h = layer(h) return h # 每个节点的上下文向量

逻辑说明:in_dim=5对应 2.2 里的特征维度,改特征就要同步改这里。n_layers=3是我在 50 点规模下试出来的平衡点,层数再多收益很小但显存涨得快。batch_first=True别忘了,否则维度顺序会错。

3.2 解码器:车辆动作与无人机动作的双头输出

解码器每一步输出两个概率分布:车辆去哪个点、无人机派去哪个点(或不起飞)。我用两个独立的线性头,各自接 softmax,再乘动作掩码。

class Decoder(nn.Module): def __init__(self, embed_dim=128): super().__init__() self.vehicle_head = nn.Linear(embed_dim, embed_dim) self.drone_head = nn.Linear(embed_dim, embed_dim) self.query = nn.Linear(embed_dim + 3, embed_dim) # 全局特征 3 维 def forward(self, node_ctx, global_feat, vehicle_mask, drone_mask): q = self.query(torch.cat([node_ctx.mean(1), global_feat], dim=-1)) # 车辆动作 logits v_logits = (self.vehicle_head(node_ctx) * q.unsqueeze(1)).sum(-1) v_logits = v_logits.masked_fill(~vehicle_mask, -1e9) # 无人机动作 logits d_logits = (self.drone_head(node_ctx) * q.unsqueeze(1)).sum(-1) d_logits = d_logits.masked_fill(~drone_mask, -1e9) return torch.softmax(v_logits, -1), torch.softmax(d_logits, -1)

逻辑说明:masked_fill把非法动作的 logit 压到 -1e9,softmax 后概率接近 0。global_feat是 3 维(车辆位置 2 维 + 无人机状态 1 维),这里为了简化把续航也并进状态,实际可以扩展。两个头共享node_ctx,但各自有投影,避免任务间干扰。

3.3 训练循环:REINFORCE + 基线

TSP 类问题用 REINFORCE 配一个贪心基线(greedy rollout)就够稳,不一定上 PPO。基线用同一网络贪心解码得到,作为优势估计的参照。

def train_step(model, batch, optimizer): # 采样一条轨迹 log_probs, reward = model.rollout(batch, greedy=False) # 贪心基线 with torch.no_grad(): _, baseline = model.rollout(batch, greedy=True) advantage = (reward - baseline).detach() loss = -(log_probs * advantage).mean() optimizer.zero_grad() loss.backward() # 梯度裁剪,TSP-D 里非常必要 nn.utils.clip_grad_norm_(model.parameters(), max_norm=1.0) optimizer.step() return loss.item()

逻辑说明:advantage用采样奖励减贪心基线,方差比纯 REINFORCE 小很多。clip_grad_norm_的max_norm=1.0是血泪经验,不裁剪的话训练到中期梯度爆炸,loss 直接变 nan。reward一般取负的总成本(车辆行驶距离 + 无人机飞行距离的加权和),具体权重看业务更在意哪个。

4. 训练不收敛、解质量差?这些坑我替你踩过了

4.1 现象:loss 前期下降后突然变 nan

原因:梯度爆炸,尤其在动作掩码把大量 logit 压到 -1e9 后,softmax 的梯度容易出现极端值。解决:加梯度裁剪(max_norm=1.0),同时把掩码值从 -1e9 改成 -1e4,避免数值溢出。我一开始用 -1e9,训练到第 200 轮必炸。

4.2 现象:训练奖励很高,但测试算例解质量很差

原因:过拟合到训练分布的坐标范围。比如训练时坐标都在 [0,1],测试时没归一化或范围不同,网络直接失效。解决:训练时就做随机缩放增强,每批数据随机乘一个 [0.8, 1.2] 的系数再归一化,让网络对尺度不敏感。

4.3 现象:无人机架次始终为 0,模型学不会放飞

原因:奖励设计里无人机飞行成本权重过高,或者无人机动作的初始概率太低,探索不到。解决:训练前期给无人机动作一个小的探索奖励(比如成功放飞并返回额外加 0.1),或者用熵正则鼓励探索。等模型学会放飞后再撤掉。

4.4 现象:车辆路径出现来回横跳,明显不优

原因:解码器每一步独立选点,缺少对"路径平滑性"的约束。解决:在奖励里加一个转向惩罚项,或者把上一步动作作为额外特征喂进解码器。我一般加转向惩罚,系数取总距离的 0.01 倍左右,太大会牺牲最优性。

4.5 现象:换一批客户点数量(比如从 50 到 100)就完全不能用

原因:编码器对节点数没有泛化能力,注意力虽然理论上支持变长,但训练分布单一。解决:训练时混合多种规模(20/50/100 点交替),或者用课程学习,先从 20 点训起再逐步加。别指望只训 50 点就能直接跑 100 点。

5. 让解更稳的三个进阶技巧:从能跑到好用

5.1 用主动搜索替代纯采样解码

训练完之后,推理阶段别只用贪心。我一般用"采样 + 选优":对同一个算例采样 128 条轨迹,取奖励最高的那条。代价是推理时间线性增长,但解质量能提升 3%~8%,对离线规划场景完全值得。

def inference(model, instance, n_samples=128): best_reward, best_route = -float('inf'), None for _ in range(n_samples): route, reward = model.rollout(instance, greedy=False) if reward > best_reward: best_reward, best_route = reward, route return best_route

逻辑说明:n_samples是采样次数,128 是我在 50 点规模下的经验值,再往上收益递减。如果对延迟敏感,可以降到 16 或 32。

5.2 用局部搜索做后处理

DRL 输出的解通常已经不错,但局部还有改进空间。接一个简单的 2-opt 或 relocate 算子,对车辆路径做几十次迭代,能再压 1%~3%。注意只对车辆路径做,无人机架次别乱动,否则可行性会被破坏。

5.3 验证方法:和 OR-Tools 对比,别自嗨

自己训完觉得解很好,一定要拿 OR-Tools 或 LKH 在同样算例上跑一遍对比。我一般这样做:

对比项配置关注指标
DRL 贪心单次解码推理时间、总成本
DRL 采样 128128 次采样选优总成本、提升幅度
OR-Tools时间限制 10s总成本、是否最优
LKH默认参数总成本

如果 DRL 采样后的解比 OR-Tools 差 10% 以上,说明模型或奖励设计有问题,别急着上生产。差距在 5% 以内且推理快一个数量级,这条路就值得继续投入。

最后说个我自己的习惯:每次改完奖励函数或网络结构,先在小规模(20 点)上跑 500 轮看趋势,趋势对了再上 50 点、100 点。直接上大规模调参,一天都跑不出一个能看的曲线。希望帮到你。

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

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

VCS编译流程、许可证管理与Verdi联合调试实战指南

/* MD / 富文本中的 .toc(含博客园搬家等嵌套结构);.toc-box 在侧栏,不受影响 */#content_views .toc,/* 编辑器常在目录前后插入空 p(:empty 仍占 20px),一并去掉避免顶空隙 */#content_views.markdown_views > p:empty:has(+ .toc),#content_views.markdown_views …

作者头像 李华
网站建设 2026/10/7 4:45:43

DeepSeek DSec解析:Agentic训练的弹性沙盒基础设施

最近大模型圈子里讨论最多的&#xff0c;除了各家开源模型的评测分数&#xff0c;就是“Agentic Training&#xff08;代理式训练&#xff09;”这个方向了。单纯靠静态的SFT数据堆出来的模型&#xff0c;在真实工具调用、代码执行这类场景里总差一口气。而DeepSeek这边放出的《…

作者头像 李华
网站建设 2026/10/7 4:45:36

CPO-BiTCN-BiGRU回归预测:MATLAB时序预测与超参数自动优化

做回归预测的朋友&#xff0c;对“优化算法深度学习模型”这种组合套路应该不陌生。这个项目标题是CPO-BiTCN-BiGRU回归预测&#xff0c;具体点说&#xff0c;就是用2024年提出的冠豪猪优化算法&#xff08;Crested Porcupine Optimizer&#xff0c;简称CPO&#xff09;去自动搜…

作者头像 李华
网站建设 2026/10/7 4:44:33

Python lambda 匿名函数:核心语法、实战场景与常见陷阱

1. 从一个排序需求说起&#xff1a;lambda 出现的理由用 Python 写代码写了这么多年&#xff0c;我越来越觉得 lambda 是个特别有意思的设计。很多刚入门的朋友看到lambda x: x[1]这种写法会觉得挺神秘&#xff0c;其实它就是一把小巧的折叠刀——平时用不着&#xff0c;但真到…

作者头像 李华
网站建设 2026/10/7 4:44:01

多模型架构落地:突破统一API盲区,做好成本、质量与安全治理

从接入三家模型到真正敢把流量切过去&#xff0c;中间隔着的不是一套网关&#xff0c;而是成本、质量、安全三座大山。很多团队跟我聊的时候都说"我们已经接了很多个模型&#xff0c;也上了统一 API&#xff0c;应该没啥问题了吧"&#xff0c;可一到月底看账单、一到…

作者头像 李华
网站建设 2026/10/7 4:43:38

实测8款龙虾AI:零门槛是噱头还是真香?TaoToken统一Key实测

/* MD / 富文本中的 .toc(含博客园搬家等嵌套结构);.toc-box 在侧栏,不受影响 */#content_views .toc,/* 编辑器常在目录前后插入空 p(:empty 仍占 20px),一并去掉避免顶空隙 */#content_views.markdown_views > p:empty:has(+ .toc),#content_views.markdown_views …

作者头像 李华