第四章:特征工程入门——时域特征提取、频域特征提取、特征选择方法

各位同行,大家好。这一章我们聊聊特征工程。

说实话,在风电运维这个行当里摸爬滚打这么多年,我越来越觉得:数据挖掘的成败,七分在特征,三分在模型。你模型再花哨,喂进去的是垃圾特征,出来的只能是垃圾结果。我自己就吃过这个亏——有一回做齿轮箱故障诊断,直接拿原始振动信号扔进神经网络,折腾了两周,准确率死活上不去。后来老老实实做特征提取,半天就搞定了。

好,咱们直接进入正题。

4.1 时域特征提取:从波形里“抠”信息

时域特征,说白了就是直接从信号波形上算出来的统计量。它最直观,也最常用。你想想看,一个振动传感器贴在轴承座上,采集到的是一串随时间变化的数值——这就是时域信号。

常用的时域特征有哪些?

我习惯把它们分成两类:有量纲特征和无量纲特征。

特征类别 特征名称 计算公式 物理意义
有量纲特征 均值 μ = (1/N) Σxᵢ 信号直流分量,反映趋势
均方根值 RMS = √[(1/N) Σxᵢ²] 信号能量,我最常用的指标
峰值 Xpeak = max|xᵢ| 冲击强度,轴承故障时明显增大
方差 σ² = (1/N) Σ(xᵢ-μ)² 信号波动程度
无量纲特征 峭度 K = (1/N) Σ[(xᵢ-μ)/σ]⁴ 冲击性指标,正常值≈3
偏度 S = (1/N) Σ[(xᵢ-μ)/σ]³ 分布不对称性
峰值因子 C = Xpeak / RMS 信号尖锐程度
我的经验:做风机叶片结冰诊断时,峭度这个特征特别敏感。正常叶片振动信号的峭度接近3,一旦结冰,冲击成分增加,峭度能飙到5以上。但要注意——峭度对早期故障敏感,对严重故障反而会下降,别被它骗了。

代码实现其实很简单。我一般用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['variance'] = np.var(signal)
    features['kurtosis'] = stats.kurtosis(signal)
    features['skewness'] = stats.skew(signal)
    features['crest_factor'] = features['peak'] / features['rms']
    return features

# 示例:处理一段振动数据
vibration_data = np.random.randn(1000)  # 模拟数据
time_features = extract_time_features(vibration_data)
print(time_features)

4.2 频域特征提取:换个角度看问题

时域特征虽然直观,但有些故障在时域里根本看不出来。举个例子,齿轮的啮合频率故障——时域波形可能只是微微抖动,但一转到频域,故障频率处的幅值会明显凸起。

频域分析的核心工具:傅里叶变换

嗯,这里要注意。FFT(快速傅里叶变换)是基础,但直接用FFT得到的频谱有512条、1024条谱线,你不可能把每条谱线都当特征。那样做的话,特征维度比样本还多,模型直接过拟合。

我通常的做法是:提取频域统计特征

  • 重心频率:频谱的能量重心位置,反映信号主频带
  • 频率方差:频谱的分散程度
  • 均方频率:各频率分量的加权平均
  • 特定频带能量:比如0-100Hz、100-200Hz等频段的能量占比

举个例子,做齿轮箱故障诊断时,我习惯把频谱分成三个频带:

频带 范围 对应故障
低频带 0-500 Hz 轴不平衡、不对中
中频带 500-2000 Hz 齿轮啮合故障
高频带 2000-5000 Hz 轴承早期故障
避坑指南:我曾经犯过一个低级错误——做FFT之前忘了加窗函数。结果频谱泄露得一塌糊涂,相邻频率的幅值互相干扰,特征完全失真。记住:做FFT之前一定要加窗!汉宁窗、海明窗都行,就是别用矩形窗。
import numpy as np
from scipy.fft import fft, fftfreq

def extract_frequency_features(signal, fs=1000):
    n = len(signal)
    # 加汉宁窗
    window = np.hanning(n)
    signal_windowed = signal * window
    
    # FFT
    spectrum = fft(signal_windowed)
    magnitude = np.abs(spectrum[:n//2])
    freqs = fftfreq(n, 1/fs)[:n//2]
    
    features = {}
    # 重心频率
    features['centroid'] = np.sum(freqs * magnitude) / np.sum(magnitude)
    # 频率方差
    features['freq_variance'] = np.sum((freqs - features['centroid'])**2 * magnitude) / np.sum(magnitude)
    # 特定频带能量
    low_band = magnitude[(freqs >= 0) & (freqs < 500)]
    features['low_band_energy'] = np.sum(low_band**2)
    
    return features

4.3 特征选择方法:去粗取精

特征提取完了,你可能得到几十个甚至上百个特征。但别急着全扔进模型——特征太多不是好事。我见过有人提取了200多个特征,结果模型训练时间长了10倍,准确率反而下降了。这就是“维度灾难”。

特征选择,就是帮你从一堆特征里挑出最有用的那几个。

常用的三类方法:

  1. 过滤法(Filter):先算特征和目标变量的相关性,再排序筛选。速度快,适合大规模数据。
  2. 包裹法(Wrapper):把特征子集扔进模型,看效果好坏。准确率高,但计算量大。
  3. 嵌入法(Embedded):在模型训练过程中自动选择特征。比如Lasso回归、决策树特征重要性。

我个人最常用的是随机森林特征重要性(嵌入法)。为什么?因为它既考虑了特征与目标的关系,又考虑了特征之间的交互作用,而且实现起来特别方便。

from sklearn.ensemble import RandomForestClassifier
import pandas as pd

# 假设X是特征矩阵,y是标签
X = pd.DataFrame(...)  # 你的特征数据
y = pd.Series(...)     # 故障标签

# 训练随机森林
rf = RandomForestClassifier(n_estimators=100, random_state=42)
rf.fit(X, y)

# 获取特征重要性
importance = rf.feature_importances_
feature_names = X.columns

# 排序并展示前10个重要特征
sorted_idx = np.argsort(importance)[::-1]
print("Top 10 重要特征:")
for i in range(10):
    print(f"{feature_names[sorted_idx[i]]}: {importance[sorted_idx[i]]:.4f}")
核心原则:特征选择不是越多越好,而是越精越好。我一般遵循“少即是多”的原则——先用过滤法快速筛掉明显无关的特征,再用嵌入法精挑细选,最后保留10-20个特征就够了。

4.4 本章知识体系总览

说了这么多,咱们用一张图把整个流程串起来。这张图是我自己画的知识框架,你一看就明白特征工程在数据挖掘中的位置了。

特征工程知识体系 原始振动/温度/功率数据 时域特征提取 • 均值、均方根值、峰值 • 峭度、偏度、峰值因子 • 波形因子、脉冲因子 频域特征提取 • FFT频谱分析 • 重心频率、频率方差 • 特定频带能量 特征选择 过滤法 → 包裹法 → 嵌入法 最终保留10-20个关键特征 高质量特征集 → 故障诊断模型

这张图把咱们这章的内容串起来了。从原始数据出发,经过时域和频域两条路径提取特征,再通过特征选择去粗取精,最终得到高质量的特征集,喂给故障诊断模型。

最后说句掏心窝的话:特征工程没有标准答案。同样的数据,不同的人提取的特征可能完全不同。我的建议是——多试、多对比、多积累。做多了,你自然就知道哪些特征对风机故障最敏感了。

公众号:蓝海数据掘金营,微信deep3321