机器学习之贝叶斯分类(python实现)

朴素贝叶斯(Naive Bayesian)是最为普遍使用的分类方法,它以几率论为基础,是基于贝叶斯定理和特征条件独立假设的分类方法。git

原理

朴素贝叶斯(Naive Bayesian)是基于贝叶斯定理和特征条件独立假设原则的分类方法。经过给出的特征计算分类的几率,选取几率大的状况进行分类。也是基于几率论的一种机器学习分类方法。分类目标肯定,属于监督学习。github

经过几率来衡量事件发生的可能性。几率论和统计学刚好是两个相反的概念,统计学是抽取部分样本进行统计来估算整体的状况,而几率论是经过整体状况来估计单个事件或者部分事情的发生状况。所以,几率论须要已知的数据去预测未知的事件。 
例如,咱们看到天气乌云密布,电闪雷鸣并阵阵狂风,在这样的天气特征(F)下,咱们推断下雨的几率比不下雨的几率大,也就是p(下雨)>p(不下雨),因此认为待会儿会下雨。这个从经验上看对几率进行判断。 
而气象局经过多年长期积累的数据,通过计算,今天下雨的几率p(下雨)=85%,p(不下雨)=15%,一样的,p(下雨)>p(不下雨),所以今天的天气预报确定预报下雨。这是经过必定的方法计算几率从而对下雨事件进行判断。
复制代码

为何叫朴素贝叶斯:简单,易于操做,基于特征独立性假设,也即各个特征彼此独立,互相不影响发生。算法

条件几率

某个事件已发生的状况下另一个事件发生的几率。计算公式以下:P(A|B)=P(A∩B) / P(B) 简单理解:画维恩图,两个圆圈相交的部分就是A发生B也发生了,由于求的是B发生下A发生的几率。B至关于一个新的样本空间。AB/B便可。bash

几率相乘法则:P(A∩B)=P(A)P(B|A) or P(A∩B)=P(B)P(A|B) 独立事件的几率:P(A∩B)=P(A)P(B)app

贝叶斯定理

若是有穷k个互斥事件,B1, B2,,,Bk 而且 P(B1)+P(B2)+⋅⋅⋅+P(Bk)=1和一个能够观测到的事件A,那么有: less

image.png

分类原理

基于几率论,二分类问题以下: 若是p1 > p2, 分入类别1; 不然分入类别2。机器学习

其次,贝叶斯定理,有 p(ci|x,y) = p(x,y|ci) * p(ci) / p(x,y) x, y 表示特征变量,以下例子中的单词。Ci表示类别。p(ci | x, y) 即表示在特征x, y出现的状况下,分入类别Ci的几率。结合如上: p(ci | x, y) > p(cj | x, y), 分入类别i, 不然分入类别j。ide

贝叶斯定理最大的好处是能够用已知的三个几率去计算未知的几率,而若是仅仅是为了比较p(ci|x,y)和p(cj|x,y)的大小,只须要已知两个几率便可,分母相同,比较p(x,y|ci)p(ci)和p(x,y|cj)p(cj)便可。函数

特征条件独立性假设原则

朴素贝叶斯经常使用与对文档分类。根据文档中出现的词汇,判断文章属于什么类别。将词汇出现的特征条件用词向量W表示,由多个值组成,值的个数和训练集中的词汇表个数相同。 上面的贝叶斯公式能够表示为: p(ci|ω)=p(ω|ci) * p(ci) / p(ω) 各个单词的出现不会相互影响,则p(ω|ci) = p(ω0|ci)*p(ω1|ci)*...* p(ωk|ci)post

算法实现

import numpy as np
np.seterr(divide='ignore', invalid='ignore')  #消除向量中除以0的警告
# 获取数据
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] #1表示侮辱性言论,0表示正常
    return postingList, classVec
复制代码

根据文档词汇构建词向量:

def createVocabList(dataSet):
    vocabSet = set([])
    for document in dataSet:
        vocabSet = vocabSet | set(document)
    return list(vocabSet)

# 对输入的词汇表构建词向量
def setOfWords2Vec(vocabList, inputSet):
    returnVec = np.zeros(len(vocabList)) #生成零向量的array
    for word in inputSet:
        if word in vocabList:
            returnVec[vocabList.index(word)] = 1 #有单词,该位置填充1
        else:
            print("the word: %s is not in my Vocabulary" % word)
            # pass
    return returnVec  #返回0,1的向量

if __name__ == '__main__':
    listPosts, listClasses = loadDataSet()
    myVocabList = createVocabList(listPosts)
    print(myVocabList)
 
复制代码

输出结果以下: ['flea', 'ate', 'how', 'licks', 'quit', 'problems', 'dog', 'I', 'garbage', 'help', 'is', 'cute', 'steak', 'to', 'worthless', 'please', 'has', 'posting', 'buying', 'love', 'food', 'so', 'my', 'take', 'dalmation', 'stop', 'park', 'not', 'stupid', 'him', 'mr', 'maybe'], 表示不一样类别言论去重后获得的词向量。 [ 1. 0. 0. 0. 0. 0. 1. 0. 0. 0. 1. 1. 1. 0. 0. 0. 1. 0. 0. 0. 0. 0. 0. 0. 0. 0. 1. 0. 0. 0. 0. 0.]: 表示词聚集1中的单词是否在词向量中出现。

如上,这个方法只记录了每一个词是否出现,并无记录出现次数,成为词集模型。若是记录词出现的次数,这样的词向量构建方法称为词袋模型,以下。本文只使用词集模型。

# 词袋模型
def bagofWords2VecMN(vocabList, inputSet):
    returnVec = [0] * len(vocabList)
    for word in inputSet:
        if word in vocabList:
            returnVec[vocabList.index(word)] += 1
    return vocabList #返回非负整数的词向量
复制代码

运用词向量计算几率:

def trainNB0(trainMatrix, trainCategory):
    numTrainDocs = len(trainMatrix)  #文档数目
    numWord = len(trainMatrix[0])  #词汇表数目
    print(numTrainDocs, numWord)
    pAbusive = sum(trainCategory) / len(trainCategory) #p1, 出现侮辱性评论的几率 [0, 1, 0, 1, 0, 1]
    p0Num = np.zeros(numWord)
    p1Num = np.zeros(numWord)

    p0Demon = 0
    p1Demon = 0

    for i in range(numTrainDocs):
        if trainCategory[i] == 0:
            p0Num += trainMatrix[i] #向量相加
            p0Demon += sum(trainMatrix[i]) #向量中1累加其和
        else:
            p1Num += trainMatrix[i]
            p1Demon += sum(trainMatrix[i])
    p0Vec = p0Num / p0Demon
    p1Vec = p1Num / p1Demon

    return p0Vec, p1Vec, pAbusive

if __name__ == '__main__':
    listPosts, listClasses = loadDataSet()
    myVocabList = createVocabList(listPosts)
    trainMat = []
    trainMat = []
    for postinDoc in listPosts:
        trainMat.append(setOfWords2Vec(myVocabList, postinDoc))
    print(trainMat)
    p0Vec, p1Vec, pAbusive = trainNB0(trainMat, listClasses)
    print(p0Vec, p1Vec, pAbusive)
复制代码

输出结果稍微有点多,慢慢来看: trainMat:表示数据中六个给定的特征在词集模型中的出现状况。

array([ 0.,  0.,  0.,  0.,  0.,  1.,  0.,  0.,  0.,  0.,  1.,  1.,  0.,
        0.,  0.,  1.,  0.,  1.,  0.,  0.,  0.,  0.,  0.,  0.,  0.,  0.,
        0.,  0.,  0.,  0.,  1.,  1.]), array([ 0.,  0.,  0.,  0.,  1.,  0.,  0.,  0.,  0.,  0.,  0.,  0.,  1.,
        0.,  1.,  0.,  1.,  1.,  1.,  0.,  0.,  0.,  1.,  0.,  0.,  0.,
        0.,  0.,  1.,  0.,  0.,  0.]), array([ 1.,  0.,  0.,  0.,  0.,  0.,  1.,  0.,  0.,  0.,  0.,  0.,  0.,
        1.,  0.,  0.,  1.,  0.,  0.,  0.,  1.,  0.,  0.,  0.,  0.,  0.,
        1.,  1.,  0.,  0.,  0.,  1.]), array([ 0.,  1.,  1.,  1.,  1.,  0.,  0.,  0.,  0.,  0.,  0.,  0.,  0.,
        0.,  0.,  0.,  0.,  0.,  0.,  0.,  0.,  0.,  0.,  1.,  0.,  0.,
        0.,  0.,  0.,  0.,  0.,  0.]), array([ 0.,  1.,  0.,  0.,  0.,  0.,  0.,  1.,  0.,  1.,  0.,  0.,  0.,
        0.,  0.,  0.,  1.,  0.,  0.,  1.,  0.,  1.,  0.,  0.,  0.,  0.,
        0.,  0.,  1.,  1.,  0.,  1.]), array([ 0.,  0.,  1.,  0.,  1.,  0.,  0.,  0.,  1.,  0.,  0.,  0.,  0.,
        0.,  0.,  0.,  0.,  1.,  0.,  0.,  0.,  0.,  0.,  0.,  1.,  1.,
        0.,  0.,  0.,  0.,  0.,  0.])]
复制代码

print(numTrainDocs, numWord): 6 32 (6个文档,一共32个词汇) print(p0Vec, p1Vec, pAbusive):pAbusive是文档中是侮辱性言论的几率,为0.5。 而p0Vec表示类别0(非侮辱言论)中的词在词向量中出现的几率:

[ 0.  0.04166667  0.04166667  0.04166667  0.04166667  0.
  0.08333333  0.04166667  0.          0.04166667  0.          0.04166667
  0.          0.04166667  0.          0.          0.04166667  0.04166667
  0.04166667  0.04166667  0.04166667  0.          0.          0.04166667
  0.04166667  0.04166667  0.          0.125       0.          0.04166667
  0.04166667  0.04166667] 
复制代码

算法的改进:

  1. 部分几率为0,用于上面计算独立特征几率相乘是永远为0.所以,将全部词出现的次数初始化为1,某类词项初始化为2.
  2. 因为计算获得的几率过小,不断的相乘可能会致使结果溢出。所以对其取对数,单调性相同,不会影响最后对结果的比较。函数以下:
def trainNB1(trainMatrix, trainCategory):
    numTrainDocs = len(trainMatrix)  #文档数目
    numWord = len(trainMatrix[0])  #词汇表数目
    pAbusive = sum(trainCategory) / len(trainCategory) #p1, 出现侮辱性评论的几率
    p0Num = np.ones(numWord)  #修改成1
    p1Num = np.ones(numWord)

    p0Demon = 2 #修改成2
    p1Demon = 2

    for i in range(numTrainDocs):
        if trainCategory[i] == 0:
            p0Num += trainMatrix[i] #向量相加
            p0Demon += sum(trainMatrix[i]) #向量中1累加其和
        else:
            p1Num += trainMatrix[i]
            p1Demon += sum(trainMatrix[i])
    p0Vec = np.log(p0Num / p0Demon)  #求对数
    p1Vec = np.log(p1Num / p1Demon)

    return p0Vec, p1Vec, pAbusive
复制代码

注意:这里获得p0Vec多是没有规律的,但其对最后的几率比较没有影响。

运用分类器函数进行文档分类

def classifyNB(vec2Classify, p0Vc,  p1Vc, pClass1):
    p1 = sum(vec2Classify * p1Vc) * pClass1
    p0 = sum(vec2Classify * p0Vc) * (1-pClass1)
    # p1 = sum(vec2Classify * p1Vc) + np.log(pClass1) #取对数,防止结果溢出
    # p0 = sum(vec2Classify * p0Vc) + np.log(1 - pClass1)
    if p1 > p0:
        return 1
    else:
        return 0
复制代码

解释一下:vec2Classify是所需分类文档的词量。根据公式 p(ci|ω)=p(ω|ci)p(ci) / p(ω), 已知特征向量求分类的几率等于 p(ω|ci)p(ci)。忽略分母:

p(ci)好求,用样本集中,ci的数量/总样本数便可 
p(ω|ci)因为各个条件特征相互独立且地位相同,`p(ω|ci)=p(w0|ci)p(w1|ci)p(w2|ci)......p(wN|ci)`,能够分别求p(w0|ci),p(w1|ci),p(w2|ci),......,p(wN|ci),从而获得p(ω|ci)。  
而求p(ωk|ci)也就变成了求在分类类别为ci的文档词汇表集合中,单个词项ωk出现的几率。
复制代码

测试分类函数

使用两个不一样的样原本测试分类函数:

# 构造样本测试
def testingNB():
    listPosts, listClasses = loadDataSet()
    myVocabList = createVocabList(listPosts)
    trainMat = []
    for postinDoc in listPosts:
        trainMat.append(setOfWords2Vec(myVocabList, postinDoc))
    p0v, p1v, pAb = trainNB0(trainMat, listClasses)
    # print(p0v, p1v, pAb)
    testEntry = ['love']
    thisDoc = setOfWords2Vec(myVocabList, testEntry)
    print(testEntry, 'classified as', classifyNB(thisDoc, p0v, p1v, pAb))

    testEntry = ['stupid', 'garbage']
    thisDoc = (setOfWords2Vec(myVocabList, testEntry))
    print(testEntry, 'classified as:', classifyNB(thisDoc, p0v, p1v, pAb))

if __name__ == '__main__':
    testingNB()
复制代码

观察结果,能够看到将两个文档正确的分类。 完整代码请查看:

github:naive_bayes

总结

  • 朴素贝叶斯分类
  • 条件几率
  • 贝叶斯定理
  • 特征条件独立性假设原则
  • 根据文档构建词向量
  • 词集模型和词袋模型
  • 几率为0,方便计算的改进和防止溢出的取对数改进

参考文章:
机器学习之朴素贝叶斯(NB)分类算法与Python实现

相关文章
相关标签/搜索