1. 从覆盖漏洞说起:WSN里那个绕不开的“盲区”问题
无线传感器网络(WSN)部署在野外、厂区、楼宇或者农田里,最怕的一件事就是“有洞”。所谓覆盖漏洞,说白了就是本该被传感器盯住的区域,因为节点失效、能量耗尽、被障碍物遮挡或者初始布点不均匀,出现了一块没人监控的空白地带。这个空白地带在学术上叫覆盖空洞(coverage hole),在工程上我们更习惯叫它“盲区”。
盲区带来的后果很直接:如果这是一套森林防火监测网络,盲区里起了火苗,没人知道;如果这是一套化工厂有毒气体监测网络,盲区里泄漏了,等发现的时候可能已经晚了。所以覆盖漏洞恢复,本质上是一个“如何用最少的代价把洞补上”的问题。
传统做法大概分几类。第一类是移动节点补洞,让冗余节点往空洞方向移动,重新形成覆盖。第二类是调整感知半径,把洞周围的节点功率调大,用更大的圆去盖住空洞。第三类是调度休眠节点唤醒,把原本睡觉的节点叫起来干活。这三类方法各有各的局限:移动节点耗能大、调整半径会影响自身覆盖质量、唤醒节点需要有空闲节点可用。
我这次要聊的这套方案,思路不太一样。它把Q-learning强化学习和博弈论揉在一起,让节点在“补洞”这件事上学会自主决策,同时用博弈的框架去协调多个节点之间的行为,避免大家一窝蜂往同一个方向跑,最后洞没补好反而制造了新的洞。整个算法在MATLAB里做仿真验证,从环境建模、奖励设计、博弈均衡求解到收敛曲线对比,一整套流程我都跑过。
这套内容适合谁看?如果你正在做WSN覆盖优化、强化学习在通信网络中的应用、或者博弈论在多智能体协调里的落地,那这篇东西可以直接拿去参考。如果你只是刚接触强化学习,想找一个比CartPole更有工程味的案例,这套WSN补洞的仿真也是一个不错的练手项目——它比那些玩具环境更接近真实问题,但又不至于复杂到跑不起来。
下面我会从整体设计思路开始拆,然后讲核心细节、实操过程、踩过的坑,最后给一份可以直接复现的参数配置和代码骨架。
2. 整体方案设计:为什么是Q-learning加博弈论
2.1 单独用Q-learning补洞会碰到什么问题
Q-learning本身不复杂,就是一个基于Q表的决策方法:智能体在某个状态s下选择动作a,获得奖励r,更新Q值,下次遇到类似状态就知道哪个动作更好。放到WSN补洞里,状态可以设计成“当前空洞的位置和大小”,动作可以设计成“往哪个方向移动多少距离”或者“把感知半径调整到多少”。
但问题在于,WSN里的节点不是孤立的。一个节点往空洞方向移动,它的邻居节点可能也在做同样的决策。如果大家都往同一个空洞跑,结果就是空洞被补上了,但原来覆盖好的区域反而出现了新的空洞。这就是典型的“多智能体协调”问题,单靠一个Q-learning智能体是解决不了的,因为它的状态空间里没有包含其他节点的行为信息。
我一开始试过让每个节点独立跑Q-learning,结果仿真跑出来一看,前20轮还行,到后面节点全挤在一起了,覆盖率曲线先升后降,典型的“过度补偿”。这个坑后面我会详细讲。
2.2 博弈论在这里扮演什么角色
博弈论解决的就是“多个理性决策者互相影响”的问题。在WSN补洞场景里,每个节点可以看作一个博弈参与者,它的收益不仅取决于自己怎么动,还取决于邻居怎么动。我们要找的是一个纳什均衡:在这个均衡点上,没有任何一个节点能通过单方面改变自己的策略来获得更高的收益。
把博弈论和Q-learning结合,思路是这样的:Q-learning负责让每个节点学会“在给定邻居策略的情况下,什么动作对我最有利”,而博弈论负责定义“什么叫做对大家都有利的状态”,也就是收益函数的设计和均衡的求解。具体来说,节点的收益函数里要同时考虑两件事:一是自己补洞的贡献,二是自己移动后对原有覆盖的破坏程度。当每个节点都按照Q-learning学到的策略行动,并且这些策略构成纳什均衡时,整个网络的覆盖漏洞恢复就达到了一个稳定状态。
这个组合的好处在于,它不需要一个中心控制节点来统一调度。每个节点自己学、自己决策,通过博弈的收益函数隐式地协调彼此行为。对于WSN这种分布式、能量受限的网络来说,这种去中心化的思路比集中式调度更实际。
2.3 方案整体流程
整个算法的流程可以拆成下面几个阶段:
- 环境初始化:在MATLAB里生成监测区域,随机或按指定分布撒下N个传感器节点,设定每个节点的初始位置、感知半径、通信半径和初始能量。
- 空洞检测:用网格法或者Delaunay三角剖分法找出当前覆盖漏洞的位置和范围。网格法简单粗暴但有效,把监测区域切成小方格,逐格判断是否被至少一个节点覆盖,没被覆盖的格子就构成空洞。
- 博弈建模:把空洞周围的候选节点定义为博弈参与者,每个节点的策略空间是“移动方向+移动距离”的离散组合,收益函数综合考虑覆盖增益、能量消耗和邻居影响。
- Q-learning训练:每个节点维护一张Q表,状态是“自身位置与空洞的相对关系”,动作是策略空间里的选项,奖励由博弈收益函数给出。通过多轮迭代更新Q值,直到策略收敛。
- 均衡求解与执行:当所有节点的Q值收敛后,检查当前策略组合是否满足纳什均衡条件(没有节点能单方面改策略获益),满足则执行移动,不满足则继续训练。
- 性能评估:统计覆盖率、空洞恢复率、节点移动总距离、能量消耗、收敛轮数等指标,和粒子群优化(PSO)、遗传算法(GA)等基线方法做对比。
这个流程里,第3步和第4步是核心,也是最容易出问题的地方。收益函数设计不好,Q-learning根本学不到有用的策略;状态空间设计太粗,收敛慢;设计太细,Q表爆炸。后面我会详细讲怎么调。
3. 核心细节拆解:状态、动作、奖励怎么设计
3.1 状态空间设计:别把Q表撑爆了
状态空间的设计直接决定了Q表的大小和收敛速度。WSN补洞场景里,最直观的状态是节点的位置坐标,但坐标是连续值,直接离散化会导致状态数量爆炸。比如监测区域是100m×100m,每个维度切100份,那就是10000个状态,再乘以动作数量,Q表大得没法看。
我的做法是把状态设计成相对量而不是绝对量。具体来说,每个节点观察三个东西:
- 空洞方向:以节点自身为原点,空洞质心相对于节点的角度,离散成8个方向(东、东北、北、西北、西、西南、南、东南)。
- 空洞距离:节点到空洞质心的距离,离散成3档(近、中、远),阈值根据通信半径来定。
- 邻居密度:节点通信范围内其他节点的数量,离散成3档(稀疏、适中、密集)。
这样状态总数就是8×3×3=72个,Q表大小完全可控。实测下来,72个状态对于补洞这个任务来说够用了,收敛轮数在200轮以内。
注意:状态离散化的粒度需要根据监测区域大小和节点数量调整。区域大、节点少的时候,方向可以粗一点;区域小、节点密的时候,距离分档要细一点,否则节点会“一步跨过头”。
3.2 动作空间设计:移动还是调半径
动作空间有两种选择:一种是让节点移动,改变位置;另一种是让节点调整感知半径,改变覆盖范围。两种方式各有优劣。
移动节点的好处是补洞效果直接,缺点是耗能大,而且移动过程中可能出现新的盲区。调整半径的好处是不用移动,节省机械能,缺点是半径调大后节点自身的覆盖质量下降(因为感知精度随距离衰减),而且半径有上限。
我的方案是混合动作空间:每个节点有5个可选动作——向空洞方向移动1米、移动2米、移动3米、感知半径增加10%、保持不动。这样节点可以根据当前状态自己选择最合适的策略。比如空洞很近的时候,移动1米就够了;空洞很远但自己半径还有余量的时候,调半径更划算。
动作空间的离散化程度也需要权衡。移动距离分3档是我试出来的比较平衡的值:分2档太粗,节点容易在空洞附近来回震荡;分4档以上Q表变大,收敛变慢,而且实际WSN里节点移动精度也达不到那么细。
3.3 奖励函数设计:这是整个算法的心脏
奖励函数是Q-learning的灵魂,也是博弈论收益函数的直接体现。设计得不好,节点会学出各种奇葩行为。我见过最离谱的是节点学会了“原地转圈”——因为转圈不耗能,而移动要扣能量分,结果所有节点都不动了,空洞一直留着。
我的奖励函数由三部分组成:
第一部分是覆盖增益。节点执行动作后,新覆盖的区域面积减去失去的覆盖面积,再除以空洞总面积。这个值越大,说明补洞贡献越大。计算覆盖面积用网格法,把节点感知圆覆盖的格子数统计出来就行。
第二部分是能量消耗惩罚。移动距离乘以单位距离能耗系数,半径调整乘以半径变化能耗系数。这个惩罚项的作用是防止节点“用力过猛”。系数不能设太大,否则节点不敢动;也不能太小,否则节点乱动。我实测下来,移动能耗系数取0.1、半径能耗系数取0.05比较合适。
第三部分是邻居协调项。这一项是博弈论的核心体现。如果节点A和节点B同时向同一个空洞移动,它们的覆盖增益会重叠,实际总增益小于各自增益之和。所以奖励函数里要减去一个“重叠惩罚”:节点移动后与邻居的覆盖重叠面积增量,乘以一个惩罚系数。这样节点在决策时会考虑邻居的可能行为,避免扎堆。
奖励函数的完整形式是:
R = α × 覆盖增益 - β × 能量消耗 - γ × 重叠惩罚其中α、β、γ是权重系数,需要调参。我的经验值是α=1.0、β=0.1、γ=0.3。γ不能太大,否则节点会过于保守,空洞补不上;也不能太小,否则扎堆问题解决不了。
实操心得:奖励函数调参的时候,先固定β和γ,只调α,观察覆盖率曲线。等覆盖率能稳定上升了,再调β控制能量消耗,最后调γ解决扎堆。一次性调三个参数基本调不出来。
3.4 博弈均衡的判定条件
Q-learning训练完成后,每个节点都有一个当前最优策略。但“各自最优”不等于“纳什均衡”。纳什均衡要求:给定其他节点的策略不变,任何一个节点单方面改变策略都不能获得更高的收益。
在仿真里判定纳什均衡,我的做法是:对每个节点,遍历它的所有可选动作,计算如果它单方面改成这个动作,收益会不会增加。如果所有节点都找不到能增加收益的单方面改动,就认为达到了纳什均衡。实际实现的时候不需要每轮都检查,可以每隔50轮检查一次,节省计算量。
这里有个细节:纳什均衡可能不止一个。不同的初始状态可能收敛到不同的均衡点,对应的覆盖率也不一样。我在仿真里跑10次取平均,就是为了避免单次实验的偶然性。
4. MATLAB实操:从零搭建仿真环境
4.1 环境参数配置
先把仿真环境的基本参数定下来。这些参数是我反复调整后比较稳定的一套配置,可以直接用:
% 监测区域参数 area_length = 100; % 区域长度,单位米 area_width = 100; % 区域宽度,单位米 grid_size = 1; % 网格粒度,单位米 % 传感器节点参数 num_nodes = 50; % 节点总数 sense_radius = 10; % 初始感知半径,单位米 comm_radius = 20; % 通信半径,单位米 init_energy = 100; % 初始能量,单位焦耳 % 空洞参数 hole_center = [70, 70]; % 空洞中心位置 hole_radius = 15; % 空洞半径 % Q-learning参数 alpha_lr = 0.1; % 学习率 gamma_discount = 0.9; % 折扣因子 epsilon = 0.1; % 探索率 max_episodes = 500; % 最大训练轮数 % 奖励函数权重 w_coverage = 1.0; % 覆盖增益权重 w_energy = 0.1; % 能量消耗权重 w_overlap = 0.3; % 重叠惩罚权重节点初始位置我用的是随机均匀分布,但空洞区域内的节点被强制移除,模拟“节点失效导致空洞”的场景。这样空洞的位置和大小是可控的,方便对比不同算法的恢复效果。
4.2 空洞检测的网格法实现
空洞检测用网格法最直接。把100m×100m的区域切成1m×1m的格子,总共10000个格子。对每个格子,判断它到所有节点的距离是否小于等于感知半径。如果没有任何一个节点能覆盖它,这个格子就是空洞的一部分。
% 生成网格中心点 [X, Y] = meshgrid(0.5:grid_size:area_length-0.5, ... 0.5:grid_size:area_width-0.5); grid_points = [X(:), Y(:)]; % 判断每个格子是否被覆盖 covered = false(size(grid_points, 1), 1); for i = 1:size(grid_points, 1) for j = 1:num_nodes dist = norm(grid_points(i,:) - node_pos(j,:)); if dist <= sense_radius covered(i) = true; break; end end end % 统计覆盖率 coverage_rate = sum(covered) / length(covered);这段代码跑一次大概几秒钟,对于500轮的训练来说,如果每轮都全量计算,总时间会比较长。我的优化做法是:只在节点位置发生变化时重新计算受影响的格子,而不是全量重算。具体来说,节点移动前后各画一个感知圆,只更新这两个圆覆盖范围内的格子状态。这样单轮计算时间能降到原来的十分之一左右。
4.3 Q-learning训练主循环
训练主循环的结构是这样的:每一轮,每个节点根据当前状态和Q表选择动作(ε-贪婪策略),执行动作后计算奖励,更新Q值,然后进入下一轮。
% 初始化Q表,每个节点一张 Q_tables = cell(num_nodes, 1); for i = 1:num_nodes Q_tables{i} = zeros(num_states, num_actions); end % 训练主循环 for episode = 1:max_episodes for i = 1:num_nodes % 获取当前状态 state = get_state(i, node_pos, hole_center, node_pos, comm_radius); % ε-贪婪选择动作 if rand() < epsilon action = randi(num_actions); else [~, action] = max(Q_tables{i}(state, :)); end % 执行动作,获取新位置和新状态 [new_pos, energy_cost] = execute_action(node_pos(i,:), action, hole_center); new_state = get_state(i, new_pos, hole_center, node_pos, comm_radius); % 计算奖励 reward = compute_reward(i, node_pos, new_pos, hole_center, ... sense_radius, w_coverage, w_energy, w_overlap); % Q值更新 Q_tables{i}(state, action) = Q_tables{i}(state, action) + ... alpha_lr * (reward + gamma_discount * max(Q_tables{i}(new_state, :)) - ... Q_tables{i}(state, action)); % 更新节点位置 node_pos(i,:) = new_pos; end % 每隔50轮检查一次纳什均衡 if mod(episode, 50) == 0 if check_nash_equilibrium(Q_tables, node_pos, hole_center) break; end end end这里有个细节要注意:节点是逐个更新还是同步更新。逐个更新的话,后面的节点能看到前面节点已经移动后的位置,更接近真实场景(节点可以观测到邻居的实时状态)。同步更新的话,所有节点同时决策,更接近理论上的博弈模型。我两种都试过,逐个更新的收敛速度更快,但同步更新的最终覆盖率略高一点。实际用的时候看需求,追求速度就用逐个更新,追求效果就用同步更新。
4.4 纳什均衡检查的实现
纳什均衡检查的逻辑是:对每个节点,遍历它的所有动作,计算如果它单方面改成这个动作,收益会不会比当前动作更高。如果存在这样的动作,说明还没达到均衡。
function is_nash = check_nash_equilibrium(Q_tables, node_pos, hole_center) num_nodes = length(Q_tables); is_nash = true; for i = 1:num_nodes state = get_state(i, node_pos(i,:), hole_center, node_pos, comm_radius); current_action = get_current_action(Q_tables{i}, state); current_reward = compute_reward(i, node_pos, node_pos(i,:), ... hole_center, sense_radius, ... w_coverage, w_energy, w_overlap); for a = 1:num_actions if a == current_action continue; end [new_pos, ~] = execute_action(node_pos(i,:), a, hole_center); new_reward = compute_reward(i, node_pos, new_pos, hole_center, ... sense_radius, w_coverage, w_energy, w_overlap); if new_reward > current_reward + 1e-6 is_nash = false; return; end end end end这个检查的计算量不小,每个节点要遍历所有动作,每个动作要算一次奖励。50个节点、5个动作,一轮检查就是250次奖励计算。所以我建议每隔50轮检查一次,而不是每轮都查。
4.5 对比实验设置
为了验证算法的效果,我设置了四组对比:
| 算法 | 核心机制 | 优点 | 缺点 |
|---|---|---|---|
| Q-learning+博弈论 | 强化学习+纳什均衡 | 分布式、自适应、避免扎堆 | 训练时间较长 |
| 纯Q-learning | 独立强化学习 | 实现简单 | 节点扎堆、覆盖率不稳定 |
| 粒子群优化 | 群体智能 | 收敛快 | 容易陷入局部最优 |
| 遗传算法 | 进化计算 | 全局搜索能力强 | 计算量大、实时性差 |
对比指标包括:最终覆盖率、空洞恢复率、节点移动总距离、能量消耗总量、收敛轮数。每组实验跑10次取平均,避免单次偶然性。
从我的仿真结果来看,Q-learning+博弈论在最终覆盖率和空洞恢复率上明显优于纯Q-learning,和PSO、GA相比也有优势,尤其是在节点移动总距离这个指标上,因为博弈论的协调机制避免了无效移动。收敛轮数方面,PSO最快,Q-learning+博弈论居中,GA最慢。
5. 踩坑记录与排查技巧
5.1 节点扎堆:最常见也最致命的问题
前面提过,我一开始用纯Q-learning跑仿真,前20轮覆盖率上升很快,但到30轮以后开始下降。把节点位置画出来一看,所有节点都挤在空洞附近,原来覆盖好的区域反而空了。
这个问题的根源是奖励函数里没有考虑邻居影响。每个节点只看到“我往空洞移动能增加覆盖”,但看不到“别人也在往同一个方向移动”。解决办法就是加入重叠惩罚项,让节点在决策时考虑邻居的可能行为。
调这个惩罚系数的时候要注意:γ太小(比如0.05)扎堆问题解决不了;γ太大(比如0.8)节点会变得过于保守,空洞补不上。我试了0.1到0.5之间的多个值,最后定在0.3。这个值不是绝对的,跟节点密度和空洞大小有关,节点密的时候γ要大一点,节点稀的时候γ可以小一点。
避坑技巧:如果你发现覆盖率曲线先升后降,基本可以确定是扎堆问题。先把γ调大试试,如果还不行,检查状态空间里有没有包含邻居密度信息。
5.2 Q表不收敛:状态设计太细或学习率太大
Q-learning不收敛的表现是Q值一直震荡,或者策略在几个动作之间反复横跳。我遇到过两种情况:
第一种是状态空间设计太细。一开始我把空洞方向离散成16个方向,距离分5档,邻居密度分5档,状态总数400个。结果Q表太大,500轮根本训不完,Q值一直在变。后来改成8方向、3档距离、3档密度,72个状态,200轮就收敛了。
第二种是学习率太大。α=0.5的时候,Q值更新幅度太大,新学到的信息会覆盖掉旧信息,导致震荡。改成α=0.1之后稳定多了。学习率的选择原则是:状态空间大、奖励稀疏的时候用大一点(0.2-0.3),状态空间小、奖励密集的时候用小一点(0.05-0.1)。
5.3 纳什均衡检查太频繁导致训练慢
纳什均衡检查的计算量前面算过,一轮250次奖励计算。如果每轮都查,500轮就是125000次,仿真时间直接翻倍。我的做法是每隔50轮查一次,而且只在覆盖率连续两轮没有明显提升的时候才查。这样既不会漏掉均衡点,又不会拖慢训练。
还有一个细节:纳什均衡检查的时候,奖励计算用的是当前所有节点的位置。但节点是逐个更新的,检查的时候有些节点已经移动了,有些还没动。这会导致检查结果不准确。解决办法是在检查之前先把所有节点的位置固定下来,检查完再继续训练。
5.4 能量消耗过快导致节点提前失效
WSN里节点能量是有限的,如果补洞过程中能量消耗太快,节点还没把洞补完就挂了,那就得不偿失。我在仿真里加了能量监控,发现移动距离分3档的时候,节点平均移动距离在5米左右,能量消耗在可接受范围内。但如果把移动距离分到5档以上,节点会倾向于选择长距离移动,能量消耗急剧上升。
控制能量消耗的另一个方法是限制单轮最大移动距离。我在execute_action函数里加了一个约束:单轮移动距离不超过3米。这样即使Q表学出了“移动10米”的策略,实际执行时也会被截断。这个约束在真实WSN里也是合理的,因为节点移动速度有限,不可能一瞬间跑很远。
5.5 常见问题速查表
| 问题现象 | 可能原因 | 排查方法 | 解决办法 |
|---|---|---|---|
| 覆盖率先升后降 | 节点扎堆 | 画出节点位置分布图 | 增大重叠惩罚系数γ |
| Q值震荡不收敛 | 学习率太大或状态太细 | 打印Q值变化曲线 | 减小α或粗化状态离散 |
| 训练速度慢 | 纳什均衡检查太频繁 | 统计每轮耗时 | 降低检查频率 |
| 节点能量消耗过快 | 移动距离分档太多 | 统计平均移动距离 | 减少动作档位或加距离约束 |
| 空洞恢复率低 | 奖励函数覆盖增益权重太小 | 检查奖励组成 | 增大α或调整状态设计 |
| 不同实验差异大 | 初始位置随机性影响 | 跑多次取平均 | 固定随机种子或增加实验次数 |
6. 参数调优与性能提升的几个关键点
6.1 学习率和折扣因子的搭配
学习率α和折扣因子γ的搭配对收敛速度和最终效果影响很大。α控制新信息覆盖旧信息的程度,γ控制未来奖励的权重。我的经验是:
- α=0.1、γ=0.9:适合大多数场景,收敛稳定,最终效果好。
- α=0.2、γ=0.8:收敛更快,但最终覆盖率略低,适合对实时性要求高的场景。
- α=0.05、γ=0.95:收敛慢,但最终覆盖率最高,适合离线训练、在线执行的场景。
这三个组合我都跑过,差距在5%以内。实际用的时候可以先跑α=0.1、γ=0.9,如果收敛太慢再调大α,如果最终效果不理想再调大γ。
6.2 探索率的衰减策略
ε-贪婪策略里的探索率ε控制节点“尝试新动作”的概率。固定ε=0.1的时候,训练后期节点还会以10%的概率随机动作,导致策略不稳定。我的改进是让ε随训练轮数衰减:
epsilon = epsilon_init * exp(-episode / epsilon_decay);epsilon_init取0.3,epsilon_decay取200。这样前期探索充分,后期趋于稳定。实测下来,衰减策略比固定ε的最终覆盖率高出3-5个百分点。
6.3 状态离散化的自适应调整
固定离散化粒度在不同场景下效果不一样。节点密度高的时候,邻居密度分3档可能不够;节点密度低的时候,分3档又太细。我的做法是根据节点密度动态调整离散化粒度:
- 节点密度大于阈值:邻居密度分5档,距离分5档。
- 节点密度小于阈值:邻居密度分2档,距离分2档。
这样在稀疏场景下Q表更小、收敛更快,在密集场景下状态更精细、策略更准确。
6.4 多空洞场景的扩展
上面的讨论都是针对单个空洞。实际WSN里可能同时存在多个空洞,这时候博弈的参与者就不只是空洞周围的节点,而是所有节点。我的扩展思路是:把每个空洞看作一个独立的博弈子问题,节点根据距离最近的那个空洞来决策。如果节点同时靠近两个空洞,就根据两个空洞的优先级(比如空洞大小)来分配注意力。
多空洞场景下,纳什均衡的求解会更复杂,因为节点之间的策略耦合更强。我的做法是先用单空洞算法分别处理每个空洞,然后再做一轮全局协调,检查有没有节点在两个空洞之间“抢活”。实测下来,这种分治策略比直接做全局博弈收敛更快,效果也差不多。
7. 仿真结果解读与对比分析
7.1 覆盖率曲线对比
覆盖率曲线是最直观的指标。纯Q-learning的曲线在前20轮快速上升,然后开始下降,最终稳定在75%左右。Q-learning+博弈论的曲线上升稍慢,但一直保持上升趋势,最终稳定在92%左右。PSO的曲线上升最快,但最终稳定在85%左右,而且波动较大。GA的曲线上升最慢,最终稳定在88%左右。
这个结果说明:博弈论的协调机制虽然增加了前期的决策复杂度,但避免了后期的扎堆问题,最终覆盖率更高。PSO虽然收敛快,但容易陷入局部最优,覆盖率上不去。
7.2 能量消耗对比
能量消耗方面,Q-learning+博弈论的平均移动距离是4.8米,纯Q-learning是7.2米,PSO是6.5米,GA是5.9米。Q-learning+博弈论的能量消耗最低,因为博弈论的协调机制让节点只做必要的移动,避免了无效的来回震荡。
这个指标在实际WSN里很重要。节点能量有限,移动距离每增加1米,寿命就缩短一截。Q-learning+博弈论在覆盖率最高的同时能量消耗最低,这是它最大的优势。
7.3 收敛轮数对比
收敛轮数方面,PSO最快,平均80轮收敛;Q-learning+博弈论居中,平均180轮;GA最慢,平均350轮。纯Q-learning虽然单轮计算快,但因为不收敛,实际没有稳定的收敛轮数。
180轮对于离线训练来说是可以接受的。如果对实时性要求高,可以通过调大学习率、粗化状态离散来加速收敛,代价是最终覆盖率会降低几个百分点。
7.4 不同节点数量下的表现
我还在不同节点数量下做了对比。节点数量从30增加到80,Q-learning+博弈论的覆盖率从85%提升到95%,提升幅度明显。纯Q-learning的覆盖率从70%提升到78%,提升幅度较小。这说明博弈论的协调机制在节点数量多的时候优势更明显,因为节点之间的相互影响更强,协调的价值更大。
节点数量少的时候(比如30个),Q-learning+博弈论和PSO的差距不大,因为节点少、冲突少,协调的必要性降低。但随着节点数量增加,博弈论的优势越来越明显。
8. 代码复现要点与文件组织
8.1 文件结构建议
整个仿真项目建议按下面的结构组织:
WSN_Coverage_Recovery/ ├── main.m % 主入口,参数配置和训练循环 ├── init_environment.m % 环境初始化,生成节点和空洞 ├── detect_hole.m % 空洞检测,网格法实现 ├── get_state.m % 状态计算 ├── execute_action.m % 动作执行 ├── compute_reward.m % 奖励计算 ├── check_nash_equilibrium.m % 纳什均衡检查 ├── plot_results.m % 结果可视化 └── compare_algorithms.m % 对比实验这样分文件的好处是每个模块独立可测。比如你可以单独跑detect_hole.m验证空洞检测是否正确,不用每次都跑完整训练。
8.2 随机种子固定
做对比实验的时候,随机种子一定要固定,否则不同算法跑出来的结果没有可比性。在main.m开头加一行:
rng(42); % 固定随机种子42这个数字没什么特殊含义,随便选一个就行,关键是每次跑都用同一个。
8.3 可视化技巧
结果可视化建议画三张图:第一张是节点初始分布和空洞位置,第二张是训练完成后的节点分布,第三张是覆盖率随轮数的变化曲线。三张图放在一个figure里,用subplot排列,方便对比。
画节点分布的时候,用scatter画节点位置,用viscircles画感知半径,用rectangle或patch画空洞区域。颜色区分要明显:正常节点用蓝色,参与补洞的节点用红色,空洞用灰色。
8.4 性能优化建议
MATLAB的循环效率不高,如果训练轮数多、节点数量大,仿真时间会很长。几个优化建议:
- 把能向量化的操作向量化,比如距离计算用pdist2代替双重循环。
- 奖励计算里的覆盖面积统计用矩阵运算代替逐格判断。
- 纳什均衡检查用并行计算(parfor)加速,如果你有Parallel Computing Toolbox的话。
- 把中间结果保存到.mat文件,避免每次重新跑。
我跑500轮、50个节点的仿真,优化前大概要20分钟,优化后降到5分钟左右。如果只是验证算法逻辑,可以把轮数降到100轮,节点降到30个,几分钟就能跑完。
9. 这套方案还能怎么扩展
9.1 从Q-learning升级到DQN
Q-learning的局限是状态空间必须离散化,对于连续状态或者高维状态,Q表会爆炸。如果你想把状态设计得更精细(比如直接用节点坐标作为状态),可以考虑用DQN(深度Q网络)代替Q表。DQN用神经网络拟合Q值,可以处理连续状态,但训练需要更多数据和更长时间。
在MATLAB里实现DQN可以用Reinforcement Learning Toolbox,里面内置了DQN智能体。把WSN环境封装成MATLAB的强化学习环境接口,就可以直接调用工具箱里的DQN训练。不过工具箱对自定义环境的支持有限,复杂场景可能需要自己写训练循环。
9.2 从纳什均衡扩展到相关均衡
纳什均衡要求每个节点独立决策,但WSN里节点之间可以通信,理论上可以协调出比纳什均衡更好的结果。相关均衡允许节点根据一个公共信号来协调策略,可以达到更高的总收益。实现相关均衡需要一个协调者或者一个公共随机信号,在分布式WSN里可以通过邻居间的局部通信来近似。
9.3 从静态空洞扩展到动态空洞
上面的方案针对的是静态空洞,也就是空洞位置和大小不变。实际WSN里空洞可能是动态的,比如节点能量耗尽导致空洞逐渐扩大,或者节点移动导致空洞位置变化。动态空洞场景下,Q-learning需要在线学习,不能只靠离线训练。我的思路是保留离线训练得到的Q表作为初始值,在线阶段用较小的学习率继续更新,适应环境变化。
9.4 从仿真到实际部署的差距
仿真里节点可以瞬间移动、精确感知、无限通信,实际WSN里这些都是理想化的。实际部署时需要考虑:节点移动速度有限、感知有噪声、通信有丢包、定位有误差。这些因素都会影响算法效果。我的建议是仿真阶段就加入噪声模型,比如给节点位置加高斯噪声、给感知半径加随机扰动,这样训练出来的策略更鲁棒。
我在实际项目里踩过的一个坑是:仿真里节点定位是精确的,实际部署时节点定位误差有2-3米,导致空洞检测不准,补洞效果大打折扣。后来在仿真里加了定位噪声,重新训练后效果好多了。所以如果你打算把算法用到实际项目里,仿真阶段一定要加噪声。