简介:SVM支持向量机故障诊断与分类预测的完整Matlab源码包,基于西储大学轴承诊断数据经特征提取后的数据集,可在Matlab2023环境中直接运行。资源主要面向需要快速开展轴承故障分类研究的科研人员、研究生以及工程应用开发者,系统解决支持向量机建模、训练、验证与结果可视化全流程问题。压缩包共包含七十一个文件,大小约一点三一兆字节,内容涵盖Matlab主脚本、LIBSVM-3.3完整工具箱(包括C/C++源文件、Python辅助脚本、可直接调用的MEX编译文件)、示例数据以及可视化结果图片,目录中还区分了tools、java、python、matlab等子模块,便于按需查阅。资源已有153人学习,体量小巧但功能闭环。借助该包可直接获得完整的SVM故障诊断方案:SVM.m训练脚本完成特征训练与分类,data.mat提供轴承特征数据集,混淆矩阵绘制函数支持精度评价,结合libsvm内置工具还能实现参数寻优与模型对比,帮助使用者快速复现实验并迁移到自身数据,有效缩短算法验证周期。
1. SVM故障诊断不是玄学:中小样本下比深度学习更稳的Matlab方案
拿到一批轴承振动数据,让你做SVM支持向量机故障诊断/分类预测,多数人的第一反应是找一段源码直接跑,看到准确率就收工。但真实产线上这个流程翻车的概率比你想象的高:特征没提取对、核参数没寻优、标签不平衡,任何一个环节都能让准确率从95%掉到65%。这篇文章把SVM故障诊断从原始信号到Matlab完整源码的每一步拆开讲清楚,覆盖特征工程、核函数选型、参数寻优、多分类实现和落地部署,读者可以照着复现。适合做设备健康管理、工业机器人产线维护的工程师,也适合用Matlab做故障诊断方向毕设的同学。
2. 把振动信号变成SVM能吃的特征矩阵:时域、频域与小波包
SVM不直接吃原始信号。你给它一段1024点的原始波形,它连该看哪都不知道——支持向量机分类预测的前提是输入向量在特征空间里近似线性可分,而原始采样点的时间序列在特征空间里基本是混沌状态。所以故障诊断的第一步永远是特征提取。常见做法是从原始信号里算出若干统计量拼成一个特征向量,每个样本一行,标签一列,喂给SVM。公开数据集里凯斯西储CWRU轴承数据和西安交大XJTU轴承数据是两种最常见的起点,拿它们跑通流程,再换产线自采数据。
2.1 时域特征先打底:12个统计特征哪些对故障敏感
时域特征是直接从原始振动信号x(n)上算的,计算量小,适合做第一层筛选。做滚动轴承故障诊断时,我一般会算12个:均值、峰值、绝对平均值、方根幅值、均方根、标准差、峭度、偏度、峰值因子、脉冲因子、裕度因子、波形因子。
这几个特征对应着不同的故障敏感度。均方根对平稳磨损状态敏感,但早期点蚀产生的冲击在RMS里被平均掉了;峭度对早期冲击非常敏感,局部故障一出现峭度立刻飙升,但它的缺点是扛不住随机干扰,偶尔一个噪声尖峰就能把峭度顶上去。所以实际使用里我不会只靠一两个特征,而是把时域统计量做成一个特征组。
特征计算在Matlab里是数组运算,不是不能用循环,而是没必要。信号长度N已知,x是列向量,一段段算完拼在一起即可。这样算出来的特征值分布范围差异很大——RMS可能是零点几,峭度可能是三到八,频域能量可能是几十上百,这种量纲差异会在后面归一化环节埋雷,具体踩坑记录放到第五章展开。
2.2 频域与小波包特征:把故障频率从噪声里抠出来
时域特征之外必须补频域特征,否则很多故障分不开。滚动轴承有典型的故障特征频率——外圈BPFO、内圈BPFI、滚动体BSF,这些频率和转频、节径、接触角相关。直接从频谱里读故障特征频率在理想工况下没问题,但转频波动和噪声干扰会让峰值位置漂移,读数不可靠。
我一般对每段信号做FFT,然后提取三个频域统计量:频谱重心频率、均方频率、频率方差。这三个量描述频谱的集中位置和分散程度,轴承内圈故障和外圈故障在这种特征下往往能拉开距离。更常用的做法是小波包分解。对信号做三层小波包分解得到八个频带,计算每个频带的能量占比。轴承出现局部故障时,能量会向特定频带集中,这个“能量分布指纹”比单纯频谱统计量可靠。Matlab里wpdec和wprcoef两个函数就能实现,需要Wavelet Toolbox。
2.3 特征提取的Matlab代码:23维特征向量一段函数跑通
下面这段函数输入一段原始信号,输出一行特征向量。实际使用时把数据集里每个样本调一次这个函数,拼成总特征矩阵。
function feat = extract_features(x, fs) % x: 单段振动信号(列向量) % fs: 采样率,单位 Hz % feat: 1xN 特征向量 x = x(:); % 强制列向量 N = length(x); % ---------- 时域特征 ---------- rms_val = sqrt(mean(x.^2)); % 均方根 peak_val = max(abs(x)); % 峰值 kurt_val = kurtosis(x); % 峭度 skew_val = skewness(x); % 偏度 std_val = std(x); % 标准差 mean_val = mean(x); % 均值 absmean = mean(abs(x)); % 绝对平均值 crest = peak_val / rms_val; % 峰值因子 impulse = peak_val / absmean; % 脉冲因子 shape = rms_val / absmean; % 波形因子 sqrt_amp = (mean(sqrt(abs(x))))^2; % 方根幅值 margin = peak_val / sqrt_amp; % 裕度因子 % ---------- 频域特征 ---------- X = fft(x); % 复数频谱 P = abs(X(1:floor(N/2))).^2; % 单边功率谱 f = (0:floor(N/2)-1)' * fs / N; % 频率轴 P = P / sum(P); % 归一化能量 fc = sum(f .* P); % 重心频率 msf = sum(f.^2 .* P); % 均方频率 fvar = sum((f - fc).^2 .* P); % 频率方差 % ---------- 小波包能量占比(三层,8个频带) ---------- wpt = wpdec(x, 3, 'db4'); % db4小波包分解 E = zeros(1, 8); for i = 1:8 r = wprcoef(wpt, [3 i-1]); % 重构第3层第i个频带 E(i) = sum(r.^2); end E = E / sum(E); % 能量占比 feat = [mean_val, rms_val, peak_val, std_val, kurt_val, skew_val, ... absmean, crest, impulse, shape, sqrt_amp, margin, ... fc, msf, fvar, E]; end这段代码把三类特征拼成一个向量:前面12个是时域统计量,中间3个是频域统计量,最后8个是小波包能量占比,总共23维。wpdec和wprcoef需要Wavelet Toolbox,如果你的Matlab装了精简版,可以把小波包那几行注释掉,用前15维先跑通全流程再决定要不要加回。
采样率fs只用于计算频率轴,不同采样率下重心频率数值会变,但SVM对特征是否带单位不敏感,只要所有样本用同一套提取流程即可。db4中的4表示滤波器长度,滚动轴承故障诊断里db4是常用起点,换db8或sym5不会本质改变能量分布格局。这一段函数是后面所有流程的地基,特征提取的质量直接决定SVM分类预测的上限。
3. 核函数与参数寻优:决定SVM分类预测效果的c和g
特征矩阵有了,接下来面临两个选择:用哪种核函数,以及核函数的参数取多少。很多人把SVM当成黑匣子,fitcsvm默认参数跑完就收工。但SVM支持向量机故障诊断的精度瓶颈往往不在模型原理,而在参数和特征没配对。RBF核在归一化后的特征上表现稳定,但它的gamma参数设不好,模型要么过拟合要么欠拟合,这是最常见的翻车现场。
3.1 四种核函数怎么选:线性、RBF、多项式、Sigmoid的适用边界
核函数的本质是隐式地把样本映射到高维空间。线性核适合特征维度高、样本量中等且本来就近似线性可分的情况——特征维度一高,非线性映射带来的收益就不明显,反而增加过拟合风险。多项式核能表达非线性边界,但阶数高了数值容易溢出,Matlab里Polynomial核经常因为内积过大出现NaN。
RBF核是故障诊断里的默认选择。它的表达式是exp(-g·||x1-x2||^2),只有一个gamma参数要调,映射后的空间维度趋近无穷,只要gamma合适,理论上能逼近任意决策边界。Sigmoid核行为类似两层的神经网络,在某些参数组合下输出异常,实际工程里我很少用它。
我的选型建议是:先归一化特征,然后用线性核跑一遍拿准确率基线;再用RBF核做网格寻优。如果RBF比线性核高出的精度不超过两个百分点,就选线性核——训练快、可解释性好,产线师傅也服气。如果线性核在验证集上明显拉胯,再上RBF。SVM和CNN相比,优势在中小样本下更明显:不需要几千张图做预训练,几十个样本就能训练出可用的分类器。
3.2 网格搜索加K折交叉验证:c和g怎么找最稳
RBF核有两个参数需要调:惩罚系数c和gamma。c是误分类惩罚权重,c越大模型越不愿意犯错,但也越容易过拟合;gamma是RBF宽度的倒数,gamma越大每个支持向量的影响范围越小、决策边界越曲折,gamma过小则边界过于平滑,两类可能直接混在一起。
最稳的参数寻优方式是网格搜索配合K折交叉验证。把c和g分别取指数网格,搜索范围覆盖2^-5到2^10:
rng(42); c_list = 2.^(-5:1:10); g_list = 2.^(-10:1:5); acc_mat = zeros(length(c_list), length(g_list)); for i = 1:length(c_list) for j = 1:length(g_list) % 固定一组c,g做5折交叉验证 acc = cross_validate_svm(features, labels, c_list(i), g_list(j)); acc_mat(i,j) = acc; end end % 找最高准确率对应的参数 [max_acc, idx] = max(acc_mat(:)); [i_best, j_best] = ind2sub(size(acc_mat), idx); best_c = c_list(i_best); best_g = g_list(j_best); fprintf('最优参数: c=%.4f, g=%.4f, cv_acc=%.2f%%\n', ... best_c, best_g, max_acc*100);cross_validate_svm是一个自定义函数,内部做5折切分、训练、预测、算准确率。网格的跨度按指数取,是因为c和g对精度的响应在指数尺度上近似均匀,线性网格会导致小数值区太密、大数值区太疏。网格范围不是固定的,如果最优值落在边界上,就把网格向那一侧扩展再搜一轮。
交叉验证折数一般取5或10。数据量大用5折省时间,数据量小用10折更稳。交叉验证只在训练集内部做,测试集全程不能参与参数选择,否则测试集就失去了独立评估的意义,严格说这叫数据泄漏,第五章有具体案例。
3.3 用fitcsvm还是LIBSVM:两条路线怎么选
Matlab里做SVM分类预测有两条路:一是自带的fitcsvm,属Statistics and Machine Learning Toolbox;二是台湾大学林智仁教授的LIBSVM第三方包。很多网上流传的老代码是LIBSVM版本,新版Matlab里跑这些代码经常报错,因为LIBSVM需要自己用mex编译。
fitcsvm的好处是官方维护、接口稳定、和Coder工具链集成好,训练好的模型可以直接生成C代码用于部署。不利之处是它天生是二分类器,多分类要自己包一层One-vs-One或One-vs-All,Matlab里fitcecoc封装好了这些策略,但理解成本比LIBSVM高一点。
LIBSVM的好处是把多分类、交叉验证、网格寻优封装成现成函数,svmtrain和svmpredict一行调用。代价是编译环境配置对新手是个门槛。我做项目的选择是:毕设或快速验证用LIBSVM省事;需要产线部署或Simulink集成用fitcsvm加fitcecoc,官方链路少踩坑。两者对比整理如下:
| 对比项 | fitcsvm / fitcecoc | LIBSVM |
|---|---|---|
| 依赖 | Statistics and ML Toolbox | 需自行编译mex |
| 多分类 | fitcecoc封装OVO/OVA | 内置多分类 |
| 交叉验证 | crossval方法 | svmtrain自带-v参数 |
| 模型导出 | Coder生成C代码 | 需自己写接口 |
| 参数设置 | KernelScale、BoxConstraint | -c、-g直传 |
4. 在Matlab中跑通SVM故障诊断源码:数据划分到混淆矩阵
前面特征已经提取完毕,特征矩阵features和标签labels已经就绪。第四章直接给一份可完整跑通的训练预测流程,按“数据划分→训练→评估”的顺序拆开。每一步都有对应代码,把注释读一遍就知道每个变量在干什么。
4.1 数据划分:分层抽样保证小故障类不缺席
故障诊断数据集的标签分布往往不均匀。正常样本可能几千条,每种故障样本几百条。如果直接randperm随机打乱划分,运气差时某类故障在训练集里只剩一点点样本,模型压根没见过这个类的全貌。
正确做法是分层抽样,按类别比例分别在每个类内部随机抽取,保证训练集和测试集中每个类的占比与总体一致。Matlab里cvpartition直接支持分层划分:
rng(42); % 固定随机种子,保证结果可复现 cvp = cvpartition(labels, 'Holdout', 0.3); % 30%做测试集 idx_train = cvp.training; idx_test = cvp.test; X_train = features(idx_train, :); y_train = labels(idx_train); X_test = features(idx_test, :); y_test = labels(idx_test); % 检查划分后各类样本数 tabulate(y_train) tabulate(y_test)cvpartition的Holdout方式指定测试集比例,0.3表示留30%做测试,内部按类别分层,不会出现某个小类在训练集里彻底消失的情况。rng(42)是血泪经验:不固定随机种子,每次跑结果都不一样,你没法判断改动特征提取代码是变好了还是随机波动造成的。
4.2 训练与预测:fitcecoc多分类代码与LIBSVM对照
标签是数值型(1、2、3)还是字符型('normal'、'inner'、'outer')都行,fitcecoc会自动识别。但标签必须是离散类别编号,不能当回归问题去拟合。
% fitcecoc多分类,内部默认One-vs-One策略 t = templateSVM('KernelFunction', 'rbf', ... 'KernelScale', 1/sqrt(2*best_g), ... 'BoxConstraint', best_c, ... 'Standardize', true); mdl = fitcecoc(X_train, y_train, 'Learners', t, ... 'Coding', 'onevsone'); % 预测测试集 y_pred = predict(mdl, X_test); % 整体准确率 acc = sum(y_pred == y_test) / length(y_test) * 100; fprintf('测试集准确率: %.2f%%\n', acc);注意:fitcsvm的KernelScale与LIBSVM的gamma不是简单倒数关系。RBF核在fitcsvm里写作exp(-||xi-xj||^2/(2·sigma^2)),而LIBSVM写作exp(-g·||xi-xj||^2),所以sigma=1/sqrt(2·g)。网格寻优得到g后,KernelScale要填1/sqrt(2·g),这一点漏掉模型效果直接不对。
predict是官方版本预测的标准接口。如果你用LIBSVM,等价流程是这样:
% LIBSVM等价流程(需先编译好mex文件) model = svmtrain(y_train, X_train, ... '-s 0 -t 2 -c 4 -g 0.25 -q'); y_pred = svmpredict(y_test, X_test, model);s表示SVM类型,0是C-SVC分类;t是核函数类型,2对应RBF;c和g就是前面寻优得到的惩罚系数和gamma。LIBSVM的输入格式是标签在前、特征矩阵在后,和fitcsvm正好相反,这也是从网上抄代码时最容易搞反的地方。
4.3 评估不能只看准确率:混淆矩阵与每类召回率
故障诊断里准确率经常骗人。假如正常类占测试集90%,把全部样本判成正常,准确率也是90%,但这个模型毫无用处。所以训练完必须看混淆矩阵和每类的召回率、精确率。
% 混淆矩阵 [C, order] = confusionmat(y_test, y_pred); % 每类召回率、精确率、F1 num_classes = length(order); recall = zeros(num_classes, 1); precision = zeros(num_classes, 1); for k = 1:num_classes recall(k) = C(k,k) / sum(C(k,:)); precision(k) = C(k,k) / sum(C(:,k)); end F1 = 2 * precision .* recall ./ (precision + recall); % 可视化 confusionchart(y_test, y_pred);confusionmat返回的C矩阵第k行表示真实类别k的样本被分到了哪些预测类别,对角线是正确分类。召回率看的是这个故障别漏掉,精确率看的是报出来的故障别报错。设备维护场景下漏报比误报更致命——漏报意味着故障在恶化,误报最多是停机检查一次。
如果某个类召回率明显低于其他类,不要急着调SVM参数,先回去检查这个类的样本量是不是太少、特征是不是和另一类高度重叠。内圈故障和滚动体故障在早期阶段时域特征几乎一样,这种情况调c和g没用,得回去补频带能量特征。
5. SVM故障诊断的5个常见坑:翻车现象、根因与解决
这一章写的都是实际跑SVM故障诊断项目时反复遇到的坑,按“现象→原因→解决”的格式记录。每一条都花过时间排查,看完能省下不少弯路。
5.1 样本不平衡导致小故障类全被吞掉
现象:测试集总准确率95%,打开混淆矩阵发现,某一类训练样本少的故障几乎全被预测成其他类,该类召回率只有40%。
原因:SVM的优化目标是整体间隔最大化,样本占比大的类在约束条件里占多数,决策边界为了迁就多数类把少数类样本牺牲掉了。
解决:给少数类提高先验权重,让误分少数类的代价变大。常见做法是按样本量的反比设置Prior:
% 按每类样本数反比设置先验,少数类获得更高权重 [~, ~, ic] = unique(y_train); class_counts = accumarray(ic, 1); prior = 1 ./ class_counts; prior = prior / sum(prior); t = templateSVM('KernelFunction', 'rbf', ... 'KernelScale', 1/sqrt(2*best_g), ... 'BoxConstraint', best_c, ... 'Standardize', true, ... 'Prior', prior); mdl = fitcecoc(X_train, y_train, 'Learners', t, 'Coding', 'onevsone');故障诊断里我倾向于权重方案。过采样容易引入重复样本导致过拟合,欠采样会丢掉多数类信息,权重方案在工程实现上最直接。
5.2 特征没归一化,RBF核直接失效
现象:用RBF核训练后准确率比线性核还低,甚至所有样本被预测成同一类。
原因:RBF核里的距离计算对特征数值范围极敏感。某个特征量级是10^4,另一个特征量级是3,欧式距离被大数值特征主导,小数值特征等于报废。
解决:所有特征在训练前做标准化。标准化的均值和标准差只能用训练集计算,测试集复用同一组统计量,不能把测试集和训练集混在一起算,否则测试集独立性就没了。fitcsvm的Standardize参数就是干这个的,LIBSVM要自己手动标准化:
% 手动Z-score标准化 mu = mean(X_train); sigma = std(X_train); X_train = (X_train - mu) ./ sigma; X_test = (X_test - mu) ./ sigma;5.3 交叉验证结果好但测试集崩盘:数据泄漏
现象:5折交叉验证准确率98%,一换到测试集只剩75%,差距大得离谱。
原因:常见泄漏有两处。一是特征提取在全部数据上先做完再划分训练测试集,比如小波包分解的某些归一化步骤用了全数据集统计量;二是网格寻优时用全量数据做交叉验证,最优参数已经偷看了测试集分布。
解决:特征提取逐样本独立处理,不做跨样本统计;数据划分在任何特征工程之前完成;交叉验证只在训练集内部做。还有一个隐蔽泄漏是特征选择也用全量数据筛选,和参数寻优泄漏是同一个问题。
5.4 多分类用One-vs-One还是One-vs-All
现象:用fitcecoc默认设置训练,某两类故障总是互相混淆,或训练时间异常长。
原因:fitcecoc默认用One-vs-One,类别数为N时训练N(N-1)/2个二分类器。5类就是10个,10类就是45个,数据量稍大训练时间就翻倍。One-vs-All只要N个分类器,但每个分类器面对的负样本是其他所有类混在一起,决策边界容易被干扰。
解决:故障类别少(5类以内)用OVO,边界更精细;类别多(8类以上)或训练时间紧张时换OVA。fitcecoc里改一个参数:
mdl = fitcecoc(X_train, y_train, ... 'Learners', t, ... 'Coding', 'onevsall');5.5 训练集有NaN或无穷值,模型直接摆烂
现象:训练没报错,预测结果全是NaN,或者fitcsvm报“Kernel function returned NaN or Inf”。
原因:特征提取时某个样本的FFT或小波包重构产生了NaN;或者标准化时某特征方差为0,除以0产生Inf。这些NaN在fitcsvm里不会自动报错,而是直接传染到核矩阵。
解决:训练前先检查特征矩阵完整性:
assert(~any(isnan(features(:))), '特征矩阵含NaN'); assert(~any(isinf(features(:))), '特征矩阵含Inf'); % 找出方差为0的特征列 zero_var_cols = var(features) < eps; fprintf('%d 列特征方差为0,将剔除\n', sum(zero_var_cols)); features(:, zero_var_cols) = [];方差为0的特征列对所有样本恒为常数,对分类没有信息量,剔除后模型反而更稳。处理NaN的根本手段还是在特征提取阶段加保护,FFT之前检查信号长度,小波包重构后检查非有限值。
6. 让模型从离线脚本走进产线:模型导出、增量更新与一类SVM
模型在测试集上准确率过了95%,接下来要解决的是它怎么从Matlab脚本变成产线上能用的东西,以及故障类型变化了怎么维护。这章讲三个进阶方向,都是SVM故障诊断落地时绕不开的环节。
6.1 用Matlab Coder把fitcecoc模型导成C代码
如果产线控制器用C语言开发,fitcsvm训练好的模型可以一键导出。Matlab Coder支持对分类对象生成可移植C代码,入口函数写法如下:
save('svm_model.mat', 'mdl'); function label = predict_label(X) %#codegen mdl = coder.loadDeepLearningNetwork('svm_model.mat'); label = predict(mdl, X); end生成C代码后注意特征提取逻辑也要移植过去,特征提取用到的均值、标准差、小波包滤波器系数都要固化在C代码里,不能在部署端重新算,否则统计量对不上,预测直接偏掉。
6.2 新故障类型出现:重训还是增量更新
产线最现实的问题是:半年后出现了一种没见过的新故障,模型预测不可靠。常见做法有两种。一是把新故障样本加入训练集,重新走网格寻优并重训,在小样本下最稳,缺点是每次要重跑全部流程。二是增量学习,但fitcsvm本身不支持增量训练,自己实现SMO增量更新复杂度高,数据量不大时完全没必要。
我一般建议:故障库未稳定时,每次有新故障就全量重训,几千条数据在Matlab里训练也就几分钟;故障库稳定后再固化模型部署。
6.3 只有正常样本时:一类SVM做异常检测
有些场景根本没有故障样本——新设备刚上线还没坏过。这时候做不了多分类,能做的是一类分类,用fitcsvm训练One-Class SVM,只吃正常样本就能建一个“正常边界”:
mdl_oc = fitcsvm(X_train_normal, ones(size(X_train_normal,1),1), ... 'KernelFunction', 'rbf', ... 'KernelScale', 1/sqrt(2*best_g), ... 'OutlierFraction', 0.05); % 预测:1表示正常,-1表示异常 y_outlier = predict(mdl_oc, X_test);OutlierFraction是预设的异常比例,根据现场先验估计,0.05表示接受5%的正常样本被误报为异常。这类模型适合设备初期预警,等积累了足够故障样本再切换成多分类模型。做产线项目这些年,我最大的教训就是:不要把SVM当成输出准确率的黑匣子,参数寻优、特征选择、数据划分任何一个环节偷懒,模型都会在你看不见的地方翻车。希望帮到你。
本文还有配套的精品资源,点击获取