简介这份资源面向无人机任务分配方向的学习者与开发者提供一套基于Python与粒子群优化算法PSO的多无人机任务分配系统完整实现。项目将每架无人机视为一个粒子以位置表示任务分配方案、速度表示调整方向通过迭代搜索最优策略并围绕任务紧急程度、载荷能力、续航时间等要素构建适应度函数兼顾负载与飞行距离等约束。资源包共14个文件以9个Python脚本为核心涵盖主流程、距离计算、适应度评估、解码与PSO迭代等模块另附4张PNG结果图与1个gitattributes配置整体约1.38MB结构紧凑便于阅读与二次开发。目前已有1789人学习下载。读者可据此掌握从问题建模、适应度设计到算法调参的完整思路并参考运行结果图验证分配效果适合作为课程设计、毕业设计或算法入门的实践素材。1. 多无人机任务分配为什么需要粒子群优化从一组炸机说起三架无人机同时起飞执行区域侦察任务点 12 个结果两架撞在同一片空域第三架电量耗尽在半路。复盘时发现问题不在飞控而在起飞前那张任务分配表——谁去哪个点、按什么顺序飞、什么时候返航全是拍脑袋定的。这就是多无人机任务分配要解决的核心问题在任务点数量、无人机数量、续航、载荷、时间窗等多重约束下找到一组让整体代价最小的分配方案。粒子群优化算法PSO之所以常被拿来做这件事是因为它天然适合连续和离散混合的优化问题实现门槛低几十行 Python 就能跑起来而且收敛速度在中小规模任务分配上表现稳定。这篇文章面向的是已经会用 Python 写基本脚本、想把这套方案真正落地到多机调度场景的工程师。我会从建模、编码、参数、避坑一路讲到怎么验证结果是不是真的可用不绕弯子。2. 把任务分配写成 PSO 能吃的数学模型2.1 决策变量怎么定义0-1 矩阵还是任务序列多无人机任务分配的第一道坎不是算法是建模。最常见的两种编码方式一种是 0-1 分配矩阵行是无人机、列是任务点矩阵元素为 1 表示该机执行该任务另一种是任务序列编码每个粒子是一个长度为任务数的排列排列中每个位置的值表示执行该任务的无人机编号。0-1 矩阵的优点是直观缺点是维度随无人机数×任务数膨胀粒子搜索空间大而且容易产生无效解一个任务被多机认领或某机没分到任务。任务序列编码的优点是维度固定为任务数天然保证每个任务只被分配一次缺点是丢失了执行顺序信息需要额外一层排序逻辑。我一般会选任务序列编码原因是它把「每个任务必须被且仅被分配一次」这个硬约束直接编码进了粒子结构里省掉大量罚函数调参的麻烦。具体做法是粒子位置向量长度等于任务数 N每个分量的取值范围是 [1, M1)M 是无人机数量取整后得到该任务归属的无人机编号。import numpy as np def decode_particle(position, n_tasks, n_uavs): 将连续位置向量解码为任务分配方案 position: 长度 n_tasks 的浮点向量 n_tasks: 任务点数量 n_uavs: 无人机数量 返回: 长度 n_tasks 的整数数组每个元素是无人机编号(0-based) # 将连续值映射到 [0, n_uavs) 区间并取整 normalized np.clip(position, 0, 1 - 1e-9) assignment np.floor(normalized * n_uavs).astype(int) return assignment这段代码的关键在np.clip那一行。PSO 迭代中粒子位置可能飞出 [0,1] 边界如果不做裁剪floor之后可能得到负数或超出无人机编号范围的值后续计算代价函数时直接索引越界。裁剪上界用1 - 1e-9而不是 1是为了避免floor(1.0 * n_uavs)恰好等于 n_uavs 导致越界。这个细节看起来小但在实际跑的时候少了这个 1e-9程序会在第几十代突然崩掉排查起来很费时间。2.2 代价函数飞行距离、时间窗和负载均衡怎么加权分配方案好不好全靠代价函数说话。多无人机任务分配的代价通常由三部分组成总飞行距离、时间窗惩罚、负载均衡惩罚。总飞行距离的计算依赖每架无人机执行其分配到的任务点的顺序。由于任务序列编码本身不含顺序需要先按某种规则排序。常见做法是对每架无人机分配到的任务点用最近邻策略生成一条飞行路径再累加距离。最近邻不是最优但计算快在 PSO 迭代几千次的场景下用精确 TSP 求解器不现实。时间窗惩罚针对的是任务点有最早开始时间和最晚结束时间约束的情况。如果无人机到达任务点的时间早于最早开始时间需要等待晚于最晚结束时间则产生惩罚。负载均衡惩罚衡量各无人机任务量的方差避免一架忙死、一架闲着。def cost_function(assignment, tasks, uavs, w11.0, w210.0, w35.0): assignment: 每个任务归属的无人机编号 tasks: 任务点列表每个含坐标、时间窗 uavs: 无人机列表每个含起始坐标、速度 w1/w2/w3: 距离/时间窗/负载均衡权重 total_distance 0.0 time_penalty 0.0 task_counts np.zeros(len(uavs)) for uav_id, uav in enumerate(uavs): # 取出该无人机负责的任务点 assigned [tasks[i] for i in range(len(tasks)) if assignment[i] uav_id] task_counts[uav_id] len(assigned) if not assigned: continue # 最近邻排序生成飞行路径 current uav[start] remaining assigned[:] elapsed 0.0 while remaining: dists [np.linalg.norm(np.array(current) - np.array(t[pos])) for t in remaining] idx int(np.argmin(dists)) nxt remaining.pop(idx) d dists[idx] total_distance d elapsed d / uav[speed] # 时间窗惩罚 if elapsed nxt[earliest]: elapsed nxt[earliest] elif elapsed nxt[latest]: time_penalty (elapsed - nxt[latest]) current nxt[pos] # 负载均衡任务数标准差 balance_penalty np.std(task_counts) return w1 * total_distance w2 * time_penalty w3 * balance_penalty权重 w1、w2、w3 的设定直接决定优化方向。w2 设得过大算法会优先满足时间窗可能牺牲总距离w3 设得过大任务会被均匀切分但每架无人机的飞行路径可能变得绕。我的经验是先把 w1 固定为 1.0w2 取 5 到 20 之间w3 取 1 到 10 之间跑几组对比看帕累托前沿再根据实际业务容忍度定。如果时间窗是硬约束w2 要大到让违反时间窗的解在进化中被淘汰。3. 用 Python 把 PSO 主循环跑起来3.1 粒子群初始化与速度更新公式的代码实现PSO 的核心就两个公式速度更新和位置更新。速度更新决定粒子下一步往哪飞、飞多快位置更新决定粒子落在哪。标准公式里速度受三部分影响惯性项保持当前方向、个体认知项向自己历史最优靠拢、社会认知项向全局最优靠拢。class PSO: def __init__(self, n_particles, n_tasks, n_uavs, max_iter, w0.7, c11.5, c21.5): self.n_particles n_particles self.n_tasks n_tasks self.n_uavs n_uavs self.max_iter max_iter self.w w # 惯性权重 self.c1 c1 # 个体学习因子 self.c2 c2 # 社会学习因子 # 初始化位置和速度 self.positions np.random.rand(n_particles, n_tasks) self.velocities np.random.uniform(-0.1, 0.1, (n_particles, n_tasks)) self.pbest_pos self.positions.copy() self.pbest_cost np.full(n_particles, np.inf) self.gbest_pos None self.gbest_cost np.inf def step(self, cost_fn): for i in range(self.n_particles): # 解码并计算代价 assignment decode_particle(self.positions[i], self.n_tasks, self.n_uavs) cost cost_fn(assignment) # 更新个体最优 if cost self.pbest_cost[i]: self.pbest_cost[i] cost self.pbest_pos[i] self.positions[i].copy() # 更新全局最优 if cost self.gbest_cost: self.gbest_cost cost self.gbest_pos self.positions[i].copy() # 速度与位置更新 r1 np.random.rand(self.n_particles, self.n_tasks) r2 np.random.rand(self.n_particles, self.n_tasks) self.velocities (self.w * self.velocities self.c1 * r1 * (self.pbest_pos - self.positions) self.c2 * r2 * (self.gbest_pos - self.positions)) # 速度限幅防止粒子飞太快跳过最优解 self.velocities np.clip(self.velocities, -0.2, 0.2) self.positions np.clip(self.positions self.velocities, 0, 1 - 1e-9)速度限幅那行np.clip(self.velocities, -0.2, 0.2)是血泪经验。不限幅的话粒子速度会随着迭代累积越来越大位置在边界反复弹跳全局最优附近根本停不下来。限幅范围一般取位置范围的 10% 到 20%这里位置范围是 [0,1]所以取 0.2 是合理的。3.2 惯性权重和学习因子的调参套路PSO 的调参玄学主要集中在三个参数惯性权重 w、个体学习因子 c1、社会学习因子 c2。惯性权重控制粒子保持原有速度的趋势。w 大全局搜索能力强但收敛慢w 小局部搜索能力强但容易早熟收敛。常见做法是线性递减迭代初期 w 取 0.9 鼓励探索后期降到 0.4 鼓励收敛。实现上就是在每次 step 里根据当前迭代次数动态调整 w。c1 和 c2 的典型取值是各 1.5 到 2.0。c1 大于 c2粒子更相信自己的经验适合多峰问题c2 大于 c1粒子更倾向于跟随全局最优收敛快但可能陷入局部最优。多无人机任务分配的场景下我一般让 c1 略大于 c2比如 c11.8、c21.4因为任务分配的解空间往往有多个局部最优太早聚集到全局最优未必是好事。参数推荐范围作用调大后果调小后果w0.4~0.9惯性权重探索强、收敛慢收敛快、易早熟c11.4~2.0个体学习因子解多样性高趋同快c21.4~2.0社会学习因子收敛快、易局部最优搜索散漫粒子数20~50种群规模计算量大覆盖不足迭代数100~500终止条件耗时未收敛粒子数不是越多越好。任务数在 20 以内时30 个粒子跑 200 代基本能收敛到可接受解。粒子数加到 100耗时翻三倍解的质量提升可能不到 5%。这个投入产出比在实际项目里要算清楚。4. 多无人机任务分配避坑5 个让我返工的问题4.1 粒子解码后出现空任务或重复分配现象跑完 PSO输出分配方案时发现某个任务没有被任何无人机认领或者同一个任务出现在两架无人机的任务列表里。原因如果用的是 0-1 矩阵编码粒子位置取整后可能产生全零列任务无人认领或某列多个 1重复分配。这是编码方式本身的缺陷不是算法 bug。解决换用任务序列编码或者在使用 0-1 矩阵时加修复算子——对全零列随机选一架无人机置 1对多 1 列只保留一个 1。修复算子要放在代价计算之前否则无效解会污染 pbest 和 gbest。4.2 代价函数权重没调好导致方案不可用现象算法收敛了代价曲线很漂亮但输出的方案里有一架无人机被分了 8 个任务另一架只有 1 个或者所有任务都堆在时间窗边缘。原因代价函数里负载均衡项或时间窗项的权重太小被总距离项压制了。解决先把各项代价归一化到相近量级再加权。比如总距离除以最大可能距离时间窗惩罚除以最大允许延迟负载均衡用标准差本身就在 0 到任务数之间。归一化之后权重才有可比性。我一般会跑一组权重组合画出帕累托前沿让业务方选一个可接受的平衡点。4.3 算法早熟收敛到局部最优现象迭代到 30 代左右gbest 就不再变化但把结果画在地图上一看明显有更优的分配方式。原因种群多样性丧失太快所有粒子挤在同一个局部最优附近。常见诱因是 c2 过大、w 过小或者粒子数太少。解决三个手段组合用。一是惯性权重线性递减前期保持探索二是加变异算子每迭代 20 代对最差的 10% 粒子做随机重置三是多跑几次取最优PSO 本身是随机算法单次结果不代表真实水平。4.4 任务点数量增加后计算时间爆炸现象任务数从 10 增加到 30单次迭代耗时从 0.1 秒涨到 3 秒跑 200 代要 10 分钟。原因代价函数里对每架无人机的任务列表做最近邻排序复杂度是 O(n²)任务数增加时平方级增长。如果还在 Python 层用循环算距离更慢。解决把距离计算向量化。用 numpy 的广播机制一次性算出所有任务点之间的距离矩阵最近邻排序时直接查表。另外如果任务规模确实大考虑把 PSO 和局部搜索结合用 PSO 做粗分配再用 2-opt 做路径精调减少 PSO 迭代次数。4.5 结果每次跑都不一样无法复现现象同样的参数、同样的数据两次运行得到不同的分配方案没法做对比实验。原因PSO 初始化用了随机数没有固定随机种子。解决在程序入口设np.random.seed(42)并把种子作为参数记录下来。如果要对比不同参数的效果固定种子后只改参数这样差异才归因于参数本身。另外最终方案不要只取 gbest把 pbest 里代价接近的几个方案都输出人工看一眼有时候次优解在实际执行中更鲁棒。5. 验证分配方案是否真的可用三个我必做的检查跑出结果只是第一步方案能不能用是另一回事。我一般会做三个检查。第一个是可视化检查。把任务点和无人机起始点画在二维平面上用不同颜色标出每架无人机的任务归属连线画出飞行路径。肉眼看一遍有没有交叉飞行、有没有某架无人机路径明显绕远。这一步能发现很多代价函数没捕捉到的问题比如两架无人机航线交叉带来的碰撞风险。import matplotlib.pyplot as plt def visualize(assignment, tasks, uavs): colors plt.cm.tab10(np.linspace(0, 1, len(uavs))) for uid, uav in enumerate(uavs): pts [uav[start]] [tasks[i][pos] for i in range(len(tasks)) if assignment[i] uid] xs, ys zip(*pts) plt.plot(xs, ys, o-, colorcolors[uid], labelfUAV{uid}) plt.legend() plt.title(Task Assignment Result) plt.show()第二个是约束校验。写一个独立的校验函数不依赖代价函数逐条检查每个任务是否只被分配一次、每架无人机任务数是否超过上限、时间窗是否满足、总航程是否超过续航。这个函数和代价函数分开写避免代价函数里的 bug 掩盖约束违反。第三个是敏感性测试。把任务点坐标随机扰动 5%重新跑 PSO看分配方案变化大不大。如果扰动一点点方案就完全变了说明这个解处在悬崖边上实际执行时 GPS 误差或风偏就可能导致任务失败。稳定的解应该在轻微扰动下保持结构不变。检查项方法通过标准任务覆盖统计每个任务被分配次数全部为 1续航约束累加每机航程不超过最大航程时间窗模拟飞行时间线无违反鲁棒性坐标扰动 5% 重跑方案结构不变负载均衡任务数标准差小于阈值最后说一个我自己的习惯每次调完参数把配置和结果一起存成 JSON包括随机种子、粒子数、迭代数、权重、最终代价、运行时间。过一周回头看能快速定位是哪次改动导致效果变差。这个习惯帮我省了很多后悔药。希望帮到你。本文还有配套的精品资源点击获取