尧图网站设计 尧图网站设计YAOTU DESIGN
ARTICLE DETAIL

资讯详情

深耕网站设计与一线实操的经验洞察。

LEACH与LEACH-C协议详解:从能耗模型到Matlab仿真实现

LEACH与LEACH-C协议详解:从能耗模型到Matlab仿真实现 研一刚进实验室那会儿导师把一沓无线传感器网络的论文甩给我第一句话就是先把LEACH给我跑通。我当时连LEACH和LEACH-C的差别都说不清更别说Matlab仿真了。后来自己啃了代码、踩了一堆坑才真正理解这套经典分簇路由协议的价值。这篇博文就是想把LEACH和LEACH-C从原理到Matlab实现完整捋一遍适合正在做WSN方向毕设、科研仿真或者想快速复现经典算法对比实验的同学。内容不绕弯子直接讲清能耗模型、簇头选举逻辑、代码模块设计和仿真里最容易翻车的几个细节。1. 能耗模型读协议之前先看懂节点是怎么把电耗光的无线传感器网络的核心约束从来不是计算能力而是电池。节点部署以后基本没法换电池可传统网络协议根本不考虑能耗这是WSN路由协议和普通Ad Hoc协议最大的分水岭。LEACH之所以成为经典就是因为它把“能耗均衡”这个指标作为第一优先级。1.1 一阶无线电能耗模型绝大多数LEACH仿真都采用一阶无线电模型First Order Radio Model。发送方发射一个 l bit 的数据包到距离为 d 的接收方发射能耗分为两部分发射电路耗能l × E_elec功率放大器耗能取决于距离距离小于阈值 d0 时采用自由空间模型l × ε_fs × d²距离大于等于 d0 时采用多径衰落模型l × ε_mp × d⁴接收方收到这个数据包同样要消耗l × E_elec。簇头对簇内数据做融合时每比特还要额外消耗E_DA的能量。典型参数如下这也是很多论文和公开代码里通用的设置参数取值网络区域100 m × 100 m节点总数 N100基站位置(50, 175)初始能量 E00.5 J数据包长度4000 bit控制包长度100 bitE_elec50 nJ/bitε_fs10 pJ/(bit·m²)ε_mp0.0013 pJ/(bit·m⁴)E_DA5 nJ/bit簇头比例 P5%d0 的计算方式是d0 sqrt(ε_fs / ε_mp) sqrt(10 / 0.0013) ≈ 87.7 m这个阈值很重要距离小于87.7米发送能耗随距离平方增长距离超过87.7米能耗随距离四次方增长一下子涨得非常快。这也是为什么WSN节点不能动不动就“一杆子捅到基站”——距离一远发送成本极其昂贵。1.2 分簇为什么能省这么多能量拿一个最直接的例子算一下。100 m × 100 m 的网络基站在 (50, 175)网络最远端节点到基站直线距离大约130米左右。如果节点直接把4000 bit的数据包发给基站E_Tx 4000 × 50nJ 4000 × 0.0013pJ × 130⁴按130米估算功率放大耗能部分大约是4000 × 1.3e-15 × 2.8561e8 ≈ 1.48e-3 J再算上电路耗能0.2 mJ总能耗接近1.7 mJ。这个数字看着不大但初始能量只有0.5 J如果每轮都这么发几百轮就没了。再看分簇的情况。簇内普通节点到簇头的距离通常只有十几二十米小于d0发送功耗按平方算E_Tx 4000 × 50nJ 4000 × 10pJ × 20² ≈ 0.2e-3 0.016e-3 0.216 mJ每个节点先把数据发给近处簇头簇头聚合所有成员的数据后再统一发给基站。这样真正承担“远距离通信”的只有少数簇头节点其他节点只需要低功率短距离发送。这就是分簇能大量节省能耗的根本逻辑。2. LEACH的簇头选举机制为什么说它是“分布式抽签”LEACH的全称是Low Energy Adaptive Clustering Hierarchy它的运行是按“轮”来进行的。每一轮分成两个阶段成簇阶段Set-up Phase和稳定传输阶段Steady-state Phase。整个协议的精髓都藏在簇头选举那个阈值公式里。2.1 阈值公式里的门道LEACH中每个节点生成一个0到1之间的随机数如果这个随机数小于阈值 T(n)节点就成为本轮的簇头。T(n)的经典定义是T(n) P / (1 - P * (r mod round(1/P)))当 n 属于 G 集合 T(n) 0当 n 不属于 G 集合其中 P 是簇头比例通常取0.05也就是网络里约5%的节点当簇头r 是当前轮数G 是最近round(1/P)轮内没有当选过簇头的节点集合。这个公式的巧妙之处在于第一轮所有节点都在G集合里每个节点当选簇头的概率大约是P当选过簇头的节点会从G集合中移除在未来1/P轮20轮左右内不再参与选举从而保证簇头角色不会总被同一批节点霸占。随着轮数增加G集合不断缩小剩余节点的 T(n) 会越来越大直到所有节点都当过簇头G集合被重置。这本质上是个“负载均衡抽签”机制每个节点都有均等机会当簇头又不会连续当选。缺点是纯靠随机数决定没有考虑节点剩余能量也没有考虑节点空间分布可能选出来的簇头扎堆在某个角落也可能选到一个快没电的节点当簇头。这也是LEACH后续各种改进协议的切入点。2.2 成簇阶段广播、入簇和TDMA调度簇头选出来之后进入成簇阶段。每个簇头向全网广播一个ADV消息普通节点收到多个簇头的广播后选择距离最近的簇头加入在Matlab仿真里通常直接按欧氏距离选择因为广播信号强度和距离在理想模型下是等价的。簇头确定成员后会为每个成员分配一个TDMA时隙并把时隙表广播给成员。这里用的时分多址机制目的是避免簇内节点同时发送数据产生碰撞同时让节点只在属于自己的时隙里醒来发送其余时间休眠进一步降低能耗。2.3 稳定传输阶段数据融合与低占空比稳定传输阶段才是网络真正干活的阶段。每个成员节点在自己的TDMA时隙内把采集到的数据包发送给簇头簇头收到簇内所有成员的数据后做数据融合通常是最简单但很实用的“取平均”或“取最大”仿真代码里往往直接当成把多个包汇总成一个包处理再把融合结果发送给基站。这个过程结束后进入下一轮重新选举簇头。LEACH的设计者显然意识到“簇头是个苦差事”所以每轮都要换人干。这种定期轮换的思路后来被几乎所有分层路由协议继承下来。3. LEACH-C当“随机抽签”变成“集中排班”LEACH-CC代表Centralized是LEACH的集中式改进版核心区别一句话就能说清楚簇头不再由节点自己抽签决定而是由基站根据全网节点的位置和能量信息统一规划。3.1 为什么需要集中式规划LEACH的分布式随机选举有一个天然缺陷随机数不看空间分布。假设某个区域里五个节点同时成为簇头另一个区域却一个簇头都没有前一个区域的节点距离簇头很近后一个区域的节点却要跨越半个网络把数据送给远处簇头能耗立刻失衡。LEACH-C的做法就是用全局信息换结构合理性。每一轮开始时所有节点先把自身当前位置和剩余能量发送给基站。基站收集完信息后运行一个模拟退火Simulated Annealing算法从所有存活节点中选出M个节点作为本轮簇头目标是让整个网络的“簇内距离平方和”最小同时兼顾节点剩余能量。3.2 为什么选模拟退火而不是穷举从N个节点中选M个簇头是个组合优化问题。N100、M5的穷举组合数高达7500多万每轮都跑一遍穷举不现实。模拟退火是这类离散组合优化场景最省事的元启发式它在高温阶段允许目标函数暂时变差对应物理退火中粒子跳出局部最优然后随时间降温逐渐收敛到较优解。实现上也简单几百行Matlab就能跑起来。目标函数通常写成最小化所有普通节点到对应簇头的距离平方之和J sum( sum( d²(member_i, CH_i) ) )每次迭代随机把当前簇头集合里的一个节点和一个普通节点交换重新计算目标函数如果新的目标函数更小就接受交换如果更大则以exp(-ΔJ / T)的概率接受这个概率随温度T降低而越来越小最终收敛到一个较优解。LEACH-C每轮都要所有节点向基站报一次位置和能量通信开销比LEACH大但它换来的是簇空间分布更均匀、能量分配更合理整体网络寿命通常明显优于LEACH。维度LEACHLEACH-C簇头选举方式节点分布式随机基站集中计算所需全局信息不需要需要所有节点位置和能量附加通信开销成簇广播每轮上报一次簇空间分布随机、不均匀均匀、受优化能耗均衡性一般较好实现复杂度低中高4. Matlab仿真实现从参数配置到两个协议的代码骨架这一部分我会按“初始化—LEACH核心逻辑—LEACH-C核心逻辑—能量更新和统计”的顺序来写代码模块。完整可运行的Matlab代码版本比较长这里给出核心逻辑骨架把最容易写错和最容易理解错的地方都标出来。4.1 节点初始化和参数配置仿真开头最重要的就是统一参数。我习惯先写一个参量表再用结构体数组保存节点信息。每个节点需要记录坐标、剩余能量、本轮角色、G标记最近是否当选过簇头和存活状态。% 网络参数 N 100; % 节点数 X 100; Y 100; % 网络区域 Sink.X 50; Sink.Y 175; % 基站位置 % 能耗参数 E0 0.5; % 初始能量 J Eelec 50e-9; % 发射/接收电路耗能 J/bit Efs 10e-12; % 自由空间功放系数 J/bit/m^2 Emp 0.0013e-12; % 多径功放系数 J/bit/m^4 EDA 5e-9; % 数据融合耗能 J/bit % 数据包长度 packetLen 4000; % 数据包 bit ctrlLen 100; % 控制包 bit % 节点初始化 for i 1:N node(i).x rand * X; node(i).y rand * Y; node(i).E E0; node(i).G 0; % 0表示可以参与簇头选举 node(i).alive 1; node(i).CH 0; % 是否是本轮的簇头 end注意这里参数单位必须统一。Matlab里通常全部使用J、bit、m三件套Eelec写50e-9而不是50nJ这种带单位的写法。4.2 LEACH的簇头选举核心逻辑每一轮先判断所有节点的存活状态然后让存活且G标记为0的节点进行随机抽签for r 1:maxRounds % 计算阈值 P 0.05; T P / (1 - P * mod(r, round(1/P))); for i 1:N if node(i).alive 1 node(i).G 0 if rand T node(i).CH 1; % 当选簇头 clusterCount clusterCount 1; end end end % 非簇头节点选择最近簇头入簇 for i 1:N if node(i).alive 1 node(i).CH 0 minDist inf; for c 1:clusterCount d sqrt( (node(i).x - ch(c).x)^2 (node(i).y - ch(c).y)^2 ); if d minDist minDist d; node(i).cluster c; end end end end % 簇头收集数据、融合、发给基站更新能量 % ... % 每轮结束更新G标记当过簇头的节点在round(1/P)轮内不再参与 if mod(r, round(1/P)) 0 for i 1:N if node(i).alive 1 node(i).G 0; end end end end这段代码有两个细节要注意。第一阈值公式里的mod(r, round(1/P))不同公开代码对r从0还是从1开始有不同约定但只要和G集合的更新时间保持一致最终效果没区别。第二G集合的更新必须在每轮结束后进行且只有在当前轮数满足mod(r, round(1/P)) 0时才把所有存活节点的G清零让新一轮选举周期重新洗牌。4.3 能量更新的关键公式在仿真过程中簇内节点和簇头的能耗必须分开算。普通节点只做一件事把数据包发送给簇头。簇头要做两件事接收所有成员的数据做融合再把结果发给基站。% 普通节点发送数据给簇头 dist sqrt( (node(i).x - ch(c).x)^2 (node(i).y - ch(c).y)^2 ); if dist d0 node(i).E node(i).E - packetLen * Eelec - packetLen * Efs * dist^2; else node(i).E node(i).E - packetLen * Eelec - packetLen * Emp * dist^4; end % 簇头接收成员数据 ch(c).E ch(c).E - ctrlLen * Eelec; % 簇头接收每个成员的数据包 for m 1:size(member, 2) ch(c).E ch(c).E - packetLen * Eelec; end % 簇头数据融合 ch(c).E ch(c).E - packetLen * EDA * (numMembers 1); % 簇头发送融合数据给基站 distSink sqrt( (ch(c).x - Sink.X)^2 (ch(c).y - Sink.Y)^2 ); if distSink d0 ch(c).E ch(c).E - packetLen * Eelec - packetLen * Efs * distSink^2; else ch(c).E ch(c).E - packetLen * Eelec - packetLen * Emp * distSink^4; end我见过不少同学在算簇头接收能耗时只算一次漏掉了“簇头要接收每一个成员的数据包”这件事。如果一个簇有15个成员接收能耗就得乘以15。这块漏了会让仿真结果出现严重偏差。4.4 LEACH-C的集中式分簇与模拟退火骨架LEACH-C的仿真逻辑比LEACH多一个环节每轮开始时所有节点向基站上报位置和能量基站在中心节点运行模拟退火选出最优簇头集合。% 模拟退火参数 T0 1; T_end 0.001; alpha 0.95; maxIter 200; M 5; % 期望簇头数按5%取 % 初始随机选M个存活节点作为簇头 currentSet randperm(N, M); currentCost clusterCost(currentSet, node, M); T T0; while T T_end for iter 1:maxIter newSet currentSet; % 随机交换一个簇头和一个普通节点 swapIdx randi(M); newNode randi(N); if ismember(newNode, newSet) continue; end newSet(swapIdx) newNode; newCost clusterCost(newSet, node, M); delta newCost - currentCost; if delta 0 || rand exp(-delta / T) currentSet newSet; currentCost newCost; end end T T * alpha; end其中clusterCost的目标函数要让每个普通节点都找到离它最近的簇头并累计距离平方function cost clusterCost(chSet, node, M) cost 0; N length(node); for i 1:N if node(i).alive 0 continue; end minD inf; for c 1:M d sqrt( (node(i).x - node(chSet(c)).x)^2 ... (node(i).y - node(chSet(c)).y)^2 ); if d minD minD d; end end cost cost minD^2; end end基站选完簇头后把簇头ID广播出去普通节点根据自己的位置选择最近的簇头入簇剩下的流程和LEACH一致。有些论文版本会让基站直接计算出最优簇划分再广播时隙表但仿真上两者差别不大至少在做LEACH和LEACH-C的对比时核心结论是稳定的。4.5 死节点判定死节点判定看似简单实际上直接决定仿真的结果曲线能不能被复现。常见做法是当节点能量小于等于0就判死。我建议用node(i).E 0 node(i).alive 1作为存活判断条件同时在能量更新后用阈值清理如果能量低于一个极小数比如1e-9直接置为0并标记死亡。这样能避免负能量节点继续参与簇头选举导致概率计算出现异常。5. 仿真结果的核心观察维度只看LND很容易被误导做完仿真拿到数据不会看结果等于白跑。很多论文只画一张“存活节点数-轮数”的折线图但那张图里能挖的信息比你想象的多。5.1 生命周期指标FND、HND、LND分开看评估WSN网络寿命有多个节点级指标指标全称含义FNDFirst Node Dies第一个节点死亡的轮数HNDHalf Node Dies一半节点死亡的轮数LNDLast Node Dies最后一个节点死亡的轮数LEACH-C通常在FND和HND上明显优于LEACH因为集中式规划让每轮簇头的空间分布更均衡没有节点因为长期处于超远距离通信而快速耗光。但LND的差距反而不一定大有时LEACH甚至更晚耗尽最后一个节点原因是LEACH的随机性会让部分节点一直“运气好”始终没当上簇头存活时间被意外拉长。所以做对比实验时一定要把FND、HND、LND三个指标全部列出来尤其以FND和HND作为主要论据否则审稿人很容易质疑结论的全面性。5.2 剩余能量分布比存活节点数更细的观察角度是每个节点的剩余能量分布。LEACH-C的节点剩余能量会更集中方差小LEACH则会出现部分节点还有很多电、部分节点已经死亡的两极分化。画法很简单选定某个轮数比如总轮数的一半把所有存活节点的剩余能量画成柱状图或直方图一眼就能看出来。5.3 累计数据包数量累计到达基站的数据包数量反映的是网络真正完成了多少有效工作。LEACH-C在早期就建立更均衡的簇结构单位能耗产出的数据量更高到了后期由于网络拓扑逐渐瓦解两者的差距会缩小。这个指标可以配合“每成功发送一个数据包的平均能耗”来看能更直观体现协议的能量效率。6. 复现LEACH仿真的五个经典坑每个我都踩过跑LEACH仿真最大的错觉是“代码跑通了就完事了”。实际上仿真结果能不能描述协议的真实特性取决于很多隐藏细节。下面这几个坑我不止一次踩过写出来给后来人省时间。6.1 阈值公式里的轮数取值从0还是从1开始不同版本的公开代码里阈值公式中r mod round(1/P)的写法几乎一样但r的起始值不同会导致首轮阈值计算有细微差别进而影响首轮簇头数量。更常见的坑是误把r直接带入r - 1或r 1导致每轮阈值整体偏移。解决方法是先在网络初始状态下跑10轮统计每轮簇头数量是否稳定在5个左右如果偏差超过2个大概率是轮数边界写错了。6.2 能量单位混用导致节点“秒死”50nJ/bit如果写成50而不是50e-9一个4000bit的数据包发送能耗就是20万焦耳节点一帧就死整条曲线变直线。这种问题第一眼看不出来因为代码逻辑没错、语法没错、图也能画出来但结果完全不可用。我建议写完仿真先做一次单元验证让一个节点向10米外的目标发送1个数据包手动核算能耗是否符合预期。数值对得上再去跑完整网络。6.3 随机数种子不固定结果无法复现LEACH和LEACH-C的成簇过程都用到了随机数。如果你没有固定随机数种子每次跑仿真都会得到不同曲线你根本无法判断两个协议的差距是真实差异还是随机波动。仿真开始时设置rng(0)或者任何固定种子保证同一套代码、同一组参数可以复现相同结果。做对比实验时建议多跑几次取平均或者至少固定多种种子分别跑一遍看趋势是否稳定。6.4 数据融合的开关不统一LEACH-C的优势很大程度来自数据融合带来的通信量压缩。如果仿真中把数据融合关闭比如每个成员都独立发一份完整数据包到基站那分簇的能耗优势会被大幅削弱两个协议的性能差距会变得不明显。相反如果LEACH关闭融合但LEACH-C开启融合对比结果又会被夸大。做公平对比时融合机制和参数必须在两个协议中保持完全一致。6.5 基站位置不是一个可以随意改的参数经典LEACH仿真里基站放在(50, 175)也就是网络区域外上方。如果放在(50, 50)即网络正中心所有节点到基站的通信距离整体缩短分布也更均匀LEACH和LEACH-C之间的差距会被压缩如果基站放得更远两种协议的优势差距会被放大。这不代表基站位置改变会导致结论反转但它会影响绝对数值写论文时一定要在实验设置里标明基站坐标对比横向工作时不要混用两套基站位置。7. 仿真之外的一点体会别只满足于把图跑出来做WSN协议仿真这几年我最大的感觉是LEACH和LEACH-C虽然老但它们几乎包揽了所有现代分层路由协议的底层设计思想——能耗模型、周期轮换、分簇、数据融合、集中式优化。把这两个协议的代码彻底吃透后面再看SEP、TEEN、DEEC这些改进算法基本就是“换目标函数、换选举公式、换优化算法”的事。如果你打算在这个方向上做更细的研究可以尝试三件事一是把簇头选举的阈值公式改成“剩余能量加权”观察网络寿命的变化二是把模拟退火换成粒子群或遗传算法对比不同寻优方法在同一目标函数下的收敛效果三是固定网络拓扑但调整节点初始能量分布看看协议在非均匀网络里的表现。这些扩展实验工作量不大但写论文时非常出效果。最后说一个实操小技巧Matlab的仿真代码里尽量把网络参数、能耗参数、仿真轮数全部集中到文件头部统一配置不要散落在代码各处。等你开始跑一百组对照实验的时候会发现这个习惯帮你省下大量改参数的时间。做协议研究跑通只是起点能把每个细节讲清楚、每个现象解释通才是真正把这项技能变成了自己的东西。
返回列表