ARTICLE DETAIL

资讯详情

深耕郑州网站建设与运营推广的一线实战洞察。

基于HHO-KELM的电厂运行数据回归预测与MATLAB实现

基于HHO-KELM的电厂运行数据回归预测与MATLAB实现 做电厂运行数据回归预测尤其是烟气NOx排放、锅炉效率这类小样本非线性问题最头疼的往往不是模型不够高级而是模型选型和优化过程太折腾。前段时间我把哈里斯鹰优化算法HHO和核极限学习机KELM在MATLAB里完整捋了一遍从原理推导到代码实现再到参数整定踩了不少坑这里把整条链路拆开写出来给正在做类似项目的朋友一个能直接落地的参考。这个项目说白了就是用HHO去自动搜索KELM的正则化系数C和核参数γ然后用优化后的KELM做电厂运行数据的回归预测。适合谁看手里有DCS/SCADA采集的机组运行数据想做预测但又不想被深度学习的高算力门槛卡住的人以及刚接触元启发式算法、想把HHO用在具体工程问题上的同学。KELM训练速度快HHO全局搜索能力强两者结合后模型精度和稳定性通常比盲调参数的ELM、普通KELM高出一截。1. 为什么是HHO-KELM电厂数据回归预测的选型逻辑1.1 电厂运行数据的典型特征电厂运行数据无论是锅炉侧还是汽机侧本质上都是从DCS系统里采出来的时间序列。温度、压力、流量、转速、阀门开度、烟气成分这些变量相互耦合而且受煤质变化、负荷升降、设备老化影响工况漂移严重。很多人在做预测时容易犯一个错误拿着几千个样本直接扔进深度学习模型。实际上电厂有效工况样本往往没那么多特别是某些启停过程、异常工况的数据可能只有几百个有效点。这种小样本、高噪声、强非线性的场景正是传统机器学习模型的舒适区。另一个痛点是特征之间高度相关。比如送风量、引风量、氧量、炉膛负压这些变量本身就存在强耦合。如果你用BP神经网络训练慢是一回事还特别容易陷入局部最优随机初始化几次结果波动很大。而KELM由于采用了核映射不需要显式设定隐层节点数避免了BP那种反复试网络结构的过程在小样本下泛化能力也明显更好。1.2 KELM核极限学习机的优势极限学习机ELM最早是黄广斌教授提出的它的核心思想非常直接输入层到隐层的权重和偏置随机生成然后固定住只求解输出层权重。这样就把一个非线性问题转化成了线性最小二乘问题训练速度极快。但传统ELM有个隐患随机生成隐层参数导致每次运行结果有波动而且需要人工设定隐层节点数。KELM在此基础上引入核函数不再依赖随机隐层映射而是直接用核矩阵来代替ELM中的隐层输出矩阵H。这样做的好处有两个一是彻底消除了随机性同参数下结果完全可重复二是核函数能够把样本映射到高维空间比固定隐层节点的ELM更容易捕捉非线性关系。KELM需要优化的参数从“隐层节点数”变成了“核参数γ和正则化系数C”这个改变看似简单实际对预测精度的影响是决定性的。1.3 HHO哈里斯鹰优化算法为什么合适KELM虽然训练快但参数C和γ如果靠手动试效率极低。网格搜索在小范围还行一旦搜索空间变大计算量陡增。这时候就需要一个高效的全局优化算法来自动搜索。HHOHarris Hawks Optimization是2019年提出的一种元启发式算法模拟哈里斯鹰捕食兔子的行为核心机制包括探索、探索转开发、软包围、硬包围几个阶段整体思路是让鹰群先全局搜索找到潜在猎物区域后逐步收缩包围圈。相比粒子群PSO和遗传算法GAHHO的优势在于参数少、收敛速度快而且它根据猎物的逃跑能量自适应地在探索和开发之间切换不容易过早陷入局部最优。在工程实测里用HHO优化KELM两个参数通常十几轮迭代就有不错的适应度下降比PSO要省不少时间。1.4 整体方案选型思路所以整个方案的逻辑就是电厂数据是小样本非线性回归问题回归拟合选KELM因为它训练快、泛化好、结果可复现KELM两个关键参数不好调选HHO自动搜因为它全局搜索能力强、参数少、实现简单。两者拼在一起就是一个兼顾精度和效率的组合。当然这不是唯一答案如果你数据量特别大比如十万级KELM的核矩阵计算会占用大量内存那时可能需要换思路。但如果你的场景是几百到几千个样本HHO-KELM是性价比很高的选择。2. 算法原理与数学模型拆解2.1 极限学习机ELM的数学基础理解KELM之前必须先看ELM。给定N个训练样本(xi, ti)ELM的模型是f(x) h(x)β其中h(x)是输入样本经过随机映射后的隐层特征行向量β是输出权重。ELM的训练过程就是解一个线性系统Hβ T输出权重β的解析解是β H(1/C HH)^(-1)T 当样本数N小于隐层节点数时或者 β (1/C HH)^(-1)HT 当样本数N大于隐层节点数时这里的1/C是正则化项防止过拟合。整个训练过程没有迭代一步算出解析解所以极快。ELM的缺点在于H是随机映射生成的即使固定节点数每次运行结果也可能不同。这对于工程应用来说是个不稳定因素。2.2 KELM的核映射原理KELM巧妙地把ELM中的H替换成了核矩阵。定义核函数K(xi, xj)核矩阵Ω满足Ω(i,j) K(xi, xj) h(xi)·h(xj)此时不需要显式计算隐层映射h(x)只需要计算样本两两之间的核函数值。KELM的输出函数可以写成f(x) [K(x, x1), K(x, x2), ..., K(x, xN)](I/C Ω)^(-1)T这个形式长得和核岭回归、支持向量机很像但训练方式完全不同。SVM需要求解对偶二次规划问题KELM仍然是解一个线性方程组训练速度依然快。常用的核函数是高斯RBF核K(xi, xj) exp(-γ ||xi - xj||²)这里γ就是核参数控制映射后特征空间的复杂度。γ太小核函数趋于线性模型欠拟合γ太大每个样本都只对自身附近的数据有响应模型容易过拟合。所以γ的选取至关重要。2.3 HHO哈里斯鹰优化算法核心机制HHO的灵感来自哈里斯鹰群体捕猎。算法中每一只鹰都代表一个候选解也就是一组(KELM的C和γ)。整个捕猎过程分为两个阶段探索和开发。探索阶段鹰在搜索空间里随机游走有两种位置更新策略通过一个随机数来决定是向群体内随机个体靠近还是在当前最优解附近搜索。这一阶段负责全局搜索。开发阶段根据兔子猎物的逃逸能量E决定包围策略。E的公式大致是E 2E0(1 - t/T)其中E0是每次迭代开始时随机生成的[-1,1]之间的数t是当前迭代次数T是最大迭代次数。当|E|≥1时鹰群继续探索当|E|1时进入开发阶段。开发阶段又分软包围、硬包围、渐进式快速俯冲等多种策略根据E和一个随机数r的组合选择不同的位置更新公式。HHO的优势在于它在迭代前期偏重探索后期偏重开发而且E0的随机性让每次寻优过程都有一定随机扰动避免同一个局部最优反复被困。用通俗的话说一群鹰先在天上盘旋找猎物密集区发现目标后不断调整俯冲角度最终完成捕获。2.4 HHO优化KELM的参数目标与流程在HHO-KELM中每个鹰的位置向量就是[C, γ]。适应度函数定义为KELM在验证集上的均方根误差RMSE或者平均绝对误差MAE。优化目标是找到一组C和γ使适应度最小。整体流程读取电厂运行数据划分训练集和测试集并做归一化。初始化HHO种群每个个体的位置是随机的(C, γ)组合。对每个个体用当前C和γ训练KELM并计算验证集适应度。用HHO的位置更新公式迭代生成新的(C, γ)。判断是否达到最大迭代次数若未达到则返回第3步。输出全局最优C和γ用它们重新在整个训练集上训练KELM。用测试集评估最终预测结果。这里面最关键的细节是适应度评估用的是验证集而不是训练集。如果你用训练集RMSE做适应度优化算法会把C和γ往过拟合方向推。工程上常用的做法是把训练数据再切一部分出来做验证集或者用交叉验证但考虑到KELM训练快简单留出20%验证集就够了。3. MATLAB实现完整流程与代码3.1 实验环境与数据准备我的环境是MATLAB R2021a以上版本不需要额外工具箱只用到基础函数和统计函数。数据方面以某机组历史运行数据为例特征选了负荷、给煤量、一次风量、二次风量、炉膛温度、含氧量等8个变量预测目标为NOx排放浓度。数据总共1000个采样点前800个作为训练集后200个作为测试集完全按时间顺序切分。这里要特别提醒电厂数据是时间序列一定不要随机打乱后再划分。随机打乱会把未来的数据混进训练集导致预测结果虚高在实际部署时完全不可用。正确做法是训练集在前、测试集在后模拟真实的在线预测场景。归一化我采用mapminmax统一归到[-1,1][X_train_norm, ps_x] mapminmax(X_train, -1, 1); X_test_norm mapminmax(apply, X_test, ps_x); [T_train_norm, ps_t] mapminmax(T_train, -1, 1); T_test_norm mapminmax(apply, T_test, ps_t);注意mapminmax是按行操作的输入必须是“特征×样本”的矩阵所以转置一下。预测结束后还要把结果反归一化。3.2 实现KELM训练与预测函数写一个独立的KELM函数输入是训练样本特征、输出、核参数γ、正则化系数C以及测试样本特征输出是预测值。这里用RBF核。function [predictY, model] KELM_Regression(X_train, Y_train, X_test, gamma, C) % KELM核极限学习机回归 % X_train: 训练特征矩阵, 每行一个样本 % Y_train: 训练输出列向量 % X_test: 测试特征矩阵 % gamma: RBF核参数 % C: 正则化系数 n size(X_train, 1); % 计算训练核矩阵 Omega_train zeros(n, n); for i 1:n for j 1:n Omega_train(i,j) exp(-gamma * norm(X_train(i,:) - X_train(j,:))^2); end end % 加入正则化并求解输出权重 % 形式: beta_solver (I/C Omega)^-1 * Y_train % 直接使用矩阵求逆, 小样本下没问题 A eye(n) / C Omega_train; alpha A \ Y_train; % 训练样本对应系数 % 计算测试核矩阵 m size(X_test, 1); Omega_test zeros(m, n); for i 1:m for j 1:n Omega_test(i,j) exp(-gamma * norm(X_test(i,:) - X_train(j,:))^2); end end predictY Omega_test * alpha; model.alpha alpha; model.X_train X_train; model.gamma gamma; model.C C; end这个实现用了两层for循环求核矩阵。样本数几百到一两千时没问题如果过万建议改成矩阵化计算。MATLAB里可以写成function K rbfKernel(X1, X2, gamma) % 向量化计算RBF核矩阵 n1 size(X1, 1); n2 size(X2, 1); dist2 zeros(n1, n2); for i 1:n1 dist2(i, :) sum((X2 - repmat(X1(i,:), n2, 1)).^2, 2); end K exp(-gamma * dist2); end实测中向量化写法比逐元素双循环快很多。3.3 实现HHO优化算法HHO主函数需要定义适应度函数句柄然后初始化种群迭代更新位置。下面给出一个简化的HHO实现搜索空间是二维边界通过lb和ub指定。function [bestPos, bestFitness, convergenceCurve] HHO( N, T, lb, ub, dim, fitnessFunc ) % N: 种群大小 % T: 最大迭代次数 % lb, ub: 各维下界和上界, 行向量 % dim: 维度 % fitnessFunc: 适应度函数句柄, 输入位置向量, 输出适应度(RMSE) % 初始化种群 X repmat(lb, N, 1) rand(N, dim) .* repmat((ub - lb), N, 1); for i 1:N fitness(i) fitnessFunc(X(i,:)); end [bestFitness, idx] min(fitness); bestPos X(idx, :); convergenceCurve zeros(1, T); % 主循环 for t 1:T for i 1:N E0 2 * rand() - 1; E 2 * E0 * (1 - t / T); if abs(E) 1 % 探索阶段 q rand(); if q 0.5 k randi([1, N]); if i ~ k X_new X(k,:) - rand() * abs(X(k,:) - 2 * rand() * X(i,:)); else X_new lb rand() * (ub - lb); end else X_new (bestPos - mean(X,1)) - rand() * ((ub - lb) * rand() lb); end else % 开发阶段 r rand(); J 2 * (1 - rand()); if r 0.5 abs(E) 0.5 % 软包围 X_new bestPos - E * abs(2 * (1 - rand()) * bestPos - X(i,:)); elseif r 0.5 abs(E) 0.5 % 硬包围 X_new bestPos - E * abs(bestPos - X(i,:)); elseif r 0.5 abs(E) 0.5 % 渐进式快速俯冲软包围 Y bestPos - E * abs(2 * (1 - rand()) * bestPos - X(i,:)); if fitnessFunc(Y) fitness(i) X_new Y; else Z Y rand() * levyFlight(dim); X_new fitnessFunc(Z) fitness(i) ? Z : Y; end else % 渐进式快速俯冲硬包围 Y bestPos - E * abs(bestPos - X(i,:)); if fitnessFunc(Y) fitness(i) X_new Y; else Z Y rand() * levyFlight(dim); X_new fitnessFunc(Z) fitness(i) ? Z : Y; end end end % 边界处理并评估 X_new max(min(X_new, ub), lb); newFitness fitnessFunc(X_new); if newFitness fitness(i) X(i,:) X_new; fitness(i) newFitness; end end [bestFitness, idx] min(fitness); bestPos X(idx, :); convergenceCurve(t) bestFitness; end end function L levyFlight(dim) % 简化列维飞行 beta 1.5; sigma (gamma(1beta) * sin(pi*beta/2) / (gamma((1beta)/2) * beta * 2^((beta-1)/2)))^(1/beta); u randn(1, dim) * sigma; v randn(1, dim); L 0.01 * u ./ (abs(v).^(1/beta)); end上面的代码省略了部分细节核心是探索和开发的切换逻辑。实际运用时要注意适应度函数会被反复调用所以KELM的训练函数必须足够精简不要在适应度函数里做多余的数据加载和归一化否则优化过程会非常慢。3.4 主程序数据划分、优化、训练与评估主脚本把整个流程串起来。先加载数据并归一化然后定义适应度函数调用HHO最后用最优参数训练KELM并评估。%% 数据加载与预处理 load power_data.mat % 包含 X, Y, 以及特征名 % 假设 X是样本×特征, Y是样本×1 trainNum 800; X_train X(1:trainNum, :); Y_train Y(1:trainNum); X_test X(trainNum1:end, :); Y_test Y(trainNum1:end); % 归一化 [X_train_norm, ps_x] mapminmax(X_train, -1, 1); X_train_norm X_train_norm; X_test_norm mapminmax(apply, X_test, ps_x); [Y_train_norm, ps_y] mapminmax(Y_train, -1, 1); Y_train_norm Y_train_norm; Y_test_norm mapminmax(apply, Y_test, ps_y); %% 定义适应度函数 % 从训练集中再留出15%作为验证集 valNum floor(trainNum * 0.15); X_val X_train_norm(1:valNum, :); Y_val Y_train_norm(1:valNum); X_tr X_train_norm(valNum1:end, :); Y_tr Y_train_norm(valNum1:end); fitnessFunc (p) evalKELM(p, X_tr, Y_tr, X_val, Y_val); % evalKELM 内部调用 KELM_Regression 并计算 RMSE %% HHO 优化 lb [0.001, 0.001]; % C 和 gamma 的下界 ub [100, 10]; % C 和 gamma 的上界 N 20; % 种群大小 T 50; % 迭代次数 dim 2; [bestPos, bestRMSE, curve] HHO(N, T, lb, ub, dim, fitnessFunc); C_best bestPos(1); gamma_best bestPos(2); %% 用最优参数训练最终模型 [predictY_norm, model] KELM_Regression(X_train_norm, Y_train_norm, X_test_norm, gamma_best, C_best); predictY mapminmax(reverse, predictY_norm, ps_y); %% 评估 rmse sqrt(mean((predictY - Y_test).^2)); mae mean(abs(predictY - Y_test)); R2 1 - sum((predictY - Y_test).^2) / sum((Y_test - mean(Y_test)).^2); fprintf(RMSE: %.4f\nMAE: %.4f\nR2: %.4f\n, rmse, mae, R2);其中evalKELM函数要注意输入输出格式function rmse evalKELM(p, X_tr, Y_tr, X_val, Y_val) C p(1); gamma p(2); % 注意这里 X_tr 是 特征×样本, 内部函数需要 样本×特征 pred KELM_Regression(X_tr, Y_tr, X_val, gamma, C); rmse sqrt(mean((pred - Y_val).^2)); end这里有个容易踩的坑mapminmax和自定义函数之间来回转置稍不注意维度就错。建议在写主程序时用disp(size())核对每一步的矩阵尺寸。3.5 参数设置与调参经验HHO的种群大小N和迭代次数T不是越大越好。电厂数据样本量在千级时KELM训练一次耗时就几十毫秒N20、T50意味着要训练1000次也就几十秒到一两分钟。如果N50、T100训练次数5000次可能等得有点久。实测下来N20~30、T50~80足够收敛再往上提精度提升有限代价却成倍增加。C和γ的搜索范围需要根据数据尺度调整。如果特征归一化到[-1,1]γ的合理范围通常在0.01到10之间C在0.001到100之间。我习惯先小范围粗搜收敛曲线出来后看bestPos是否落在边界附近如果贴着边界就扩大对应边界再跑一轮。γ的搜索步长不是线性的因为RBF核的敏感性随γ变化差异很大。实际中把γ上界设到10如果最优值大于8说明数据可能需要更复杂的核映射但不建议超过100否则极易过拟合。4. 实际运行中的常见问题与排查技巧4.1 数据归一化与反归一化的细节算预测指标前千万别忘了做反归一化。很多新手用mapminmax归一化后直接用归一化的预测值和真实值算RMSE得到的结果虽然能反映模型在归一化空间的表现但没法跟实际物理量对比。反归一化时也要注意维度关系mapminmax(reverse, Y_pred_norm, ps_y)因为之前归一化是行向量输入所以这里需要把预测列向量转成行向量再转回来。另一个细节是测试集归一化必须使用训练集的ps_x和ps_y而不是自己另算一套。如果单独对测试集做mapminmax等于把测试集信息泄漏进了模型导致测试结果虚高。这里跟时间序列切分的道理一样一切是为了模拟真实部署场景。4.2 KELM核矩阵计算慢或内存爆炸KELM需要计算训练样本两两之间的核矩阵复杂度O(n²)。样本数3000时核矩阵是3000×3000double类型就是72MB还能接受样本数到10000时核矩阵是800MB直接内存溢出。所以KELM适合中小规模数据。如果样本量过大有两个方案一是用分块核矩阵每次只计算一块并缓存到磁盘二是改用ELM或者其它线性模型。MATLAB里使用两层for循环计算核矩阵虽然直观但效率很悲剧。我这里强烈建议写成向量化版本function K rbfKernelVec(X1, X2, gamma) n1 size(X1,1); n2 size(X2,1); XX1 sum(X1.^2, 2); XX2 sum(X2.^2, 2); D repmat(XX1,1,n2) repmat(XX2,n1,1) - 2*X1*X2; D(D0) 0; K exp(-gamma * D); end这个算法利用范数展开||a-b||² ||a||² ||b||² - 2a·b把双重循环变成了矩阵乘法。实测2000个样本时速度能提升十几倍。4.3 HHO陷入局部最优怎么办HHO毕竟是随机优化算法单次运行结果会有波动。如果发现bestRMSE在迭代早期就停止下降或者收敛曲线一直平移多半是陷入局部最优。可以尝试几个措施一是增大种群规模让初始覆盖更充分二是增加迭代次数三是修改HHO里的E0随机机制因为E0生成方式会影响探索和开发的平衡四是连续运行多次取最优结果或者平均值。我个人的习惯是连续跑3次HHO记录每次的最优C、γ和RMSE。如果三次结果相差很小说明优化稳定如果某一次明显偏离就把那次的数据作为异常剔除取另外两次中更好的结果。4.4 R2为负是怎么回事R² 1 - SSE/SST当预测效果比直接用均值预测还差时SSE会大于SSTR²就变成负数。这一般不是KELM的问题而是数据切分不合理。比如电厂数据有强烈的时间趋势训练集是低负荷工况测试集是高负荷工况模型没有见过这种情况预测自然跑偏。解决办法是先用特征可视化确认训练集和测试集的分布是否接近或者改用滑窗交叉验证。另外如果目标变量本身波动特别小比如排烟温度在某个工况下基本恒定那么R²也会很低这时候看RMSE和MAE更直观。4.5 代码调试与性能优化技巧调试时先用小数据跑通流程比如只取前100个样本、种群N5、迭代T3确认没有报错再放大规模。我用的技巧是在HHO主循环里加一个fprintf每10轮输出一次当前bestRMSE这样能直观看到收敛情况。优化结束后把convergenceCurve画出来如果曲线平缓下降没有波动说明算法在正常探索到开发的过渡。还有一个细节MATLAB里嵌套函数和匿名函数的变量捕获会影响性能。我建议在主脚本里把训练数据设置为全局变量或者嵌套函数的外部变量而不是每次把数据作为额外参数传给fitnessFunc否则每次调用都会复制一份数据拖慢速度。当然数据量小时无所谓数据量大时非常明显。5. 实战效果与扩展思路5.1 一组典型结果示例以NOx排放预测为例特征8个样本1000个前800训练、后200测试。我用HHO-KELM实测的结果基本稳定在RMSE 5.2 mg/m³左右R² 0.93~0.96。作为对比默认参数C1γ1的KELMRMSE通常在8左右传统ELM如果随机隐层节点不好调RMSE可能在9~12之间波动。HHO优化后精度提升很明显。从收敛曲线来看前15轮bestRMSE下降很快后面逐渐趋于平稳50轮时基本收敛。这个过程中HHO能够自动找到一个处于“欠拟合”和“过拟合”平衡点的γ值通常并不会落在搜索边界。5.2 与其他优化算法对比把HHO替换成PSO和GA做同样的优化在相同种群规模和迭代次数下HHO的收敛速度明显更快。PSO需要调的参数有惯性权重w、个体学习因子c1、社会学习因子c2GA需要交叉率、变异率参数一多调试成本就高。而HHO的核心参数基本只有种群规模和迭代次数用起来非常省心。当然HHO也有它的局限性针对某些高维问题它的后期开发阶段可能收敛过快导致精度不够这时可以结合局部搜索算法做混合。5.3 可扩展方向这个框架稍微改造一下就能迁移到不少场景把目标变量从NOx换成CO、氧含量、锅炉效率、汽轮机热耗把特征换成风压、各部温度、阀门开度也可以把HHO换成别的元启发式算法做对比甚至把KELM换成LSSVM、相关向量机等。核心的HHO优化框架和MATLAB代码骨架几乎不用动换数据和目标变量就能跑。另一个工程化方向是模型更新。电厂运行工况会随季节、煤种变化固定模型跑一段时间后精度可能下降。可以把在线运行数据按滑窗方式不断加入训练集每隔一定时间用HHO重新优化一次参数实现一个轻量的在线学习闭环。最后分享一个小技巧在部署到你自己的工程环境前先用交叉验证评估模型的稳定性。我自己习惯把数据切成5折每折跑一次HHO-KELM看RMSE的均值和方法。如果方差很小说明这个模型在当前数据上是可信的如果方差很大说明某一段工况数据异常需要先做数据清洗而不是调模型。这个习惯帮我避免了很多次“训练集效果好、上线后崩掉”的尴尬情况。
返回列表