National Central University
Uedu Main Site
Explore Uedu
Student Console
Register as Member/Login
Research Informed Consent Center
Survey Center
Educational Big Data Challenge
Claim or create a course
Try an AI Scenario Task
Support & Messages
Uptime Data

UeduGPTs

--

Jupyters

3

Local AI

--

Uedu Code

--

中央大學 AQI 72 24°C PM2.5 13
AI Reply Desktop Notifications

Show a desktop notification when the AI TA finishes replying

Chat Message Notifications

Notify me when classmates post messages in the forum

Sound notification

Play an alert sound whenever there is a new notification

Unit 12

單純貝氏與文字分類

詞袋模型與條件機率
Interaction實作 30 minute(s)

Learning Objectives

  • 說明詞袋模型怎麼把文字變成數字,以及中文為什麼用字元 n-gram
  • 說明 TF-IDF 給哪些字比較低的權重
  • 手算一次單純貝氏的條件機率,並說明平滑與對數的作用
  • 用 MultinomialNB 訓練文字分類器,並判斷測試結果可不可信

文字要先變成數字

前面的模型吃的都是數字,但很多資料是文字:垃圾訊息、課程回饋、客服問題。要分類文字,第一步是把它變成數字。

最簡單的方法是詞袋模型(bag of words):先列出所有出現過的詞當作欄位(詞彙表),每一則文字變成一列,每一格是這個詞出現幾次。順序被丟掉了,就像把詞倒進袋子裡。

中文沒有空格,要嘛先斷詞,要嘛直接用字元 n-gram:把連續 n 個字當成一個單位。ngram_range=(1, 2) 表示單字與連續兩個字都算。「今天」會變成「今」「天」「今天」三個單位。這個做法不需要斷詞工具,對短訊息很實用。

CountVectorizer 預設以空白與標點分開單字,中文一句話會被當成一個詞,所以中文要明確設定 analyzer="char"。

TF-IDF 再多考慮一件事:出現在每一則裡的字(例如「的」「了」)沒什麼鑑別力,應該降低權重。

idf(字) = ln( (1 + 文件數) / (1 + 含這個字的文件數) ) + 1

(這是 scikit-learn 的預設公式。)每一格是「次數 × idf」,最後每一列再調整成長度 1。

逐步看:詞袋與 TF-IDF

三則短訊息。注意「今天」三則都有。X 是稀疏矩陣(大部分是 0,只存非零的格子),toarray() 才會變成一般陣列。

貝氏定理與「單純」的假設

有了次數,就可以算機率。我們想知道的是 P(垃圾 | 這則訊息)。貝氏定理把它換成比較容易從資料算的東西:

P(類別 | 訊息) ∝ P(類別) × P(訊息 | 類別)

P(類別) 是這個類別佔多少比例(先驗機率)。P(訊息 | 類別) 很難直接算,因為完全相同的訊息很少出現兩次。單純貝氏(naive Bayes)做了一個大膽的假設:知道類別之後,每個字的出現彼此獨立。於是可以拆開相乘:

P(訊息 | 類別) ≈ P(字1 | 類別) × P(字2 | 類別) × …

這個假設明顯不符合現實(「免」後面常接「費」),所以叫「單純」。但用來分類時常常夠用,因為我們只需要比較哪個類別的分數比較高。

還有兩個實作細節:

  • 平滑:訓練資料裡沒出現過的字,機率會是 0,乘起來整個變 0。所以分子加 1、分母加上不同字的數量 V,叫拉普拉斯平滑(Laplace smoothing):P(字 | 類別) = (次數 + 1) / (該類總字數 + V)。
  • 對數:很多小於 1 的數相乘會小到電腦存不下,所以改成對數相加。

逐步看:手算一次,再和 scikit-learn 比對

六則訊息,三則垃圾、三則一般。要判斷「免費課」。看每個字的條件機率,最後和 MultinomialNB 的結果比對。

逐步看:訓練一個垃圾訊息分類器

messages.csv 是程式用句型組合產生的模擬短訊(不是真實資料,也沒有真實人名、電話或網址)。用 pipeline 把向量化與模型接起來,直接傳文字就可以。

測試結果太好時,先懷疑

測試準確率 1.0 不代表這個分類器可以直接上線。這裡的原因很清楚:

  • 這份資料是由句型組合產生的,測試資料和訓練資料的句型幾乎相同,還有完全重複的訊息;
  • 所以這個分數只說明「它認得這些句型」,不代表它處理得了真實世界的新訊息。

真實的垃圾訊息會變換寫法、故意寫錯字,而且會隨時間改變。遇到高得不尋常的分數,先檢查測試資料和訓練資料是不是太像,或是不是有資料洩漏(第 14 課)。

另外,單純貝氏輸出的機率常常過度接近 0 或 1。字和字其實不獨立,同一個線索被重複計算(這裡「點」「此」「點此」都算一次),所以它的排序比機率的數值可靠。

下面把訓練資料減少到 10%,才看得到模型會錯在哪裡。

逐步看:訓練資料很少時錯在哪裡

只用 32 則訓練,用其他 288 則測試。看它錯在哪些訊息,再和 TF-IDF 比較。

實作:自己算單純貝氏的分數

完成 nb_scores(docs, labels, query):回傳 [類別 0 的分數, 類別 1 的分數],每個都四捨五入到小數 3 位。分數=先驗機率的對數+每個字 (次數 + 1) / (該類總字數 + V) 的對數,V 是全部訊息裡不同字的數量。這一題只用字元(unigram)。

你的輸出
預期輸出

小測驗:文字分類

1.

下面程式會印出什麼?

from sklearn.feature_extraction.text import CountVectorizer
vec = CountVectorizer(analyzer="char", ngram_range=(1, 2))
vec.fit(["今天"])
print(len(vec.get_feature_names_out()))
2.

某一類共 12 字,全部資料有 16 個不同的字,某個字在這一類沒出現過。用拉普拉斯平滑,下面程式會印出什麼?

print(round((0 + 1) / (12 + 16), 3))
3.

如果沒有平滑,訊息裡只要有一個字沒在某類訓練資料出現過,會發生什麼?

4.

實作單純貝氏時,為什麼把機率相乘改成對數相加?

5.

單純貝氏的「單純」是指什麼假設?

6.

某個字出現在每一則訊息裡,它的 idf(scikit-learn 預設公式)會怎樣?

7.

垃圾訊息分類器的測試準確率是 1.0,資料是由句型組合產生的,還有重複的訊息。最恰當的解讀是?

第 1 次作答

重點整理

  • 詞袋模型把文字變成次數;中文可以用字元 n-gram(analyzer="char"、ngram_range=(1, 2)),不需要斷詞。
  • TF-IDF 降低到處都出現的字的權重,但不一定比次數好,要實際比較。
  • 單純貝氏:P(類別) × 每個字的 P(字 | 類別),假設字彼此獨立。用平滑處理沒看過的字,用對數避免下溢。MultinomialNB 預設 alpha=1.0。
  • 訓練快、資料少時也能用,機率的數值不可靠,排序比較可靠。
  • 測試分數高得不尋常時,先檢查資料:測試資料和訓練資料是不是太像。
  • 下一課專門談分類的評估:準確率不夠用時,要看混淆矩陣、精確率與召回率。
這一課完成了!
Next lesson:分類的評估
你目前沒有登入,學習進度只存在這個瀏覽器。 Login 後,進度會跟著帳號,換電腦也看得到。