垃圾分类

机器学习实践:垃圾邮件分类

这个案例是通过训练一个朴素贝叶斯分类器,对垃圾邮件进行判别,使用R语言进行操作。朴素贝叶斯算法是一种简单高效的分类算法,利用贝叶斯定理,通过简单的概率计算对样本进行分类。算法的基本假设是各个事件之间相互独立,假如A与B相互独立,则有:P(AB) = P(A)*P(B)。当有多个事件时,这些事件同时发生的概率就等于这些事件独立发生的概率的连乘积。具体到这个案例,假如一封邮件中出现 one 这个单词的概率为0.0001,出现 two 的概率为0.002,则one 与 two 同时出现的概率为0.0001*0.002

导入包
主要使用文本挖掘的tm 包  

library(tm)

对文件路径进行处理

spam.path = "E:/R/ML/data/classify/spam/"
spam_2.path = "E:/R/ML/data/classify/spam_2/"
easy_ham.path = "E:/R/ML/data/classify/easy_ham/"
easy_ham_2.path = "E:/R/ML/data/classify/easy_ham_2/"
hard_ham.path = "E:/R/ML/data/classify/hard_ham/"
hard_ham_2.path = "E:/R/ML/data/classify/hard_ham_2/"

这里有3类文件,spam是垃圾邮件,easy_ham是易识别的普通邮件,hard_ham是不易识别的正常邮件,本例就是通过训练spam和easy_ham,来识别hardham

先对垃圾邮件进行处理,
获取每个目录下的文档列表,注意事项:”cmds”文件不是邮件

spam.docs = dir(spam.path)
spam.docs = spam.docs[which(spam.docs!="cmds")]

然后从每个文档里面取出正文内容
一般来说,正文内容位于邮件的第一个空白行后面
定义一个获取文档内容的函数

get.msg = function(path){
  #先打开文件

  cons = file(path,open = "rt",encoding = "latin1")
  text = readLines(cons)

  #选取正文:从空白行后的第一行到最后一行
  if(is.na(which(text == "")[1])){
    msg = ""
  }
  else{
    msg = text[seq((which(text == "")[1]+1),length(text))]
  }

  close(cons)

  #返回一个字符串向量,以换行符号进行拼接

  return(paste(msg,collapse = "\n"))
}

通过get.msg函数可以从一个邮件里获取正文
将这个函数应用于spam.docs向量中的每一个文档,
就能获得所有的垃圾邮件的正文

all.spam = sapply(spam.docs,function(p) {get.msg(paste(spam.path,p,sep = ""))})

上面这段代码的意思是:将spam.docs中的每个元素p作为参数
传入函数,这个函数的操作是,拼接完整文档的路径,然后获取
正文,最后返回一个向量

完成词项、文档矩阵

get.tdm = function(doc.vec){

  #建立一个语料库 corpus 对象
  doc.corpus = Corpus(VectorSource(doc.vec))

  #建立词项文档矩阵
  doc.tdm = TermDocumentMatrix(doc.corpus,
                               control = list(stopwords=T,removePunctuation=T,removeNumbers=T,minDocFreq = 2))
  return(doc.tdm)

}

建立垃圾邮件的词项文档矩阵

spam.tdm = get.tdm(all.spam)
spam.matrix = as.matrix(spam.tdm)

在这个矩阵里,列表示文档,行表示词

按行求和,求出每一个词在所有文档中出现的总次数

spam.counts = rowSums(spam.matrix)

现在我们已经知道了在所有的文档中,每个词出现的文档的数目,数目越多说明这个词被使用得越频繁

构建一个词频表,列出每个词的对应频次

spam.df = data.frame(cbind(names(spam.counts),as.numeric(spam.counts)),stringsAsFactors = F)
names(spam.df) = c("term","frequency")
spam.df$frequency = as.numeric(spam.df$frequency)

计算某个词出现的文档在所有文档中的比例

spam.occurrence = sapply(1:nrow(spam.matrix), function(p){length(which(spam.matrix[p,]>0))/ncol(spam.matrix)})

ncol求的是矩阵的列数,也就是文档数,which函数计算出现频数大于0的列,表示出现过

spam.density = spam.df$frequency/sum(spam.df$frequency)

spam.df = transform(spam.df,density = spam.density,occurrence = spam.occurrence)

head(spam.df[with(spam.df,order(-occurrence)),])

接下来用同样的方法处理容易区分的正常邮件(easy_ham)

easy_ham.docs = dir(easy_ham.path)
easy_ham.docs = easy_ham.docs[which(easy_ham.docs!="cmds")]

获取正文

all.easy_ham = sapply(easy_ham.docs,function(p){get.msg(paste(easy_ham.path,p,sep = ""))})

建立词项文档矩阵

easy_ham.tdm = get.tdm(all.easy_ham)
easy_ham.matrix = as.matrix(easy_ham.tdm)
>建立词项词频表
>先计算每个词出现的次数
easy_ham.conuts = rowSums(easy_ham.matrix)
easy_ham.df = data.frame(cbind(names(easy_ham.conuts),as.numeric(easy_ham.conuts)),stringsAsFactors = F)
names(easy_ham.df) = c("term","frequence")
easy_ham.df$frequence = as.numeric(easy_ham.df$frequence)

计算出现某个词的文档数占比

easy_ham.occurrence = sapply(1:nrow(easy_ham.matrix), function(i){length(which(easy_ham.matrix[i,]>0))/ncol(easy_ham.matrix)})

计算词频密度

easy_ham.density = easy_ham.df$frequence/sum(easy_ham.df$frequence)

整合数据

easy_ham.df = transform(easy_ham.df,density = easy_ham.density,occurrence = easy_ham.occurrence)
head(easy_ham.df[with(easy_ham.df,order(-occurrence)),])

定义分类器

classify.email = function(path,train.df,prior = 0.5,c = 1e-6){
  msg = get.msg(path)
  msg.tdm = get.tdm(msg)
  msg.freq = rowSums(as.matrix(msg.tdm))

  #prior表示经验概率,或者说是一个权重,用来表示在随机猜测的情况下猜出是垃圾邮件的概率
  # c是一个调整值,用来表示不存在的词的概率,这个值可以根据拉普拉斯法进行调整  

  #找出测试集与训练集相同的词

  msg.match = intersect(names(msg.freq),train.df$term)

  #假如没有交集,则使用很小的概率
  if(length(msg.match)

使用不易识别的邮件进行预测

hard_ham.docs = dir(hard_ham.path)
hard_ham.docs = hard_ham.docs[which(hard_ham.docs!="cmds")]

使用垃圾邮件作为训练集预测

hard_ham_spamtest = sapply(hard_ham.docs, function(p){
  classify.email(path = file.path(hard_ham.path,p),train.df = spam.df)
})

使用正常邮件作为训练集

hard_ham_hamtest = sapply(hard_ham.docs, function(p){
  classify.email(path = file.path(hard_ham.path,p),train.df = easy_ham.df)
})

作出判断

hardham.res = ifelse(hard_ham_spamtest>hard_ham_hamtest,T,F)
summary(hardham.res)
 FALSE    TRUE    NA's 
 245       4       0 
在所有的不易分辨的正常邮件中,被分类为垃圾邮件的有4个

将上面的操作封装成一个函数,以便一次执行

spam.classifier = function(path){
  pr.spam = classify.email(path,spam.df)
  pr.ham = classify.email(path,easy_ham.df)
  return(c(pr.spam,pr.ham,ifelse(pr.spam>pr.ham,1,0)))
}

使用第二组不易区分的邮件进行预测

hard_ham2_docs = dir(hard_ham_2.path)
hard_ham2_docs = hard_ham2_docs[which(hard_ham2_docs!="cmds")]

class.hardham2 = lapply(hard_ham2_docs, function(p){
  spam.classifier(path = file.path(hard_ham_2.path,p))
})

class.df = as.data.frame(do.call(rbind,class.hardham2))
names(class.df) = c("pr.spam","pr.ham","spam")
table(class.df$spam)
  0   1 
243   5 
将5封难以识别的正常邮件误判为垃圾邮件
准确率有点高,可能原因是训练样本中正常邮件的比例比垃圾邮件大,这方面需要进行调整

这个例子简单地构建一个朴素贝叶斯分类器,并没有进行优化。不过从整个过程看,这个案例的难点主要在于数据的整理:按照目录读取文本、提取正文、分词、建立词项文档矩阵等等,可见在真实项目中,数据处理的过程极重要。

  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值