☰
轴承寿命预测实战:从时域变换到CNN的完整流程解析
2026/10/10 9:26:50 网站建设 项目流程

简介:这是一份面向工业物联网与设备健康管理场景的轴承寿命预测代码资源包,适合从事设备状态监测、预测性维护或相关课题研究的技术人员。资源共3个文件,均为m脚本,压缩包大小仅1KB,代码体量精炼,却覆盖了从数据读取、预处理到时域特征提取与寿命预测的完整分析链路。内容围绕轴承寿命预测的关键方法展开,重点展示均方根、峭度、幅值等时域变换特征的计算与判读,以及通过振动信号识别早期故障的思路。研读这些脚本,可掌握时域特征工程与轴承剩余寿命建模的实用流程;同时,脚本结构清晰,可作课程设计或课题研究的基础框架,方便针对实际数据调整特征与预测策略。这套资源已有1138人浏览学习,对入门设备健康管理并希望动手实践预测算法的开发者具有参考价值。

1. 时域变换做轴承寿命预测:先想清楚“预测什么”,再谈“怎么预测”

做轴承寿命预测的人,大多是从“丢一段振动信号进网络,输出一个剩余寿命数字”这个画面开始的。可真到落地你会发现,最难的从来不是模型,而是把“轴承寿命”这件事定义得可计算、可验证。同样是轴承全寿命信号,有人把一个周期当成一条样本做回归,有人从原始波形里手动抠一堆特征再进模型,还有人直接让卷积网络自己学时域变换——三条路我都走过,表现差别极大。这篇笔记就按“数据与标签 → 时域变换 → 模型训练 → 踩坑排查 → 维修决策”的顺序,把一套在普通工作站上能跑通的轴承寿命预测流程完整拆开讲。适合正在做设备健康管理、预测性维护的工程师,也适合刚接触剩余寿命预测、想找一条可靠路径的研究者。

2. 寿命预测的数据与标签:没有可靠的 RUL,再好的时域变换也白搭

2.1 数据从哪来:先看采样率、转速与每转采样点数

做轴承寿命预测,第一步不是建模,而是确认手头的数据到底是什么形态。我一般会先拿到某公开的轴承加速寿命数据集,这类数据通常包含垂直和水平两个方向的加速度振动信号,以及对应的转速和采样率配置。拿到之后,第一件事是打印基本信息,确认每个文件里是一整条连续波形,还是已经切好的短样本。以下是我常用的检查代码:

import numpy as np data = np.load("bearing_channel.npy") # 形状: (通道数, 采样点数) fs = 25600 # 采样率,单位 Hz rpm = 2000 # 台架转速,单位 r/min rot_freq = rpm / 60.0 # 转频,单位 Hz samples_per_rot = int(fs / rot_freq) # 每转对应的采样点数 print("数据形状:", data.shape) print("采样率:", fs, "Hz") print("转频:", round(rot_freq, 2), "Hz") print("每转采样点数:", samples_per_rot)

这段代码里最关键的是samples_per_rot,它决定后面所有的时域变换参数。比如 25.6 kHz 采样、2000 r/min 时,每转大约 768 个点;如果转速变成 3000 r/min,每转就只有 512 个点。窗口长度、时域同步平均的分段长度、滑动步长都要跟着这个数字走,而不是拍脑袋定一个“1024 或 2048”就完事。数据形状、采样率和转速这三项确认清楚,后面所有处理才有依据。我见过不少人直接拿原始波形去训练,完全没意识到数据里混着多个转速段,结果模型学到的其实是“转速规律”而不是“退化规律”。

2.2 RUL 标签怎么打:分段线性退化替代全寿命直线

有了波形数据,下一步是把每一段波形对应到一个剩余寿命(RUL)标签。最简单的做法是:最后一个采样点对应 RUL=0,往前按时间线性递增。这个做法的问题在于,轴承全寿命的前 60% 几乎看不到明显的退化迹象,把这一大段“健康期”全部标成不同的线性值,等于让模型去学习“1000 秒的波形和 900 秒的波形有什么区别”,而实际上它们在信号形态上毫无差别,模型只能强行记忆噪声。我一般改用分段线性标签:前一段压缩成一个常数或缓慢下降值,后一段才进入真正的线性退化段。

def piecewise_rul(t, t_total, knee_ratio=0.6, health_value=None): t = np.asarray(t, dtype=float) knee = t_total * knee_ratio rul = t_total - t if health_value is None: health_value = t_total * knee_ratio rul[t < knee] = health_value return rul

这里的knee_ratio需要根据实际数据的退化起点来调。我的做法是:先画出 RMS 趋势曲线,找到 RMS 开始持续上升的拐点,把拐点对应的时间占比设为knee_ratio。如果你观察到的退化起点在中后段,比如全寿命的 70% 处,那就把它设成 0.7。knee_ratio设得太小会把正常样本划入退化段,设得太大则健康样本过多,模型会偏向输出保守的“健康值”。这个参数值得花半小时反复看曲线调,它直接影响模型的训练信号质量。

2.3 滑动窗口切样本:窗口长度跟着转频走

时域模型要求输入是一段一段的波形,不是整条长序列。滑动窗口切样本时,窗口长度不能随便定。我的经验是:窗口至少要覆盖 2 到 3 个完整旋转周期。以 25.6 kHz 采样、2000 r/min 为例,每转 768 点,取 2048 点大约 2.7 转,既能包含多个冲击周期,又不会因为窗口过长把瞬态冲击平滑掉。重叠率我通常取 50%,这样相邻窗口之间既有连续性,又不会让训练样本几乎重复。

def sliding_window(seq, window_len=2048, overlap=0.5): step = int(window_len * (1 - overlap)) windows = [] for start in range(0, len(seq) - window_len + 1, step): windows.append(seq[start:start + window_len]) return np.stack(windows)

窗口长度设好之后,再把每个窗口和它对应当前时刻的 RUL 标签配成一对训练样本。注意,窗口的起点决定了标签的值,也就是说一个窗口内所有点共享同一个“窗口起始时刻的剩余寿命”。这样做会带来轻微延迟,但对长序列退化趋势来说可以接受。如果后续发现预测曲线整体滞后,可以改成用窗口终点时刻或中点时刻对应的标签,这属于回归任务里常见的对齐问题,后面避坑章节再展开。

3. 时域变换的核心:从原始波形到能反映退化的特征表示

3.1 时域同步平均:按转频对齐,周期性冲击从噪声里浮出来

原始振动波形里同时混着周期性冲击、旋转部件的谐波成分和环境噪声。时域同步平均(TSA)是先把长信号按转频切成一段一段,每一段正好覆盖一转,再逐点叠加求平均。周期性成分会被保留,随机噪声因为相位不同,幅值会按平均次数的平方根衰减。这个手段能让外圈、内圈故障引起的冲击在时域里变得更清晰,后续特征和模型都能从中受益。

def time_sync_average(signal, samples_per_rot, n_avg=32): usable = n_avg * samples_per_rot trimmed = signal[:usable] reshaped = trimmed.reshape(n_avg, samples_per_rot) tsa = reshaped.mean(axis=0) return tsa

这里n_avg不是越大越好。平均次数从 8 增加到 32,随机噪声降到原来的约 1/5.7,效果提升明显;但如果继续增大到 256 次,早期点蚀产生的微弱脉冲也会被当作非周期性成分压掉,反而丢了退化信息。我一般限制在 16 到 64 次之间。另一个前提是转速必须稳定,如果转速波动大,每转的实际采样点数不固定,直接 reshape 会错位,这时就得先做角域重采样,把信号从时间域映射到角度域再平均。TSA 做完后,我通常会把原始波形和 TSA 波形叠在一起画图,快速确认冲击是否真的清晰了。

3.2 时域统计特征组合:RMS、峭度、峰值因子各自管哪一段退化

做完 TSA 之后,还要把波形压缩成能反映退化状态的数字。单一特征不够用:RMS 反映信号整体能量,但早期退化能量变化很小;峭度对偏离高斯分布的冲击很敏感,早期点蚀阶段就会抬头;峰值因子反映“尖峰程度”,中期裂纹扩展时明显上升;波形因子则相对稳定,适合用来做参考基准。把这几个特征组合成一个向量,相当于对原始波形做了一次低维时域变换,把退化过程的不同阶段分别映射到对应的特征轴上。

def time_features(x): rms = np.sqrt(np.mean(x ** 2)) peak = np.max(np.abs(x)) kurtosis = np.mean((x - np.mean(x)) ** 4) / (np.std(x) ** 4 + 1e-12) crest = peak / (rms + 1e-12) shape_factor = rms / (np.mean(np.abs(x)) + 1e-12) return np.array([rms, kurtosis, crest, shape_factor]) def extract_feature_matrix(signal_all, window_len, overlap): feats = [] for w in sliding_window(signal_all, window_len, overlap): feats.append(time_features(w)) return np.stack(feats)

下面是我常用的特征和退化阶段对应关系,这组对应关系不是绝对的,但能帮你判断特征是否选对了方向:

特征含义主要响应阶段
RMS信号有效值,反映振动能量中晚期,裂纹扩展后显著上升
峭度四阶中心矩,反映冲击成分占比早期,点蚀和微裂纹阶段
峰值因子峰值与 RMS 之比中期,冲击幅值增大但不连续
波形因子RMS 与绝对均值之比全周期相对平稳,作参考

如果某个特征在整条寿命曲线上几乎无变化,要么是这个特征对该退化模式不敏感,要么是信号里根本没有对应的故障特征。这时候不要急着换模型,先回头确认数据里是不是真的包含完整的退化过程。

3.3 特征趋势平滑:滑动中值比滑动平均更能保住退化拐点

提取出来的特征曲线每个点都抖得很厉害,直接喂给模型会让模型去拟合噪声。常见的做法是平滑,但滑动平均会把拐点拉平。举个例子,RMS 在晚期会有一个快速爬升段,滑动平均窗口稍微大一点,这个爬升起点就被前移了,模型学到的退化起点就有偏差。我一般改用滑动中值,窗口长度取为samples_per_rot的整数倍,在平滑噪声的同时保留突变拐点。

def smooth_median(x, window_len=768): from scipy.ndimage import median_filter return median_filter(x, size=window_len, mode='nearest')

window_len取 768 是以 25.6 kHz、2000 r/min 为例,正好对应一个旋转周期。如果转速不同,按samples_per_rot实时计算。这里要注意,中值滤波适合去掉孤立尖峰,但如果退化信号本身就是连续上升的,中值滤波对趋势的保真度比均值好得多。做完平滑之后,把特征曲线和时间轴画出来,确认拐点位置符合物理直觉,再做归一化进入模型,这一步能省掉后面很多莫名其妙的调试。

4. 用一维卷积把时域波形变成剩余寿命:最小可跑模型与参数细节

4.1 为什么直接输入原始时域波形,而不是手工特征

手工特征相当于人工定义了一次时域变换,优点是计算快、可解释,缺点是你只能看到预设模式。复合故障、变工况、早期微弱退化这些情况,手工特征往往覆盖不全。一维卷积网络则直接在原始时域波形上做特征提取,它自己学到的滤波器某种程度上就是一种可学习的时域变换。我一般先手工特征跑通基线,拿到一个“能用的精度”,再切换到波形输入提上限。建议你也不要一开始就上模型,先用手工特征把数据、标签、评估流程验证一遍,模型只是把时域变换从手工换成自适应。

4.2 一个能跑通的最小 CNN 结构:Conv1d + 全局池化 + 回归头

下面这个结构是我在多个寿命预测数据上验证过的最小可用版本,输入是 2048 点单通道波形,输出是一个剩余寿命标量。卷积核宽度取 32,对应 25.6 kHz 采样率下约 1.25 ms 的时间范围,可以覆盖一个冲击脉冲的上升沿;stride 设置为 4,相当于第一层就做了 4 倍降采样,后面再接卷积时感受野足够大又不至于参数量过大。

import torch import torch.nn as nn class RULNet(nn.Module): def __init__(self, in_len=2048): super().__init__() self.features = nn.Sequential( nn.Conv1d(1, 16, kernel_size=32, stride=4, padding=16), nn.BatchNorm1d(16), nn.ReLU(), nn.Conv1d(16, 32, kernel_size=16, stride=4, padding=8), nn.BatchNorm1d(32), nn.ReLU(), ) self.pool = nn.AdaptiveAvgPool1d(1) self.head = nn.Linear(32, 1) def forward(self, x): x = self.features(x) x = self.pool(x).squeeze(-1) return self.head(x).squeeze(-1)

回归头不加激活函数,输出直接是剩余寿命的数值。卷积层后面跟 BatchNorm 在回归任务里也能帮助稳定训练,尤其当输入波形的幅值量级在不同工况间有差异时。损失函数我不用 MSE,而用 Huber loss(delta=1.0),它对晚期那些偏离很大的样本惩罚比 MSE 温和,不容易被少数极端点带偏。代码里输入 shape 是 (B, 1, 2048),如果你的采样率或窗口长度改了,只需保证输入长度对得上即可,卷积层的 padding 已经把边界兜住了。

4.3 训练策略:按时间顺序划分训练验证,不随机打乱

寿命预测的数据是时间序列,切样本时如果随机打乱,训练集和验证集会混入相邻时刻的样本,验证集 loss 会极其好看,但一到真实部署就崩。正确做法是:整条寿命数据按时间顺序切段,前 70% 做训练、中间 15% 做验证、最后 15% 做测试。训练集和验证集之间留一段空隙,防止滑窗重叠导致信息泄漏。

def train_one_epoch(model, loader, opt, loss_fn): model.train() total_loss = 0 for xb, yb in loader: opt.zero_grad() pred = model(xb) loss = loss_fn(pred, yb) loss.backward() opt.step() total_loss += loss.item() * len(xb) return total_loss / len(loader.dataset)

训练参数方面,我的常用配置是:Adam 优化器、学习率 1e-3、batch size 32、训练 50 个 epoch,验证 loss 连续 5 个 epoch 不下降就提前停止。归一化要特别注意:只在训练段上计算均值和标准差,再用这组统计量去标准化验证段和测试段。如果图省事在整条序列上做标准化,等于把未来的均值、方差信息泄漏给了训练过程,模型在训练时就已经“偷看”了未来的数据分布。

训练结束后,把测试段的预测 RUL 曲线和真实 RUL 画在同一张图上。我判断模型好坏的第一眼不是看 loss,而是看曲线有没有跟随真实退化趋势,拐点是不是对齐。曲线趋势对了,数值有偏差是可以调的;曲线趋势都不对,那就要回查标签定义或者时域变换的参数了。

5. 轴承寿命预测落地避坑:现象、原因与排查路径

5.1 训练集 loss 很低,测试集一塌糊涂

现象:训练集和验证集上 loss 都收敛得很好,但拿到最后一段测试数据上,预测曲线明显滞后或直接输出一个常数。

原因:切样本后随机打乱了训练集和验证集,导致验证集里混入了训练集中相邻时刻的样本。滑窗有 50% 重叠,相邻窗口内容高度相似,模型相当于在“考试”时见过“答案”旁边的内容,真正没见过的时间段一进来就露馅。

解决:严格按时间顺序划分数据集,训练、验证、测试三段之间留出间隔,间隔长度至少大于一个滑窗步长。评估时只看最后一段真实未参与训练的测试数据,不要用验证集结果代表泛化性能。

5.2 预测值长时间停在初始值附近,最后跳水

现象:模型输出的 RUL 在很长一段寿命内都停留在同一个数值附近,直到接近末端才突然下降,曲线形似“跳崖”。

原因:分段线性标签的健康段饱和值设得太大。如果前 60% 全部标成一个常数,模型在这个区间内拿不到任何梯度信号,只能输出一个无意义的基准值。后期进入线性段时,训练样本占比少,模型来不及拟合就结束了。

解决:把饱和区压缩到前 40% 到 50%,在饱和区到线性区之间加一段 10% 左右的过渡段,让标签从健康值缓慢下降进入退化段,这样模型在每个阶段都能收到梯度。另一个补救措施是样本重采样,让退化段的样本在训练集中占更高比例,避免健康样本把模型带偏。

5.3 TSA 之后冲击反而看不见了

现象:做完时域同步平均,RMS 确实变得稳定了,但峭度明显下降,早期微弱点蚀特征反而消失了。

原因:TSA 的平均次数太多。轴承早期点蚀产生的脉冲幅值很小,相位也不完全精确,当成千上万次平均后,这些脉冲被当作非周期成分压掉了。TSA 只能保留严格周期且相位稳定的分量,微弱点蚀恰恰不满足这个条件。

解决:把 TSA 平均次数限制在 64 次以内,或者对原始波形计算峭度、对 TSA 波形计算 RMS,不同特征采用不同的预处理路径。要知道时域变换不是越干净越好,丢掉噪声的同时也可能丢掉故障信息,每次都建议把变换前后的特征趋势画出来对比后再决定。

5.4 换一台设备或转速就不准

现象:模型在训练的转速下预测精度不错,换到另一台转速不同的设备上直接失效。

原因:输入波形没有做转频归一化。模型学到的脉冲间隔和幅值都是针对特定转速的,转速一变,脉冲间隔在采样点上的宽度全变了,之前的卷积核就失效了。

解决:滑窗长度、TSA 分段长度全部按samples_per_rot动态换算,做幅值归一化时用每个样本自身的 RMS 去除。更彻底的做法是在输入里显式加入转速或转频作为额外条件输入,让模型知道当前工况。我见过不少团队在模型结构上反复折腾,最后发现是没归一化转速,这个坑最容易在人手不足时被忽略。

5.5 预测曲线和真实曲线“趋势对但相位偏”

现象:从图上肉眼可见预测曲线和真实 RUL 下降趋势一致,但整体滞后或提前了一段,RMSE 不算大,维修决策却会错位。

原因:滑窗样本的标签取的是窗口起始时刻,但模型输出的是“这一段波形的剩余寿命”,窗口终点时刻的退化状态更接近模型实际看到的内容。这个偏差本身就是窗口长度对应的延迟。末端阶段标签线性下降,而真实退化往往加速,线性标签和真实退化的差异也会造成相位偏移。

解决:把窗口标签改为窗口终点或中点时刻对应的 RUL,这相当于主动补偿窗口引起的延迟;末段改用加权损失,提高近期样本的权重,让模型更关注末端快速退化段。

5.6 排查的通用顺序:先特征、再标签、最后模型

遇到预测结果异常,不要一上来就调网络结构。按这个顺序排查:第一步看特征曲线,RMS 和峭度是否在合理阶段出现变化,特征没有退化趋势时模型也无法凭空学到;第二步看标签,画出 RUL 标签曲线,确认饱和区、线性区的转折点是否符合 RMS 拐点;第三步看模型输出,把预测曲线和特征曲线叠在一起对比拐点。如果特征和标签都正常,模型输出仍异常,再考虑调整网络结构或训练参数。按这个顺序排查,大多数问题都能在一小时内定位到原因,而不是在模型上瞎试。

6. 从预测值到维修决策:预警触发验证法

预测模型交付时,现场工程师要的不是一串 RUL 数字,而是“什么时候该准备备件、什么时候必须停机更换”。RMSE 只能说明整体误差大小,说明不了预警时刻是否准确。我习惯用预警触发验证法来评估模型:在训练集的健康段上计算预测 RUL 的 95% 分位数作为健康阈值,然后在测试段上找到预测值首次持续低于该阈值的时间点,把这个时间点与真实退化起点(RMS 趋势拐点或标签进入线性段的时刻)对比,得到提前或滞后的时间。

health_threshold = np.percentile(train_pred_rul, 5) # 健康段低分位数 alarm_idx = np.where(test_pred_rul < health_threshold)[0][0] alarm_time = alarm_idx * step_time # step_time 为窗口步长对应的时间 lead_time = true_degrad_start - alarm_time # 正值表示提前报警,负值表示滞后

如果lead_time是负的,说明模型报警比真实退化起点晚,这在维修场景里属于漏报,比虚报更危险。如果提前太多,则误报率高,现场会被频繁的假警报骚扰。这个指标比 RMSE 更贴近维修决策的真实需求,而且计算成本极低。

我现在的习惯是:先用 RMS 趋势找退化起点校准标签,再用模型训练,最后第一眼看的不是 loss,而是把预测曲线和 RMS 曲线叠在一起,看两个拐点是否对齐。这一套流程帮我避免了很多解释不清的翻车。希望帮到你。

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

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

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

立即咨询