简介:面向滚刀磨损状态识别需求,资源包提供一整套基于机器学习方法的项目源码与配套数据集。项目覆盖卷积神经网络、长短期记忆网络、门控循环单元、支持向量机、随机森林等多种算法,从数据预处理、特征生成到模型训练与测试均有完整脚本实现。压缩包内共有十五个文件,以八个脚本和四个数据文件为核心,另含特征可视化图片、说明文档及版本忽略配置,整体大小约二点五四兆字节,结构精简,便于直接运行和二次扩展。数据集选用铣刀一号作为训练样本,铣刀四号和六号作为测试样本,每把刀具均包含初期磨损、正常磨损、急剧磨损三类样本,可系统比较不同模型在时间序列分类任务上的鲁棒性与准确率。已有二百八十人浏览学习,适合智能监测领域的研究者、工程师或高年级学生,借助源码快速复现实验流程,并根据自身数据调整模型结构或评估策略,为实际产线刀具状态识别提供参考实现。
1. 滚刀状态识别到底在解决什么问题:TBM 施工中最烧钱的停机原因
某施工段,刀盘掘进到第 12 环时异常停机,拆开检查发现边缘滚刀偏磨到只剩刀毂,刀圈早就该换了。换一把滚刀加上停机时间,直接损失按小时计算,这还没算刀盘拆装的安全风险。滚刀状态识别这个方向,就是用刀盘振动、推进压力、扭矩这些能在线测的信号,在滚刀彻底报废前预判出「偏磨」「崩刃」「卡阻」等状态,把被动拆机变成主动换刀。标题里的 CNN、LSTM、GRU、SVM、随机森林,本质上是在同一条数据流水线上做模型选型对比:哪些模型适合小样本,哪些模型能吃到时序特征,哪些模型部署时最省心。适合正在做机械设备状态监测、故障诊断课题,或者负责盾构/TBM 施工设备管理的工程师参考。
2. 先把数据底子打好:滚刀状态数据的采集、切分与预处理
滚刀状态识别做得成不成,七成取决于数据,剩下三成才是模型。很多项目拿到源码跑不动、跑起来精度又很低,问题基本都出在数据切分和标签对齐上。这一章把采集方案、滑窗切分和特征工程一次讲透。
2.1 滚刀状态数据从哪来:振动、推进参数与换刀记录的标签对齐
滚刀状态识别的原始信号,通常来自刀盘附近安装的振动加速度传感器,以及液压系统自带的推进压力、刀盘扭矩、贯入度、转速等运行参数。常见做法是采集多个通道:刀盘振动 X/Y/Z 轴加速度、推进油缸压力、刀盘扭矩。采样率方面,振动信号常见 1kHz 到 20kHz,推进压力和扭矩这类慢变信号可以降到 10Hz 到 50Hz,合并时统一重采样。
标签来源是换刀记录。每把滚刀从装上新刀圈到被拆下来更换,会有一个完整生命周期;施工单位在换刀时会写状态描述,比如「偏磨」「崩刃」「正常磨损」。这些记录就是分类标签的真值。工程上最稳的对齐逻辑是:按刀号和时间戳,把换刀记录映射回传感器时间轴,每个时刻的振动特征对应一个滚刀状态标签。
import pandas as pd import numpy as np # 换刀记录示例:每把刀的安装与拆卸时间、状态标签 tool_records = pd.DataFrame({ 'tool_id': [1, 1, 2, 2], 'install_time': ['2024-03-01 08:00:00', '2024-04-01 08:00:00', '2024-03-05 08:00:00', '2024-04-10 08:00:00'], 'remove_time': ['2024-03-28 08:00:00', '2024-04-20 08:00:00', '2024-04-01 08:00:00', '2024-04-25 08:00:00'], 'state_label': ['normal', 'abnormal_wear', 'partial_wear', 'normal'] }) # 振动信号长表:time, tool_id, vib_x, vib_y, vib_z, thrust, torque sensor_df = pd.read_csv('vibration_log.csv', parse_dates=['time']) # 把传感器每一秒的读数,按刀号与时间区间打上状态标签 sensor_df['state_label'] = None for _, rec in tool_records.iterrows(): mask = ((sensor_df['time'] >= rec['install_time']) & (sensor_df['time'] < rec['remove_time']) & (sensor_df['tool_id'] == rec['tool_id'])) sensor_df.loc[mask, 'state_label'] = rec['state_label'] sensor_df = sensor_df.dropna(subset=['state_label'])install_time和remove_time必须精确到分钟级,因为滚刀磨损是渐进过程,但换刀瞬间信号会突变。标签对齐时最怕两种错误:一是换刀记录本身漏填,二是把同一把刀在多个刀位间的换装时间混淆。所以我在实际项目中会让施工方额外提供一个「拆刀日期 + 装机日期 + 刀位号」的表格,按三个字段联合对齐,而不是只靠一个时间戳。
2.2 滑动窗口切分与特征工程:把时间序列变成模型能吃的样本
滚刀振动信号是非平稳时间序列,不能把一整段几小时的数据直接喂给模型。常规做法是用滑动窗口切成长度固定的样本,窗口长度取振动信号单圈旋转的整数倍,比如 256 点、512 点或 1024 点。窗口重叠率常见 50%,用来做数据增强,但重叠样本在训练集和验证集之间会造成数据泄漏,后面避坑章节会专门讲。
def sliding_window(data, window_size=512, step_size=256): """滑窗切分,默认 50% 重叠。返回样本数组和对应时间戳。""" samples = [] timestamps = [] for start in range(0, len(data) - window_size + 1, step_size): end = start + window_size samples.append(data[start:end]) timestamps.append(data.index[end]) return np.stack(samples), np.array(timestamps) # 假设 sensor_df 已按时间排序,且只保留数值列 feature_cols = ['vib_x', 'vib_y', 'vib_z', 'thrust', 'torque'] values = sensor_df[feature_cols].values win_size = 512 step = 256 win_data, _ = sliding_window(values, window_size=win_size, step_size=step)window_size=512对应大约 0.5 秒的振动数据(1kHz 采样),足以覆盖滚刀转一圈产生的冲击周期。step_size越大样本数越少,训练越快,但会漏掉状态切换的边沿;我一般先保持 50% 重叠,模型效果稳定后再加大步长减少冗余样本。
特征工程方面,常见的做法是先算时域统计特征再用传统模型,包括均方根值、峭度、峰值因子、方差、峰峰值、频域质心。峭度对崩刃和偏磨这类冲击性状态很敏感,正常磨损振动幅值平稳,峭度接近 3;出现崩刃时脉冲冲击增多,峭度会显著上升。这个特征在滚刀状态的早期筛选里几乎是必看的。
from scipy.stats import kurtosis from scipy.fft import rfft, rfftfreq def extract_features_from_windows(win_data, sample_rate=1000): """每个滑窗样本提取一组统计特征。""" features = [] for w in win_data: rms = np.sqrt(np.mean(w ** 2, axis=0)) kurt = kurtosis(w, axis=0) peak = np.max(np.abs(w), axis=0) peak_factor = peak / (rms + 1e-8) # 频域质心:功率谱重心 spectrum = np.abs(rfft(w, axis=0)) ** 2 freqs = rfftfreq(w.shape[0], d=1 / sample_rate) centroid = np.sum(freqs[:, None] * spectrum, axis=0) / (np.sum(spectrum, axis=0) + 1e-8) features.append(np.concatenate([rms, kurt, peak_factor, centroid])) return np.array(features)extract_features_from_windows把每个窗口的多通道时域信号压缩成一组低维统计特征,这组特征可以直接喂给 SVM 和随机森林。rms反映振动能量,kurt反映冲击性,peak_factor反映瞬时冲击强度,centroid反映信号频谱重心位置。参数里最容易忽略的是1e-8的平滑项,窗口内如果有一段停机静默,RMS 接近 0,峰值因子会爆炸成无穷大,不加平滑项传统模型会直接翻车。
2.3 数据平衡与标签噪声处理:滚刀样本永远是不均衡的
滚刀状态数据天然不均衡,正常运行样本可能占七成以上,崩刃和卡阻样本稀少,偏偏这些稀少状态才是安全上最要命的。直接拿原始样本训练,模型会学成「什么都不报也能有 70% 正确率」。处理办法有两个方向:数据层面做欠采样或过采样,算法层面给模型加类别权重。
from sklearn.model_selection import train_test_split from sklearn.utils.class_weight import compute_class_weight labels = np.array(sensor_df['state_label'].values) # 按滑窗后的样本对齐标签,这里假设 win_labels 已生成 win_labels = labels[:win_data.shape[0]] # 计算每个类别的权重,少数类自动获得更高权重 class_weight = compute_class_weight(class_weight='balanced', classes=np.unique(win_labels), y=win_labels) weight_dict = dict(zip(np.unique(win_labels), class_weight)) print('类别权重:', weight_dict) # 分层切分,保证每个状态在训练和验证集中都有 X_train, X_val, y_train, y_val = train_test_split( win_data, win_labels, test_size=0.2, stratify=win_labels, random_state=42)compute_class_weight用balanced模式自动对少数类加权,崩刃样本如果只占 3%,权重可能到 10 倍以上。切分用stratify保持各类别比例一致,避免验证集里偏偏缺了「崩刃」这个关键类别。对于 SVM 和随机森林,直接把weight_dict传给模型;对于 CNN 和 LSTM,在损失函数里加一个类别权重张量即可。
标签噪声主要来自换刀记录的人工填写。施工人员可能在繁忙时段把「偏磨」随手写成「正常磨损」,我遇到过一批数据里同一个刀位连续三次记录都是「正常」,但振动信号的峭度曲线明显有异常抬升。处理方式是在标签对齐后,用无监督聚类做一遍离群检查,把特征分布明显异常但标签为正常的样本挑出来逐条复核,而不是盲信人工记录。
3. 用 SVM 和随机森林打基线:传统模型在小样本上的价值
滚刀状态识别的落地场景里,样本量常常只有几百到几千个窗口,这时候深度模型容易过拟合,传统模型反而更稳。SVM 和随机森林在这个任务上的定位是:先跑通、拿到可解释的基线,再决定要不要上深度学习。
3.1 为什么先跑传统模型:小样本下深度模型的翻车概率更高
一个常见误区是拿到源码直接跑 CNN,结果训练集 99%,验证集 60%,还找不到原因。滚刀状态数据集往往只有几十把刀的生命周期数据,滑窗后虽有几千个样本,但同一把刀不同时段的样本高度相关,有效独立样本数远小于表面数量。SVM 的几何间隔机制在样本量小的时候泛化能力更好,随机森林的袋外估计能直接反映泛化误差,这两个模型都能在几分钟内给出一个诚实的基线结果。
如果传统模型已经能到 90% 以上的 F1,深度模型的价值就要重新评估。我见过不少项目,CNN 把准确率从 93% 提到 95%,但训练和部署成本翻了好几倍,施工方根本不愿意换。反过来,如果基线只有 75%,说明特征工程或标签对齐本身有问题,这时先解决问题再上深度模型才有意义。
3.2 SVM 的核函数选择与参数:RBF 核是滚刀数据的默认起点
SVM 在滚刀状态识别里用 RBF 核是默认选择,因为振动特征在高维空间里往往有复杂的非线性边界,线性核表现一般。两个关键参数:C(误分类惩罚)和gamma(RBF 核宽度)。C越大越容易过拟合,gamma越大决策边界越复杂。
from sklearn.svm import SVC from sklearn.preprocessing import StandardScaler from sklearn.pipeline import make_pipeline from sklearn.model_selection import GridSearchCV # 标准化很重要:SVM 对特征尺度极其敏感 svm_pipe = make_pipeline( StandardScaler(), SVC(kernel='rbf', class_weight='balanced', probability=True) ) param_grid = { 'svc__C': [0.1, 1, 10, 100], 'svc__gamma': [0.001, 0.01, 0.1, 1] } grid = GridSearchCV(svm_pipe, param_grid, cv=5, scoring='f1_macro', n_jobs=-1) grid.fit(X_train_features, y_train) print('最优参数:', grid.best_params_)StandardScaler必不可少,振动特征里 RMS 的量级可能是峭度的几十倍,不标准化会让 SVM 的训练过程被大数值特征主导。probability=True是为了后面能取预测概率做决策阈值调整,虽然会多点计算开销,但在滚刀状态识别里值得。f1_macro作为打分指标比默认准确率靠谱,因为类别不均衡会让准确率虚高。C和gamma的搜索范围按经验值给,数据量小的时候从网格中心开始即可,不必追求过细。
3.3 随机森林的树数量、深度与特征重要性解释
随机森林在这个任务上的优势是:不用做特征标准化、对噪声鲁棒、能输出特征重要性,方便跟施工方解释「模型在靠哪些信号做判断」。这在工程验收时特别好用,现场工程师愿意相信「振动峭度」这种物理含义明确的特征。
from sklearn.ensemble import RandomForestClassifier rf = RandomForestClassifier( n_estimators=200, max_depth=12, min_samples_leaf=4, class_weight='balanced', random_state=42, n_jobs=-1 ) rf.fit(X_train_features, y_train) val_acc = rf.score(X_val_features, y_val) print(f'验证集准确率: {val_acc:.4f}') # 特征重要性排序,辅助向业务方解释模型 feat_names = feature_cols + ['rms', 'kurtosis', 'peak_factor', 'centroid'] importance = sorted(zip(feat_names, rf.feature_importances_), key=lambda x: x[1], reverse=True) for name, imp in importance[:6]: print(f'{name}: {imp:.4f}')n_estimators=200足够稳定,再往上对精度的提升很有限,只会拖慢训练。max_depth=12配合min_samples_leaf=4是控制过拟合的组合,让每棵树不要长得太深,叶子节点也别太碎。class_weight='balanced'和 SVM 里同理。特征重要性输出后你会发现,峭度和频域质心通常排在前列,推进压力又比振动信号更能看出磨损后期的推力补偿——因为刀圈磨短后,维持同样贯入度需要更大推力。
4. 用 CNN、LSTM、GRU 做时序识别:深度模型的实现与参数取舍
传统模型跑通之后,如果样本量足够(通常滑窗后达到上万个样本),就可以上深度模型。CNN、LSTM、GRU 在滚刀状态识别上各有分工,选择标准不是「哪个模型更高级」,而是「信号里的判别特征在时间轴上怎么分布」。
4.1 CNN 提取局部冲击特征、LSTM 捕捉长程依赖:两种视角的分工
滚刀旋转过程中,每把刀与掌子面接触会产生周期性冲击,冲击波形在时间轴上只占几个毫秒。这种局部模式恰好是卷积神经网络的强项:一维卷积核扫过振动波形,在局部窗口内识别冲击形状、间隔和幅值模式。崩刃和偏磨的差异,往往就体现在冲击脉冲的形态上。
而磨损是一个缓慢的渐变过程,当前时刻的振动特征取决于过去数十分钟到数小时的演化趋势。LSTM 和 GRU 存在的意义是捕捉这种长程依赖:某个状态特征在时间轴上反复出现、逐渐增强,形成序列模式。所以一个合理的搭配是:CNN 在前层提取局部冲击特征,GRU 在后层建模状态随时间的变化趋势。纯 CNN 对渐变类状态容易误判,纯 LSTM 又容易在局部冲击细节上丢信息。
4.2 用 PyTorch 搭一个一维 CNN 分类器:结构与关键参数
一维 CNN 在滚刀数据上的输入形状和图像不一样,输入是[batch, channels, seq_len],channels对应振动和压力通道数,seq_len是窗口长度。下面的结构参考了常见的一维残差卷积方案,但做了简化适配。
import torch import torch.nn as nn class CnnToolState(nn.Module): """一维 CNN 滚刀状态分类器。输入: [B, C, L] 输出: [B, n_class]""" def __init__(self, n_channels=5, n_class=5, n_filters=64): super().__init__() self.features = nn.Sequential( nn.Conv1d(n_channels, n_filters, kernel_size=7, padding=3), nn.BatchNorm1d(n_filters), nn.ReLU(inplace=True), nn.MaxPool1d(kernel_size=2), nn.Conv1d(n_filters, n_filters * 2, kernel_size=5, padding=2), nn.BatchNorm1d(n_filters * 2), nn.ReLU(inplace=True), nn.MaxPool1d(kernel_size=2), nn.AdaptiveAvgPool1d(1) ) self.classifier = nn.Sequential( nn.Dropout(0.5), nn.Linear(n_filters * 2, n_class) ) def forward(self, x): features = self.features(x) return self.classifier(features.flatten(1)) model = CnnToolState(n_channels=5, n_class=5) print(model)kernel_size=7在 1kHz 采样率下对应 7 毫秒的感知范围,刚好能覆盖一次滚刀冲击脉冲的主峰。padding=3保证卷积后长度不变,避免边界信息丢失。AdaptiveAvgPool1d(1)把任意长度的序列池化到 1 维,这样窗口长度变化时不需要改全连接层。Dropout(0.5)放在全连接前,滚刀数据量小,全连接层是最容易过拟合的位置。
训练时把原始多通道窗口直接作为输入,不需要手动提取统计特征,CNN 会在卷积层里自己学到冲击模式。但要注意,输入数据要按通道做归一化,不能直接喂原始电压值。
4.3 GRU 与 LSTM 的取舍:参数更少、收敛更快,但别指望永远更好
LSTM 和 GRU 的结构差异在工程上直接体现为:GRU 少了输出门,参数量大约少三分之一。在滚刀这种中等规模的数据集上,GRU 收敛更快,过拟合风险更低,是性价比更高的选择。但 LSTM 在序列特别长、依赖关系跨很长距离时上限更高,所以两者都值得跑一遍,以实验结果为准。
class GruToolState(nn.Module): """GRU 序列分类器。输入: [B, L, C] 输出: [B, n_class]""" def __init__(self, input_size=5, hidden_size=64, n_layers=2, n_class=5, dropout=0.3): super().__init__() self.gru = nn.GRU(input_size, hidden_size, n_layers, batch_first=True, dropout=dropout, bidirectional=True) self.classifier = nn.Sequential( nn.Dropout(0.5), nn.Linear(hidden_size * 2, n_class) ) def forward(self, x): out, _ = self.gru(x) # 取最后一个时间步的输出 last_out = out[:, -1, :] return self.classifier(last_out) model = GruToolState(input_size=5, hidden_size=64, n_layers=2, n_class=5)batch_first=True让输入维度是[B, L, C]而不是[L, B, C],减少写代码时维度搞反的概率。bidirectional=True会让 GRU 同时看到过去和未来,但代价是延迟预测时不能用流式推理,需要等整个窗口采集完才能给出结果。hidden_size=64对滚刀数据的窗口长度(几百到上千点)来说够用,加大到 128 通常不会提升精度,只会增加计算量。n_layers=2是常见的折中,单层欠拟合,三层以上在小数据集上几乎必然过拟合。
训练时 LSTM/GRU 比 CNN 更敏感,学习率和梯度裁剪必须配好。常见配置是 AdamW 优化器配学习率1e-3,加 CosineAnnealing 学习率衰减,同时用clip_grad_norm_把梯度范数限制在 1.0 以内,防止长序列的梯度爆炸把模型权重冲乱。
optimizer = torch.optim.AdamW(model.parameters(), lr=1e-3, weight_decay=1e-4) scheduler = torch.optim.lr_scheduler.CosineAnnealingLR(optimizer, T_max=30) clip_value = 1.0 for epoch in range(30): for batch_x, batch_y in train_loader: optimizer.zero_grad() logits = model(batch_x) loss = criterion(logits, batch_y) loss.backward() torch.nn.utils.clip_grad_norm_(model.parameters(), clip_value) optimizer.step() scheduler.step()weight_decay=1e-4做 L2 正则化,配合clip_grad_norm_(..., 1.0)是 LSTM/GRU 训练不顺时的第一道解药。T_max=30对应训练轮数,学习率会在 30 轮内从1e-3余弦降到接近 0。如果损失曲线在前几轮震荡不降,优先检查学习率和输入归一化,而不是换成更复杂的模型结构。
5. 模型对比与避坑指南:五种模型在滚刀数据上的真实差距
模型对比不是把五个模型各自跑一遍取最高分,而是要在同一套数据划分、同一套预处理、同一套评价指标下做公平比较。这章先讲对比实验怎么设计,再列出滚刀状态识别里最常见的几个坑,都是实际项目中踩过的。
5.1 对比实验怎么设计才公平:划分方式、评价指标与混淆矩阵
公平对比的前提是大家用完全相同的训练集和验证集。先固定数据切分,再把数据集保存下来,五个模型共用同一份train_set.npz和val_set.npz,而不是每次跑模型时重新随机切分。最稳妥的做法是先写好一个数据加载函数,把窗口切分、标签对齐、标准化参数全部固定,然后作为公共模块被五个模型调用。
评价指标不能只看准确率。滚刀状态识别里,崩刃漏报的代价远高于误报,所以重点看每个类别的召回率,尤其是崩刃和卡阻。用一个加权 F1 做总指标,配合混淆矩阵看具体哪里出错。混淆矩阵里如果偏磨样本大量被预测成正常,说明模型对该状态的特征不敏感,这时优先调整类别权重或补充样本,而不是换模型。
| 状态类别 | 样本占比 | 误判后果 | 优先关注的指标 |
|---|---|---|---|
| 正常 | 65%~75% | 无直接后果 | 特异性 |
| 正常磨损 | 10%~20% | 换刀时机偏晚 | 召回率 |
| 偏磨 | 5%~10% | 刀圈偏磨报废 | 召回率 |
| 崩刃 | 2%~5% | 刀圈碎裂、伤及刀盘 | 召回率 |
| 卡阻 | 1%~3% | 整机停机 | 召回率 + 精确率 |
这张表是滚刀状态识别项目的核心。越往下的状态样本越少、后果越重,评价指标就越要向召回率倾斜。模型对比时只看accuracy或者只看宏平均 F1 都会掩盖这个关键问题,要把混淆矩阵打印出来逐类核对。
5.2 数据泄漏的三个常见来源:随机切分、重叠窗口、提前归一化
数据泄漏是状态识别项目里精度虚高的头号原因。表现是训练集和验证集都 98%,部署到现场直接掉到 70% 以下。第一个来源是随机切分:同一个时间段的相邻窗口被分到了训练集和验证集两侧,模型等于见过答案。修复方式是按时间顺序切分,前 80% 训练、后 20% 验证。
第二个来源是重叠窗口。用 50% 重叠切分后,某个窗口的一部分数据在它的相邻窗口里也出现,如果相邻窗口被分到另一侧,信息就泄漏了。第三个来源是归一化参数:标准化时用全体数据的均值和方差,等于验证集的信息提前参与了训练。正确做法是只对训练集计算均值方差,再用同一套参数归一化验证集。
scaler = StandardScaler() X_train_scaled = scaler.fit_transform(X_train.reshape( X_train.shape[0], -1)).reshape(X_train.shape) # 验证集用训练集的统计量,不能重新 fit X_val_scaled = scaler.transform(X_val.reshape( X_val.shape[0], -1)).reshape(X_val.shape)5.3 实测踩坑记录:滚刀状态识别最常见的五个问题
现象一:训练集准确率 99%,验证集只有 60%。原因和解决:随机切分造成了时间序列泄漏。解决方法是改成按时间顺序划分,保证验证集只包含训练集时间之后的数据。我在项目里吃过这个亏,后来干脆把切分方式写成固定规则,任何模型都只能用它。
现象二:偏磨状态召回率只有 30%,模型把所有样本都判成正常。原因和解决:类别不平衡,正常样本占到七成,模型学会了偷懒。解决方法是给少数类加高权重,或者对少数类做 SMOTE 过采样。注意 SMOTE 只适用于特征向量,不能直接用于原始时间序列。
现象三:LSTM 训练过程损失震荡,跑 50 轮还不收敛。原因和解决:学习率太高,长序列的梯度爆炸。解决方法是把学习率降到3e-4,加上梯度裁剪clip_value=1.0,同时检查输入序列是否做了归一化。这个坑几乎每个用 LSTM 的人都踩过,GRU 虽然稍好一些,也一样需要这些配置。
现象四:SVM 训练等了几个小时还没结束。原因和解决:直接把原始波形拉平喂给 SVM,特征维度上千,SVM 核矩阵计算量爆炸。解决方法是先用统计特征降到几十维,再跑 SVM。这就是第二章为什么要做特征工程,传统模型和深度模型在数据准备上完全不是一回事。
现象五:离线验证 93%,上现场掉到 75%。原因和解决:训练集和现场数据的分布不一致,换了一批传感器、换了个安装位置、推进速度不同,振动幅值和频段都变了。解决的常见做法是重新采集一段现场数据做微调,或者做简单的域自适应,把现场数据的均值方差对齐到训练集分布。最彻底的办法是建立「一项目一模型」的迭代流程,每个新的施工段用少量新数据校准一次。
6. 从离线实验到现场可用:模型部署与持续迭代技巧
模型在 Jupyter Notebook 里跑通只是第一步,现场能用才是终点。部署时不要把单个窗口的预测结果直接当作换刀依据,那样误报率会高到施工方不敢用。我习惯的做法是滑动窗口推流 + 多数投票:模型每处理一个窗口输出一个状态,但最终判定取最近 N 个窗口中投票数最多的结果。N 一般取 5 到 10,既避免单次误判,又能在一个窗口对应的半秒到一秒内及时反应。
模型导出用 ONNX Runtime 比较省心,PyTorch 模型可以直接转 ONNX,推理时不依赖训练框架,现场工控机上装个轻量运行时就能跑。量化方面,int8 量化后模型体积能缩小到原来的四分之一,在振动信号这种容错率还可以的场景里精度损失通常小于两个百分点。
再训练的节奏很关键。滚刀状态识别不能训一次用到项目结束,每把刀的生命周期、每段地层的岩性变化都会让数据分布漂移。常见做法是每周把新增的换刀记录和对应信号增量标注一次,用新数据对模型做二次微调,而不是重新全量训练。这样模型能跟上掌子面岩层的变化,换刀判断的准确率才能维持住。
我在最新一个项目上的习惯是:模型版本号和换刀记录一一对应,每次现场反馈误判,就把那条样本单独存下来。积累到一定量后统一复查,看是标签错了还是特征被新工况覆盖了。先查数据、再调模型,这个顺序能省掉大量无效调参时间。希望帮到你。
本文还有配套的精品资源,点击获取