前幾天的討論中,我們已經探討了迴歸分析、邏輯迴歸,以及最近兩天介紹的 K-Nearest Neighbors (KNN)。今天要討論的是另一種基礎且直覺性極強的分類演算法: 樸素貝氏分類器 (Naive Bayes Classifier)。儘管樸素貝氏分類器的基本原理非常簡單,甚至經常被視為基礎模型,但在實務應用中,它仍然是許多場合的首選,尤其是在文本分類領域,例如垃圾郵件分類與情感分析。
Naive Bayes 的核心思想來自貝氏定理 (Bayes' Theorem):
$$
P(y|X) = \frac{P(X|y)P(y)}{P(X)}
$$
但直接計算 $P(X|y)$ 是困難的,尤其當特徵數量龐大且互相關聯時。因此 Naive Bayes 做了一個極簡的假設——「條件獨立假設 (Conditional Independence Assumption)」,即假設特徵之間彼此獨立:
$$
P(X|y) = P(x_1|y) \times P(x_2|y) \times \cdots \times P(x_n|y)
$$
這個假設大幅簡化了問題,讓計算變得非常快速且易於實現。雖然這個假設在現實世界中往往不成立,但 Naive Bayes 的實務表現卻通常仍然相當穩健。
本次實作會以多項式 Naive Bayes 為例,因為它在文本分類中表現卓越,並且可展示 Naive Bayes 的強項: 速度快、表現穩定且容易理解。我們將使用經典的 SMS Spam Collection 資料集,透過 Naive Bayes 分辨垃圾訊息與正常訊息,這個過程就不過多敘述。
import pandas as pd
from sklearn.model_selection import train_test_split
from sklearn.feature_extraction.text import CountVectorizer
from sklearn.naive_bayes import MultinomialNB
from sklearn.metrics import classification_report, accuracy_score
# 載入資料集
url = "https://raw.githubusercontent.com/justmarkham/pycon-2016-tutorial/master/data/sms.tsv"
data = pd.read_csv(url, sep='\t', header=None, names=['label', 'message'])
# 轉換目標變數為數值型
data['label_num'] = data.label.map({'ham':0, 'spam':1})
# 分割資料集
X_train, X_test, y_train, y_test = train_test_split(
data['message'], data['label_num'], test_size=0.2, random_state=42
)
# 特徵提取
vectorizer = CountVectorizer()
X_train_dtm = vectorizer.fit_transform(X_train)
X_test_dtm = vectorizer.transform(X_test)
模型訓練,這邊要補充 nb = MultinomialNB() 等價於 nb = MultinomialNB(alpha=1.0),這邊的重點就是 alpha 用來處理 Laplace Smoothing (拉普拉斯平滑) 的問題。(可以到文章下方看一下補充: 經典且嚴重的問題)
# 建立並訓練 Naive Bayes 模型
nb = MultinomialNB()
nb.fit(X_train_dtm, y_train)
預測與模型評估。
# 預測
train_pred = nb.predict(X_train_dtm)
test_pred = nb.predict(X_test_dtm)
# 評估
print(f"Train Accuracy: {accuracy_score(y_train, train_pred):.4f}")
print(classification_report(y_train, train_pred))
print(f"Test Accuracy: {accuracy_score(y_test, test_pred):.4f}")
print(classification_report(y_test, test_pred))
import pandas as pd
from sklearn.model_selection import train_test_split
from sklearn.feature_extraction.text import CountVectorizer
from sklearn.naive_bayes import MultinomialNB
from sklearn.metrics import classification_report, accuracy_score
# 載入資料集
url = "https://raw.githubusercontent.com/justmarkham/pycon-2016-tutorial/master/data/sms.tsv"
data = pd.read_csv(url, sep='\t', header=None, names=['label', 'message'])
# 轉換目標變數為數值型
data['label_num'] = data.label.map({'ham':0, 'spam':1})
# 分割資料集
X_train, X_test, y_train, y_test = train_test_split(
data['message'], data['label_num'], test_size=0.2, random_state=42
)
# 特徵提取
vectorizer = CountVectorizer()
X_train_dtm = vectorizer.fit_transform(X_train)
X_test_dtm = vectorizer.transform(X_test)
# 建立並訓練 Naive Bayes 模型
nb = MultinomialNB()
nb.fit(X_train_dtm, y_train)
# 預測
train_pred = nb.predict(X_train_dtm)
test_pred = nb.predict(X_test_dtm)
# 評估
print(f"Train Accuracy: {accuracy_score(y_train, train_pred):.4f}")
print(classification_report(y_train, train_pred))
print(f"Test Accuracy: {accuracy_score(y_test, test_pred):.4f}")
print(classification_report(y_test, test_pred))
Train Accuracy: 0.9933
precision recall f1-score support
0 1.00 1.00 1.00 3859
1 0.98 0.97 0.97 598
accuracy 0.99 4457
macro avg 0.99 0.98 0.99 4457
weighted avg 0.99 0.99 0.99 4457
Test Accuracy: 0.9919
precision recall f1-score support
0 0.99 1.00 1.00 966
1 1.00 0.94 0.97 149
accuracy 0.99 1115
macro avg 1.00 0.97 0.98 1115
weighted avg 0.99 0.99 0.99 1115
Naive Bayes 在本案例表現非常好,訓練集準確率達 99.3%,測試集也達到 98.21%,證明 Naive Bayes 在文本分類的適用性,尤其在高維且稀疏的資料空間中。
然而,我們也看到測試集中垃圾訊息 (spam) 的召回率 (Recall) 稍微偏低,表示有一些垃圾訊息被誤判為正常訊息 (ham)。接下來,我們可以考慮:
在 Naive Bayes 中,機率是用「訓練資料的詞頻」來估計的。
$$
P(\text{word} \mid \text{spam}) = \frac{\text{spam 中該詞出現次數}}{\text{spam 總詞數}}
$$
如果某個詞 從來沒在垃圾郵件中出現過,就會導致:
$$
P(\text{word} \mid \text{spam}) = 0
$$
這會導致整體機率直接歸零,因為我們要計算的是:
$$
P(\text{spam} \mid \text{new email}) \propto P(\text{spam}) \cdot \prod_{i} P(x_i \mid \text{spam})
$$
→ 只要有一個 $P(x_i \mid \text{spam}) = 0$,整個乘積變成 0
解決方法: Laplace Smoothing
Naive Bayes 雖然簡單,但卻憑藉「簡單」帶來了高效且實用的分類能力。無論是作為基準模型 (Baseline),還是迅速建立第一版產品原型,它都能提供穩定且可靠的初步結果。透過今天的實作案例,我們再次體會到:並非所有模型都需要複雜的運算與深奧的數學。很多時候,最直覺、最簡單的方法往往就是最佳解。