頓悟的跡象

在講點正經的之前,先講點不正經的。這週受到 AI 供應鏈教父 女色 的影響,股票跌出了新高度。反正長期持有就是這樣,資產慢慢累積,振幅也會跟著變大。這次在 11/5 累積跌 8.26%,單日跌幅也達到最多位數;現在受惠於台幣貶值,已收復不少失土。據說台幣會跌一陣子,所以數據會有所美化。

這有讓我頓悟什麼嗎?沒有。所以我來講講頓悟有什麼跡象。

假設我們有一個會計算 mod 97 的 AI model,包括普通的 embedding 層、Transformer 層、和 FFN / MLP 層。顧名思義,它學會 2+96 後就會輸出 1 ,因為 mod (2+96)=mod (98) = 1。

當我們把所有 0~96 和 0~96 的組合都送進去測試,會有 9,409 種組合,如果每題都答對,當然 model 就算學會了。但是這個 model 是在哪一步開始頓悟加法的真諦?或是純粹就是靠死背呢?我很好奇,更發現其他人已經有大量的研究了。我就算晉身此界也是師弟,當不了師叔。

想像一個有 97 格的環。被加數站在 96,再向前走 2 格代表加數,結果會回到 1。對加法而言,把數字 a 放到角度 2πka/97,加 b 就像再轉 2πkb/97。這裡的 k 是你選擇繞環的頻率。若寫成複數波 exp(2πika/97),輸入平移 b 格,頻域只需乘上一個相位因子;也就是訊號處理課教的循環平移。這樣就解釋了加法。

我訓練了一個 1 層 transformer 來做 (a+b) mod 97,訓練集佔 50%。大約 5,000 步後測試準確率從約 49% 升到 100%,也就是 grokking。示意程式由老馬 (斯克) 的 Grok Bot 提供,叫 Grok 研究 Grokking 正好。

import numpy as np
def embedding_spectrum(W_E):
# 沿 token 軸做長度 97 的 DFT
F = np.fft.fft(W_E, axis=0) # 97 x d
power = np.linalg.norm(F, axis=1) # 每個頻率在模型維度上的能量
return power[1:49] # 頻率 k 與 97-k 成對,只看 k = 1..48
def gini(x):
x = np.sort(np.abs(x))
n = len(x)
return (2 * np.arange(1, n + 1) - n - 1) @ x / (n * x.sum())
spec = embedding_spectrum(W_E)
print("Gini:", gini(spec))
print("能量最大的 5 個頻率:", np.argsort(spec)[::-1][:5] + 1)

頓悟後,我們可以看到下圖左有五根紅棒特別高。這意味著 model 學到五組頻率後 (由隨機種子決定),大概就對加法之道運作自如了。此處是用 Gini 法評估,把 embedding 各頻率的能量排出來,看是少數頻率吃掉大部分能量,還是大家平均分。下圖右在 4200 步附近頓悟,有那種突然醒過來的感覺。

此案例中,Gini 只有 0.33,不算高。因為 48 個頻率裡只有 5 個突出,其餘不是零,底噪偏大。如果想看「稀疏度」,也可以只算峰值佔總能量的比例。其他實證的手法比較厚工,包括:

  • Excluded loss:把模型認定的關鍵 Fourier 成分從 **logits(輸出分數)**拿掉,再用訓練題算損失。若移掉後訓練損失變高,表示這些成分確實在幫忙。
  • Restricted loss:反過來只保留所選的 Fourier 成分,其他拿掉,看看留下來的部分能不能解題。

以上的說明,對於加減法都適用。那乘法呢?Furuta [4] 等人在加法基底下分析模乘,觀察到許多頻率都參與;後來 Nguyen [5] 用另一組基底重新檢視,發現他的模 113 模型其實有稀疏結構。

這裡要補一個關鍵字:原根(primitive root)。對模 97 而言,5 是一個原根。意思是從 1 開始不斷乘 5、或曰計算 5 的 j 次方,每次再取除以 97 的餘數,會在回到 1 之前,剛好走遍 1~96 每個非零餘數一次。前幾步是:
5^0 mod 97 = 1 → 5^1 = 5 → 5^2 = 25 → 5^3 = 28 → 5^4 = 43 → … → 5^96 mod 97 = 1。

上面那串不是把數字由小排到大,而是把「乘以 5」這個動作排成一個 96 格的環。零不在裡面:零乘多少次都還是零,必須另外處理。因為每個非零數都能寫成某個 5^j mod 97,我們可以反過來把它標成 j;這個 j 就叫 以 5 為底的離散對數(discrete log)。

例如 25 的標號是 2,28 的標號是 3。現在再看乘法:如果 a=5^i、b=5^j,那 a×b mod 97 = 5^(i+j) mod 97。乘法在這個新編號裡變成指數相加(mod 96)。

故這時先把 embedding 的非零數字列按 1,5,25,28,… 重新排列,再做長度 96 的 DFT,就相當於用乘法群的 Fourier 基底看它。Nguyen 在 mod 113 的模型上報告:原本的加法 DFT 看起來很分散;改用離散對數排序後,Gini 從 0.07 左右到 0.58 左右,而且只剩幾個主要頻率。很酷吧!

當然,有道德感的 AI 會吐槽我有邏輯漏洞,所以我必須多講幾句,免得天網把我列為難相處的對象,進而斬殺。

  • 兩種電路都可能解同一題。 Zhong [1] 等人發現模加法可以長出 Clock,也可以長出 Pizza,甚至更複雜的程序。嵌入有規律,不代表你已經知道後面是哪套演算法。
  • 關鍵頻率常是事後找的。 如果先看最終模型再回放中途 checkpoint,就不是一個完全不看答案、事先固定的偵測器;真正要預測新模型時,頻率選擇和門檻須先鎖定 [2]。
  • 稀疏不是必要條件。 某些模型的解法可能分布於更多頻率;單看 Gini 低,不能叫人家「沒開竅」。Gromov [3] 對不同模算術任務提供了可解析的兩層網路解,正好提醒我們別把某一種 Transformer 頻譜當定義。
  • 表示的基底會改變結論。 加法 DFT 不是乘法的萬用眼鏡,原根與離散對數是為了把乘法的結構對準。[4][5]
  • 頻譜分散有很多原因。 在還沒 grok 的模型上看到分散的頻譜,可能是還在背題,也可能只是電路還沒長出來,不能直接讀成「這個模型學不會」。反之,也不能在 training 過程中,只看到一點跡象就猜它頓悟了。

最後,對於好學不倦有始有終的網友們介紹除法。a/b = a * b-1。先找出 b 在模 97 下的乘法逆元 b⁻¹,再計算 a * b-1(mod 97) 即可。由於 97 是質數,除了 0 以外,1 到 96 的每個整數都保證存在唯一的模逆元。 至於找出模逆元的方法有費馬小定理 (Fermat’s Little Theorem)、擴展歐幾里得演算法(Extended Euclidean Algorithm)、查表法 (本人真心推薦,可以減少人腦強化學習後,所造成的崩潰或災難性遺忘)。


[REF]

  1. Zhong, Liu, Tegmark, Andreas (2023). The Clock and the Pizza
  2. anda, Chan, Lieberum, Smith, Steinhardt (2023). Progress measures for grokking via mechanistic interpretability
  3. Gromov (2023). Grokking modular arithmetic
  4. Furuta, Minegishi, Iwasawa, Matsuo (2024). Towards empirical interpretation of internal circuits and properties in grokked transformers on modular polynomials
  5. Nguyen (2026). The Discrete-Log Clock
  6. Grokking: Generalization Beyond Overfitting on Small Algorithmic Datasets — Power et al., 2022

發表留言