iT邦幫忙

2025 iThome 鐵人賽

DAY 9
0
AI & Data

30 天入門常見的機器學習演算法系列 第 9

(Day 9) 樸素貝氏分類器 (Naive Bayes Classifier)

  • 分享至 

  • xImage
  •  

前幾天的討論中,我們已經探討了迴歸分析、邏輯迴歸,以及最近兩天介紹的 K-Nearest Neighbors (KNN)。今天要討論的是另一種基礎且直覺性極強的分類演算法: 樸素貝氏分類器 (Naive Bayes Classifier)。儘管樸素貝氏分類器的基本原理非常簡單,甚至經常被視為基礎模型,但在實務應用中,它仍然是許多場合的首選,尤其是在文本分類領域,例如垃圾郵件分類與情感分析。

模型介紹

模型邏輯與核心概念

Naive Bayes 的核心思想來自貝氏定理 (Bayes' Theorem):

$$
P(y|X) = \frac{P(X|y)P(y)}{P(X)}
$$

  • $P(y|X)$: 在給定特徵 $X$ 下的目標 $y$ 的後驗機率 (posterior probability)
  • $P(X|y)$: 在已知目標 $y$ 下觀察到特徵 $X$ 的可能性 (likelihood)
  • $P(y)$: 目標 $y$ 的先驗機率 (prior probability)
  • $P(X)$: 觀察到特徵 $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 常見種類

  • Gaussian Naive Bayes (高斯樸素貝氏): 假設特徵為連續數值,並服從高斯分布。
  • Multinomial Naive Bayes (多項式樸素貝氏): 特別適用於文本數據,特徵通常為計數 (例如詞頻)。
  • Bernoulli Naive Bayes (伯努利樸素貝氏): 特徵為二元變數 (例如詞的出現與否)。

適用情境

  • 特徵數量大且離散,尤其文本分類
  • 需要模型快速訓練與預測
  • 基準模型 (Baseline Model) 的建立

限制條件

  • 特徵之間存在強烈相關性時,效果可能較差
  • 無法捕捉特徵之間的交互作用

模型實作

本次實作會以多項式 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)。接下來,我們可以考慮:

  • 特徵工程提升效果: 使用 TF-IDF 替換純粹的詞頻計數。
  • 平衡資料集: 增加垃圾訊息樣本或使用過採樣技術。
  • 嘗試其他模型: 例如 SVM 或更複雜的模型如 BERT,與 Naive Bayes 做比較。

補充: 經典且嚴重的問題

在 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),還是迅速建立第一版產品原型,它都能提供穩定且可靠的初步結果。透過今天的實作案例,我們再次體會到:並非所有模型都需要複雜的運算與深奧的數學。很多時候,最直覺、最簡單的方法往往就是最佳解。


上一篇
(Day 8) K-近鄰 (K-Nearest Neighbors)
下一篇
(Day 10) 支援向量機 (Support Vector Machine)
系列文
30 天入門常見的機器學習演算法30
圖片
  熱門推薦
圖片
{{ item.channelVendor }} | {{ item.webinarstarted }} |
{{ formatDate(item.duration) }}
直播中

尚未有邦友留言

立即登入留言