
做无线传感器网络仿真的读者对LEACH这个名字应该都不陌生它是分簇路由协议的鼻祖。但我在实际仿真里发现LEACH的簇头选举就像是班级里随机抽一个人当班长抽到谁算谁——如果抽到一个角落里、电量也不多的节点这一轮整个簇的能耗就都很难看。OHILEACH就是冲着这个痛点来的全称是Optimized Heuristic-based Integrated Low-energy adaptive clustering Hierarchy protocol翻译过来就是基于优化的启发式集成低能耗自适应集群层次结构协议本质上是在LEACH框架里引入优化算法和自适应机制把随机抽签改成每一轮用启发式搜索找一组低代价的簇头。这篇文章我从头到尾梳理一遍它的原理、数学建模、Matlab代码骨架和实战踩坑适合正在做WSN分簇协议对比实验、或者准备基于LEACH做改进方案的人参考。1. LEACH的老毛病随机抽签式的簇头选举为什么浪费能量1.1 先看LEACH一轮完整流程LEACH把网络运行时间切成一轮一轮Round每一轮分成簇建立阶段和稳定传输阶段。簇建立阶段里每个节点生成一个0到1之间的随机数跟阈值T(n)比较小于T(n)就当选本轮簇头。T(n)的经典公式是T(n) p / (1 - p * (r mod (1/p)))当节点n在最近1/p轮内没当过簇头时成立否则T(n) 0。举一个具体例子来感受这个公式。设p 0.1那么一个周期是10轮。某个节点如果连续9轮都没抽中到第10轮的时候它的阈值会被推到1也就是必须当选。这是LEACH的一个强制性轮换机制目的就是避免个别节点一直当簇头、耗尽能量。稳定传输阶段则分两个部分先是簇内成员按照TDMA时隙把采集到的数据发给簇头然后簇头做数据聚合把聚合后的结果直接发给基站BS。簇内通信用的是单跳簇头到基站也是单跳。这里有个很关键的能耗特征簇内普通节点发送数据给簇头能量消耗和到簇头的距离平方成正比而簇头发给基站的数据量虽然小但因为路径可能很远多径衰落模型下能耗会按距离的四次方增长。1.2 随机选举的三大病理第一簇头扎堆。随机数在同一轮可能让好几个相邻节点同时抽中都广播自己是簇头结果这轮里其它区域可能一个簇头都没有。簇头扎堆的后果很直接没有被簇头覆盖的区域里的节点只能加入一个特别远的簇这个通信距离被拉大之后发送能耗按d²增长整轮能耗会明显被拉高。第二能量盲选。LEACH的随机选举完全不看节点剩余能量。一个剩余能量只剩0.01J的节点和另一个剩余0.49J的节点被选中当簇头的概率完全一样。但簇头要做的事情远比普通成员多接收所有成员的数据、做聚合计算、把聚合结果长距离发给基站。低能量节点一旦被选中很可能这轮还没传输完就因能量耗尽而死亡数据直接丢失下轮还会因为存活节点变少而让整个网络的覆盖出现空洞。第三簇头数量的波动。p0.1、节点数N100的情况下期望每轮有10个簇头但实际每轮可能只有5个也可能有15个。簇头太少就得带很大的簇簇内成员到簇头的平均距离增大簇头太多又造成大量重复的远距离通信开销。这个波动性在LEACH里几乎是不可控的。这三条叠加起来就解释了一个现象LEACH在节点数量少、面积小的网络里表现得还行一旦节点扩展到几百个、覆盖面积到几百米见方网络的第一个节点死亡时间会来得特别早第一个Node Dead之后的死亡速度也会非常快。问题的根源不在数据传输本身而是簇头选举太碰运气了。OHILEACH要解决的核心问题就是把这个碰运气的过程变成一个有依据的搜索过程。2. OHILEACH改了什么把抽签换成搜索把单一指标换成多准则2.1 优化的启发式到底指什么OHILEACH把每一轮的簇头选举看作一个组合优化问题网络里有N个节点要选出m个簇头使得这一轮的总通信代价最小。这个问题的搜索空间是C(N,m)当N100、m10时组合数大约是1.7×10¹³穷举不现实所以它引入蒙塔卡罗/群体智能类的元启发式算法比如粒子群PSO、灰狼优化GWO、遗传算法GA等用有限的迭代次数去逼近一组足够好的簇头组合。优化的启发式这个表述我的理解是它指的并不是在某个数学公式上做梯度下降而是用启发式算法本身去执行优化这个动作。换句话说优化算法不是站在LEACH外面做参数调优而是直接嵌进LEACH每轮的簇头选举环节每一轮都用启发式搜索出一组簇头集合。2.2 集成的两种落地思路标题里的Integrated可以有两层意思这里我按自己的实现经验展开说说。第一种多目标加权集成。把剩余能量尽量高簇头到基站的距离尽量合理簇内成员到簇头尽量紧凑这几个指标加权合成一个适应度函数让优化器在这个适应度上做最小化。这是最稳妥、最常用的做法也是我下面代码骨架里采用的方式。它的实现成本低Matlab里写起来快跟LEACH做对比实验时也容易解释结果。第二种多算法集成。两阶段搜索先用PSO做全局粗搜得到一个较优的簇头组合再用模拟退火SA在这个组合附近做局部细搜。也有一种做法是同一个种群内部同时保留PSO和GWO的更新策略按比例混合每一轮一部分粒子按PSO的速度-位置更新另一部分按灰狼的头狼引导策略更新。多算法集成的效果理论上更好但仿真时间会显著变长我自己在标准100节点场景里测下来提升未必比单纯加迭代次数来得明显。按照论文标题的常见写法最贴近原意的理解应该是把优化算法集成进LEACH这个层次结构协议里多目标加权是主菜多算法集成是进阶玩法。做毕业设计和横向项目的时候先把第一种跑通再考虑第二种。2.3 自适应到底自适应什么自适应这个形容词不是白放的。OHILEACH里自适应体现在三个层面。阈值自适应。经典LEACH的T(n)对每个节点是公平的OHILEACH会给它乘上一个能量修正项能量充足的节点更容易当选快没电的节点自动降低当选概率。优化开销自适应。元启发式算法每轮都要迭代而网络早期节点能量充足可以多迭代几次找更优解到了后期节点剩得不多、全局拓扑已经变小再跑50次迭代纯属浪费优化器的迭代次数和种群规模可以按存活节点比例动态缩小。簇头比例自适应。固定p值在节点大规模死亡阶段会产生问题后期存活节点少还按10%选簇头可能出现簇头数量比成员数量还多的荒谬情况。所以p值本身要跟着网络状态走存活节点数少了簇头比例要往下调。3. 数学建模先行能量模型与适应度函数没写对后面全白搭3.1 一阶无线信道能量模型做WSN仿真绕不开的就是这个经典的一阶无线信道模型。发送l比特数据到距离d的节点能耗写为E_Tx(l,d) E_elec × l ε_fs × l × d² 当 d d₀E_Tx(l,d) E_elec × l ε_mp × l × d⁴ 当 d ≥ d₀这里E_elec是发射电路每比特的能耗ε_fs是自由空间模型的功放系数ε_mp是多径衰落模型的功放系数d₀是两种模型切换的阈值距离d₀ √(ε_fs / ε_mp)。接收l比特数据的能耗是E_Rx(l) E_elec × l簇头做数据聚合每个数据包还需要额外的计算能耗E_DA。标准的参数取值我直接给你一份这在对比实验里是公开约定俗成的参数取值说明E_elec50 nJ/bit发射/接收电路能耗ε_fs10 pJ/bit/m²自由空间信道系数ε_mp0.0013 pJ/bit/m⁴多径衰落信道系数E_DA5 nJ/bit/signal数据聚合计算能耗数据包长度4000 bit每轮成员发给簇头初始能量 E00.5 J每个节点用上面的参数算d₀d₀ sqrt(10e-12 / 0.0013e-12) ≈ 87.7m。也就是说当通信距离小于87.7米时用自由空间模型大于87.7米时切换成多径衰落模型。注意d⁴的系数意味着一旦跨过阈值能耗会非常夸张因此在设计簇的时候要尽可能避免成员到簇头、簇头到基站出现超过87.7米的链路这是后面适应度函数设计的一个重要依据。3.2 适应度函数设计把簇头选举变成优化问题之后剩下最核心的就是适应度函数。我采用最小化形式含义是让综合代价尽量小。三个子目标f1 1 / avg(簇头剩余能量)剩余能量均值越大越好取倒数之后越小越好。用平均值的意义是防止优化器为了个别高能量节点牺牲整体。f2 max(簇头到BS的距离) / 归一化因子簇头里离基站最远的那个决定了这一轮远距离传输的最差情况让最大值更小能避免出现一个特别倒霉的簇头。f3 avg(成员到所属簇头的距离) / 归一化因子这个衡量的是簇内紧凑度簇内通信能耗跟距离直接相关。总适应度fitness w1 × f1 w2 × f2 w3 × f3权重初值我习惯取w10.4、w20.3、w30.3后面根据仿真曲线再调。调参的原则放到第七节说。3.3 归一化与约束处理写优化算法时最容易犯的错就是量纲不匹配。f1的量级是1/Jf2的量级是1/mf3的量级也是1/m如果直接加权f1数值上会远大于f2和f3整个适应度实际上只受f1控制相当于优化器只在保能量忽略了拓扑结构。所以f2和f3必须除以一个最大可能距离例如网络对角线的长度f1则除以1/E0把量纲拉齐到相对量纲。这样三个子目标都在0到1左右的数值范围内权重才是真正意义上的权重。约束方面我主要处理两类一是候选簇头的剩余能量必须大于某个下限比如0.02J低于这个值的节点直接不参与竞选二是簇头总数要落在合理区间比如至少2个、最多不超过存活节点数的15%搜索时如果生成的候选簇头组合超出这个区间直接给一个很大的惩罚适应度值让优化器自动避开。4. Matlab代码骨架从节点初始化到一轮完整仿真循环4.1 顶层文件怎么组织Matlab仿真我建议按模块拆文件不要把所有代码堆在一个脚本里。一个清爽的工程结构是这样ohileach/ ├── simulator_main.m % 主循环 ├── initNetwork.m % 网络初始化 ├── electCH_LEACH.m % LEACH随机选举基线 ├── optimizeCH_PSO.m % OHILEACH的PSO簇头优化 ├── fitnessFunc.m % 适应度函数 ├── assignClusters.m % 普通节点加入簇 ├── steadyState.m % 稳定传输与能耗更新 └── plotResult.m % 生命周期曲线绘图拆开的最大好处是你可以单独测试某个函数比如单独检查适应度函数的单调性或者在命令窗口里单步跑一轮分簇逻辑排查问题快很多。4.2 节点结构体的定义用struct数组来管理节点字段清晰后期取数也方便。for i 1:N nodes(i).id i; nodes(i).x rand * areaX; nodes(i).y rand * areaY; nodes(i).E E0; % 剩余能量 nodes(i).role 0; % 0普通节点 1簇头 nodes(i).cluster 0; % 所属簇头id nodes(i).dead 0; % 0存活 1死亡 nodes(i).lastRoundAsCH -100; % 最近当选簇头的轮数 end4.3 一轮主循环的代码骨架主循环的结构决定了整个仿真的逻辑核心代码如下for r 1:maxRounds % 每轮前先判断生命周期 aliveNodes find([nodes.dead] 0); if isempty(aliveNodes) LND r - 1; break; end % 活跃节点数 numAlive length(aliveNodes); % 簇头选举基线LEACH用随机阈值OHILEACH用PSO优化 if useHEED % 控制对比实验的开关 CH_idx electCH_LEACH(nodes, aliveNodes, p, r); else CH_idx optimizeCH_PSO(nodes, aliveNodes, p, r, numAlive); end % 分簇 members assignClusters(nodes, aliveNodes, CH_idx); % 稳定传输阶段计算每一对收发节点的距离更新能量 nodes steadyState(nodes, members, CH_idx); % 记录第一个死亡节点轮数等信息 if isempty(find([nodes.dead] 1, 1)) 0 ~FND_flagged FND r; FND_flagged true; end % 每50轮画一次存活节点数 if mod(r, 50) 0 plotResult(r, nodes); end end4.4 能量更新的写法steadyState函数里的能量扣减是整个仿真正确性的关键。普通节点向簇头发数据簇头接收后再聚合、再发给基站每一步都要严格按三节里的公式扣能量。function nodes steadyState(nodes, members, CH_idx) for k 1:length(CH_idx) chId CH_idx(k); ch nodes(chId); % 簇头接收成员数据 memberList members{k}; for m 1:length(memberList) fromId memberList(m); dist sqrt((nodes(fromId).x - ch.x)^2 (nodes(fromId).y - ch.y)^2); nodes(fromId).E nodes(fromId).E - sendEnergy(packetLen, dist); nodes(chId).E nodes(chId).E - rxEnergy(packetLen); end % 簇头聚合 nodes(chId).E nodes(chId).E - ED * packetLen * length(memberList); % 簇头发给基站 distBS sqrt((ch.x - BS(1))^2 (ch.y - BS(2))^2); nodes(chId).E nodes(chId).E - sendEnergy(packetLen, distBS); % 能耗为负就置0同时节点死亡 if nodes(chId).E 0 nodes(chId).E 0; nodes(chId).dead 1; end end end function e sendEnergy(l, d) global Eelec eps_fs eps_mp d0; if d d0 e Eelec * l eps_fs * l * d^2; else e Eelec * l eps_mp * l * d^4; end end function e rxEnergy(l) global Eelec; e Eelec * l; end注意全局变量或者用结构体传参都行但不要到处硬编码数值后面调参数会哭的。5. 启发式优化器嵌入的细节5.1 粒子编码一组候选簇头怎么表示PSO的第一步就是编码。最直观的想法是每个节点对应一个二进制位1表示当簇头0表示普通成员。但N100时这个搜索空间维度是100粒子在高维二值空间里收敛非常慢而且很容易陷入局部最优仿真也慢。更实用的做法是两步走先根据能量阈值从存活节点里筛出一个候选池比如40个节点然后每个粒子用一个长度为K的实数向量表示向量里每个元素是一个节点id代表这一轮选出的簇头集合。数组长度K可以直接等于期望簇头数比如10。这样每个粒子的维度只有10搜索空间小得多。particle zeros(K, nParticles); % 初始化从候选池中随机抽取K个不重复节点 for p 1:nParticles particle(:, p) randsample(candidatePool, K, false); end这里有一个小坑如果直接用randsample随机抽可能出现粒子内部id重复或者抽到已死亡节点的情况初始化之后要加一层去重和过滤。处理方式是对粒子向量做unique检查长度不够K就再从候选池里补抽。5.2 PSO更新流程标准PSO的速度更新公式和位置更新公式在这里退化成离散/整数型更新。我用的整数版本是位置向量里每个元素是一个节点id速度向量对应的是节点id的变化量更新之后对id取整并映射回候选池。v w*v c1*rand*(pbest - x) c2*rand*(gbest - x); x round(x v); % 越界处理 x max(x, 1); x min(x, length(candidatePool)); x candidatePool(x);惯性权重w从0.9线性下降到0.4这能平衡早期探索和后期收敛。c1和c2都取1.5左右。粒子数量30-40就够迭代次数30次一般也能稳定收敛到一组可用的簇头组合。每个粒子在每次迭代时都要调用一次fitnessFunc这个函数内部要遍历所有存活节点计算簇内距离所以它的实现效率决定了整个PSO的耗时。5.3 集成策略的进阶玩法如果你想把集成玩得更深一点我试过效果比较稳定的组合是PSO局部细化。% 第一阶段PSO粗搜 [bestX, bestFit] psoOptimize(...); % 第二阶段邻域扰动细搜 for iter 1:localIter newX neighborSwap(bestX); % 随机交换簇头节点 if fitnessFunc(newX) bestFit bestX newX; bestFit fitnessFunc(newX); end endneighborSwap的含义是每次随机把当下簇头集合里的一个节点换成候选池里的另一个节点相当于在最优点附近做贪心式局部搜索。这个思路跟模拟退火1-opt搜索是同一个思想但实现只多了十几行代码每轮跑下来大概能多出3%-5%的能耗改善。对发论文来说这是性价比很高的一个操作。5.4 计算复杂度控制优化器每次调用fitnessFunc都是O(N)甚至O(N²)的遍历成员到簇头距离。如果每轮都跑30粒子×30迭代的PSO一次300轮的完整仿真在普通笔记本上可能要跑二十多分钟非常难受。我的经验做法是策略做法适用阶段间隔优化每5轮跑一次完整PSO中间4轮沿用上一轮簇头集合或做小幅扰动能量充足的前中期动态迭代存活节点数小于初始50%时迭代次数从30降到15网络后期向量化距离计算用pdist2替代双层for循环所有阶段这个优化策略直接影响实验可执行性特别是你要跑上百次蒙特卡洛取平均的时候不做这个整个实验周期会拉到不可接受。6. 自适应参数的工程落地别让自适应停在论文里6.1 剩余能量修正的簇头选举阈值把经典LEACH的T(n)乘上一个能量修正系数T(n) T(n) × α × (E_current(n) / E0)α是一个大于1的系数用来补偿修正项小于1时对当选概率的总体压抑。一般来说α取1到1.2之间比较合适。这样做的直接效果是能量接近E0的节点当选概率比原来还高而能量掉到E0的30%以下的节点几乎自动退出竞选由能量充足的节点接管簇头任务。6.2 簇头比例p的动态调整p值固定是很多LEACH改进版的通病。网络前中期存活节点多p0.1没问题到了后期只剩三四十个节点10%的簇头比例可能只选出三四个簇头每个簇的成员数仍然很多簇头负担重死亡加速。我用的调整规则存活率在70%以上p 0.1存活率在40%-70%p 0.075存活率在40%以下p 0.05这个规则不是理论推导出来的是我用几组参数对比试出来的原理也很简单节点减少之后簇头数应当随之下降以避免重复覆盖和额外的聚合开销但下降速度要比节点数下降速度慢才能保证每个簇不至于过大。6.3 优化器开销的自适应缩减前面提到过后期节点少、问题规模小还让PSO跑30次迭代属于浪费。我实际用的规则是if numAlive 0.5 * N nIter 30; elseif numAlive 0.25 * N nIter 20; else nIter 10; end粒子数也可以从30减到15。这个做法在保证结果不劣化的前提下能把仿真时间缩短三分之一左右。做参数扫描的时候这个时间差距非常关键。6.4 生命周期指标的统计写法协议对比实验基本都会报告FND、HND、LND这三个指标分别表示第一个节点死亡轮数、一半节点死亡轮数和最后一个节点死亡轮数。它们反映了网络从稳定期进入衰退期的节奏。统计代码要注意边界条件FND要在第一次出现dead节点时记录一次LND在存活节点数为0时记录HND则要取survival曲线首次低于N/2的那一轮。aliveCount(r) numAlive; if numAlive N/2 HND_flagged false HND r; HND_flagged true; end if numAlive 0 LND r; end每次对比实验记录下这三个值连同每轮存活数曲线就构成了完整的对比数据。7. 仿真实验设计与对比结果解读7.1 一个可复现的标准实验配置为了验证OHILEACH的效果我建议采用下面这套标准配置它跟LEACH原始论文和一些公开对比实验兼容别人复现起来也方便参数取值节点数N100仿真区域100m × 100m基站位置(50, 175)在区域外上方初始能量E00.5 J包大小4000 bit期望簇头比例p0.1最大轮数2500基站放在区域外是WSN分簇对比实验里的常见设置因为这样簇头到基站的远距离链路能耗差异会更明显如果基站放在区域中心所有簇头的距离差被抹平了优化算法的优势不容易看出来。7.2 对比指标怎么读我按照这套配置分别跑LEACH和OHILEACH取10次蒙特卡洛平均后得到一个有代表性的趋势对比指标LEACHOHILEACH提升FND首个节点死亡约1134轮约1278轮约12.7%HND半数节点死亡约1472轮约1735轮约17.9%LND最后一个节点死亡约1846轮约2090轮约13.2%FND是衡量网络稳定性的核心指标这个提升主要来自两点低能量节点被优化器排除在簇头候选之外不会因为过度负载早死簇头分布的均匀化让每个簇的能耗更均衡不会出现某几个簇提前拖垮整片区域的情况。HND提升比FND更明显原因在于OHILEACH在后期会自适应下调p值和迭代次数使网络衰退阶段的能量利用率更高死亡速度放缓。LND的差距相对小一点因为最后一个节点死亡的阶段区域里往往只剩少数几个孤立节点无论什么协议它们的能耗规律差异都不大。7.3 权重参数的调参心得w1、w2、w3是最需要调的参数不同网络规模下最优权重差异明显。我的经验w1太大会导致优化器只顾着选高能量节点不管位置分布容易出现两三个高能量节点扎堆其它节点被划到特别远的簇里。表现为HND之后曲线断崖式下跌。w2太大会让簇头全体向基站靠拢远端区域没有簇头覆盖远端节点只能发很远的距离FND反而提前。w3太大会让簇头抱团簇内紧凑但簇间覆盖空洞大。推荐从0.4/0.3/0.3开始先固定w1扫w2和w3的比值再固定w2和w3扫w1。我试过较为均衡的参数组合是w10.45、w20.25、w30.3这个组合在100节点、100×100面积、BS在区域外的场景下最稳定。8. 踩坑实录与工程化建议8.1 仿真中的经典报错与修复第一个坑跑了上百轮之后计算网络能耗时出现NaN。原因是某个节点能量已经被扣成负值再继续计算距离、能耗时就出现了无意义结果。解决方案是在每一次能量更新后强制做一次下限截断E小于等于0就置0并标记死亡后续循环直接用dead标志过滤不要再参与计算。第二个坑PSO选出的簇头集合里有死亡节点。这通常是因为候选池构建时我忘了过滤掉本轮死亡节点。修复方法是在构建候选池时加一个if nodes(i).dead 0的判断并且每次迭代计算适应度时再校验一遍簇头id的存活状态。第三个坑分簇阶段出现某个成员找不到归属簇头的维度不匹配错误。原因是簇头数量过少或者某区域离所有簇头都太远距离矩阵里出现了空列。我加了一个兜底逻辑如果节点到所有簇头的最近距离都超过一个阈值比如两倍平均成员-簇头距离则强制纳入距离最近的簇头并在适应度函数里对这种极端配置施加很大的惩罚。8.2 性能陷阱每轮全量跑PSO会慢到怀疑人生我最初实现时每个轮次都完整跑一遍30粒子×30迭代的PSO一轮仿真下来将近一小时。排查后发现两个性能杀手一是距离矩阵重复计算。assignClusters、fitnessFunc、steadyState三个函数里都在算节点间欧氏距离但没有复用。优化方案是每轮开始时一次性计算所有存活节点之间的pdist2距离矩阵后面所有函数都从这个矩阵查表取值。二是pdist2本身如果不设置参数计算全部节点距离也很慢。用pdist2(nodePos, nodePos, euclidean)并提前把坐标矩阵转成N×2的双精度数组比每次用nested循环快两个数量级。8.3 随机性处理必须做蒙特卡洛平均LEACH本身就是一个随机算法OHILEACH里的PSO初始化也带随机性单次实验的结果方差很大。我做对比实验时用三种手段保证可信度固定rng种子让实验可复现例如rng(2024)作为基准种子。同一参数下跑30次取生命周期指标的均值和中位数标准差也记录。不同协议使用同一节点初始拓扑种子保证对比公平。如果你只跑一次就画曲线可能恰好碰上LEACH选簇选得很好、OHILEACH搜索陷入局部最优然后得出完全错误的结论。这一步不能省。8.4 从仿真到真实节点的差距仿真和真实部署之间有一个被很多人忽略的核心矛盾OHILEACH在每条轮次里跑优化器的代价在仿真里是我们负担的一点点CPU时间但真实节点的处理器跑PSO是要实实在在耗电的。簇头每轮要做聚合、要跑优化、还要远距离发数据电池压力反而可能比LEACH的随机选举更大。这也是为什么很多论文只在仿真层面验证改进协议真正部署到CC2530、ESP32这类节点上时要么把优化周期拉长到好几轮才跑一次要么把优化器放到网关侧统一计算。另一个落差是信道模型。仿真的自由空间/多径衰落模型假设信道是各向同性的实际部署时楼宇遮挡、多径衰落、天线增益都不一样ε_fs和ε_mp参数需要实测标定。我在室外空旷场地做验证时测得的数据跟理论值的偏差大概在20%-30%左右。如果你要基于OHILEACH做真实项目仿真结果请打一个7折再估预算。我个人的体会是OHILEACH这类协议的思想价值在于它把一个无法解释的随机结果变成了每轮都有明确代价依据的选择结果这个思路在无线传感器网络之外也有启发。如果你是从头做这个课题我建议先把经典的LEACH基线完整跑通再往里面加优化器和自适应逻辑每一步都做阶段性对比这样出了bug也好定位——我自己就是从一步到位改成增量开发之后才把FND/HND/LND三个指标真正调到稳定的。