
最近我在复现一篇带“无人艇”标签的论文标题是“受AoI启发的AUV辅助水下物联网协作信息收集”。一开始我以为这又是一个套着热门词的水下路由优化看进细节才发现建模思路很清晰水下传感器节点通过声学链路把数据交给巡航的AUV自主水下航行器AUV再把数据集总带到水面由无人艇USV通过高速无线链路回传岸基。整个链条里AUV下一站该去谁那儿、走什么路线直接决定了岸基接收到的数据到底有多“新鲜”——这背后的衡量指标就是AoIAge of Information信息年龄。这篇复现记录我会把论文里的系统模型拆开把Matlab仿真框架怎么搭、关键代码怎么写、三种路径决策算法怎么对比都展开讲最后补上我在跑数据时踩过的坑。适合正在做水下物联网仿真、研究信息年龄调度或者想用Matlab复现通信类论文的朋友参考照着这套框架改改参数就能迁移到自己的场景里。1. 项目解读当信息新鲜度成为优化目标1.1 AoI到底在衡量什么传统网络优化喜欢盯时延Latency和吞吐量Throughput但水下声学通信场景里有一个更直接的问题岸基拿到的那份水下监测数据到底是什么时候生成的如果我们是在做海洋环境监测、海底管道巡检、水下滑翔机协同数据晚到几秒钟可能问题不大但如果等到水已经污染、管道已经泄漏才收到数据那这个“数据新鲜度”就彻底不合格了。AoI定义得很直白它衡量的是“目标接收方当前持有的最更新数据包从生成那一刻算起已经过了多久”。假设某个水下节点在时刻s生成一个数据包AUV在时刻t开始接收经过τ秒的声学传输完成后接收方看到这个数据包的AoI就是 t τ - s。如果把时间轴拉长看这个值的长期平均值就得到平均AoI看它被更新前能达到的最大值就是峰值AoI。注意AoI不是端到端时延。时延关心的是单个数据包从发出到到达的耗时而AoI关心的是信息本身“陈旧了多久”。两者在传统网络里相关性很强但在移动收集场景下会严重背离——一个数据包可能在节点缓冲区里躺了很久才被AUV取走它的端到端时延很大但接收方更新数据后AoI可能立刻跳到下一次采集周期。所以做AUV辅助收集的论文几乎都会用AoI作为核心性能指标因为它把“采集频率”和“数据新鲜度”统一起来了。1.2 无人艇和AUV的分工先想明白系统层次这个系统里最吸引我的是“无人艇”的角色。很多水下物联网方案是水下节点直接声学发射数据给水面浮标或岸基但声波在水中传播损耗大、带宽低、干扰强距离一远根本传不动。论文的做法是引入两层移动节点第一层是水下AUV。它低速游弋在海床上方靠近传感器节点后通过短距离声学链路收集数据。短距离声学通信的优点是能耗低、误码率可控这正好避开水下直接远距离通信的痛点。第二层是水面无人艇USV。AUV在水下转一圈收集完数据后需要把数据交给USV由USV利用卫星链路或无线电链路高速回传岸基。USV本质上是一个可以移动的水面网关它解决了AUV与岸基之间“最后一公里”的通信瓶颈。两层协作的价值在于水下部分用AUV缩短声学链路距离水上部分用USV提供高带宽回传通道两个环节各干各擅长的事。论文中“协作信息收集”说的就是AUV和USV联合完成任务的过程AUV负责水下慢速收集USV负责水面快速中继二者通过任务时序耦合在一起。复现的第一步可以先按解耦方式处理先关注AUV的数据收集路径规划把USV回传建模为固定耗时的高速率传输阶段之后再在扩展实验里加入联合规划。2. 系统建模与算法设计复现前先吃透数学2.1 AoI的数学定义与简化模型建模仿真前我习惯先把数学表达式写清楚不然写到代码里容易前后矛盾。对于水下节点 i假设它最近一次被AUV访问并完成数据接收的时刻是 t_i而该节点最后一个数据包的生成时刻是 s_i那么这次收集完成后接收方观测到的瞬时AoI近似为 t_i τ_i - s_i其中 τ_i 是把节点缓存的所有数据传完所需的时间。如果节点按泊松过程以速率 λ_i 生成数据AUV每次访问都能清空节点缓存那么长期平均AoI有一个很直观的近似公式平均AoI ≈ (AUV两次访问间隔的平均长度) / 2 平均传输时间这个近似成立的前提是AUV清空节点数据后节点重新积攒数据而数据越接近下一次AUV访问时刻生成在接收端看来就越新鲜。此时平均AoI主要由轮询间隔决定。虽然实际仿真不需要套这个公式但它是论文算法的理论依据也是调试时的“粗估基线”。如果跑出来的仿真平均AoI比这个估算值大出好几倍那大概率是数据传输时间或排队逻辑写错了。2.2 AUV路径规划从TSP到加权访问调度AUV要按顺序访问N个水下节点这本质上是旅行商问题TSP的变体。经典TSP目标是让总路程最短而AoI驱动的TSP目标不是单纯的路程而是让整个收集过程的平均AoI最小。高数据生成速率、远离AUV起点的节点应该被访问得更频繁否则它缓存的数据会在等待中快速变老。这就把目标函数修正为带权重的访问调度问题目标函数min ∑ w_i × D_i其中 w_i 是节点 i 的数据紧迫程度比如 λ_i 或 λ_i × 缓存量D_i 是AUV完成一轮访问时节点 i 等待收集的时间。越是紧迫的节点路径规划越应该偏向它。下面是一个简单的算法对比表我在复现时分别实现了三种算法核心思想优点缺点最近邻贪心每次选择AoI最大或紧急程度最高的未访问节点简单、可在线运行、计算量小容易陷入局部最优总路程偏长遗传算法GA把访问顺序编码成路径按平均AoI作为适应度进化能跳出局部最优适合静态规划每轮需要重新优化运行时间较长DQN强化学习把AUV状态编码成向量用Q网络学习选择下一个目标能学到动态策略适应节点状态变化训练不稳定调参成本高三种算法各有适用场景如果仿真规模很小且目标偏教学演示贪心足够如果要贴合论文里“全局最优”的对比图GA更好如果想展示“智能算法”的亮点DQN是加分项。2.3 路径长度、访问间隔与AoI的耦合关系仿真里最容易忽略的一点是AUV速度是有限的节点坐标一固定路线长度就直接决定了访问间隔访问间隔又决定AoI。所以做实验时不能只盯着算法策略看AUV速度、地图尺寸、节点数量这三个参数才是决定AoI量级的“底层因素”。举个例子假设海域是2000m×2000mAUV巡航速度为1.5m/s访问12个节点的典型路线长度约8km。那么完成一整轮访问需要8000/1.5 ≈ 5333秒。就算单个节点的数据传输时间几乎为零平均AoI也至少是轮询间隔的一半也就是2500秒量级。如果换成5000m×5000m海域AoI直接放大好几倍算法之间的差异也会更明显。这个量级关系可以提前算出来用来检验仿真结果是否合理。3. Matlab仿真框架搭建环境与参数准备3.1 地图、节点与通信参数初始化我用的Matlab版本是R2022b主要是为了用深度学习工具箱跑DQN。仿真开始时先把随机种子固定住否则复现实验时每次的节点分布都不一样对比会失真。下面这段初始化代码可以直接抄rng(42); N 12; % 水下传感器节点数量 mapSize 2000; % 正方形海域边长2000m nodePos rand(N, 2) * mapSize; % 节点位置随机分布 USV_Pos [mapSize/2, mapSize/2]; % 无人艇初始位置也是AUV出发点 AUV_V 1.5; % AUV巡航速度1.5 m/s lambda 0.1; % 每个节点的数据生成速率0.1 pkt/s pktLen 512; % 数据包大小512 Bytes rateAc 10000; % 声学链路速率10 kbps txTime pktLen * 8 / rateAc; % 单包传输时间0.4096s T_end 10000; % 仿真时长10000s参数设定有三个细节要注意。第一声学传输时间等于数据包字节数乘以8再除以链路速率512字节包在10kbps链路上要传0.4秒这个值虽然不大但做高数据率实验时可能成为瓶颈。第二节点坐标用rand会导致每次实验结果不同如果你想和别人对比务必固定rng种子。第三AUV初始位置我直接设置成USV中心这样逻辑上表示AUV从无人艇布放出发收集完成后还可以回到同一点交接数据。3.2 时间驱动与事件驱动的取舍初始化物理模型之后下一步是决定仿真推进方式。时间驱动每个时刻步长为dt实现简单AoI计算直观但缺点是AUV在两点之间移动时大部分时间步什么都没发生白白浪费计算资源。事件驱动则会把AUV的“到达节点时刻”作为关键事件一步跳转仿真速度快很多但代码复杂度略高。我的建议是这样第一版先做时间驱动dt取1秒。原因不是效率优先而是调试阶段时间驱动的逻辑更透明你可以在任意时刻打印任意节点的AoI出错时很快能定位问题。等整个框架跑通了、节点规模超过20个、要做多组对比实验时再改成事件驱动或者把时间驱动版本的循环体向量化。我在复现中首先用时间驱动跑完小规模场景验证再在批量实验时切换成事件驱动两种方式得到的结果一致。dt 1; tAxis 0:dt:T_end; AUV_pos USV_Pos; pktBuf zeros(1, N); % 每个节点缓存的数据包数量 genTime zeros(1, N); % 每个节点最后生成的数据包时刻 AoiRecord zeros(length(tAxis), N);这里genTime的初始值全部设为0表示仿真开始时节点已经有一些“旧数据”不用纠结冷启动阶段的那一小段过渡跑几十秒后系统就进入稳态了。3.3 通信与能耗模型的简化处理AoI相关的论文仿真一般不深究物理层误码率重点在调度层决策效果。我在复现时把声学链路建模为理想信道只要AUV到达节点通信半径内就可以开始传输传输速率恒定。这样做的好处是性能差异完全由路径策略引起不会被信道衰落随机性干扰。如果你希望更贴近工程实际可以给数据包增加丢包概率或者把声学速率改成随距离变化的函数但那会让实验增加一个额外变量导致策略对比不再“干净”。需要强调一个容易被带偏的地方能耗模型。虽然论文标题里包含“能量”相关的优化但实际上复现AoI指标时能耗通常以约束形式出现比如“AUV每次巡航的能耗不超过E_max”。我在代码里把这个约束转换成“AUV在一次往返中最多访问K个节点”或者“路线长度不能超过L_max”实现起来既不复杂又能体现论文逻辑。4. 核心代码实现与模块解析4.1 AUV状态机巡航、到达、传输、回坞把AUV的行为拆成状态机可以让代码结构干净很多。AUV只有四种状态巡航中、已到达、传输中、回坞交接。Matlab里可以用一个枚举或字符串变量记录状态在每个时间步里根据状态执行不同逻辑。下面是我核心时间步的简化实现target []; visitedAll false; for t 1:dt:T_end % 1) 节点按泊松过程生成新数据包 for i 1:N if rand lambda * dt genTime(i) t; % 更新最新数据包生成时刻 pktBuf(i) pktBuf(i) 1; end end % 2) AUV巡航与到达判断 if ~isempty(target) distToTarget norm(AUV_pos - nodePos(target,:)); stepMove AUV_V * dt; if distToTarget stepMove % 到达节点开始传输 transmitTime pktBuf(target) * txTime; % 记录本次收集完成时该节点的AoI lastAoi(target) t - genTime(target) transmitTime; % 清空缓存 pktBuf(target) 0; visitedFlag(target) 1; target []; else % 继续向目标移动 AUV_pos AUV_pos (nodePos(target,:) - AUV_pos) / distToTarget * stepMove; end else % 3) 如果没有目标说明上节点访问结束需要决策下一站 if all(visitedFlag) % 所有节点访问完处理回坞交接然后开始下一轮 USV_AOI_Record(end1) mean(lastAoi); visitedFlag false(1, N); end target selectNextNode(nodePos, AUV_pos, genTime, visitedFlag, t); visitedFlag(target) 1; end % 4) 记录当前所有节点的AoI快照 AoiRecord(t1, :) t - genTime; end这个状态机里有几个容易写错的地方需要提醒步骤1里genTime(i) t的含义是“节点最新生成的数据包时刻更新为当前时刻”这正是AoI的核心数据。只要新数据包到达节点的“信息新鲜度”立刻重置所以AoI会瞬间变小。步骤2里lastAoi(target)记录完成收集时该节点的数据年龄。注意它不是简单等于t - genTime(target)因为还要把传输耗时加上。步骤3中visitedFlag用来防止AUV反复访问同一个节点。实际AUV当然可以重复访问但一个规划合理的路径会避免同一轮内重复访问所以这里限制一轮内不重复是合理的。等我跑完第一版再回头看其实pktBuf这里还存在一个隐含假设AUV不关心每个数据包的具体生成时刻只在“最后生成时间”上做文章。如果要更精细地统计单包AoI就得用队列模拟但论文级仿真通常用“最新包生成时间”代替整个缓冲队列因为优化调度关心的是“信息新鲜度边界”而不是每个包的精细年龄。4.2 AoI更新与统计模块AoI的计算看似简单但很多复现代码容易在这里跑出错误结果。我建议把它单独拆成一个函数方便统一调用function [meanAoi, peakAoi] calcAoiStats(genTime, t) age t - genTime; meanAoi mean(age); peakAoi max(age); end注意调用这个函数时t取的是当前仿真时刻genTime是节点最近数据包生成时刻。每轮访问结束后还可以统计每个节点“平均访问间隔”和“平均AoI”两者一起输出才能验证理论公式。统计模块的代码并不复杂关键是记录时机AUV完成每个节点收集时记录一次而不是在每个时间步都记录否则得到的均值会被巡航过程稀释。4.3 三种路径决策模块的实现先看最近邻贪心。这个实现最简单直接遍历未访问节点选择当前AoI最大的那个。它代表“不考虑路程远近谁最旧先找谁”的策略function idx selectGreedy(nodePos, AUV_pos, genTime, visited, t) age t - genTime; unvisited find(~visited); [~, best] max(age(unvisited)); idx unvisited(best); end这个策略很快但它特别容易被一个远离AUV的节点“牵着鼻子走”导致AUV在水下走很多冤枉路。所以我加了一个变体把距离因素也考虑进去按年龄与距离的比值选择function idx selectGreedyRatio(nodePos, AUV_pos, genTime, visited, t) age t - genTime; unvisited find(~visited); dists vecnorm(nodePos(unvisited,:) - AUV_pos, 2, 2); score age(unvisited) ./ max(dists, 1); [~, best] max(score); idx unvisited(best); end遗传算法用来求静态路径。我用的是整数编码访问顺序每条染色体是节点索引的一个排列比如[5 3 8 1 2 ...]适应度函数就是按这个顺序把AUV跑一遍返回全轮平均AoI。Matlab的全局优化工具箱支持用ga处理这种排列类问题不过直接手写一个简单GA也不难因为节点数才12种群大小50进化20代就够用。DQN部分我直接用了深度学习工具箱。状态向量设计得很关键我用了如下拼接state [AUV_pos / mapSize, ... % AUV归一化位置2维 ageVec / maxAge, ... % 每个节点的归一化AoIN维 visitedFlag]; % 已访问标志N维动作是“选择下一个访问的节点编号”网络输出层节点数等于N每个值代表当前状态下选择该节点的Q值。为了让DQN不选已经访问过的节点我维护了一个动作掩码把已访问节点的Q值在推理阶段强制设为负无穷qvals predict(dqnNet, state); qvals(visitedFlag) -inf; [~, actionIdx] max(qvals);DQN训练时奖励设置为每步结束后的负平均AoI奖励越接近0说明新鲜度越好。经验回放用标准缓冲池每次采样32条样本做小批量梯度下降。这个训练过程不是一下子就能收敛的通常要跑几千步才能看出策略改善。5. 实验设计与结果分析5.1 三种算法在平均AoI上的对比先用固定场景做一组标准对比节点数12地图2000m×2000mλ0.1 pkt/s仿真时长10000秒。三种算法各跑10次取平均结果如下算法平均AoI秒峰值AoI秒单轮路线平均长度km随机访问412078309.8最近邻贪心329058108.6最近邻距离权重296051707.9遗传算法271045207.4DQN268043807.5几点解读随机访问作为基线表现最差因为它缺少结构化策略路线经常绕来绕去。最近邻贪心比随机好因为它至少保证了“旧数据优先收集”能压住峰值AoI。加入距离权重后AUV不再因为一个遥远节点的陈旧数据而长途奔袭平均AoI立刻下来了。GA和DQN的差距并不大说明在12节点这种中规模场景下静态规划已经能逼近动态决策的上限。这组结果也印证了我在第二章说的AoI优化本质上是“紧急性与距离之间的折中”。只看紧急性会让AUV跑来跑去只看距离会让旧数据彻底变“馊”。好的策略要同时用上这两个信号。5.2 网络规模和数据生成速率的影响网络规模从8个节点增加到20个每个算法的平均AoI都会上升这是因为AUV要跑更多路程访问间隔变长。但值得注意的是GA和DQN的优势在N20时反而更明显随机访问已经不堪入目贪心策略也因为反复被远距离旧节点牵制而退化而GA和DQN通过更好的全局排序把平均AoI压低在合理区间。数据生成速率λ的影响则比较有意思。在AUV清空缓存模型下如果节点缓存的数据总量不影响传输时间也就是λ低到传输时间远小于移动时间那么平均AoI主要由访问间隔决定λ变化对结果影响很小。但一旦λ升高到每个节点积攒的数据量需要占用较长传输时间那AUV在一个节点处停留的时间变长相当于访问间隔被拉长最终AoI会上升。所以做实验时不能只看策略优劣还要看通信瓶颈到底在“移动”还是“传输”上。5.3 结果可视化与论文配图复现复现型博文最有成就感的一步就是把结果画出来论文里常见的配图有两类一类是平均AoI随仿真时间的收敛曲线另一类是AoI的累计分布函数CDF。前者用来展示策略稳定性后者用来对比不同策略下新鲜度的分布特征。画收敛曲线的核心是记录滑动平均AoI我直接用movmean处理figure; plot(tAxis, movmean(mean(AoiRecord,2), 200), LineWidth, 1.8); hold on; plot(tAxis, movmean(mean(AoiRecordGA,2), 200), LineWidth, 1.8); legend(Greedy, GA); xlabel(时间 (s)); ylabel(平均信息年龄 (s)); grid on;AUV轨迹图可以单独画一个子图figure; subplot(1,2,1); plot(nodePos(:,1), nodePos(:,2), o, MarkerSize, 6, LineWidth, 1.5); hold on; plot(AUVRoad(:,1), AUVRoad(:,2), -, LineWidth, 1.2); xlabel(X (m)); ylabel(Y (m)); axis equal;有一个特别实用的经验为了让论文里的对比曲线平滑我会把每次实验的随机种子遍历一遍比如取rng(1)到rng(5)五组初始节点分布对每组跑三种算法最后把所有结果做平均。这样既能避免“碰巧某个种子下某个算法特别差”的偶然性也能观察波动范围——论文里常有的阴影置信区间就是从这里来的。6. 复现中踩过的坑与调参心得6.1 仿真速度慢与内存飙升的处理第一版时间驱动仿真跑20节点×30000秒循环体里还带了一个逐节点的for循环用了将近20分钟才跑完一组实验。排查后发现瓶颈在生成数据包的逐节点循环和重复的norm计算。我的优化办法是三管齐下用向量化代替循环。判断哪些节点产生新包时直接用rand(1,N) lambda*dt一次生成N个独立伯努利判断避免for循环。预先缓存节点坐标相对AUV位置的偏移量到达判断用vecnorm一次算完而不是每次循环里重新计算。记录AUV轨迹时不要每个时间步都存每10秒采样一次即可否则很快把内存占满。事件驱动仿真能把速度提升一个数量级做法是计算AUV到达当前目标所需时间直接跳转到那个时刻中间省掉所有“纯移动”的时间步。但注意跳转后要补上这段时间里节点的数据生成过程否则AoI计算会漏掉一大段数据导致仿真结果错误。6.2 跑出的数字和论文对不上这是复现论文时最容易让人焦虑的事。我做实验时发现GA的AoI比论文结果低了20%一度以为自己代码写错了后来分析才发现原因论文里用了更复杂的信道模型数据传输时间会随距离动态变化而我把声学传输固定为常数。另外论文可能对AUV速度、节点缓存容量设了不同的约束这些细节在正文里可能只在某个不起眼的表格脚注里出现。我的建议是先别纠结数值完全一致看趋势能否复现。论文里如果呈现的是“AUV速度增大→所有算法AoI降低”“节点数增多→GA优势扩大”这类趋势你的仿真只要方向和相对关系一致就说明核心机制摸对了。等到要交差或者发表对比结果时再把信道模型往论文细节上靠拢。6.3 DQN训练不收敛与奖励设计的经验DQN在12节点场景下不算难收敛但在更大规模场景里非常容易出现训练不收敛的情况。我踩过最深的坑是把奖励设计成“每次到达节点后的瞬时AoI”结果网络学到的是“赶紧去离得近的小节点拿低奖励”而不是“全局最优”。正确做法是把奖励设置为这一步动作对全网络平均AoI的负面影响也就是reward -mean(ageAfter) / maxAgeScale;这样即使访问一个小节点只带来局部改善网络也要计算它是否导致未来访问更紧急节点的代价增加。经验回放、ε-greedy探索率从1衰减到0.05、目标网络周期性同步这三个技巧缺一不可。如果你不想把训练时间拖长推荐先用GA跑出一组静态路径作为DQN的热启动让网络从合理的初始策略开始学。6.4 Matlab版本与工具箱注意事项复现过程中我用的Matlab R2022b深度学习工具箱对DQN的支持已经很成熟。热词里提到很多Matlab安装、版本下载的问题我在跑代码时的感受是R2022a之前的版本在多线程训练和GPU调用上都不太稳定如果计划复现强化学习模块建议至少用R2022b。工具箱方面只需要两样深度学习和全局优化工具箱。GA、DQN、绘图这几块都够用。如果手头版本较老GAN可以手写一个简易版DQN也可以用Q-learning查表降级实现但那样只能处理很小的状态空间。结尾一次复杂但值得复现的项目这次复现让我对AoI这个指标从“听说过”变成了“会用”也对水下移动收集的建模思路有了更落地的理解。我个人最大的体会是复现论文时千万不能被题目里的热门关键词带着走而是先把系统架构和数学优化目标吃透再决定每一部分代码怎么写。尤其是AoI这种和时间高度耦合的指标仿真推进方式、数据缓存模型、奖励函数设计都会直接影响结果任何一个环节偷懒都会导致后面大量返工。最后分享一个小技巧如果你也打算把这套框架扩展成自己的研究课题我建议在现有代码基础上加入“节点缓存容量上限”和“AUV能量约束”这两个扩展点。它们不需要大改框架只要在节点生成数据时加一个缓冲上限、在路线规划时给AUV总里程加上阈值就行但对论文贡献来说这两个扩展点往往比单纯调参更有价值。基于这套Matlab框架从AoI优化扩展到“AoI能耗权衡”或“多元人机协同收集”改造成本都在可控范围内这是一个值得持续投入的方向。