@[toc]
在这里插入图片描述

EWMA、加权平均与一次低通滤波的对比与选型

三者解决的共同问题

三者都用于把“抖动/噪声较大”的观测序列变成更稳定的信号,从而更适合做展示、阈值判断、控制调节等。其共同代价是:越平滑,越容易产生滞后(变化被延迟反映)。(维基百科)


用“权重形状”理解原理与异同点

加权平均

对一组样本按权重求和(一次性计算),不要求时间连续,也不内置“上一时刻状态”的概念:
$$
[
y=\frac{\sum_{i=0}^{N-1} w_i x_i}{\sum_{i=0}^{N-1} w_i}
]
$$

  • 权重 (w_i) 可任意设计(线性、分段、业务权重等)
  • 需要拿到整组样本(或至少拿到一个窗口内的样本)

移动平均/加权移动平均(FIR 思路)

在固定窗口内做平均或加权平均,窗口之外权重为 0,因此是典型 FIR(有限冲激响应):冲激响应在有限长度后严格为 0。(维基百科)
“移动平均滤波”是最典型的 FIR 例子之一,用于降低随机噪声。(analog.com)

FIR 的通用差分方程

长度为 (M) 的因果 FIR 滤波器可用“有限卷积”表示:
$$
[
y[n]=\sum_{k=0}^{M-1} b_k,x[n-k]
]
$$
其中 $({b_0,\dots,b_{M-1}})$ 就是 FIR 系数(也等同于有限冲激响应 $(h[k])$)。

EWMA(指数加权移动平均)

EWMA 采用递推更新,只保留一个状态值,用指数衰减方式“记住历史”:
$$
[
S_t=\alpha x_t+(1-\alpha)S_{t-1}
]
$$
NIST 的“简单指数平滑(Single Exponential Smoothing)”给出了同类递推形式,并直接把平滑序列称为 EWMA。(NIST)

一次低通滤波(First-order Low-pass)

在离散实现中,一次低通常写为:
$$
[
y_t=y_{t-1}+\alpha(x_t-y_{t-1})
\Rightarrow y_t=\alpha x_t+(1-\alpha)y_{t-1}
]
$$
该差分形式与 EWMA 在数学上等价,只是命名与参数语义更偏“滤波/控制”。一些工程资料也直接将该类指数滤波称为 EWMA/指数平滑,并指出其是“一阶滞后/一阶惯性”的离散等价。(gregstanleyandassociates.com)


核心差异总结

维度 加权平均 (加权)移动平均 FIR EWMA 一次低通
结构 一次性求和 有限窗口卷积 递推 + 反馈 递推 + 反馈
记忆 仅样本集合 严格只看最近 N 个 指数衰减,无“硬截断” 与 EWMA 等价
存储 需要样本集合 需保存 N 个样本 仅 1 个状态 仅 1 个状态
更新开销 O(N) 通常 O(N) O(1) O(1)
响应/滞后 取决于权重 窗口越大越滞后 (\alpha) 越小越滞后 (\alpha)/(\tau) 越大越滞后
稳定性 非滤波器概念 FIR 天然稳定 IIR 取决于系数 IIR 取决于系数
  • FIR 的“有限冲激响应/有限记忆”特征:窗口外影响严格为 0。(维基百科)
  • IIR 的“无限冲激响应”来自对历史输出的依赖(反馈),冲激响应理论上不会在有限时间完全变为 0。(维基百科)
  • FIR 通常具有更直观的稳定性特征(讲义中给出 FIR 稳定性结论)。(ETH Zürich)

优劣势与使用场景

加权平均

优势

  • 权重可完全按业务语义设计(例如对不同来源、不同质量样本赋权)
  • 输出解释直观:就是“按权重的平均”

劣势

  • 流式场景需要缓存样本或维护窗口,无法天然 O(1) 在线更新
  • 需要明确“平均对象是什么”(一批样本、一个窗口、一个周期)

适用场景

  • 分数融合、指标合成、一次性评估(离线/批处理)
  • 明确知道要综合的样本集合(例如一次请求内多个子结果合并)

加权移动平均(FIR)

优势

  • “只看最近 N 个”的语义清晰,窗口之外完全遗忘
  • 对某些 FIR 结构可获得线性相位等性质(在信号处理里常见)

劣势

  • 需要保存 N 个样本;N 大时内存与计算成本明显
  • 对突变同样会滞后,且窗口越大滞后越明显

适用场景

  • 需要严格窗口定义(最近 60 秒、最近 100 次)
  • 对滤波器性质(如线性相位)有明确要求的 DSP 场景
  • 更新频率较低或窗口较小的在线平滑

EWMA

优势

  • 只维护一个状态,单次更新 O(1),非常适合热路径/实时路径
  • 对噪声抑制强,且“近期样本权重更大”符合很多趋势判断需求(指数衰减)

劣势

  • 没有“硬窗口边界”,旧数据影响只会渐近变小而非突然归零
  • 平滑引入滞后;(\alpha) 选得过小会导致对变化过迟钝

适用场景

  • 压力/拥塞/忙碌度趋势(队列长度、重试率、丢包率、负载)
  • 动态控制:用平滑后的信号调节并发度、批量大小、速率
  • 指标展示:吞吐、延迟、利用率等需要更稳定的曲线(指数平滑常被视为低通去高频噪声)。(维基百科)

一次低通滤波(与 EWMA 等价)

优势

  • 与 EWMA 同样是 O(1) 状态、O(1) 更新
  • 参数可以用“时间常数 (\tau)”表达,便于在采样间隔变化时保持语义一致(按真实时间衰减)

劣势

  • 与 EWMA 相同:必然滞后
  • 若采样间隔变化但仍固定 $(\alpha)$,滤波语义会漂移(需要时间归一化)

适用场景

  • 传感器信号、控制量平滑(工程上常以“一阶惯性/一阶低通”描述)
  • 采样周期不恒定的事件驱动更新(用 $(\tau) + (\Delta t)$ 动态算系数)

示例 C 代码

1) 加权平均(一次性计算)

1
2
3
4
5
6
7
8
9
10
11
12
13
#include <stddef.h>

double weighted_average(const double *x, const double *w, size_t n) {
double num = 0.0;
double den = 0.0;

for (size_t i = 0; i < n; i++) {
num += w[i] * x[i];
den += w[i];
}

return (den == 0.0) ? 0.0 : (num / den);
}

2) 加权移动平均(FIR,线性权重示例)

  • 最新样本权重最大,越旧权重越小
  • 需要环形缓冲区保存窗口内样本
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
#include <stddef.h>

typedef struct {
double *buf;
size_t cap; // window size
size_t idx; // next write position
size_t len; // current filled length
} wma_t;

void wma_push(wma_t *w, double x) {
w->buf[w->idx] = x;
w->idx = (w->idx + 1) % w->cap;
if (w->len < w->cap) w->len++;
}

double wma_eval_linear(const wma_t *w) {
if (w->len == 0) return 0.0;

double num = 0.0, den = 0.0;

// newest -> oldest
for (size_t k = 0; k < w->len; k++) {
size_t pos = (w->idx + w->cap - 1 - k) % w->cap;
double weight = (double)(w->len - k); // len, len-1, ..., 1
num += weight * w->buf[pos];
den += weight;
}
return num / den;
}

3) EWMA(递推,固定 $(\alpha)$)

NIST 的简单指数平滑属于同类递推结构。(NIST)

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
typedef struct {
double s;
int initialized;
} ewma_t;

double ewma_update(ewma_t *e, double x, double alpha) {
// alpha in (0, 1]
if (!e->initialized) {
e->s = x;
e->initialized = 1;
return e->s;
}
e->s = alpha * x + (1.0 - alpha) * e->s;
return e->s;
}

4) 一次低通(时间常数 $(\tau)$ 版本,采样间隔可变)

该写法常用于把“滤波强度”用时间常数表达,并按 $(\Delta t)$ 做指数衰减;工程资料也将其视为离散的一阶滞后/指数滤波。(gregstanleyandassociates.com)

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
#include <math.h>
#include <stdint.h>

typedef struct {
double y;
uint64_t last_ts; // timestamp, same unit as tau
int initialized;
} lp1_t;

// now_ts 与 tau 使用同一时间单位(例如都用 ns 或都用 ms)
// 编译时若使用 GCC/Clang,可能需要链接 -lm
double lowpass1_update(lp1_t *f, double x, uint64_t now_ts, double tau) {
if (!f->initialized) {
f->y = x;
f->last_ts = now_ts;
f->initialized = 1;
return f->y;
}

uint64_t dt_u = now_ts - f->last_ts;
double dt = (double)dt_u;

// alpha = 1 - exp(-dt/tau)
double alpha = 1.0 - exp(-dt / tau);

f->y = alpha * x + (1.0 - alpha) * f->y;
f->last_ts = now_ts;
return f->y;
}

选型速记

  • 必须严格只看最近 N 个样本:选(加权)移动平均(FIR),窗口语义明确。(维基百科)
  • 必须 O(1) 在线更新,且只需要趋势:选 EWMA / 一次低通(本质等价)。(NIST)
  • 采样间隔不稳定但仍想保持“按时间”衰减语义:选“一次低通的 (\tau)+(\Delta t)”动态系数版本。(gregstanleyandassociates.com)