机器学习朴素贝叶斯算法

朴素贝叶斯属于监督学习的生成模型,实现简单,没有迭代,学习效率高,在大样本量下会有较好表现。但因为假设太强——特征条件独立,在输入向量的特征条件有关联的场景下,并不适用。

朴素贝叶斯算法:主要思路是通过联合概率P(x,y)=P(x|y)P(y)建模,运用贝叶斯定理求解后验概率P(y|x);将后验概率最大者对应的类别作为预测类别。

首先定义训练集T=[(x1,y1),(x2,y2),(x3,y3)...,(xN,yN)],其类别yi\in (c1,c2,c3,...cK)

输入待预测的数据,预测类别:

                                                  

由贝叶斯定理可知:

                                    

由于p(x)是恒等的,因此预测类别等价于:

                                         

最后,贝叶斯将分类问题转化成了求条件概率先验概率的最大乘积问题。由于做了条件独立性的假设,求解公式可以转化为:

                                   

选取后验概率醉的的类别作为预测类别,是因为后验概率最大化,可以使得期望风险最小化。

极大似然估计

在朴素贝叶斯学习中,需要估计先验概率与条件概率,一般时采用极大似然估计。先验概率的极大似然估计:

                                             

其中,I是指示函数,满足括号内条件时为1否则为0;可以看作为计数

设第 j 维特征的取值空间为,且输入变量的第 j 维x^{(j)}=a_{j}l,则条件概率的极大似然估计为:

                     

贝叶斯估计

在估计先验概率与条件概率时,有可能出现为0的情况,则计算得到的后验概率亦为0,从而影响分类的效果。因此,需要在估计时做平滑,这种方法被称为贝叶斯估计。先验概率的贝叶斯估计:

                                          

后验概率的贝叶斯估计为:

                           

\lambda =1的时候,被称为拉普拉斯平滑

以上就是朴素贝叶斯算法的数学原理。接下来附上案例代码。(详细过程见注释)

import numpy as np
from functools import reduce


#创建数据样本
#postingList的每一行,代表一个样本数据
#classVec是每个样本数据所对应的标签,0是非侮辱类,1是侮辱类
def loadDataSet():
    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']]
    classVec = [0,1,0,1,0,1]
    return postingList,classVec



#根据vocabList词汇表,将inputSet向量化,向量的每个元素为1或0
#参数里,vocabList就是createVocabList方法最后返回的元素不重复的数组(词汇表),
#inputSet是取的原始数据集里的某一行,比如:['my', 'dog', 'has', 'flea', 'problems', 'help', 'please']
def setOfWords2Vec(vocabList,inputSet):
    returnVec = [0]*len(vocabList)                        #创建一个所有元素都是0的数组,长度和vocabList一样,returnVec=[0,0,0,0..........0,0,0]
    for word in inputSet:                                 #将['my', 'dog', 'has', 'flea', 'problems', 'help', 'please']里的每个元素遍历
        if word in vocabList:                             #如果某个元素在词汇表里
            returnVec[vocabList.index(word)] = 1          #那就找到该元素在词汇表中的对应索引,并在returnVec里的同样索引位置赋值,值为1
       # else: print("the word: %s is not in my Vocabulary!" % word)
    return returnVec

# 该方法最终返回的结果如下:
# [1, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 1, 0, 0, 1, 0, 0, 0, 0, 0, 0, 0, 1, 0, 1, 0]
# 其中"1"代表的索引位置,就是['my', 'dog', 'has', 'flea', 'problems', 'help', 'please']里每个元素在我的词汇表vocabList里的索引位置




#将切分的实验样本词条整理成不重复的此条例列表,也就是词汇表
#dataSet是最初的数据样本,也就是loadDataSet方法里的postingList数据集
def createVocabList(dataSet):
    vocabSet = set([])                        #先创建一个空的字典(set创建字典,注意字典是不能有重复元素的)
    for document in dataSet:                  #将dataSet的每一行都进行遍历
        vocabSet = vocabSet | set(document)   #set(document)表示取每行的不重复数据,每次迭代后vocabSet返回值类似于:['my','dog','flea',.....]
    print('词汇表为:\n',vocabSet)                           #"" | "是并集符号,表示将每次迭代的不重复数据集都和之前的vocabSet进行合并                                                       
    return list(vocabSet)                     #将所有行的不重复数据都进行合并之后,最后得到整个dataSet的不重复的数据集,也就是词汇表

    #   该方法最后返回的list(vocabSet)词汇表l类似如下结构:
    #   ['dog', 'help', 'ate', 'is', 'buying', 'him', 'mr', 
    #   'maybe', 'park', 'quit', 'so', 'stupid', 'stop', 'steak',
    #   'licks', 'food', 'please', 'problems', 'cute', 'I', 'flea',
    #   'posting', 'love', 'how', 'dalmation', 'take', 'garbage', 'not',
    #   'my', 'to', 'has', 'worthless']
    
    
#分类器训练函数
# 其中trainMatrix是setOfWords2Vec方法返回的returnVec所组成的矩阵,如下:
# [[1,1,0,0,0,0,0,0,0,0,0,0,0,0,0,0,1,1,0,0,1,0,0,0,0,0,0,0,1,0,1,0],
#  [1,0,0,0,0,1,0,1,1,0,0,1,0,0,0,0,0,0,0,0,0,0,0,0,0,1,0,1,0,1,0,0],
#  [0,0,0,1,0,1,0,0,0,0,1,0,0,0,0,0,0,0,1,1,0,0,1,0,1,0,0,0,1,0,0,0],
#  [0,0,0,0,0,0,0,0,0,0,0,1,1,0,0,0,0,0,0,0,0,1,0,0,0,0,1,0,0,0,0,1],
#  [0,0,1,0,0,1,1,0,0,0,0,0,1,1,1,0,0,0,0,0,0,0,0,1,0,0,0,0,1,1,0,0],
#  [1,0,0,0,1,0,0,0,0,1,0,1,0,0,0,1,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,1]]
# trainCategory表示类别标签向量,即loadDataSet方法里的classVec,classVec=[0,1,0,1,0,1]
#该方法最终将出现错误
def trainNB0(trainMatrix,trainCategory):
    numTrainDocs = len(trainMatrix)           #返回文档数目,numTrainDocs = 6
    numWords = len(trainMatrix[0])            #计算词汇表里的词条数,numWords = 32
    pAbusive = sum(trainCategory)/float(numTrainDocs)     #计算侮辱类文档占总文档的概率(比例)pAbusive = 3/6 = 0.5
    p0Num = np.zeros(numWords);p1Num = np.zeros(numWords) #创建两个numpy数组,每个元素都是0,长度为32
    p0Denom = 0.0; p1Denom = 0.0              #初始化两个数,这两个数是之后算条件概率的分母,p0Denom是非侮辱性相对应的条件概率的分母,p1Denom则是侮辱性的分母
    for i in range(numTrainDocs):             #对类别标签进行遍历,也就是对[0,1,0,1,0,1]的每个元素进行遍历,0代表非侮辱,1代表侮辱
        if trainCategory[i]==1:               #如果是侮辱的
            p1Num += trainMatrix[i]           #那就把trainMatrix矩阵中对应的那一行的标签累加到p1Num上,
            p1Denom += sum(trainMatrix[i])    #然后,将所有词出现的次数累加到p1Denom上
                                              #最后p1Num得到的结果是:
                                              # p1Num = [2. 0. 0. 0. 1. 1. 0. 1. 1. 1. 0. 3. 1. 0. 0. 1. 0. 0. 0. 0. 0. 1. 0. 0. 0. 1. 1. 1. 0. 1. 0. 2.]
                                              # 该数组的意思:在整个词条中,第一个词出现的频次为2,第二个词频次为0,以此类推
                                              #p1Denom=19,意思是侮辱性文档中所有词汇出现的总次数,也就是p1Num中所有元素相加,p1Denom = 19  
        else:                                 #如果是非侮辱的,同上所述,将得到非侮辱性文档的结果,
            p0Num += trainMatrix[i]           #p0Num = [1. 1. 1. 1. 0. 2. 1. 0. 0. 0. 1. 0. 1. 1. 1. 0. 1. 1. 1. 1. 1. 0. 1. 1. 1. 0. 0. 0. 3. 1. 1. 0.]
            p0Denom += sum(trainMatrix[i])    #p0Denom = 24
    p1Vect = p1Num/p1Denom                    #最后需要分别计算在侮辱类和非侮辱类中,每个词出现的概率,
    p0Vect = p0Num/p0Denom                    #比如对于侮辱类,第一个词出现两次,除以所有侮辱类的次数19.得到2/19=0.10526316
    return p0Vect,p1Vect,pAbusive             #p0Vect是非侮辱类每个词出现的概率,p1Vect是侮辱类每个词出现的概率,pAbusive是侮辱类文档占总文档的比例
#该方法最后返回的结果将是:
#     p1Vect = [0.10526316 0.         0.         0.         0.05263158 0.05263158
#               0.         0.05263158 0.05263158 0.05263158 0.         0.15789474
#               0.05263158 0.         0.         0.05263158 0.         0.
#               0.         0.         0.         0.05263158 0.         0.
#               0.         0.05263158 0.05263158 0.05263158 0.         0.05263158
#               0.         0.10526316]
    
#     p0Vect = [0.04166667 0.04166667 0.04166667 0.04166667 0.         0.08333333
#               0.04166667 0.         0.         0.         0.04166667 0.
#               0.04166667 0.04166667 0.04166667 0.         0.04166667 0.04166667
#               0.04166667 0.04166667 0.04166667 0.         0.04166667 0.04166667
#               0.04166667 0.         0.         0.         0.125      0.04166667
#               0.04166667 0.        ]
    
#     pAbusive = 3/6 = 0.5



#修正后的贝叶斯分类器训练函数
def trainNB1(trainMatrix,trainCategory):
    numTrainDocs = len(trainMatrix)                            #计算训练的文档数目
    numWords = len(trainMatrix[0])                             #计算每篇文档的词条数
    pAbusive = sum(trainCategory)/float(numTrainDocs)          #文档属于侮辱类的概率
    p0Num = np.ones(numWords); p1Num = np.ones(numWords)       #创建numpy.ones数组,词条出现数初始化为1,拉普拉斯平滑
    p0Denom = 2.0; p1Denom = 2.0                               #分母初始化为2,拉普拉斯平滑
    for i in range(numTrainDocs):
        if trainCategory[i] == 1:                              
            p1Num += trainMatrix[i]                            #p1Num=[1. 1. 2. 2. 1. 2. 2. 1. 1. 3. 1. 2. 1. 1. 1. 1. 3. 2. 2. 1. 1. 1. 2. 2. 1. 2. 2. 1. 1. 4. 1. 2.]
            p1Denom += sum(trainMatrix[i])                     #p1Denom = 21
        else:                                                
            p0Num += trainMatrix[i]                            #p0Num=[2. 2. 1. 1. 2. 1. 2. 2. 2. 1. 2. 1. 2. 2. 2. 2. 2. 1. 1. 2. 2. 2. 1. 2. 2. 1. 3. 4. 2. 1. 2. 1.]
            p0Denom += sum(trainMatrix[i])                     #p0Denom = 26
    p1Vect = np.log(p1Num/p1Denom)                             #取对数,防止下溢出        
    print('p1Vect:\n',p1Vect)
    p0Vect = np.log(p0Num/p0Denom) 
    print('p0Vect:\n',p0Vect)
    return p0Vect,p1Vect,pAbusive   

#该函数最后返回的p0Vect,p1Vect结果是:(相比之前的训练器,将初始数组的元素0改成1,分母初始值由0改为2,然后概率取对数,就得到以下结果
#                                       元素0改为1,属于拉普拉斯平滑,概率取对数是防止下溢出)
#     p0Vect = [-2.56494936 -2.56494936 -3.25809654 -3.25809654 -2.56494936 -3.25809654
#               -2.56494936 -2.56494936 -2.56494936 -3.25809654 -2.56494936 -3.25809654
#               -2.56494936 -2.56494936 -2.56494936 -2.56494936 -2.56494936 -3.25809654
#               -3.25809654 -2.56494936 -2.56494936 -2.56494936 -3.25809654 -2.56494936
#               -2.56494936 -3.25809654 -2.15948425 -1.87180218 -2.56494936 -3.25809654
#               -2.56494936 -3.25809654]


#     p0Vect = [-3.04452244 -3.04452244 -2.35137526 -2.35137526 -3.04452244 -2.35137526
#               -2.35137526 -3.04452244 -3.04452244 -1.94591015 -3.04452244 -2.35137526
#               -3.04452244 -3.04452244 -3.04452244 -3.04452244 -1.94591015 -2.35137526
#               -2.35137526 -3.04452244 -3.04452244 -3.04452244 -2.35137526 -2.35137526
#               -3.04452244 -2.35137526 -2.35137526 -3.04452244 -3.04452244 -1.65822808
#               -3.04452244 -2.35137526]


#朴素贝叶斯分类器分类函数
#vec2Classify是待测试的词条
#p0Vec是非侮辱类的条件概率数组
#p1Vec是侮辱类的条件概率数组
#pClass1是文档属于侮辱类的概率
def classifyNB(vec2Classify,p0Vec,p1Vec,pClass1):
    p1 = sum(vec2Classify * p1Vec) + np.log(pClass1)           #计算该文档属于侮辱类的概率
    p0 = sum(vec2Classify * p0Vec) + np.log(1.0 - pClass1)     #计算该文档属于非侮辱类的概率
    print('属于p0的概率取对数:',p0)                            #注意上面两行的计算公式省略了贝叶斯公式里的分母
    print('属于p1的概率取对数:',p1)                            #因为不管是计算p0还是p1,分母都是一样的,而我们要比较概率的大小,只需要比较分子即可
    if p1 > p0:
        return 1                                               #如果p1大于p0,说明属于侮辱类的可能性更大,返回1,(1代表侮辱性)
    else:
        return 0                                               #反之,则返回0(代表非侮辱性)

    
    
def testingNB():
    listOPosts,listClasses = loadDataSet()                                  #创建实验样本
    myVocabList = createVocabList(listOPosts)                               #创建词汇表
    trainMat=[]
    for postinDoc in listOPosts:
        trainMat.append(setOfWords2Vec(myVocabList, postinDoc))             #将实验样本向量化
    p0V,p1V,pAb = trainNB1(np.array(trainMat),np.array(listClasses))        #训练朴素贝叶斯分类器
    testEntry = ['love', 'my', 'dalmation']                                 #测试样本1
    thisDoc = np.array(setOfWords2Vec(myVocabList, testEntry))              #测试样本向量化
    if classifyNB(thisDoc,p0V,p1V,pAb):
        print(testEntry,'属于侮辱类')                                        #执行分类并打印分类结果
    else:
        print(testEntry,'属于非侮辱类')                                       #执行分类并打印分类结果
    testEntry = ['stupid', 'garbage']                                       #测试样本2

    thisDoc = np.array(setOfWords2Vec(myVocabList, testEntry))              #测试样本向量化
    if classifyNB(thisDoc,p0V,p1V,pAb):
        print(testEntry,'属于侮辱类')                                        #执行分类并打印分类结果
    else:
        print(testEntry,'属于非侮辱类')


if __name__ == '__main__':
    testingNB()

最后打印信息,并分类得出的结果是:

词汇表为:
 {'to', 'problems', 'help', 'buying', 'park', 'dalmation', 'how', 'I', 'is', 'posting', 'stupid', 'ate', 'mr', 'has', 'stop', 'take', 'quit', 'him', 'garbage', 'my', 'steak', 'not', 'cute', 'maybe', 'flea', 'love', 'dog', 'worthless', 'food', 'so', 'licks', 'please'}
p1Vect:
 [-2.35137526 -3.04452244 -3.04452244 -2.35137526 -2.35137526 -3.04452244
 -3.04452244 -3.04452244 -3.04452244 -2.35137526 -1.65822808 -3.04452244
 -3.04452244 -3.04452244 -2.35137526 -2.35137526 -2.35137526 -2.35137526
 -2.35137526 -3.04452244 -3.04452244 -2.35137526 -3.04452244 -2.35137526
 -3.04452244 -3.04452244 -1.94591015 -1.94591015 -2.35137526 -3.04452244
 -3.04452244 -3.04452244]
p0Vect:
 [-2.56494936 -2.56494936 -2.56494936 -3.25809654 -3.25809654 -2.56494936
 -2.56494936 -2.56494936 -2.56494936 -3.25809654 -3.25809654 -2.56494936
 -2.56494936 -2.56494936 -2.56494936 -3.25809654 -3.25809654 -2.15948425
 -3.25809654 -1.87180218 -2.56494936 -3.25809654 -2.56494936 -3.25809654
 -2.56494936 -2.56494936 -2.56494936 -3.25809654 -3.25809654 -2.56494936
 -2.56494936 -2.56494936]
属于p0的概率取对数: -7.694848072384611
属于p1的概率取对数: -9.826714493730215
['love', 'my', 'dalmation'] 属于非侮辱类
属于p0的概率取对数: -7.20934025660291
属于p1的概率取对数: -4.702750514326955
['stupid', 'garbage'] 属于侮辱类

 

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值