Экспоненциальный фильтр (EMA)

Урок про лёгкий фильтр с одной строкой памяти — экспоненциальное сглаживание.

Экспоненциальный фильтр (EMA) обновляет оценку как взвешенную смесь нового отсчёта и прошлой оценки: чем больше вес нового, тем быстрее реакция.

Скользящее среднее хранит целое окно. Экспоненциальный фильтр обходится одной переменной — прошлой оценкой — и потому идеален для микроконтроллеров. Он плавно «забывает» старое и подмешивает новое.

Рекуррентная формула

Новая оценка — смесь измерения $x_k$ и прошлой оценки $y_{k-1}$:

$$ y_k = \alpha\,x_k + (1 - \alpha)\,y_{k-1}, \qquad 0 \lt \alpha \le 1 $$

При $\alpha$ близком к 1 фильтр быстрый, но почти не сглаживает; при $\alpha$ близком к 0 — сильно сглаживает, но медленно реагирует.

def ema(xs, alpha):
    y = xs[0]
    out = [round(y, 3)]
    for x in xs[1:]:
        y = alpha * x + (1 - alpha) * y
        out.append(round(y, 3))
    return out

data = [20.1, 22.3, 19.8, 25.0, 21.1, 20.5, 23.2]
print("alpha=0.5:", ema(data, 0.5))
print("alpha=0.2:", ema(data, 0.2))

Вывод:

alpha=0.5: [20.1, 21.2, 20.5, 22.75, 21.925, 21.212, 22.206]
alpha=0.2: [20.1, 20.54, 20.392, 21.314, 21.271, 21.117, 21.533]

Что значит alpha

Вклад отсчёта $n$ шагов назад убывает как $(1-\alpha)^n$ — экспоненциально, отсюда название. Эффективную «глубину памяти» можно грубо оценить как

$$ N_{eff} \approx \frac{1}{\alpha} $$

# вклад прошлых отсчётов при alpha=0.3
alpha = 0.3
for n in range(5):
    weight = alpha * (1 - alpha)**n
    print("отсчёт", n, "назад -> вес", round(weight, 4))
print("эффективная память ~", round(1/alpha, 1), "отсчётов")

Вывод:

отсчёт 0 назад -> вес 0.3
отсчёт 1 назад -> вес 0.21
отсчёт 2 назад -> вес 0.147
отсчёт 3 назад -> вес 0.1029
отсчёт 4 назад -> вес 0.072
эффективная память ~ 3.3 отсчётов

Как работает под капотом

EMA — это цифровой аналог RC-цепочки (резистор-конденсатор), классического аналогового сглаживающего фильтра. Параметр $\alpha$ связан с постоянной времени: чем меньше $\alpha$, тем больше «инерция». Огромный плюс — память в один отсчёт и одно умножение на шаг, поэтому EMA стоит почти в каждом датчике с цифровым выходом. Минус тот же, что у скользящего среднего: против выбросов он слаб, а слишком сильное сглаживание вносит заметную задержку.

Частые ошибки

  • Брать $\alpha$ слишком малым — фильтр отстаёт от реального сигнала.
  • Инициализировать $y_0$ нулём вместо первого измерения — будет долгий «разгон».
  • Считать EMA защитой от выбросов — он лишь сглаживает, но выброс всё равно протекает.

Итог

  • EMA: $y_k = \alpha x_k + (1-\alpha) y_{k-1}$, память в один отсчёт.
  • Большое $\alpha$ — быстро и шумно, малое — гладко и медленно.
  • Эффективная память примерно $1/\alpha$ отсчётов.
Проверьте себя
1. Что произойдёт, если в EMA взять alpha близким к 1?
AФильтр сильно сгладит шум
BФильтр будет быстрым, но почти не сгладит
CФильтр перестанет работать
DПамять станет бесконечной
2. Чему примерно равна эффективная глубина памяти EMA?
Aα
B1/α
Cα²
D√α