本期主要是實(shí)操在R語(yǔ)言中如何使用樸素貝葉斯算法實(shí)現(xiàn)對(duì)中文垃圾郵件的分類,并嘗試優(yōu)化模型分類效果。本文中所用到的數(shù)據(jù)均為真實(shí)的中文郵件文本數(shù)據(jù),因此整個(gè)過(guò)程十分貼近真實(shí)的操作場(chǎng)景,能夠幫助我們更深入的理解和掌握在R語(yǔ)言中如何進(jìn)行中文文本處理和如何使用樸素貝葉斯算法進(jìn)行分類。關(guān)于算法原理本文將不作介紹,需要了解的同學(xué)可以百度一下,網(wǎng)上有許多非常深入和詳細(xì)解讀。
數(shù)據(jù)基本情況
在開(kāi)始進(jìn)行文本分類之前我們需要了解一下數(shù)據(jù)的基本情況以便我們理清數(shù)據(jù)處理的思路,這是非常重要的一步,對(duì)數(shù)據(jù)結(jié)構(gòu)有了清晰的認(rèn)識(shí)才能夠事半功倍。數(shù)據(jù)獲?。篽ttps://trec.nist.gov/data/spam.html,下載2006垃圾郵件語(yǔ)料庫(kù),其中的trec06c文件為本文中使用的數(shù)據(jù)
首先,我們導(dǎo)入一份郵件,來(lái)看看這份數(shù)據(jù)文件內(nèi)的中文郵件長(zhǎng)什么樣子。
setwd("~/Desktop/R/python/email/trec06c")
email_exm <- read.table("data/001/005",fill=TRUE,fileEncoding = "GB18030",sep = "|")
該文件內(nèi)包含了一份郵件所應(yīng)該的全部信息,然而我們實(shí)際需要的只是其中郵件的正文部分,其他的信息都是基本用不到,是要被清理掉的,這其中主要是一些英文字符、特殊符號(hào)以及數(shù)字。另外,為了方便后續(xù)對(duì)中文文本進(jìn)行分詞的處理,我們需要將讀入多行的數(shù)據(jù)合并成一行。
粗略閱讀該郵件的內(nèi)容,發(fā)現(xiàn)這是一份翻譯公司的推銷郵件, 顯然這是一份垃圾郵件。full文件夾中的index文件中標(biāo)記了郵件是否為垃圾郵件,我們將index文件導(dǎo)入,查看一下這份數(shù)據(jù)的結(jié)構(gòu)。
library(dplyr)
email_class_full <- read.table("full/index",fill=TRUE,fileEncoding = "GB18030",col.names = c("type","path"))
str(email_class_full)
prop.table(table(email_class_full$type))
head(email_class_full)
filter(email_class_full,path == "../data/001/005")
index文件包含了所有郵件的文件路徑以及郵件的是否為垃圾郵件,一共64620行數(shù)據(jù),即64620份郵件,其中非垃圾郵件占比為33.68%,垃圾郵件占比66.32%;另外我們查看了data/001/005的類型,為spam,其的確為垃圾郵件。后續(xù)的數(shù)據(jù)處理中我們將隨機(jī)抽取其中的10000份郵件,通過(guò)index中的文件路徑將抽樣的文本導(dǎo)入。
文本數(shù)據(jù)導(dǎo)入
library(dplyr)
library(plyr)
library(stringr)
#隨機(jī)抽取10000條數(shù)據(jù)
email_class_full <- email_class_full[sample(1:length(email_class_full$type),size=10000),]
#準(zhǔn)備for循環(huán)中用到的變量
warn_path = c()
emails = c()
num <- length(email_class_full$type)
#逐一讀入文件
for(i in 1:num){
#截取郵件的文件路徑
path <- str_sub(email_class_full[i,2],4)
#個(gè)別郵件包含無(wú)法識(shí)別的特殊字符無(wú)法讀入,將其抓取出來(lái)
warn <-tryCatch(
{text <- read.table(path,fill=TRUE,fileEncoding = "GB18030",colClasses ="character",sep = "|")},
warning = function(w){"warning"}
)
if(warn == "warning"){ warn_path <- c(warn_path,as.character(email_class_full[i,2]))}
#去除文本中的英文字符、特殊字符
arrange_text <- gsub("[a-zA-Z]","",as.character(warn)) %>%
gsub('[\\(\\.\\\\)-<\n-=@\\>?_]',"",.)
#將處理后的文本保存
emails <- c(emails,arrange_text)
}
normal_emails <- mutate(email_class_full,text = emails,type = factor(type)) %>% filter(.,text !="")
warn_emails <- mutate(email_class_full,text = emails) %>% filter(.,text =="")
隨機(jī)抽取10000份郵件導(dǎo)入并進(jìn)行初步的數(shù)據(jù)清洗后,我們查看一下導(dǎo)入后的數(shù)據(jù)情況?;旧衔谋緝?nèi)容均是我們所需要的漢字正文,可以進(jìn)行下一步的分詞提取關(guān)鍵詞了。
文本分詞處理
在用樸素貝葉斯算法進(jìn)行郵件分類之前,需對(duì)整段的中文文本進(jìn)行分詞,并提取關(guān)鍵詞。本文中用的中文分詞包為jiebaR,分詞后的數(shù)據(jù)格式整理用的是tm包,如果是處理英文文本,則可直接使用tm包,其十分強(qiáng)大,能夠很方便的處理英文文本,但是對(duì)中文文本的支持則沒(méi)有那么完善。
#分詞并提取關(guān)鍵詞
engine <- worker()
keys = worker("keywords",topn=20)
clean_word <- function(data){
return(paste(unique(vector_keywords(segment(data$text,engine),keys)),collapse=" "))
}
email_words <- ddply(normal_emails,.(type,path),clean_word) %>% rename(.,replace = c("V1" = "words"))
#建立語(yǔ)料庫(kù)
emails_corpus <- Corpus(VectorSource(email_words$words)) %>% tm_map(.,stripWhitespace)
#創(chuàng)建文檔-單詞矩陣
emails_dtm <- DocumentTermMatrix(emails_corpus)
垃圾郵件分類
在文本數(shù)據(jù)處理完后,則可以開(kāi)始進(jìn)行垃圾郵件分類了!本文中進(jìn)行垃圾郵件分類用的是e1071包。
library(dplyr)
library(plyr)
library(stringr)
library(tm)
library(jiebaR)
library(e1071)
#隨機(jī)抽取70%的數(shù)據(jù)作為訓(xùn)練集,剩下的30%作為測(cè)試集
train_row <- sample(1:length(email_words$type),size = floor((length(email_words$type) *0.7)))
email_words_train <- email_words[train_row,]
email_words_test <- email_words[-train_row,]
emails_dtm_train <- emails_dtm[train_row,]
emails_dtm_test <- emails_dtm[-train_row,]
emails_corpus_train <- emails_corpus[train_row]
emails_corpus_test <- emails_corpus[-train_row]
#選取詞頻>=5的詞匯
emails_words_dict <- findFreqTerms(emails_dtm_train,5)
emails_corpus_freq_train <- DocumentTermMatrix(emails_corpus_train,list(dictionry = emails_words_dict))
emails_corpus_freq_test <- DocumentTermMatrix(emails_corpus_test,list(dictionry = emails_words_dict))
#將訓(xùn)練集和測(cè)試集中的詞用0,1分別標(biāo)記在文本中未出現(xiàn)、出現(xiàn)某一詞匯
convert_counts <- function(x){
x <- ifelse(as.numeric(as.character(x))>0,1,0)
x <- factor(x,levels = c(0,1),labels = c("No","Yes"))
return(x)
}
emails_corpus_convert_train <- apply(emails_corpus_freq_train,MARGIN = 2,convert_counts)
emails_corpus_convert_test <- apply(emails_corpus_freq_test,MARGIN = 2,convert_counts)
#利用樸素貝葉斯算法進(jìn)行分類
emails_naiveBayes <- naiveBayes(emails_corpus_convert_train,email_words_train$type,laplace = 1)
#測(cè)試分類的效果
emails_predict <- predict(emails_naiveBayes,emails_corpus_convert_test)
我們來(lái)看一下分類器對(duì)測(cè)試集的分類效果,
library(gmodels)
CrossTable(emails_predict,email_words_test$type)
926條非垃圾郵件數(shù)據(jù)中的14條被誤分為垃圾郵件,占比1.5%;1464條垃圾郵件數(shù)據(jù)中的1295條被正確分類,占比88.5%。詳細(xì)情況如下:
分類過(guò)程中的嘗試
在得到最終的分類結(jié)果之前,還進(jìn)行了一些嘗試,得到分類器效果均不及最終的分類結(jié)果,具體情況如下:
1.在分詞后,關(guān)鍵詞選取文本中出現(xiàn)的名詞、形容詞作為輸入模型的數(shù)據(jù),得到的分類器將926條非垃圾郵件數(shù)據(jù)中的30條誤分為垃圾郵件,占比3.2%;1464條垃圾郵件數(shù)據(jù)中的1202條被正確分類,占比83.3%。
2.以TF-IDF值篩選關(guān)鍵詞,但是訓(xùn)練分類器時(shí)未加入拉普拉斯平滑,得到的分類器將926條非垃圾郵件數(shù)據(jù)中的20條誤分為垃圾郵件,占比2.2%;1464條垃圾郵件數(shù)據(jù)中的1230條被正確分類,占比84%。
以上是在R語(yǔ)言用樸素貝葉斯算法進(jìn)行垃圾郵件分類的全過(guò)程。如有做的不好或這不對(duì)的地方還請(qǐng)大家指正!





