《机器学习实战》学习笔记之朴素贝叶斯(Naive Bayes)

本文介绍了如何使用朴素贝叶斯算法进行垃圾邮件检测。通过计算单词在正常邮件和垃圾邮件中出现的概率,判断邮件的类别。在实现过程中,改进了原始代码,添加了中文注释,并提供了运行示例数据的下载链接。
摘要由CSDN通过智能技术生成

原理

假如邮箱中有n个单词,

如果returnVec[i]=0代表这个单词在这封邮件中不出现,

returnVec[i]=1代表这个单词在邮件中出现了。

设训练集中每个邮件都有标记为是垃圾邮件和不是垃圾邮件,是垃圾邮件的分类为1,不是垃圾邮件的分类为0。

算法原理:

提取邮件内单词,改写为小写单词输入字典,过滤长度不大于2的单词。

利用词汇表计算出单词属于正常词汇的概率p0V=(正常邮件中该单词出现次数)/(正常邮件数量)。

辱骂性词汇的概率p1V=(垃圾邮件中该单词出现次数)/(垃圾邮件数量)。

后比较p0V和p1V的概率大小,如果p0V大于p1V就认为他属于正常邮件,如果p1V大于p0V就认为他属于垃圾邮件。

pb为(对应类别的邮件数)/(所有邮件总数)。

文章中的改进:设定初始一行的单词数计数为2,是为了防止下溢,避免因为某个特性下概率为0。计算概率使用了log函数简化乘除法。

对原文章中的代码进行些许改进,添加了中文注释,加入了主函数,能够直接运行出结果。因为能力有限,有部分可能解释不清,希望大牛批评指正!

代码如下:

# -*- coding: utf-8 -*-
from numpy import *
def loadDataSet():
    #记录数据集,使用python的list,一共是6条,每条又包含了若干条
    postingList = [['my', 'dog', 'has', 'flea', 'problems', 'help', 'please'],
                   ['maybe', 'not', 'take', 'him', 'to', 'dog', 'park', 'stupid'],
                   ['my', 'dalmation', 'is', 'so', 'cute', 'I', 'love', 'him'],
                   ['stop', 'posting', 'stupid', 'worthless', 'garbage'],
                   ['mr', 'licks', 'ate', 'my', 'steak', 'how', 'to', 'stop', 'him'],
                   ['quit', 'buying', 'worthless', 'dog', 'food', 'stupid']]
    #分类向量,1 代表侮辱性文字,0代表正常言论。
    classVec = [0,1,0,1,0,0] 
    #返回的第一个变量是进行词条切分后的文档集合,第二个变量是一个类别标签的集合  
    return postingList,classVec

#创建一个包含在所有文档中出现的不重复词的列表
def createVocabList(dataSet):
    #创建一个空集
    vocabSet = set([])
    #循环实现如果这个词出现在数据集中,就创建两个集合的并集。重复的词只会被加入进去一次。也就实现了只存储不重复词的功能
    for document in dataSet:
        vocabSet = vocabSet | set(document)#创建两个集合的并集
    #返回这个词汇列表
    return list(vocabSet)

#这个函数实现的功能:将两个词汇表进行对比,如果输入的数据集在词汇表中,就让向量值为1,否则输出该词不在文档中。
#输入的参数为词汇表及某个文档,输出的是文档向量。
def wordstoVec(vocabList, inputSet):
    #创建一个其中所含元素都为0的向量,向量长度与词汇表长度一致
    returnVec = [0]*len(vocabList)
    #对输入集中每一个单词执行,如果单词在词汇表中就使这个词的索引位置相应的的文档向量值为1,否则输出这个词不是词汇表中的
    for word in inputSet:
        if word in vocabList:
            returnVec[vocabList.index(word)] = 1
        else: print "这个单词: %s 不是我词汇表里的!" % word
    #返回这个转化后的向量
    return returnVec
    
#输入的参数为文档矩阵trainMatrix,以及由每篇文档类别标签所构成的向量trainCategory。
#首先计算文档属于侮辱性文档的概率,即P(1)。因为是二类分类问题,所以可以通过1-P(1)得到p(0)。
def trainNB0(trainMatrix,trainCategory):
    #训练文本单词数为训练矩阵的长度。
    TrainDocsNum = len(trainMatrix)
    #单词数为训练矩阵第一行的长度,这只是为了初始化
    Wordsnum = len(trainMatrix[0])
    #辱骂性词汇出现的概率为所有训练类别相加除以训练文本单词总数,因为辱骂性词汇的类别都是1.
    pbad = sum(trainCategory)/float(TrainDocsNum)
    #计算p(wi|c1)和p(wi|c0),p(w|c)指在分类c下,词语w出现的样本数与分类c所有样本中所有词数
    #让类别为0的单词出现的次数设为全为1的向量,向量的高度与单词数相同。
    p0 = ones(Wordsnum)
    #让类别为1的单词出现的次数设为全为1的向量,向量的高度与单词数相同。
    p1 = ones(Wordsnum)
    #设定初始一行的单词数计数为2,这也是一处改进,为了防止下溢,避免因为某个特性下概率为0 就导致整体概率为0。把计数从1改为2
    p0Denom = 2.0; p1Denom = 2.0
    #让所有的向量对应元素相加,一旦某个词在文档出现,对该词对应的个数就加1。而且总词数也加1。
    #range代表从0到TrainDocsNum的整数列表
    for i in range(TrainDocsNum):
        #假如训练种类对应的值为1就使类别1单词出现的次数加1
        if trainCategory[i] == 1:
            p1 += trainMatrix[i]
        #假如训练种类对应的值为0就使类别0单词出现的次数加1
        else:
            p0 += trainMatrix[i]
    p1Denom = sum(trainCategory)
    p0Denom = len(trainCategory) - p1Denom
    #对每个元素出现的次数除以该类别中对应某行的总词数,概率进行乘法过后数值会比较小,取log方便比较
    p1Vect = log(p1/p1Denom)        
    p0Vect = log(p0/p0Denom)
    return p0Vect,p1Vect,pbad

#朴素贝叶斯分类函数
def classifyNB(vec2Classify, p0Vec, p1Vec, pClass1):
    #元素相乘,对应元素相乘,然后将词汇表中所有词的对应值相加,然后将该值加到类别的对数概率上。
    p1 = sum(vec2Classify*p1Vec) + log(pClass1)
    p0 = sum(vec2Classify*p0Vec) + log(1.0 - pClass1)
    #比较类别的概率返回大概率对应的类别标签。
    if p1>p0:
        return 1
    else:
        return 0
    
#便利函数,封装了所有操作。      
def testingNB():
    #加载数据集
    listOPosts,listClasses = loadDataSet()
    #创建词汇表
    myVocabList = createVocabList(listOPosts)
    #创建一个空集
    trainMat = []
    #循环所有数据集中词汇,转化为词汇向量
    for postingDoc in listOPosts:
        trainMat.append(wordstoVec(myVocabList,postingDoc))
    #调用函数计算0类词出现概率和1类词出现的概率以及辱骂性词汇出现的概率
    p0V,p1V,pb = trainNB0(array(trainMat),array(listClasses))
    testEntry = ['I','love','you']
    thisDoc = array(wordstoVec(myVocabList,testEntry))
    print testEntry, '被分到: ', classifyNB(thisDoc,p0V,p1V,pb),'类\n','是正常词的概率为:\n',p0V,'\n是辱骂性词的概率为:\n',p1V,'\n辱骂性词占得比例:',pb
    testEntry = ['stupid','garbage']
    thisDoc = array(wordstoVec(myVocabList,testEntry))
    print testEntry, '被分到: ', classifyNB(thisDoc,p0V,p1V,pb),'类\n','是正常词的概率为:\n',p0V,'\n是辱骂性词的概率为:\n',p1V,'\n辱骂性词占得比例:',pb

#应用部分:使用朴素贝叶斯过滤垃圾邮件,使用到交叉验证。
#数据准备会有垃圾邮件文件夹下面全部是标记的垃圾邮件,正常邮件文件夹下是正常邮件。
#textParse函数接受一个字符串并将其解析为字符串列表,把大写字符转化成小写,并且只存储大于两个字母的词。
#spamTest函数在50封邮件中选取10篇邮件随机选择为测试集交叉验证。
def textParse(bigString):
    import re
    listOfTokens = re.split(r'\W*',bigString)
    return[tok.lower() for tok in listOfTokens if len(tok) > 2]

#对贝叶斯垃圾邮件分类器进行自动化处理。导入文件夹spam和ham下的文本文件,并将它们解析为词列表。
def spamTest():
    docList = []; classList = []; fullText = []
    #导入并解析文本
    for i in range(1,26):
        wordList = textParse(open('email/垃圾邮件/%d.txt' % i).read())
        docList.append(wordList)
        fullText.extend(wordList)
        classList.append(1)
        wordList = textParse(open('email/正常邮件/%d.txt' % i).read())
        docList.append(wordList)
        #extend()方法只接受一个列表作为参数,并将该参数的每个元素都添加到原有的列表中。
        fullText.extend(wordList)
        #append() 方法向列表的尾部添加一个新的元素。只接受一个参数。
        classList.append(0)
    vocabList = createVocabList(docList)
    #随机构建训练集,进行留存交叉验证
    trainingSet = range(50); testSet = []
    for i in range(15):
        #uniform() 方法将随机生成下一个实数,它在[x,y]范围内
        randIndex = int(random.uniform(0,len(trainingSet)))
        testSet.append(trainingSet[randIndex])
        del(trainingSet[randIndex])
    trainMat = []; trainClasses = []
    for docIndex in trainingSet:
        trainMat.append(wordstoVec(vocabList, docList[docIndex]))
        trainClasses.append(classList[docIndex])
    p0V,p1V,pSpam = trainNB0(array(trainMat),array(trainClasses))
    errorCount = 0
    #对测试集分类
    for docIndex in testSet:
        wordVector = wordstoVec(vocabList, docList[docIndex])
        if classifyNB(array(wordVector),p0V,p1V,pSpam)!= classList[docIndex]:
            errorCount += 1
            print "分类错误了!分类的错误词是: \n",docList[docIndex],'分类错误的数目有: ',errorCount
    print '分类错误率为:',float(errorCount)/len(testSet)

if __name__ == "__main__":
    listOPosts,listClasses = loadDataSet()
    myVocabList = createVocabList(listOPosts)
    print '       ————————————————————我的词汇列表:——————————————————\n',myVocabList
    trainMat = []
    for postinDoc in listOPosts:
        trainMat.append(wordstoVec(myVocabList, postinDoc))
    p0V,p1V,pAb = trainNB0(trainMat, listClasses)
    print '       ——————————————————————测试案例———————————————————————————\n'
    testingNB()
    print '       ———————————————第1次垃圾邮件分类自动化处理结果——————————————————\n'
    spamTest()
    print '       ———————————————第2次垃圾邮件分类自动化处理结果——————————————————\n'
    spamTest()
    print '       ———————————————第3次垃圾邮件分类自动化处理结果——————————————————\n'
    spamTest()
    

运行时,注意python的版本,要注意修改代码。要安装numpy才能正常运行,案例中邮箱,可以去机器学习实战官网下载,也可以去这个网址下载,http://www.robot-ai.org/forum.php?mod=viewthread&tid=986&extra=注意改名。以下是运行结果:


  • 0
    点赞
  • 2
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值