L2.0 17 分鐘閱讀 2026年9月

L2.0 民調只問一千人:小批次與噪聲

本篇重用M1.0兩台體重計:Gaussian 的線性組合·M3.0醉漢一小時後在哪:Random Walk 到 Brownian Motion·M3.1水流加亂流:SDE 是加了噪聲的 ODE

起點:民調只問一千人

全國有兩千萬個選民。民調公司問了一千人,就敢報一個支持率,還敢附上「誤差 ±3%」。

這件事之所以合理,是因為兩件不同的事都成立:一千人的平均沒有系統性的偏差(不會一直高估或一直低估),而且它的誤差有一個算得出來的量級±3%\pm3\% 那個數字就是算出來的)。

訓練一個模型的時候,我們面對的是同一個結構。要算一步梯度,照定義得掃過全部資料:

g=L(θ)=1ni=1n(fθ(xi),yi).g=\nabla L(\theta)=\frac1n\sum_{i=1}^n \nabla \ell\big(f_\theta(x_i),y_i\big).

nn 是一千萬的時候,這一步就要一千萬次前向與反向。而 霧中下山,一步該走多大:梯度下降的一頁 告訴我們,走到有用的地方需要成千上萬步。兩個數字乘起來就走不完了。

只看一個 batch 就算一步梯度,
這樣算出來的方向能用嗎?
如果能用,那我付出的代價是什麼?

課堂提問Q1

把「只問一千人」翻成數學:gBg_B 怎麼寫?它和全批次的 gg 差多少——這個差是偏差還是抖動?如果只是抖動,那多走幾步平均掉不就好了?

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

寫下來。 每一步從 nn 筆裡抽出一個大小 BB 的子集 SS,用它算平均:

gB=1BiS(fθ(xi),yi).g_B=\frac1B\sum_{i\in S}\nabla \ell\big(f_\theta(x_i),y_i\big).

SS 是均勻隨機抽的,每一項的期望都是 gg(每一筆被抽到的機率相同),所以

E[gB]=g.\mathbb E[g_B]=g .

沒有偏差——這是民調那件事的第一半。gBg_B 不是「近似的梯度」,它是梯度的無偏估計

差的是抖動,而且量級可以算。 記單筆梯度的變異數為 σg2=Eig2\sigma_g^2=\mathbb E\lVert\nabla\ell_i-g\rVert^2,那麼(放回抽樣時)

EgBg2=σg2B.\mathbb E\lVert g_B-g\rVert^2=\frac{\sigma_g^2}{B}.

BB 變四倍,抖動變四分之一,誤差的長度只變一半——和民調的 1/N1/\sqrt N 是同一條式子。

「多走幾步平均掉」為什麼不是完整的答案。 如果每一步都站在同一個 θ\theta 上抽,那確實會平均掉。但我們每一步都往前走了,所以下一次抽樣是在一個被上一次的噪聲推歪過的位置上做的。噪聲不是加在讀數上,是加在軌跡上——這正是 醉漢一小時後在哪:Random Walk 到 Brownian Motion 裡「每一步都是新抽的一小步」那個結構。結果是:軌跡會在最小點附近繞著一個有寬度的區域打轉,而那個寬度由 η\etaBB 決定。本篇後半會把它量出來。

一條帶噪聲的下山路

霧中下山,一步該走多大:梯度下降的一頁 的圖像是霧中下山:量出腳下最陡的方向,走一步,重新量。現在把那個「量」換成一個有雜訊的儀器——它平均起來是對的,但每一次的讀數都會歪一點。

於是同一趟下山變成兩件事的疊加:

  • 往下的漂移:來自真正的梯度 gg,每一步大約帶你下降 ηg2\eta\lVert g\rVert^2
  • 隨機的推擠:來自 gBgg_B-g,每一步大約把你推開 ησg/B\eta\sigma_g/\sqrt B,方向隨機。

離最小點還遠的時候,g\lVert g\rVert 很大,漂移壓過推擠,軌跡看起來就像全批次的下山。接近最小點時 g0\lVert g\rVert\to 0,但推擠不會跟著變小——於是你停不下來。這個「漂移 vs 推擠」的分工就是 水流加亂流:SDE 是加了噪聲的 ODE 的 SDE 結構:dx=Ldt+2εdW\mathrm dx=-\nabla L\,\mathrm dt+\sqrt{2\varepsilon}\,\mathrm dW 裡的兩項,而 ε\varepsilon 這裡大約就是 ησg2/B\eta\sigma_g^2/B

抖動有多大,地板有多高

抖動。 實務上抽 batch 幾乎都是不放回(把資料洗牌後切成一段一段),這時上面那條式子多一個有限母體修正:

EgBg2=σg2BnBn1.\mathbb E\lVert g_B-g\rVert^2=\frac{\sigma_g^2}{B}\cdot\frac{n-B}{n-1}.

右邊那個因子在 BnB\ll n 時幾乎是 1,而在 B=nB=n正好是 0——整份資料都抽進來了,gBg_B 就是 gg,沒有任何抖動。這個因子解釋了下一節實測表格的最後一列。

地板。 把「漂移 vs 推擠」寫成一個一維的算式就看得到地板。取最簡單的二次損失 L(w)=λ2(ww)2L(w)=\frac{\lambda}{2}(w-w^\star)^2,每一步

wk+1w=(1ηλ)(wkw)ηξk,E[ξk]=0, E[ξk2]=σg2B.w_{k+1}-w^\star=(1-\eta\lambda)(w_k-w^\star)-\eta\,\xi_k, \qquad \mathbb E[\xi_k]=0,\ \mathbb E[\xi_k^2]=\frac{\sigma_g^2}{B}.

兩邊取平方期望,令它在穩態時不再變化(左右相等),解出

E[(ww)2]=η2σg2/B1(1ηλ)2ησg22λBE[L]Lησg24B.\mathbb E\big[(w_\infty-w^\star)^2\big]=\frac{\eta^2\sigma_g^2/B}{1-(1-\eta\lambda)^2}\approx\frac{\eta\,\sigma_g^2}{2\lambda B} \quad\Longrightarrow\quad \mathbb E[L]-L^\star\approx\frac{\eta\,\sigma_g^2}{4B}.

兩個旋鈕以同一個組合 η/B\eta/B 進來,而 λ\lambda(曲率)被消掉了。

固定學習率的 SGD 不會收斂到最小點,它停在一個高出 LL^\star 大約 η/B\eta/B 那麼多的地板上。

這也立刻給出兩條實務規則。想壓低地板:η\eta 減半,或把 BB 加倍,效果同一個量級。想真的走到最小點:η\eta 隨步數趨近 0(learning-rate decay),這是 Robbins–Monro 條件 ηk=\sum\eta_k=\inftyηk2<\sum\eta_k^2<\infty 的實務版本。

注意「噪聲有幫助」這句話說的是別的事

本篇量的是收斂到哪裡,答案是「η/B\eta/B 越小越靠近最小點」。這很容易被讀成「噪聲純粹是壞事,能消就消」,但那不是完整的圖像。

非凸的問題上,噪聲還有第二個作用:它讓軌跡有機會跳出一個窄的局部谷底。這件事和上面的地板是兩個不同層次的效應——地板講的是「在一個谷底裡繞多遠」,跳出去講的是「在哪個谷底」。同一張地圖從不同地方出發:損失曲面與初始化 會回到後者。

還有一個更常被引用的說法:小 batch 的噪聲有正則化效果,所以泛化較好。這件事的實驗證據隨任務差異很大,而且大多是在「同時調了 η\eta」的情況下量的——這一篇不下這個結論。能穩穩站得住的只有:η/B\eta/B 決定你停在多高的地板上。

回到情境:民調的三個假設,訓練也有

無偏來自「均勻隨機抽」。 民調要隨機撥號,不能只問市話;訓練要每個 epoch 重新洗牌。如果資料檔按類別排好而你順序讀,那前面幾個 batch 全是同一類,E[gB]g\mathbb E[g_B]\ne g——那不是抖動,是偏差,而偏差不會因為多走幾步而平均掉。

誤差 ±3% 來自「知道 σ\sigma」。 訓練這邊的 σg\sigma_g 是可以量的:跑幾個 batch,看梯度之間差多少。量過之後,「BB 該多大」就從猜變成算。

這一切依賴什麼。 一,每一筆資料獨立;同一位使用者的多筆、時間相鄰的多筆會讓有效樣本數遠小於 BB(和 只有三十筆資料:交叉驗證 裡 fold 相依是同一個問題)。二,σg\sigma_g 在訓練過程中不是常數——它通常隨著損失下降而變小,所以地板也會跟著降一點。三,上面的地板公式是在二次損失、固定 η\eta、穩態下算的;真實訓練還沒到穩態時,看到的多半是漂移那一段。

回到情境:把抖動與地板量出來

第一段:無偏與 1/B1/B 共用 toy(n=120n=120、五次多項式特徵),在一個不是最小點的位置上,用 4000 次不放回抽樣估 gBg_B

import numpy as np
from ml_toy import toy_1d

x, y = toy_1d(n=120, seed=0)
V = np.vander(x, 6, increasing=True); V = V/np.abs(V).max(0)
n = len(x)
w = np.linalg.lstsq(V, y, rcond=None)[0] * 0.5      # 一個不在最小點的參考位置
g = (V.T @ (V@w - y))/n                             # 全批次梯度

rng = np.random.default_rng(0)
for B in (1, 4, 16, 60, 120):
    G = np.array([(V[i].T @ (V[i]@w - y[i]))/B
                  for i in (rng.choice(n, B, replace=False) for _ in range(4000))])
    bias = np.linalg.norm(G.mean(0) - g)/np.linalg.norm(g)
    var  = ((G - g)**2).sum(1).mean()
    print(f"  B={B:4d}  平均與全批次的相對差 {bias:.4f}   E‖g_B−g‖² {var:.5f}   ×B = {var*B:.4f}")
  B=   1  平均與全批次的相對差 0.0054   E‖g_B−g‖² 0.22878   ×B = 0.2288
  B=   4  平均與全批次的相對差 0.0303   E‖g_B−g‖² 0.05433   ×B = 0.2173
  B=  16  平均與全批次的相對差 0.0172   E‖g_B−g‖² 0.01269   ×B = 0.2030
  B=  60  平均與全批次的相對差 0.0015   E‖g_B−g‖² 0.00201   ×B = 0.1207
  B= 120  平均與全批次的相對差 0.0000   E‖g_B−g‖² 0.00000   ×B = 0.0000

第一欄印證無偏:4000 次抽樣的平均與全批次梯度只差 0.50.53%3\%,而且那個殘差本身隨抽樣次數縮小,不隨 BB 系統性地變化。B=120B=120 那一列是 0.00000.0000——整份資料都抽進來了。

第三欄印證 1/B1/B0.2290.0540.0130.229\to0.054\to0.013BB 每變四倍就掉四分之一。第四欄把 EgBg2×B\mathbb E\lVert g_B-g\rVert^2\times B 印出來,B16B\le16 時穩定在 0.200.200.230.23——那就是單筆梯度的變異數 σg2\sigma_g^2B=60B=60 掉到 0.120.12B=120B=120 掉到 00,正是上一節那個 (nB)/(n1)(n-B)/(n-1) 因子:B=60B=60 時它是 60/1190.5060/119\approx0.500.229×0.50=0.1150.229\times0.50=0.115,量到 0.1210.121

第二段:地板。 換一組正交化的特徵(條件數 11,這樣 GD 本身很快收斂,看得到的就只有噪聲),從最小點出發跑 6 萬步,取最後 1 萬步的平均損失:

Q = np.linalg.qr(np.vander(x, 4, increasing=True))[0]*np.sqrt(n)   # 條件數 = 1
L = lambda w: ((Q@w - y)**2).mean()
w_opt = np.linalg.lstsq(Q, y, rcond=None)[0]
for eta in (0.02, 0.05, 0.1):
    for B in (1, 4, 16):
        rng = np.random.default_rng(1); w = w_opt.copy(); tail = []
        for t in range(60000):
            idx = rng.choice(n, B, replace=False)
            w -= eta*(Q[idx].T@(Q[idx]@w - y[idx]))/B
            if t >= 50000: tail.append(L(w))
        d = np.mean(tail) - L(w_opt)
        print(f"  η={eta:<5} B={B:<3} 高出最小點 {d:.5f}   除以 η/B = {d/(eta/B):.4f}")
  η=0.02  B=1   高出最小點 0.00138   除以 η/B = 0.0692
  η=0.02  B=4   高出最小點 0.00033   除以 η/B = 0.0660
  η=0.02  B=16  高出最小點 0.00007   除以 η/B = 0.0524
  η=0.05  B=1   高出最小點 0.00385   除以 η/B = 0.0771
  η=0.05  B=4   高出最小點 0.00084   除以 η/B = 0.0673
  η=0.05  B=16  高出最小點 0.00018   除以 η/B = 0.0560
  η=0.10  B=1   高出最小點 0.00913   除以 η/B = 0.0913
  η=0.10  B=16  高出最小點 0.00037   除以 η/B = 0.0596

η/B\eta/B 掃過 80 倍(0.001250.001250.10.1),超出量掃過 130 倍,而兩者的比值只在 0.0520.0520.0910.091 之間動。 這就是上一節那條式子的實測版本:地板 cη/B\approx c\cdot\eta/B,這個問題上 c0.07c\approx0.07

從表裡直接讀出兩個等價:(η,B)=(0.02,1)(\eta,B)=(0.02,1) 的地板 0.001380.00138(0.05,4)(0.05,4)0.000840.00084 差不多;(0.05,1)(0.05,1)0.003850.00385(0.10,4)(0.10,4)0.001780.00178 也差不多。BB 加倍與把 η\eta 減半換到同一個地方——這也是「加大 batch 之後要把學習率一起放大」那條經驗規則的來源。

刻意違反:把最小點換成一個「還沒收斂」的位置。 如果把上面的實驗改回 霧中下山,一步該走多大:梯度下降的一頁 那組沒有正交化的特徵(條件數 1.6×1071.6\times10^7),同樣跑 2 萬步,η=0.2\eta=0.2 的最終損失反而低於 η=0.05\eta=0.050.0470.0470.0830.083)——和地板公式的方向相反。原因不是公式錯了,是那個實驗根本還沒進入穩態:條件數那麼大時,2 萬步還在漂移那一段,η\eta 大的走得遠。

地板公式描述的是穩態;訓練還在往下掉的時候,你看到的是漂移,不是地板。

分辨方法很直接:看損失曲線是不是已經平了。平了才在讀地板,還在掉就是在讀漂移——這兩段要用完全相反的策略(漂移段要大 η\eta,地板段要小 η\eta),而這正是學習率 schedule 存在的理由。

拖 η 與 B,看軌跡與地板

互動 demo:η 與 B 只以 η/B 的組合出現:兩個旋鈕分別掃出一樣的噪聲地板。

先消化一下

想一想

一個團隊把 batch size 從 32 改成 256(八倍),學習率不動,發現訓練損失下降變慢了。最合理的解讀是:

想一想

在第一段實測裡,B=n=120B=n=120 那一列的 EgBg2\mathbb E\lVert g_B-g\rVert^20.000000.00000。這說明:

想一想

一個訓練跑了很久,損失曲線已經平了三萬步。要讓它再往下,下列哪一個最直接

想一想

下列哪一句不對

參考文獻

  1. Robbins, H., Monro, S. A Stochastic Approximation Method. Annals of Mathematical Statistics 1951.(隨機近似的原始論文,ηk=\sum\eta_k=\inftyηk2<\sum\eta_k^2<\infty 這組條件的來源。)
  2. Bottou, L., Curtis, F., Nocedal, J. Optimization Methods for Large-Scale Machine Learning. SIAM Review 2018.(SGD 的收斂分析:固定步長的噪聲地板、遞減步長、batch size 的角色。)
  3. Smith, S., Kindermans, P.-J., Ying, C., Le, Q. Don’t Decay the Learning Rate, Increase the Batch Size. ICLR 2018.(把 η\etaBB 的等價當成實務工具,並在大規模訓練上驗證它的適用範圍。)