跳轉到

2.2 事件與機率#

本節目標#

  • 理解 Kolmogorov 1933 年的機率公理,知道「機率」被定義成什麼
  • 掌握互斥事件的加法法則與一般事件的廣義加法法則
  • 學會用蒙地卡羅模擬驗證機率,並理解估計誤差為什麼與 \(1/\sqrt{N}\) 成正比
  • 用理論+模擬解決經典的「生日問題」

問題:機率到底是什麼?#

上一節我們用「數個數相除」算骰子問題,這招叫古典機率,但它有明顯的天花板:樣本點不等可能(一枚不均勻的銅板)、樣本空間無限(明天幾點下雨)時就沒轍了。17 世紀到 19 世紀,「機率是主觀信念還是長期頻率」吵了兩百多年,各種定義都只在部分場景成立。

真正的出路不是再發明一個「機率的哲學定義」,而是公理化:像幾何學家處理「點、線」一樣,不問機率「本質上是什麼」,只規定它必須滿足哪幾條規則。滿足規則的任何函數都是合法的機率,至於實際場景該怎麼指定,交給應用去解釋。這是 Hilbert 公理化浪潮(1900 年前後)影響下的典型思路。

歷史:1933 年 Kolmogorov 的公理系統#

蘇聯數學家 Andrey Kolmogorov 在 1933 年出版德文小冊子 Grundbegriffe der Wahrscheinlichkeitsrechnung(機率論基礎概念),用測度論的語言給出三條公理。這本不到 60 頁的書讓機率論從「賭徒的算術」變成嚴格的數學分支,現代機率論、隨機過程、乃至金融數學都站在它上面。

設樣本空間 \(S\) ,事件集合對補集與可數聯集封閉,機率 \(P\) 是把事件映射到實數的函數,滿足:

公理一(非負性):對任何事件 \(A\) , \(P(A) \ge 0\) 。

公理二(規範性): \(P(S) = 1\) 。

公理三(可加性):若 \(A_1, A_2, \ldots\) 兩兩互斥( \(A_i \cap A_j = \varnothing\) ),則

$$ P\Big(\bigcup_{i=1}^{\infty} A_i\Big) = \sum_{i=1}^{\infty} P(A_i)

$$ 三條公理簡單得驚人,卻足以推出所有常用規則。注意公理三沒有說「任何事件的機率可加」——只有互斥事件才能直接把機率相加,這是初學者最常踩的地雷。


加法法則#

互斥事件的加法法則#

若 \(A \cap B = \varnothing\) ,由公理三(取前兩項):

$$ P(A \cup B) = P(A) + P(B)

$$ 例:丟一顆骰子,「出現 1 或 6」: \(P = 1/6 + 1/6 = 1/3\) 。

廣義加法法則#

若 \(A\) 、 \(B\) 不互斥,直接相加會把 \(A \cap B\) 那塊算兩次。從文氏圖(或把 \(A \cup B = A \cup (B \cap A^c)\) 拆成兩塊互斥事件)可以推得:

$$ P(A \cup B) = P(A) + P(B) - P(A \cap B)

$$ 三個事件的一般式:

$$ P(A \cup B \cup C) = P(A) + P(B) + P(C) - P(AB) - P(AC) - P(BC) + P(ABC)

$$ (重疊奇數次的多加、偶數次的多減,符號交替,稱為容斥原理。)

驗證上一節的例子: \(P(A \cup B) = \frac{6}{36} + \frac{11}{36} - \frac{2}{36} = \frac{15}{36} \approx 0.4167\) 。


動手:100 萬次模擬驗證加法法則#

理論是推導出來的,但我們也可以「做實驗」:讓電腦重複丟骰子 100 萬次,用事件發生的頻率估計機率。這叫蒙地卡羅模擬(Monte Carlo simulation,名字取自賭城)。

import rstatistics as r

g = r.set_seed(2026)
N = 1_000_000

# 丟兩顆骰子,事件 A:和為 7;事件 B:至少一顆為 6
d1 = g.sample_from(list(range(1, 7)), size=N, replace=True)
d2 = g.sample_from(list(range(1, 7)), size=N, replace=True)

hits_A = sum(1 for a, b in zip(d1, d2) if a + b == 7)
hits_B = sum(1 for a, b in zip(d1, d2) if a == 6 or b == 6)
hits_AB = sum(1 for a, b in zip(d1, d2) if a + b == 7 and (a == 6 or b == 6))
hits_union = sum(1 for a, b in zip(d1, d2) if a + b == 7 or a == 6 or b == 6)

pA, pB, pAB = hits_A / N, hits_B / N, hits_AB / N
print(f"P(A)  模擬 = {pA:.6f}   理論 = {6/36:.6f}")
print(f"P(B)  模擬 = {pB:.6f}   理論 = {11/36:.6f}")
print(f"P(A∩B) 模擬 = {pAB:.6f}   理論 = {2/36:.6f}")
print(f"廣義加法:P(A∪B) 模擬 = {hits_union/N:.6f}")
print(f"          P(A)+P(B)-P(A∩B) = {pA+pB-pAB:.6f}   理論 = {15/36:.6f}")
print(f"估計誤差量級 1/sqrt(N) = {1/N**0.5:.6f}")

輸出:

P(A)  模擬 = 0.166872   理論 = 0.166667
P(B)  模擬 = 0.304891   理論 = 0.305556
P(A∩B) 模擬 = 0.055425   理論 = 0.055556
廣義加法:P(A∪B) 模擬 = 0.416338
          P(A)+P(B)-P(A∩B) = 0.416338   理論 = 0.416667
估計誤差量級 1/sqrt(N) = 0.001000

三個重點:

  1. 模擬與理論吻合到小數點後三位——注意模擬值沒有一個剛好等於理論值,它們會在理論值附近抖動。
  2. P(A)+P(B)-P(A∩B)(用模擬值組合)與直接數聯集的模擬值逐位相同——因為這是同一批樣本算出來的恆等式,廣義加法法則在經驗頻率上就成立。
  3. **估計誤差量級是 \(1/\sqrt{N}\) **: \(N = 10^6\) 時約 \(0.001\) 。想把小數精度多拿一位(誤差除以 10),樣本數要乘 100——這是蒙地卡羅方法的代價,也解釋了為什麼本書模擬都用百萬級的 \(N\) 。誤差 \(\sim 1/\sqrt{N}\) 的嚴格版本是第 6 章的中央極限定理,這裡先當作經驗法則記住。

動手:生日問題#

23 個人的班級裡,「至少兩人同一天生日」的機率有多大?多數人直覺認為要 \(365/2 \approx 183\) 人才過半,答案是 23 人就超過 50%——這個反直覺叫生日問題(birthday problem)。

理論:直接算「至少兩人同天」很雜,改算補事件「全體生日相異」:

$$ P(\text{相異}) = \frac{365}{365} \times \frac{364}{365} \times \cdots \times \frac{365-n+1}{365} = \frac{365!}{(365-n)!\, 365^n}

$$ 逐項相乘的直覺:第一個人隨便;第二個人要避開第一個人的生日( \(364/365\) );第三個人要避開前兩個( \(363/365\) )……這正是 2.3 節乘法法則的預演。於是

$$ P(\text{至少兩人同天}) = 1 - \frac{365!}{(365-n)!\, 365^n}

$$ 模擬:

import rstatistics as r

# 生日問題:n 個人至少兩人同天生日的機率
def birthday_theory(n):
    p_no = 1.0
    for i in range(n):
        p_no *= (365 - i) / 365
    return 1 - p_no

g = r.set_seed(7)
N = 100_000
for n in (23, 30, 50):
    cnt = 0
    for _ in range(N):
        bdays = g.sample_from(list(range(365)), size=n, replace=True)
        if len(set(bdays)) < n:
            cnt += 1
    est = cnt / N
    th = birthday_theory(n)
    print(f"n = {n:2d}: 模擬 = {est:.4f}   理論 = {th:.4f}   差 = {abs(est-th):.4f}")

輸出:

n = 23: 模擬 = 0.5090   理論 = 0.5073   差 = 0.0017
n = 30: 模擬 = 0.7053   理論 = 0.7063   差 = 0.0010
n = 50: 模擬 = 0.9703   理論 = 0.9704   差 = 0.0001

模擬與理論吻合得很好。為什麼 23 人就過半?因為「至少兩人同天」考察的是 \(\binom{23}{2} = 253\) 對人——每一對都是一次「撞生日」的機會,配對數隨人數平方增長,所以機率上漲得比直覺快得多。這個效應有實際用途:密碼學中的生日攻擊靠同樣的數學,大約 \(2^{64}\) 次雜湊就能撞出 128 位元雜湊的碰撞,而不是 \(2^{128}\) 次。


與 R 對照#

set.seed(2026)
N <- 1000000
d1 <- sample(1:6, N, replace = TRUE)
d2 <- sample(1:6, N, replace = TRUE)
A <- d1 + d2 == 7
B <- d1 == 6 | d2 == 6
mean(A); mean(B); mean(A & B); mean(A | B)

輸出(與本節 Python 模擬逐位相同——兩者用同一個與 R 位元等價的亂數流):

[1] 0.166872
[1] 0.304891
[1] 0.055425
[1] 0.416338

生日問題的 R 版:

set.seed(7)
N <- 100000
birthday_sim <- function(n) {
  cnt <- 0
  for (i in 1:N) {
    b <- sample(365, n, replace = TRUE)
    if (length(unique(b)) < n) cnt <- cnt + 1
  }
  cnt / N
}
birthday_th <- function(n) 1 - prod((365:(365-n+1))/365)
sapply(c(23, 30, 50), birthday_sim)
sapply(c(23, 30, 50), birthday_th)

輸出:

[1] 0.50895 0.70531 0.97027
[1] 0.5072972 0.7063162 0.9703736

小結#

  1. Kolmogorov(1933)用三條公理定義機率:非負、 \(P(S)=1\) 、互斥事件可加;機率論從此成為嚴格數學。
  2. 加法法則:互斥時 \(P(A \cup B) = P(A) + P(B)\) ;一般情況要扣掉交集 \(P(A \cup B) = P(A) + P(B) - P(AB)\) 。
  3. 蒙地卡羅模擬用頻率估機率,誤差量級 \(1/\sqrt{N}\) ——精度要一位小數,樣本得百倍。
  4. 生日問題:23 人即過半,關鍵在「配對數」隨人數平方增長。

練習#

  1. 事件 \(P(A) = 0.6\) 、 \(P(B) = 0.5\) ,且 \(P(A \cup B) = 0.8\) 。求 \(P(A \cap B)\) ,並說明 \(A\) 、 \(B\) 是否可能互斥。(提示:互斥時聯集機率最大能到多少?)
  2. 丟兩顆骰子,求「和為 7 或至少一顆為 6」的機率:先理論推導,再模擬 100 萬次驗證。(提示:答案 \(15/36\) 。)
  3. 三個事件 \(A\) 、 \(B\) 、 \(C\) 各自機率 0.3,兩兩交集 0.1,三者交集 0.02。求 \(P(A \cup B \cup C)\) 。(提示:容斥原理。)
  4. 修改生日問題的程式:房間要有幾人才使「至少兩人同天」機率超過 95%?用模擬掃 \(n = 40\) 到 50 找出最小值,再與理論比對。(提示: \(n = 47\) 附近。)
  5. 用模擬畫出誤差隨 \(N\) 的縮小:對 \(N = 10^3, 10^4, 10^5, 10^6\) 各估 \(P({}\) 和為 7 \()\) ,記下與 \(1/6\) 的絕對誤差,驗證 \(N\) 每放大 10 倍誤差約縮小 \(\sqrt{10}\) 倍。(提示:每個 \(N\) 多跑幾組種子看誤差的「典型大小」。)