简介:面向刀具磨损状态识别场景的机器学习项目资源包,整合CNN、LSTM、GRU、SVM、随机森林等多种模型,用于解决滚刀走刀数据下的磨损阶段分类问题。整个资源包共包含15个文件,核心为8个Python脚本,分别负责数据合并、特征生成、模型训练与测试等环节;4个CSV文件提供铣刀1、4、6的走刀数据集,另有PNG示意图与Markdown说明文档,方便快速理解代码结构。压缩后仅2.54MB,轻量且目录结构清晰,数据划分明确:铣刀1共315个样本用于训练,铣刀4、6各315个样本作为测试集,可直接基于源码运行,减少数据整理与预处理工作量。目前已吸引276人学习,适合正在从事工业故障诊断、时间序列分类课题的开发者参考。借助这套源码可深入掌握一维CNN对时间序列局部特征的捕捉能力,同时对比LSTM、GRU在序列建模上的表现,以及SVM、随机森林等传统模型的基准效果;训练与测试脚本已按上述划分准备好,便于快速复现实验、替换不同模型,并在此基础上扩展自己的特征工程方案。
1. 用Python做滚刀状态识别,最容易被忽视的不是模型而是数据
用Python做盾构/TBM滚刀状态识别,大多数人一上来就选模型,把CNN、LSTM、GRU、SVM、随机森林挨个试一遍,结果发现测试集指标不错,现场一用就失灵。问题通常不在模型,而在数据:传感器信号怎么切、标签怎么对齐、时频图怎么生成、不平衡样本怎么处理,这些环节决定识别系统能否稳定落地。这里按一套可复用的工程流程,把滚刀状态识别的数据准备、特征构造、模型参数、训练排错和部署阈值串起来,适合正在做工业数据挖掘或设备健康管理的工程师参考,也适合作为入门机器学习的完整实战案例。
2. 滚刀状态识别的数据与特征工程:从传感器信号到CNN输入
模型只是流水线的后半段。滚刀状态识别通常不直接拿原始电压信号送给模型,而是先完成切片、去噪、特征化和标签对齐。这一章按实际项目最常见的顺序展开,把数据采集和特征构造中的关键坑位讲清楚。
2.1 数据采集与状态标签对齐
滚刀状态识别用的数据,工业现场最常见的是振动加速度信号,传感器安装在刀盘或滚刀座附近,同步采集的还有推进速度、转速、推力、扭矩等工艺参数。采样频率要覆盖可能出现的故障频率,一般不低于1kHz,实验台条件下甚至会到10kHz以上。如果只用推力、扭矩这类工艺参数,模型学到的更接近掘进工况而不是滚刀磨损,所以振动信号才是识别磨损的核心来源。
标签是监督学习的起点。常见做法有两种:一种是在实验台上对滚刀做定时磨损测量,用磨损量连续值划分状态;另一种是在盾构机停机开仓时人工检查滚刀磨损等级。第二种更贴近“状态识别”的定义,但检查周期长,标签量少。无论哪种都要注意标签对齐:传感器数据是连续流,而标签对应一个时间段,如果直接把整段时间的样本都标成同一状态,会混入状态切换前后的过渡数据。一般建议把标签边界前后各裁掉一部分,或者单独设置一个验证集排除边界样本,避免模型学到“换刀前后”的时序假象。
2.2 信号切片、滤波与归一化的具体做法
连续信号需要切成样本。常见窗口长度为1到2秒,重叠率50%。这样每个样本保留至少一个旋转周期的完整信息,又不会因为窗口太短丢失低频分量。切完之后做三类处理:去趋势、带通滤波、归一化。去趋势用于消除传感器零漂,带通滤波抑制低频扰动和高频噪声,归一化让不同掘进环境的信号尺度可比。
import numpy as np from scipy import signal def preprocess_signal(raw, fs=1000, lowcut=10, highcut=500): # 去趋势:去除线性漂移 detrended = signal.detrend(raw, type='linear') # 巴特沃斯带通滤波,order=4 b, a = signal.butter(4, [lowcut, highcut], btype='bandpass', fs=fs) filtered = signal.filtfilt(b, a, detrended) # z-score归一化,消除振幅尺度差异 normalized = (filtered - np.mean(filtered)) / np.std(filtered) return normalized def make_spectrogram(signal_1d, fs=1000, nperseg=128): # 短时傅里叶变换,返回频率轴、时间轴和功率谱 f, t, Sxx = signal.spectrogram( signal_1d, fs=fs, nperseg=nperseg, noverlap=nperseg // 2 ) # 对数压缩,避免少量大值主导网络梯度 return f, t, 10 * np.log10(Sxx + 1e-10)参数说明:detrend(type='linear')去掉线性趋势项,防止后续归一化被缓慢漂移带偏;butter的4阶带通在1kHz采样率下常用10~500Hz,低频段排除刀盘回转分量,高频段滤掉电器噪声;filtfilt是零相位滤波,避免群延迟导致标签和信号错位。nperseg决定频率分辨率,值越大频率越细,但时间分辨率越低,128在1kHz下约对应0.128秒窗口,适合滚刀冲击类信号。
2.3 时域、频域和时频图像特征:给CNN和SVM喂不同输入
CNN天然适合从时频图像里提取局部模式,所以把一维信号转成STFT灰度图或小波时频图,比直接输入一维波形更稳定。传统模型则需要一组手工特征,喂给SVM和随机森林。
| 特征类型 | 典型特征 | 适用模型 |
|---|---|---|
| 时域 | RMS、峰值因子、峭度、波形因子 | SVM、随机森林、LSTM |
| 频域 | 重心频率、均方频率、频带能量占比 | SVM、随机森林 |
| 时频图像 | STFT灰度图、CWT尺度图 | CNN |
| 原始序列 | 多时间步振动幅度 | LSTM、GRU |
常见误区是让LSTM去吃STFT图,或者让CNN去吃一维波形。CNN看到的是“整张图的空间结构”,LSTM看到的是“时间步之间的顺序依赖”。如果做混合模型,需要保证数据量足够,否则容易过拟合。工程上更稳的做法是分别建基线,再决定是否融合。生成CNN输入时,只需把预处理后的信号切片交给make_spectrogram,保存为灰度图即可。
2.4 类别不平衡与数据增强:别让正常样本淹没磨损信号
正常滚刀的样本远多于磨损和崩裂样本,直接训练会让分类器把所有样本都判为“正常”。处理方式分两类:对深度学习模型,在时频图上做增强;对传统模型,在特征空间做重采样。时频图增强可以用时间掩码、频率掩码和随机裁剪,类似语音识别的SpecAugment思路,但裁剪幅度不要太大,否则高频冲击部分被丢光。
from imblearn.over_sampling import SMOTE from sklearn.model_selection import train_test_split # X_features 是手工特征,y_labels 是状态类别 X_train, X_test, y_train, y_test = train_test_split( X_features, y_labels, test_size=0.2, stratify=y_labels, random_state=42 ) # 只在训练集上做SMOTE,避免验证集信息泄漏 smote = SMOTE(k_neighbors=3, random_state=42) X_train_res, y_train_res = smote.fit_resample(X_train, y_train)参数说明:stratify=y_labels保证切分后各类别比例一致;k_neighbors=3是SMOTE合成样本时的近邻数,设置过小容易生成噪声点,过大则合成样本贴近原始点、失去多样性。注意SMOTE只适合手工特征空间,不能用于一维序列或时频图,否则会破坏时间连续性。
3. 模型选型与核心参数:CNN、LSTM、GRU、SVM、随机森林怎么调才能对比
不同模型的输入和参数差异很大,但对比时必须使用同一份验证集和同一组评估指标,否则得到的“谁更好”不成立。这一章讲清楚每个模型在滚刀状态识别里的定位,以及动手前必须确定的参数。
3.1 CNN处理时频图的关键参数
CNN输入形状是(height, width, 1),height对应频率轴,width对应时间轴。我一般先用两个卷积块看收敛情况,而不是一上来堆ResNet。卷积核用3×3,池化用2×2最大值池化,滤波器数量按32、64、128递增。优化器选Adam,初始学习率1e-3,配合学习率衰减。
| 参数 | 建议值 | 说明 |
|---|---|---|
| batch_size | 16~64 | 时频图占显存较多,太大容易OOM |
| filters | 32 -> 64 -> 128 | 逐层翻倍 |
| kernel_size | 3×3 | 对振动时频图的局部频率-时间模式最稳 |
| dropout | 0.3~0.5 | 放在全连接层之前 |
| optimizer | Adam(lr=1e-3) | 后期用ReduceLROnPlateau降到1e-5 |
这里的关键是池化次数。输入图如果只有32×32,池化两次就变成8×8,再池化会丢失太多细节。CNN在滚刀识别里容易过拟合,因为样本量通常只有几千到几万,所以Dropout和早停比网络深度更重要。
3.2 LSTM/GRU序列建模:时间步长与门控单元选择
如果使用原始一维信号,LSTM输入形状是(batch, time_steps, features)。time_steps决定模型能看到多长的历史,一般取采样频率的一半到一倍。例如1kHz采样,1秒窗口对应1000个时间步,但直接塞给LSTM会让训练很慢。工程上会先降采样到200~500Hz,或者在一个时间步内放若干个采样点的统计量。
| 参数 | LSTM | GRU |
|---|---|---|
| units | 32~128 | 32~128 |
| dropout | 0.2~0.4 | 0.2~0.4 |
| bidirectional | 可选 | 可选 |
| return_sequences | 最后一层False | 同左 |
GRU参数少一个门,在中小数据集上比LSTM更稳。滚刀状态识别如果只有几万样本,优先试验GRU。归一化要按整个窗口的均值和标准差做,不能在时间步内部单独做,否则破坏幅值变化信息。
3.3 SVM和随机森林:特征尺度与类别权重
SVM对特征尺度极其敏感,必须先用StandardScaler。RBF核是最常用选择,C是误分类惩罚,gamma控制单个样本的影响半径。随机森林不要求标准化,但需要对特征做重要性筛选,否则树的分裂会被无关特征干扰。两个模型都建议设置class_weight='balanced',让模型自动提高少数类权重。
| 模型 | 关键参数 | 排查方向 |
|---|---|---|
| SVM | kernel='rbf', C=10, gamma='scale' | C过大过拟合,gamma过小欠拟合 |
| 随机森林 | n_estimators=500, min_samples_leaf=2 | 叶子节点太小容易过拟合 |
| 通用 | class_weight='balanced' | 解决类别不平衡 |
实际项目中,SVM在小样本特征集上往往比深度学习高,因为手工特征已经压缩了信息。随机森林训练快,还能输出特征重要性,适合作为筛选特征的工具。
3.4 统一评估流程:多模型对比不能只看准确率
不平衡数据下准确率没有意义,要用macro F1和混淆矩阵。深度学习模型固定训练/验证/测试集切分,传统模型用5折交叉验证。最后统一输出分类报告。
from sklearn.metrics import classification_report, confusion_matrix from tensorflow.keras.callbacks import EarlyStopping model.compile( loss='sparse_categorical_crossentropy', optimizer='adam', metrics=['accuracy'] ) early = EarlyStopping( monitor='val_loss', patience=10, restore_best_weights=True ) history = model.fit( X_train, y_train, validation_data=(X_val, y_val), epochs=50, batch_size=32, callbacks=[early] ) y_pred = np.argmax(model.predict(X_test), axis=1) print(classification_report(y_test, y_pred)) print(confusion_matrix(y_test, y_pred))参数说明:EarlyStopping监控验证集loss,连续10轮不改善就停止训练;restore_best_weights=True保证结束后的权重不是最后一轮而是验证集最优的一轮。classification_report会输出每个类别的precision、recall、F1,比只看accuracy可靠得多。
4. 从源码到训练:滚刀状态识别项目结构与实操代码
项目源码通常会包含数据预处理、特征提取、模型训练三个模块。这一章给出一套能直接运行的代码骨架,并结合上一章的参数说明,把散落的操作连起来。
4.1 项目目录与环境依赖怎么搭
我习惯把数据、特征、模型、输出分开,配合统一的配置文件。目录结构如下:
rolling_cutter_status/ ├── data/ │ ├── raw/ # 原始传感器数据 │ ├── processed/ # 切片、去噪、增强后的数据 │ └── labels.csv # 状态标签 ├── features/ │ ├── extract_features.py │ └── make_spectrograms.py ├── models/ │ ├── train_cnn.py │ ├── train_lstm_gr.py │ ├── train_svm_rf.py │ └── evaluate.py ├── configs/ │ └── params.yaml └── outputs/ ├── checkpoints/ └── reports/依赖环境建议用conda或venv隔离。常用包与用途:
| 依赖包 | 用途 |
|---|---|
| numpy/scipy | 信号处理与数值计算 |
| pandas | 标签与特征表管理 |
| scikit-learn | SVM、随机森林、评估指标 |
| imbalanced-learn | SMOTE过采样 |
| tensorflow | CNN、LSTM、GRU训练 |
| matplotlib | 时频图绘制与结果可视化 |
安装时注意TensorFlow和Python版本兼容,Windows上建议用WSL2或CPU版本跑小数据量实验,GPU版本再放到服务器。
4.2 传统机器学习模型训练代码(SVM、随机森林)
手工特征表通常已经降到几十维,先读入特征和标签,再切训练测试集。SVM需要pipeline,随机森林可以直接用原始特征。
import pandas as pd from sklearn.svm import SVC from sklearn.ensemble import RandomForestClassifier from sklearn.preprocessing import StandardScaler from sklearn.pipeline import make_pipeline train_df = pd.read_csv('features/train_features.csv') X = train_df.drop('label', axis=1).values y = train_df['label'].values # SVM:尺度敏感,pipeline里做标准化 svm_model = make_pipeline( StandardScaler(), SVC(kernel='rbf', C=10, gamma='scale', class_weight='balanced') ) svm_model.fit(X, y) # 随机森林:不要求标准化,class_weight处理不平衡 rf_model = RandomForestClassifier( n_estimators=500, min_samples_leaf=2, class_weight='balanced', n_jobs=-1 ) rf_model.fit(X, y)参数说明:class_weight='balanced'让模型在损失函数中提高少数类权重;n_jobs=-1使用所有CPU核并行训练,但保存的模型文件会更大。SVM的gamma='scale'表示按特征数自动缩放,避免手动调参。
4.3 深度学习模型训练代码(CNN、LSTM/GRU)
CNN读入时频谱图,输入最后一维是通道数。这里以Keras为例,先构建模型,再结合上一章的早停策略训练。
import numpy as np from tensorflow.keras.models import Sequential from tensorflow.keras.layers import Conv2D, MaxPooling2D, Flatten, Dense, Dropout # spectrograms shape: (N, height, width) X = np.load('data/processed/spectrograms.npy') y = np.load('data/processed/labels.npy') X = X[..., np.newaxis] # 增加通道维度 num_classes = len(np.unique(y)) model = Sequential([ Conv2D(32, (3, 3), activation='relu', padding='same', input_shape=X.shape[1:]), MaxPooling2D((2, 2)), Conv2D(64, (3, 3), activation='relu', padding='same'), MaxPooling2D((2, 2)), Flatten(), Dense(128, activation='relu'), Dropout(0.4), Dense(num_classes, activation='softmax') ]) model.summary()说明:X[..., np.newaxis]把(N, height, width)变成(N, height, width, 1),否则Conv2D会报错。padding='same'让卷积不缩小特征图尺寸,池化负责降维。全连接层输出128维,加Dropout是为了抑制过拟合,最后的Dense输出类别数,softmax概率和为1。
训练LSTM/GRU时,只需把输入换成(batch, time_steps, features),模型第一层换成LSTM或GRU,其余评估流程一致。
4.4 训练排错:过拟合、数据泄漏和标签偏移
实际训练中遇到的绝大多数问题可以从损失曲线和混淆矩阵里看出来。以下是我的排查表:
| 现象 | 可能原因 | 对策 |
|---|---|---|
| 训练loss下降,验证loss上升 | 过拟合 | 增强数据、增加dropout、减少epoch |
| 准确率高但少数类召回低 | 类别不平衡 | 调整class_weight、用macro F1早停 |
| 验证指标高于测试指标很多 | 数据泄漏 | 检查归一化和SMOTE是否只fit在训练集 |
| 模型表现和标签随机打乱后差不多 | 标签时间对齐错误 | 重新检查标签边界,裁掉过渡段 |
数据泄漏最隐蔽。如果先对全量数据做StandardScaler再切分,测试集的信息已经参与计算均值和方差,测试结果会虚高。正确做法是只在训练集上计算统计量,再应用到验证和测试集。标签偏移可以通过时间偏移实验验证:把标签整体平移若干秒后重新训练,如果模型精度变化不大,说明模型更多在学工况变化,而不是滚刀状态。
5. 部署时的概率阈值调整:滚刀状态识别落地的最后一个技巧
模型训练完,直接把argmax结果接到现场报警系统会很难受:因为信号有波动,单个样本的预测类别会来回跳,导致频繁误报。更稳的做法是输出每个类别的概率,然后在业务层设置“置信度阈值”和“犹豫区”。例如只有最大概率超过0.7才更新当前状态,概率在0.4到0.7之间保留上一次确认状态,这样现场显示是平滑的,不会被单次抖动带偏。
import numpy as np def commit_status(proba, last_status, high_thres=0.7, low_thres=0.4): confidence = np.max(proba) predicted = int(np.argmax(proba)) if confidence > high_thres: return predicted elif confidence < low_thres: return last_status else: # 犹豫区,维持上一个稳定状态 return last_status代码中的阈值需要根据现场误报率调整。如果报警频繁,调高high_thres;如果希望更早发现磨损,调低阈值。这个阈值还可以按滚刀服役时间做动态调整,比如更换新刀后前50米提高阈值,越接近预估寿命越敏感。
另一个落地细节是模型导出和预处理参数固化。训练好的Keras模型可以先保存为H5或SavedModel,再转成ONNX或TFLite格式,脱离Python环境部署到C++或Java服务里。转换前要确认预处理逻辑一致,特别是滤波系数和归一化统计量,必须和训练时完全相同,否则概率分布会漂移,现场效果大打折扣。
model.save('models/cnn.h5') # 转换成轻量化TFLite,方便边缘设备加载 converter = tf.lite.TFLiteConverter.from_keras_model(model) tflite_model = converter.convert() with open('models/cnn.tflite', 'wb') as f: f.write(tflite_model)部署时建议把lowcut、highcut、nperseg、归一化均值方差写进配置文件,与模型文件一起发布。这样一旦现场出现异常,可以先检查配置是否被改过,而不是怀疑模型本身。滚刀状态识别最考验工程一致性,数据和模型在训练时是什么样子,部署时就应该原样重现,真正的精度不是训练出来的,是每一步都不走样地保下来的。
本文还有配套的精品资源,点击获取