搞故障诊断这些年,CNN-SVM组合模型是我用下来最顺手的一套搭配之一。轴承、齿轮、电机这类旋转机械的故障诊断,本质上就是拿振动信号做分类——把正常状态、内圈故障、外圈故障、滚动体故障这些类别区分开。CNN负责自动提取特征,SVM负责最后的分类决策,两者一拼,效果通常比单独用CNN或单独用手工特征加SVM都要好用。
这篇内容适合正在做设备故障诊断、滚动轴承故障识别、旋转机械状态监测相关课题的工程师和研究生,特别是手上有Matlab环境、想快速搭一套CNN-SVM模型但不想被一堆理论绕晕的人。我会把整个方案从设计思路、数据准备、网络结构、代码实现到坑点排查完整过一遍,尽量把每步为什么要这么做讲清楚,方便直接复现到自己的数据上。
1. 为什么把CNN和SVM撮合到一起
1.1 传统故障诊断的路子卡在哪
传统的故障诊断流程是这样的:先对振动信号做特征提取,时域上算均方根值、峰值因子、峭度,频域上做幅值谱、功率谱分析,或者用小波包、经验模态分解做时频变换,然后把手工特征拼成一个向量,丢给SVM、BP网络或者随机森林去分类。
这条路线最大的瓶颈在特征工程。特征得靠人的经验去设计,换一种故障类型、换一台设备,原来那套特征可能就不灵了。尤其早期故障阶段,信号里的故障特征非常微弱,冲击成分被噪声盖住,手工统计量很难稳定地区分不同故障。我做过的某次实验里,用传统时域特征分类,正常样本和轻微磨损样本的重叠区域很大,分类器怎么调都分不开。这就是特征表达能力不够的表现。
1.2 纯CNN在小样本上的短板
CNN的价值在于自动学习特征,卷积核会自己去学哪些局部模式对分类有用,比如周期性冲击、转频谐波这类故障敏感成分。你不需要手工设计特征,只要把原始振动信号喂进去就行。
但纯CNN问题也很明显。首先是参数多,训练慢,对样本量要求高。故障诊断场景跟图像分类不一样,设备不可能一直坏着让你采集大量样本,多数数据是靠台架实验模拟故障得到的,一个工况下往往只有几百到几千条样本。CNN在这种量级下很容易过拟合,训练集准确率接近100%,测试集一塌糊涂。
其次,CNN最后通常接的是softmax分类器。softmax在样本充足、类别均衡时表现不错,但小样本、类别不平衡的情况下,它的决策边界泛化能力不一定好。我实测过几次,同类数据下softmax的测试准确率比SVM低2到5个百分点是常事。
1.3 组合模型的分工逻辑
CNN-SVM的核心思路是分工合作:
- 前段是CNN,把原始振动信号逐步卷积、池化,学出高层次的深度特征;
- 把CNN最后一个全连接层(或者池化层)的输出向量取出来,当做"自动提取的特征";
- 用SVM替代softmax去做最终分类。
有人会问,这不就是把CNN当特征提取器用吗?对,就是这么回事。但这一步在工程上非常有效。CNN在小样本下学到的特征不一定线性可分,而SVM通过核函数能把特征映射到高维空间找最优超平面,对中等维度特征、小样本场景的泛化能力通常更强。
背后的原理是SVM遵循结构风险最小化,不单纯追求训练集分类正确,而是同时控制模型复杂度和分类误差。相比之下,softmax分类器在小样本下更容易把噪声模式也学进去。简单说:CNN负责把原始信号变成好用的特征,SVM负责在特征空间里画出更稳的分类边界,两者各干各擅长的活。
2. 整体方案设计与前期准备
2.1 数据从哪来、怎么处理
做故障诊断第一步是准备振动信号数据。常见来源包括公开的轴承故障数据集,或者实验室里用电火花、激光加工在轴承部件上人为制造缺陷后采集的信号,也有工厂现场从状态监测系统里录下来的历史数据。
拿到原始振动信号后,最核心的预处理操作是分帧切片。振动信号是一条很长的时间序列,没法直接整条丢给网络,需要用滑动窗口切成等长的样本。
我常用的参数是:采样率12.8kHz,窗口长度1024点,滑动步长512点,也就是50%重叠。这样做有两个好处:一是样本数量大大增加,小样本问题缓解不少;二是相邻窗口有重叠,故障特征不会因为恰好落在窗口边界附近而被切碎。
切片之后,每条样本是一行1024点的向量。网络输入格式上,我习惯把它组织成[1×1024]的矩阵,也就是高度为1、宽度为1024的"伪图像",这样可以直接用二维卷积层处理,Matlab里卷积层也更容易定义。
2.2 样本划分的讲究
数据划分比很多人想的更重要。常见做法是按比例随机切分训练集和测试集,比如70%训练、30%测试。但这里有个容易踩的坑:如果同一段连续信号切出来的窗口被随机分配到训练集和测试集两边,相邻窗口高度相关,会导致测试准确率虚高。模型可能背下了窗口序列的局部模式,而不是真正学到了故障特征。
更严格的划分应该按"段"分开:比如有50段独立采集的信号,那就按段来划分,30段训练、20段测试,而不是按窗口来随机分。这样测试结果才更贴近实际部署的效果。我在实际项目中吃过这个亏,按窗口随机分的时候准确率98%,按段分开之后掉到了92%,后者才是真实水平。
2.3 网络结构怎么定
对小样本的1D振动信号,网络不宜太深太宽,否则必然过拟合。我用得比较顺的结构是:
- 输入层:1×1024
- 卷积层1:1×3卷积核,16个通道,same填充
- 批归一化 + ReLU
- 最大池化层:1×2,步长2
- 卷积层2:1×3卷积核,32个通道,same填充
- 批归一化 + ReLU
- 最大池化层:1×2,步长2
- 展平
- 全连接层:64维
- ReLU + Dropout(比例0.3)
- 全连接层:类别数
- Softmax + 分类层
注意一个点:特征提取的位置我选的是第一个全连接层的输出,也就是64维向量。为什么不是更前面的卷积层输出?卷积层输出维度太高(比如几百上千维),SVM在高维稀疏特征上容易过拟合,训练也慢。64维是一个适中的尺度,既保留了高层语义信息,又不会让SVM输入维度过大。
整个网络的卷积核都取1×3这种小尺寸。振动信号是时间序列,局部特征沿着时间轴分布,小卷积核足够捕捉冲击脉冲这类短时模式,同时参数少,对小样本友好。
3. SVM接管分类的几个关键技术点
3.1 特征从哪一层取
前面说了我会从第一个全连接层取特征,但这不是唯一选择。实际调参时可以对比几个候选层的效果:
| 特征提取位置 | 维度 | 特点 |
|---|---|---|
| 池化层输出(展平后) | 较高 | 保留更多空间信息,特征更细,但维度大、冗余多 |
| 第一个全连接层 | 适中 | 语义特征浓缩,常用,SVM效果好 |
| 最后一个全连接层(softmax前) | 较少 | 最贴近分类目标,但可能已经丢失了细节区分度 |
我实测下来,第一个全连接层是最稳的。太靠前的特征太原始,太靠后的特征已经为softmax做了"定向压缩",丢失了一部分对SVM有用的判别信息。建议你拿到自己数据后,把这三个位置都跑一遍对比,花不了多少时间。
3.2 多分类策略与核函数选择
故障诊断通常是多分类问题,SVM本身是二分类器,多分类要靠策略组合。Matlab里fitcecoc用的是纠错输出编码框架,默认对多类问题采用一对一编码。这个方案在类别数不多(比如4到6类)时非常可靠,每一对类别单独训练一个SVM,投票决定最终类别。
核函数方面,线性核和高斯核(RBF)是最常用的两个选择。信号特征经过CNN提取后,往往是非线性的分布,RBF核的表达能力更强,适用面更广。
RBF核有两个关键参数:盒子约束C和核尺度gamma。C控制对误分类的惩罚,太大容易过拟合;gamma控制高斯核的宽度,太大会导致每个样本都变成孤立的支持向量。我在Matlab里习惯先设'KernelScale','auto'跑一遍,然后根据交叉验证结果手动微调。
3.3 特征标准化:最容易忽略的步骤
这一步我几乎每次都会强调,因为太容易被忽略:CNN提取的特征喂给SVM之前,必须做标准化。
CNN中间层的特征值范围很不统一,有的维度集中在0到1附近,有的维度可能到几十。SVM的RBF核基于样本间距离计算相似度,数值范围大的维度会主导距离计算,把小范围维度的贡献给压掉。这不是细节问题,是直接影响结果的问题。
Matlab里一句代码搞定:
featuresTrain = (featuresTrain - mean(featuresTrain)) ./ std(featuresTrain); featuresTest = (featuresTest - mean(featuresTrain)) ./ std(featuresTrain);注意测试集标准化用的均值和方法必须来自训练集,不能拿测试集自己算,否则等于把测试集信息泄露到训练流程里,评估结果会虚高。
4. Matlab实操:从数据到结果的完整流程
4.1 环境与数据准备
需要Matlab的Deep Learning Toolbox和Statistics and Machine Learning Toolbox两个工具箱,前者用来搭CNN和提取特征,后者用来训练SVM和做交叉验证。
假设你手里已经有一段整理好的数据文件,我习惯的存储格式是:
signals:样本矩阵,每行一条样本,长度1024;labels:类别标签,可以是 categorical 类型或者整数,比如1代表正常、2代表内圈故障、3代表外圈故障、4代表滚动体故障。
加载和划分的代码:
load('bearing_data.mat'); signals = signals; % [N x 1024] double labels = categorical(labels); % 转为类别型 % 按样本点随机划分训练/测试集 cv = cvpartition(labels, 'HoldOut', 0.3); trainIdx = training(cv); testIdx = test(cv); XTrain = signals(trainIdx, :); YTrain = labels(trainIdx); XTest = signals(testIdx, :); YTest = labels(testIdx); % 转换成深度学习网络需要的4维格式 XTrain = reshape(XTrain, [1, 1024, 1, size(XTrain, 1)]); XTest = reshape(XTest, [1, 1024, 1, size(XTest, 1)]);深度学习工具箱里,二维卷积层接受的数据格式是[高度×宽度×通道数×样本数]。把每个样本看成一张高为1、宽为1024的图,通道数是1,所以reshape成[1, 1024, 1, N]。
4.2 CNN训练与特征提取
搭网络和训练的代码:
numClasses = numel(unique(labels)); layers = [ imageInputLayer([1 1024 1], 'Normalization', 'zscore') convolution2dLayer([1 3], 16, 'Padding', 'same') batchNormalizationLayer reluLayer maxPooling2dLayer([1 2], 'Stride', [1 2]) convolution2dLayer([1 3], 32, 'Padding', 'same') batchNormalizationLayer reluLayer maxPooling2dLayer([1 2], 'Stride', [1 2]) fullyConnectedLayer(64, 'Name', 'feature_layer') reluLayer dropoutLayer(0.3) fullyConnectedLayer(numClasses) softmaxLayer classificationLayer]; options = trainingOptions('adam', ... 'MaxEpochs', 40, ... 'MiniBatchSize', 64, ... 'InitialLearnRate', 0.001, ... 'ValidationData', {XTest, YTest}, ... 'ValidationFrequency', 20, ... 'Plots', 'training-progress', ... 'Verbose', false); net = trainNetwork(XTrain, YTrain, layers, options);给全连接层起个名字'feature_layer'这个细节很重要,后面提取特征要用这个名字索引。训练时如果只关注结果不想看图,可以把'Plots'关掉,省一点显示开销。
训练完成后,提取特征:
featuresTrain = activations(net, XTrain, 'feature_layer'); featuresTest = activations(net, XTest, 'feature_layer'); % 转为二维矩阵:样本数×特征维数 featuresTrain = squeeze(featuresTrain)'; featuresTest = squeeze(featuresTest)';activations函数可以指定层名,返回的是该层所有样本的激活值,维度和中间层的结构有关。展平并转置后,就是标准的数据矩阵格式。
4.3 SVM训练与评估
特征准备好之后,先标准化再训练SVM:
% 标准化(统计量只从训练集计算) mu = mean(featuresTrain); sigma = std(featuresTrain); featuresTrain = (featuresTrain - mu) ./ sigma; featuresTest = (featuresTest - mu) ./ sigma; % 多分类SVM,一对一策略,RBF核 t = templateSVM('KernelFunction', 'rbf', 'KernelScale', 'auto'); svmModel = fitcecoc(featuresTrain, YTrain, 'Learners', t); % 预测与评估 YPred = predict(svmModel, featuresTest); acc = mean(YPred == YTest); fprintf('测试集准确率: %.2f%%\n', acc * 100); % 混淆矩阵 figure; confusionchart(YTest, YPred);评估时不要只看总准确率,混淆矩阵信息量更大。它能告诉你哪些类别之间容易混淆。比如外圈故障和正常状态可能有少量误判,这在物理上可能是早期故障特征太弱导致的,需要从数据或特征层面想办法,而不是盲目调分类器参数。
4.4 参数怎么调:我实际试过的组合
训练参数这块我踩过不少坑,直接给一组比较稳的起始值:
| 参数 | 推荐值 | 说明 |
|---|---|---|
| 优化器 | adam | 小样本下收敛稳定,默认动量参数够用 |
| 初始学习率 | 0.001 | 太大训练震荡,太小收敛慢 |
| BatchSize | 32~64 | 样本少可以降到16 |
| Epoch | 30~60 | 配合早停,看验证曲线定 |
| 卷积核大小 | 1×3 | 捕捉短时冲击,参数少 |
| Dropout | 0.3~0.5 | 小样本防过拟合很关键 |
| KernelScale | auto起步 | 不行再手动调 |
SVM那边,调参重点在C和KernelScale。我常用的做法是用fitcecoc的交叉验证选项跑一组网格:
% 用交叉验证快速评估不同核尺度的效果 rs = rng(0); cvsvm = fitcecoc(featuresTrain, YTrain, ... 'Learners', templateSVM('KernelFunction', 'rbf', 'KernelScale', 'auto'), ... 'CrossVal', 'on', 'KFold', 5); cvAcc = kfoldLoss(cvsvm, 'LossFun', 'ClassifError');然后用kfoldLoss对比不同参数组合,选交叉验证误差最小的那组再训练最终模型。别一上来就全量训练,交叉验证那个阶段很快,能省不少时间。
5. 常见问题与排查技巧实录
5.1 CNN过拟合怎么处理
症状很典型:训练准确率接近100%,验证集准确率卡在80%左右不动,两者差距越来越大。
排查顺序:
- 先看样本量是不是太少。低于几百条样本,任何CNN都容易过拟合。优先检查数据切片是否合理,能不能用更小步长增加样本数。
- 加Dropout。我在全连接层之前加0.3的Dropout,通常能把过拟合压下来一截。
- 降低网络容量。卷积通道数从64降到32,或者砍掉一层卷积,效果往往比强行加正则好。
- 加L2正则。
trainingOptions里的'L2Regularization'默认是1e-4,过拟合严重时可以提高到1e-3。
还有一个容易被忽略的点:如果验证集和训练集来自同一次实验的不同片段,可能存在数据泄露,导致看起来没过拟合,实际部署时却很糟。这类问题从训练曲线上看不出来,得靠严格的按段划分来规避。
5.2 SVM效果反而比softmax差
这种情况我遇到过不止一次。先别怀疑组合模型思路,多数原因在流程细节上。
最常见的原因是特征提取位置不对。如果从靠前的卷积层取特征,特征维度过大且冗余严重,SVM在小样本上的表现会大打折扣。试着把提取点往后移到第一个全连接层。
第二个原因是忘了做标准化。这个前面说过,特征数值范围不一致会严重干扰RBF核的距离计算。
第三个原因是核参数没调。'KernelScale','auto'在部分数据上算出来的尺度并不理想,可以手动试一组对数间隔的值,比如0.01、0.1、1、10,用交叉验证选最优。
如果这些都试过还是不如softmax,还有一个可能:你的样本量其实已经足够大(比如上万条),这种情况下SVM相对于softmax的优势不再明显,甚至CNN里的softmax借助端到端训练会更好。CNN-SVM更适合小样本场景,样本丰富时没必要硬凑。
5.3 训练速度慢或者内存不够
转动信号样本如果切片很密,样本量可能到几万条,在CPU上训练CNN会非常煎熬。几个实用技巧:
- 把
MiniBatchSize设大一点,配合GPU能大幅提速; - 输入层加
'Normalization','zscore'后,网络内部会处理标准化,不需要额外写预处理; - 训练时把
'Plots','training-progress'关掉,图形渲染也会拖慢速度; - 如果内存不够,试试把数据切块分批训练,或者先把样本量降下来。
Matlab在GPU上跑小网络加速很可观,前提是有NVIDIA独立显卡并且装好了并行计算工具箱。没有GPU的话,尽量把网络压小一点,卷积通道数控制在32以内,训练时间还能接受。
5.4 样本类别不平衡
设备故障数据经常是不平衡的,正常状态样本多,故障样本少。这种情况直接训练,SVM决策边界会向多数类偏斜。
处理办法常用两个:
一是重采样。故障样本少就把它们多复制几份,或者用滑窗更小的步长多切几个窗口,让各类样本量接近。
二是在SVM里设置类别权重。fitcecoc可以选择带权重的SVM学习器,'Prior'参数设为'empirical'并按类别样本比例自动调整:
t = templateSVM('KernelFunction', 'rbf', ... 'KernelScale', 'auto', ... 'Standardize', false, ... 'Prior', 'empirical');注意这个'Standardize'参数和前面手动标准化不要重复做,我习惯手动标准化就不在网络这边再设了。
6. 实操心得与可扩展方向
6.1 我踩过的几个坑
第一个坑是拿softmax层的输出去喂SVM。softmax输出是概率分布,所有维度加起来等于1,这种"退化特征"信息量太低,SVM效果很差。一定要从中间层取特征,而不是从输出层取。
第二个坑是随机划分数据导致准确率虚高。某次项目里我按窗口随机划分,报告写了98%的准确率,后来发现不同段的信号存在明显差异,模型靠记住段内模式"作弊"了。改成按段划分后准确率降到90%出头,这才是真实水平。现在我做任何实验都会先问自己一句:测试集和训练集的独立性够不够?
第三个坑是SVM调参时一直手动试。后来把网格搜索加交叉验证写成了一个脚本,跑一圈几分钟出结果,比手动一个个试高效得多。推荐你也这样做。
6.2 这套模型还能怎么扩展
CNN-SVM只是基础框架,在实际项目里可以往几个方向扩展。
一是把输入从原始时域波形换成时频图。把振动信号做短时傅里叶变换或小波变换,得到二维时频图,再交给CNN提取特征。时频图同时包含时间信息和频率信息,对变转速工况下的故障诊断效果更好。这时候卷积就不是"伪1D"而是真正的二维卷积,网络可以复用图像分类的成熟结构。
二是加入迁移学习思路。如果故障样本实在少,可以先在类似设备的大数据集上预训练CNN,只把尾部全连接层换掉,在新数据上微调。这样特征提取器已经见过足够多样的信号模式,小样本下更容易学到有效特征。
三是部署到在线监测场景。整个流程里,CNN特征提取加SVM预测,单个样本推理耗时在毫秒级,完全满足实时性要求。我做过一个边缘端的小系统,把训练好的模型参数导出,嵌入采集程序里,振动信号边采集边诊断,效果很稳定。
这套模型真正好用的地方在于它把深度学习的特征学习能力和经典机器学习的小样本分类能力结合了起来,工程落地的时候容错率高。做故障诊断的人手里备着这套方案,遇到新数据至少不会抓瞎。