指数移動平均 (Exponential Moving Average; EMA)
指数移動平均(EMA)は、時系列データを平滑化する代表的な手法の一つです。現在の観測値と1ステップ前の平滑化された値に重み付けをして足し合わせることで、新しい平滑化値を計算します。
\[ y_t = (1 - \alpha) y_{t-1} + \alpha x_t \]ここで、
- \(y_t\) : 時刻 \(t\) におけるEMAの値
- \(x_t\) : 時刻 \(t\) における元の観測値
- \(\alpha\) : 平滑化係数(または平滑化率、\(0 < \alpha \le 1\) )
この式は、最近のデータに大きな重みを置きつつ、古いデータも完全に切り捨てずに考慮するという特徴があります。元の記事の式は \(\beta\) を使っていますが、一般的に \(\alpha\) が使われることが多いので、ここでは \(\alpha\) に統一します。元の記事の \(\beta\) は、ここでいう \(1-\alpha\) に相当します。
周波数特性:ゲイン特性と位相特性
フィルタの周波数特性は、入力信号の周波数成分がフィルタによってどのように変化するかを示します。これは、フィルタの伝達関数 \(G(s)\) を用いて分析されます。
上記のEMAの式をZ変換(離散時間システムの解析に用いられる)すると、伝達関数 \(G(z)\) は次のようになります。
\[ Y(z) = (1 - \alpha) z^{-1} Y(z) + \alpha X(z) \]\[ G(z) = \frac{Y(z)}{X(z)} = \frac{\alpha}{1 - (1 - \alpha) z^{-1}} \]ここで、\(z = e^{j\omega T}\) (\(T\) はサンプリング周期、ここでは \(T=1\) と仮定)とすると、周波数応答 \(G(j\omega)\) が得られます。
\[ G(j\omega) = \frac{\alpha}{1 - (1 - \alpha) e^{-j\omega}} \]この周波数応答から、ゲイン特性 \(|G(j\omega)|\) と位相特性 \(\angle G(j\omega)\) を導出できます。
ゲイン特性
\[ |G(j\omega)| = \frac{\alpha}{\sqrt{1 - 2(1 - \alpha) \cos \omega + (1 - \alpha)^2}} \]
この式は \(\omega=0\) で \(|G(j0)| = \alpha / \alpha = 1\) となり、直流成分は減衰なく通過することが分かります。一方 \(\omega=\pi\) (ナイキスト周波数、\(T=1\) のときサンプリング周波数の半分)では \(|G(j\pi)| = \alpha / (2-\alpha)\) となり、\(\alpha\) が小さいほどこの値は0に近づく(強い平滑化)、\(\alpha\) が1に近いほど1に近づく(ほぼ素通し)ことが式からも確認できます。
-3dB遮断角周波数の導出
ゲイン特性が \(1/\sqrt{2}\) (-3dB点)まで低下する角周波数 \(\omega_c\) を、ゲインの2乗を\(1/2\) と置いて解析的に求めます。
\[ \frac{\alpha^2}{1 - 2(1-\alpha)\cos\omega_c + (1-\alpha)^2} = \frac{1}{2} \]分母を払って \(\cos\omega_c\) について整理すると、
\[ \cos\omega_c = \frac{1 + (1-\alpha)^2 - 2\alpha^2}{2(1-\alpha)} \]を得ます。さらに \(\omega\) が小さいとき \(\cos\omega \approx 1-\omega^2/2\) という近似を上式に適用すると、
\[ \omega_c \approx \frac{\alpha}{\sqrt{1-\alpha}} \]という簡潔な近似式が導かれます。これを実際にPythonで数値計算した結果と突き合わせると次の通りで、\(\alpha=0.1\) のような小さい値では近似誤差が0.05%程度に収まる一方、\(\alpha\) が大きくなるにつれ小角近似が崩れて誤差が拡大することが分かります。
| \(\alpha\) | \(\omega_c\) (解析解, rad/sample) | 小角近似 \(\alpha/\sqrt{1-\alpha}\) | 近似誤差 |
|---|---|---|---|
| 0.1 | 0.10546 | 0.10541 | 0.05% |
| 0.3 | 0.36052 | 0.35857 | 0.54% |
| 0.5 | 0.72273 | 0.70711 | 2.16% |
| 0.7 | 1.38642 | 1.27802 | 7.82% |
| 0.9 | 3.14159(=π) | 2.84605 | 9.41% |
エッジケース: \(\alpha=0.9\) の解析解が \(\pi\) (ナイキスト周波数そのもの)になっている点に注意してください。これは近似の問題ではなく、\(\alpha\) がある閾値を超えると通過帯域がナイキスト周波数まで達し、表現可能な周波数範囲内に-3dB点が存在しなくなることを意味します。実際、\(\omega=\pi\) でのゲインを計算すると \(|G(j\pi)| = 0.9/(2-0.9) = 0.818\) であり、\(1/\sqrt{2}=0.707\) を下回っていません。境界となる\(\alpha\) は \(|G(j\pi)| = 1/\sqrt2\) を解いて \(\alpha = 2 - \sqrt{2} \approx 0.5858\) と求まり、これより大きい\(\alpha\) では「-3dB遮断周波数」という概念自体がナイキスト周波数内に収まらなくなります。フィルタ設計時にこの点を見落とすと、意図した遮断特性が得られていると誤解する恐れがあります。
位相特性
\[ \angle G(j\omega) = \arctan\left(\frac{(1 - \alpha) \sin \omega}{1 - (1 - \alpha) \cos \omega}\right) \]元の記事の式は符号が逆になっていますが、これは\(\arctan(Y/X)\) の計算において\(X\) と\(Y\) の符号の組み合わせを考慮する必要があるためです。一般的な定義では上記のとおりです。

考察(実際に計算した数値、\(\omega=\pi/8, \pi/4, \pi/2\) における値):
| \(\alpha\) | ゲイン@\(\pi/8\) | 位相@\(\pi/8\) | ゲイン@\(\pi/4\) | 位相@\(\pi/4\) | ゲイン@\(\pi/2\) | 位相@\(\pi/2\) |
|---|---|---|---|---|---|---|
| 0.1 | 0.2608 | 63.93° | 0.1364 | 60.26° | 0.0743 | 41.99° |
| 0.3 | 0.6767 | 37.17° | 0.4242 | 44.42° | 0.2458 | 34.99° |
| 0.5 | 0.8756 | 19.58° | 0.6786 | 28.68° | 0.4472 | 26.57° |
| 0.7 | 0.9564 | 9.02° | 0.8579 | 15.07° | 0.6705 | 16.70° |
| 0.9 | 0.9907 | 2.41° | 0.9657 | 4.35° | 0.8955 | 5.71° |
- \(\alpha\) が小さい場合(例: \(\alpha=0.1\) ):フィルタの周波数帯域が狭くなり、より強い平滑化効果が得られます(\(\omega=\pi/8\) でゲイン0.26まで減衰)。しかし、位相遅れが大きくなります(同じ点で63.93°の遅れ)。
- \(\alpha\) が大きい場合(例: \(\alpha=0.9\) ):平滑化能力は低下しますが(同じ点でゲイン0.99とほぼ素通し)、位相遅れは小さくなります(2.41°)。
これは、平滑化の度合いと応答速度(位相遅れ)の間にトレードオフがあることを示しています。なお、位相特性のグラフが \(\omega=\pi\) で必ず0°に収束するのは、\(\sin\pi=0\) より分子が恒等的に0になるためで、全ての\(\alpha\) に共通する構造的な性質です。
時定数によるもう一つの見方
EMAフィルタの伝達関数の極は \(z=1-\alpha\) にあり、これはステップ応答が指数関数的に \((1-\alpha)^n\) で減衰することを意味します。連続時間の一次遅れ系との対応から、サンプル単位の時定数 \(\tau\) は
\[ \tau = -\frac{1}{\ln(1-\alpha)} \]で与えられます。計算すると、\(\alpha=0.1\) で \(\tau=9.49\) サンプル、\(\alpha=0.5\) で \(\tau=1.44\) サンプル、\(\alpha=0.9\) で \(\tau=0.43\) サンプルとなり、\(\alpha\) を大きくするほど応答が速くなる(=時定数が短くなる)ことが定量的に裏付けられます。実務上は、サンプリング周波数と欲しい応答速度から \(\tau\) を先に決め、\(\alpha = 1 - e^{-1/\tau}\) で逆算する設計手順がよく使われます。
プログラム
以下のPythonコードは、EMAフィルタのゲイン特性・位相特性・-3dB遮断角周波数を計算し、プロットするものです(実際にこのコードを実行して本記事の図を生成しています)。
import numpy as np
import matplotlib.pyplot as plt
# 平滑化係数αのリスト(代表値。0.2刻みで9本描くと重なって見づらいため5本に絞る)
ALPHAS = [0.1, 0.3, 0.5, 0.7, 0.9]
NUM_POINTS = 1000 # プロット点数
def get_gain(alpha, omega):
"""EMAフィルタのゲインを計算する"""
denominator = np.sqrt(1 - 2 * (1 - alpha) * np.cos(omega) + (1 - alpha)**2)
return alpha / denominator
def get_phase(alpha, omega):
"""EMAフィルタの位相を計算する (ラジアン)"""
numerator = (1 - alpha) * np.sin(omega)
denominator = 1 - (1 - alpha) * np.cos(omega)
return np.arctan2(numerator, denominator) # atan2で正しい象限の角度を得る
def cutoff_analytic(alpha):
"""-3dB遮断角周波数の解析解 cos(wc) = (1+(1-a)^2-2a^2) / (2(1-a))"""
a = 1 - alpha
cos_wc = (1 + a**2 - 2 * alpha**2) / (2 * a)
cos_wc = np.clip(cos_wc, -1, 1) # alphaが大きいとwcがpiを超えるため飽和させる
return np.arccos(cos_wc)
omega = np.linspace(1e-6, np.pi, NUM_POINTS) # 0からπ(ナイキスト周波数)まで
# 各alphaについてomega=pi/8, pi/4, pi/2での具体値と-3dB遮断角周波数を出力
for alpha in ALPHAS:
for name, w in [("pi/8", np.pi/8), ("pi/4", np.pi/4), ("pi/2", np.pi/2)]:
g = get_gain(alpha, w)
p = np.degrees(get_phase(alpha, w))
print(f"alpha={alpha}, omega={name}: gain={g:.4f}, phase={p:.2f}deg")
print(f"alpha={alpha}: omega_c={cutoff_analytic(alpha):.5f} rad/sample")
# ゲイン特性のプロット
plt.figure(figsize=(8, 5))
plt.xlabel('Angular frequency (rad/sample)')
plt.ylabel('Gain')
plt.title('EMA Filter Gain Characteristics')
for alpha in ALPHAS:
plt.plot(omega, get_gain(alpha, omega), label=f"alpha={alpha}")
plt.axhline(1/np.sqrt(2), linestyle='--', label='-3dB')
plt.legend()
plt.grid(True)
plt.tight_layout()
plt.savefig('Gain.png')
# 位相特性のプロット
plt.figure(figsize=(8, 5))
plt.xlabel('Angular frequency (rad/sample)')
plt.ylabel('Phase (degrees)')
plt.title('EMA Filter Phase Characteristics')
for alpha in ALPHAS:
plt.plot(omega, np.degrees(get_phase(alpha, omega)), label=f"alpha={alpha}")
plt.legend()
plt.grid(True)
plt.tight_layout()
plt.savefig('Phase.png')
実行結果(抜粋)は以下の通りです。
alpha=0.1, omega=pi/8: gain=0.2608, phase=63.93deg
alpha=0.1, omega=pi/4: gain=0.1364, phase=60.26deg
alpha=0.1, omega=pi/2: gain=0.0743, phase=41.99deg
alpha=0.1: omega_c=0.10546 rad/sample
alpha=0.5, omega=pi/8: gain=0.8756, phase=19.58deg
alpha=0.5, omega=pi/4: gain=0.6786, phase=28.68deg
alpha=0.5, omega=pi/2: gain=0.4472, phase=26.57deg
alpha=0.5: omega_c=0.72273 rad/sample
alpha=0.9, omega=pi/8: gain=0.9907, phase=2.41deg
alpha=0.9, omega=pi/4: gain=0.9657, phase=4.35deg
alpha=0.9, omega=pi/2: gain=0.8955, phase=5.71deg
alpha=0.9: omega_c=3.14159 rad/sample
数値は前節の表と完全に一致しており、解析的に導出した式が実装と整合していることを確認できます。
EMAと他のフィルタの比較
EMAフィルタは実装が簡単で計算コストが低い一方、他のフィルタと比較すると異なる特性を持ちます。
| フィルタ | 遮断特性 | 計算コスト | パラメータ数 | リアルタイム性 |
|---|---|---|---|---|
| EMA | なだらか | \(O(1)\) | 1(\(\alpha\) ) | 優秀 |
| 移動平均 | なだらか | \(O(N)\) | 1(窓幅\(N\) ) | 優秀 |
| バターワース | 急峻(次数依存) | \(O(N)\) | 2(次数、遮断周波数) | 良好 |
| FIR/IIR | 設計次第 | \(O(N)\) | 多数 | FIR:良好, IIR:優秀 |
EMAは1次のIIRフィルタとして見ることができ、パラメータ\(\alpha\) のみで制御できる点が実務上の大きな利点です。より急峻な遮断特性が必要な場合は バターワースフィルタ を、位相遅れを最小化したい場合は ウィーナーフィルタ を検討してください。
scipy.signalを用いたEMAの実装
Pythonのscipy.signalモジュールを使うと、EMAをIIRフィルタとして実装できます。
import numpy as np
from scipy import signal
def ema_scipy(x, alpha):
"""scipy.signalを用いたEMA実装"""
# EMA: y[n] = alpha * x[n] + (1 - alpha) * y[n-1]
# IIRフィルタの伝達関数: H(z) = alpha / (1 - (1-alpha) * z^{-1})
b = [alpha] # 分子係数
a = [1, -(1 - alpha)] # 分母係数
return signal.lfilter(b, a, x)
# 使用例:5Hzのサイン波にノイズを加えた信号を平滑化する
np.random.seed(42)
t = np.linspace(0, 1, 1000) # fs=1000 (相当)、1秒分
clean = np.sin(2 * np.pi * 5 * t)
noise = 0.5 * np.random.randn(len(t))
x = clean + noise
for alpha in [0.1, 0.5, 0.9]:
y = ema_scipy(x, alpha)
resid = y[50:] - clean[50:] # 過渡応答が収まった後の残差ノイズ
reduction = (1 - resid.std() / noise[50:].std()) * 100
print(f"alpha={alpha}: output_std={y[50:].std():.4f}, resid_std={resid.std():.4f}, noise_reduction={reduction:.1f}%")
実行結果は以下の通りです(入力信号の標準偏差は0.8562、うち純粋なサイン波成分0.7068、ノイズ成分0.4894)。
alpha=0.1: output_std=0.6931, resid_std=0.2083, noise_reduction=57.5%
alpha=0.5: output_std=0.7631, resid_std=0.2804, noise_reduction=42.8%
alpha=0.9: output_std=0.8367, resid_std=0.4425, noise_reduction=9.7%
\(\alpha=0.1\) では残差ノイズの標準偏差が0.2083まで下がり、元のノイズ(0.4894)に対して57.5%のノイズ低減が得られています。一方 \(\alpha=0.9\) ではほぼ入力をそのまま通しているため低減率は9.7%にとどまります。これは前節で導出したゲイン特性(5Hz、\(T=1/1000\) s換算で \(\omega = 2\pi \times 5/1000 \approx 0.0314\) rad/sampleにおけるゲインが \(\alpha=0.1\) で0.958、\(\alpha=0.9\) で0.9999)と整合しており、信号成分をほぼ減衰させずにノイズだけを落とせているかどうかがノイズ低減率の差として現れています。
以下の図は、この入力信号と各\(\alpha\) でのEMA出力を時間領域で重ねたものです。\(\alpha=0.1\) (薄い青)は滑らかですが波形のピークに遅れて追従し、\(\alpha=0.9\) (濃い青)はノイズをほぼそのまま通してしまう一方で遅れがほとんどないことが視覚的に確認できます。

この実装は自前のループ実装よりも高速で、scipy.signalの他のフィルタ関数(butter、firwin等)と統一的なインターフェースで扱えます。
最新研究動向:ニューラルネットの重みEMAという応用
信号処理のローパスフィルタとしてのEMAは古典的な手法ですが、同じ再帰的指数平滑化の数式は近年、機械学習モデルの学習過程にも応用されています。深層学習では、勾配降下法で更新される重み \(\theta_t\) に対して同じ漸化式を適用し、学習中の重みを平滑化した「EMAモデル」を推論に使う手法が広く使われています。
\[ \bar\theta_t = (1-\alpha)\bar\theta_{t-1} + \alpha\theta_t \]機械学習分野では減衰率を \(1-\alpha\) 側に着目して \(\beta\) と書くことが多く、\(\beta=1-\alpha\) に相当します。
Morales-Brotons、Vogels、Hendrikxによる論文 “Exponential Moving Average of Weights in Deep Learning: Dynamics and Benefits”(Transactions on Machine Learning Research, 2024, arXiv:2411.18704 )は、この重みEMAの学習ダイナミクスを体系的に解析しています。同論文は、重みEMAが学習初期から良好な性能を示すこと、SGDと比べて学習率の減衰をより緩やかにできること(平滑化自体がノイズを抑制する暗黙的正則化として働くため)、さらにラベルノイズへの頑健性・予測の一貫性・キャリブレーション・転移学習性能の向上につながることを報告しています。重要な指摘として、EMAで得られる解は最終イテレーションの重みそのものとは異なる、独立した最適化経路の産物であるとしています。
また拡散モデルの学習では、Karrasらの “Analyzing and Improving the Training Dynamics of Diffusion Models”( arXiv:2312.02696 )が、学習後に事後的にEMAの減衰長を調整できる手法を提案し、ImageNet-512での画像生成品質(FID)を当時の最高記録である2.41から1.81まで改善しています。本記事で導出したゲイン・位相特性やカットオフ周波数の考え方は、時系列信号のローパスフィルタとしてだけでなく、こうした「学習軌跡という時系列」を平滑化する際のパラメータ選択(\(\alpha\) が小さいほど強く平滑化されるが過去の情報に強く依存する、というトレードオフ)を理解する上でも共通の枠組みとして役立ちます。
おすすめ書籍
ディジタルフィルタと FFT の原理を基礎から丁寧に解説した定番の入門書です。本記事で扱った処理の理論的背景を体系的に学べます。
※ 上記は Amazon アソシエイトのリンクです。
関連記事
- 移動平均フィルタの種類と比較 - 単純移動平均(SMA)、加重移動平均(WMA)、EMAの特性比較とPython実装を解説しています。
- 高速フーリエ変換(FFT)の仕組みとPython実装 - EMAの周波数特性を理解する基盤となるDFT/FFTのアルゴリズムと実践的な周波数解析を解説しています。
- カルマンフィルタの理論とPython実装 - EMAの確率的拡張であるカルマンフィルタの理論と実装を解説しています。
- 信号処理におけるフィルタリング手法の基礎 - カルマンフィルタ、EKF、UKF、粒子フィルタの概要を体系的に解説しています。
- ローパスフィルタの設計と比較:移動平均・バターワース・チェビシェフ - EMAを含む各種ローパスフィルタの周波数応答・群遅延・ステップ応答を比較しています。
- カルマンスムーザ(RTS Smoother)の理論とPython実装 - EMAの発展形であるカルマンフィルタを拡張し、全時刻の観測データを使って推定精度を向上させるスムーザを解説しています。
- Matplotlib実践Tips:論文品質のグラフを作る - 周波数特性のグラフをさらに高品質に仕上げるための設定やカラーマップの選択方法を紹介しています。
- 窓関数とパワースペクトル密度(PSD)の理論とPython実装 - FFTにおける窓関数の数学的背景とPSD推定のWelch法を解説しています。
- 時系列データの異常検知:統計的手法からカルマンフィルタまで - EMAを使った異常検知手法とカルマンフィルタベースの手法を比較しています。
- ノッチフィルタの設計とPython実装 - EMAがローパス特性を持つのに対し、特定周波数のみを除去するノッチフィルタの設計を解説しています。
- バターワースフィルタの設計とPython実装 - EMAと異なり、通過帯域が最大限平坦な周波数特性を持つバターワースフィルタの設計を解説しています。
- FIRフィルタとIIRフィルタの比較 - EMA(IIRフィルタの一種)の位置づけを、FIRフィルタとの比較から理解できます。
- ウィーナーフィルタの理論とPython実装 - 統計的最適性に基づくフィルタ設計。EMAとは異なるアプローチでノイズ除去を行います。
- ウェーブレット変換の基礎とPython実装 - 時間-周波数解析により、EMAでは対応しにくい非定常信号のフィルタリングを解説しています。
- Unscented Kalman Filter(UKF)の理論とPython実装 - EMAの確率的拡張であるカルマンフィルタを、さらに非線形システムに対応させたUKFを解説しています。
- サビツキー・ゴーレイフィルタの理論とPython実装 - EMAと異なり、多項式フィッティングにより信号の形状を保存しながら平滑化するフィルタを解説しています。
- 適応フィルタ(LMS/RLS)の理論とPython実装 - EMAが固定パラメータのフィルタであるのに対し、環境に適応してパラメータを自動調整する適応フィルタを解説しています。
- 時間周波数解析の選び方ハブ - EMAなど時間領域フィルタの周波数応答を出発点に、FFT・STFT・Wavelet・Hilbert変換による時間周波数解析の使い分けを整理した統合ハブです。
- Prometheusメトリクスの異常検知:EWMA適応閾値とカルマンフィルタをPythonで比較 - 本記事のEMAを、サーバ監視メトリクスの異常検知の適応的しきい値として応用し、カルマンフィルタと定量比較しています。
参考
関連ツール
- DevToolBox - 開発者向け無料ツール集 - JSON整形、正規表現テスターなど85種類以上の開発者向けツール
- CalcBox - 暮らしの計算ツール - 統計計算、複利計算など61種類以上の計算ツール