跳轉到

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(X_{n+1} = j \\mid X_n = i, \\dots, X_0) = P(X_{n+1} = j \\mid X_n = i) \]

右端記為 \(p_{ij}\) ,與 \(n\) 無關時稱為齊次鏈。所有 \(p_{ij}\) 排成轉移矩陣 \(P\) ,每行和為一,即

\[ \\sum_j p_{ij} = 1 \]

\(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\) ,即

\[ \\lim_{n \\to \\infty} P^n = 1 \\pi^T \]

其中 \(1\) 為全一列向量。這意味著長期頻率穩定,時間平均收斂到空間平均。Markov 進一步證明了相依情形的大數法則與中央極限定理雛形。

以上表的 \(V\) – \(C\) 鏈為例,解 \(\\pi = \\pi P\) 得

\[ \\pi_V \\approx 0.21, \\quad \\pi_C \\approx 0.79 \]

與 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 平穩分佈程式,內含轉移矩陣次方收斂與直方圖對照。

補充:程式實作#

對應程式#

1906-markov_stationary.py

理論呼應#

本文核心為無記憶性 \(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}")