简介:这份资源面向机械加工监测、工业信号处理与深度学习方向的开发者及学生,围绕刀具磨损状态识别这一典型工业场景,提供基于一维卷积神经网络与长短期记忆网络的完整机器学习实践方案。压缩包共15个文件,约2.62MB,以8个Python脚本为核心,覆盖数据合并、特征生成、CNN与LSTM模型构建及测试等环节,并配有4个CSV数据文件、1张结果图与README说明,便于直接运行与复现。资源已有395人学习下载,适合希望将时序深度学习落地到设备状态监测的读者。通过脚本与数据,可掌握1DCNN提取局部特征、LSTM建模长期依赖并完成铣刀磨损分类的完整流程,理解数据预处理、模型训练与测试集评估的衔接方式,为工业刀具磨损实时识别提供可参考的实现思路与排错基础。
1. 刀具磨损状态识别:从听声音到看火花,机器学习到底能替老师傅做什么
车间里最贵的从来不是机床,是那个能听声辨磨损的老师傅。他退休那天,整条线的良品率掉了三个点——这事我见过不止一次。刀具磨损状态识别要解决的,就是把这种"手感"变成可复现的信号处理加机器学习流水线:主轴电流、振动、声发射、切削力,哪一路信号先动,特征怎么提,模型怎么选,最后怎么在产线上跑起来。它适合两类人:一类是设备工程师,手里有数据但不知道怎么建模;另一类是算法工程师,模型调得溜但不懂切削工艺的边界在哪。这篇不聊虚的,从信号采集一路讲到模型上线,中间该踩的坑一个不落。
2. 先搞清楚刀具磨损到底"磨损"了什么:信号选型与标签定义
2.1 后刀面磨损带 VB 才是你要预测的目标
刀具磨损不是单一现象。前刀面月牙洼、后刀面磨损带、微崩刃、塑性变形,四种失效模式对应的信号特征完全不同。工业标准 ISO 8688 里定义的 VB 值——后刀面磨损带宽度——是最常用的量化指标。粗加工场景下 VB 超过 0.3mm 通常就要换刀,精加工可能 0.15mm 就得换。
问题在于:产线上你不可能每切一刀就把刀拆下来测 VB。所以标签的获取方式决定了整个项目的可行性。常见做法有三种:
- 离线测量:每加工若干件后停机,用工具显微镜测 VB。数据质量最高,但采样稀疏,一个刀具全生命周期可能只有 10~20 个标签点。
- 间接标注:用已标注的工件表面粗糙度反推磨损等级。粗糙度超差就标"磨损",但这里有个滞后——粗糙度恶化时磨损往往已经比较严重了。
- 分段离散化:把连续 VB 值分成"初期磨损/正常磨损/急剧磨损"三段,做分类而非回归。这是工程上最务实的做法,因为换刀决策本身就是离散的。
我一般建议新手从三分类做起。回归看起来更"高级",但 VB 的测量噪声和标签稀疏性会让回归模型的 R² 难看到你想砸键盘。
2.2 四路信号的性价比排序
不是所有信号都值得采。按投入产出比排:
| 信号类型 | 传感器成本 | 安装难度 | 对磨损敏感度 | 产线适用性 |
|---|---|---|---|---|
| 主轴电流 | 极低(驱动器自带) | 无需安装 | 中 | 极好 |
| 振动加速度 | 低 | 简单(磁吸) | 高 | 好 |
| 声发射 | 中 | 需耦合 | 极高 | 一般 |
| 切削力测力仪 | 极高 | 需改装夹具 | 极高 | 差 |
主轴电流是性价比之王。伺服驱动器本身就输出电流信号,不用额外装传感器,采样率要求也不高(1~10kHz 足够)。振动信号敏感度高,但安装位置很讲究——装在主轴端和装在工件端,特征差异能大到让你怀疑是不是同一把刀。声发射对微崩刃特别灵,但切削液一浇上去信噪比就崩。测力仪精度最好,但那个价格和安装复杂度,除非是实验室项目,产线上基本不用想。
我的建议:主轴电流 + 振动双通道起步。电流做粗判,振动做精判,融合之后效果比单通道稳得多。
2.3 采样参数怎么定:别让混叠毁了你的一切
采样率、采样时长、采样触发方式,这三个参数定错了,后面特征工程做得再花哨都是白搭。
import numpy as np # 采样参数配置示例 FS = 10000 # 采样率 10kHz,覆盖振动信号主要频段 DURATION = 0.5 # 每次采样 0.5 秒,覆盖至少 3 个主轴旋转周期 TRIGGER_MODE = "spindle_on" # 主轴启动后延迟触发,避开启停瞬态 # 抗混叠滤波器截止频率必须小于 FS/2 ANTI_ALIAS_CUTOFF = 4000 # 留 1kHz 余量给滤波器滚降 # 每次走刀采集的样本数 n_samples = int(FS * DURATION) print(f"每次采样点数: {n_samples}") # 5000 点 # 主轴转速 3000rpm = 50Hz,0.5秒覆盖 25 个旋转周期 spindle_rpm = 3000 rev_per_sample = (spindle_rpm / 60) * DURATION print(f"覆盖旋转周期数: {rev_per_sample}") # 25 个周期采样率的选择有个经验公式:至少是关注频率的 2.5 倍。刀具磨损相关的特征频率通常在 1~5kHz 范围(取决于齿数和转速),所以 10kHz 采样率是安全线。采样时长要覆盖至少 3 个主轴旋转周期,否则每转之间的波动会让你误判。触发方式一定要避开启停瞬态——主轴刚启动那 0.2 秒的电流冲击和正常切削完全不是一回事,混进去就是给模型喂噪声。
注意:如果采样率不够又没加抗混叠滤波器,高频噪声会折叠到低频,你看到的"特征频率"可能是假的。这个坑我在三个项目里见过,每次都是查了半天才发现是混叠。
3. 特征工程:时域、频域、时频域,哪个先上
3.1 时域特征:最快出结果的起点
时域特征计算快、物理意义明确,是验证数据质量的第一道关。常用的就那几个:均值、方差、均方根、峰值因子、峭度、偏度。
import numpy as np from scipy.stats import kurtosis, skew def time_domain_features(signal): """提取时域特征,输入为一维振动或电流信号""" features = {} features['mean'] = np.mean(signal) features['std'] = np.std(signal) features['rms'] = np.sqrt(np.mean(signal**2)) features['peak'] = np.max(np.abs(signal)) # 峰值因子:峰值/RMS,对冲击性磨损敏感 features['crest_factor'] = features['peak'] / (features['rms'] + 1e-10) # 峭度:正常磨损时接近3,微崩刃时飙升 features['kurtosis'] = kurtosis(signal) features['skewness'] = skew(signal) # 波形因子:RMS/整流均值 features['shape_factor'] = features['rms'] / (np.mean(np.abs(signal)) + 1e-10) return features # 模拟一段振动信号 np.random.seed(42) normal_signal = np.random.normal(0, 1, 5000) worn_signal = np.random.normal(0, 1.5, 5000) + np.random.exponential(0.5, 5000) feat_normal = time_domain_features(normal_signal) feat_worn = time_domain_features(worn_signal) for k in feat_normal: print(f"{k:15s} 正常: {feat_normal[k]:8.4f} 磨损: {feat_worn[k]:8.4f}")峭度是最值得盯的指标。正常切削时振动信号接近高斯分布,峭度约等于 3;一旦出现微崩刃或严重磨损,信号里出现冲击成分,峭度能飙到 5 以上。峰值因子同理,对早期磨损比 RMS 更敏感。但时域特征有个致命问题:它们对切削参数变化同样敏感。你改了进给量,RMS 也会变,模型分不清是磨损还是参数调整。所以时域特征通常只做粗筛,不能单独用。
3.2 频域特征:主轴转频和齿通过频率的谐波变化
频域分析的核心逻辑是:刀具磨损会改变切削力波形,进而改变频谱中谐波的幅值分布。主轴转频 f_r = rpm/60,齿通过频率 f_t = f_r × 齿数。磨损加剧时,f_t 及其谐波的幅值通常上升,而高频段能量占比也会变化。
from scipy.fft import fft, fftfreq def freq_domain_features(signal, fs, spindle_rpm, n_teeth): """提取频域特征,重点关注转频和齿通过频率的谐波""" n = len(signal) yf = np.abs(fft(signal))[:n//2] / n xf = fftfreq(n, 1/fs)[:n//2] f_r = spindle_rpm / 60 # 主轴转频 f_t = f_r * n_teeth # 齿通过频率 features = {} # 转频及其前3阶谐波幅值 for i in range(1, 4): idx = np.argmin(np.abs(xf - f_r * i)) features[f'amp_fr_{i}x'] = yf[idx] # 齿通过频率及其前3阶谐波幅值 for i in range(1, 4): idx = np.argmin(np.abs(xf - f_t * i)) features[f'amp_ft_{i}x'] = yf[idx] # 高频段能量占比(>2kHz) high_mask = xf > 2000 features['high_freq_ratio'] = np.sum(yf[high_mask]**2) / (np.sum(yf**2) + 1e-10) # 频谱重心 features['spectral_centroid'] = np.sum(xf * yf) / (np.sum(yf) + 1e-10) return features feats = freq_domain_features(normal_signal, fs=10000, spindle_rpm=3000, n_teeth=4) for k, v in feats.items(): print(f"{k:20s}: {v:.6f}")频域特征比时域稳,因为它对切削参数的变化没那么敏感——转速变了,你跟踪的频率也跟着变,比值关系保持稳定。但前提是你得知道转速和齿数。如果产线上转速经常调,要么实时读 PLC 的转速值,要么用阶次分析代替固定频率分析。
高频段能量占比是我最常用的单一频域指标。磨损加剧时,切削从"剪切"逐渐变成"摩擦+挤压",高频成分明显增多。这个指标在多个项目里都排进了特征重要度前五。
3.3 时频域:小波包分解怎么选基函数和层数
时频域方法适合非平稳信号——变转速、变切深的场景。小波包分解是工程上最常用的,但基函数和分解层数的选择经常让人纠结。
import pywt import numpy as np def wavelet_packet_features(signal, wavelet='db4', level=3): """小波包分解提取各频带能量占比""" wp = pywt.WaveletPacket(data=signal, wavelet=wavelet, mode='symmetric', maxlevel=level) features = {} # 获取所有叶子节点 nodes = [node.path for node in wp.get_level(level, 'natural')] energies = [] for node_path in nodes: node_data = wp[node_path].data energy = np.sum(node_data**2) energies.append(energy) total_energy = sum(energies) + 1e-10 for i, e in enumerate(energies): features[f'wp_energy_band_{i}'] = e / total_energy return features # db4 对切削振动信号通常表现较好 wp_feats = wavelet_packet_features(normal_signal, wavelet='db4', level=3) print(f"频带数量: {len(wp_feats)}") # 2^3 = 8 个频带 for k, v in list(wp_feats.items())[:4]: print(f"{k:25s}: {v:.4f}")基函数选择有个实用原则:db4 到 db8 之间试,别用 haar。haar 太粗糙,频带泄漏严重;db 系列阶数太高又容易过拟合。分解层数看你的采样率和关注频段——3 层分解 10kHz 采样信号,每个频带宽度约 625Hz,够用了。层数太多,每个频带的样本点太少,能量估计不稳定。
注意:小波包分解后的特征维度是 2^level,3 层就是 8 维,4 层 16 维。别一上来就搞 5 层,32 维特征扔进分类器,小样本下必过拟合。
4. 模型选型与训练:从随机森林到一维 CNN 的实战对比
4.1 小样本先上随机森林和 SVM
刀具磨损数据有个特点:标签贵。你不可能为了训练模型把几百把刀都切废。所以大多数实际项目里,标注样本可能就几百到几千条。这个量级下,随机森林和 SVM 比深度学习稳得多。
from sklearn.ensemble import RandomForestClassifier from sklearn.svm import SVC from sklearn.preprocessing import StandardScaler from sklearn.pipeline import Pipeline from sklearn.model_selection import cross_val_score, StratifiedKFold import numpy as np # 假设 X 是特征矩阵 (n_samples, n_features),y 是标签 (0/1/2) # 这里用模拟数据演示流程 np.random.seed(42) n_samples = 600 n_features = 20 X = np.random.randn(n_samples, n_features) # 模拟三类:初期磨损、正常磨损、急剧磨损 y = np.random.choice([0, 1, 2], size=n_samples, p=[0.3, 0.5, 0.2]) # 随机森林管道 rf_pipe = Pipeline([ ('scaler', StandardScaler()), ('clf', RandomForestClassifier( n_estimators=200, # 树的数量,200 通常够用 max_depth=8, # 限制深度防过拟合 min_samples_leaf=5, # 叶节点最少样本数 class_weight='balanced',# 类别不平衡时自动加权 random_state=42 )) ]) # SVM 管道 svm_pipe = Pipeline([ ('scaler', StandardScaler()), ('clf', SVC( kernel='rbf', C=10, # 正则化参数,越大越容易过拟合 gamma='scale', # RBF 核宽度 class_weight='balanced', probability=True )) ]) cv = StratifiedKFold(n_splits=5, shuffle=True, random_state=42) rf_scores = cross_val_score(rf_pipe, X, y, cv=cv, scoring='f1_macro') svm_scores = cross_val_score(svm_pipe, X, y, cv=cv, scoring='f1_macro') print(f"随机森林 F1-macro: {rf_scores.mean():.4f} ± {rf_scores.std():.4f}") print(f"SVM F1-macro: {svm_scores.mean():.4f} ± {svm_scores.std():.4f}")随机森林的优势是特征重要度直接可看,能告诉你哪个信号、哪个特征在起作用。SVM 在小样本高维下表现好,但核函数和 C 值调起来比较玄学。我的经验是:先跑随机森林看特征重要度,砍掉不重要的特征,再上 SVM 精调。这样比一上来就 SVM 网格搜索快得多。
4.2 一维 CNN 什么时候值得上
一维 CNN 适合两种场景:一是原始信号直接输入,不做手工特征;二是样本量足够大(至少几千条以上)。它的优势是能自动学习时序模式,省去特征工程的麻烦。但代价是需要更多数据和算力。
import tensorflow as tf from tensorflow.keras import layers, models def build_1d_cnn(input_length, n_classes): """一维 CNN 用于原始振动信号分类""" model = models.Sequential([ # 第一层卷积:提取局部波形特征 layers.Conv1D(32, kernel_size=15, strides=2, padding='same', activation='relu', input_shape=(input_length, 1)), layers.BatchNormalization(), layers.MaxPooling1D(pool_size=2), # 第二层卷积:组合局部特征 layers.Conv1D(64, kernel_size=7, strides=1, padding='same', activation='relu'), layers.BatchNormalization(), layers.MaxPooling1D(pool_size=2), # 第三层卷积:高层抽象 layers.Conv1D(128, kernel_size=3, strides=1, padding='same', activation='relu'), layers.GlobalAveragePooling1D(), # 替代 Flatten,减少参数 layers.Dropout(0.5), # 防过拟合 layers.Dense(64, activation='relu'), layers.Dense(n_classes, activation='softmax') ]) model.compile( optimizer=tf.keras.optimizers.Adam(learning_rate=1e-3), loss='sparse_categorical_crossentropy', metrics=['accuracy'] ) return model model = build_1d_cnn(input_length=5000, n_classes=3) model.summary()几个关键参数:第一层 kernel_size 要覆盖至少一个齿通过周期,15 是在 10kHz 采样、3000rpm、4 齿条件下的经验值。GlobalAveragePooling 替代 Flatten 能大幅减少参数,小样本下更稳。Dropout 0.5 是标配,别省。
注意:一维 CNN 对输入归一化很敏感。原始振动信号幅值范围可能从 0.1 到 50,不做归一化训练 loss 会震荡得你怀疑人生。用训练集的均值和标准差做 z-score,验证集和测试集用同样的参数。
4.3 类别不平衡:急剧磨损样本太少怎么办
实际数据里,急剧磨损阶段通常只占整个寿命的 10%~15%,三分类下这个类可能只有几十条样本。直接训练,模型会倾向于忽略这个类。
处理方式按优先级排:
- class_weight='balanced':最简单,sklearn 和 Keras 都支持,先试这个。
- SMOTE 过采样:对特征空间做插值,适合特征工程后的表格数据。但别对原始信号做 SMOTE,插值出来的信号没有物理意义。
- focal loss:深度学习场景下替代交叉熵,让模型关注难分类样本。
- 调整决策阈值:不做重采样,在预测阶段根据业务需求调整分类阈值。比如宁可误报也不漏报急剧磨损。
from imblearn.over_sampling import SMOTE from collections import Counter # 假设 X_train, y_train 已经准备好 print(f"原始分布: {Counter(y_train)}") smote = SMOTE( sampling_strategy='not majority', # 只对少数类过采样 k_neighbors=5, # 近邻数,小样本时调小 random_state=42 ) X_resampled, y_resampled = smote.fit_resample(X_train, y_train) print(f"过采样后分布: {Counter(y_resampled)}")SMOTE 的 k_neighbors 在少数类样本少于 10 条时要调小,否则找不到足够的近邻。另外,SMOTE 必须在训练集上做,验证集和测试集保持原始分布——否则你评估出来的指标是虚高的。
5. 避坑与排查:那些让模型在产线上翻车的细节
5.1 现象:实验室 F1 0.95,产线上不到 0.6
原因:训练数据和产线数据的分布不一致。最常见的是切削参数变了——实验室固定转速进给,产线上根据工件材料自动调整。另一个原因是传感器安装位置或耦合方式变了。
解决:在特征层面做域适应。简单做法是加入切削参数作为特征(转速、进给、切深),让模型学会在参数变化下判断磨损。进阶做法是用对抗域适应,但那个工程量就大了。最务实的做法是:产线换型时,采集少量新数据做微调,哪怕每类只有 20 条,fine-tune 一下效果就能拉回来。
5.2 现象:模型把"换刀后初期磨损"误判为"急剧磨损"
原因:新刀刚换上时,后刀面有微观粗糙度,振动信号特征和急剧磨损有相似之处——都是高频能量偏高。模型没学过"刀具寿命阶段"这个上下文。
解决:加入时间特征。最简单的做法是记录每把刀的累计切削时间或累计切削长度,作为额外特征输入。这样模型能区分"新刀的初期磨损"和"老刀的急剧磨损"。如果产线有刀具管理系统,直接读刀具寿命数据;没有的话,用主轴功率积分估算累计切削量。
5.3 现象:交叉验证分数很高,但换一批数据就崩
原因:数据泄漏。最常见的是同一次走刀的数据被随机分到了训练集和测试集。同一次走刀的样本高度相关,模型相当于在"背答案"。
解决:按走刀或按刀具做分组划分,而不是随机划分。sklearn 的 GroupKFold 就是干这个的。
from sklearn.model_selection import GroupKFold import numpy as np # groups 标记每个样本属于哪一次走刀或哪一把刀 groups = np.repeat(np.arange(30), 20) # 30 次走刀,每次 20 个样本 X = np.random.randn(600, 20) y = np.random.choice([0, 1, 2], size=600) gkf = GroupKFold(n_splits=5) scores = [] for train_idx, test_idx in gkf.split(X, y, groups=groups): # 确保同一次走刀的数据不会同时出现在训练和测试集 assert len(set(groups[train_idx]) & set(groups[test_idx])) == 0 # 这里做训练和评估 scores.append(0.85) # 占位 print(f"分组交叉验证平均分: {np.mean(scores):.4f}")5.4 现象:模型推理延迟太高,跟不上产线节拍
原因:用了太深的 CNN 或太多特征。产线节拍可能只有几秒,模型推理必须在几百毫秒内完成。
解决:先看特征维度。如果用了小波包 5 层分解,特征维度 32,加上时域频域总共可能上百维。用随机森林做特征选择,砍到 15~20 维,推理时间能降一个数量级。如果必须用 CNN,考虑模型量化或知识蒸馏。但大多数产线场景,梯度提升树 + 20 维特征就够用了,推理时间在 10ms 以内。
5.5 现象:振动信号里混进了工频干扰
原因:传感器供电或信号线屏蔽不好,50Hz 工频串进来了。这个干扰会污染低频段特征。
解决:硬件上检查屏蔽和接地。软件上做 50Hz 陷波滤波。
from scipy.signal import iirnotch, filtfilt def remove_powerline_noise(signal, fs=10000, freq=50, Q=30): """50Hz 工频陷波""" b, a = iirnotch(freq, Q, fs) return filtfilt(b, a, signal) # 如果工频谐波也明显,在 100Hz、150Hz 也做陷波 cleaned = remove_powerline_noise(normal_signal, fs=10000, freq=50)Q 值选 30 左右,太窄了滤波效果不够,太宽了会把附近的有效频率也吃掉。陷波之后一定要用 filtfilt 做零相位滤波,否则引入的相位延迟会让时域特征偏移。
6. 上线前的最后一道关:用混淆矩阵和置信度校准做验收
模型训练完,别只看准确率。产线上最怕的是"模型很自信地判错"。验收阶段我必做两件事:混淆矩阵细看和置信度校准。
混淆矩阵要按业务代价加权看。把"急剧磨损判成正常磨损"的代价设为 10,"正常磨损判成急剧磨损"的代价设为 1——前者是漏报,后者是误报。漏报导致刀具崩刃、工件报废甚至机床损坏;误报只是提前换刀,浪费一点刀具寿命。两者代价差一个数量级。
from sklearn.metrics import confusion_matrix, ConfusionMatrixDisplay import numpy as np # 假设 y_true, y_pred 是测试集上的真实标签和预测标签 y_true = np.array([0, 1, 2, 0, 1, 2, 0, 1, 2, 0]) y_pred = np.array([0, 1, 2, 0, 1, 0, 0, 2, 2, 0]) cm = confusion_matrix(y_true, y_pred) # 代价矩阵:cm[i,j] 表示真实为 i 预测为 j 的代价 cost_matrix = np.array([ [0, 1, 5], # 真实初期磨损:判成正常代价1,判成急剧代价5 [1, 0, 10], # 真实正常磨损:判成初期代价1,判成急剧代价10 [20, 5, 0] # 真实急剧磨损:判成初期代价20,判成正常代价5 ]) total_cost = np.sum(cm * cost_matrix) print(f"加权总代价: {total_cost}") print(f"混淆矩阵:\n{cm}")置信度校准用可靠性图看。如果模型说"90% 概率是急剧磨损",那在所有说 90% 的样本里,实际急剧磨损的比例应该接近 90%。如果差很远,说明模型过度自信,需要做 Platt scaling 或 isotonic regression 校准。
from sklearn.calibration import CalibratedClassifierCV # 对已训练的分类器做概率校准 calibrated_clf = CalibratedClassifierCV( base_estimator=rf_pipe, # 用你训练好的模型 method='isotonic', # 样本少时用 'sigmoid' cv=3 ) # calibrated_clf.fit(X_train, y_train) # 之后用 calibrated_clf.predict_proba 获取校准后的概率校准之后,产线上可以设一个"灰区":置信度低于 0.7 的样本不自动决策,转人工确认。这样既保证了自动化率,又避免了高代价的误判。
我自己的习惯是:每次模型上线前,一定找产线老师傅看一遍混淆矩阵。他看一眼就能告诉你哪个错判最要命,比任何指标都直接。这个习惯帮我省了至少两次重大翻车。希望帮到你。
本文还有配套的精品资源,点击获取