4. 特征工程:时域特征提取、频域特征提取、特征选择与降维

好,咱们进入第四章。这一章,说白了就是“怎么让数据说话”。

传感器采集回来的原始信号,就像一堆乱码。你直接扔给模型,它根本看不懂。我刚开始做风机诊断那会儿,就犯过这个错——把原始振动信号直接丢进神经网络,结果模型训练出来跟个傻子一样,啥也分不清。

后来我才明白,关键一步是特征工程。你得从原始信号里,把那些能反映故障本质的“特征”给提炼出来。今天咱们就聊聊,怎么干这活。

4.1 时域特征提取:看波形,找规律

时域特征,就是直接在时间轴上分析信号。你想想看,一个正常的风机轴承,它的振动波形是平稳的、有规律的。一旦出现故障,波形就会“炸毛”——出现冲击、幅值变大、分布变乱。

我个人习惯,先把下面这几个最常用的时域特征算出来:

特征名称 计算公式 物理意义
均值 \(\bar{x} = \frac{1}{N}\sum_{i=1}^{N}x_i\) 信号的中心趋势,反映静态分量
均方根值 (RMS) \(X_{rms} = \sqrt{\frac{1}{N}\sum_{i=1}^{N}x_i^2}\) 信号的能量大小,磨损越严重,RMS越大
峰值 \(X_{peak} = \max(|x_i|)\) 信号的最大冲击,对早期故障敏感
峭度 (Kurtosis) \(K = \frac{1}{N}\sum_{i=1}^{N}(\frac{x_i-\bar{x}}{\sigma})^4\) 信号分布的“尖锐”程度,冲击故障时峭度会飙升
峰值因子 \(C = X_{peak} / X_{rms}\) 冲击性故障的敏感指标
我的经验: 峭度这个特征,对早期轴承点蚀特别敏感。我曾经在项目里,靠峭度值从正常数据里揪出了两个早期故障样本,当时巡检人员还觉得我多虑了,结果拆下来一看,滚珠上确实有芝麻大的剥落点。

代码实现也很简单,用Python的numpy和scipy几行就搞定:

import numpy as np
from scipy import stats

def extract_time_features(signal):
    features = {}
    features['mean'] = np.mean(signal)
    features['rms'] = np.sqrt(np.mean(signal**2))
    features['peak'] = np.max(np.abs(signal))
    features['kurtosis'] = stats.kurtosis(signal)
    features['crest_factor'] = features['peak'] / features['rms']
    return features

4.2 频域特征提取:听声音,找频率

时域特征能看出“有没有问题”,但很难说清“哪里有问题”。这时候就得看频域了。

频域分析,说白了就是把信号从“时间-幅值”坐标系,转换到“频率-幅值”坐标系。每个机械部件(轴承内圈、外圈、滚动体、齿轮的齿)都有自己特定的故障频率。一旦某个部件坏了,对应的频率成分就会“冒出来”。

嗯,这里要注意:做频域分析前,一定要先做傅里叶变换(FFT)。我见过有人直接把原始数据当频谱用,那肯定是不对的。

常用的频域特征包括:

  • 重心频率:频谱的“重心”位置,反映信号主频带的变化
  • 均方频率:频率的均方值,对高频成分敏感
  • 频率方差:频谱能量的分散程度
  • 特定频带能量:比如提取轴承内圈故障频率附近的能量值

我建议,在提取频域特征时,先计算一下关键部件的理论故障频率。比如轴承内圈故障频率 \(f_i = \frac{n}{2} f_r (1 + \frac{d}{D} \cos \alpha)\),然后重点看那个频率附近有没有能量抬升。

import numpy as np
from scipy.fft import fft

def extract_freq_features(signal, fs):
    n = len(signal)
    freqs = np.fft.fftfreq(n, 1/fs)
    spectrum = np.abs(fft(signal))[:n//2]
    freqs = freqs[:n//2]
    
    # 重心频率
    fc = np.sum(freqs * spectrum) / np.sum(spectrum)
    # 均方频率
    msf = np.sum(freqs**2 * spectrum) / np.sum(spectrum)
    # 频率方差
    vf = np.sum((freqs - fc)**2 * spectrum) / np.sum(spectrum)
    
    return {'fc': fc, 'msf': msf, 'vf': vf}

4.3 特征选择与降维:别让模型“挑花了眼”

特征提取完了,你可能手里有几十个甚至上百个特征。但特征不是越多越好。你想想看,如果塞进去一堆无关特征,模型反而会“学偏”。

我曾经在一个项目里,提取了50多个特征,结果模型训练完,准确率还不如只用10个关键特征。后来一分析,很多特征之间高度相关,等于重复信息。

避坑指南: 我曾经犯过一个错误——把所有特征一股脑全扔进模型,结果模型过拟合严重,在测试集上表现一塌糊涂。后来才意识到,特征选择这一步,绝对不能省。

常用的方法有这几类:

4.3.1 过滤法

先算每个特征与目标变量(正常/故障)的相关性,然后挑出相关性高的。比如用皮尔逊相关系数、互信息等。速度快,适合初步筛选。

4.3.2 包裹法

把特征子集扔进模型,看效果。比如递归特征消除(RFE),每次去掉一个最不重要的特征,直到找到最优子集。效果好,但计算量大。

4.3.3 嵌入法

在模型训练过程中自动做特征选择。比如Lasso回归(L1正则化),它会自动把不重要的特征系数压缩到0。随机森林也能输出特征重要性排序。

4.3.4 降维:PCA

如果特征太多,又不想丢掉信息,可以用主成分分析(PCA)。它能把高维特征压缩成几个“主成分”,每个主成分都是原始特征的线性组合。

我个人习惯,先用过滤法快速筛一遍,把相关性低于0.1的特征扔掉。然后用随机森林看特征重要性,保留Top 10-15个。如果特征之间相关性太高(比如>0.9),只保留其中一个。

from sklearn.ensemble import RandomForestClassifier
from sklearn.feature_selection import SelectKBest, f_classif

# 过滤法:选择与目标最相关的K个特征
selector = SelectKBest(score_func=f_classif, k=15)
X_selected = selector.fit_transform(X, y)

# 随机森林特征重要性
rf = RandomForestClassifier()
rf.fit(X, y)
importances = rf.feature_importances_
# 保留重要性大于阈值的特征
important_idx = np.where(importances > 0.02)[0]

4.4 知识体系总览

说了这么多,我把这一章的核心逻辑画成了一张图,方便你理解:

特征工程知识体系 原始振动信号 时域特征提取 频域特征提取 均值、RMS、峰值 峭度、峰值因子 波形因子、脉冲因子 重心频率、均方频率 频率方差、频带能量 边频带特征 特征选择与降维

核心要点:

  • 时域特征:快速判断“有没有故障”,对冲击类故障敏感
  • 频域特征:定位“哪个部件故障”,需要结合理论故障频率
  • 特征选择:不是越多越好,要挑“有用”的,去掉冗余的
  • 降维:当特征太多时,用PCA等工具压缩维度,保留主要信息

好了,这一章的内容就到这儿。特征工程做得好,模型训练事半功倍。下一章咱们聊聊模型选型——到底用传统机器学习还是深度学习?到时候再细说。


专注资料整理