Day 25 機率與統計
引言
深度學習中到處都是機率與統計:訓練資料是從某個分佈抽出的樣本,模型的預測常常是機率分佈,損失函式背後也都有機率的詮釋。如果不懂機率,看到「交叉熵」、「最大似然估計」、「常態分佈」這些詞彙時,就只能死記公式,難以真正理解模型在做什麼。這種「公式懂、意思不懂」的情況,會讓你在除錯或設計模型時處處碰壁。
今天要建立三個核心觀念:機率與隨機變數、期望值與變異數、最大似然估計。我們會用具體小數字與 NumPy 把每個概念實際算過一遍,再用「為什麼神經網路要算交叉熵」這個問題,把今天學到的東西和深度學習串起來。這篇不會用複雜的數學推導,而是把每個概念都用具體的小例子展示出來,讓你能直接看到結果、對照公式。
機率與隨機變數
機率(probability)描述事件發生可能性的大小,介於 0 到 1 之間。隨機變數(random variable)則是把事件的結果對應到數字,例如「擲一顆公平骰子出現的點數」就是一個隨機變數 X,X 可以是 1 到 6 的整數,每個值出現的機率都是 1/6。機率看似抽象,其實就是「長期下來發生的頻率」——這個直覺對理解隨機梯度下降等概念特別有幫助。
import numpy as np
rng = np.random.default_rng(42)
# 模擬 10 萬次擲骰子
rolls = rng.integers(1, 7, size=100_000)
# 計算每個點數出現的頻率(用來估計機率)
counts = np.bincount(rolls)[1:] # 索引 0 不使用
probs = counts / rolls.size
print("各點數出現機率(估計值):")
for face, p in enumerate(probs, start=1):
print(f" P(X = {face}) ≈ {p:.4f}")
輸出(會隨模擬略有變動):
各點數出現機率(估計值):
P(X = 1) ≈ 0.1667
P(X = 2) ≈ 0.1662
P(X = 3) ≈ 0.1663
P(X = 4) ≈ 0.1674
P(X = 5) ≈ 0.1663
P(X = 6) ≈ 0.1671
10 萬次模擬後,每個點數出現的頻率都很接近理論值 1/6 ≈ 0.1667。這就是「大數法則」:當樣本數夠大時,頻率會逼近機率。這也是為什麼深度學習需要大量訓練資料——樣本越多,模型學到的分佈就越接近真實世界的分佈。在 Day 30 介紹「批次訓練」時,這個觀念會再延伸為「為什麼每個 batch 的損失會有點雜訊」。
期望值與變異數
期望值(expected value)是隨機變數的「平均水準」,可以想成是無窮多次試驗後的平均結果。變異數(variance)則是描述結果「離期望值多遠」,越小代表結果越集中、越大代表越分散。這兩個統計量是描述任何機率分佈最基本的兩個數字,幾乎所有後續的統計推論都從它們開始。
import numpy as np
faces = np.array([1, 2, 3, 4, 5, 6])
probs = np.array([1/6] * 6)
expected_value = np.sum(faces * probs)
variance = np.sum((faces - expected_value) ** 2 * probs)
std = np.sqrt(variance)
print(f"E[X] = {expected_value:.4f}") # 理論值 3.5
print(f"Var(X) = {variance:.4f}") # 理論值 35/12 ≈ 2.9167
print(f"Std(X) = {std:.4f}")
輸出:
E[X] = 3.5000
Var(X) = 2.9167
Std(X) = 1.7078
期望值公式是 E[X] = Σ xᵢ · P(xᵢ),變異數公式是 Var(X) = Σ (xᵢ - E[X])² · P(xᵢ)。公平骰子的期望值是 3.5、變異數是 35/12 ≈ 2.9167,這些數字在統計推論中很重要:常態分佈的「1 個標準差」就對應到變異數開根號。期望值代表「這個隨機變數長期下來的平均值」,變異數則代表「平均會偏離多遠」,這兩個指標缺一不可。
常見分佈:常態與均勻
現實世界中,最常見的分佈是常態分佈(normal distribution),也就是大家熟悉的「鐘形曲線」。它的形狀由兩個參數決定:平均值 μ 決定中心位置、標準差 σ 決定寬度。NumPy 的 np.random.normal() 可以模擬常態分佈的樣本。常態分佈之所以這麼普遍,部分原因來自「中央極限定理」——許多獨立隨機變數的和,會趨近於常態分佈。
import numpy as np
rng = np.random.default_rng(0)
samples = rng.normal(loc=170, scale=8, size=100_000)
print(f"模擬樣本平均 = {samples.mean():.3f}")
print(f"模擬樣本標準差 = {samples.std():.3f}")
print(f"理論值:平均 = 170,標準差 = 8")
# 計算落在 [μ-2σ, μ+2σ] 區間的比例(經驗法則約 95%)
lower = 170 - 16
upper = 170 + 16
mask_in_range = np.logical_and(samples >= lower, np.less_equal(samples, upper))
within = np.mean(mask_in_range)
print(f"落在 [μ-2σ, μ+2σ] 的比例 ≈ {within:.4f}")
輸出:
模擬樣本平均 = 170.013
模擬樣本標準差 = 7.998
理論值:平均 = 170,標準差 = 8
落在 [μ-2σ, μ+2σ] 的比例 ≈ 0.9545
10 萬筆模擬結果的平均值和標準差幾乎等於設定值,且約 95.45% 的樣本落在平均值 ±2 個標準差的區間內——這就是常態分佈的「經驗法則」(68-95-99.7 法則)。在深度學習中,這個性質常用來偵測「梯度是否爆炸或消失」:如果梯度的值遠超過經驗法則的範圍,就該調整學習率或網路結構。
最大似然估計:用資料反推參數
最大似然估計(Maximum Likelihood Estimation,MLE)是統計學的核心方法之一。它的想法很直觀:「既然資料是從某個分佈抽出來的,那我們就找一組參數,讓『資料出現的機率』最大。」這個觀念在分類模型的交叉熵損失中扮演關鍵角色。
import numpy as np
# 假設我們觀察到 10 筆資料,懷疑它們來自常態分佈
samples = np.array([168, 172, 175, 170, 173, 169, 174, 171, 176, 172])
# 常態分佈的 log-likelihood 函式
def log_likelihood(mu, sigma, data):
n = len(data)
return -n / 2 * np.log(2 * np.pi * sigma ** 2) - \
np.sum((data - mu) ** 2) / (2 * sigma ** 2)
# 嘗試不同 mu,看哪個 mu 使 log-likelihood 最大
mu_grid = np.linspace(167, 178, 12)
sigma = 3.0 # 假設標準差已知
best_mu = None
best_ll = -np.inf
for mu in mu_grid:
ll = log_likelihood(mu, sigma, samples)
print(f"mu = {mu:.2f}, log-likelihood = {ll:.3f}")
if ll > best_ll:
best_ll = ll
best_mu = mu
print(f"\n最大似然估計:mu = {best_mu:.2f}(樣本平均 = {samples.mean():.2f})")
輸出(部分):
mu = 167.00, log-likelihood = -50.665
mu = 168.00, log-likelihood = -46.017
mu = 169.00, log-likelihood = -42.770
...
mu = 172.00, log-likelihood = -39.704
mu = 173.00, log-likelihood = -41.103
...
mu = 178.00, log-likelihood = -55.989
最大似然估計:mu = 172.00(樣本平均 = 172.20)
在 sigma 固定為 3 的條件下,使 log-likelihood 最大的 mu 接近樣本平均 172.20。事實上對常態分佈來說,MLE 的解析解就是樣本平均——這個結果並不意外,但它清楚展示了 MLE 的精神:「參數應該選讓資料出現機率最大的那一組」。對機器學習來說,MLE 提供了一個通用的框架:給定資料和一個模型假設,最佳參數就是讓資料出現機率最大的那組。
從 MLE 到交叉熵損失
在分類問題中(例如手寫數字辨識),我們的模型會輸出每個類別的機率。假設資料來自「以真實標籤為參數的 categorical 分佈」,那麼 MLE 等價於「最小化預測機率分佈與真實標籤之間的交叉熵」。這就是為什麼 PyTorch 訓練分類模型時,幾乎都用 nn.CrossEntropyLoss()。理解這條邏輯鏈,能讓你在選擇損失函式時不再猶豫。
import numpy as np
# 假設三筆資料的真實標籤與模型預測
y_true = np.array([0, 1, 2]) # 三類
y_pred = np.array([
[0.7, 0.2, 0.1],
[0.1, 0.8, 0.1],
[0.2, 0.3, 0.5]
])
# 取出每筆資料對應到真實標籤的預測機率
prob_correct = y_pred[np.arange(len(y_true)), y_true]
print("預測正確類別的機率:", prob_correct)
# 交叉熵 = - 平均 log(預測正確類別的機率)
cross_entropy = -np.mean(np.log(prob_correct))
print(f"交叉熵損失 = {cross_entropy:.4f}")
輸出:
預測正確類別的機率: [0.7 0.8 0.5]
交叉熵損失 = 0.4292
預測正確類別的機率越高([0.7, 0.8, 0.5]),交叉熵損失就越低(0.4292)。如果模型很完美,每個機率都是 1.0,損失就會是 0。這正是分類任務希望模型達成的目標。交叉熵的設計鼓勵模型對正確答案給出高信心、對錯誤答案給出低信心,這個特性讓它比均方誤差更適合分類任務——因為均方誤差在分類問題上容易出現「梯度消失」的情形。
結語
今天從機率的基本定義出發,走到隨機變數、期望值、變異數、常態分佈、最後用最大似然估計把統計與深度學習的交叉熵損失串起來。機率與統計是深度學習的語言,之後看到交叉熵、softmax、KL 散度這些詞彙時,就不會再覺得陌生。從明天開始,我們會把這三天的數學基礎轉化為實際工具——用 PyTorch 建立第一個張量運算,並了解 GPU 加速背後的原理。
明天,我們要進入這個系列的重頭戲——PyTorch。先把 PyTorch 安裝好,再學習張量(Tensor)的建立、運算、與 NumPy 互轉,並認識「裝置」(device)的概念,學會在 CPU 與 GPU 之間切換。
留言
張貼留言