0
本文作者: AI研習社 | 2017-07-19 11:11 |
雷鋒網(wǎng)按:本文原文來自一篇國外大神的博客,由雷鋒網(wǎng)字幕組 彭艷蕾、林立宏 兩位組員共同編譯完成,轉(zhuǎn)載請注明出處。
文本挖掘(Text Mining,從文字中獲取信息)是一個比較寬泛的概念,這一技術(shù)在如今每天都有海量文本數(shù)據(jù)生成的時代越來越受到關(guān)注。目前,在機器學習模型的幫助下,包括情緒分析,文件分類,話題分類,文本總結(jié),機器翻譯等在內(nèi)的諸多文本挖掘應(yīng)用都已經(jīng)實現(xiàn)了自動化。
在這些應(yīng)用中,垃圾郵件過濾算是初學者實踐文件分類的一個很不錯的開始,例如 Gmail 賬戶里的“垃圾郵箱”就是一個垃圾郵件過濾的現(xiàn)實應(yīng)用。下面我們將基于一份公開的郵件數(shù)據(jù)集 Ling-spam,編寫一個垃圾郵件的過濾器。Ling-spam 數(shù)據(jù)集的下載地址如下:
這里我們已經(jīng)從 Ling-spam 中提取了相同數(shù)量的垃圾郵件和非垃圾郵件,具體下載地址如下:
下面我們將通過以下幾個步驟,編寫一個現(xiàn)實可用的垃圾郵件過濾器。
1. 準備文本數(shù)據(jù);
2. 創(chuàng)建詞典(word dictionary);
3. 特征提取;
4. 訓練分類器。
最后,我們會通過一個測試數(shù)據(jù)集對過濾器進行驗證。
這里我們將數(shù)據(jù)集分成了訓練集(702封郵件)和測試集(260封郵件)兩部分,其中垃圾和非垃圾郵件各占 50%。這里因為每個垃圾郵件的數(shù)據(jù)集都以 spmsg 命名,因此很容易區(qū)分。
在大部分的文本挖掘問題中,文本清理都是第一步,即首先要清理掉那些與我們的目標信息無關(guān)的詞句,本例中也一樣。通常郵件里一般都會包含很多無用的字符,比如標點符號,停用詞,數(shù)字等等,這些字符對檢測垃圾郵件沒什么幫助,因此我們需要將它們清理掉。這里 Ling-spam 數(shù)據(jù)集里的郵件已經(jīng)經(jīng)過了以下幾個步驟的處理:
a) 清除停用詞 --- 像 "and", "the", "of" 等這些停用詞在英語語句中非常常見。然而,這些停用詞對于判定郵件的真實身份并沒有什么卵用,所以這些詞已經(jīng)從郵件中被移除。
b) 詞形還原 --- 這是一種把同一個詞的不同形式組合在一起,以便被當做一個單獨項目來分析的過程。舉個栗子,"include", "includes" 和 "included" 就可以全部用 "include" 來代表。與此同時,語句的上下文含義也會通過詞形還原的方法保留下來,這一點不同于詞干提取 (stemming) 的方法(注:詞干提取是另一種文本挖掘的方法,此法不考慮語句的含義)。
此外,我們還需要移除一些非文字類的符號(non-words),比如標點符號或者特殊字符之類的。要實現(xiàn)這一步有很多方法,這里,我們將首先創(chuàng)建一個詞典(creating a dictionary),之后再移除這些非文字類的符號。需要指出的是,這種做法其實非常方便,因為當你手上有了一個詞典之后,對于每一種非文字類符號,只需要移除一次就 ok 了。
一個數(shù)據(jù)集里的樣本郵件一般長這樣:
Subject: posting
hi , ' m work phonetics project modern irish ' m hard source . anyone recommend book article english ? ' , specifically interest palatal ( slender ) consonant , work helpful too . thank ! laurel sutton ( sutton @ garnet . berkeley . edu
你會發(fā)現(xiàn)郵件的第一行是標題,從第三行開始才是正文。這里我們只在郵件正文內(nèi)容的基礎(chǔ)上做文本分析,來判定該郵件是否為垃圾郵件。第一步,我們需要創(chuàng)建一個文字的詞典和文字出現(xiàn)的頻率。為了創(chuàng)建這樣一個“詞典”,這里我們利用了訓練集里的 700 封郵件。具體實現(xiàn)詳見下面這個 Python 函數(shù):
def make_Dictionary(train_dir):
emails = [os.path.join(train_dir,f) for f in os.listdir(train_dir)]
all_words = []
for mail in emails:
with open(mail) as m:
for i,line in enumerate(m):
if i == 2: #Body of email is only 3rd line of text file
words = line.split()
all_words += words
dictionary = Counter(all_words)
# Paste code for non-word removal here(code snippet is given below)
return dictionary
詞典創(chuàng)建好之后,我們只要在上面函數(shù)的基礎(chǔ)上再加幾行代碼,就可以移除之前提到的那些非文字類符號了。這里我還順手刪掉了一些與垃圾郵件的判定無關(guān)的單字符,具體參見如下的代碼,注意這些代碼要附在 def make_Dictionary(train_dir) 函數(shù)的末尾。
list_to_remove = dictionary.keys()
for item in list_to_remove:
if item.isalpha() == False:
del dictionary[item]
elif len(item) == 1:
del dictionary[item]
dictionary = dictionary.most_common(3000)
這里通過輸入 print dictionary 指令就可以輸出詞典。需要注意的是,你在打印輸出的詞典里可能會看到許多無關(guān)緊要的詞,這一點無需擔心,因為我們在后續(xù)的步驟中總是有機會對其進行調(diào)整的。另外,如果你是嚴格按照上文提到的數(shù)據(jù)集操作的話,那么你的詞典里應(yīng)該會有以下這些高頻詞(本例中我們選取了頻率最高的 3000 個詞):
[('order', 1414), ('address', 1293), ('report', 1216), ('mail', 1127), ('send', 1079), ('language', 1072), ('email', 1051), ('program', 1001), ('our', 987), ('list', 935), ('one', 917), ('name', 878), ('receive', 826), ('money', 788), ('free', 762)
詞典準備好之后,我們就可以對訓練集里的每一封郵件提取維度是 3000 的詞數(shù)向量 word count vector(這個向量就是我們的特征),每一個詞數(shù)向量都包含之前選定的 3000 個高頻詞具體的出現(xiàn)頻率。當然,你可能猜到了,大部分出現(xiàn)的頻率應(yīng)該會是 0。舉個栗子:比如我們字典里有 500 個詞,每個詞數(shù)向量包含了訓練集里這 500 個詞的出現(xiàn)頻率。假設(shè)訓練集有一組文本:“Get the work done, work done”。那么,這句話對應(yīng)的詞數(shù)向量應(yīng)該是這樣的:[0,0,0,0,0,…….0,0,2,0,0,0,……,0,0,1,0,0,…0,0,1,0,0,……2,0,0,0,0,0]。在這里,句中的每個詞出現(xiàn)的頻率都能顯示出來:這些詞分別對應(yīng)長度為 500 的詞數(shù)向量中的第 296,359,415 和 495 的位置,其他位置顯示為 0。
下面這個 python 函數(shù)會幫助我們生成一個特征向量矩陣,該矩陣有 700 行 3000 列。其中每一行代表訓練集中 700 封郵件的的每一封郵件,每一列代表詞典中的 3000 個關(guān)鍵詞。在 “ij” 位置上的值代表了詞典中第 j 個詞在該郵件(第 i 封)中出現(xiàn)的次數(shù)。
def extract_features(mail_dir):
files = [os.path.join(mail_dir,fi) for fi in os.listdir(mail_dir)]
features_matrix = np.zeros((len(files),3000))
docID = 0;
for fil in files:
with open(fil) as fi:
for i,line in enumerate(fi):
if i == 2:
words = line.split()
for word in words:
wordID = 0
for i,d in enumerate(dictionary):
if d[0] == word:
wordID = i
features_matrix[docID,wordID] = words.count(word)
docID = docID + 1
return features_matrix
在這里我們會使用 scikit-learn 機器學習庫來訓練分類器,scikit-learn 庫的相關(guān)鏈接如下:
這是一個綁定在第三方 python 發(fā)行版 Anaconda 的開源機器學習庫,可以跟隨 Anaconda 一同下載安裝,或者也可以按照以下鏈接中的提示獨立安裝:
安裝好了之后,我們只需要將其 import 到我們的程序中就可以使用了。
這里我們訓練了兩個模型,分別是樸素貝葉斯分類器和 SVM(支持向量機)。樸素貝葉斯分類器是一個傳統(tǒng)的監(jiān)督型概率分類器,在文本分類的場景中非常常用,它基于貝葉斯定理,假設(shè)每一對特征都是相互獨立的。SVM 是監(jiān)督型的二分類器,面對特征數(shù)量較多的場景時非常有效,其最終目標是從訓練數(shù)據(jù)中分離出一組子集,稱為支持向量(分離超平面的邊界)。判定測試數(shù)據(jù)最終類別的 SVM 決策函數(shù)正是基于該支持向量和內(nèi)核技巧(kernel trick)的。
分類器訓練完成后,我們可以在測試集上測試模型的性能。這里我們?yōu)闇y試集中的每封郵件提取字數(shù)向量,然后用訓練好的樸素貝葉斯分類器和 SVM 模型,預(yù)測它的類別(普通郵件或垃圾郵件)。下面是垃圾郵件分類器的完整 python 代碼,另外還需要包含我們在步驟 2 和步驟 3 中定義的兩個函數(shù)。
import os
import numpy as np
from collections import Counter
from sklearn.naive_bayes import MultinomialNB, GaussianNB, BernoulliNB
from sklearn.svm import SVC, NuSVC, LinearSVC
from sklearn.metrics import confusion_matrix
# Create a dictionary of words with its frequency
train_dir = 'train-mails'
dictionary = make_Dictionary(train_dir)
# Prepare feature vectors per training mail and its labels
train_labels = np.zeros(702)
train_labels[351:701] = 1
train_matrix = extract_features(train_dir)
# Training SVM and Naive bayes classifier
model1 = MultinomialNB()
model2 = LinearSVC()
model1.fit(train_matrix,train_labels)
model2.fit(train_matrix,train_labels)
# Test the unseen mails for Spam
test_dir = 'test-mails'
test_matrix = extract_features(test_dir)
test_labels = np.zeros(260)
test_labels[130:260] = 1
result1 = model1.predict(test_matrix)
result2 = model2.predict(test_matrix)
print confusion_matrix(test_labels,result1)
print confusion_matrix(test_labels,result2)
這里我們的測試集中包含 130 封垃圾郵件和 130 封非垃圾郵件,如果你已經(jīng)順利完成了之前的所有步驟,那么你將會得到如下的結(jié)果。這里顯示的是兩個模型在測試數(shù)據(jù)中的混淆矩陣,對角元素代表了正確識別的郵件數(shù),非對角元素代表的則是錯誤的分類。
可以看到,兩個模型在測試集上有著相近的性能,但 SVM 更傾向垃圾郵件的判定。需要注意的是,這里的測試數(shù)據(jù)集既沒有用于創(chuàng)建字典,也沒有用于模型訓練。
感興趣的朋友可以按照上文所述的步驟進行一些拓展,這里介紹拓展相關(guān)的數(shù)據(jù)庫和結(jié)果。
拓展使用的是已經(jīng)預(yù)處理好的 Euron-spam 數(shù)據(jù)庫,其中包含了 6 個目錄,33716 封郵件,每個目錄中都包含非垃圾郵件和垃圾郵件子目錄,非垃圾郵件和垃圾郵件的總數(shù)分別為 16545 封和 17171 封。Euron-spam 庫的下載鏈接如下:
需要注意的是,由于 Euron-spam 數(shù)據(jù)庫的組織形式有別于上文提到的 ling-spam 庫,因此上文的一些函數(shù)也需要做少量的修改才能應(yīng)用于 Euron-spam。
這里我們將 Euron-spam 數(shù)據(jù)庫按照 3:2 的比例分為訓練集和測試集,按照上文的步驟,我們在 13478 封測試郵件中得到了如下結(jié)果:
可以看到,SVM 的表現(xiàn)略勝于樸素貝葉斯。
在本文中我們盡量保持簡單易懂的敘述,省略了許多技術(shù)性強的講解和名詞。我們希望這是一篇簡單易懂的教程,希望這篇教程可以對文本分析感興趣的初學者們有所裨益。
有些朋友可能會對樸素貝葉斯模型和 SVM 模型背后的數(shù)學原理感到好奇,這里需要指出的是,SVM 在數(shù)學上屬于比較復(fù)雜的模型,而樸素貝葉斯則相對更容易理解一些。我們當然鼓勵對數(shù)學原理感興趣的朋友們深入探索,關(guān)于這些數(shù)學模型網(wǎng)上有非常詳細的教程和實例。除此之外,采用不同的方式實現(xiàn)同一個目標,也是一種很好的研究方法。例如可以調(diào)節(jié)如下一些參數(shù),觀察它們對垃圾郵件過濾的實際效果的影響:
a) 訓練數(shù)據(jù)的大小
b) 詞典的大小
c) 不同的機器學習模型,包括 GaussianNB,BernoulliNB,SVC
d) 不同的 SVM 模型參數(shù)
e) 刪除無關(guān)緊要的詞來改進詞典 (例如手動刪除)
f) 采用其他特征模型 (尋找 td-idf)
最后,博客中提到的完整 python 代碼詳見如下鏈接:
若有問題,歡迎在文末留言討論。
雷鋒網(wǎng)相關(guān)閱讀:
雷峰網(wǎng)原創(chuàng)文章,未經(jīng)授權(quán)禁止轉(zhuǎn)載。詳情見轉(zhuǎn)載須知。