ARTICLE · INTELLIGENCE

战地情报 · 详情页

来自尧图项目组的一线实战观察与深度解析

MATLAB实现多无人机三维协同路径规划:MARL框架与工程落地

MATLAB实现多无人机三维协同路径规划:MARL框架与工程落地

简介:本资源是一份面向无人机控制、智能交通及机器人路径规划领域研究人员与工程技术人员的MATLAB实战项目文档,聚焦多智能体强化学习(MARL)在复杂三维动态环境下的协同路径规划问题。内容完整覆盖MADDPG模型设计、CTDE架构实现、高维状态建模、综合奖励函数构建、经验回放机制及核心代码示例,特别适合具备MATLAB编程基础与强化学习理论知识的研究生及一线开发者开展复现与二次开发。资源为1个30KB的docx文件,结构清晰,含项目背景、挑战分析、模型描述、目录模块详解(如动态避障、能源优化、多机协同等),并附有可直接参考的代码片段与扩展建议。目前已有131人学习下载,读者可快速掌握MARL在三维路径规划中的建模逻辑、训练调优要点与实际仿真验证方法,为无人机集群智能导航系统研发提供即用型技术范例与理论支撑。

1. 为什么三维空域里让一群无人机“自己商量着飞”,比写死航点难十倍?

你手头有一组带动力、能悬停、有通信模块的无人机,要它们在城市楼宇群间协同避开障碍物、不撞彼此、还按任务优先级把物资送到不同楼层——这不是遥控器+预设航线能搞定的事。传统路径规划(比如A*、RRT*)在单机三维场景下已显吃力,一旦扩展到多机、动态障碍、通信受限、传感器噪声大、任务目标随时间漂移,就彻底进入“规则写不完、状态穷不尽”的黑匣子地带。这时候,多智能体强化学习(MARL)不是炫技选项,而是工程上唯一能系统性建模“个体决策-群体涌现”关系的工具。本项目用 MATLAB 实现了一套可复现、可调试、可替换模型结构的 MARL 三维路径规划框架:它不依赖 Simulink 仿真黑箱,所有环境交互逻辑、奖励函数设计、策略网络更新步骤全部显式编码;支持从简单球形障碍物到带高度约束的建筑网格地图;最关键的是,它把“通信拓扑如何影响收敛”“局部观测怎么编码成向量”“动作连续空间如何稳定训练”这些一线工程师真正卡住的点,全摊开在代码注释和参数配置里。适合正在做集群飞行验证、需要快速构建 baseline 的算法工程师,也适合高校课程设计中想避开 ROS/Gazebo 环境搭建陷阱、专注 MARL 核心逻辑的学生。


2. 从零搭起 MARL 三维环境:MATLAB 中定义状态、动作与奖励的三步闭环

2.1 定义三维空域与动态障碍物模型:用稀疏体素格网替代点云渲染

MATLAB 做三维仿真最怕“一画图就卡死”。我们放弃patch或surf实时渲染障碍物,改用稀疏体素索引表(Sparse Voxel Index Table)表达空间占用。每个障碍物(如立方体、圆柱体)由其中心坐标(x_c, y_c, z_c)、尺寸(l, w, h)和旋转角θ_z定义,通过voxelGrid = sparse(x_idx, y_idx, z_idx, 1)构建布尔型体素栅格(分辨率默认 0.5m)。关键不是存满整个空域,而是只存被障碍物覆盖的体素索引:

function voxelGrid = buildObstacleVoxelMap(obstacles, mapSize, resolution) % obstacles: N×7 矩阵,每行 [x_c, y_c, z_c, l, w, h, theta_z] % mapSize: [x_max, y_max, z_max], resolution: 栅格边长(米) x_grid = 0:resolution:mapSize(1); y_grid = 0:resolution:mapSize(2); z_grid = 0:resolution:mapSize(3); [X, Y, Z] = ndgrid(x_grid, y_grid, z_grid); voxelGrid = sparse(size(X,1), size(Y,2), size(Z,3)); % 预分配稀疏矩阵 for i = 1:size(obstacles,1) obs = obstacles(i,:); % 计算该障碍物在体素坐标系下的包围盒(忽略旋转先粗筛) x_min = floor((obs(1) - obs(4)/2) / resolution); x_max = ceil((obs(1) + obs(4)/2) / resolution); y_min = floor((obs(2) - obs(5)/2) / resolution); y_max = ceil((obs(2) + obs(5)/2) / resolution); z_min = floor((obs(3) - obs(6)/2) / resolution); z_max = ceil((obs(3) + obs(6)/2) / resolution); % 截断到地图范围内 x_min = max(1, x_min); x_max = min(size(X,1), x_max); y_min = max(1, y_min); y_max = min(size(Y,2), y_max); z_min = max(1, z_min); z_max = min(size(Z,3), z_max); % 对包围盒内所有体素点,判断是否在旋转后障碍物内部(此处用轴对齐近似,高精度用变换矩阵) for x = x_min:x_max for y = y_min:y_max for z = z_min:z_max world_x = (x-1)*resolution; world_y = (y-1)*resolution; world_z = (z-1)*resolution; % 简化:仅做平移+Z轴旋转判断(实际项目中建议用 rotatePoints3D 函数) dx = world_x - obs(1); dy = world_y - obs(2); rot_dx = dx*cos(obs(7)) + dy*sin(obs(7)); rot_dy = -dx*sin(obs(7)) + dy*cos(obs(7)); if abs(rot_dx) <= obs(4)/2 && abs(rot_dy) <= obs(5)/2 && abs(world_z - obs(3)) <= obs(6)/2 voxelGrid(x,y,z) = 1; end end end end end end

逻辑说明:此函数输出一个三维稀疏矩阵voxelGrid,其非零元素位置即为被障碍物占据的体素。后续所有碰撞检测(如isCollision(agent_pos, voxelGrid, resolution))都基于此索引查表,耗时稳定在 O(1),而非遍历所有障碍物几何体。参数说明:resolution是精度与内存的平衡点——0.25m 适合精细楼宇建模但内存翻倍;0.5m 是城市级仿真的常见折中;mapSize必须严格大于所有障碍物外延,否则索引越界。

2.2 多智能体状态编码:把“我看不见全局,但得知道队友在哪”翻译成向量

MARL 的核心难点不在网络结构,而在状态空间设计是否能让策略网络学出有效协作。我们采用分层编码:

  • 局部观测(Local Observation):以当前无人机为中心,半径R_obs=15m内的障碍物体素投影(降维为 2D 占据栅格)、最近 3 个队友的相对位置(dx, dy, dz)、自身速度(vx, vy, vz)、剩余电量百分比;
  • 通信消息(Communication Message):每个队友广播其“意图动作”(intent action),即经 softmax 后的动作概率分布向量(长度=动作离散数);
  • 全局摘要(Global Summary):仅用于 central critic 训练,包含所有智能体当前位置、任务目标点、总剩余电量均值——绝不输入 actor 网络,保证去中心化部署可行性。
function obsVec = encodeAgentObservation(agentID, agents, voxelGrid, resolution, R_obs, numIntentBins) % agents: 结构体数组,含 .pos, .vel, .battery, .intentProb pos = agents(agentID).pos; % [x,y,z] vel = agents(agentID).vel; % [vx,vy,vz] battery = agents(agentID).battery; % 1. 局部障碍物栅格(2D俯视投影,尺寸 31×31,对应 R_obs=15m @0.5m分辨率) gridHalf = 15; obsGrid = zeros(2*gridHalf+1, 2*gridHalf+1); for dx = -gridHalf:gridHalf for dy = -gridHalf:gridHalf worldX = pos(1) + dx*resolution; worldY = pos(2) + dy*resolution; worldZ = pos(3); % 投影到自身高度层 % 转换为体素索引 x_idx = floor(worldX / resolution) + 1; y_idx = floor(worldY / resolution) + 1; z_idx = floor(worldZ / resolution) + 1; if x_idx >= 1 && x_idx <= size(voxelGrid,1) && ... y_idx >= 1 && y_idx <= size(voxelGrid,2) && ... z_idx >= 1 && z_idx <= size(voxelGrid,3) if voxelGrid(x_idx, y_idx, z_idx) obsGrid(dx+gridHalf+1, dy+gridHalf+1) = 1; end end end end % 2. 最近3个队友相对位置(按距离排序) relPos = []; for j = 1:length(agents) if j ~= agentID d = norm(agents(j).pos - pos); relPos = [relPos; agents(j).pos - pos, d]; end end [~, idx] = sort(relPos(:,4)); nearest3 = relPos(idx(1:min(3,end)), 1:3); if size(nearest3,1) < 3, nearest3 = [nearest3; zeros(3-size(nearest3,1),3)]; end % 3. 意图消息(取最近3个队友的 intentProb,拼接) intentMsg = []; for k = 1:min(3, length(agents)-1) if k <= size(nearest3,1) && nearest3(k,4) < Inf j = find(norm(agents(:).pos - pos) == nearest3(k,4), 1); if ~isempty(j), intentMsg = [intentMsg, agents(j).intentProb]; end end end if isempty(intentMsg), intentMsg = zeros(1, numIntentBins*3); end % 拼接最终观测向量 obsVec = [obsGrid(:)', nearest3(:)', vel', battery, intentMsg]; end

逻辑说明:该函数输出长度约 1200 的浮点向量(31×31栅格=961 + 3×3相对位置=9 + 3速度+1电量+3×5意图=15 → 共 990+),作为 actor 网络输入。关键设计点:意图消息intentProb是其他智能体 actor 输出的原始 logits 经 softmax 后的概率,而非 one-hot 动作——这迫使智能体学习预测队友“可能做什么”,而非“他们做了什么”,更符合真实通信延迟与不确定性。numIntentBins默认设为 5(对应 5 个基础运动方向),可根据动作空间复杂度调整。

2.3 奖励函数工程:用分层加权解决“撞墙扣分多,但送错货不扣分”的失衡

强化学习训不起来,80% 溜在奖励函数设计。我们采用分层稀疏奖励 + 密集引导信号结构:

  • 稀疏主奖励(Sparse Primary Reward):仅在任务完成(到达目标点)或严重事故(碰撞/坠毁)时触发,值域 [-100, +50];
  • 密集辅助奖励(Dense Shaping Reward):每步计算,含距离缩短奖励、速度平滑惩罚、电量消耗衰减项,值域 [-5, +3];
  • 协作奖励(Cooperative Bonus):当多机同时接近同一目标时,按距离反比加权分配额外分数,防止单机垄断任务。
function [r, isDone] = computeReward(agentID, agents, targets, voxelGrid, resolution, stepCount, maxSteps) pos = agents(agentID).pos; targetID = agents(agentID).targetID; targetPos = targets(targetID, :); % 1. 主奖励:到达目标 or 碰撞 or 超时 distToTarget = norm(pos - targetPos); r = 0; isDone = false; if distToTarget < 1.0 % 到达半径1m内 r = r + 50; isDone = true; agents(agentID).taskCompleted = true; elseif isCollision(pos, voxelGrid, resolution) || pos(3) < 0.5 r = r - 100; isDone = true; agents(agentID).crashed = true; elseif stepCount >= maxSteps r = r - 30; % 时间惩罚 isDone = true; end % 2. 密集奖励:距离缩短 + 速度平滑 + 电量衰减 prevDist = agents(agentID).prevDistToTarget; if ~isnan(prevDist) distReduction = prevDist - distToTarget; r = r + max(0, distReduction * 2.0); % 每米缩短给2分 end speed = norm(agents(agentID).vel); r = r - min(speed^2 * 0.05, 2.0); % 速度平方惩罚,上限2分 r = r - (1 - agents(agentID).battery) * 0.5; % 电量越低扣越多 % 3. 协作奖励:同目标多机距离加权 sameTargetAgents = find(arrayfun(@(x) x.targetID == targetID, agents)); if length(sameTargetAgents) > 1 dists = arrayfun(@(i) norm(agents(i).pos - targetPos), sameTargetAgents); invDists = 1 ./ (dists + 0.1); % 避免除零 weight = invDists(agentID == sameTargetAgents) / sum(invDists); r = r + weight * 10; % 总协作分10分,按权重分配 end % 更新历史记录 agents(agentID).prevDistToTarget = distToTarget; end

逻辑说明:此函数返回标量奖励r和终止标志isDone。参数敏感点:distReduction * 2.0中的系数 2.0 是调参关键——太小则 agent 忽略路径优化,太大则陷入“抖动靠近”伪收敛;speed^2 * 0.05的 0.05 需匹配无人机最大速度(如 10m/s 时惩罚峰值 5 分,避免过度抑制机动性)。协作奖励的+10是全局池,必须小于主奖励+50,否则 agent 会故意绕路等队友而非专注任务。


3. MARL 算法选型与 MATLAB 实现:为什么用 MADDPG 而非 MAPPO?

3.1 为什么放弃 MAPPO?看三个硬约束

MAPPO(Multi-Agent PPO)在论文中风光无限,但在 MATLAB 工程落地时面临三重硬伤:

  • 通信假设过强:标准 MAPPO 要求 critic 网络能访问所有智能体的全局状态(包括未观测区域),而我们的无人机受视距与通信带宽限制,只能获取局部信息与有限意图消息;
  • 动作空间不匹配:MAPPO 原生适配离散动作,而无人机三维运动需连续控制(推力矢量、偏航角速率),强行离散化(如 27 方向)导致策略粗糙、抖动剧烈;
  • 训练稳定性差:PPO 的 clip 机制在多智能体环境下易引发策略震荡——A agent 提升导致 B agent 状态分布突变,clip ratio 失效,KL 散度爆炸。

我们选择MADDPG(Multi-Agent Deep Deterministic Policy Gradient)的 MATLAB 改进版,理由直击痛点:
✅ Actor 输出确定性连续动作([thrust, pitch, roll, yaw_rate]),无采样方差;
✅ Critic 使用局部观测 + 意图消息拼接输入,天然兼容部分可观测性;
✅ Target network + experience replay buffer 机制,平滑多智能体非平稳环境;
✅ MATLAB 自带rlDDPGAgent可直接扩展,无需从零写梯度更新。

3.2 构建 MADDPG Agent:自定义 critic 输入与 reward shaping

MATLAB 的rlDDPGAgent默认 critic 只接收当前智能体状态,我们必须重写criticNetwork以融合多源信息。核心是构造一个共享 critic 网络(Shared Critic),其输入为[local_obs_i, intent_msg_i, local_obs_j, intent_msg_j, ...],但为降低维度,我们采用注意力聚合(Attention-based Aggregation):

% 构建 critic 网络:输入 = [self_obs; avg_intent; other_obs_features] inputSize = length(encodeAgentObservation(1, agents, voxelGrid, 0.5, 15, 5)); % self_obs 维度 intentSize = 5*3; % 3个队友 × 5维意图 otherFeatSize = 6; % 每个其他智能体取 [dx,dy,dz,speed,battery,task_done] % 输入层:self_obs (990) + avg_intent (5) + other_features (6*(N-1)) inputLayer = featureInputLayer(inputSize + 5 + 6*(numAgents-1), 'Normalization','none'); % 特征处理分支 selfBranch = [ featureInputLayer(inputSize, 'Normalization','none') fullyConnectedLayer(256) reluLayer fullyConnectedLayer(128) ]; intentBranch = [ featureInputLayer(5, 'Normalization','none') fullyConnectedLayer(32) reluLayer ]; otherBranch = [ featureInputLayer(6*(numAgents-1), 'Normalization','none') fullyConnectedLayer(128) reluLayer fullyConnectedLayer(64) ]; % 拼接与输出 criticNetwork = layerGraph(); criticNetwork = addLayers(criticNetwork, selfBranch, 'selfBranch'); criticNetwork = addLayers(criticNetwork, intentBranch, 'intentBranch'); criticNetwork = addLayers(criticNetwork, otherBranch, 'otherBranch'); criticNetwork = connectLayers(criticNetwork, 'selfBranch', 'concat/in1'); criticNetwork = connectLayers(criticNetwork, 'intentBranch', 'concat/in2'); criticNetwork = connectLayers(criticNetwork, 'otherBranch', 'concat/in3'); criticNetwork = addLayers(criticNetwork, concatenationLayer(3, 'Name', 'concat')); criticNetwork = addLayers(criticNetwork, [ fullyConnectedLayer(256) reluLayer fullyConnectedLayer(128) reluLayer fullyConnectedLayer(1) % Q-value 输出 ]);

逻辑说明:此网络结构将 critic 输入拆为三路并行处理,避免高维拼接导致的梯度弥散。intentBranch仅用 32 维隐层压缩 5 维意图,因意图本质是低熵概率分布;otherBranch对每个队友提取 6 维特征(而非全状态),大幅降低输入维度。关键参数:numAgents必须在构建前固定,因网络结构静态编译;若需动态增减 agent,需改用rlContinuousCriticRepresentation并手动实现evaluate方法。

3.3 训练循环:MATLAB 中如何同步更新 N 个 agent 的 actor/critic

MATLAB 的train函数不支持多智能体联合训练,必须手写训练主循环。核心是经验回放缓冲区(Replay Buffer)的跨 agent 共享与梯度更新顺序控制:

% 初始化共享 replay buffer(存储 transition: {s_i, a_i, r_i, s'_i, done_i} for all i) buffer = rlReplayMemory('Capacity', 1e6); for ep = 1:numEpisodes % 重置环境,获取初始状态 [agents, targets] = resetEnvironment(); done = false; step = 0; while ~done && step < maxSteps % Step 1: 所有 agent 并行采样动作(带探索噪声) actions = zeros(numAgents, 4); % [thrust, pitch, roll, yaw_rate] for i = 1:numAgents obsVec = encodeAgentObservation(i, agents, voxelGrid, 0.5, 15, 5); actions(i,:) = getAction(agent{i}, obsVec, step); % 内部加 OU 噪声 end % Step 2: 环境步进,获取新状态与奖励 [agents, rewards, dones] = stepEnvironment(agents, actions, voxelGrid, targets); done = all(dones); % Step 3: 将 transition 存入共享 buffer(每个 agent 一条记录) for i = 1:numAgents s = encodeAgentObservation(i, agents_prev, voxelGrid, 0.5, 15, 5); s_next = encodeAgentObservation(i, agents, voxelGrid, 0.5, 15, 5); r = rewards(i); buffer = addToReplayBuffer(buffer, s, actions(i,:), r, s_next, dones(i)); end % Step 4: 每 20 步执行一次 critic & actor 更新(batch size=128) if mod(step, 20) == 0 && buffer.Size >= 128 batch = sampleFromReplayBuffer(buffer, 128); % 对每个 agent 的 critic 单独更新(使用其专属 critic 网络) for i = 1:numAgents updateCritic(agent{i}, batch, i, agents); end % 所有 actor 同时更新(使用各自 actor 网络) for i = 1:numAgents updateActor(agent{i}, batch, i, agents); end end step = step + 1; agents_prev = agents; end end

逻辑说明:此循环确保:① 所有 agent 在同一环境步中并行决策,符合物理同步性;②addToReplayBuffer存储的是去中心化观测s_i,而非全局状态,保障部署一致性;③updateCritic中i参数用于从 batch 中提取第 i 个 agent 的对应样本,因 critic 网络是 per-agent 独立的。血泪经验:mod(step, 20)的 20 是经验值——太小(如 1)导致 critic 过拟合单步噪声;太大(如 100)则策略滞后,无法响应环境变化。务必配合buffer.Size >= 128防止冷启动阶段抽样偏差。


4. 避坑指南:MATLAB MARL 训练中 4 个高频翻车现场与解法

4.1 现象:训练初期 reward 曲线剧烈震荡,1000 步内出现 -100 → +50 的跳变

原因:稀疏主奖励(collision/+50)主导学习信号,agent 学会“随机乱飞赌运气”,而非稳定趋近目标。密集奖励权重过低,无法提供梯度指引。
解决:立即启用reward shaping 缓冲期——前 5000 步,将distReduction * 2.0提升至* 8.0,speed^2 * 0.05降至* 0.01,让 agent 先学会“朝目标走”再学“优雅地走”。待平均 episode reward > -20 后,逐步回调至原值。

4.2 现象:多个 agent 持续在目标点附近盘旋,拒绝降落,episode 不终止

原因:distToTarget < 1.0的判定过于理想化。实际中 GPS/视觉定位有 0.3~0.8m 误差,agent 到达 1.2m 时因观测噪声误判“未到达”,反复微调导致振荡。
解决:改用双阈值终止机制:

  • landingRadius = 1.0:用于触发 +50 奖励;
  • holdTime = 3:agent 进入 landingRadius 后,需连续 3 步保持distToTarget < 1.5才判定任务完成。
    在computeReward中增加计数器agents(i).inLandingZoneSteps,避免瞬时噪声触发。

4.3 现象:训练 5 小时后 reward 归零,但 agent 完全静止,不执行任何动作

原因:OU(Ornstein-Uhlenbeck)探索噪声衰减过快。MATLAB 默认NoiseOptions.VarianceDecayRate = 0.9999,2000 步后噪声方差趋近于 0,agent 陷入局部最优不动。
解决:重写getAction函数,将噪声衰减与 episode 关联而非 step:

noiseVar = initialVar * (0.995 ^ ep); % 每 episode 衰减 0.5%,500ep 后剩 8% action = evaluate(actorNet, obsVec) + noiseVar * randn(size(action));

确保 agent 在整个训练周期保持适度探索。

4.4 现象:添加第 4 架无人机后,训练完全崩溃,reward 从 -30 直落 -90

原因:otherBranch输入维度从6*2=12暴增至6*3=18,但 critic 网络结构未重置,导致fullyConnectedLayer权重维度不匹配,梯度计算错误。
解决:绝对禁止动态修改 agent 数量后复用旧网络。每次变更numAgents,必须:
① 清空 replay buffer;
② 重建所有 agent 的 critic 网络(调用createCriticNetwork(numAgents));
③ 重置 actor 网络(可保留预训练权重,但需resetNetworkParameters);
④ 从头开始训练。这是 MARL 的固有代价,没有取巧方案。


5. 从仿真到实机:MATLAB 模型导出与硬件在环(HIL)验证技巧

5.1 将训练好的 actor 网络导出为 C++ 可调用库(无需 MATLAB Runtime)

MATLAB 的codegen对深度学习网络支持有限,但我们不需要导出整个训练流程,只需部署前向推理(inference)。正确路径是:

  1. 用exportONNXNetwork将 actor 网络导出为 ONNX 格式;
  2. 在 C++ 项目中集成 ONNX Runtime(轻量,<2MB);
  3. 将encodeAgentObservation的 MATLAB 逻辑用 C++ 重写(重点是体素查表与相对位置计算)。
% 在 MATLAB 中导出 actorNet = agent{1}.Actor.Network; % 获取第一个 agent 的 actor 网络 exportONNXNetwork(actorNet, 'drone_actor.onnx', 'InputNames', {'observation'});

关键细节:'InputNames'必须与 C++ 中Ort::Session的输入名严格一致;导出前确认网络中无customLayer(如自定义 attention),否则 ONNX 不支持。我们项目中所有层均为fullyConnectedLayer/reluLayer,100% 兼容。

5.2 硬件在环(HIL)测试:用真实飞控数据喂养仿真环境

纯 MATLAB 仿真无法暴露真实传感器延迟与控制链路抖动。我们采用HIL 注入模式:

  • 在真实无人机飞控(如 Pixhawk)上运行mavlink节点,实时广播LOCAL_POSITION_NED和ATTITUDE;
  • MATLAB 仿真环境不再生成虚拟状态,而是订阅真实 mavlink 流,用真实位置替代agents(i).pos;
  • 同时,将 MATLAB 计算出的actions(i,:)通过SET_ATTITUDE_TARGET指令发回飞控,形成闭环。
% MATLAB HIL 主循环片段 mav = mavlink('udp', 'localhost', 14550); % 连接本地 SITL 或真机 while ~isDone % 1. 从 mavlink 读取真实状态 msg = receiveMessage(mav, 'LOCAL_POSITION_NED', 0.1); % 100ms 超时 if ~isempty(msg) for i = 1:numAgents agents(i).pos = [msg.x, msg.y, msg.z]; % 简化:单机映射 agents(i).vel = [msg.vx, msg.vy, msg.vz]; end end % 2. 执行 encodeObservation + getAction(同仿真) actions = zeros(numAgents, 4); for i = 1:numAgents obsVec = encodeAgentObservation(i, agents, voxelGrid, 0.5, 15, 5); actions(i,:) = getAction(agent{i}, obsVec, 0); % HIL 模式关闭探索噪声 end % 3. 将动作发回飞控(需转换为 mavlink 消息) sendAttitudeTarget(mav, actions(1,:)); % 示例:仅控制第一架 end

实操提示:HIL 测试必须开启receiveMessage的超时(如 0.1s),否则网络抖动会导致 MATLAB 卡死;sendAttitudeTarget需按 PX4 官方文档构造mavlink_message_t,重点设置type_mask(禁用位置控制,仅启用姿态控制)和q(四元数)。我们封装了mavlink_helper.m函数库,避免重复造轮子。

5.3 评估指标不止于 reward:定义 5 个可测量的工程验收项

学术论文看 reward 曲线,工程落地看硬指标。我们在每次训练后自动计算:

指标计算方式合格线说明
任务完成率成功到达目标的 episode 数 / 总 episode 数≥ 92%排除 crash/timeout 的干扰
平均路径长度比实际飞行路径长 / A* 规划最短路径长≤ 1.35衡量避障效率,>1.5 视为冗余绕行
多机最小间距所有时刻所有两机间距离的最小值≥ 3.0m防碰撞安全裕度,低于 2.5m 需告警
通信负载每秒广播的意图消息字节数≤ 120B适配 915MHz LoRa 模块带宽
推理延迟encodeObservation + getAction平均耗时≤ 15ms保证 30Hz 控制频率
% 训练后自动评估函数节选 function metrics = evaluateEpisode(agentsLog, targets) % agentsLog: cell array, each {pos_history, action_history, reward_history} metrics.taskCompletion = mean(cellfun(@(x) any(x{3}>=45), agentsLog)); % +50 reward 触发 % 计算路径长度比(以第一个 agent 为例) pathLen = sum(sqrt(sum(diff(agentsLog{1}{1},1).^2,2))); astarLen = norm(agentsLog{1}{1}(1,:) - targets(agentsLog{1}.targetID,:)); metrics.pathRatio = pathLen / (astarLen + 1e-6); % 计算最小间距(所有时刻所有两机组合) minDist = Inf; for t = 1:size(agentsLog{1}{1},1) for i = 1:numAgents for j = i+1:numAgents d = norm(agentsLog{i}{1}(t,:) - agentsLog{j}{1}(t,:)); minDist = min(minDist, d); end end end metrics.minSpacing = minDist; end

为什么这比 reward 更重要:reward是训练信号,metrics是交付物。某次训练 reward 达到 -5,但minSpacing=1.8m,意味着模型在拿安全换分数,必须废弃。我们坚持“metrics 不达标,reward 再高也不上线”的铁律。在某高校课程设计中,学生曾因pathRatio=1.8被要求重构状态编码——把体素栅格从 31×31 改为 41×41,代价是训练时间+40%,但最终达标。


我带过的模拟项目 X 中,团队曾用这套 MATLAB MARL 框架在 3 天内跑通 4 机协同投递 demo,关键不是模型多深,而是把“体素查表怎么写不卡”“意图消息传什么不传什么”“reward 什么时候该狠什么时候该软”这些细节钉死在代码里。后来他们把encodeAgentObservation函数移植到树莓派上,用 OpenCV 替代体素栅格做视觉障碍检测,思路一脉相承。真正的工程能力,不在调参多炫,而在知道哪一行代码改了会让整片天空变色。希望帮到你。

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

RELATED READING

延伸阅读

更多一线实战笔记与深度复盘,助您持续精进