谢谢指正学习
因为最近的课设,在网上学习了一下垃圾邮件过滤
网上存在的基于贝叶斯和SVM的垃圾邮件过滤方法
在原文http://www.kdnuggets.com/2017/03/email-spam-filtering-an-implementation-with-python-and-scikit-learn.html中学习了使用Python的机器学习库,进行对垃圾邮件的甄别
以下是从网站学习的源代码+自己的理解与分析
# -*- coding: utf-8 -*-
import os #用于文件操作
import collections #用于统计操作
import numpy as np#用于对二维列表的操作,导包中使用as是改变模块的引用对象名字
from sklearn.naive_bayes import MultinomialNB
from sklearn.svm import LinearSVC
from sklearn.metrics import confusion_matrix
def make_Dictionary(tran_dir):
#获取到参数文件夹下的所有文件名
emails = [os.path.join(tran_dir,f) for f in os.listdir(tran_dir)]
#定义一个Counter类,存储单词和出现次数
dictionary = collections.Counter()
# 定义一个空列表,用于存储所有的单词
all_words = []
for mail in emails:
with open(mail) as m:
for i,line in enumerate(m):
if i==2:
words=line.split()
#将字符以空格分割之后,保存在all_words列表中
all_words += words
#返回一个字典,包含了all_words中的所有对象以及对应的数量
# dictionary.update(collections.Counter(all_words))
dictionary = collections.Counter(all_words)
#获取到字典中的所有主键,放在list_to_remove列表中
list_to_remove = dictionary.keys()
# print(type(all_words))
# print(type(list_to_remove))
#python2中keys()返回的列表,Python3中返回的属性是dict_keys,所以list转换一下
for item in list(list_to_remove):
#使用字符串方法isalpha()判断该主键是否全部为字母组成,如果不是,从字典删除
if item.isalpha() == False:
del dictionary[item]
#使用字符串len()方法,判断主键是一个字符时,从字典中删除
elif len(item)==1:
del dictionary[item]
#返回一个排序后的列表。从次数高到次数低。如果n没有被指定,则返回所有元素。当多个元素计数值相同时,排列是无确定顺序的。
dictionary = dictionary.most_common(3000)
# print(all_words)
# print(dictionary)
#返回字典,即此方法是返回文件夹查找之后的单词,次数的字典
return dictionary
train_dir = 'ling-spam\\train-mails'
dictionary = make_Dictionary(train_dir)
#此方法用于生成一个特征向量矩阵,即将文件夹路径传入
# 生成,行为文件,列为词典中的3000个单词,这样一个二维列表
# [ij]表示第i个文件中,第j个单词出现的次数
def extract_features(mail_dir):
#构建一个文件列表
files = [os.path.join(mail_dir,fi) for fi in os.listdir(mail_dir)]
#zeros返回一个二维数组,行数是文件数量,列数是3000,代表词典中前3000个常用词,默认填充为0
features_matrix = np.zeros((len(files),3000))
#定义一个行标,i
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:
#遍历每个词,并定义一个列表,j
wordID = 0
#通过变量i递增,enumerate返回一个enumerate对象,在字典中返回键值组成的索引序列。
for i,d in enumerate(dictionary):
#d[0]表示键值
if d[0] == word:
#将索引值赋给列标
wordID = i
#正文中的每一个词进行计数,出现的次数便是,二维列表的[i,j]出值
features_matrix[docID,wordID] = words.count(word)
docID = docID + 1
#返回对文件夹中文件的分析列表
return features_matrix
#返回一个由0填充,长度为702的列表
train_labels = np.zeros(702)
#对于初始来说,正常邮件和垃圾邮件的概率为1/2,所以将后半列表定义为1
train_labels[351:701] = 1
#生成一个由训练文件而产生的分析表
#这是为后面的学习构建一个模型
train_matrix = extract_features(train_dir)
#训练SVM和贝叶斯模式
#朴素贝叶斯分类器
# 条件概率P(A|B),表示事件B发生的条件下,事件A发生的概率
#全概率公式,P(A)=∑[P(Bi)P(A|Bi)],表示在所有因素Bi的影响下,事件A发生的概率
#贝叶斯公式,P(Bi|A),表示事件A发生时,由因素Bi引发的概率
model1 = MultinomialNB()
#支持向量机
#通常希望一个分类的过程是一个机器学习的过程。对于一些数据点,存在n维空间中,我们希望有一个n-1维的超平面去将数据点分为两类
#或许存在不止一个超平面能够将数据按照要求分为两类,但是还是希望能找到分类最佳的平面,即使得两个不同类的数据点间隔最大的那个超平面
#支持向量机将向量(数据点),映射到一个更高维的空间中,并在这个空间里建立一个最大间隔超平面
#在分开数据的超平面的两侧建立两个相互平行的超平面,这两个平行平面的间隔越大,分类器的总误差越小
#SVM的关键在于核函数。低维空间向量集通常难于划分,解决的方法是将它们映射到高维空间。
#但这个办法带来的困难就是计算复杂度的增加,而核函数正好巧妙地解决了这个问题。
#也就是说,只要选用适当的核函数,就可以得到高维空间的分类函数。在SVM理论中,采用不同的核函数将导致不同的SVM算法。
model2 = LinearSVC()
#fit函数表示训练某种模型。函数返回值一般为调用fit方法的对象本身
model1.fit(train_matrix,train_labels)
model2.fit(train_matrix,train_labels)
#测试
#test_dir = 'ling-spam\\test1'
test_dir = 'ling-spam\\test-mails'
test_matrix = extract_features(test_dir)
# test_labels = np.zeros(6)
# test_labels[3:6] = 1
test_labels = np.zeros(260)
test_labels[130:260] = 1
#根据所训练的模型,进行预测
result1 = model1.predict(test_matrix)
result2 = model2.predict(test_matrix)
#confusion_matrix混淆矩阵,用于呈现算法性能的可视化效果
#通过对角来判定准确率,其余为偏差量
print('ham spam')
print(confusion_matrix(test_labels,result1))
print(confusion_matrix(test_labels,result2))
在源链接中存有原作者上传的语料库,我这重新上传了一份到百度云里,解压在项目文件夹中便可http://pan.baidu.com/s/1qXQZ7GC
接下来的是通过学习http://blog.csdn.net/shijing_0214/article/details/51200965博客的通过基于朴素贝叶斯的垃圾邮件过滤,相对于使用了机器学习库的实现来说,该文更为通俗易懂,且含有具体的分析过程,在实验中,发现通过该垃圾邮件分析的垃圾邮件在上述的机器学习中的贝叶斯模式能判断为垃圾邮件,但是SVM模型将其判断为正常邮件
以下是该博主在GitHub的文件链接https://github.com/shijing888/BayesSpam
通过使用jieba分词器,需要提前下载库https://pypi.python.org/pypi/jieba/
以下是博主的主要代码:
#spamEmailBayes.py
#encoding=utf-8
'''
Created on 2016年4月18日
@author: lenovo
'''
import jieba;
import os;
class spamEmailBayes:
#获得停用词表
def getStopWords(self):
stopList=[]
for line in open("../data/中文停用词表.txt"):
stopList.append(line[:len(line)-1])
return stopList;
#获得词典
def get_word_list(self,content,wordsList,stopList):
#分词结果放入res_list
res_list = list(jieba.cut(content))
for i in res_list:
if i not in stopList and i.strip()!='' and i!=None:
if i not in wordsList:
wordsList.append(i)
#若列表中的词已在词典中,则加1,否则添加进去
def addToDict(self,wordsList,wordsDict):
for item in wordsList:
if item in wordsDict.keys():
wordsDict[item]+=1
else:
wordsDict.setdefault(item,1)
def get_File_List(self,filePath):
filenames=os.listdir(filePath)
return filenames
#通过计算每个文件中p(s|w)来得到对分类影响最大的15个词
def getTestWords(self,testDict,spamDict,normDict,normFilelen,spamFilelen):
wordProbList={}
for word,num in testDict.items():
if word in spamDict.keys() and word in normDict.keys():
#该文件中包含词个数
pw_s=spamDict[word]/spamFilelen
pw_n=normDict[word]/normFilelen
ps_w=pw_s/(pw_s+pw_n)
wordProbList.setdefault(word,ps_w)
if word in spamDict.keys() and word not in normDict.keys():
pw_s=spamDict[word]/spamFilelen
pw_n=0.01
ps_w=pw_s/(pw_s+pw_n)
wordProbList.setdefault(word,ps_w)
if word not in spamDict.keys() and word in normDict.keys():
pw_s=0.01
pw_n=normDict[word]/normFilelen
ps_w=pw_s/(pw_s+pw_n)
wordProbList.setdefault(word,ps_w)
if word not in spamDict.keys() and word not in normDict.keys():
#若该词不在脏词词典中,概率设为0.4
wordProbList.setdefault(word,0.4)
sorted(wordProbList.items(),key=lambda d:d[1],reverse=True)[0:15]
return (wordProbList)
#计算贝叶斯概率
def calBayes(self,wordList,spamdict,normdict):
ps_w=1
ps_n=1
for word,prob in wordList.items() :
print(word+"/"+str(prob))
ps_w*=(prob)
ps_n*=(1-prob)
p=ps_w/(ps_w+ps_n)
# print(str(ps_w)+"////"+str(ps_n))
return p
#计算预测结果正确率
def calAccuracy(self,testResult):
rightCount=0
errorCount=0
for name ,catagory in testResult.items():
if (int(name)<1000 and catagory==0) or(int(name)>1000 and catagory==1):
rightCount+=1
else:
errorCount+=1
return rightCount/(rightCount+errorCount)
定义的一个类,计算各词在文件中的概率
#encoding=utf-8
'''
Created on 2016年4月18日
@author: lenovo
'''
from spam.spamEmail import spamEmailBayes
import re
#spam类对象
spam=spamEmailBayes()
#保存词频的词典
spamDict={}
normDict={}
testDict={}
#保存每封邮件中出现的词
wordsList=[]
wordsDict={}
#保存预测结果,key为文件名,值为预测类别
testResult={}
#分别获得正常邮件、垃圾邮件及测试文件名称列表
normFileList=spam.get_File_List(r"F:\pycharm\Study\BayesSpam-master\BayesSpam-master\data\normal")
spamFileList=spam.get_File_List(r"F:\pycharm\Study\BayesSpam-master\BayesSpam-master\data\spam")
#testFileList=spam.get_File_List(r"F:\pycharm\Study\BayesSpam-master\BayesSpam-master\data\test")
testFileList=spam.get_File_List(r"F:\pycharm\Study\BayesSpam-master\BayesSpam-master\data\test1")
#获取训练集中正常邮件与垃圾邮件的数量
normFilelen=len(normFileList)
spamFilelen=len(spamFileList)
#获得停用词表,用于对停用词过滤
stopList=spam.getStopWords()
#获得正常邮件中的词频
for fileName in normFileList:
wordsList.clear()
for line in open("../data/normal/"+fileName):
#过滤掉非中文字符
rule=re.compile(r"[^\u4e00-\u9fa5]")
line=rule.sub("",line)
#将每封邮件出现的词保存在wordsList中
spam.get_word_list(line,wordsList,stopList)
#统计每个词在所有邮件中出现的次数
spam.addToDict(wordsList, wordsDict)
normDict=wordsDict.copy()
#获得垃圾邮件中的词频
wordsDict.clear()
for fileName in spamFileList:
wordsList.clear()
for line in open("../data/spam/"+fileName):
rule=re.compile(r"[^\u4e00-\u9fa5]")
line=rule.sub("",line)
spam.get_word_list(line,wordsList,stopList)
spam.addToDict(wordsList, wordsDict)
spamDict=wordsDict.copy()
# 测试邮件
for fileName in testFileList:
testDict.clear( )
wordsDict.clear()
wordsList.clear()
for line in open("../data/test1/" + fileName):
# for line in open("../data/test/"+fileName):
rule=re.compile(r"[^\u4e00-\u9fa5]")
line=rule.sub("",line)
spam.get_word_list(line,wordsList,stopList)
spam.addToDict(wordsList, wordsDict)
testDict=wordsDict.copy()
#通过计算每个文件中p(s|w)来得到对分类影响最大的15个词
wordProbList=spam.getTestWords(testDict, spamDict,normDict,normFilelen,spamFilelen)
#对每封邮件得到的15个词计算贝叶斯概率
p=spam.calBayes(wordProbList, spamDict, normDict)
if(p>0.9):
testResult.setdefault(fileName,1)
#垃圾邮件
else:
testResult.setdefault(fileName,0)
#正常邮件
#计算分类准确率
testAccuracy=spam.calAccuracy(testResult)
for i,ic in testResult.items():
print(i+"/"+str(ic))
print(testAccuracy)
通过对语料库中的每份垃圾邮件和正常邮件进行一个计算,得出一个垃圾邮件的词频和正常邮件的词频,对于测试的文件使用的出的词频进行分析,得出邮件的测试结果。
对下载的语料库的data进行解压的路径要注意一下
最后非常感谢这次机会对于朴素贝叶斯和SVM支持向量机的一个学习,也感谢一些好心的分享,这次野同样加深了自己的Python操作。