M5.2 15 分鐘閱讀 2026年9月

M5.2 追一隻自己也在跑的狗:移動目標、EMA 與 Stop-Gradient

本篇重用M1.1從鞋子猜身高:Conditional Expectation·M3.0醉漢一小時後在哪:Random Walk 到 Brownian Motion

一隻不肯停的狗

公園裡一隻狗在跑。你戴著一個定位手環的接收器,每秒收到它一個位置——但手環很便宜,每個讀數都有大約 3 公尺的隨機誤差。你想知道狗現在在哪,好走過去接它。

最直接的做法:相信最新的那個讀數。結果是你每秒都被 3 公尺的雜訊甩來甩去,方向一直改。第二種做法:把過去十秒的讀數平均。雜訊被壓下去了,但狗跑得快——十秒前它在二十公尺外,你的平均永遠指向它已經離開的地方,你一直追過頭。

有沒有一種介於兩者之間的做法?它要「記得」多久?請先用直覺回答:如果狗跑得更快,你該記得更久還是更短?如果雜訊更大呢?寫下你有多確定。

課堂提問Q1

翻成數學。「狗的真實位置」與「讀數」各是什麼?「記得多久」可以用一個什麼樣的規則寫出來,讓它不需要真的存十秒的資料?

先想一想,再展開看整理後的答案

課堂上會先把真實位置寫成 μt\mu_t(一條會動的曲線),讀數寫成 xt=μt+εtx_t=\mu_t+\varepsilon_tεt\varepsilon_t 是均值 0、標準差 σ3\sigma\approx3 公尺、彼此獨立的雜訊。我們想要的估計 mtm_t 要同時滿足兩件矛盾的事:接近 μt\mu_t(跟得緊)、又不被 εt\varepsilon_t 甩動(雜訊小)。

「記得多久」若用「存最近 kk 個讀數取平均」來寫,要存 kk 個數、每秒重算。有一個更簡潔的規則,只存一個數:

mt=βmt1+(1β)xt,β[0,1).m_t=\beta\,m_{t-1}+(1-\beta)\,x_t,\qquad\beta\in[0,1).

每秒把「上一刻的估計」與「這一刻的讀數」按 β:(1β)\beta:(1-\beta) 混合。β=0\beta=0 是「只信最新讀數」,β1\beta\to1 是「幾乎不更新」。這叫 exponential moving average(EMA)。它「記得多久」由 β\beta 一個數決定——下一節會把這個「多久」算出來。

先抓住這個畫面:一桶會漏的水

mtm_t 想成一桶水的水位。每秒倒進 (1β)(1-\beta) 桶新水(今天的讀數 xtx_t),同時舊水只留下 β\beta 的比例。一週前倒進去的水,現在只剩 β7×86400\beta^{7\times86400}——早就漏光了;一秒前的還剩 β\beta。所以水位是過去所有讀數的加權平均,權重按時間往回幾何遞減:越近的越重,越遠的越輕,沒有一個硬的截止點。

β\beta 控制漏得多快。β=0.9\beta=0.9 時,十秒前的水只剩 0.91035%0.9^{10}\approx35\%,二十秒前剩 12%12\%——「有效記憶」大約十秒。β=0.99\beta=0.99 時有效記憶約一百秒。這比「存最近 kk 個」好在兩件事:只要一個數的儲存空間,而且沒有「第 kk 個算、第 k+1k+1 個不算」那種突然的切換。

寫成數學:一個係數,兩個代價

把遞迴展開(代入 mt1m_{t-1},再代入 mt2m_{t-2},…):

mt=(1β)k=0βkxtk,(1β)k0βk=1.m_t=(1-\beta)\sum_{k=0}^{\infty}\beta^k\,x_{t-k},\qquad(1-\beta)\sum_{k\ge0}\beta^k=1 .

權重和為 1,所以它是一個合法的加權平均。有效記憶長度取權重的平均延遲:kk(1β)βk=β/(1β)1/(1β)\sum_k k\,(1-\beta)\beta^k=\beta/(1-\beta)\approx1/(1-\beta)

現在把兩個代價算出來。代入 xt=μt+εtx_t=\mu_t+\varepsilon_t,EMA 是線性的,所以 mt=(1β)kβkμtk對真實位置的追蹤+(1β)kβkεtk雜訊m_t=\underbrace{(1-\beta)\sum_k\beta^k\mu_{t-k}}_{\text{對真實位置的追蹤}}+\underbrace{(1-\beta)\sum_k\beta^k\varepsilon_{t-k}}_{\text{雜訊}}

雜訊的代價。 第二項是獨立零均值雜訊的加權和,變異數是權重平方和乘 σ2\sigma^2M3.0 的「變異數相加」):

Var(雜訊項)=σ2(1β)2kβ2k=σ2(1β)21β2=σ21β1+β.\mathrm{Var}(\text{雜訊項})=\sigma^2(1-\beta)^2\sum_k\beta^{2k}=\sigma^2\,\frac{(1-\beta)^2}{1-\beta^2}=\sigma^2\,\frac{1-\beta}{1+\beta}.

β=0.9\beta=0.9:雜訊標準差變成 σ0.1/1.90.23σ\sigma\sqrt{0.1/1.9}\approx0.23\sigma,3 公尺壓到 0.7 公尺。β\beta 越接近 1 壓得越多。

滯後的代價。 若狗以等速 vv 跑,μtk=μtvk\mu_{t-k}=\mu_t-vk,第一項變成 μtv(1β)kkβk=μtvβ1β\mu_t-v\,(1-\beta)\sum_k k\beta^k=\mu_t-v\,\frac{\beta}{1-\beta}EMA 永遠指向狗「有效記憶長度」之前的位置,落後 vβ/(1β)v\beta/(1-\beta)β=0.9\beta=0.9、狗每秒跑 2 公尺:落後 18 公尺。

兩個代價往相反方向動:

σ21β1+ββ 變小vs(vβ1β)2β 變大.\underbrace{\sigma^2\frac{1-\beta}{1+\beta}}_{\beta\uparrow\ \text{變小}}\qquad\text{vs}\qquad\underbrace{\Big(v\frac{\beta}{1-\beta}\Big)^2}_{\beta\uparrow\ \text{變大}} .

β\beta 是「跟多緊」與「多平滑」之間唯一的旋鈕。 狗跑得快(vv 大)就該把 β\beta 調小、記得短;雜訊大(σ\sigma 大)就該調大、記得長。最佳的 β\beta 讓兩項加起來最小——它有 closed form,但更重要的是它vvσ\sigma 的比值決定,不是一個放諸四海皆準的常數。

展開細節有偏的起點、Adam 的修正、與 Polyak averaging

遞迴從 m₀ = 0 開始時,m_t = (1−β)Σ_{k<t} βᵏ x_{t−k} 的權重和是 1 − βᵗ < 1——前面幾步的估計被系統性地拉向 0。Adam [3] 的「bias correction」就是把 m_t 除以 (1 − βᵗ),讓權重和回到 1;t 大了以後這個修正自動消失。

另一個常見的用法是把 EMA 套在一串會收斂的估計上(Polyak & Juditsky [2]):如果 x_t 本身是某個演算法的第 t 步迭代、它在真值附近抖動,那 EMA 把抖動壓掉、留下更穩的答案——這時「目標會動」的問題反而是好事:目標最終不動了,滯後也就消失。實務上把 β 設成 0.999 甚至更接近 1 的「權重平均」就是這個用法。

一個更危險的情境:追的是自己的影子

上面的狗有一個好性質:它的位置 μt\mu_t 不會因為你怎麼估而改變。現在換一個情境。

你在訓練一個估計器 fθf_\theta,它的訓練目標是由它自己的輸出定義的——例如「fθ(今天)f_\theta(\text{今天}) 應該接近 fθ(明天)f_\theta(\text{明天}) 的某個變換」。這種「用自己的預測當作自己的目標」叫 bootstrap。強化學習裡的 Q-learning(Q(s,a)Q(s,a) 的目標是 r+γmaxQ(s,)r+\gamma\max Q(s',\cdot))、自監督學習裡「兩個視角的表徵要一致」的方法(BYOL [4])都是這種結構。

它有一個致命的平凡解:fθf_\theta 輸出常數。常數對常數,目標與預測完全一致,loss 為零,但什麼都沒學到。狗的比喻裡,這相當於狗的位置是由你的估計定義的——你站著不動,狗就「在你腳下」。

兩個技術一起避開這個坑:

  1. Stop-gradient。 計算目標時把它當成常數,不讓梯度流過去:L=fθ(a)sg[f(b)]2\mathcal L=\|f_\theta(a)-\mathrm{sg}[f(b)]\|^2。這樣「把目標移過來」這條捷徑被切斷了,只剩「把預測移過去」。
  2. 慢速目標(EMA target)。 目標不用當前的 θ\theta,用它的 EMA θˉt=βθˉt1+(1β)θt\bar\theta_t=\beta\bar\theta_{t-1}+(1-\beta)\theta_tβ\beta 很接近 1(0.99–0.999)。目標網路是一個「落後、平滑」的自己——就是這一篇的 EMA,但這裡滯後是想要的:它讓目標在幾百步內幾乎不動,把一個「追自己」的問題變回「追一隻跑得很慢的狗」。DQN [5] 用的「target network 每隔 CC 步複製一次」是同一個想法的硬截止版。

兩者的分工:stop-gradient 決定梯度往哪個方向流(只更新預測端),EMA 決定目標動多快。少了前者,最速下降會直接把目標拉向預測;少了後者,目標每步都跳、訓練震盪。

回到公園:直覺對了,數字現在有了

回答起點問題。狗跑得快 → β\beta調小(記得短,落後 vβ/(1β)v\beta/(1-\beta) 才不會太大);雜訊大 → β\beta調大(雜訊標準差是 σ(1β)/(1+β)\sigma\sqrt{(1-\beta)/(1+\beta)})。兩者衝突時,最佳 β\betav/σv/\sigma 決定:狗每秒 2 公尺、雜訊 3 公尺時,把總誤差 σ21β1+β+(vβ1β)2\sigma^2\frac{1-\beta}{1+\beta}+\big(v\frac{\beta}{1-\beta}\big)^2β\beta 掃一遍,最小值落在 β0.35\beta\approx0.35 附近(落後約 1.1 公尺、雜訊約 2.1 公尺;β=0.5\beta=0.5 則是落後 2 公尺、雜訊 1.7 公尺,總誤差差不多);狗若停下來(v=0v=0),最佳 β1\beta\to1

這個判斷依賴的假設:狗的速度大致穩定(等速假設;急轉彎時任何 β\beta 都會在轉彎處落後一段)、雜訊獨立且零均值(若手環有系統性偏差,EMA 會忠實地保留它)、以及目標不受估計影響。最後一點失效時——目標由估計者定義——就要把 stop-gradient 與慢速目標拿出來。

回到公園:跑一次,讓狗急轉彎一次,然後追自己的影子一次

import numpy as np
rng = np.random.default_rng(0)
T, v, sigma = 200, 2.0, 3.0
mu = v * np.arange(T)                                   # 狗等速直線
x  = mu + sigma * rng.standard_normal(T)                # 帶雜訊的讀數
def ema(x, beta):
    m = np.empty_like(x); m[0] = x[0]
    for t in range(1, T): m[t] = beta*m[t-1] + (1-beta)*x[t]
    return m
for beta in [0.0, 0.5, 0.9]:
    m = ema(x, beta); err = m[50:] - mu[50:]
    print(beta, err.mean().round(1), err.std().round(2),     # 實測 滯後 / 雜訊
          (-v*beta/(1-beta)), (sigma*np.sqrt((1-beta)/(1+beta))).round(2))   # 理論 滯後 / 雜訊
# 破壞 1:狗在 t=100 急轉彎(速度反向)
mu2 = np.where(np.arange(T) < 100, mu, 2*mu[100] - mu)
m = ema(mu2 + sigma*rng.standard_normal(T), 0.9); print(np.abs(m - mu2)[100:130].max().round(1))   # 轉彎後最大偏差約 18 公尺、再花約 10 步追回
# 破壞 2:追自己的影子——目標 = 自己的輸出,沒有 stop-gradient
theta = 5.0
for _ in range(100): target = theta; theta -= 0.1 * 2*(theta - target)   # 梯度為零,theta 永遠不動:平凡解
print(theta)

前三行:β=0.9\beta=0.9 時實測落後 17.9-17.9 公尺、雜訊 0.630.63 公尺,與理論值 vβ/(1β)=18v\beta/(1-\beta)=18σ0.1/1.9=0.69\sigma\sqrt{0.1/1.9}=0.69 吻合;β=0.5\beta=0.5 落後 2、雜訊 1.7。急轉彎:β=0.9\beta=0.9 的 EMA 在轉彎後的最大偏差約 18 公尺——轉彎前它落後 18 公尺、轉彎那一刻反而「超前」18 公尺,然後要花約 1/(1β)=101/(1-\beta)=10 步才追回來;「等速」的假設破了,公式的滯後項在那幾秒失效。最後那個「追自己的影子」:目標就是自己,梯度恆為零,θ\theta 停在 5——loss 是零,什麼也沒學。

先消化一下

想一想

你要在手機上顯示「目前的網速」。網速本身每秒都在變、量測也有雜訊,而且使用者受不了數字一秒跳一次。你該用:

想一想

狗的定位手環有一個 +2 公尺的固定偏差(每個讀數都往東偏 2 公尺)。EMA 的估計會:

想一想

一個 bootstrap 式的訓練把 EMA 目標的 β\beta 從 0.99 改成 0.5。最可能發生的是:

參考文獻

  1. Brown, R. G. Smoothing, Forecasting and Prediction of Discrete Time Series. Prentice-Hall, 1963.(exponential smoothing 的系統性整理;滯後與平滑的取捨。)
  2. Polyak, B. T., Juditsky, A. B. Acceleration of Stochastic Approximation by Averaging. SIAM Journal on Control and Optimization 30(4), 1992.(對迭代序列做平均可以壓掉抖動——展開框裡的用法。)
  3. Kingma, D. P., Ba, J. Adam: A Method for Stochastic Optimization. ICLR 2015.(EMA 的 bias correction mt/(1βt)m_t/(1-\beta^t)。)
  4. Grill, J.-B. et al. Bootstrap Your Own Latent: A New Approach to Self-Supervised Learning. NeurIPS 2020.(EMA 目標網路 + stop-gradient 避開平凡解的代表性例子。)
  5. Mnih, V. et al. Human-level Control through Deep Reinforcement Learning. Nature 518, 2015.(target network:每隔 CC 步複製一次的「硬截止」版慢速目標。)