【NLP】Day 8: 你拿定主意的話...葛萊芬多!BOW&TF-IDF

「拿定主意了嗎?你能成大器,你知道,在你一念之間,史萊哲林能幫助你走向輝煌,這毫無疑問——不樂意?那好,既然你已經拿定主意——那就最好去葛來芬多吧!」
分類帽《哈利波特:神秘的魔法石》

這幾天下來我們學了正規表達式、斷詞等等,這些都是屬於資料前處理的範疇。是說,剛剛在寫稿前,還被我的隊友罵說,啊我的正規表達式怎麼寫那麼少!欸不是,在這邊跟各位解釋一下,我的規劃就是簡單介紹正規表達式的三個函式,然後正規表達式常用的三個函式就是那三個,如果用到第四個再去查就可以了,我甚至很懷疑有沒有第四個函式,這個系列的文章就是適合我引你進門,至於修行就是要看個人啦!如果還想要更詳細的介紹文章,那我大力推薦我隊友fish_in_bed以及cjon_06991寫的文章,內容真的很豐富,在這邊提供傳送門給各位。

OK!閒聊結束!

今天漸漸要進入自然語言目前主流的機器學習方法,預計將會介紹詞袋(Bag-of-Words, BoW)、TF-IDF,應該啦,如果我寫得了這麼多的話。

語言模型(Language Modeling)

不知道各位在第一次看到所謂「語言模型」時,想到的是什麼?是以語言學搭建的機器學習模型嗎?還是大量資料丟進去演算的人工智慧?(在這裡,我偏好使用自然語言處理)其實自然語言處理在過去的研究脈絡中,傾向於將各語言的最小單位視作一個一個的token,並透過這些token進行統計上的機率模擬及運算,因此也有些人也會稱其為統計語言模型(Statistical Language Modeling)

像是有一篇計算語言學學生必讀的經典論文 A Neural Probabilistic Language Model 摘要的第一行就寫著:

統計語言模型的目標是透過文字序列了解在語言中字與字之間的聯合機率。

其實就是將文字轉成數字的形式讓電腦理解啦!不過若你仔細一想,倒也很好可以理解為什麼需要從統計的角度去看待自然語言處理,這是因為電腦跟人腦不同,電腦無法直接處理文字資料,但電腦非常擅長處理數值資料的演算,因此自然語言處理領域有一大半都是將文字轉成數值資料的研究。

而我們接下來要介紹的就是三種經典文本轉數值的常見方法。

詞袋(Bag-of-Words)

前人種樹,後人乘涼。另外一本計算語言學的必讀經典 Speech and Language Processing 其中的一張圖就完美詮釋了詞袋(Bag of Words)的概念。我們前面已經介紹過斷詞,其實詞袋就是將斷詞過後所得到的這些詞類作為一個一個的袋子,接著將這些字一一丟進這些袋子裡面。這就是詞袋的概念,這種計算詞頻的方式也是詞袋的其中一種做法。

另外一種做法,則是以獨熱編碼(One-Hot Encoding)的形式呈現其實根本沒有人會用中文吧? 也就是說,先找出詞袋後,詞袋中的詞序為隨機,接著對照每一個句子,若句子有詞袋中的字,則編碼為1,無則0

讓我們來看看以下例子:

  • 斷詞後的結果
    1
    2
    魯夫/說/他/想要/成為/海賊王
    娜美/說/他/想要/描繪/世界海圖
  • 詞袋
    1
    ['魯夫', '娜美', '説', '他', '想要', 成為', '描繪', '海賊王', '世界海圖']
  • 獨熱編碼(One-Hot Encoding)
    1
    2
    [1, 0, 1, 1, 1, 1, 0, 1, 0]
    [0, 1, 1, 1, 1, 0, 1, 0, 1]
    我們可以發現詞袋像是一條模板一樣,將它跟句子比對之後,接著賦值。這麼一來,每一個句子就會有屬於自己的encoding,並利用這種方式賦予每一個句子獨立的存在。但就如同前面我們不斷建立的一個觀念一樣,沒有最好或是最差的方式,只有最適合的方式。既然是最早的文字轉數值方法,一定有這種方法的缺點:
  1. 維度災難(Disaster of Dimensionality):假如說今天的文本詞類高達1000多種,那輸入資料的維度也就高達1000多維,意即有1000多個欄位,這是何其驚人!如此龐大的維度其實並不利於電腦的機器學習,可能會造成維度災難(Disaster of Dimensionality),也對電腦是一大負擔。
  2. 資料稀疏(Data sparsity):當資料越來越多,詞彙也跟著越來越多時,你可能會發現說,這麼一來在編碼內的0也會越來越多。這種情形稱為資料的稀疏性(Data sparsity)。相對於其他數值上的機器學習,這在NLP中是一個很嚴重的問題。這是因為資料一旦稀疏,就難以讓演算法判別不同句子之間的差異性(記得之前提到BoW的一個很大重點是句子之間的差異嗎?)更不用說語言具有無限可能。若出現其他新的模型沒看過的資料,BoW就比較沒辦法處理了。

那麼後人為了解決這些問題,TF-IDF以及Word2Vec出現了。

TF-IDF

TF-IDF,是一種在一個大語料庫中,找出每一篇文章關鍵字的計算方式。顧名思義,包含了字詞頻率(Term Frequency)以及逆向文件頻率(Inversed Document Frequency),並將兩者相乘,所得的分數稱為TF-IDF。

Term frequency (TF)

記得前面的斷詞所分成的token嗎?TF就是這些token在文本中的出現頻率,再除以那篇文章的字數,這是因為文本的篇幅都有長有短,一篇落落長的文章,詞彙出現的次數當然就可能會比較多囉,為了避免這種狀況,通常都會除上文章字數,這樣子的過程叫做將文本正規化

檔案https://chart.googleapis.com/chart?cht=tx&chl=d_%7B%7Bj%7D%7D中共有$k$個詞語
https://chart.googleapis.com/chart?cht=tx&chl=%7B%5Cdisplaystyle%20n_%7Bk%2Cj%7D%7Dhttps://chart.googleapis.com/chart?cht=tx&chl=%7B%5Cdisplaystyle%20t_%7Bk%7D%7D在檔案$d_$中出現的次數
https://chart.googleapis.com/chart?cht=tx&chl=n_%7B%7Bi%2Cj%7D%7D:該詞在檔案$d_$中的出現次數
https://chart.googleapis.com/chart?cht=tx&chl=%7B%5Csum%20_%7Bk%7Dn_%7B%7Bk%2Cj%7D%7D%7D:在檔案https://chart.googleapis.com/chart?cht=tx&chl=d_%7B%7Bj%7D%7D中所有字詞的出現次數之和。

Inversed Document frequency (IDF)

那為了要減少term frequency在某些文章中過高,或者是有些字在文章中雖然很常出現,但是卻不代表任何意義的情形(e.g. a, an, the),所以也要乘上一個數,可以使其值減低。對機器學習有一點概念的朋友,我認為可以把IDF想像成Penalty。Inversed Document frequency計算的則是以語料庫中的文本數除以文本中含有token的文本數,接著再取自然對數。白話的意思就是,若某些字集中出現在某幾篇文本中,代表這些字在這些文本中越重要,idf就越高,反之,若某些字在幾乎所有文本中都有出現,代表這些字其實並不那麼重要,idf就越低。

https://chart.googleapis.com/chart?cht=tx&chl=%7CD%7C:語料庫中的檔案總數
https://chart.googleapis.com/chart?cht=tx&chl=%7C%5C%7Bj%3At_%7B%7Bi%7D%7D%5Cin%20d_%7B%7Bj%7D%7D%5C%7D%7C:包含詞語https://chart.googleapis.com/chart?cht=tx&chl=t_%7B%7Bi%7D%7D的檔案數目

下面提供簡單程式碼:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
from sklearn import feature_extraction  
from sklearn.feature_extraction.text import TfidfTransformer
from sklearn.feature_extraction.text import CountVectorizer
corpus = ["魯夫 是 想要 成為 海賊王 的 男人",
"索隆 是 想要 成為 世界 最強 劍士 的 男人",
"娜美 想要 畫出 全 世界 的 海圖",
"香吉士 想要 找到 ALL_BLUE",
"騙人布 想要 成為 勇敢 的 海上戰士",
"喬巴 想要 成為 萬靈藥",
"羅賓 想要 找到 所有 歷史本文",
"佛朗基 想要 千陽號 航向 世界 盡頭",
"布魯克 想要 環繞 世界 重逢 拉布"]
vectorizer=CountVectorizer()
transformer=TfidfTransformer()
tfidf=transformer.fit_transform(vectorizer.fit_transform(corpus))
word=vectorizer.get_feature_names()
weight=tfidf.toarray()
for i in range(len(weight)):
print(u"-------這裡輸出第",i,u"類文字的詞語tf-idf權重------")
for j in range(len(word)):
print(word[j],weight[i][j] )
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
"""
all_blue 0.0
世界 0.0
佛朗基 0.0
劍士 0.0
勇敢 0.0
千陽號 0.0
喬巴 0.0
娜美 0.0
布魯克 0.0
想要 0.21155991248018197
成為 0.3582020693353289
所有 0.0
找到 0.0
拉布 0.0
最強 0.0
歷史本文 0.0
海上戰士 0.0
海圖 0.0
海賊王 0.552052456377027
環繞 0.0
男人 0.4662722935918962
畫出 0.0
盡頭 0.0
索隆 0.0
羅賓 0.0
航向 0.0
萬靈藥 0.0
重逢 0.0
香吉士 0.0
騙人布 0.0
魯夫 0.552052456377027
"""

問題與討論

被台灣教育荼毒過的對這五個字一定很熟悉,但很重要!不免俗,我們來討論一下TF-IDF可能需要特別注意哪些地方,以及缺點。

  • 語料庫的選擇很重要
    前面有提到這些TF-IDF的計算方法高度依賴語料庫中其他文本的詞類,因此當你要找關鍵字時,詞庫與詞庫間、文本檔案與檔案間的對應要特別留意,假如你要找的是某領域語料庫的關鍵字有哪些,那就不能用相同領域的對應語料庫(referent corpus),否則關鍵字就浮不出來了;同理,如果你要找的是某特定文本在某個語料庫中的關鍵字,那就要以「自己比自己」的方式,方可找到你要的結果。孰好孰壞?老話一句,看你的目的為何
  • 放大檢視優缺點
    優點就是簡單快速容易理解,但缺點的話,由於詞與詞之間都被切開來,單純以詞頻的角度去看待文章,這麼做反倒會忽略詞在文章中的位置(我們得要知道同一個詞在文章中的不同位置,可能代表不同的意義)另外,很少見的生澀詞彙可能會爆冷門變成關鍵詞,但重要的人名地名等等反倒不會出現在關鍵詞的前幾名中。

參考資料:

  1. NLP的基本執行步驟(II) — Bag of Words 詞袋語言模型
  2. Why is it a big problem to have sparsity issues in Natural Language processing (NLP)?
  3. 改进TF-IDF算法
  4. [python] 使用scikit-learn工具計算文字TF-IDF值