滤波方法(二):指数平滑滤波

上一篇介绍了移动平均滤波。它有三个局限:窗口内所有观测权重相等、需要存储整段窗口历史、对趋势信号跟踪滞后。指数平滑(Exponential Smoothing)用一条递推公式同时解决了前两个问题。

与移动平均的关系

移动平均给窗口内每个观测权重 $1/W$,窗口外的权重为零——这是一个矩形窗。指数平滑把这个权重函数换成指数衰减:越近的观测权重越大,越远的观测权重越小,且衰减到无穷远。更关键的是,这个指数加权的递推只需要存储上一个时刻的估计值,无需维护历史窗口。

递推公式

递推公式极为简洁:

$$\hat{x}k = \alpha \cdot z_k + (1 - \alpha) \cdot \hat{x}$$

参数 $\alpha \in (0, 1)$ 是平滑因子。$\alpha$ 接近 1 时追踪速度快但噪声残留多,接近 0 时输出平滑但反应迟钝。将递推式展开可清楚看到权重呈几何衰减:

$$\hat{x}k = \alpha \sum$$}^{\infty} (1 - \alpha)^j \, z_{k-j

频域特性

指数平滑是控制理论中一阶低通滤波器的离散等价。其频域幅频响应为

$$|H(\omega)| = \frac{\alpha}{\sqrt{1 + (1 - \alpha)^2 - 2(1 - \alpha) \cos \omega}}$$

与移动平均的多零点和旁瓣结构不同,指数平滑呈现平滑衰减的单峰特性,没有旁瓣振铃。它在时间序列预测中极其常用——Holt-Winters 方法正是其多参数推广。

可视化

下图的 GIF 演示 $\alpha$ 从 0.02 连续变化到 0.98 的过程中,滤波器从极度平滑到紧密追踪的转变。

滤波02-图1 指数平滑:平滑因子α从0.02到0.98的变化

Python 实现

import os
import numpy as np
import matplotlib.pyplot as plt
import matplotlib.animation as animation

os.makedirs('images', exist_ok=True)

np.random.seed(42)
k = np.arange(200)
true = np.sin(2 * np.pi * k / 50) + (k > 100).astype(float)
obs = true + np.random.randn(200) * 0.5

def exp_smooth(x, alpha):
    est = np.zeros_like(x)
    est[0] = x[0]
    for i in range(1, len(x)):
        est[i] = alpha * x[i] + (1 - alpha) * est[i-1]
    return est

a01 = exp_smooth(obs, 0.1)
a03 = exp_smooth(obs, 0.3)
a07 = exp_smooth(obs, 0.7)

fig, ax = plt.subplots(figsize=(10, 5))
ax.plot(k, true, 'k-', label='True Signal', linewidth=1.5)
ax.plot(k, obs, '.', color='gray', alpha=0.4, label='Observations')
ax.plot(k, a01, label='alpha=0.1', linewidth=1.2)
ax.plot(k, a03, label='alpha=0.3', linewidth=1.2)
ax.plot(k, a07, label='alpha=0.7', linewidth=1.2)
ax.set_xlabel('Time Step k')
ax.set_ylabel('Value')
ax.set_title('Exponential Smoothing')
ax.legend(loc='upper right')
ax.grid(True, alpha=0.3)
fig.tight_layout()
fig.savefig('images/exponential_smoothing.svg')
fig.savefig('images/exponential_smoothing.png', dpi=150)
plt.close(fig)

omega = np.linspace(0, np.pi, 500)
fig, axes = plt.subplots(1, 2, figsize=(12, 4))
for alpha in [0.1, 0.3, 0.7]:
    H = alpha / np.sqrt(1 + (1 - alpha)**2 - 2 * (1 - alpha) * np.cos(omega))
    axes[0].plot(omega / np.pi, H, label=f'alpha={alpha}')
axes[0].set_xlabel('Normalized Frequency (omega/pi)')
axes[0].set_ylabel('|H(omega)|')
axes[0].set_title('Frequency Response')
axes[0].legend()
axes[0].grid(True, alpha=0.3)
for alpha in [0.1, 0.3, 0.7]:
    est = exp_smooth(obs, alpha)
    axes[1].plot(k, est, label=f'alpha={alpha}', linewidth=1.2)
axes[1].plot(k, true, 'k--', label='True Signal', linewidth=1.0)
axes[1].set_xlabel('Time Step k')
axes[1].set_ylabel('Value')
axes[1].set_title('Time Domain')
axes[1].legend()
axes[1].grid(True, alpha=0.3)
fig.tight_layout()
fig.savefig('images/exponential_smoothing_freq.svg')
fig.savefig('images/exponential_smoothing_freq.png', dpi=150)
plt.close(fig)

fig, ax = plt.subplots(figsize=(10, 5))
alphas = np.linspace(0.02, 0.98, 49)
line_true, = ax.plot(k, true, 'k-', label='True Signal', linewidth=1.5)
line_obs, = ax.plot(k, obs, '.', color='gray', alpha=0.3, label='Observations')
line_es, = ax.plot(k, a01, 'r-', label='Exponential Smoothing', linewidth=1.5)
title = ax.set_title('alpha=0.02')
ax.set_xlabel('Time Step k')
ax.set_ylabel('Value')
ax.legend(loc='upper right')
ax.grid(True, alpha=0.3)
ax.set_ylim(-2.5, 3.0)

def update(i):
    alpha = alphas[i]
    line_es.set_ydata(exp_smooth(obs, alpha))
    title.set_text(f'alpha={alpha:.2f}')
    return line_es, title

ani = animation.FuncAnimation(fig, update, frames=len(alphas), interval=150, blit=False)
writer = animation.PillowWriter(fps=6)
ani.save('images/滤波02-图1.gif', writer=writer)
plt.close(fig)

import csv
with open('images/exponential_smoothing.csv', 'w', newline='') as f:
    w = csv.writer(f)
    w.writerow(['k', 'true', 'obs', 'a01', 'a03', 'a07'])
    for i in range(200):
        w.writerow([i, true[i], obs[i], a01[i], a03[i], a07[i]])

演化与局限

与移动平均相比,指数平滑在两个维度上有了实质进步:权重分配更合理(近期数据更重要),计算和存储更经济(只需一个标量状态)。但两者共享同一个根本局限:它们都假设信号在局部近似恒定,只能做单变量滤波,无法处理具有内部动力学的多维系统。当我们需要估计的不仅是当前位置,还有速度、加速度等多个互相耦合的状态分量时,这两个方法都无能为力。

要突破这个瓶颈,需要从两个方向入手。第一个方向是在频域中利用信号和噪声的统计特性来设计全局最优滤波器——这是维纳滤波,下一篇的主题。第二个方向是用状态空间模型描述系统内部动力学,实现递推的多维估计——这是卡尔曼滤波,第四篇的主题。

参考文献

[1] arXiv:2410.21184 — Shannon-like Interpolation with Spectral Priors and Weighted Hilbert Spaces.

Category
Tagcloud
Junck Kalman Tool Science Moving Average Exponential Smoothing UKF Nonlinear Filtering FckZhiHu Wiener Filter Code GIS Probability Code Generation Bayesian Story Architecture Non-Gaussian Nonlinear Hackintosh Hadoop Matplotlib OSX-KVM Optimal Estimation History Lens Cellular Automata Translate Unscented Transform Discuss Sequential Monte Carlo Bayesian Estimation Photo OpenWebUI Time Series Windows AI PVE Computability State Space Algorithm QGIS Kalman Filter Photography CUDA Signal Processing Pyenv RX590 PHD C Radio Communicate Prerequisites Ubuntu Sigma Point Simulation LLM Tools Geology Ollama Visualization GlumPy Programming University Scholar Particle Filter ChromeBook Hack RTL-SDR Kivy Guide Qwen3 Camera Math EKF Turing LlamaFactory AMD Jacobian Book Data Game Ventoy Hardware VirtualMachine Mac Frequency Domain Memory Computer GPT-OSS Learning Linux Filtering ML Python Life NumPy Mathematical Modeling Poem Microscope