1. 程式人生 > >python 和 scikit-learn 實現垃圾郵件過濾

python 和 scikit-learn 實現垃圾郵件過濾

文本挖掘(Text Mining,從文字中獲取信息)是一個比較寬泛的概念,這一技術在如今每天都有海量文本數據生成的時代越來越受到關註。目前,在機器學習模型的幫助下,包括情緒分析,文件分類,話題分類,文本總結,機器翻譯等在內的諸多文本挖掘應用都已經實現了自動化。

在這些應用中,垃圾郵件過濾算是初學者實踐文件分類的一個很不錯的開始,例如 Gmail 賬戶裏的“垃圾郵箱”就是一個垃圾郵件過濾的現實應用。下面我們將基於一份公開的郵件數據集 Ling-spam,編寫一個垃圾郵件的過濾器。Ling-spam 數據集的下載地址如下:

http://t.cn/RKQBl9c

這裏我們已經從 Ling-spam 中提取了相同數量的垃圾郵件和非垃圾郵件,具體下載地址如下:

http://t.cn/RKQBkRu

下面我們將通過以下幾個步驟,編寫一個現實可用的垃圾郵件過濾器。

1.準備文本數據;

2.創建詞典(word dictionary);

3.特征提取;

4.訓練分類器。

最後,我們會通過一個測試數據集對過濾器進行驗證。


1. 準備文本數據

這裏我們將數據集分成了訓練集(702封郵件)和測試集(260封郵件)兩部分,其中垃圾和非垃圾郵件各占 50%。這裏因為每個垃圾郵件的數據集都以 spmsg 命名,因此很容易區分。

在大部分的文本挖掘問題中,文本清理都是第一步,即首先要清理掉那些與我們的目標信息無關的詞句,本例中也一樣。通常郵件裏一般都會包含很多無用的字符,比如標點符號,停用詞,數字等等,這些字符對檢測垃圾郵件沒什麽幫助,因此我們需要將它們清理掉。這裏 Ling-spam 數據集裏的郵件已經經過了以下幾個步驟的處理:

a) 清除停用詞 --- 像 "and", "the", "of" 等這些停用詞在英語語句中非常常見。然而,這些停用詞對於判定郵件的真實身份並沒有什麽卵用,所以這些詞已經從郵件中被移除。

b) 詞形還原 --- 這是一種把同一個詞的不同形式組合在一起,以便被當做一個單獨項目來分析的過程。舉個栗子,"include", "includes" 和 "included" 就可以全部用 "include" 來代表。與此同時,語句的上下文含義也會通過詞形還原的方法保留下來,這一點不同於詞幹提取 (stemming) 的方法(註:詞幹提取是另一種文本挖掘的方法,此法不考慮語句的含義)。

此外,我們還需要移除一些非文字類的符號(non-words),比如標點符號或者特殊字符之類的。要實現這一步有很多方法,這裏,我們將首先創建一個詞典(creating a dictionary),之後再移除這些非文字類的符號。需要指出的是,這種做法其實非常方便,因為當你手上有了一個詞典之後,對於每一種非文字類符號,只需要移除一次就 ok 了。


2. 創建詞典(Creating word dictionary)

一個數據集裏的樣本郵件一般長這樣:

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

你會發現郵件的第一行是標題,從第三行開始才是正文。這裏我們只在郵件正文內容的基礎上做文本分析,來判定該郵件是否為垃圾郵件。第一步,我們需要創建一個文字的詞典和文字出現的頻率。為了創建這樣一個“詞典”,這裏我們利用了訓練集裏的 700 封郵件。具體實現詳見下面這個 Python 函數:

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 belowfeifanshifan8.cn)

return dictionary

詞典創建好之後,我們只要在上面函數的基礎上再加幾行代碼,就可以移除之前提到的那些非文字類符號了。這裏我還順手刪掉了一些與垃圾郵件的判定無關的單字符,具體參見如下的代碼,註意這些代碼要附在 def make_Dictionary(train_dir) 函數的末尾。

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 指令就可以輸出詞典。需要註意的是,你在打印輸出的詞典裏可能會看到許多無關緊要的詞,這一點無需擔心,因為我們在後續的步驟中總是有機會對其進行調整的。另外,如果你是嚴格按照上文提到的數據集操作的話,那麽你的詞典裏應該會有以下這些高頻詞(本例中我們選取了頻率最高的 3000 個詞):

[(’order’, 1414), (’address’, 1293), (’report’, 1216 www.yongshiyule178.com), (’mail’, 1127), (’send’, 1079), (’language’, 1072), (’email’, 1051), (’program’, 1001), (’our’, 987), (’list’, 935), (’one’, 917), (’name’, 878), (’receive’, 826), (’money’, 788), (’free’, 762)


3. 特征提取

詞典準備好之後,我們就可以對訓練集裏的每一封郵件提取維度是 3000 的詞數向量 word count vector(這個向量就是我們的特征),每一個詞數向量都包含之前選定的 3000 個高頻詞具體的出現頻率。當然,你可能猜到了,大部分出現的頻率應該會是 0。舉個栗子:比如我們字典裏有 500 個詞,每個詞數向量包含了訓練集裏這 500 個詞的出現頻率。假設訓練集有一組文本:“Get the work done, work done”。那麽,這句話對應的詞數向量應該是這樣的:[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]。在這裏,句中的每個詞出現的頻率都能顯示出來:這些詞分別對應長度為 500 的詞數向量中的第 296,359,415 和 495 的位置,其他位置顯示為 0。

下面這個 python 函數會幫助我們生成一個特征向量矩陣,該矩陣有 700 行 3000 列。其中每一行代表訓練集中 700 封郵件的的每一封郵件,每一列代表詞典中的 3000 個關鍵詞。在 “ij” 位置上的值代表了詞典中第 j 個詞在該郵件(第 i 封)中出現的次數。

def extract_features(mail_dir):

files = [os.path.join(mail_dir,fi) for fi in os.listdir(mail_dir)www.dajinnylee.cn]

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 www.6788878.cn)

docID = docID + 1

return features_matrix


4.訓練分類器

在這裏我們會使用 scikit-learn 機器學習庫來訓練分類器,scikit-learn 庫的相關鏈接如下:

http://t.cn/SMzAoZ

這是一個綁定在第三方 python 發行版 Anaconda 的開源機器學習庫,可以跟隨 Anaconda 一同下載安裝,或者也可以按照以下鏈接中的提示獨立安裝:

http://t.cn/8kkrVlQ

安裝好了之後,我們只需要將其 import 到我們的程序中就可以使用了。

這裏我們訓練了兩個模型,分別是樸素貝葉斯分類器和 SVM(支持向量機)。樸素貝葉斯分類器是一個傳統的監督型概率分類器,在文本分類的場景中非常常用,它基於貝葉斯定理,假設每一對特征都是相互獨立的。SVM 是監督型的二分類器,面對特征數量較多的場景時非常有效,其最終目標是從訓練數據中分離出一組子集,稱為支持向量(分離超平面的邊界)。判定測試數據最終類別的 SVM 決策函數正是基於該支持向量和內核技巧(kernel trick)的。

分類器訓練完成後,我們可以在測試集上測試模型的性能。這裏我們為測試集中的每封郵件提取字數向量,然後用訓練好的樸素貝葉斯分類器和 SVM 模型,預測它的類別(普通郵件或垃圾郵件)。下面是垃圾郵件分類器的完整 python 代碼,另外還需要包含我們在步驟 2 和步驟 3 中定義的兩個函數。

import os

import numpy as np

from collections import Counter

from sklearn.naive_bayes import MultinomialNB, G www.aomenyonli.cn aussianNB, 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 封非垃圾郵件,如果你已經順利完成了之前的所有步驟,那麽你將會得到如下的結果。這裏顯示的是兩個模型在測試數據中的混淆矩陣,對角元素代表了正確識別的郵件數,非對角元素代表的則是錯誤的分類。

可以看到,兩個模型在測試集上有著相近的性能,但 SVM 更傾向垃圾郵件的判定。需要註意的是,這裏的測試數據集既沒有用於創建字典,也沒有用於模型訓練。


拓展

感興趣的朋友可以按照上文所述的步驟進行一些拓展,這裏介紹拓展相關的數據庫和結果。

拓展使用的是已經預處理好的 Euron-spam 數據庫,其中包含了 6 個目錄,33716 封郵件,每個目錄中都包含非垃圾郵件和垃圾郵件子目錄,非垃圾郵件和垃圾郵件的總數分別為 16545 封和 17171 封。Euron-spam 庫的下載鏈接如下:

http://t.cn/RK84mv6

需要註意的是,由於 Euron-spam 數據庫的組織形式有別於上文提到的 ling-spam 庫,因此上文的一些函數也需要做少量的修改才能應用於 Euron-spam。

這裏我們將 Euron-spam 數據庫按照 3:2 的比例分為訓練集和測試集,按照上文的步驟,我們在 13478 封測試郵件中得到了如下結果:

可以看到,SV www.senta77.com M 的表現略勝於樸素貝葉斯。


總結

在本文中我們盡量保持簡單易懂的敘述,省略了許多技術性強的講解和名詞。我們希望這是一篇簡單易懂的教程,希望這篇教程可以對文本分析感興趣的初學者們有所裨益。

有些朋友可能會對樸素貝葉斯模型和 SVM 模型背後的數學原理感到好奇,這裏需要指出的是,SVM 在數學上屬於比較復雜的模型,而樸素貝葉斯則相對更容易理解一些。我們當然鼓勵對數學原理感興趣的朋友們深入探索,關於這些數學模型網上有非常詳細的教程和實例。除此之外,采用不同的方式實現同一個目標,也是一種很好的研究方法。例如可以調節如下一些參數,觀察它們對垃圾郵件過濾的實際效果的影響:

a) 訓練數據的大小

b) 詞典的大小

c) 不同的機器學習模型,包括 GaussianNB,BernoulliNB,SVC

d) 不同的 SVM 模型參數

e) 刪除無關緊要的詞來改進詞典 (例如手動刪除)

f) 采用其他特征模型 (尋找 td-idf)

最後,博客中提到的完整 python 代碼詳見如下鏈接:

http://t.cn/R6ZeuiN

若有問題,歡迎在文末留言討論。

雷鋒網(公眾號:雷鋒網)相關閱讀:

Google:機器學習檢測垃圾郵件準確率已達99.9%

python 和 scikit-learn 實現垃圾郵件過濾