简介:一份基于LSTM与SVM的设备故障诊断Python源码项目,属于答辩评分96分的高分毕设成果,面向计算机、人工智能、通信工程、自动化等专业的在校学生、教师或企业开发人员,可支撑毕业设计、课程设计、作业或项目初期演示。项目融合长短期记忆网络与支持向量机,采用LSTM提取时序特征、SVM完成故障分类,覆盖数据预处理、特征提取、模型训练与结果评估等完整流程,代码经完整测试,运行稳定。资源包共63个文件、约53.92MB,以53个.mat数据文件为主体(主要来自凯斯西储大学轴承数据集),辅以脚本文件、说明文档、特征表格等,便于对照学习算法细节与实验结构。目前已有143人学习。下载后建议优先阅读README与数据说明,再进行运行与二次开发;如有运行问题,作者提供远程教学支持,适合需要快速上手故障诊断项目或拓展毕设思路的学习者。
1. 先搞清楚:这份 LSTM+SVM 源码到底帮你解决了什么问题
设备故障诊断在工业场景里不是 "跑个模型" 那么简单,真实痛点在于——振动信号是非平稳的、带噪的、还带着负载变化,拿裸数据直接丢给分类器,准确率能让你怀疑人生。这份基于 LSTM 和 SVM 的 Python 源码,核心思路是让 LSTM 先吃时序信号、自动抽时序特征,再把特征交给 SVM 做最终分类,两段式分工。这意味着你不需要手动设计几十个时域频域特征,也不用担心传统机器学习对序列信息不敏感的问题。适合谁用?一是做毕设或课程设计、正在找 "能跑通的故障诊断代码" 的学生,二是想快速搭一个轴承故障分类基线、后续再替换数据集的工程师。下载后打开 README.md,按顺序跑通,你就能得到一套完整的 "数据预处理→特征提取→LSTM→SVM→评估" 流水线。
2. 为什么是 LSTM + SVM 而不是纯 LSTM:两段式架构的选型逻辑与数据流
2.1 单一模型为什么不够:时序特征与分类边界的矛盾
纯 LSTM 擅长建模时间依赖,但它的输出层通常接 softmax 做多分类,这在故障信号类别多、样本少的情况下容易过拟合,而且决策边界是线性 softmax 的,对高维特征空间里 "分布紧凑但边界弯曲" 的故障类别并不友好。纯 SVM 的问题反过来:它需要人工构造特征向量,而原始振动信号直接拉平成一维特征,不仅维度爆炸,还会把时间上的前后依赖关系抹掉。你可以把 SVM 想成一个 "很挑食" 的分类器——你喂它什么特征,它就只能在这个特征空间里画边界。
LSTM + SVM 的混合结构就是冲着这个矛盾去的。LSTM 部分相当于一个可训练的特征提取器,它把长度为 N 的时序窗口压缩成一个固定维度的特征向量(通常是最后一个时间步的隐状态,或者全部时间步的均值池化),这个特征向量已经包含了时序演化信息;SVM 部分则在这个特征空间里找一个最大间隔分类超平面。最大间隔的好处是,即使 LSTM 提取的特征在某个类别上有轻微重叠,SVM 也能通过间隔最大化抑制过拟合,这在样本量只有几百条的工业故障数据上尤其重要。
2.2 数据流拆解:从 .mat 到特征向量再到分类结果
这套源码的数据流可以拆成五个阶段,你需要先理解数据在哪一步变成了什么,才能动手改参数而不至于把流程跑飞。第一阶段是原始数据导入,西储大学数据集是 .mat 格式,里面是 12kHz 采样率下的驱动端振动加速度信号,这份源码里对应的是 data 目录下的 winddata.txt 和 12k winddata.txt,前者是原始信号,后者是截取后用于训练的信号段。第二阶段是滑窗切分,把长信号切成固定长度的时间窗,比如每 1024 个点一个窗口,这个窗口就是 LSTM 的一个样本。第三阶段是特征提取,源码的 tezhengxiangliang 目录下存放了 B007.xlsx 等文件,这是西储大学数据说明文档里 B007 类故障(滚动体故障,损伤直径 0.007 英寸)的特征向量,已经按 Excel 表格组织好,说明作者在 MATLAB 里用 run_1.m 和 run_2.m 做了特征工程,再把特征导出给 Python 用。
第四阶段是 LSTM 特征压缩,Python 端读取特征向量后,用一个单层 LSTM 把时序特征映射为固定维度的嵌入向量。第五阶段是 SVM 分类,把嵌入向量作为输入喂给 SVM,输出故障类别。这里有一个工程细节值得注意:run_1.m 和 run_2.m 是 MATLAB 脚本,说明整个项目并不是纯 Python 端到端,而是 MATLAB 做信号分解和特征提取、Python 做 LSTM + SVM 分类的混合流程。你在复现时要先跑 MATLAB 脚本生成特征文件,再切到 Python 环境跑分类,顺序不能反。
2.3 为什么西储大学数据集是默认选项
代码里默认用的是西储大学(CWRU)轴承数据集,这基本是故障诊断领域的事实标准——它的数据是公开的、采样率固定(12kHz 和 48kHz)、故障类别标注清晰(正常、滚动体故障、内圈故障、外圈故障,且每种故障又按损伤直径分为 0.007、0.014、0.021 英寸三档)。源码的 README.md 里也配套了《西储大学数据说明.docx》,说明作者默认你是拿这份数据做训练和测试。这里有个重要信号:如果你的毕设换成了其他数据集,比如你自己的实验室台架数据,你需要重新做滑窗参数设定和特征提取,不能直接复用。
3. 把数据集和特征文件对齐:从 winddata.txt 到 B007.xlsx 的完整映射
3.1 文件清单与角色定位
先别急着跑代码,把文件角色理清楚,这一步走错后面全是黑匣子。根据项目正文列出的资源清单,里面有几类文件要分清:
| 文件/目录 | 角色 | 说明 |
|---|---|---|
| data/ | 原始数据目录 | 存放西储大学原始振动信号,含 winddata.txt 等 |
| 12k winddata.txt | 12kHz 采样信号文本 | 原始驱动端振动加速度数据,长序列,需要滑窗切分 |
| tezhengxiangliang/ | 特征向量目录 | 存放 B007.xlsx 等特征矩阵,Excel 格式 |
| B007.xlsx | 特征向量文件 | 滚动体故障特征,按样本行组织的特征表 |
| run_1.m / run_2.m / tu.m | MATLAB 预处理脚本 | 读取 .mat 数据、做经验模态分解或特征统计量提取 |
| esmd4j / Esmd.class | 信号分解工具 | 可能是 EMD 的 Java 实现辅助工具,用于信号分解 |
| README.md | 项目说明 | 运行顺序和依赖说明 |
| 西储大学数据说明.docx | 数据集说明文档 | CWRU 数据采集参数、故障类型、损伤直径说明 |
这些文件的分工不是随意的——run_1.m 和 run_2.m 负责把原始 .mat 信号处理成特征向量,导出的 Excel 放在 tezhengxiangliang 目录下,Python 端再去读 Excel。注意你下载的资源里如果只看到 B007.xlsx 这一个特征文件,说明作者只导出了滚动体故障这一类做演示,完整复现时需要你自行用 MATLAB 脚本把内圈、外圈、正常的特征都导出来。
3.2 从原始信号到滑窗样本的常见做法
假设你要处理一段 12k winddata.txt,它是一列很长的浮点数,代表加速度值。常见做法是先用滑窗切成等长片段,每个片段作为一个样本:
import numpy as np import pandas as pd # 读取西储大学 12kHz 振动信号文本,取前 100000 个点作为示例 raw_data = pd.read_csv('12k winddata.txt', header=None).values.flatten() sample_rate = 12000 # 采样率 12kHz window_size = 1024 # 每个窗口 1024 个采样点,约 85ms 的信号 step_size = 512 # 滑动步长 512,窗口之间 50% 重叠 samples = [] for start in range(0, len(raw_data) - window_size, step_size): end = start + window_size window = raw_data[start:end] samples.append(window) samples = np.array(samples) # 形状: (样本数, 1024) # 给每个样本附上标签:0 表示正常,1 表示滚动体故障(B007) # 这里用占位标签,实际使用时按每个文件对应的故障类型赋值 labels = np.zeros((samples.shape[0], 1)) labels[:, 0] = 1 # 假设当前文本全是 B007 故障数据参数说明:window_size 取 1024 是折中方案——太短(比如 256)会丢失一个完整振动周期的上下文,LSTM 很难学到周期性冲击特征;太长(比如 2048)会增加计算量,而且在样本量有限的情况下样本数会骤减。step_size 取 window_size 的一半做重叠采样,是为了让相邻窗口之间的边界信息不丢失。这里的 labels 是占位符,实际使用时要对应到每个文件真实的故障类别,否则后面 SVM 训练出来的模型是错乱的。
3.3 特征向量 Excel 怎么和 LSTM 输入对齐
源码的 tezhengxiangliang 目录下已经有 B007.xlsx,这是作者在 MATLAB 里算好的特征表。但 LSTM 输入要求的是三维张量 (batch, time_steps, input_dim),所以你要把 Excel 里的二维特征矩阵 reshape 成时序格式:
import pandas as pd # 读取作者预先提取的特征向量 feat_df = pd.read_excel('tezhengxiangliang/B007.xlsx', engine='openpyxl') print(feat_df.shape) # 典型输出: (样本数, 特征维度) # 假设每行是一个样本,每个样本有 64 个特征值 # 常见做法:把 64 个特征切成 8 个时间步,每个时间步 8 个特征 n_samples = feat_df.shape[0] n_features_total = feat_df.shape[1] # 应为 64 time_steps = 8 features_per_step = n_features_total // time_steps # 8 # reshape 成 LSTM 输入格式 X_seq = feat_df.values.reshape(n_samples, time_steps, features_per_step) print("LSTM 输入形状:", X_seq.shape) # (样本数, 8, 8)逻辑说明:这里把扁平特征向量人为地折叠成"伪时序"结构。为什么这么做?因为 MATLAB 里提取的特征是平铺的,但 LSTM 需要时序维度。切成 8 步、每步 8 维,相当于把特征按顺序分成 8 组,让 LSTM 去学组与组之间的"先后关系"。参数调整:如果 B007.xlsx 的特征维度不是 64,你需要自己算整除关系,time_steps 和 features_per_step 的乘积必须等于总特征数,否则 reshape 会直接报错。另外要注意,openpyxl 引擎是读 .xlsx 必需的,如果你的环境里没装,用pip install openpyxl。
这里最容易翻车的地方是:很多同学拿到 B007.xlsx 就直接丢给 LSTM,结果发现维度不匹配。你先用feat_df.shape看清楚到底有多少列,再决定怎么拆时间步。我一般会先打印前五行的值,确认特征量纲是否一致——如果有的特征是均值、有的是标准差、有的是峰值,量纲差几个数量级,最好先做标准化,否则 LSTM 收敛会很慢。
4. LSTM 特征压缩与 SVM 分类器衔接:核心代码逐段拆解
4.1 构建 LSTM 特征提取器
LSTM 在整套代码里的定位不是最终分类器,而是特征压缩器。它把上面 reshape 出来的 (样本数, time_steps, features_per_step) 张量压缩成一个固定维度的向量,这个向量会作为后续 SVM 的输入。用 Keras 实现这个特征提取器:
import numpy as np from tensorflow.keras.models import Sequential from tensorflow.keras.layers import LSTM, Dropout from tensorflow.keras.optimizers import Adam # 输入形状: (time_steps=8, features_per_step=8) model = Sequential() model.add(LSTM(units=32, input_shape=(8, 8), return_sequences=False)) model.add(Dropout(0.2)) model.add(Dense(16, activation='relu')) model.compile(optimizer=Adam(learning_rate=0.001), loss='mse') print(model.summary())逻辑说明:LSTM 层的 units=32 表示隐状态维度为 32,return_sequences=False 表示只返回最后一个时间步的隐状态,也就是说输入 8 个时间步后,输出是一个 32 维向量。这个向量就是压缩后的时序特征。Dropout 0.2 是为了防止 LSTM 在小样本数据上过拟合。后面的 Dense 16 层是把 32 维再压到 16 维,让 SVM 的输入维度更低、更利于找间隔。参数调整:units 从 32 调到 64 会增加模型容量,但如果样本只有几百条,隐状态太大反而容易把噪声记住,我一般先用 32,看 SVM 分类结果再决定是否加大。loss 参数在这里无所谓,因为你不是用这个模型做最终预测,它只是特征提取器,你甚至可以不用 compile 直接predict,但保留 compile 会更规范。
4.2 提取特征并喂给 SVM
模型训练好之后,用它的predict方法提取所有样本的特征向量,再交给 SVM。这里有个关键细节:LSTM 模型的训练方式有两种选择,一种是直接用原始信号做有监督训练(比如用故障类别做标签),另一种是无监督训练(比如重构信号),然后取中间层特征。在故障诊断场景下,常见做法是有监督训练——直接让 LSTM 学分类任务,然后取最后一个隐状态作为特征,因为这样提取到的特征对分类最有判别力。
from sklearn import svm from sklearn.model_selection import train_test_split from sklearn.metrics import classification_report, confusion_matrix # X_seq 是上一步 reshape 好的特征张量,y 是标签向量 # 示例:假设你有 3 类故障 + 1 类正常,共 4 类 X_train, X_test, y_train, y_test = train_test_split( X_seq, y, test_size=0.3, random_state=42, stratify=y ) # 用 LSTM 模型提取训练集和测试集特征 train_features = model.predict(X_train) # 形状: (n_train, 32) test_features = model.predict(X_test) # 形状: (n_test, 32) # 训练 SVM 分类器 clf = svm.SVC(kernel='rbf', C=0.1, gamma='scale', probability=True) clf.fit(train_features, y_train) # 预测并评估 y_pred = clf.predict(test_features) print(classification_report(y_test, y_pred))参数说明:SVM 的 kernel 用 rbf 是默认选择,因为 LSTM 提取的特征通常不是线性可分的,rbf 核可以映射到高维空间找超平面。C=0.1 是正则化参数,值越小对误分类的惩罚越轻,决策边界越平滑——在故障诊断场景下,样本噪声大,C 设小一点能抑制过拟合。如果你发现训练集准确率 100% 但测试集掉到 60%,首先调大 C 的反面——把 C 从 0.1 往 0.01 调,让边界更平滑。gamma='scale' 是让 sklearn 根据特征方差自动设置核宽度,通常比手动设 gamma 值更稳。
stratify=y 这个参数容易被忽略,但非常重要。故障数据集的类别通常不均衡——正常样本往往比故障样本多,如果不分层采样,测试集里可能全是正常样本,SVM 只看混淆矩阵一列就误以为模型很好。加上 stratify 之后,训练集和测试集里各类别比例与原始数据集一致,评估结果才是真实的。
4.3 整个流程串起来的执行顺序和参数矩阵
上面三段代码是拆开的,在实际源码里是串在一个主脚本里的。你动手改的时候,建议把参数集中放在脚本顶部,方便调试:
# 全局参数配置区 WINDOW_SIZE = 1024 STEP_SIZE = 512 TIME_STEPS = 8 FEATURES_PER_STEP = 8 LSTM_UNITS = 32 SVM_C = 0.1 SVM_GAMMA = 'scale' TEST_SIZE = 0.3 RANDOM_SEED = 42参数调整策略:如果 SVM 测试准确率不理想,优先动 LSTM_UNITS 和 SVM_C,不要同时改所有参数。我一般会先固定 SVM 参数,只调 LSTM_UNITS(从 16 到 64 逐步试),找一个测试集准确率最高的点;然后再反过头来微调 SVM_C。TIME_STEPS 这个参数要注意:它代表你切分特征向量的时间步数,太大(比如 16)会让每个时间步内的特征维度过低,LSTM 学不到足够信息;太小(比如 2)则时间序列感太弱,退化成普通前馈网络。8 是一个经验值,样本量在几百到一千时表现稳定。
5. 避坑与排查:复现这套源码最常见的五个坑
5.1 坑:MATLAB 脚本跑出来是乱码或者报错
现象:run_1.m 在 MATLAB 里打开报中文注释乱码,或者直接报"未定义函数或变量"。
原因:一是文件编码不对,原作者可能用的是 GBK 编码,你电脑上 MATLAB 默认用 UTF-8 读取;二是脚本依赖的 esmd4j 是 Java 工具,如果 MATLAB 没配置 Java 环境或者 jar 包路径不对,调用 Esmd.class 就会失败。
解决:先在 MATLAB 里用"打开"功能手动选择文件,在弹出的对话框里把编码改成 GBK 或 gb2312,重新保存为 UTF-8 再运行。esmd4j 的问题,检查一下 esmd4j 目录下有没有对应的 .jar 文件,并在 MATLAB 里执行javaaddpath('esmd4j目录路径')把 jar 包加进 Java 路径。如果还是找不到类,直接放弃 MATLAB 端特征提取,改用 Python 的 PyEMD 库做经验模态分解,效果一样,只是特征值可能和原作者略有差异,不影响整个流程跑通。
5.2 坑:读 12k winddata.txt 时 pandas 报解析错误
现象:pd.read_csv('12k winddata.txt')报ParserError,说数据里有多个分隔符或者空行。
原因:西储大学的原始文本文件里,每行可能包含多个空格分隔的浮点数,而且文件末尾有空行。pandas 默认逗号分隔,遇到空格就炸。
解决:明确指定分隔符和如何处理空行,用正则表达式\s+匹配任意空白:
import pandas as pd df = pd.read_csv('12k winddata.txt', header=None, sep=r'\s+', engine='python') # 去掉全为 NaN 的空行 df = df.dropna(how='all') raw_data = df.values.flatten() print(f"读取到 {raw_data.shape[0]} 个数据点")关键是sep=r'\s+'和engine='python',前者告诉 pandas 用连续空白做分隔符,后者避免 C 引擎对正则分隔符不支持的问题。这一步是纯数据读取,但很多同学卡在这里一整天,就是因为没意识到文本是空格分隔的。
5.3 坑:LSTM 训练 loss 不下降或直接 NaN
现象:fit 过程中 loss 一直不变,或者某个 epoch 后 loss 变成 NaN。
原因:最常见的是数据里面有 NaN 值,或者是特征量纲差异过大导致梯度爆炸。B007.xlsx 里如果有空单元格,pandas 会读成 NaN,喂给神经网络后反向传播梯度直接失效;另外如果原始振动信号的幅度没做归一化,LSTM 的输入值可能在几百的级别,梯度更新一步就是天文数字。
解决:在把特征喂给 LSTM 之前加一个标准化步骤:
from sklearn.preprocessing import StandardScaler # 把原始信号窗口拉到 0-1 范围,保持时序形状 X_samples = samples.reshape(-1, 1) scaler = StandardScaler() X_scaled = scaler.fit_transform(X_samples).reshape(samples.shape) # 检查数据里是否有 NaN assert not np.isnan(X_scaled).any(), "数据中存在 NaN,请检查源文件"注意 StandardScaler 是逐点标准化,不改变时序结构,对每个特征列独立标准化。如果还是出现 NaN,在 LSTM 层后加一个clipnorm或用 Adam 默认的梯度裁剪参数,Keras 里可以用Adam(clipnorm=1.0)限制梯度范数。
5.4 坑:SVM 分类准确率很高但实际是无效模型
现象:训练集准确率 99%,测试集 70%,看起来不错,但你把混洧矩阵打出来一看,所有类别都被分到了同一个类别。
原因:这是类别不均衡导致的"假性准确率"。比如正常样本占 80%,模型把所有样本都判成正常,准确率也有 80%,但完全没有诊断能力。源码里如果训练时没用stratify,或者数据集本身故障样本就很少,这种问题就会被掩盖。
解决:强制看混淆矩阵,不要只看准确率。分类报告里要看每一类的 precision 和 recall,特别是故障类别。一个合格的故障诊断模型,故障类的 recall 至少要达到 90% 以上,否则在实际产线上漏一次报警就是一次事故。如果发现故障类 recall 低,优先做数据增强——对少数类样本加噪声、时移、幅值扰动,或者直接把滑窗重叠率从 50% 提到 75% 以生成更多样本。
5.5 坑:Python 环境缺依赖,最基础的 sklearn 和 keras 都装不上
现象:from sklearn import svm报 ModuleNotFoundError,或者 keras 装完之后 tensorflow 和它版本冲突。
原因:sklearn 对 Python 版本有要求,keras 依赖 tensorflow 后端,pip 直接装很容易在依赖树上打架。
解决:用 conda 建一个干净的虚拟环境:
conda create -n fault_diag python=3.8 conda activate fault_diag pip install numpy pandas scikit-learn tensorflow==2.10 openpyxlPython 3.8 是个安全版本,tensorflow 2.10 是最后一个原生支持 Windows GPU 的版本,后面 2.11 起 GPU 支持只在 Linux 上了。如果你用的是 M 系列 Mac,装tensorflow-macos而不是tensorflow。装的时候看 pip 的解析日志,如果有红色冲突,先pip uninstall掉旧版本再重装。
6. 验证模型是不是真的在工作:混淆矩阵、B007 对齐与参数快速扫描
6.1 先把"分类正确"变成"诊断可信"
很多人跑完代码看到准确率 95% 就收工了,但故障诊断项目的交付标准不是准确率,而是每个故障类别能不能被可靠识别。一个简单的验证方法是用作者已经导出的 B007.xlsx 做基准对齐——如果这份 Excel 对应的是滚动体故障,那你用这份特征文件训练出来的 SVM,在测试集里至少要能分辨出"这是 B007 故障"和"这不是 B007"。可以将测试结果按类别拆分,逐一检查 recall:
from sklearn.metrics import confusion_matrix, recall_score import numpy as np # 假设类别标签: 0=正常, 1=B007 滚动体故障, 2=内圈故障, 3=外圈故障 cm = confusion_matrix(y_test, y_pred) print("混淆矩阵:\n", cm) # 计算每个类别的 recall(即对角线除以该行总和) recalls = np.diag(cm) / cm.sum(axis=1, dtype=np.float64) for idx, rec in enumerate(recalls): print(f"类别 {idx} 的召回率: {rec:.3f}") # 如果 B007 的 recall 低于 0.9,说明 LSTM 没有抓到滚动体故障的冲击特征 if recalls[1] < 0.9: print("警告:B007 故障识别率偏低,建议检查滑窗长度或增加该类别样本")输出里的警告比准确率数字更有价值。如果 B007 召回率低,优先调 WINDOW_SIZE——滚动体故障的特征是周期性冲击,窗口太短会截断冲击段,窗口太长又混入过多正常段噪声。从 1024 往 512 和 2048 两边各试一次,对比召回率变化。
6.2 用网格扫描快速找参数,而不是靠猜
调参最忌讳一次改多个变量,改完不知道是谁生效的。我更推荐用 sklearn 的 GridSearchCV 在 SVM 参数上做小范围扫描,先固定 LSTM 的特征提取部分:
from sklearn.model_selection import GridSearchCV from sklearn import svm # 用前面提取好的 train_features 和 y_train param_grid = { 'C': [0.01, 0.1, 1, 10], 'gamma': ['scale', 'auto', 0.1, 0.01], 'kernel': ['rbf'] } grid = GridSearchCV( svm.SVC(probability=True), param_grid, cv=5, scoring='f1_macro', # 多类别不均衡时用 macro F1 比 accuracy 更可靠 n_jobs=-1 ) grid.fit(train_features, y_train) print("最优参数:", grid.best_params_) print("最优 F1:", grid.best_score_) # 用最优参数重新训练并评估 best_clf = grid.best_estimator_ y_pred_best = best_clf.predict(test_features)参数说明:为什么 scoring 用 f1_macro 而不是 accuracy?因为故障诊断里各类别样本数不均衡,accuracy 会被多数类主导,macro F1 是对每个类别算 F1 再取平均,任何一类诊断能力差都会拉低分数。cv=5 做五折交叉验证,防止测试集选得太偏。n_jobs=-1 并行跑,省时间。这样一轮扫描下来,SVM 参数基本就固定在最优点了,不用靠手感调。
6.3 最后的习惯:每次强制走一遍完整性检查
从那以后我每次拿到类似项目,都会强制走一遍这套验证流程,不跳过任何一步:先看数据读取有没有 NaN,再确认标签和文件对应关系,然后跑完 LSTM 提取特征后打印一下特征向量的均值和方差,判断信号是否正常送入 SVM,最后一定看一眼混淆矩阵而不是只看 accuracy。这个过程花不了五分钟,但能挡掉 80% 的"假模型"。这套源码本身已经跑通,你要做的不是怀疑它的正确性,而是验证你自己换数据、换参数之后它依然健康。希望帮到你。
本文还有配套的精品资源,点击获取