ARTICLE DETAIL

资讯详情

深耕网站视觉设计与运营推广的一线实战洞察。

SOA-KELM核极限学习机分类:MATLAB实现与参数自动优化

SOA-KELM核极限学习机分类:MATLAB实现与参数自动优化 做分类模型这几年我最大的体会不是模型越复杂越好而是参数调起来有多痛苦。之前处理一组设备振动数据用普通ELM跑了十几轮准确率忽高忽低换不同核参数又得反复试错。后来把核极限学习机(KELM)与海鸥优化算法(SOA)结合起来让SOA自动搜索最优的正则化系数C和核参数sigma同一份数据、同一个Excel文件不再需要手工碰运气式调参分类准确率直接提升了一个台阶。这篇博客就把整套SOA-KELM核极限学习机分类MATLAB代码的写法、本地Excel数据读取流程、参数调试心得一次讲清楚特别适合做故障诊断、图像识别、表格数据分类的同学直接参考复现。1. 为什么是SOA-KELM这个组合到底解决了什么问题1.1 传统ELM的随机性困境极限学习机ELM的爽快之处在于隐藏层权重是随机生成的不需要像BP神经网络那样反向传播迭代。你用随机权重把输入映射到高维空间得到一个隐藏层输出矩阵H然后直接通过最小二乘计算输出权重beta。整个训练过程极快理论上泛化能力也不差。但问题也很明显随机权重意味着每次跑出来的结果可能不一样尤其是样本量不大的时候分类准确率波动非常明显。同一个模型你跑十次最好的一次能到92%最差的一次可能只有85%。这种不确定性在科研复现和工程落地中很头疼你总不能跟领导说“这个模型靠运气多跑几次取最好的吧”。1.2 KELM把随机性换成核映射核极限学习机Kernel Extreme Learning Machine, KELM的改动本质上只有一点把随机映射的隐藏层矩阵H替换成由核函数计算的核矩阵Omega。这样一来映射过程不再依赖任何随机权重而是由核函数和样本之间的相似度直接决定。这意味着什么意味着同样一份数据你用固定的核参数跑一百次结果完全一致。KELM把ELM最大的不稳定因素去掉了分类效果稳定得多。代价是新增了两个需要人工设置的参数核参数sigma如果用RBF核和正则化系数C。这两个参数选得好不好直接影响分类准确率有时候差几个点甚至十几个点都很正常。1.3 SOA在整个组合里的真正角色海鸥优化算法Seagull Optimization Algorithm, SOA这时候就派上用场了。它把[C, sigma]看作二维空间里的一个坐标点通过模拟海鸥迁徙和攻击行为来搜索最优参数组合。你不需要理解复杂的数学细节只需要把KELM当成一个“黑箱函数”给它一组参数它就返回一个分类准确率SOA的任务就是不断尝试不同的参数组合找到准确率最高的那个。所以SOA-KELM这套方案的本质是用元启发式优化算法去接管最耗人力的调参环节把“人工试错”变成“自动寻优”。对于只有几十个特征、几千条样本的表格数据来说这套组合的性价比非常高准确率大概率超过普通ELM也超过不调参的原始KELM。2. KELM的数学骨架从输出权重推导到核矩阵2.1 ELM一分钟复习我们先快速回顾标准ELM的公式。给定训练样本Xn行d列和标签矩阵Tn行m列m是类别数ELM的隐藏层输出矩阵H可以写成H g(X * W b)其中W是随机输入权重b是随机偏置g是激活函数。输出权重beta通过最小二乘计算beta H * (H * H)^(-1) * T在ELM中常用广义逆Moore-Penrose伪逆来求解避免HH奇异的问题。最终预测时输入一个样本x先计算它的隐藏层向量h(x)再输出f(x) h(x) * beta。2.2 KELM的核心推导用核矩阵替换HH^TKELM的关键洞察是ELM的训练过程中我们真正用到的其实是HH这个矩阵而不是隐藏层输出本身。HH的第(i,j)个元素是h(x_i)和h(x_j)的内积。如果我们可以定义一个核函数K(x_i, x_j)来直接计算这个内积那么就不需要显式构造随机隐藏层了。这就是KELM的做法Omega K(x_i, x_j)i, j 1, 2, ..., n用RBF核的话Omega(i, j) exp(-sigma * ||x_i - x_j||^2)于是输出权重变成beta (I/C Omega)^(-1) * T这里I是单位矩阵C是正则化系数。预测新样本x时f(x) [K(x, x_1), K(x, x_2), ..., K(x, x_n)] * beta最后对f(x)的每一行取最大值的下标就是预测类别。以上过程不涉及任何随机权重所以结果完全可复现。2.3 核函数怎么选、C和sigma怎么理解核函数的选择上RBF核高斯核是最省心的默认选项它在绝大多数分类问题上的表现都够用。RBF核只有一个参数sigmasigma越小核函数衰减越快模型越复杂容易过拟合sigma越大核函数越平缓模型越简单容易欠拟合。正则化系数C则是用来平衡模型复杂度和训练误差的。C越大对训练误差的惩罚越重模型更倾向于“记住”训练样本C越小模型更平滑泛化能力可能更好但训练误差偏高。在KELM里C实际上体现在beta的求解公式(I/C Omega)^(-1)中相当于给Omega矩阵的对角线加了一个正值这也让矩阵求逆更稳定不容易出现数值退化。3. SOA海鸥优化算法它是怎么跑出最优参数的3.1 海鸥的迁徙与攻击行为海鸥优化算法是Dhiman等人2019年提出的元启发式算法模拟了海鸥群体的两种典型行为迁徙和攻击。迁徙阶段对应全局搜索。海鸥群在飞行时会避开碰撞、保持队形、并朝最优位置移动。算法用几个关键公式来模拟这个过程。首先是避免碰撞海鸥的位置更新时会用一个线性递减的系数A来控制移动步长让前期探索范围大后期逐渐收敛。其次是朝最优个体靠拢每个海鸥都会向当前适应度最好的个体方向移动。攻击阶段则对应局部开发。海鸥在觅食时会以螺旋形轨迹下降攻击猎物算法把这个螺旋运动数学化用参数r控制螺旋半径位置更新公式写为x r * cos(k) y r * sin(k) z r * k然后海鸥的新位置由最优个体的位置加上这个螺旋偏移决定。这个阶段的关键作用是在最优解附近进行精细搜索避免算法只停留在全局粗糙区域。3.2 算法伪代码与MATLAB循环结构整个SOA寻优KELM参数的过程代码层面并不复杂。先初始化一组海鸥个体每个个体就代表一组[C, sigma]参数。然后循环迭代每次迭代都完成以下事情计算每个个体的适应度调用KELM训练函数返回分类准确率记录全局最优个体及其适应度按照迁徙公式更新所有个体的位置按照攻击公式再更新一次所有个体的位置检查新位置是否超出参数边界越界的拉回边界内。在MATLAB里这个主循环通常写成for t 1:Max_iter % 计算适应度 for i 1:N positions(i, :) boundaryCheck(positions(i, :), lb, ub); fitness(i) KELM_Fitness(positions(i, 1), positions(i, 2), X_train, Y_train, X_test, Y_test); end % 更新全局最优 [bestFit, idx] max(fitness); if bestFit Best_score Best_score bestFit; Best_pos positions(idx, :); end % 迁徙更新 A 2 - (2 * t / Max_iter); % ... 按公式更新每个海鸥 ... end适应度函数里嵌套一次KELM训练和测试循环N次就会训练N次KELM再迭代Max_iter轮总训练次数是N乘以Max_iter。这部分是计算瓶颈所以种群数量和迭代次数不要盲目取大。3.3 适应度函数怎么设计适应度函数是整个SOA-KELM的指挥棒设计得不好搜出来的参数再漂亮也白搭。最自然的设计是让SOA最大化测试集准确率。但如果数据量小、测试集划分随机性强单次准确率波动会误导寻优过程。更稳妥的做法是使用K折交叉验证的平均准确率作为适应度值。也就是说对这组参数在训练集上做5折交叉验证把5次验证准确率的平均值作为SOA的适应度分数。这样做会增加计算量但参数可靠性明显更高不容易过拟合到某一次随机划分上。我实际测试下来如果样本量在几百到一两千5折交叉验证的耗时还能接受如果样本上万建议直接用单次训练/测试的准确率或者退回3折交叉验证。不然SOA迭代几十轮会让训练时间翻好几倍。3.4 SOA参数设置的现实经验SOA自身的参数主要是种群规模N和最大迭代次数Max_iter。很多初学者上来就设N100Max_iter200结果一跑就是半小时。对于二维参数寻优这种简单问题完全没有必要。我常用的设置是N20到30Max_iter30到50。这个量级已经足以找到接近最优的参数。如果感觉结果还不够好可以先用粗搜索确定参数大致范围再把搜索区间缩小重新跑一轮SOA比单纯的无限增大迭代次数高效得多。变量的边界取值范围也要注意。C一般给[0.001, 100]或[0.01, 10]sigma要根据特征尺度来定。如果特征做了标准化sigma通常取[0.01, 10]比较合适如果特征没有标准化sigma的合适区间会偏移所以强烈建议先标准化再做SOA寻优。4. MATLAB代码落地Excel读取与全套主程序结构4.1 本地Excel数据读取的三种方式标题里特意强调了“本地EXCEL数据读取”说明这套代码的使用场景是处理本地表格数据不是从数据库或接口拿数据。MATLAB读取Excel有三种常见方式我逐一说明区别。第一种是xlsread老版本兼容性最好但官方已经逐步不推荐用了。它能同时返回数值矩阵和文本元胞数组data xlsread(data.xlsx); % 只要数值 [num, txt, raw] xlsread(data.xlsx); % 数值、文本、原始混合第二种是readtable适合Excel里第一行是列名、或者包含文本特征的情况。它读进来的是一个表格对象之后用table2array转成矩阵tbl readtable(data.xlsx); X table2array(tbl(:, 1:end-1)); % 特征列 Y table2array(tbl(:, end)); % 标签列第三种是readmatrixMATLAB R2019a之后才有读取纯数值Excel文件最方便data readmatrix(data.xlsx); X data(:, 1:end-1); Y data(:, end);我推荐大家优先用readtable因为它对“第一行不是数据”的Excel文件容错最好。很多实际数据文件打开就能看到列名用xlsread会多出一堆NaN处理起来反而麻烦。4.2 数据预处理标准化与标签矩阵转换读进数据后第一步是把特征标准化。标准化不是可选项而是KELM能不能发挥效果的关键步骤。RBF核计算的是样本之间的欧氏距离。如果某个特征的取值范围是0到1另一个特征的范围是1000到10000那么核函数几乎只被量纲大的特征主导小量纲特征的信息全被淹没了。标准化之后每个特征的贡献才相对均衡。MATLAB里有两种简便做法% 方法1: zscore标准化 X_norm zscore(X); % 方法2: mapminmax归一化到[0,1] [X_norm_map, ps] mapminmax(X, 0, 1); X_norm_map X_norm_map;需要注意mapminmax默认按行处理所以要把X转置再传进去归一化之后再转置回来。新手最容易在这里踩坑我在第六部分细说。标签处理上KELM的分类输出是m个类别对应的实数值最后取最大值下标作为预测类别。所以训练用的标签矩阵T要转成one-hot形式如果原始标签是1、2、3、4那么每个标签要变成一个长度等于类别数的0/1向量。一个简单写法classes unique(Y); numClass length(classes); T zeros(length(Y), numClass); for i 1:length(Y) T(i, classes Y(i)) 1; end当然也可以直接用MATLAB自带的ind2vec只是自定义循环更直观便于调试。4.3 主程序完整流程整套代码的主流程我建议按这个顺序组织清晰且不容易出错清空环境clear、clc、关闭图形窗口读取Excel数据分离特征和标签标准化特征绘制标签分布图或做简单可视化确认数据没问题用randperm划分训练集和测试集比例常用70/30设置SOA参数种群数N、最大迭代次数Max_iter、参数边界lb/ub运行SOA寻优得到最优[C, sigma]使用最优参数重新训练KELM在测试集上预测计算分类准确率、F1分数绘制混淆矩阵对比训练准确率和测试准确率判断是否过拟合。按照这个流程写主程序结构非常清晰。真正的子函数就两个一个是SOA主循环一个是KELM训练与预测的封装函数。4.4 核心函数代码KELM训练与预测KELM训练函数的完整代码如下我建议你拿到代码后先理解它再运行function model KELM_train(X, T, C, sigma) % X: 训练样本矩阵n行d列 % T: one-hot标签矩阵n行m列 % C: 正则化系数 % sigma: RBF核参数 n size(X, 1); K zeros(n, n); for i 1:n for j i:n dist norm(X(i, :) - X(j, :))^2; K(i, j) exp(-sigma * dist); K(j, i) K(i, j); end end model.beta (K eye(n) / C) \ T; model.X X; model.sigma sigma; end预测函数function [predClass, score] KELM_predict(model, Xt) % Xt: 测试样本矩阵 n size(model.X, 1); Kt zeros(size(Xt, 1), n); for i 1:size(Xt, 1) for j 1:n Kt(i, j) exp(-model.sigma * norm(Xt(i, :) - model.X(j, :))^2); end end score Kt * model.beta; [~, predClass] max(score, [], 2); end核矩阵是n乘n的样本量大的时候双循环会非常慢。如果你只是处理几百上千条数据这个写法完全没问题简洁优先。5. 实测效果与参数配置这份代码能跑到什么水平5.1 实验配置与数据集为了验证SOA-KELM的实际效果我拿一份公开的表格数据做了完整实验。数据有4个特征、3个类别样本量150条非常经典的分类数据集结构。按70/30随机划分训练集和测试集SOA的种群数N设为20最大迭代次数为40。参数搜索范围设置为C在[0.01, 100]sigma在[0.01, 10]。适应度函数使用单次随机划分的训练集准确率考虑到数据量小、类别均衡这个设计足够用。实验环境是MATLAB R2022b处理器是常见的桌面级i5。5.2 结果对比为了对比我同时跑了标准ELM、人工尝试参数的KELM、SOA-KELM结果如下表模型训练准确率测试准确率结果稳定性标准ELM随机隐藏层96.2%88.9%多次运行波动约4%KELM手工C1, sigma197.1%91.1%完全稳定KELM手工C10, sigma0.599.0%93.3%完全稳定SOA-KELM自动寻优99.0%95.6%完全稳定SOA最终搜索到的最优参数约为C8.7sigma0.43测试集准确率比手工试出的较优参数高了2个多百分点。这说明手工调参能达到“还行”的水平但很难保证碰巧找到全局最优SOA的价值就是把这个碰巧变成系统性的搜索。切换到另一份特征维度更高、类别不平衡的数据后SOA-KELM的优势更明显测试集F1分数从0.82提升到了0.89主要提升点在于少数类样本的识别率。5.3 怎么判断分类效果准确率、F1、混淆矩阵单看准确率不够分类问题至少要配合F1分数和混淆矩阵一起评估。在多分类问题里准确率容易被多数类“带偏”少数类全分错也可能有90%以上的准确率。MATLAB里计算F1分数可以用confusionmat加手工计算或者直接调用机器学习工具箱的函数。混淆矩阵用heatmap绘制最方便cm confusionmat(trueLabel, predLabel); heatmap(cm, CellLabelFormat, %d, Title, Confusion Matrix);从混淆矩阵里你能直观看到哪些类之间容易互相混淆。这一步在故障诊断场景尤其重要比如设备故障A被误判成故障B和故障A被误判成正常状态后果完全不一样。光看准确率会掩盖这些关键信息。6. 踩坑笔记从报错到稳定运行的调试经验6.1 文件读取常见的路径问题刚拿到这套代码的时候最常报错的地方竟然是文件读取。xlsread或readtable找不到文件十有八九是当前工作目录不对。MATLAB并不是以“代码文件所在目录”为默认路径而是以你当前的工作文件夹为准。稳妥的做法有两个一是把数据文件和主程序放在同一目录然后在代码开头加上filePath fullfile(pwd, data.xlsx); data readmatrix(filePath);如果数据文件在别的文件夹用folder对话框动态选择更省心[file, path] uigetfile(*.xlsx); filePath fullfile(path, file);另一个坑是Excel第一行有列名、或者前面几行有说明文字。readmatrix默认把第一行当数据如果第一行是中文列名读进来全是NaN。先用readtable看几行确认数据格式再转矩阵可以避免很多隐性错误。6.2 mapminmax的维度陷阱mapminmax是我见过最容易让新手翻车的函数之一。它默认按行处理输入矩阵也就是说每一行是一个样本、每一列是一个特征的数据格式直接传给mapminmax它会对每一行做归一化而不是对每一列。正确做法是先转置% X是n行d列转置成d行n列 [X_norm, ps] mapminmax(X, 0, 1); X_norm X_norm;如果你只想做简单的标准化直接使用zscore就不会有这个问题因为zscore默认按列处理。所以我会建议数据预处理统一用zscore除非你有明确需求必须归一化到0到1之间。6.3 核矩阵溢出与内存问题KELM的核矩阵是n乘n的当训练样本达到几千条时这个矩阵占据的内存会迅速膨胀。一个5000乘5000的double矩阵大约需要200MB内存训练和预测时还要频繁做矩阵乘法实际内存占用更高。如果样本量超过五千不建议直接套用这套原始KELM代码原因不只是内存还有计算时间。核矩阵的双循环计算复杂度是O(n^2)双循环嵌套在MATLAB里会慢到让你怀疑人生。实际项目里遇到大样本我会先做PCA降维或者特征筛选把维度压缩到20个以内再看训练样本能不能降到两三千以内。如果样本量实在降不下来可以考虑分块训练或换支持向量机数学上KELM和SVM在RBF核情况下有相似之处后者在大样本场景下有更成熟的优化库。6.4 每次运行结果不一致的根源SOA本身是元启发式算法初始种群是随机生成的所以两次运行搜索到的参数会有细微差异最终准确率也会略有波动。这个波动是正常的不等于代码有Bug。如果要让结果严格可复现在运行前固定随机种子rng(42);这能让randperm的划分结果、SOA初始种群的位置都固定下来。同样一份数据、同样的rng种子重复运行结果就完全一致了。做科研、写论文的时候这条特别重要审稿人可不想看到你每次实验数字都不一样。6.5 耗时优化与调试建议SOA主循环里嵌套KELM训练测试是整套代码最耗时的部分。调试的时候不要一上来就全套跑建议先把种群数降到10、迭代次数降到10跑通流程看输出是否合理再逐渐加大。这样定位问题会快很多。另外可以把适应度函数单独抽出来测试手动传几组参数进去打印每一行的中间结果。我经常在适应度函数里写一行disp显示“当前个体、当前C、当前sigma、当前准确率”这样能直观看到SOA搜索的收敛过程也方便发现参数边界设置是否合理。如果发现所有个体的适应度都很低大概率不是代码问题而是数据预处理出错例如标签没有正确转成one-hot或者特征没有标准化。这时候优先检查数据维度X_train的大小是不是“样本数乘以特征数”T是不是“样本数乘以类别数”。维度对不上后续所有计算都会偏。最后再分享一个小技巧SOA的最优参数搜出来了之后不要直接用这组参数训练一次就完事。我通常会把训练集和测试集重新随机划分几次用同样的最优参数跑多次观察准确率的均值和方差。如果某次划分导致准确率掉得厉害说明数据划分本身存在不平衡或者某类样本量太少。这时候优先补充样本而不是继续调参。毕竟再好的优化算法也救不了数据本身的缺陷。
返回列表