滤波算法入门 从移动平均到一维卡尔曼
传感器读数偶尔跳一下,监控曲线锯齿很多,或者一段趋势被单点尖峰打断,都会让人想到“加个滤波”。但平滑后的曲线更顺眼,不代表它离真实状态更近:突变可能被拖慢,真正的异常也可能被抹掉。
本文放在数据分析分类,承接 NumPy 常用统计与数据可视化。先比较四种常见方法,再手算一维卡尔曼的预测与更新。所有示例都只使用当前及过去数据,不把离线看过未来的结果冒充实时滤波。
去尖峰、看趋势,还是估计一个隐藏状态?
窗口方向、初始值、采样间隔与缺失规则。
误差、突变响应和延迟必须一起观察。
进阶可继续阅读位置—速度卡尔曼与协方差,把标量预测扩展为联合状态,检查位置观测怎样通过交叉协方差修正速度,以及变化采样间隔下的过程噪声。
先区分四类方法
设观测序列为 y_t,希望输出更适合当前任务的估计 s_t。滤波器不能凭空知道哪些变化来自噪声,哪些变化是真实信号。
| 方法 | 利用的信息 | 常见用途 | 主要代价 |
|---|---|---|---|
| 移动平均 | 最近 W 个观测等权汇总 | 看局部均值、抑制独立零均值波动 | 滞后,尖峰会影响多个输出 |
| 指数平滑 | 当前观测与上一次估计 | 低状态成本的在线平滑 | 参数影响记忆长度与响应速度 |
| 中值滤波 | 窗口内排序后取中间值 | 对付孤立的极端读数 | 非线性,也可能消除真实短脉冲 |
| 卡尔曼滤波 | 动态模型、观测与误差方差 | 递归估计隐藏状态 | 依赖模型和噪声假设,普通版本不抗任意异常值 |
线性差分滤波可以用 FIR、IIR 的语言描述;SciPy lfilter 官方说明给出了输入输出的差分形式。中值不是线性滤波,不能把同一个线性频率响应直接套给它。本文只实现一维标量例子,不覆盖图像二维滤波和完整频域设计。
flowchart TD
A[当前观测到达] --> B{主要需要什么}
B --> C[窗口均值 看局部平均]
B --> D[指数平滑 递归低状态成本]
B --> E[窗口中值 处理孤立尖峰]
B --> F[状态模型 预测加观测更新]
C --> G[同时检查延迟 边界 与真实事件]
D --> G
E --> G
F --> G
因果移动平均:窗口越大,响应越慢
满窗口时,尾随移动平均定义为:
1 | s_t = (y_t + y_(t−1) + ... + y_(t−W+1)) / W |
若窗口内真实状态恒为 μ,观测噪声独立、均值为零且方差同为 σ²,则平均仍无偏,噪声方差降为 σ²/W。这里的条件很重要:窗口内真实状态改变时,把不同状态平均在一起会引入偏差;噪声相关时,均值方差还要加入协方差项。
对于无噪声线性斜坡 y_t=a t+b,满窗口输出恰为 a(t−(W−1)/2)+b。这说明它相对斜坡落后了 (W−1)/2 个采样间隔,并不是所有信号都可以用一个固定时间移位解释。
本文在序列刚开始、不足 W 项时只平均已有数据,不做零填充。因此启动阶段的分母会变化,不能把整段实现都直接当作固定系数、时不变的 W 点滤波器。
指数平滑:把历史压成一个状态
取 0<α≤1:
1 | s_0 = y_0 |
展开递推,较旧观测的权重按几何级数衰减;初始值的残留权重为 (1−α)^t。α 大时更跟随当前观测,α 小时历史记忆更长。
若真实状态恒定、观测噪声独立同方差,且忽略已衰减的启动影响,稳态噪声方差为:
1 | σ² α² Σ_(j≥0) (1−α)^(2j) = σ² α/(2−α) |
这同样是特定噪声模型下的结论,不是所有时间序列上的效果保证。α=1 时原样输出;本文不接受 α=0,以免把“不再响应新数据”混入默认接口。
中值滤波:去尖峰也可能去掉真实短事件
对最近 W 项排序后取中位数。本文使用奇数 W;序列启动时仍使用已有项,若已有项为偶数,取中间两值的平均。
三点窗口 [0,8,0] 的均值为 8/3,中值为 0;[0,4,4] 的中值为 4。这解释了它为何能抑制一个孤立尖峰,但“尖峰是噪声”必须来自任务判断。如果真实事件只持续一个采样点,同样可能被去掉。
窗口方向也是定义的一部分。本文是尾随窗口;SciPy medfilt还明确规定了零填充边界,不能不对齐方向和边界就逐项比较输出。离线居中窗口可能用到未来样本。
同一组观测怎样手算
使用观测 [0,0,8,0,0,4,4,4,4],人为参考状态为 [0,0,0,0,0,4,4,4,4]。8 是注入的单点干扰,随后是持续阶跃;这只是用于解释行为的合成构造。
| t | 观测 | 参考状态 | 三点均值 | 指数平滑 α=0.5 | 尾随三点中值 |
|---|---|---|---|---|---|
| 0—1 | 0 | 0 | 0 | 0 | 0 |
| 2 | 8 | 0 | 8/3 | 4 | 0 |
| 3 | 0 | 0 | 8/3 | 2 | 0 |
| 4 | 0 | 0 | 8/3 | 1 | 0 |
| 5 | 4 | 4 | 4/3 | 2.5 | 0 |
| 6 | 4 | 4 | 8/3 | 3.25 | 4 |
| 7 | 4 | 4 | 4 | 3.625 | 4 |
| 8 | 4 | 4 | 4 | 3.8125 | 4 |
中值去掉了 t=2 的尖峰,却直到 t=6 才跟上阶跃;均值在 t=5、t=6 分别为 4/3、8/3,也展示了响应滞后。评估应查看完整轨迹,不能只挑一个看起来正确的点。
一维卡尔曼:先写模型,再讨论增益
采用等间隔随机游走和直接观测:
1 | x_t = x_(t−1) + w_t Var(w_t)=Q |
假定过程噪声、观测噪声与初始误差零均值,且在需要的时间和相互关系上不相关;Q≥0,R>0,初始误差方差 P≥0。若初始状态及噪声进一步满足线性高斯条件,递推给出相应的后验均值和方差;仅有二阶矩条件时,不应自动宣称得到了任意分布下的完整后验。
状态空间递归估计的原始依据见 Kalman 1960 论文。本文只推导其标量特例,Q、R 都是方差,单位为观测单位的平方;Q 对应一个采样间隔,不能不加调整就移给另一种采样频率。
1 | 预测均值:m⁻ = m |
为什么是这个 K?用线性修正 m=m⁻+K(y−m⁻),在预测误差与观测噪声不相关的条件下,修正误差方差是 (1−K)²P⁻+K²R。对 K 求导并令其为零,得到上式;二阶导数 2(P⁻+R)>0,因而是唯一最小值。
P⁻ 大意味着预测更不确定,K 较大;R 大意味着观测更不可靠,K 较小。这不是“测量一定比模型可信”或相反。代码用等价的标量 Joseph 形式 (1−K)²P⁻+K²R 更新方差,避免直接相减带来的部分数值问题。
flowchart LR
A[上一时刻均值 m 与方差 P] --> B[预测均值不变 方差加 Q]
B --> C{当前观测缺失吗}
C -- 是 --> D[只保留预测 方差仍增长]
C -- 否 --> E[由预测方差与 R 计算 K]
E --> F[按创新修正均值 更新方差]
D --> A
F --> A
三步卡尔曼怎样核对
在第一条观测之前设置 m=0、P=1,Q=1、R=1,每个时间步都先预测,第一步也先加 Q。依次收到 [1,2,3]:
| 步骤 | 观测 | 预测均值 | P⁻ | K | 更新均值 | 更新方差 |
|---|---|---|---|---|---|---|
| 1 | 1 | 0 | 2 | 2/3 | 2/3 | 2/3 |
| 2 | 2 | 2/3 | 5/3 | 5/8 | 3/2 | 5/8 |
| 3 | 3 | 3/2 | 13/8 | 13/21 | 17/7 | 13/21 |
若某时刻没有观测,仍做 P←P+Q,但不做观测修正。把缺失值填成 0 再更新,相当于凭空告诉滤波器“确实测到了零”,含义完全不同。
完整标准库实现
均值和中值接口要求完整有限观测;卡尔曼额外接受 None 作为缺失标记。NaN、无穷和布尔值均不作为普通数值吞入。所有方法返回的列表用于展示;在线使用时可只保留相应的窗口或状态。
1 | import json |
滑动和实现的均值为 O(N) 时间、O(W) 工作空间;浮点滑动和仍可能随长序列累积舍入误差,必要时定期重算或使用更稳定的流式统计。指数平滑与标量卡尔曼均为 O(N) 时间、O(1) 状态。这里的中值每步排序,时间 O(N W log W)、工作空间 O(W),不是高性能在线中位数数据结构。输入副本和返回列表另占 O(N) 空间。
验证程序 tests/verify-filtering-article.cjs 以独立的有理数窗口与显式历史权重核对 600 组序列的 23,489 个位置;对 220 组带缺失的随机模型,用批量高斯二次型消元核对 985 次递归后验均值和方差,并检查因果前缀、平移、Q=0、确定初态及 23 类非法输入。图中折线与本文实际计算输出一致;这些检查不证明某个真实设备的噪声满足模型假设。
验证程序 tests/verify-filtering-article.cjs 以独立的有理数窗口与显式历史权重核对 600 组序列的 23,489 个位置;对 220 组带缺失的随机模型,用批量高斯二次型消元核对 985 次递归后验均值和方差,并检查因果前缀、平移、Q=0、确定初态及 23 类非法输入。图中折线与本文实际计算输出一致;这些检查不证明某个真实设备的噪声满足模型假设。
选用与失败边界
| 情形 | 应检查什么 |
|---|---|
| 需要真实在线报警 | 当前输出有没有读到未来;平滑后报警是否延后 |
| 看到孤立尖峰 | 是传感器毛刺还是要检测的真实短事件 |
| 卡尔曼跟踪迟缓 | Q、R 的大小与单位是否合理,状态是否真能用随机游走描述 |
| 卡尔曼被异常值拉走 | 普通高斯/二阶矩模型不自动具有鲁棒性 |
| 输入有缺失或不等间隔 | 缺失要跳过观测更新,不同时间间隔需要对应的状态和 Q |
| 与某库的结果不同 | 先对齐窗口方向、填充方式、初始化和缺失规则 |
| 只看平滑后的图 | 同时看原始数据、真实事件和独立验证指标,不能只评“好看” |
SciPy filtfilt采用前后双向处理并处理边界,不能当作不看未来的实时实现。本文的四个实现也不是全部滤波算法:频率选择任务还要分析采样率、目标频带和滤波器设计,状态模型复杂时则要扩展状态和观测方程。
自检:缺失观测为什么不能简单填成 0?
缺失表示没有新测量信息,随机游走模型只推进预测及不确定性;零则是一条有明确数值、会影响增益修正的观测。两者在概率模型中不是同一事件。