
简介基于LSTM和SVM的设备故障诊断Python源码项目融合长短期记忆网络提取时序特征与支持向量机分类判别面向人工智能、自动化、电子信息等专业的毕业设计、课程设计与项目初期演示也适合故障诊断方向的学习者作为进阶参考。压缩包共63个文件以53个mat数据文件为主体辅以m格式运行脚本、txt/md/docx说明文档及xlsx特征向量表整体53.92MB数据与代码模块清晰便于定位和二次修改。该代码经过完整测试并成功运行答辩评审平均分达96分已有143人浏览学习搭配西储大学轴承数据说明与README可快速复现算法流程、理解特征处理与模型融合思路。对于希望在LSTMSVM组合模型上快速落地实验的研究者或学生这套源码提供了可复用的实验数据、特征提取脚本与分类判别流程能够显著节省从零搭建与调试的时间。1. 一台设备的“提前报警”为什么必须同时用LSTM和SVM设备故障诊断在工业现场的核心诉求很简单别等设备坏了再停机最好提前几小时甚至几天告诉你“这台机器不对劲”。但难点在于传感器采集到的信号振动、电流、温度是持续变化的时间序列故障特征藏在波形和趋势里不是看一眼数值就能判断的。LSTM擅长从时间序列里提取特征但它在小样本分类上并不擅长SVM在小样本、高维特征的分类上非常可靠但它处理不了原始时序数据。把两者串起来正好互补——这也是“基于LSTM和SVM实现设备故障诊断python源码”这类项目最常见的核心结构。对于正在做课程设计、毕业设计或者想给预测性维护方案搭一个初版原型的工程师这个“LSTM提取特征SVM分类”的组合是最值得先跑通的结构。本文要讲的就是这个组合的完整落地路径从原理到代码到调参再到底层踩过的坑。2. 先搞清楚套路把设备故障诊断拆成“时序特征提取分类”两段式2.1 为什么单用LSTM或单用SVM都会翻车——从故障数据的三个脾气说起设备故障数据有三个特点。第一故障样本少。正常设备占绝大多数故障可能是偶发的一个工厂能拿到的故障样本可能只有几十条到几百条。第二信号是时序的。故障往往表现为某个频段能量升高、某个趋势突变跟时间上下文强相关。第三故障类型之间边界模糊。比如轴承早期磨损和润滑不良在振动信号上可能只有细微差别。单用LSTM做端到端分类理论上可行实际上在小样本场景容易过拟合。LSTM参数量大需要大量数据才能压住泛化误差而这个项目标题对应的场景往往拿不到那么多故障样本。单用SVM做分类它本身是个强大的分类器但得先把时序数据变成特征向量。常见的做法是手动提取统计特征均值、方差、峰值、峭度、频谱能量等。这套手工特征工程在工业场景里确实能用但对人的经验要求太高换个设备、换个工况特征可能就要重新试。所以最稳的套路是让LSTM自动从原始时序里学特征把学到的特征向量交给SVM去分类。LSTM不用承担分类压力SVM不用担心原始序列太长没法处理。两个模型各干各擅长的事。这个设计思路就是整个python源码项目的骨架。2.2 两段式结构怎么搭LSTM当特征提取器SVM当分类器整体结构可以分为三段。第一段是数据切片把连续采样的传感器信号按固定窗口切成长度相同的片段每个片段对应一个标签。第二段是特征提取把每个窗口的数据送入LSTM取最后一个时间步的隐藏状态或者对全部时间步做池化作为这段数据的特征向量。第三段是分类把特征向量送入SVM输出故障类别。需要注意LSTM的输出是个三维张量batch_size、seq_len、hidden_size。如果输入一个窗口的序列seq_len就是窗口长度hidden_size是你设定的隐含层维度。我们要的特征向量通常取最后一个时间步的输出形状是batch_size、hidden_size或者对所有时间步做均值池化得到同样形状的向量。这个向量就是“机器自己学出来的特征”不需要你手动算峭度、峰值这些统计量。为什么选SVM而不是直接用softmax层在故障样本少的场景SVM的间隔最大化思想让它在高维小样本分类上比神经网络末端的softmax更稳。尤其是类别数不多比如正常加3种故障SVM配合RBF核决策边界可以很灵活也不容易像神经网络那样在小数据上反复震荡。如果你在框架里看到LSTM后面接的是SVM而不是全连接层设计意图就在这里。2.3 完整数据流从原始振动/温度数据到故障标签要过几道关一个完整的数据流大概是原始采集数据连续时间序列→ 滑窗切片每个窗口长度例如256或512个采样点→ 归一化每个窗口内做z-score→ 划分训练/验证/测试集注意按设备或按时间段划分不要随机打乱跨序列→ LSTM提取特征 → SVM分类 → 输出故障标签。这里有三个关键点。第一窗口长度怎么选。窗口太短一个窗口内包含的故障特征不完整窗口太长LSTM的序列计算量变大而且一个窗口里可能混入多个状态变化。常见做法是先看采样频率比如采样率是1kHz一个窗口覆盖0.5到1秒取256或512个点比较常见。第二归一化不能全局做。很多人习惯对整个数据集做一次归一化再切窗这在时序问题上是有风险的如果训练集和测试集来自不同时间段全局归一化会把未来数据的统计信息泄露到训练过程里。正确做法是在每个窗口内部做z-score或者用训练集的统计量去归一化测试集。第三数据集划分不能随机打乱。如果同一个设备的连续信号被切成了几百个窗口随机打乱后训练集和测试集会包含时间上相邻的窗口导致评估结果虚高。工业场景里应该按“设备”或“时间段”划分保证模型是在没见过的时段上做预测。后面第5章会展开讲这个坑因为在故障诊断项目里这是最容易让结果“看起来完美”的陷阱。3. 在Python里把LSTMSVM跑通数据准备、模型定义与训练3.1 数据准备窗口切片、归一化、训练集/测试集划分先给出完整的数据准备代码。这里假设你有一个CSV文件第一列是timestamp第二列是振动值后面可能还有其他传感器通道。import numpy as np import pandas as pd def load_and_slice(csv_path, window_size256, stride128): df pd.read_csv(csv_path) # 假设第二列以后都是传感器信号 data df.iloc[:, 1:].values.astype(np.float32) windows [] for start in range(0, len(data) - window_size, stride): end start window_size windows.append(data[start:end]) # 每个窗口形状: (window_size, n_channels) return np.array(windows) def zscore_per_window(windows): # 每个窗口内部做z-score避免全局归一化泄露未来信息 mean windows.mean(axis1, keepdimsTrue) std windows.std(axis1, keepdimsTrue) 1e-6 normalized (windows - mean) / std return normalized windows load_and_slice(bearing_data.csv, window_size256, stride128) windows zscore_per_window(windows) print(windows.shape) # (样本数, 256, 通道数)逻辑说明这里用stride128意思相邻窗口有50%重叠可以在样本量少的时候变相扩充数据。每个窗口内部做z-score是为了让LSTM输入每个通道的数值都在0附近波动避免某个传感器量纲过大主导梯度更新。加1e-6是为了防止信号在某段恒定导致std为0。参数说明window_size256和stride128是起步值。如果你的信号采样率很高、故障特征变化比较慢可以加大window_size到512或1024stride可以保持50%重叠。注意窗口重叠比例太高会让相邻窗口高度相似训练集和测试集之间如果切分不当评估结果会虚高。这一步做完你应该得到一个形状为(样本数, 窗口长度, 通道数)的三维数组这就是LSTM的标准输入格式。3.2 LSTM特征提取层的PyTorch实现用PyTorch定义LSTM特征提取器输出每个窗口的特征向量。import torch import torch.nn as nn class LSTMTFeatureEncoder(nn.Module): def __init__(self, input_size, hidden_size, num_layers1, dropout0.2): super().__init__() self.lstm nn.LSTM( input_sizeinput_size, hidden_sizehidden_size, num_layersnum_layers, batch_firstTrue, dropoutdropout if num_layers 1 else 0 ) def forward(self, x): # x: (batch_size, seq_len, input_size) out, (h_n, c_n) self.lstm(x) # out: (batch_size, seq_len, hidden_size) # 取最后一个时间步输出作为整个窗口的特征 last_step out[:, -1, :] # (batch_size, hidden_size) return last_step逻辑说明input_size就是你每个窗口里的传感器通道数比如只用振动信号就是1加上温度和电流就是3。batch_firstTrue让我们传入的数据形状是(batch_size, seq_len, input_size)更符合PyTorch模块里读写数据的习惯。取out[:, -1, :]就是最后一个时间步的隐藏状态它浓缩了整段序列的信息。参数说明hidden_size是核心参数一般取64、128、256。数据量小就少一些数据量大可以大一些。num_layers建议从1开始不要一上来就堆两层因为设备故障数据量通常不大两层LSTM的参数量和梯度传播难度会让训练变得不稳定。dropout只在num_layers大于1时生效单层LSTM加dropout反而会削弱记忆能力这也是很多初学者容易踩的坑。如果你想用均值池化替代最后一个时间步把last_step改成out.mean(dim1)即可在某些数据集上会更稳。3.3 把LSTM输出拉成特征向量喂给SVMLSTM训练好之后要把训练集和测试集都过一遍LSTM拿到特征向量然后用scikit-learn训练SVM。from sklearn import svm from sklearn.model_selection import train_test_split def extract_features(model, windows, batch_size128): model.eval() features [] with torch.no_grad(): for i in range(0, len(windows), batch_size): batch torch.tensor(windows[i:ibatch_size], dtypetorch.float32) feat model(batch) features.append(feat.numpy()) return np.concatenate(features, axis0) # 假设已有训练窗口和标签 X_train_w, X_test_w, y_train, y_test train_test_split( windows, labels, test_size0.2, stratifylabels, random_state42 ) # 加载训练好的LSTM权重第3.4节会讲怎么训练 model LSTMTFeatureEncoder(input_size1, hidden_size128) model.load_state_dict(torch.load(lstm_encoder.pth)) train_feat extract_features(model, X_train_w) test_feat extract_features(model, X_test_w) # 用RBF核的SVMC和gamma先给一个宽泛值 clf svm.SVC(kernelrbf, C1.0, gammascale, probabilityTrue) clf.fit(train_feat, y_train) print(SVM test acc:, clf.score(test_feat, y_test))逻辑说明模型切到eval模式后关闭梯度计算这一步很关键——如果不加torch.no_grad()虽然不影响提取结果但会把特征图存进计算图内存直接爆掉。SVM不关心梯度它只需要数值特征。参数说明gammascale意味着gamma会自动取1/(n_features * X.var())比手动设gamma0.01要稳健第一次跑就用这个。C1.0是SVM的正则化强度越大越容易过拟合越小越强调间隔最大化。probabilityTrue允许SVM输出概率后面做可视化诊断时会有用但会显著增加训练时间如果你只需要类别标签可以去掉。如果特征提取时发现内存不足把batch_size从128降到64或32。3.4 训练流程先训LSTM到收敛再训SVM——还是端到端一起训这是新手最容易搞混的问题。LSTM和SVM能不能端到端一起训严格来说可以但SVM的损失函数不是平滑的很难和LSTM的梯度一起反传。常见做法是分阶段训练。第一阶段训练LSTM本身。有两种选择一是用LSTM接一个临时的全连接分类层用交叉熵损失训练到收敛二是把LSTM当自编码器通过重建原始信号来学习特征。设备故障诊断数据量小我一般用第一种训完之后把临时的全连接层丢掉只用LSTM的隐藏状态作为特征。第二阶段冻结LSTM提取特征训练SVM。这样LSTM的训练和SVM的训练是两个独立过程互不干扰也便于排查到底是哪个环节出了问题。class LSTMTEncoderWithHead(nn.Module): def __init__(self, encoder, num_classes): super().__init__() self.encoder encoder self.classifier nn.Linear(encoder.lstm.hidden_size, num_classes) def forward(self, x): feat self.encoder(x) return self.classifier(feat) # 训练若干epoch后只保存encoder部分 torch.save(model.encoder.state_dict(), lstm_encoder.pth)逻辑说明把临时分类头去掉之后LSTM就成了一个纯粹的特征提取器。这样做的另一个好处是你可以在不同的数据集上复用同一个LSTM特征提取器只要设备的信号特性相似就不需要重新训练。参数说明训练LSTM的优化器我一般用Adam学习率从1e-3起步batch_size在64到256之间。注意LSTM输入数据形状必须是(batch_size, seq_len, input_size)如果你的数据是(input_size, seq_len)会在forward里报维度错误这几乎是高频报错。训练epoch数建议50到100配合早停策略——验证集loss连续10个epoch不降就停止。这里不推荐直接把SVM的hinge loss接在LSTM后面做端到端训练原因会在第5章展开。4. 参数怎么设LSTM的hidden_size、层数和SVM的核函数4.1 LSTM侧hidden_size、num_layers、dropout的常见取值hidden_size决定特征向量的维度也是LSTM参数量最大的来源。hidden_size64在大约几百个样本的场景下足够hidden_size128适合千级样本如果样本超过几千条可以尝试256。注意hidden_size增大之后SVM输入特征维度也变高RBF核gammascale的自动计算会帮你平衡但特征维度太高、样本量太少SVM同样会过拟合。num_layers从1开始。很多教程默认两层LSTM效果更好但设备故障诊断的数据量通常撑不起两层。如果你发现单层LSTM在训练集上loss降不下来先调hidden_size和学习率不要急着加层。dropout对单层LSTM无效这是因为PyTorch里dropout参数在num_layers1时不会应用。如果你非要加正则化可以在LSTM之后加一个单独的Dropout层。另一个容易被忽略的参数是学习率LSTM的梯度传播路径长学习率太高容易震荡太低收敛太慢。我常用1e-3起步loss不降就减半。4.2 SVM侧RBF核的C和gamma怎么调RBF核有两个关键参数C和gamma。C控制误分类惩罚C越大越不愿意放过训练集里的错误容易过拟合C越小决策边界越平滑。gamma控制单个样本的影响半径gamma越大每个样本的影响范围越小决策边界越复杂。一个简单有效的调参方法是网格搜索配合交叉验证。scikit-learn里面有GridSearchCV可以直接用提取好的特征跑。from sklearn.model_selection import GridSearchCV from sklearn.svm import SVC param_grid { C: [0.1, 1, 10], gamma: [scale, 0.01, 0.001], kernel: [rbf] } grid GridSearchCV(SVC(probabilityTrue), param_grid, cv5, scoringf1_macro) grid.fit(train_feat, y_train) print(grid.best_params_) print(grid.best_score_)逻辑说明这里用f1_macro而不是accuracy是因为设备故障数据里类别通常不平衡正常样本远多于故障样本accuracy会被多数类带偏。f1_macro会同时关注每个类别的精确率和召回率更适合故障诊断场景。参数说明C1、gammascale是起步值。如果交叉验证结果中best_params是C10、gamma0.01附近说明特征空间比较稀疏如果best_params落在C0.1、gammascale说明模型倾向更平滑的边界。注意GridSearchCV会重新跑5折交叉验证特征量大的时候耗时较长建议先跑小的参数网格确认趋势再扩充。如果你想更快可以先用LinearSVC跑一遍作为基线如果线性核的F1分数已经不错RBF核的提升空间就有限。4.3 一个能直接起步的参数表参数起步值调整方向window_size256故障特征变化慢就加大到512或1024stride128样本少就减小保持50%重叠LSTM hidden_size128样本多/特征复杂加到256否则降到64LSTM num_layers1先别加层除非单层欠拟合明显LSTM优化器Adam学习率1e-3loss震荡就降到5e-4batch_size128显存不够就降到64或32SVM kernelRBF特征维度特别高时可以试linearSVM C1.0过拟合调小欠拟合调大SVM gammascale网格搜索0.01、0.001这个表的思路是先跑通一个“正常”结果再根据训练集和测试集的表现差异去调。高手和新手的区别不是谁会调大调小而是谁能从loss曲线和验证集指标判断到底是哪个环节出了问题。下一章就专门讲这些判断。5. 避坑与排查从“loss下降但测试集一塌糊涂”到“SVM根本跑不动”5.1 时序切窗时泄露未来信息导致评估虚高现象训练时loss降得很漂亮测试集准确率也很高但一到真实场景就完全失灵。原因大多数情况下是数据划分出了问题。如果训练集和测试集来自同一段连续信号随机打乱之后测试集里会有大量与训练集时间相邻的窗口信息重复严重。更隐蔽的是全局归一化——如果先对整个数据集计算均值和标准差再切窗测试集的统计信息就已经被模型看过了。解决按时间段或按设备划分数据集。比如前80%的时间段作为训练集后20%作为测试集。归一化参数只能用训练集计算然后应用到测试集。如果信号有多个设备来源划分时保证同一个设备不能同时出现在训练集和测试集里。注意在时序故障诊断里“随机划分”基本等于作弊。你要的不是在已见过的信号片段上准确率多高而是对没见过的故障形态有泛化能力。这也是“高分项目”里最容易拿分的点——把数据划分讲清楚比模型调参更能体现工程意识。5.2 LSTM输出特征没有归一化SVM训练时直接崩现象SVM训练耗时极长或者gammascale计算出来的值异常小训练完评估准确率只有30%左右。原因LSTM的隐藏状态经过tanh或sigmoid激活后数值范围通常不是标准正态分布可能集中在某个区间。SVM的RBF核依赖样本间的欧氏距离特征尺度不一致会让某些维度主导距离计算。gammascale虽然会自动调整但对分布偏移严重的高维特征稳定性并不好。解决在提取完LSTM特征后对特征向量再做一次标准化。from sklearn.preprocessing import StandardScaler scaler StandardScaler() train_feat_scaled scaler.fit_transform(train_feat) test_feat_scaled scaler.transform(test_feat) clf.fit(train_feat_scaled, y_train) print(clf.score(test_feat_scaled, y_test))逻辑说明这里的StandardScaler用训练集特征拟合再用同一组均值和标准差去变换测试集特征。如果你直接在全部特征上fit_transform又犯了信息泄露的错。这一步在故障诊断项目里出现的频率极高因为LSTM作为黑匣子很多人默认它输出的特征可以直接用其实不然。参数说明标准化之后的特征均值接近0、方差接近1RBF核的距离计算会均衡得多。记住LSTM解决了特征提取问题但它不负责把特征整理成适合SVM的分布。这一个坑踩下去SVM的准确率可能直接从60%跳到85%属于性价比最高的修复操作。5.3 故障类别不平衡SVM的决策边界被多数类带偏现象测试集整体准确率85%但看一下每一类的准确率正常的识别率98%某个故障的识别率只有40%。原因设备故障场景里正常样本数量远大于故障样本是常态。SVM在训练时会优先把多数类分对以降低整体损失少数类的边界被压缩。解决处理类别不平衡有两条路。第一条是数据层面给少数类做重采样比如对少数类窗口做更小的stride让它产生更多重叠窗口。第二条是算法层面给SVM设置class_weightbalanced让scikit-learn自动按类别频率加权。clf SVC(C1.0, kernelrbf, gammascale, class_weightbalanced)逻辑说明class_weightbalanced会让每个类别的惩罚权重与该类别样本数量成反比。样本少的类别分错了会被罚得更重决策边界就不会一边倒。参数说明如果你发现class_weightbalanced之后多数类准确率有所下降这是正常现象总体的F1分数会上升。故障诊断场景里漏报一次严重故障的代价远高于多报几次正常维护所以宁可牺牲一点正常类准确率。5.4 想端到端训练LSTMSVMloss要么不更新要么直接NaN现象有人想直接把SVM的hinge loss接到LSTM后面做端到端训练结果发现loss是NaN或者根本不会更新。原因SVM的hinge损失在间隔大于等于1时梯度为0且决策函数里没有可导的软间隔映射。就算使用hinge loss的次梯度当样本落在间隔内部时梯度方向也不稳定和LSTM的时序反向传播叠加后极容易梯度爆炸。解决不要端到端训练。先把LSTM用临时softmax头训练到收敛再提取特征训练SVM。如果你一定想追求端到端常见做法是把SVM替换成一个带margin loss的全连接层或者用triplet loss让LSTM学习类间距离但这已经是另一个方向的改造超出了这个项目标题的范围。我见过不少人在这一步卡了一周最后把模型拆开分阶段训练问题立刻消失。5.5 SVM训练时概率模式太慢内存直接爆掉现象样本量有几万个窗口每个窗口提取的hidden_size128的特征理论上不大但如果你用概率型SVCprobabilityTrue训练时间会从几分钟变成几小时甚至内存报错。原因SVC的probabilityTrue会额外做Platt缩放需要多跑一次交叉验证复杂度比不带概率的版本高很多。另外RBF核要计算所有样本两两之间的核矩阵样本量上万时核矩阵大小是1e8级别内存瞬间吃满。解决如果不需要输出概率去掉probabilityTrue。如果一定要概率可以改用LinearSVC配合CalibratedClassifierCV。如果特征维度很高且类别数不多可以先试linear核训练快很多。clf SVC(C1.0, kernelrbf, gammascale, probabilityFalse)逻辑说明去掉概率输出后SVM只需要求解一次二次规划问题。对故障诊断来说多数场景输出类别就够用概率只是锦上添花。参数说明如果你的样本量超过2万或者特征维度超过512建议开始考虑用LinearSVC替代RBF核SVC。LinearSVC在大样本上训练快得多虽然决策边界不如RBF核灵活但在高维特征空间里线性边界往往已经够用。这个坑在你把LSTM的hidden_size调大之后特别容易出现因为特征维度越高核矩阵计算越慢。6. 最后的落地技巧把诊断结果可视化并验证模型是真的能用6.1 用混淆矩阵和t-SNE验证特征可分性模型训练完之后我习惯先画两个图。一个是SVM在测试集上的混淆矩阵可以一眼看出哪两类故障最容易混。另一个是t-SNE把LSTM特征降到2D看不同类别的点在空间里是否自然聚团。import matplotlib.pyplot as plt from sklearn.manifold import TSNE from sklearn.metrics import confusion_matrix, ConfusionMatrixDisplay y_pred clf.predict(test_feat_scaled) cm confusion_matrix(y_test, y_pred) ConfusionMatrixDisplay(cm).plot() plt.savefig(confusion_matrix.png, dpi150) tsne TSNE(n_components2, perplexity30, random_state42) feat_2d tsne.fit_transform(test_feat_scaled) plt.scatter(feat_2d[:, 0], feat_2d[:, 1], cy_test, cmaptab10, s10) plt.savefig(tsne_features.png, dpi150)逻辑说明混淆矩阵里的混类位置会告诉你下一步该往哪调。如果发现故障A经常被识别成故障B可以回去看这两个类别的原始信号差异或者增加对应传感器通道。t-SNE则是一个可视化“体检报告”如果同类别点没有聚团说明LSTM提取的特征还没学好直接调SVM也没用。这两个图放进课程设计或毕业设计的报告里能直观说明你做了特征层面的验证而不是只报一个准确率数字。6.2 把模型封装成“输入一段时序→输出故障类别”的函数实验做完之后最终要交付一个能直接用的诊断函数。我的做法是写一个predict_fault函数输入一段原始信号内部完成归一化、LSTM特征提取、特征标准化、SVM预测四步。def predict_fault(raw_signal, encoder, scaler, clf, window_size256): # raw_signal: 一维numpy数组长度至少为window_size if len(raw_signal) window_size: raise ValueError(信号长度不足一个窗口) # 取最后一个窗口 window raw_signal[-window_size:].astype(np.float32) # 窗口内z-score mean, std window.mean(), window.std() 1e-6 window (window - mean) / std # 输入LSTM x torch.tensor(window.reshape(1, window_size, 1), dtypetorch.float32) encoder.eval() with torch.no_grad(): feat encoder(x).numpy() # 标准化 SVM预测 feat_scaled scaler.transform(feat) return clf.predict(feat_scaled)[0], clf.predict_proba(feat_scaled)[0] if hasattr(clf, predict_proba) else None逻辑说明这个函数把四步串起来输入是原始信号数组输出是故障类别和概率。注意窗口取的是最后window_size个点意味着你只需要采集这么长的实时信号就能触发一次诊断。如果信号维度不是1而是多通道把reshape里的1改成对应的通道数即可。6.3 旧习惯先保存LSTM特征再调SVM别每次都从头训最后一个经验LSTM训练很贵SVM调参很快。所以我在提取完特征后第一件事就是把特征矩阵保存成npy文件后面调SVM参数的时候直接加载不用再跑一遍LSTM。这样反复调C和gamma的成本几乎为零迭代速度大幅提升。这个习惯帮我省了很多时间尤其当样本量大或者LSTM层数加深时。如果你已经在做设备故障诊断方向建议把这个流程固定在代码里训练LSTM → 提取并保存特征 → 调SVM → 只修改SVM参数重新训练。希望帮到你。本文还有配套的精品资源点击获取