简介:这份资源面向机械加工与智能制造方向的本科生、研究生及算法入门者,提供一套基于机器学习的滚刀状态识别完整项目,可用于毕业设计、课程设计或期末大作业。项目以Python实现,覆盖CNN、LSTM、GRU、SVM、随机森林等多种模型,帮助读者对比深度学习与经典算法在刀具状态识别任务上的表现差异。压缩包共15个文件,约2.63MB,包含8个py源码文件、4个csv数据集、1个png结果图、1个md说明文档及gitignore配置,源码按数据合并、特征生成、模型训练与测试等环节拆分,结构清晰,便于逐模块阅读与二次修改。目前已有233人学习下载。读者可直接获得可运行的项目代码与配套数据,快速完成数据预处理、特征提取、模型搭建与评估全流程,并参考README说明完成环境部署,适合作为入门机器学习与工业信号分析的实践案例。
1. 从振动信号到刀具健康度:滚刀状态识别到底在识别什么
盾构机在复合地层里掘进时,滚刀是直接接触岩面的那批零件,它一旦偏磨、崩刃或者卡死,整条掘进循环的效率会立刻掉下来。现场最常见的诉求不是"预测还剩多少寿命",而是"现在这一刀盘上的滚刀,到底处于正常、磨损还是异常状态"。这个标题讲的就是用 Python 把这件事做成一个可复现的机器学习分类任务:采集滚刀工作时的振动、电流、转速等信号,做特征工程,再分别用 CNN、LSTM、GRU、SVM、随机森林去建模,比较谁在滚刀状态识别上更稳。
它适合两类人:一类是手里已经有台车或试验台数据、想快速跑通一套基线的人;另一类是想拿一个真实工况的多模型对比项目练手的人。滚刀状态识别本质上是一个多传感器时间序列分类问题,难点不在模型多花哨,而在标签怎么定、窗口怎么切、特征怎么对齐。下面我按自己搭这套流程的顺序,把选型理由、代码、参数和踩过的坑一次讲清。
2. 数据准备与标签体系:滚刀状态识别的地基怎么打
2.1 先想清楚标签从哪来
滚刀状态识别最容易翻车的地方不是模型,是标签。现场能拿到的原始记录通常是"某段时间内某把刀被换下",但换刀时刻和真实失效时刻之间往往差几十分钟甚至几个班。我一般用三种方式交叉定标签:一是换刀记录里的刀具编号和磨损量,二是掘进参数(推力、扭矩、贯入度)的突变点,三是停机检修时的照片或测量值。三者对齐后,把每把刀的状态归成三类:正常、磨损、异常(崩刃/卡死/偏磨)。
如果只有试验台数据,标签会干净很多,直接按加载工况分。但要注意,试验台的转速和载荷分布跟现场差得远,模型迁移过去会掉点,这点后面避坑章会细说。
2.2 采样、切窗与对齐
振动信号采样率常见 5kHz 到 20kHz,电流和转速一般 1kHz 以下。多源信号频率不一致,必须先重采样到统一时间轴,再切窗。窗口长度我一般取 1024 或 2048 点,重叠 50%,这样既能覆盖一个完整的刀具啮合周期,又不至于样本太少。
import numpy as np import pandas as pd from scipy.signal import resample def align_and_window(vib, cur, rpm, fs_vib=10000, fs_low=1000, win=2048, overlap=0.5): # 把低频信号重采样到振动同一时间轴 n = len(vib) cur_up = resample(cur, n) rpm_up = resample(rpm, n) step = int(win * (1 - overlap)) windows = [] for start in range(0, n - win + 1, step): seg_v = vib[start:start + win] seg_c = cur_up[start:start + win] seg_r = rpm_up[start:start + win] # 三通道堆叠成 (3, win) windows.append(np.vstack([seg_v, seg_c, seg_r])) return np.array(windows, dtype=np.float32)这段代码的关键点是resample把电流和转速拉到振动的时间分辨率,保证同一窗口内三路信号是同一时刻的。win=2048对应 10kHz 采样约 0.2 秒,overlap=0.5让相邻窗口共享一半数据,样本量翻倍但要注意后续划分数据集时按时间段切,不能随机打乱,否则同一段信号会同时进训练和测试,指标虚高。
2.3 特征工程:时域、频域、时频域各取什么
深度学习模型可以直接吃原始波形,但 SVM 和随机森林必须吃特征。为了公平对比,我一般先做一套统一特征,再让 CNN/LSTM/GRU 用原始窗口。时域取均方根、峭度、峰值因子、裕度因子;频域取 FFT 后各频带能量占比;时频域用 5 层小波包分解的能量熵。
| 特征类型 | 具体特征 | 维度 | 适用模型 |
|---|---|---|---|
| 时域 | RMS、峭度、峰值因子、裕度因子 | 4 | SVM、RF |
| 频域 | 8 个频带能量占比 | 8 | SVM、RF |
| 时频域 | 小波包 5 层能量熵 | 32 | SVM、RF |
| 原始波形 | 三通道窗口 | 3×2048 | CNN、LSTM、GRU |
峭度对冲击类异常特别敏感,滚刀崩刃时峭度会明显抬升;RMS 对整体磨损更敏感。这两类特征配合使用,SVM 和随机森林的区分度就够用了。
3. 五个模型怎么落地:从 CNN 到随机森林的代码与参数
3.1 CNN:把振动窗口当图像处理
CNN 在滚刀状态识别里的思路是把 3×2048 的窗口当成一张"图",用卷积核去抓局部冲击模式。我一般用三层一维卷积,因为信号是时序的,一维卷积比二维更省参数也更贴合物理意义。
import torch import torch.nn as nn class CNN1D(nn.Module): def __init__(self, n_class=3): super().__init__() self.net = nn.Sequential( nn.Conv1d(3, 32, kernel_size=7, padding=3), nn.BatchNorm1d(32), nn.ReLU(), nn.MaxPool1d(2), nn.Conv1d(32, 64, kernel_size=5, padding=2), nn.BatchNorm1d(64), nn.ReLU(), nn.MaxPool1d(2), nn.Conv1d(64, 128, kernel_size=3, padding=1), nn.BatchNorm1d(128), nn.ReLU(), nn.AdaptiveAvgPool1d(1) ) self.fc = nn.Linear(128, n_class) def forward(self, x): # x: (batch, 3, 2048) return self.fc(self.net(x).squeeze(-1))kernel_size=7对应第一层感受野约 0.7ms,能覆盖单次冲击;AdaptiveAvgPool1d(1)把时间维压掉,避免全连接层参数爆炸。训练时学习率 1e-3,batch 64,Adam 优化器,早停 patience 设 10。CNN 的优点是训练快、对局部冲击敏感,缺点是窗口一长就抓不住长程依赖,滚刀从正常到磨损的渐变过程它看不全。
3.2 LSTM 与 GRU:抓滚刀状态的时序演化
滚刀磨损是渐变过程,单窗口看不出趋势,需要把多个窗口串成序列。我一般把连续 20 个窗口作为一个序列样本,每个窗口先过一个共享的 CNN 提特征,再送进 LSTM 或 GRU。
class CNNLSTM(nn.Module): def __init__(self, n_class=3, hidden=64, layers=2): super().__init__() self.cnn = nn.Sequential( nn.Conv1d(3, 32, 7, padding=3), nn.ReLU(), nn.MaxPool1d(4), nn.Conv1d(32, 64, 5, padding=2), nn.ReLU(), nn.MaxPool1d(4) ) self.lstm = nn.LSTM(64, hidden, layers, batch_first=True, bidirectional=True, dropout=0.3) self.fc = nn.Linear(hidden * 2, n_class) def forward(self, x): # x: (batch, seq=20, 3, 2048) b, s, c, w = x.shape x = x.view(b * s, c, w) feat = self.cnn(x).mean(-1) # (b*s, 64) feat = feat.view(b, s, -1) out, _ = self.lstm(feat) return self.fc(out[:, -1, :])GRU 把nn.LSTM换成nn.GRU即可,参数量少约 25%,训练更快。双向 LSTM 能同时看前后文,但滚刀状态识别是因果任务,实际部署时未来窗口拿不到,所以我更推荐单向。dropout=0.3是防过拟合的关键,序列样本少的时候可以提到 0.5。
3.3 SVM 与随机森林:小样本下的稳妥选择
现场数据往往只有几百到几千个带标签窗口,这时候 SVM 和随机森林反而比深度学习稳。SVM 用 RBF 核,C和gamma用网格搜索定;随机森林树数 200 到 500,max_depth控制在 10 到 15 防过拟合。
from sklearn.svm import SVC from sklearn.ensemble import RandomForestClassifier from sklearn.preprocessing import StandardScaler from sklearn.pipeline import Pipeline from sklearn.model_selection import GridSearchCV svm_pipe = Pipeline([ ("scaler", StandardScaler()), ("svm", SVC(kernel="rbf", probability=True)) ]) svm_param = {"svm__C": [1, 10, 100], "svm__gamma": ["scale", 0.01, 0.1]} svm_gs = GridSearchCV(svm_pipe, svm_param, cv=5, scoring="f1_macro") rf = RandomForestClassifier(n_estimators=300, max_depth=12, class_weight="balanced", n_jobs=-1)SVM 对特征尺度敏感,StandardScaler不能省。随机森林的class_weight="balanced"在异常样本少的时候很重要,否则模型会偏向多数类。这两个模型训练只要几分钟,适合做基线,也适合在数据量不足时先跑通流程。
4. 避坑与排查:滚刀状态识别里最容易翻车的五件事
4.1 随机切分导致指标虚高
现象:测试集准确率 98%,一上现场就掉到 60%。原因是切窗时用了随机划分,同一段连续信号被分到训练和测试两边,模型其实在"背答案"。解决:按时间段或按刀具编号划分数据集,训练集和测试集在时间上不重叠。
4.2 类别不平衡被忽略
现象:异常类召回率极低,模型几乎全预测成正常。原因是正常样本远多于异常样本。解决:用class_weight="balanced"、过采样或者 focal loss,评估指标从准确率换成 macro-F1 和异常类召回率。
4.3 重采样引入虚假频率
现象:电流信号重采样到 10kHz 后,频谱里出现一堆高频噪声。原因是低采样率信号上采样只是插值,不会带来新信息,反而放大噪声。解决:要么把振动降采样到低频,要么对低频信号单独提特征再和振动特征拼接,不要强行拉到同一采样率。
4.4 归一化用了全局统计量
现象:离线训练很好,在线推理时第一批数据就崩。原因是归一化用了包含测试集的全局均值方差,在线时拿不到。解决:归一化参数只在训练集上算,保存下来,推理时直接加载。
4.5 序列样本跨了换刀点
现象:LSTM 在换刀前后误判率特别高。原因是序列窗口跨越了换刀时刻,前后其实是两把刀的状态。解决:切序列时以换刀点为边界,不跨刀切窗。
5. 让模型真正可用的两个进阶技巧
第一个技巧是用滑动投票代替单次预测。现场部署时,单窗口预测会抖,我一般让模型对最近 10 个窗口输出概率,取平均后再判类,状态切换的误报能降一半以上。第二个技巧是留一个"未知"类。滚刀状态识别最怕的是遇到训练集里没见过的失效模式,硬分到某一类反而误导检修。我的做法是在 SVM 里用概率阈值,最大概率低于 0.6 就输出"待人工确认",在深度学习里加一个基于能量距离的 OOD 检测头。
def sliding_vote(model, seq_windows, n_vote=10): # seq_windows: 最近 n_vote 个窗口 model.eval() with torch.no_grad(): logits = model(seq_windows) prob = torch.softmax(logits, dim=-1).mean(0) if prob.max() < 0.6: return "待人工确认" return prob.argmax().item()验证方法上,我习惯留一台设备的全部数据做跨设备测试,而不是只做同设备交叉验证。同设备指标再高,跨设备掉 20 个点都是常事,这个数字才是能不能上现场的真实门槛。参数上,投票窗口数n_vote取 8 到 15 之间,太少压不住抖动,太多响应变慢;概率阈值 0.6 是我在几个项目里试出来的经验值,样本少时可以降到 0.5。
这套流程我从数据对齐一路搭到滑动投票,最大的教训是:模型选型从来不是瓶颈,标签质量和数据划分方式才是决定这套滚刀状态识别能不能落地的关键。先把标签对齐、按时间切分做扎实,再谈 CNN 还是 LSTM,顺序反了就是白干。希望帮到你。
本文还有配套的精品资源,点击获取