Экспоненциальный фильтр (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$ отсчётов.