朴素贝叶斯原理及Python实战

原理

朴素贝叶斯(Naive Bayes)法是基于贝叶斯定理和特征条件独立的假设(这是一个较强的假设,虽然使得方法变得简单,但有时会牺牲一定的分类准确率)的分类方法,属于生成(Generative Approach)方法的一种。

为什么说它属于生成方法呢?

它通过训练数据集学习联合概率分布 p ( X , Y ) p(X,Y) p(X,Y) , 所以就可以从统计的角度表示数据的分布情况,能够反映同类数据本身的相似度。

具体的,我们的目标是求后验概率 p ( Y = c k ∣ X = x ( 1 ) , . . . . , x ( n ) ) p(Y=c_k|X=x^{(1)},....,x^{(n)}) p(Y=ckX=x(1),....,x(n)), 即已知输入 X X X取值,求输出 Y = c k Y = c_k Y=ck的概率,其中概率最大的就是分类结果。

而由条件概率公式 : P ( Y ∣ X ) = P ( X ∣ Y ) ∗ P ( Y ) / P ( X ) P(Y|X) = P (X|Y) *P(Y) / P(X) P(YX)=P(XY)P(Y)/P(X)

其中先验概率分布 P ( Y = c k ) P(Y=c_k) P(Y=ck) k = 1,2,…n 可以直接从训练数据得到。

对于条件概率 P ( X = x ∣ Y = c k ) P (X=x|Y=c_k) P(X=xY=ck) 展开如下面,发现它是有指数级的参数,其估计实际是不可能的。:
在这里插入图片描述
这里就体现朴素贝叶斯方法简单(朴素)的特点:我们对这里的条件概率分布做了条件独立性的假设。
在这里插入图片描述
这里条件假设独立假设等于说 用于分类的特征在类确定的条件下都是条件独立的

而我们的目标 P ( Y ∣ X ) P(Y|X) P(YX)中分母由P(X)全概率公式展开为:
在这里插入图片描述
再带入上面的条件独立性假设公式
在这里插入图片描述
而最后我们选择是概率最大的结果,所以贝叶斯分类器可以表示为:
在这里插入图片描述
注意到,分母对于所有的 c k c_k ck都是相同的,所以实际上最后分类器只用求:
在这里插入图片描述

实战

文档词语的分类:朴素贝叶斯训练和分类函数

准备数据

先构造一个用朴素贝叶斯对文档词语进行分类的小数据集:

def loadDataSet():
    """
    创建数据集
    :return: 单词列表postingList, 所属类别classVec
    """
    postingList = [['my', 'dog', 'has', 'flea', 'problems', 'help', 'please'], #[0,0,1,1,1......]
                   ['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']]
    classVec = [0, 1, 0, 1, 0, 1]  # 1 is abusive, 0 not
    return postingList, classVec

根据Nlp里对词表示的方法,我们很直接把文本看成单词向量,这里利用One-Hot的表示方法:先构建一个所有单词集合,然后对于文档里出现单词向量值为1,反之为0:

def createVocabList(dataSet):
   """
   获取所有单词的集合
   :param dataSet: 数据集
   :return: 所有单词的集合(即不含重复元素的单词列表)
   """
   vocabSet = set()  # create empty set
   for document in dataSet:
       # 操作符 | 用于求两个集合的并集
       vocabSet = vocabSet | set(document)  # union of the two sets
   return list(vocabSet)

def setOfWords2Vec(vocabList, inputSet):
   """
   遍历查看该单词是否出现,出现该单词则将该单词置1
   :param vocabList: 所有单词集合列表
   :param inputSet: 输入数据集
   :return: 匹配列表[0,1,0,1...],其中 1与0 表示词汇表中的单词是否出现在输入的数据集中
   """
   # 创建一个和词汇表等长的向量,并将其元素都设置为0
   returnVec = [0] * len(vocabList) #[0,0......]
   # 遍历文档中的所有单词,如果出现了词汇表中的单词,则将输出的文档向量中的对应值设为1
   for word in inputSet:
       if word in vocabList:
           returnVec[vocabList.index(word)] = 1
       else:
           print("the word: %s is not in my Vocabulary!" % word)
   return returnVec
训练函数

这部分就是利用数据训练出条件概率 p ( X = x ( 1 ) , . . . . , x ( n ) ∣ Y = c k ) p(X=x^{(1)},....,x^{(n)}|Y=c_k) p(X=x(1),....,x(n)Y=ck)
输入就是用上面函数的生成字词矩阵和结果标签向量。
代码里对公式有两点改进:
在这里插入图片描述

  • 如果某一类特征没有出现过 p ( X = x ( 1 ) , . . . . , x ( n ) ∣ Y = c k ) p(X=x^{(1)},....,x^{(n)}|Y=c_k) p(X=x(1),....,x(n)Y=ck) 中某一项为0,会影响判断,于是统计时默认所有特征出现数从1开始,分母从2开始。
  • 累乘可能会导致小数的溢出,对这个式子取log。

然后相对于《机器学习实战》上面只处理二分类问题,我将代码小小的修改一下可以处理多分类的问题。

def trainNB0(trainMatrix, trainCategory):
    """
    训练数据优化版本
    :param trainMatrix: 文件单词矩阵
    :param trainCategory: 文件对应的类别
    :return:
    """
    # 总文件数
    numTrainDocs = len(trainMatrix)
    # 总单词数
    numWords = len(trainMatrix[0])

    #统计结果总共有多少种分类
    Category = set()
    for cat in trainCategory :
        Category.add(cat)

    #存不同结果ci的 P(ci)
    pStatus = list(ones(len(Category)))
    #print(pStatus)
    for item in Category:
        cnt = 0
        for train in trainCategory:
            if train == item:
                 cnt += 1
        pStatus[item] = cnt / float(numTrainDocs)
    # 构造单词出现次数列表
    pNum = []     # pNum[ci] 统计输出结果为ci的输入数据里各个特征出现值之和 ,由于如果出现次数为0,相乘都是0,所以修改版用1初始化
    pDenom = []   # 整个数据集单词出现总数,2.0根据样本/实际调查结果调整分母的值(2主要是避免分母为0,同时分子为,当然值可以调整)
    for cat in Category:
        pNum.append(ones(numWords))
        pDenom.append(2.0)

    for i in range(numTrainDocs):
        for cat in Category:
            if trainCategory[i] == cat:
                pNum[cat] += trainMatrix[i]         # 统计结果cat的i位置特征的值之和,是一个数组
                pDenom[cat] += sum(trainMatrix[i])  # 统计结果cat的所有位置特征值之和,是一个值

    pVect = list(ones(len(Category)))
    for cat in Category:
       pVect[cat] = log(pNum[cat] / pDenom[cat])  # 后面累乘运算可能小数位数过多,结果会溢出,所以取Log,变成累加。
    return pVect, pStatus
分类函数

这里就选出log(P(w|c1) * P(c1))最大的那一类别。

def classifyNB(vec2Classify,pVect ,pStatus):
    ans = float('-Inf')
    ansindex = 0
    for index in range(len(pStatus)):
        p = sum(vec2Classify * pVect[index]) + log(pStatus[index]) # log(P(w|c1) * P(c1))
        if p > ans:
            ans = p
            ansindex = index
    return ansindex

使用朴素贝叶斯过滤垃圾邮件

#!/usr/bin/python3
# encoding: utf-8
# Author MrYx
# @Time: 2019/6/27 22:03

from NaiveBayes.bayes import setOfWords2Vec, createVocabList , trainNBO, classifyNB
import random ,os
from numpy import *

def textParse(bigString):
    '''
    Desc:
        接收一个大字符串并将其解析为字符串列表
    Args:
        bigString -- 大字符串
    Returns:
        去掉少于 2 个字符的字符串,并将所有字符串转换为小写,返回字符串列表
    '''
    import re
    # 使用正则表达式来切分句子,其中分隔符是除单词、数字外的任意字符串
    listOfTokens = re.split(r'\W*', bigString)
    return [tok.lower() for tok in listOfTokens if len(tok) > 2]

def spamTest():
    '''
    Desc:
        对贝叶斯垃圾邮件分类器进行自动化处理。
    Args:
        none
    Returns:
        对测试集中的每封邮件进行分类,若邮件分类错误,则错误数加 1,最后返回总的错误百分比。
    '''
    docList = []
    classList = []
    fullText = []
    end = 23
    for i in range(1, 26):
        # 切分,解析数据,并归类为 1 类别
        try:
        #print(os.getcwd() + '\email\ham\%d.txt')
            wordList = textParse(open(os.getcwd()+'\email\spam\%d.txt' % i).read())
            docList.append(wordList)
            classList.append(1)
            # 切分,解析数据,并归类为 0 类别
            #print(os.getcwd()+'\email\ham\%d.txt')
            wordList = textParse(open(os.getcwd()+'\email\ham\%d.txt' % i).read())
            docList.append(wordList)
            fullText.extend(wordList)
            classList.append(0)
        except:
            end = i
            pass

    # 创建词汇表
    vocabList = createVocabList(docList)
    trainingSet = list(range(46))
    testSet = []
    # 随机取 10 个邮件用来测试
    for i in range(10):
        # random.uniform(x, y) 随机生成一个范围为 x ~ y 的实数
        randIndex = int(random.uniform(0, len(trainingSet)))
        testSet.append(trainingSet[randIndex])
        del trainingSet[randIndex]
    trainMat = []
    trainClasses = []
    for docIndex in trainingSet:
        trainMat.append(setOfWords2Vec(vocabList, docList[docIndex]))
        trainClasses.append(classList[docIndex])
    pVect, pStatus = trainNBO(array(trainMat), array(trainClasses))
    errorCount = 0
    for docIndex in testSet:
        wordVector = setOfWords2Vec(vocabList, docList[docIndex])
        # print(classifyNB(array(wordVector), pVect, pStatus) )
        # print(classList[docIndex])
        # c = input('???')
        if classifyNB(array(wordVector), pVect, pStatus) != classList[docIndex]:
            errorCount += 1

    print('the errorCount is: ', errorCount)
    print('the testSet length is :', len(testSet))
    print('the error rate is :', float(errorCount)/len(testSet))

if __name__ == '__main__':
    spamTest() 
    spamTest()
    spamTest()
    spamTest()

参考

本文内容是博主总结于《统计学习方法》,《机器学习实战》的笔记,非原创。

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

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值