01 起
大數(shù)據(jù)時代朵诫,我們的“隱私”早已不再是隱私辛友,一個特別直接的證據(jù)是什么呢?
我們的郵箱也好剪返、手機也好废累,經(jīng)常收到惱人的垃圾郵件、垃圾短信
被這些東西煩的不行脱盲,怎么辦呢邑滨?網(wǎng)上有很多垃圾郵件過濾軟件,可以拿來直接用的钱反,其中的原理是什么呢掖看?
今天我們自己造個輪子來過濾郵箱里的垃圾郵件吧!
系好安全帶面哥,我要開車了哎壳!
02 過濾原理
垃圾郵件過濾的原理其實很簡單:樸素貝葉斯(Naive Bayes)
NB的詳細原理我們在這一文中講過:統(tǒng)計學習方法 | 樸素貝葉斯法,大家可以點擊學習
簡單點尚卫,利用樸素貝葉斯原理過濾垃圾郵件归榕,內(nèi)在邏輯是這樣的:
樸素貝葉斯公式:P(Y|X)=P(X|Y)*P(Y)/P(X)
我們把Y看作郵件分類結果(0不是垃圾郵件、1是垃圾郵件)吱涉,X看作郵件中的各個詞語
于是刹泄,P(Y)表示訓練集中各類郵件出現(xiàn)的概率(條件概率)(大數(shù)定理,即各類郵件出現(xiàn)次數(shù)/總郵件數(shù))怎爵,若訓練樣本較少循签,大數(shù)定理不適用,本文旨在深刻理解NB原理疙咸,因此即使本文中訓練樣本較少县匠,但仍然使用了大數(shù)定理。
P(X)表示郵件某個詞語出現(xiàn)的概率,P(X|Y)表示某類郵件中詞語X出現(xiàn)的概率(先驗概率)
P(Y|X)表示郵件包含X詞語時乞旦,該郵件為Y類的概率(后驗概率)贼穆,是垃圾郵件過濾的結果,我們會將測試樣本X歸類到使得P(Y|X)最大的那個Y類
這就是利用NB進行垃圾郵件分類的原理
03 造輪子
知道了過濾原理兰粉,我們就來寫個算法過濾垃圾郵件吧故痊!
本次郵件過濾使用的樣本為我qq郵箱中的74條郵件,人工標注了是否為垃圾郵件玖姑,其中有26條垃圾郵件愕秫,樣本數(shù)據(jù)長這樣可以看到,樣本數(shù)據(jù)中有較多符號等非文字內(nèi)容焰络,于是我們需要對樣本進行清洗戴甩,總得來說,造輪子主要是一下幾個步驟:
- 讀取樣本數(shù)據(jù)(自己qq郵箱74條郵件)
- 數(shù)據(jù)清洗(去除樣本中的符號等非文字內(nèi)容)
- 數(shù)據(jù)處理(訓練集測試集分割闪彼、文本向量化)
- 調(diào)用sklearn中的NB模型訓練甜孤,評估,優(yōu)化模型(本文重點不在此畏腕,略去)
- 自己寫個NB模型分辨垃圾郵件
數(shù)據(jù)清洗
剛才我們已經(jīng)讀取了樣本數(shù)據(jù)缴川,下面我們進行數(shù)據(jù)清洗,清洗思路如下:
- 去除待處理文本每條文本中的符號描馅、數(shù)字把夸、字母,僅剩中文字符(即以符號铭污、數(shù)字恋日、字母為分隔符,隔開每條文本作文item况凉,構成一個list)
- 去除文本list中長度小于等于1的item
- 將文本list(text[i])每個item分詞(可加載自定義詞典),并放入新的對應index的文本list中(text_word[i])
- 將新的文本list(text_word[i])用空格連接起來并返回
基于此思路各拷,給出以下清洗函數(shù)
def text_format():
import jieba
import re
import pandas as pd
print ("待處理文本格式要求:utf-8編碼格式刁绒,僅包含待處理文本,每行為一條文本")
text_path=input("請輸入待清洗文本路徑+名字:")
#加載用戶自定義詞典用于分詞
userdict_path=input("請輸入自定義分詞詞典路徑+名字(可不輸入):")
if userdict_path !="":
jieba.load_userdict(userdict_path)
#根據(jù)用戶輸入地址烤黍,讀取文件
with open(text_path,"r",encoding="utf-8") as file:
text=file.readlines()
for i in range(len(text)):
text[i]=text[i].strip()
#定義一個空列表知市,用于存放分詞后的文本,長度和text一致
text_word=[[] for i in range(len(text))]
splitter=re.compile(r"\W+|\d+|[a-z]+") #正則匹配速蕊,去除文本中的符號嫂丙、數(shù)字、字母等非中文字符的元素
for i in range(len(text)):
text[i]=splitter.split(text[i].lower())
text[i]=[word for word in text[i] if len(word)>1] #每條文本已經(jīng)被分為一段一段的句子规哲,每條文本此時是一個list跟啤,先去除其中字段長度小于等于1的單詞
for word in text[i]:
text_word[i].extend(jieba.lcut(word))
text_word[i]=" ".join(text_word[i]) #為了便于TfidfVectorizer等文本向量化處理,將每條標題用元素用空格連起來
return text_word
清洗前后效果如下
清洗前
數(shù)據(jù)處理
接下來就是數(shù)據(jù)處理,包括以下步驟:
- 將每條郵件對應的標簽提取出來成為一個list
- 利用train_test_split將清洗后的數(shù)據(jù)與其對應的標簽切分為訓練集隅肥、測試集(這個分割函數(shù)也可以自己寫竿奏,本文略去)
- 構成詞袋模型,記錄各個詞出現(xiàn)的頻率
- CountVectorizer(stop_words=).fit_transform(訓練集)
- CountVectorizer(stop_words=).transform(測試集)
#建立訓練集腥放、測試集
label=emails.type.tolist()
X_train,X_test,Y_train,Y_test=train_test_split(emails_format,label,test_size=0.15,random_state=5)
#加載并處理停用詞典
with open(r"E:\python\data\stopwords.txt","r",encoding="utf-8") as file:
stop_words=file.readlines()
for i in range(len(stop_words)):
stop_words[i]=stop_words[i].strip("\n")
#構成詞袋模型泛啸,記錄各個詞出現(xiàn)的頻率
cv=CountVectorizer(stop_words=stop_words)
X_train_count=cv.fit_transform(X_train)
X_test_count=cv.transform(X_test)
處理后的結果如下,可以看到秃症,訓練集62條郵件候址、測試集12條郵件,共提取出386個文本特征
輪子來了:樸素貝葉斯模型
接下來就是重頭戲了种柑,自定義NB模型岗仑,思路如下:
- 利用各詞出現(xiàn)的頻率和對應標簽概率,訓練NB模型各概率參數(shù)
- 將測試集各特征在訓練集對應的先驗概率帶入計算莹规,得到測試集各樣本后驗概率赔蒲,取后驗概率最大的標簽類別為該測試樣本類別
1. 利用訓練集,訓練概率參數(shù)(拉普拉斯平滑)[類似mnb.fit()]
#先將訓練集的內(nèi)容和標簽合為一個dataframe
d={"content":X_train_count.toarray().tolist(),"label":Y_train}
emails_train=pd.DataFrame(data=d)
#將正常郵件(Y=0)和垃圾郵件(Y=1)分為兩個子集
normal=emails_train[emails_train.label==0]
normal.reset_index(inplace=True,drop=True) #重置normal索引良漱,作用于原表舞虱,丟棄之前的索引
spam=emails_train[emails_train.label==1]
spam.reset_index(inplace=True,drop=True) #重置spam索引,作用于原表母市,丟棄之前的索引
"""計算Y_train=0矾兜、1的條件概率(拉普拉斯平滑)"""
Py0=(len(normal)+1)/(len(emails_train)+2)
Py1=(len(spam)+1)/(len(emails_train)+2)
"""計算X_train各特征向量取各特征值時的先驗概率(拉普拉斯平滑)"""
"""計算垃圾郵件中,各特征向量的先驗概率"""
vd=len(spam.content[0]) #特征向量的維度
spam_count_dict={} #用于保存content特征向量按列累加的結果
spam_count_prob={} #用于保存垃圾郵件中各特征向量出現(xiàn)的概率
#求content各特征向量按列累加的結果,用于計算各向量在訓練集中出現(xiàn)的概率
for i in range(len(spam)):
for j in range(vd):
spam_count_dict[j]=spam_count_dict.get(j,0)+spam.content[i][j] #計算垃圾郵件中各特征向量出現(xiàn)的次數(shù)患久,即椅寺,求content各特征向量count按列累加的結果
for j in range(vd):
spam_count_prob[j]=(spam_count_dict.get(j,0)+1)/(len(spam)+2)#計算垃圾郵件中各特征向量出現(xiàn)的概率(拉普拉斯平滑)
"""計算正常郵件中,各特征向量的先驗概率"""
normal_count_dict={} #用于保存content特征向量按列累加的結果
normal_count_prob={} #用于保存正常郵件中各特征向量出現(xiàn)的概率
#求content各特征向量按列累加的結果,用于計算各向量在訓練集中出現(xiàn)的概率
for i in range(len(normal)):
for j in range(vd):
normal_count_dict[j]=normal_count_dict.get(j,0)+normal.content[i][j] #計算垃圾郵件中各特征向量出現(xiàn)的次數(shù)蒋失,即返帕,求content各特征向量count按列累加的結果
for j in range(vd):
normal_count_prob[j]=(normal_count_dict.get(j,0)+1)/(len(normal)+2)#計算垃圾郵件中各特征向量出現(xiàn)的概率(拉普拉斯平滑)
2. 將測試集各特征向量值帶入訓練的概率參數(shù)中,計算后驗概率篙挽,取使后驗概率最大的Y=ck為測試樣本的分類[類似mnb.predict(), mnb.predict_proba()]
"""計算各測試樣本的后驗概率"""
test_classify={} #用于保存測試集各樣本的后驗概率 P(Y|X)=P(Y)*P(X|Y)/P(X)
Px_spam={} #用于保存測試集各樣本在垃圾郵件下的先驗概率 P(X|Y)
Px_normal={} #用于保存測試集各樣本在正常郵件下的先驗概率 P(X|Y)
for i in range(X_test_array.shape[0]):
for j in range(X_test_array.shape[1]):
if X_test_array[i][j]!=0:
Px_spam[i]=Px_spam.get(i,1)*spam_count_prob.get(j)#計算垃圾郵件下荆萤,各測試樣本的后驗概率
Px_normal[i]=Px_normal.get(i,1)*normal_count_prob.get(j)#計算正常郵件下,各測試樣本的后驗概率
test_classify[i]=Py0*Px_normal.get(i,0),Py1*Px_spam.get(i,0) #后驗概率P(Y|X)=P(Y)*P(X|Y)/P(X)
#比較各樣本屬于不同分類時(正常/垃圾)的后驗概率铣卡,去后驗概率大的為樣本分類結果
results={} #用于存放郵件判定結果
for key,value in test_classify.items():
if value[0]<=value[1]:
results[key]=1
else:
results[key]=0
測試集12條郵件的分類結果如下链韭,其中第二列為該郵件不是垃圾郵件的概率,第三列為是垃圾郵件的概率:3. 計算分類準確率 [類似mnb.score()]
#計算分類準確率
count=0 #計數(shù)煮落,統(tǒng)計被正確分類的郵件數(shù)量
for key,value in results.items():
if value==Y_test[key]:
count+=1
score=count/len(Y_test)
print ("NB模型分類準確率為:{0}%".format(score*100))
輸出結果為:04 總結
本文從樸素貝葉斯原理出發(fā)敞峭,自己寫了一個NB模型,用于濾除個人郵箱中的垃圾郵件蝉仇,拋磚引玉旋讹,希望可以啟發(fā)大家殖蚕。
另外,本文設計的NB模型不足之處在于骗村,訓練樣本太少嫌褪,分類不夠客觀準確。解法其實很簡單:增加樣本量胚股。
當然笼痛,本文重點在于理解NB算法原理,下期實操預告:決策樹實現(xiàn)琅拌。