4、经典降噪方法(二):主成分分析降噪、奇异值分解降噪、经验模态分解、变分模态分解
上一章我们聊了傅里叶和小波,算是频域降噪的经典套路。这一章我们换个思路,从矩阵分解和信号分解的角度切入。说实话,这几种方法在订单簿数据上各有千秋,我个人的经验是——没有银弹,只有合适的场景。
核心思路:把噪声看作数据中的「小扰动」,通过分解找到真正的结构。
4.1 主成分分析降噪(PCA Denoising)
PCA降噪,说白了就是「抓大放小」。你想想看,订单簿的买卖价差、深度、成交量这些特征,其实很多是相关的。噪声往往藏在那些方差很小的成分里。
原理:对数据做PCA分解,保留前k个主成分,扔掉后面的。因为噪声通常贡献很小的方差。
import numpy as np
from sklearn.decomposition import PCA
def pca_denoise(data, n_components=0.95):
"""
data: shape (n_samples, n_features)
n_components: 保留的方差比例
"""
pca = PCA(n_components=n_components)
transformed = pca.fit_transform(data)
denoised = pca.inverse_transform(transformed)
return denoised
# 示例:对订单簿的10档买卖数据进行降噪
orderbook_data = np.random.randn(1000, 20) # 模拟数据
clean_data = pca_denoise(orderbook_data, n_components=0.9)
print(f"原始维度: {orderbook_data.shape[1]}, 保留主成分数: {clean_data.shape[1]}")
我的经验:我在处理高频订单簿时,发现保留85%-95%的方差效果最好。保留太少会丢失信号,保留太多噪声又没去掉。你可以用交叉验证来调这个阈值。
4.2 奇异值分解降噪(SVD Denoising)
SVD降噪和PCA本质上是同一件事,但SVD更直接——它直接对矩阵做分解。我个人更喜欢用SVD,因为不需要计算协方差矩阵,数值稳定性更好。
原理:对数据矩阵做SVD分解,保留最大的几个奇异值,把小的置零,再重构。
import numpy as np
def svd_denoise(data, threshold=0.1):
"""
data: shape (m, n)
threshold: 保留奇异值的比例阈值
"""
U, s, Vt = np.linalg.svd(data, full_matrices=False)
# 计算保留的奇异值数量
total_energy = np.cumsum(s**2) / np.sum(s**2)
k = np.searchsorted(total_energy, 1 - threshold) + 1
# 截断
s_denoised = np.zeros_like(s)
s_denoised[:k] = s[:k]
# 重构
denoised = U @ np.diag(s_denoised) @ Vt
return denoised
# 对订单簿的买卖价差序列做降噪
spread_data = np.random.randn(500, 10) # 模拟10个时间窗口的价差
clean_spread = svd_denoise(spread_data, threshold=0.15)
print(f"保留的奇异值数量: {np.sum(s_denoised > 0)}")
避坑指南:我曾经在实盘数据上直接用SVD降噪,结果发现重构后的数据出现了负的买卖价差——这显然不合理。后来我加了非负约束,才解决了这个问题。记住:降噪后的数据要符合业务逻辑。
4.3 经验模态分解(EMD)
EMD是个很有意思的方法。它不需要预设基函数,完全靠数据自己「说话」。你想想看,订单簿的价格波动,有趋势、有周期、有噪声,EMD能自动把它们拆开。
原理:通过「筛分」过程,把信号分解成若干个本征模态函数(IMF)和一个残差。噪声通常落在高频的IMF里。
# 使用PyEMD库(需要安装:pip install EMD-signal)
from PyEMD import EMD
import numpy as np
def emd_denoise(signal, noise_imfs=1):
"""
signal: 1D array
noise_imfs: 去掉前几个高频IMF(通常1-2个)
"""
emd = EMD()
imfs = emd(signal)
# 去掉前noise_imfs个高频IMF
denoised = np.sum(imfs[noise_imfs:], axis=0)
return denoised
# 对订单簿的中间价序列做降噪
mid_price = np.sin(np.linspace(0, 10, 1000)) + 0.5 * np.random.randn(1000)
clean_price = emd_denoise(mid_price, noise_imfs=2)
# 可视化(示意)
print(f"原始信号长度: {len(mid_price)}, 降噪后长度: {len(clean_price)}")
我的建议:EMD有个问题——模态混叠。就是不同频率的成分会混在一起。我在处理订单簿的突发大单时遇到过这个问题,后来改用EEMD(集合经验模态分解)才解决。你可以试试加白噪声辅助分解。
4.4 变分模态分解(VMD)
VMD是EMD的升级版。它把分解问题变成了一个优化问题,说白了就是「我要找一组模态,让它们的带宽之和最小」。这样分解出来的模态更干净,不会混叠。
原理:通过求解变分问题,把信号分解成K个带宽受限的模态。每个模态的中心频率和带宽都是自动确定的。
# 使用vmdpy库(需要安装:pip install vmdpy)
from vmdpy import VMD
import numpy as np
def vmd_denoise(signal, K=5, alpha=2000, tau=0):
"""
signal: 1D array
K: 模态数量
alpha: 带宽惩罚参数
tau: 噪声容忍度
"""
u, u_hat, omega = VMD(signal, alpha, tau, K, 0, 1, 1e-7)
# 通常去掉第一个高频模态(噪声)
denoised = np.sum(u[1:], axis=0)
return denoised
# 对订单簿的成交量序列做降噪
volume = np.random.randn(1000) + 10 * np.sin(np.linspace(0, 20, 1000))
clean_volume = vmd_denoise(volume, K=5, alpha=2000)
print(f"分解出 {clean_volume.shape[0]} 个模态")
关键参数:VMD的K值很关键。K太小会欠分解,K太大会过分解。我一般用中心频率的分布来判断——如果两个模态的中心频率太接近,就减少K。
4.5 四种方法的对比与选择
好了,四种方法都介绍完了。你可能会问:到底该用哪个?嗯,这里我整理了一个对比表,方便你快速决策。
| 方法 | 适用场景 | 优点 | 缺点 | 我的推荐 |
|---|---|---|---|---|
| PCA | 多特征订单簿(如10档买卖数据) | 简单、快速、可解释 | 假设线性关系 | ⭐⭐⭐⭐ |
| SVD | 矩阵形式的数据(如时间窗口切片) | 数值稳定、无需协方差 | 对异常值敏感 | ⭐⭐⭐⭐ |
| EMD | 非平稳信号(如价格序列) | 自适应、无需预设基函数 | 模态混叠、计算慢 | ⭐⭐⭐ |
| VMD | 需要精确分离频率成分 | 模态清晰、抗混叠 | 需要调参(K、alpha) | ⭐⭐⭐⭐⭐ |
实战建议:我个人习惯先用PCA或SVD做快速降噪,如果效果不满意,再上VMD。EMD我一般只用在探索性分析阶段,因为它不需要调参,能快速看到信号的结构。
4.6 知识体系图
下面这张图总结了本章的核心逻辑,方便你理解四种方法的关系和适用场景。
这张图把四种方法的关系理清楚了。你从中心出发,根据你的数据类型和需求,选择合适的分支。记住:没有最好的方法,只有最合适的。
最后提醒:降噪不是目的,增强预测信号才是。我见过太多人把降噪做得太狠,结果把有用的交易信号也去掉了。降噪后一定要回测,看看预测效果是否真的提升了。
好了,这一章的内容就到这里。四种方法各有特色,建议你在自己的数据上多试试,找到最适合的那一款。