特征工程基础:时域与频域特征提取

大家好,我是老张。今天咱们聊聊特征工程里最基础、也最核心的一块——时域和频域特征提取。说实话,我见过不少刚入行的同事,一上来就堆各种深度学习模型,结果效果还不如一个简单的随机森林。为什么?因为特征没做好。你想想看,模型再强,喂进去的是垃圾,出来的还是垃圾。

在风电AI运维里,我们面对的是海量的振动信号、温度曲线、功率数据。这些原始数据,说白了就是一堆时间序列。怎么从里面挖出有用的信息?这就是特征工程要干的事。

核心观点:特征工程决定了模型效果的上限,算法只是逼近这个上限。在风电异常检测中,好的特征能让模型提前3-5天预警轴承故障。

风电AI运维特征工程知识体系 原始振动/温度/功率数据 时域特征提取 均值/方差/标准差 峰值/峰峰值/峭度 均方根/波形因子 偏度/峰度/裕度 频域特征提取 FFT变换 频谱幅值/相位 功率谱密度 边频/谐波分析 特征选择:方差过滤 → 相关性分析 → 模型筛选

一、时域特征提取:最直观的信号描述

时域特征,说白了就是直接在时间轴上算统计量。我刚开始做风电项目时,觉得这些太简单了,不就是算个平均数吗?后来发现,选对时域特征,比调模型参数重要得多。

1.1 基本统计量

咱们先看几个最常用的。这些特征计算简单,但信息量很大。

特征名称 公式 物理意义 风电场景应用
均值 μ = (1/N)∑xᵢ 信号的中心趋势 判断齿轮箱温度是否整体偏高
方差 σ² = (1/N)∑(xᵢ-μ)² 信号的离散程度 检测振动幅值的波动异常
标准差 σ = √σ² 与方差相同,量纲一致 设定报警阈值时常用
峰值 Xpeak = max|xᵢ| 信号的最大瞬时值 捕捉冲击性故障(如轴承点蚀)
均方根 RMS = √((1/N)∑xᵢ²) 信号的有效能量 评估振动烈度,ISO标准常用

我的经验:在风电齿轮箱振动监测中,我习惯同时看均方根和峰值。均方根反映整体磨损,峰值捕捉突发冲击。两者比值(峰值因子)超过3.5时,基本可以判定有早期故障。

1.2 高阶统计量

除了基本量,还有几个高阶特征,在风电异常检测里特别好用。

  • 峭度(Kurtosis):衡量信号分布的陡峭程度。正常振动信号接近正态分布,峭度≈3。轴承出现故障时,冲击成分会让峭度飙升到5甚至10以上。我曾在一次项目中,靠峭度指标提前两周发现了变桨轴承的裂纹。
  • 偏度(Skewness):衡量分布的不对称性。齿轮磨损时,偏度会明显偏离0。
  • 波形因子:RMS与整流平均值的比值。这个特征对负载变化不敏感,适合变工况场景。
  • 裕度因子:峰值与方根幅值的比值。对冲击性故障非常敏感,但容易受噪声干扰。
# Python代码:时域特征提取示例
import numpy as np
import pandas as pd

def extract_time_domain_features(signal):
    """
    提取时域特征
    signal: 一维数组,振动信号
    """
    features = {}
    
    # 基本统计量
    features['mean'] = np.mean(signal)
    features['var'] = np.var(signal)
    features['std'] = np.std(signal)
    features['peak'] = np.max(np.abs(signal))
    features['rms'] = np.sqrt(np.mean(signal**2))
    
    # 高阶统计量
    features['kurtosis'] = np.mean((signal - features['mean'])**4) / (features['std']**4)
    features['skewness'] = np.mean((signal - features['mean'])**3) / (features['std']**3)
    
    # 无量纲指标
    features['crest_factor'] = features['peak'] / features['rms']
    features['shape_factor'] = features['rms'] / np.mean(np.abs(signal))
    
    return features

# 模拟一段风电振动信号
np.random.seed(42)
normal_signal = np.random.normal(0, 1, 1000)  # 正常信号
fault_signal = normal_signal.copy()
fault_signal[500:510] += 5  # 模拟冲击故障

print("正常信号峭度:", extract_time_domain_features(normal_signal)['kurtosis'])
print("故障信号峭度:", extract_time_domain_features(fault_signal)['kurtosis'])

注意:峭度对单次冲击非常敏感,但容易受噪声干扰。我建议在实际项目中,先做滑动平均去噪,再计算峭度。否则一个传感器误报就能让峭度值失控。

二、频域特征提取:看到时域看不到的东西

时域特征能告诉你信号有多强、有多散,但说不清信号里有哪些频率成分。举个例子,齿轮啮合频率是500Hz,轴承故障频率是120Hz,这两个在时域里混在一起,你根本分不清。但一转到频域,清清楚楚。

2.1 FFT与频谱分析

FFT(快速傅里叶变换)是频域分析的基础。说白了,就是把时域信号拆解成不同频率的正弦波组合。

# Python代码:FFT频谱分析
import numpy as np
import matplotlib.pyplot as plt

def compute_fft(signal, fs):
    """
    计算FFT频谱
    signal: 时域信号
    fs: 采样频率 (Hz)
    """
    n = len(signal)
    fft_vals = np.fft.fft(signal)
    fft_freq = np.fft.fftfreq(n, 1/fs)
    
    # 只取正频率部分
    positive_idx = np.where(fft_freq >= 0)
    fft_freq = fft_freq[positive_idx]
    fft_vals = np.abs(fft_vals[positive_idx]) / n  # 幅值归一化
    
    return fft_freq, fft_vals

# 模拟齿轮箱振动信号
fs = 1000  # 采样率 1000Hz
t = np.arange(0, 1, 1/fs)

# 正常信号:50Hz基频 + 谐波
normal_signal = np.sin(2*np.pi*50*t) + 0.3*np.sin(2*np.pi*100*t) + 0.1*np.sin(2*np.pi*150*t)

# 故障信号:增加了120Hz的轴承故障频率成分
fault_signal = normal_signal + 0.5*np.sin(2*np.pi*120*t)

freq_n, amp_n = compute_fft(normal_signal, fs)
freq_f, amp_f = compute_fft(fault_signal, fs)

print("正常信号主要频率成分:", freq_n[np.argsort(amp_n)[-5:]])
print("故障信号主要频率成分:", freq_f[np.argsort(amp_f)[-5:]])

2.2 频域特征指标

拿到频谱后,我们还需要提取一些关键指标。我个人常用这几个:

  • 重心频率:频谱的能量重心位置。齿轮磨损时,高频成分增加,重心频率会右移。
  • 频率方差:频谱的分散程度。故障越严重,频谱越分散。
  • 边频带幅值:在啮合频率两侧出现的调制边频。这是齿轮故障的典型特征。
  • 谐波能量比:各次谐波能量与总能量的比值。可以用来判断故障的严重程度。

避坑指南:我曾经在分析某风场数据时,发现频谱里有个明显的峰值,以为是轴承故障。后来仔细一查,是附近一台变频器的开关频率干扰。所以做频域分析前,一定要先搞清楚设备的特征频率,别把干扰当故障。

2.3 功率谱密度

功率谱密度(PSD)是频域分析的另一个利器。它描述的是信号功率在频率上的分布。对于风电这种非平稳信号,PSD比普通频谱更稳定。

# 使用Welch方法计算功率谱密度
from scipy import signal

def compute_psd(signal, fs):
    """
    计算功率谱密度
    """
    freqs, psd = signal.welch(signal, fs, nperseg=256)
    return freqs, psd

freqs, psd = compute_psd(normal_signal, fs)
print("PSD峰值频率:", freqs[np.argmax(psd)])

三、特征选择方法:别让垃圾特征拖后腿

特征提取完了,你可能得到几十甚至上百个特征。但并不是每个都有用。我见过有人提取了200多个特征,结果模型训练时间翻了三倍,准确率反而下降了。这就是典型的「维度灾难」。

3.1 过滤法

过滤法是最简单粗暴的方法。先算每个特征与目标变量的相关性,然后设定阈值筛选。

  • 方差过滤:方差太小的特征,说明数据几乎不变,对模型没贡献。直接扔掉。
  • 相关系数法:计算每个特征与标签的皮尔逊相关系数。低于0.1的,基本可以放弃。
  • 互信息法:能捕捉非线性关系,比相关系数更全面。

3.2 包裹法

包裹法把特征选择看作一个搜索问题。我常用的有递归特征消除(RFE)。

# 递归特征消除示例
from sklearn.feature_selection import RFE
from sklearn.ensemble import RandomForestClassifier

# 假设 X 是特征矩阵,y 是标签
# X = ...  # 你的特征数据
# y = ...  # 异常/正常标签

# model = RandomForestClassifier(n_estimators=100)
# selector = RFE(model, n_features_to_select=10)
# selector.fit(X, y)
# selected_features = X.columns[selector.support_]

3.3 嵌入法

嵌入法把特征选择嵌入到模型训练过程中。比如Lasso回归的L1正则化,会自动把不重要的特征系数压缩到0。随机森林的特征重要性也是嵌入法的一种。

我的建议:在实际项目中,我通常三步走:先用过滤法快速筛掉明显无用的特征(比如方差接近0的),再用嵌入法(随机森林特征重要性)缩小范围,最后用包裹法精挑细选。这样既快又准。

四、实战经验总结

说了这么多,最后分享几个我在风电项目里的实际经验:

  1. 时域和频域要结合着用。时域特征对突发冲击敏感,频域特征对周期性故障敏感。两者互补,缺一不可。
  2. 特征不是越多越好。我见过一个团队提取了300多个特征,结果模型过拟合得一塌糊涂。记住:10个高质量特征,胜过100个垃圾特征。
  3. 注意工况变化。风电的工况(风速、转速)变化很大。同一个特征,在低风速和高风速下的分布完全不同。我习惯先做工况分段,再分别提取特征。
  4. 特征工程要反复迭代。不要指望一次就能找到最优特征集。先跑一个基线模型,然后根据模型反馈,不断调整特征组合。

最后提醒:特征工程没有银弹。同样的特征,在这个风场好用,换一个风场可能就失效了。因为每个风场的机型、工况、环境都不一样。所以,多试、多验证,才是正道。


公众号:蓝海资料掘金营,微信deep3321