1906 年無記憶連環案:Markov 鏈的接龍追兇#
聖彼得堡科學院,一位脾氣火爆的數學家為了反駁主教,數了兩萬個字母。他證明了一件事:忘記過去,也能預言未來。這樁文字案,成了 Google、金融與 AI 的祖傳兇器。
| 欄位 | 內容 |
|---|---|
| 發生時間 | 1906 年首篇論文,1913 年以 Pushkin 長詩實證,跨度七年的文字追兇 |
| 地點 | 聖彼得堡科學院;文本現場為 Pushkin 長詩 Eugene Onegin 前兩萬字母 |
| 報案人 | Pavel Nekrasov 與主教派哲學家,宣稱大數法則需要意志自由與獨立性 |
| 偵探 | Andrey Markov(1856–1922),機率論硬漢,與 Lyapunov、Chebyshev 同門 |
| 兇器 | 無記憶性 \(P(X_{n+1} \\mid X_n, \\dots) = P(X_{n+1} \\mid X_n)\) ,轉移矩陣 \(P\) 與平穩分佈 \(\\pi\) |
| 結論 | 相依序列仍有大數法則與中央極限定理,字母接龍服從鏈式法則,隨機過程理論開張 |
案發現場#
1906 年的俄國數學界,表面是分析與數論的天下,暗地裡是哲學官司。
莫斯科的 Nekrasov 宣稱:大數法則之所以成立,是因為人類意志自由,每次試驗互相獨立。若事件相依,平均值不會收斂,上帝與自由意志才有容身之處。這套說法披著數學外衣,行神學之實。
Markov 大怒。他是無神論者,脾氣剛烈,曾因抗議沙皇被開除教籍也要硬剛到底。他決定用數學打臉:構造一個處處相依、但大數法則依然成立的反例。
案發現場於是從賭場搬進了文學。Markov 選了 Pushkin 的 Eugene Onegin,逐字母標記元音與輔音,數了兩萬個。這是人文學科第一次被當成隨機實驗室。
他發現的現象很微妙。元音之後出現輔音的機率,與輔音之後出現元音的機率並不相等,但都穩定。字母有記憶,但記憶只有一格:下一個字母只跟當前字母有關,跟更早的歷史無關。
這就是無記憶性。過去兩萬個字母的恩怨,一格之後一筆勾銷。
偵查過程#
Markov 的偵查分三步:定義兇器、追蹤腳印、找到老巢。
第一步:定義無記憶鏈#
設狀態序列為 \(X_0, X_1, \\dots\) ,狀態空間有限。Markov 要求
右端記為 \(p_{ij}\) ,與 \(n\) 無關時稱為齊次鏈。所有 \(p_{ij}\) 排成轉移矩陣 \(P\) ,每行和為一,即
\(n\) 步轉移由矩陣次方給出, \(P^n\) 的 \((i,j)\) 元即為 \(n\) 步後由 \(i\) 到 \(j\) 的機率。這是 Chapman–Kolmogorov 方程的離散版。
舉一個最小的案子:只有元音 \(V\) 與輔音 \(C\) 兩態。Markov 實測的轉移頻率大略為
| 由上一字母 | 下一為元音 \(V\) | 下一為輔音 \(C\) |
|---|---|---|
| 上一為元音 \(V\) | \(0.13\) | \(0.87\) |
| 上一為輔音 \(C\) | \(0.25\) | \(0.75\) |
注意每行和為一。元音之後多半接輔音,輔音之後仍多半接輔音,但機率不同。這點差異就是相依的證據,也是鏈式結構的指紋。
第二步:大數法則依然成立#
Nekrasov 的賭注是:相依就沒有收斂。Markov 證明:只要鏈不可約非週期,收斂照樣來。
設鏈有平穩分佈 \(\\pi\) ,滿足 \(\\pi = \\pi P\) 且分量和為一。則無論初值為何, \(n\) 步分佈趨向 \(\\pi\) ,即
其中 \(1\) 為全一列向量。這意味著長期頻率穩定,時間平均收斂到空間平均。Markov 進一步證明了相依情形的大數法則與中央極限定理雛形。
以上表的 \(V\) – \(C\) 鏈為例,解 \(\\pi = \\pi P\) 得
與 Onegin 全文元音比例約兩成吻合。短期相依,長期穩定,這正是 Markov 要的反例:記憶存在,收斂亦存在,意志自由不必出場。
第三步:分類與追蹤老巢#
Markov 還給出了偵探手冊:狀態如何分類。
| 概念 | 定義速寫 | 偵查意義 |
|---|---|---|
| 不可約 | 任兩態可互達 | 不會有孤島,長期頻率唯一 |
| 週期 | 返回步數的最大公約數 | 週期大於一時分佈震盪,需取平均 |
| 常返 | 以機率一返回 | 俄羅斯輪盤終會轉回來 |
| 平穩分佈 \(\\pi\) | \(\\pi = \\pi P\) | 長期老巢,頻率的歸宿 |
| 細緻平衡 | \(\\pi_i p_{ij} = \\pi_j p_{ji}\) | 可逆鏈的對稱指紋,MCMC 基石 |
Pushkin 文本的鏈不可約非週期,故平穩分佈存在唯一。Markov 數兩萬字母,就是為了讓經驗頻率逼近 \(\\pi\) ,用數據給哲學家定罪。
結案報告#
1913 年,Markov 發表了 Onegin 字母統計,Nekrasov 的哲學辯護當場失效。相依序列的大數法則白紙黑字,意志自由不必為收斂負責。
但結案只是開端。這條鏈的後代遍佈天下。
Kolmogorov 在 1931 年把離散鏈推廣為連續時間過程與前向後向方程。Chapman–Kolmogorov 成了隨機過程的通用語法。PageRank 把網頁排名寫成巨大 Markov 鏈的平穩分佈 \(\\pi\) ,MCMC 把貝氏推論寫成可逆鏈的遍歷平均,隱 Markov 模型把語音辨識寫成帶噪聲的鏈。
Pushkin 若泉下有知,會很意外:他的情詩成了隨機過程的驗屍報告,還是 AI 的族譜第一頁。
Markov 本人晚年體弱,仍堅持上課。他 1922 年去世,沒見過電腦。但他留下的無記憶假設,成了所有序列模型的起點:忘記,是為了更好地預言。
證據與工具#
| 證物 | 數學形式 | 用途 |
|---|---|---|
| 無記憶性 | \(P(X_{n+1} \\mid X_n, \\dots) = P(X_{n+1} \\mid X_n)\) | 鏈的定義,忘記歷史的合法性 |
| 轉移矩陣 | \(P = [p_{ij}]\) ,行和為一 | 一步行動圖, \(n\) 步為 \(P^n\) |
| Chapman–Kolmogorov | \(p^{(m+n)}_{ij} = \\sum_k p^{(m)}_{ik} p^{(n)}_{kj}\) | 多步分解,矩陣乘法的機率意義 |
| 平穩分佈 | \(\\pi = \\pi P\) | 長期頻率,PageRank 與 MCMC 的獵物 |
| Onegin 字母表 | \(V\) – \(C\) 二態矩陣如上 | 第一個真實數據驗證,大數法則反例 |
辦案備註:數值實驗可模擬 \(V\) – \(C\) 鏈十萬步,統計元音頻率,應逼近 \(\\pi_V \\approx 0.21\) 。改變初值重跑,極限不變,親驗遍歷性。對應程式見 _code 目錄中 1906 年 Markov 平穩分佈程式,內含轉移矩陣次方收斂與直方圖對照。
補充:程式實作#
對應程式#
理論呼應#
本文核心為無記憶性 \(P(X_{n+1} \mid X_n, \dots) = P(X_{n+1} \mid X_n)\) 與轉移矩陣 \(P\) 。 平穩分佈滿足 \(\pi = \pi P\) ,核心為長期頻率的歸宿。 程式以十萬步模擬驗證經驗頻率逼近 \(\pi\) ,且與初值無關。 多步轉移滿足 \(p^{(m+n)}_{ij} = \sum_k p^{(m)}_{ik} p^{(n)}_{kj}\) ,正是矩陣次方收斂的機率意義。
執行方式#
python3 _code/1906-markov_stationary.py
實測輸出#
P=[[0.7, 0.3], [0.4, 0.6]] N=100000
analytic_pi=[0.571429, 0.428571]
empirical_freq=[0.568510, 0.431490]
abs_err=[0.002919, 0.002919] max_err=0.002919 (tol 0.01)
VERIFY 1906-markov_stationary PASS freq0=0.5685 pi0=0.5714 max_err=0.00292
讀者實驗#
試將 \(P_{01}\) 由 \(0.3\) 改為 \(0.5\) ,觀察平穩分佈是否移向新的解析解。
完整程式如下:
# 對應 wiki:1906 年 Markov 鏈 / 平穩分佈(Chapman-Kolmogorov、遍歷性)
# 說明:二態鏈 P=[[0.7,0.3],[0.4,0.6]];解析平穩分佈 pi 滿足 pi=piP,
# 得 pi0=0.4/(0.3+0.4)=4/7, pi1=3/7;以 10 萬步模擬頻率對照,誤差 < 0.01。
# 僅用 numpy,固定種子,不畫圖只印數字。
import numpy as np
SEED = 1906
P = np.array([[0.7, 0.3], [0.4, 0.6]], dtype=np.float64)
N = 100_000
# 解析平穩分佈(留數法):pi0 = P10/(P01+P10)
pi0 = P[1, 0] / (P[0, 1] + P[1, 0])
pi = np.array([pi0, 1.0 - pi0])
rng = np.random.default_rng(SEED)
U = rng.random(N)
state = 0
c0 = 0
for u in U:
# 依目前狀態決定下一狀態
p_stay = P[state, state]
if u < p_stay:
pass # 留在原態
else:
state = 1 - state
if state == 0:
c0 += 1
freq = np.array([c0 / N, 1.0 - c0 / N])
abs_err = np.abs(freq - pi)
max_err = float(abs_err.max())
print(f"P={P.tolist()} N={N}")
print(f"analytic_pi=[{pi[0]:.6f}, {pi[1]:.6f}]")
print(f"empirical_freq=[{freq[0]:.6f}, {freq[1]:.6f}]")
print(f"abs_err=[{abs_err[0]:.6f}, {abs_err[1]:.6f}] max_err={max_err:.6f} (tol 0.01)")
assert max_err < 0.01, f"Markov stationary max_err {max_err} >= 0.01"
print(f"VERIFY 1906-markov_stationary PASS freq0={freq[0]:.4f} pi0={pi[0]:.4f} max_err={max_err:.5f}")