☰
滚刀状态识别怎么做?Python机器学习实战CNN、LSTM、SVM
2026/9/28 11:15:26 网站建设 项目流程

简介:在盾构/TBM隧道施工中,滚刀磨损与故障直接决定工期和成本,但传统人工巡检难以连续监测。机器学习技术为工业时序数据分类提供了新思路:通过振动传感器、扭矩、贯入度等多通道信号,采用CNN提取局部波形特征,LSTM/GRU建模时间依赖,结合SVM、随机森林处理小样本高维特征,实现对正常、偏磨、崩刃、断裂等状态的自动识别。理解信号预处理、滑动窗口切分、标签对齐和数据划分,是避免模型过拟合与泄漏的关键。从离线评估到现场部署,需结合混淆矩阵、阈值调整和置信度规则,构建数据闭环。本文面向设备管理与算法工程师,讲解滚刀状态识别的完整技术路线与工程落地经验。

1. 滚刀状态识别这件事,为什么值得用机器学习来做

盾构机和TBM在隧道施工中最头疼的不是推进参数怎么调,而是刀盘上的滚刀什么时候坏、坏到什么程度。换刀一次动辄要停机几小时甚至几天,误判一次就是工期和成本的双重损失。传统做法靠有经验的工程师听异响、看渣样、翻施工记录,但这套经验很难复制,也做不到连续监测。Python基于机器学习的滚刀状态识别,要做的就是把振动信号、掘进参数这些时序数据变成分类模型的输入,用CNN、LSTM、GRU、SVM、随机森林等模型判断滚刀当前处于正常磨损还是偏磨、崩刃、断裂状态。这篇文章适合两类人:一类是做盾构/TBM设备管理的技术人员,想搞清楚这套方案到底怎么落地;另一类是搞机器学习的工程师,想找一个真实工业时序分类项目来练手,搞懂信号数据怎么预处理、模型怎么选、坑在哪里。

2. 滚刀状态识别的五种模型是怎么选出来的:数据形态与建模逻辑

2.1 滚刀状态数据集的结构:传感器信号与标签定义

先搞清楚你手里会有什么数据。滚刀状态识别项目里的数据集,绝大多数不是图像,而是多通道的时间序列。常见的信号来源是加速度传感器、声发射传感器、刀盘扭矩、推进油缸压力和贯入度,采样率从几百赫兹到几万赫兹不等。每个通道在某个时间窗口内的一段波形,对应一个滚刀状态的标签。

标签怎么定,这是个容易被低估的问题。我见过不少项目把标签简单写成“正常”“磨损”“崩刃”三类,但实际工程里“偏磨”和“正常磨损”的边界很模糊。更靠谱的做法是结合刀具巡检记录:每次开仓检查时记录滚刀的实际状态照片和磨损量,再把这段时间之前的传感器信号打上对应标签。这里有个关键点,信号和标签之间有时间对应关系,不是随便取一段信号就能打标签,必须对齐到刀具真实的服役时间段。

数据格式上,常见组织方式是每个样本保存为一行特征向量,或者每个样本是一个二维数组,形状大概是(通道数, 序列长度)。如果是原始波形,通道数就是传感器数量,序列长度就是窗口覆盖的采样点数。有些数据集会直接把时域特征提取好,存成csv交给分类器用。拿到数据第一步永远是看数据字典,确认每一列是什么、采样率多少、标签取值有哪些,别急着跑模型。

2.2 CNN、LSTM、GRU各自在处理哪类信号特征

滚刀破岩产生的振动信号有很强的局部突变特征。刀圈崩刃时,振动波形会出现尖峰;偏磨时,波形的高频成分分布会发生规律性变化。CNN在这种场景下的优势是它能自动学习局部波形模式,不需要你手工设计“峰值是不是异常高”这种规则。一维卷积核在时间轴上滑动,本质上是在搜索“什么样的小段波形对应什么样的状态”。即使把原始信号直接喂进去,CNN也能提取出有区分度的特征。

但滚刀信号毕竟是个时间序列,状态变化是有前后文关系的。磨损是一个渐进过程,前一段的振动特征在某种程度上预示了后一段的状态。LSTM就是为这种时间依赖设计的,它通过门控机制把历史信息保留在隐状态里。GRU可以看成是LSTM的轻量变体,参数更少,训练更快,在数据量不够大的时候往往比LSTM更稳。

一个实用经验是:如果数据是切片式的状态判断,CNN足够;如果数据是连续监测流,需要结合窗口序列做趋势判断,LSTM或GRU更合适。很多项目实际做的是先把信号切片,用CNN提取空间特征,再接LSTM建模时间依赖,最后用全连接层分类。这种组合结构在滚刀识别里很常见,不一定要从零发明什么新网络。

2.3 SVM和随机森林在滚刀识别里的位置:特征工程还是端到端

深度学习固然热门,但滚刀状态识别项目里SVM和随机森林从来不是摆设。原因很实际:真实隧道工程能积累的有效故障样本很少,崩刃和断裂样本可能只有几十条,这种规模下深度学习很容易过拟合。SVM在小样本、高维特征下的分类能力依然能打,随机森林则对特征噪声和过拟合的容忍度都很高。

这两个模型的关键在于特征工程。常见做法是从每个信号窗口里提取时域特征(均值、方差、均方根、峰值因子、峭度)和频域特征(FFT主频幅值、频谱能量占比),再加上掘进参数(推力、扭矩、贯入度)作为补充特征。特征向量拼接好后做标准化,喂给SVM或随机森林。跟端到端的CNN相比,这条路线的优势是训练快、可解释性强——你可以直接看出“哪个特征对崩刃判断贡献最大”,这会帮你反向验证传感器布局是否合理。

3. 把原始信号变成模型能吃的输入:预处理、窗口化与数据划分

3.1 加载数据并清洗无效段

原始采集信号里大量存在无效段:停机保压阶段、换刀前后的空转阶段、传感器掉线导致的平线尖峰。这些数据如果不清理,模型学会的不是滚刀状态,而是“设备有没有在转动”。第一步先把这些无效段剔除。下面这段代码演示了从csv加载多通道信号,并用简单的统计规则过滤掉明显异常段。

import numpy as np import pandas as pd # 假设原始数据是逐行采样的多通道信号 # 列名: time, vib_x, vib_y, vib_z, torque, thrust df = pd.read_csv("raw_signal.csv") # 去掉振动通道全为零的段(传感器掉线常表现为平线) vib_cols = ["vib_x", "vib_y", "vib_z"] df["vib_energy"] = df[vib_cols].pow(2).sum(axis=1) # 一个滑动均值窗口,把瞬间尖峰和掉线平线都暴露出来 df["energy_ma"] = df["vib_energy"].rolling(window=100, min_periods=1).mean() # 保留能量高于下限的段,数值需要根据实际信号均值调 valid_df = df[df["energy_ma"] > 0.01].copy() # 再按刀盘转速过滤:转速接近0说明刀盘没有正常掘进 valid_df = valid_df[valid_df["rpm"] > 0.5].copy()

这段代码的逻辑分三步:先算振动能量的滑动均值,用来过滤传感器掉线造成的平线;再用刀盘转速判断是否处于掘进状态。参数window=100和阈值0.01不是通用答案,需要先画出信号总览图,看正常掘进段的能量水平大概是多少,再定阈值。这一步做得越仔细,后面模型的准确率越靠谱。

3.2 滑动窗口切分与标签对齐

清洗完成后,信号是连续的长序列,不能直接当样本用。需要把长序列切成固定长度的窗口,每个窗口对应一条样本和一个标签。窗口长度怎么选?这取决于刀盘转速和信号采样率。假设采样率是10kHz,刀盘转速是每分钟3转,那么一转就是2000个采样点。窗口至少要覆盖一个完整旋转周期,取2048或4096比较稳妥。步长可以设成窗口的一半,这样相邻窗口有50%重叠,样本量翻倍的同时保留时间连续性。

def sliding_window(data, labels, window_size, step_size): samples = [] sample_labels = [] for i in range(0, len(data) - window_size, step_size): # 取一个窗口内的多通道信号,形状为 (channels, window_size) window = data[i:i + window_size].T samples.append(window) # 标签取窗口中间时刻对应的标签,避免边界突变 mid_idx = i + window_size // 2 sample_labels.append(labels[mid_idx]) return np.array(samples), np.array(sample_labels)

注意这里标签的取法,用的是窗口中间时刻的标签而不是窗口起始时刻。原因是刀具状态在窗口内可能正好发生切换,起始标签未必能代表整个窗口的状态。中间时刻最稳妥。窗口形状设计成(channels, window_size)是为了方便后续喂给CNN,Conv1d的输入格式正是这个形状。如果后面要用LSTM,需要转置成(batch, seq_len, features),代码里通过np.transpose调整即可。

3.3 训练集/验证集/测试集怎么划分才不算泄漏

时间序列项目最容易踩的坑就是随机打乱数据划分。相邻滑动窗口的重叠部分高度相似,如果随机划分,训练集里可能藏着测试集窗口的“复印件”,验证出来的准确率虚高,一到现场就原形毕露。正确的做法是按刀号或者按掘进段划分:同一把刀的数据要么全部在训练集,要么全部在测试集,不能混。

from sklearn.model_selection import GroupShuffleSplit # cutter_id 是每个窗口对应的刀具编号 groups = window_labels_df["cutter_id"].values splitter = GroupShuffleSplit(n_splits=1, test_size=0.2, random_state=42) train_idx, test_idx = next(splitter.split(samples, labels, groups)) X_train, X_test = samples[train_idx], samples[test_idx] y_train, y_test = labels[train_idx], labels[test_idx]

GroupShuffleSplit保证了同一组数据不会被切到两边。这里groups传的是刀具编号,这是滚刀识别项目里最合理的分组维度。如果数据里没有刀具编号,至少要按连续掘进段编号,不要把一整段连续信号切开分到两边。另外,random_state固定好以后不要频繁改动,否则不同模型对比时的数据基础就变了。

4. 在本地复现五种模型的最简实现:从CNN到随机森林

4.1 用一维CNN识别信号的局部磨损特征

CNN模型是滚刀识别里最快见效的模型。输入是一段多通道波形,输出是状态类别概率。网络结构不用太复杂,三个卷积块加一个全连接层就足够作为初版。关键参数是卷积核大小和通道数:核大小决定了模型观察的局部窗口宽度,在10kHz采样率下,kernel_size=32相当于观察3.2毫秒内的波形;通道数决定特征图数量,不宜一开始就设太大,64到128之间比较常见。

import torch import torch.nn as nn class CnnClassifier(nn.Module): def __init__(self, n_channels, n_classes): super().__init__() self.features = nn.Sequential( # 输入 (batch, n_channels, seq_len) nn.Conv1d(n_channels, 64, kernel_size=32, stride=2, padding=16), nn.BatchNorm1d(64), nn.ReLU(inplace=True), nn.MaxPool1d(kernel_size=2, stride=2), nn.Conv1d(64, 128, kernel_size=16, stride=2, padding=8), nn.BatchNorm1d(128), nn.ReLU(inplace=True), nn.AdaptiveAvgPool1d(output_size=8), ) self.classifier = nn.Sequential( nn.Flatten(), nn.Dropout(p=0.3), nn.Linear(128 * 8, 64), nn.ReLU(inplace=True), nn.Linear(64, n_classes), ) def forward(self, x): return self.classifier(self.features(x))

这个结构的意图是:前两个卷积层逐步把原始波形压缩成高维特征图,AdaptiveAvgPool1d把长度不固定的特征图池化成固定长度,以便接入全连接层。Dropout(0.3)用来缓解小样本过拟合。训练时注意学习率不要一开始设太大,我通常用1e-3配合AdamW,batch size在32到64之间。等模型收敛后,再微调kernel_size=32这个参数,看看更小的核是否更关注局部尖峰,或者更大的核是否能捕捉到磨损的周期模式。

4.2 LSTM和GRU:把时间顺序当作背景知识

LSTM和GRU把每个采样点按顺序输入,网络内部维护一个记忆状态,让最后一个时间步的输出携带整个序列的历史信息。在滚刀振动信号上,这意味着模型能捕捉到“振动幅值逐渐变大然后突然出现尖峰”这类渐进变化模式。但纯LSTM有个缺点:它对局部特征的响应不如CNN敏锐。所以实际项目里常见做法是用CNN先降维,把CNN提取的特征序列再输入LSTM。下面是一个可以单独运行的纯LSTM分类器。

import torch import torch.nn as nn class LstmClassifier(nn.Module): def __init__(self, n_features, hidden_size, num_layers, n_classes): super().__init__() self.lstm = nn.LSTM( input_size=n_features, hidden_size=hidden_size, num_layers=num_layers, batch_first=True, bidirectional=True, dropout=0.2 if num_layers > 1 else 0.0, ) self.fc = nn.Sequential( nn.Dropout(0.3), nn.Linear(hidden_size * 2, n_classes), ) def forward(self, x): # x 形状: (batch, seq_len, n_features) lstm_out, (h_n, c_n) = self.lstm(x) # 取双向LSTM最后一步的隐状态拼接 last_h = torch.cat((h_n[-2, :, :], h_n[-1, :, :]), dim=1) return self.fc(last_h)

这里的bidirectional=True是双刃剑。双向LSTM能同时利用窗口前后的上下文,比单向更准,但推理延迟也更高。在滚刀监测场景里,如果是离线分析无所谓;如果要做实时在线监测,建议先试单向。hidden_size一般取64或128,num_layers=2处理复杂时序结构,但如果训练样本少,层数降为1反而更稳。GRU的实现几乎一模一样,把nn.LSTM换成nn.GRU,返回值的结构少一个c_n即可。

4.3 SVM和随机森林:手工特征加经典分类器

如果数据量小或者你想快速看一个基线结果,SVM和随机森林是最快的路。它们的输入不是原始波形,而是手工提取的特征向量。特征提取这一步决定了模型上限。下面代码从每个窗口里提取时域和频域特征,然后分别训练两个模型。

import numpy as np from scipy.fft import rfft, rfftfreq from sklearn.svm import SVC from sklearn.ensemble import RandomForestClassifier from sklearn.preprocessing import StandardScaler from sklearn.pipeline import make_pipeline def extract_features(window): feats = [] for channel in window: # 时域特征 feats.append(np.mean(channel)) feats.append(np.std(channel)) feats.append(np.sqrt(np.mean(channel ** 2))) # 均方根 feats.append(np.max(np.abs(channel))) # 峰值 feats.append(np.mean((np.abs(channel) > np.std(channel)).astype(float))) # 峰值占比 # 频域特征 spectrum = np.abs(rfft(channel)) freqs = rfftfreq(len(channel), d=1 / sample_rate) dominant_idx = np.argmax(spectrum[1:]) + 1 feats.append(freqs[dominant_idx]) # 主频 feats.append(spectrum[dominant_idx] / len(channel)) # 主频幅值归一化 return feats X_feat = np.array([extract_features(w) for w in X_train]) X_test_feat = np.array([extract_features(w) for w in X_test]) # SVM:RBF核适合非线性边界,C控制过拟合 svm_model = make_pipeline(StandardScaler(), SVC(C=10, gamma="scale", kernel="rbf")) svm_model.fit(X_feat, y_train) # 随机森林:n_estimators 用200,max_depth 限制在10以内防止过拟合 rf_model = RandomForestClassifier(n_estimators=200, max_depth=10, random_state=42) rf_model.fit(X_feat, y_train)

SVM对特征尺度敏感,所以管道里必须先做StandardScaler。gamma="scale"是根据特征数量自动计算核宽度的常用设定,比手动调gamma稳得多。随机森林不需要标准化,但max_depth一定要限制,否则在特征维度高的时候很容易记住噪声。这两类模型训练完可以直接看feature_importances_,如果主频相关的特征排在最前面,说明滚刀状态确实在频域上有明显反映;如果推力扭矩特征贡献很大,也别意外,这代表负载变化比振动更能区分状态。

4.4 统一评估口径:混淆矩阵与分类报告

五个模型全部训练完之后,评估方式不能各看各的准确率。准确率在类别不平衡时是骗人的,要统一用每个类别的精确率、召回率和F1分数。下面的代码输出分类报告并绘制混淆矩阵。

from sklearn.metrics import classification_report, confusion_matrix # 以测试集为例,y_pred 替换成任意模型的预测结果 y_pred = svm_model.predict(X_test_feat) print(classification_report(y_test, y_pred, target_names=["正常", "偏磨", "崩刃", "断裂"])) cm = confusion_matrix(y_test, y_pred) print(cm) # 从混淆矩阵里提取最常见错误 # 每一行是真实类别,每一列是预测类别 for i in range(len(cm)): for j in range(len(cm)): if i != j and cm[i][j] > 0: print(f"真实类别 {i} 被误判为 {j} 的数量: {cm[i][j]}")

这里最关键的不是总准确率,而是“崩刃被误判成正常”的数量。这个数字如果是两位数,系统就不敢上线,因为漏检的代价极高。对比模型时,我会把每个模型的混淆矩阵单独保存,重点看少数类别的召回率。CNN在数据量充足时通常能拿到最高的整体F1,但SVM和随机森林在少数类上的稳定性有时反而更好,这也是为什么项目里经常保留多个模型做最终投票。

5. 滚刀状态识别项目的五个典型坑:现象、原因、处理

5.1 训练集acc很高,现场换数据就翻车

现象:本地测试集准确率95%以上,拿到另一台盾构机的数据或者换了传感器安装位置后,准确率直接掉到60%以下。

原因:模型学到了当前工况和传感器布局特有的噪声模式,而不是滚刀状态本身的规律。最典型的是不同标段岩石硬度不同,振动幅值分布差异巨大;传感器安装力矩不同,频响特性也随之改变。模型只在单一分布上训练,换场景自然失效。

解决:数据收集阶段就要刻意涵盖多种工况,至少包括不同岩石硬度、不同刀盘转速、不同推进力区间。训练前对每个窗口做幅值归一化,让振动信号除以该窗口的均方根值,削弱绝对幅值差异。更兜底的做法是把“工况参数”也作为模型输入特征,让模型学会在推力大、转速高的时候适当调高崩刃判断阈值。

5.2 正常样本占九成,少数类全被淹掉

现象:训练集里正常样本占了90%以上,崩刃和断裂样本加一起不到5%。模型把所有样本都预测成正常,准确率90%,但完全没有实用价值。

原因:隧道工程里滚刀大部分时间确实是正常的,故障本来就是小概率事件。模型在交叉熵损失下只要全预测成多数类,就能把损失压得很低。

解决:先做类别重采样,用imblearn的SMOTE对少数类过采样,或者简单地把少数类样本复制几份。然后给损失函数加类别权重,让少数类样本的梯度贡献变大。最后一个关键操作是改变评估指标,不再看准确率,改看少数类的召回率和F1。模型目标从“整体最准”变成“故障最不能漏”。

5.3 LSTM比CNN慢很多,精度反而不占优

现象:训练时间翻了几倍,推理速度慢一个数量级,最终F1还比CNN低一两个百分点。

原因:纯LSTM按时间步逐个处理,顺序计算无法并行,训练和推理天然就慢。滚刀振动信号里决定状态的关键特征更多是局部突变,而不是长距离依赖。LSTM的优势没有发挥出来,反而增加了过拟合风险。

解决:先用CNN跑一版,看看结果;再在CNN输出的特征序列上接一层单向、单层的LSTM。这样既保留了局部特征提取能力,又只用少量参数建模时间趋势。如果加了LSTM后准确率没有提升超过1个百分点,直接把LSTM去掉,工业项目里差一个百分点的准确率换两倍推理性能,不划算。

5.4 标签是人工标的,边界本身就模糊

现象:训练集里同一段振动波形,A工程师标“偏磨”,B工程师标“正常磨损”。模型训练时这两个标签互相打架,loss降不下去。

原因:人工巡检记录的刀具状态本来就带有主观判断,尤其是“偏磨”和“正常磨损”之间的过渡态,连现场照片都很难说清。

解决:不要强行让模型学一个不存在的清晰边界。把容易混淆的类别合并,比如只分“正常”和“异常”,“异常”里再做二次细分类。另一个办法是清理标签:把两个工程师标注不一致的样本挑出来,人工复核,确实模糊的直接丢掉。模型训练宁缺毋滥,几十个干净样本好过几百个互相矛盾的样本。

5.5 深度学习与传统模型对比时的不公平

现象:报告里写“CNN准确率93%,SVM只有85%”,然后得出SVM不行的结论。仔细一看,CNN用了全部原始信号,SVM却只用了三个手工特征。

原因:对比基线没有建立在同样的信息基础上。传统模型给的特征太少,深度学习吃的是全量信息,输了很正常,但这不是公平的模型对比。

解决:给特征工程模型喂同样丰富的信息。把原始波形也做成特征,比如把信号分帧后计算每一帧的均方根和峰值,拼进特征向量。SVM和随机森林的优势本身就是处理高维特征,模型对比时尽量让两边输入的信息量一致。统一数据划分和评估函数,谁优谁劣才有说服力。

6. 让模型下现场:从离线指标到可用性验证

6.1 嫌准确率不够直观?先看混淆矩阵读错

五个模型训练完,不要只盯着一行准确率。我一般会做出每个模型的混淆矩阵,打印成表格,然后手工算两类代价:误报警代价和漏报警代价。误报警意味着无谓停机开仓检查,一次成本几十万;漏报警意味着带着崩裂的滚刀硬推,严重时刀圈碎块卡死刀盘,成本是几百万起。所以判断模型能不能用,先看漏检数字,再看误检数字。如果混淆矩阵里“断裂被判成正常”不是0,这个模型就还不具备上线条件。

如果模型表现不好,先试试调整判别阈值。分类模型输出的是概率,默认阈值是0.5,但对滚刀状态来说,应该把“崩刃”和“断裂”的预测阈值调低,比如0.3就触发告警,宁可多看一次也别漏过一次。这个操作不需要重新训练模型,只是换一个判别规则。

prob = cnn_model.predict_proba(X_window) # 返回各类别概率 cutter_break_prob = prob[:, 2] + prob[:, 3] # 崩刃+断裂概率合并 alert_level = np.where(cutter_break_prob > 0.7, 2, 0) alert_level = np.where((cutter_break_prob > 0.3) & (cutter_break_prob <= 0.7), 1, alert_level) # 0=正常 1=预警 2=立即停机检查

两级告警比单一分类更符合现场操作习惯。预警级别可以让工程师加密观察渣样和掘进参数,立即停机级别才触发开仓检查。阈值0.3和0.7不是拍脑袋定出来的,需要结合历史误报率反推:如果频繁误报导致班组不再相信告警,就把0.7往上调;如果出现过漏报,就把0.3往下调。这个调参过程本身就是对模型可靠性的验证。

6.2 用置信度输出与维修决策联动

模型下现场的最终形态不是一个孤立的预测脚本,而是跟维修决策联动。我习惯在模型输出后加一个规则层:模型输出“偏磨”且置信度高于0.8时,系统自动将这条记录写入维修工单建议;置信度在0.5到0.8之间时,只推送预警消息给值班工程师;低于0.5则不推送。这样做的目的是减少告警疲劳,让工程师把注意力花在高置信度的异常事件上。

这里还有一个长期经验:每次开仓检修后,把实际检查结果回填到数据集里,形成新的训练样本。模型部署不是终点,而是数据闭环的一个节点,滚刀识别模型的准确率是靠不断回填真实检查结果迭代上去的。项目做到最后,最值钱的部分往往不是某个模型有多先进,而是你积累了一批带真实状态标签的难样本。我会把这些难样本单独存档,每次模型迭代都拿同一批样本做回归测试,防止新版本修了一个问题又引入另一个问题。这就是做工业识别的职业习惯,模型再花哨,不如数据闭环可靠。希望这些经验对你有用,也祝你的滚刀识别项目少走弯路。

本文还有配套的精品资源,点击获取

需要专业的网站建设服务?

联系我们获取免费的网站建设咨询和方案报价,让我们帮助您实现业务目标

立即咨询