使用朴素贝叶斯过滤垃圾邮件
1.问题描述:
现有50封电子邮件,存放在数据集task1中,试基于朴素贝叶斯分类器原理,用Python编程实现对垃圾邮件和正常邮件的分类。采用交叉验证方式并且输出分类的错误率及分类错误的文档。
2.实验原理:
-
朴素贝叶斯:
基于概率论的分类方法:朴素贝叶斯,要求分类器给出一个最优的类别猜测结果,同时给出这个猜测的概率估计值。一种有效计算条件概率的方法称为贝叶斯准则,贝叶斯准则告诉我们如何交换条件概率中的条件与结果,即如果已知$ P(x|c)$,要求 P ( c ∣ x ) P(c|x) P(c∣x),那么可以使用下面的计算方法:
P ( c ∣ x ) = P ( x ∣ c ) P ( c ) P ( x ) P(c \mid x)=\frac{P(x \mid c) P(c)}{P(x)} P(c∣x)=P(x)P(x∣c)P(c)于是,可以定义贝叶斯分类准则为:若那么属于类别若$ P(c1|x,y)>P(c2|x,y)$, 那么属于类别 C1
若那么属于类别若 P ( c 1 ∣ x , y ) < P ( c 2 ∣ x , y ) P(c1|x,y)<P(c2|x,y) P(c1∣x,y)<P(c2∣x,y), 那么属于类别 C2
使用贝叶斯准则,可以通过已知的三个概率值来计算未知的概率值。
-
由词向量计算:
由于这里的特征是词向量,于是重写贝叶斯准则,将之前的x、y 替换为w。粗体w表示这是一个向量,即它由多个数值组成,长度N为词典长度。在词袋模型中,数值个数与词典中的词个数相同,贝叶斯准则公式如下:
P ( c i ∣ w ) = P ( w ∣ c i ) P ( c i ) P ( w ) P(ci|w)=P(w|ci)P(ci)P(w) P(ci∣w)=P(w∣ci)P(ci)P(w)
我们使用上述公式,对每个类别计算该值,然后比较这两个概率值的大小。首先通过类别(垃圾邮件或正常邮件)中样本数除以总的样本数来计算概率$ P(ci)$,接下里计算 P ( w ∣ c i ) P(w|ci) P(w∣ci) ,这里使用朴素贝叶斯假设,若将w展开为一个个独立特征,那么就可以将上述概率写作$ P(w0,w1,w2,…wN|ci)$ ,这里假设所有词都互相独立,该假设也称作条件独立性假设,它意味着可以使用 如下公式计算上述概率:
P ( w ∣ c i ) = P ( w 0 ∣ c i ) P ( w 1 ∣ c i ) P ( w 2 ∣ c i ) . . . P ( w N ∣ c i ) P(w|ci)=P(w0|ci)P(w1|ci)P(w2|ci)...P(wN|ci) P(w∣ci)=P(w0∣ci)P(w1∣ci)P(w2∣ci)...P(wN∣ci)
这样就极大的简化了计算过程。 -
具体步骤:
-
设D是词向量集合,每个词向量用一个N维向量 $w={w0,w1,w2,…,wN} $表示。
-
假定有m个类 c i = { c 1 , c 2 , . . . , c m } ci=\{c1,c2,...,cm\} ci={c1,c2,...,cm},给定词向量w,分类法将预测w属于最高后验概率的类,即,朴素贝叶斯分类法预测w属于类ci,当且仅当
P ( c i ∣ w ) > P ( c j ∣ w ) , 1 ≤ j ≤ m , j ≠ i P(ci|w)>P(cj|w), 1≤j≤m,j≠i P(ci∣w)>P(cj∣w),1≤j≤m,j=i
其中,P(ci|w) 最大的类 ci 称为最大后验概率。 -
做条件独立性假设,于是将公式简化:
P ( w ∣ c i ) = ∏ k = 1 N w k ∣ c i P\left(w \mid c_{i}\right)=\prod_{k=1}^{N} w_{k} \mid c_{i} P(w∣ci)=k=1∏Nwk∣ci -
训练:利用公式,对每个词向量每个类别分别计算条件概率,得出 P 0 v , P 1 v , P s p a m P0v,P1v,Pspam P0v,P1v,Pspam ,分类器训练完成。
-
应用:利用贝叶斯分类准则,对预测词向量分别计算$ P0,P1$ ,选择最大概率对应的分类作为目标预测结果。
3.代码实现:
-
数据处理:
1)需要从文本中获取特征,需要拆分文本,分成词条
2)获取数据字典
3)向量化处理,方便进行特征提取
实现:
-
分词:
def textParse(bigString): import re listOfTokens=re.split('\W',bigString) #匹配非字母数字下划线 return [tok.lower() for tok in listOfTokens if len(tok)>2] #取出掉长度过短的单词
-
获取数据字典:
def creatVocabList(dataset): vocabSet=set([]) for document in dataset: vocabSet=vocabSet|set(document) #两个集合的并集 return list(vocabSet)
-
向量化处理:
这里可以使用词集模型或者词袋模型:
1)词集模型:只关注token是否出现,并不关注出现的次数。
def setOfWords2Vec(vocabList, inputSet): returnVec = [0]*len(vocabList) for word in inputSet: if word in vocabList: return Vec[vocabList.index(word)] = 1 return returnVec
2)词袋模型:关注token的出现次数,有较好的处理结果
#词袋模型 def bagOfWords2Vec(vocabList,inputSet): #参数分别为词汇表,输入文档 returnVec=[0]*len(vocabList) for word in inputSet: if word in vocabList: returnVec[vocabList.index(word)]+=1 return returnVec
-
-
训练模型:
朴素贝叶斯是基于概率的分类器,其训练过程就是计算各个概率的过程。
流程为:
对每篇训练文档: ===对每个类别: ======如果词条出现在文档中,则增加该词条的计数 ======增加所有出现词条的计数值 ===对每个类别: ======对每个词条: =========将该词条的数目除以总词条的数目得到条件概率 ===返回每个类别的条件概率
代码:
#朴素贝叶斯分类函数 def classifyNB(vec2Classify,p0Vec,p1Vec,pClass): #参数分别为:要分类的向量以及使用trainNB0()计算得到的三个概率 p1=sum(vec2Classify*p1Vec)+np.log(pClass) p0=sum(vec2Classify*p0Vec)+np.log(1-pClass) if p1>p0: return 1 else: return 0
-
测试:
在50封邮件中,正常邮件和垃圾邮件各占25,在在其中随机抽取10封电子邮件,预测其所在的类别。
#测试算法:使用朴素贝叶斯交叉验证。同时保存分类模型的词汇表以及三个概率值,避免判断时重复求值 def spamTest(): docList = [] # 文档(邮件)矩阵 classList = [] # 类标签列表 for i in range(1, 26): wordlist = textParse(open('spam/{}.txt'.format(str(i))).read()) docList.append(wordlist) classList.append(1) wordlist = textParse(open('ham/{}.txt'.format(str(i))).read()) docList.append(wordlist) classList.append(0) vocabList = creatVocabList(docList) # 所有邮件内容的词汇表 import pickle file=open('vocabList.txt',mode='wb') #存储词汇表 pickle.dump(vocabList,file) file.close() # 对需要测试的邮件,根据其词表fileWordList构造向量 # 随机构建40训练集与10测试集 trainingSet = list(range(50)) testSet = [] for i in range(10): randIndex = int(np.random.uniform(0, len(trainingSet))) testSet.append(trainingSet[randIndex]) del (trainingSet[randIndex]) trainMat = [] # 训练集 trainClasses = [] # 训练集中向量的类标签列表 for docIndex in trainingSet: # 使用词袋模式构造的向量组成训练集 trainMat.append(bagOfWords2Vec(vocabList, docList[docIndex])) trainClasses.append(classList[docIndex]) p0v,p1v,pAb=trainNB0(trainMat,trainClasses) file=open('threeRate.txt',mode='wb') #用以存储分类器的三个概率 pickle.dump([p0v,p1v,pAb],file) file.close() errorCount=0 for docIndex in testSet: wordVector=bagOfWords2Vec(vocabList,docList[docIndex]) if classifyNB(wordVector,p0v,p1v,pAb)!=classList[docIndex]: errorCount+=1 return float(errorCount)/len(testSet)
4.测试结果:
由于测试集的生成是随机的,所以分类器误分率每次运行结果不一致。通过运行10次的平均值作为分类器的分类效果: