特征工程:时域特征、频域特征、统计特征

特征工程这件事,说白了你得跟数据“混熟”。我刚做异常检测那会儿,上来就怼深度学习模型,结果效果奇差。后来发现,问题是特征没选对。你想想看,传感器采集的原始波形,里面藏着大量信息,你不把它提炼出来,模型连看都看不懂。

我个人习惯第一步先画时序图。眼睛扫一遍,基本能判断这组数据有没有明显的周期、突变、或者漂移。然后我再决定从哪个域去挖特征。今天我们就聊三个最常用的域:时域、频域、统计特征。

核心思路:将原始信号映射到不同空间,让异常模式更明显。别贪多,三个域每样挑几个最稳的特特征就够了。
特征工程三大域 —— 异常检测知识结构图 原始信号 ⏱ 时域特征 📊 频域特征 📈 统计特征 峰值 / 谷值 均值 / 方差 过零率 FFT 幅度谱 功率谱密度 谱峰频率 偏度 / 峰度 分位数 / IQR 滑动窗口熵 💡 通常先提取时域+统计特征做快速筛查,频域特征用于周期性异常的精细诊断。 三个域的特征组合后,再用 PCA 或互信息筛选,不要一股脑全塞给模型。

时域特征:最直观,但也最容易漏

时域特征就是直接在时间轴上算各种值。平均值、方差、最大值、最小值,这些太基础了,但千万别因为基础就小看它。我记得有次在振动数据里,异常点恰恰是均值没有变化、而峰峰值突然翻倍。如果只盯着均值,你可能永远抓不到它。

我建议你至少包含这几项:

  • 峰值(Peak):最大值与最小值之差,对冲击型异常非常敏感。
  • 有效值(RMS):反应信号的总体能量水平,平稳型异常看它。
  • 过零率(Zero Crossing Rate):单位时间内信号穿越零点的次数。频率信息的一种廉价替代。
  • 波形因子:RMS 与整流平均值的比值。我在电机故障检测中经常用。
import numpy as np

def extract_time_features(signal):
    peak = np.max(signal) - np.min(signal)
    rms = np.sqrt(np.mean(signal**2))
    zcr = np.sum(np.diff(np.sign(signal)) != 0) / len(signal)
    # 波形因子
    form_factor = rms / np.mean(np.abs(signal))
    return {'peak': peak, 'rms': rms, 'zcr': zcr, 'form_factor': form_factor}  
注意:时域特征很依赖对齐。如果信号有相位偏移或时间漂移,建议先做滑动窗口对齐再提取,否则特征值会狂跳。

频域特征:周期异常无处遁形

频谱分析是我个人觉得很“性感”的一块。时域里混在一起的不同频率成分,到了频域里像分垃圾一样清清楚楚。异常很多时候就是某些频率分量突然变强或变弱。比如机器轴承磨损,高频段能量就会明显上升。

怎么做?先对窗口数据做 FFT,得到幅值谱或功率谱。

def extract_freq_features(signal, fs=1000):
    n = len(signal)
    fft_vals = np.fft.rfft(signal)
    freqs = np.fft.rfftfreq(n, d=1/fs)
    mag = np.abs(fft_vals)
    
    # 谱能量
    total_energy = np.sum(mag**2)
    # 主频
    peak_freq = freqs[np.argmax(mag)]
    # 高频段能量占比(例如 > 200Hz)
    high_energy = np.sum(mag[freqs > 200]**2)
    return {'total_energy': total_energy, 'peak_freq': peak_freq,
            'high_ratio': high_energy / (total_energy + 1e-12)}  

这里有个经验:窗口长度选 2 的幂次(如 256, 512, 1024),并且使用汉宁窗减少频谱泄露。我曾经因为没用窗函数,导致两个相近频率的异常直接被漏掉。

统计特征:老司机最爱

统计特征不依赖时序,把数据当集合看。偏度(Skewness)告诉你分布是否左偏或右偏,峰度(Kurtosis)告诉你尾部有多厚。在异常检测里,峰度是“神级”特征——很多异常事件会让数据产生极值,峰度瞬间飙高。

除了经典的四阶矩,我还会加两个东西:

  • 四分位距(IQR):Q3 - Q1,对离群值鲁棒,比标准差更稳。
  • 滑动窗口熵(Sliding Entropy):窗口内数据的随机程度。如果信号突然变得规律,熵会下降,这本身就是一种异常。
def extract_stat_features(signal):
    from scipy.stats import skew, kurtosis
    skewness = skew(signal)
    kurt = kurtosis(signal)   # 超额峰度
    q1, q3 = np.percentile(signal, [25, 75])
    iqr = q3 - q1
    
    # 离散熵(将值离散化为10个bin)
    hist, _ = np.histogram(signal, bins=10, density=True)
    hist = hist[hist > 0]
    entropy = -np.sum(hist * np.log2(hist))
    return {'skew': skewness, 'kurtosis': kurt, 'iqr': iqr, 'entropy': entropy}  
组合技巧:将三个域的特征拼接成特征向量。我用过 3(时域)+ 4(频域)+ 4(统计)共 11 维特征,在 wind turbine 故障检测上 AUC 从 0.87 提升到了 0.94。千万别小看这几步特征工程。

特征选择:别什么都往里塞

提取完一大堆特征,直接喂给模型?我劝你别急。特征太多反而会引入噪声。我会先做一次相关性分析,把高度相关的特征(>0.95)去掉,然后用互信息或随机森林重要性排序,选 top-K 个。

特征相关性(与异常标签)我是否保留
RMS0.72保留
峰度0.81保留
峰值频率0.33按需
均值0.12丢弃
小技巧:如果数据量大,可以用 PCA 降维保留 95% 方差。但我更推荐保持特征可解释性——这样线上部署后异常归因时好定位。你想想看,老板让你解释为什么报警,你总不能说“因为 PCA 第3个分量超标”吧?

嗯,特征工程就聊到这里。记住,时域看幅度突变,频域看周期变化,统计看分布形状。三管齐下,异常基本无处可逃。