【机器学习实战】-基于概率论的分类方法:朴素贝叶斯

【机器学习实战】-基于概率论的分类方法:朴素贝叶斯


【机器学习实战】读书笔记
**朴素贝叶斯:**称为“朴素”的原因,整个形式化过程只做最原始、最简单的假设,特征之间没有关联,是统计意义上的独立。
**优点:**在数据较少的情况下仍然有效,可以处理多类别问题。
**缺点:**对于输入数据的准备方式较为敏感。
**适用数据类型:**标称型数据。

基于贝叶斯决策理论的分类方法

贝叶斯是贝叶斯决策理论的一部分,假设有两类数据p1(x, y)表示数据点(x, y)属于类别1的概率,p2(x, y)表示数据点属于类别2的概率,对一个新的数据点A(x, y),用下面的规则来判断它的类别:

  • 如果p1(x, y) > p2(x, y),那么类别为1。
  • 如果p2(x, y) > p1(x, y),那么类别为2。

以上原则体现出了贝叶斯决策论的核心思想,选择最高概率的决策。贝叶斯概率引入先验知识和逻辑推理来处理不确定命题。另一种概率解释称为频数概率(frequency probability),它只从数据本身获得结论,并不考虑逻辑推理及先验知识

条件概率(贝叶斯准则)

贝叶斯准则
如果已知P(x|c),要求P(c|x),可以使用下面的计算方法:
p ( c ∣ x ) = p ( x ∣ c ) p ( c ) p ( x ) p(c|x)=\frac{p(x|c)p(c)}{p(x)} p(cx)=p(x)p(xc)p(c)
在这给出简单证明:
∵ p ( x ∣ c ) = p ( x c ) p ( c ) ∴ p ( x ∣ c ) p ( c ) = p ( x c ) → p ( c ∣ x ) = p ( x c ) p ( x ) ∴ p ( c ∣ x ) = p ( x ∣ c ) p ( c ) p ( x ) 原命题得证 \because p(x|c)=\frac{p(xc)}{p(c)}\\ \therefore p(x|c)p(c)=p(xc) \to p(c|x)=\frac{p(xc)}{p(x)}\\ \therefore p(c|x)=\frac{p(x|c)p(c)}{p(x)}原命题得证 p(xc)=p(c)p(xc)p(xc)p(c)=p(xc)p(cx)=p(x)p(xc)p(cx)=p(x)p(xc)p(c)原命题得证
语言通俗解释,p(x|c)p( c)获得就是c和x同时发生的概率,除以x发生的概率,就是在x的前提下c发生的概率。

使用条件概率来分类

对上面的点就可以使用贝叶斯分类来计算属于某个类c的概率
p ( c i ∣ x , y ) = p ( x , y ∣ c i ) p ( c i ) p ( x , y ) p(c_i|x,y)=\frac{p(x,y|c_i)p(c_i)}{p(x,y)} p(cix,y)=p(x,y)p(x,yci)p(ci)
使用这些定义,可以定义贝叶斯分类准则为:

  • 如果P(c1|x,y)>P(c2|x,y),那么属于类别c1。
  • 如果P(c1|x,y)<P(c2|x,y),那么属于类别c2。

使用朴素贝叶斯进行文档分类

朴素贝叶斯是用于文档分类的常用算法。朴素贝叶斯的一般过程:
(1) 收集数据:可以使用任何方法。书上使用RSS源。
(2) 准备数据:需要数值型或者布尔型数据。
(3) 分析数据:有大量特征时,绘制特征作用不大,此时使用直方图效果更好。
(4) 训练算法:计算不同的独立特征的条件概率。
(5) 测试算法:计算错误率。
(6) 使用算法:一个常见的朴素贝叶斯应用是文档分类。可以在任意的分类场景中使用朴
素贝叶斯分类器,不一定非要是文本

书中还提到了独立的概念,独立指的是统计意义上的独立,即一个特征或者单词出现的可能性与它和其他单词相邻没有关系。这个假设正是朴素贝叶斯分类器中朴素(naive)一词的含义。朴素贝叶斯分类器中的另一个假设是,每个特征同等重要。举了个例子,如果每个特征需要N个样本,那么对于10个特征将需要N^10的样本,当特征为1000时,那就是N的1000次方,所需样本数随着特征数目的增大而迅速增大,如果特征之间相互独立,那么数据量就变为10特征时为10×N,特征数为1000时为1000×N,因为有关联的时候是十个特征共同构建一个模型,所以空间就是N的10次方,但是独立时,每个特征可以用N个数据单独建立,最后组合起来就可以了,所以是10×N。

朴素贝叶斯分类器通常有两种实现方式:一种基于贝努利模型实现,一种基于多项式模型实现。这里采用前一种实现方式。该实现方式中并不考虑词在文档中出现的次数,只考虑出不出现,因此在这个意义上相当于假设词是等权重的。后面给出的实际上是多项式模型,它考虑词在文档中的出现次数。

使用python进行文本分类

文本中获取特征,需要拆分文本,特征是来自文本的词条(token),一个词条是字符的任意组合。将每一个文本片段表示为一个词条向量,其中值为1表示词条出现在文档中,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']
    ]
    # 1代表侮辱性文字,0代表正常言论
    classVec = [0, 1, 0, 1, 0, 1]
    return postingList, classVec

def createVocaList(dataSet):
    # 创建一个空集
    vocabSet = set([])
    # 创建并返回两个集合的并集
    for document in dataSet:
        vocabSet = vocabSet | set(document)
    return list(vocabSet)

# 词集模型,每个词的出现与否作为一个特征
def setOfWords2Vec(vocabList, inputSet):
    # 创建一个其中所含元素都为0的向量
    returnVec = [0] * len(vocabList)
    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

loadDataSet()函数创建一些实验样本,返回的第一个变量是词条切分后的文档集合,第二个返回的变量是类别标签集合,有两类,侮辱性和非侮辱性。createVocaList()函数使用set集合的形式,把文档中出现的不重复的词作为集合返回。而setOfWord2Vec()函数输入参数是词汇表vocabList和文档inputSet,创建一个为0的词汇表向量,来记录文档在当前词汇表所形成的向量,若文档中的词在词汇表中出现,则对应的词汇表向量标记为1,如果文档中的词条没在词汇表中,则不标记,最后返回文档对应的词汇表向量。
书中提到目前的setOfWord2Vec()函数是把词的出现与否作为一个特征,为词集模型(set-of-words model)。如果一个词在文档中出现不止一次,这可能意味着包含该词是否出现在文档中所不能表达的某种信息,这种方法被称为词袋模型(bag-of-words model)。在词袋中,每个单词可以出现多次,而在词集中,每个词只能出现一次。词袋模型对应的词集模型(文档词向量标记函数)如下更修改

# 词袋模型,每个词出现的次数做为特征
def setOfWords2Vec1(vocabList, inputSet):
    # 创建所含元素都为0的向量
    returnVec = [0] * len(vocabList)
    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

2.训练算法:从词向量计算概率

原理:重写贝叶斯准则,将之前的x、y替换为ww表示是一个向量,有多个数值组成,在这个例子中,数值个数与词汇表中的词的个数相同即 p ( w ) = 1 p(w) = 1 p(w)=1,重写的贝叶斯准则也如下所示:
p ( c i , w ) = p ( w ∣ c i ) p ( c i ) p ( w ) p(c_i, w) = \frac{p(w|c_i)p(c_i)}{p(w)} p(ci,w)=p(w)p(wci)p(ci)
公式中p(w)已知,p(ci)可以直接根据标签量计算出来,唯一不好计算的就是p(w|ci),这里使用朴素贝叶斯的假设,将w展开为一个个独立的特征,就实现以下转换
p ( w 0 , w 1 , w 2 . . . w N ∣ c i ) = p ( w 0 ∣ c i ) p ( w 1 ∣ c i ) p ( w 2 ∣ c i ) … p ( w N ∣ c i ) p(w_0, w_1,w_2...w_N|c_i)=p(w_0|c_i)p(w_1|c_i)p(w_2|c_i) \dots p(w_N|c_i) p(w0,w1,w2...wNci)=p(w0ci)p(w1ci)p(w2ci)p(wNci)
转换成计算每个类别的词条出现的概率。
伪代码如下:

计算每个类别中的文档数目
对每篇训练文档:
    对每个类别:
        如果词条出现在文档中->增加改词条的计数值
        增加所有词条的计数值
     对每个类别:
         对每个词条:
             将该词条的数目除以总词条数目得到条件概率
     返回每个类别的条件概率

代码如下:

# 朴素贝叶斯分类器
def trainNB0(trainMatrix, trainCategory):
    """
    :param trainMatrix: 对应标签的词组向量类似于[0,1,0,1,0,0,1]这种,记录每个标签对应的词条出现情况
    :param trainCategory: 文档标签
    :return:标签1的log(概率),标签2的log(概率),类别1的概率
    """
    numTrainDocs = len(trainMatrix)
    numWords = len(trainMatrix[0])
    pAbusive = sum(trainCategory)/float(numTrainDocs)
    # p0Num = zeros(numWords); p1Num = zeros(numWords)
    # p0Denom = 0.0; p1Denom = 0.0
    # 上面两个公式是假设每个特征独立,如果有个特征为0,则全部特征为0,为避免这种情况,将所有词出现数初始化为1,将分母初始化为2
    p0Num = ones(numWords); p1Num = ones(numWords)
    p0Denom = 2.0; p1Denom = 2.0
    for i in range(numTrainDocs):
        if trainCategory[i] == 1:
            p1Num += trainMatrix[i]
            p1Denom += sum(trainMatrix[i])
        else:
            p0Num += trainMatrix[i]
            p0Denom += sum(trainMatrix[i])

    # 另一个碰到的问题就是下溢出问题,可以取对数避免下一处或者浮点数舍入导致的错误,
    p1Vect = log(p1Num/p1Denom)
    p0Vect = log(p0Num/p0Denom)
    return p0Vect, p1Vect, pAbusive

numTrainDocs记录文本数量,numWords记录词汇表的数量,pAbusive记录类别为1的比例是多少,p0Num、p0Denom分别记录类别为0的词汇量的个数,和词汇量的总数,同理类比p1Num和p1Denom。因为使用朴素贝叶斯假设,所以防止一个概率为0其他都为0,都初始化为1,分母初始化为2。因为大部分因子都非常小,防止程序下溢或浮点数舍入导致错误,采用自然对数进行处理,这样概率大的还是大,不会影响最终结果,所以采用log。

3.朴素贝叶斯分类函数

代码:

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 = createVocaList(listOPosts)
    trainMat = []
    for postinDoc in listOPosts:
        trainMat.append(setOfWords2Vec(myVocabList, postinDoc))
    p0V, p1V, pAb = trainNB0(array(trainMat), array(listClasses))
    testEntry = ['love', 'my', 'dalmation']
    thisDoc = array(setOfWords2Vec(myVocabList, testEntry))
    print(testEntry, 'classified as: ', classifyNB(thisDoc, p0V, p1V, pAb))
    testEntry = ['stupid', 'garbage']
    thisDoc = array(setOfWords2Vec(myVocabList, testEntry))
    print(testEntry, 'classified as: ', classifyNB(thisDoc, p0V, p1V, pAb))

classifyNB是计算贝叶斯规则的,输入分别是要分类的向量vec2Classify,p(w|c0),p(w|c1),P(c1),多个参数构成w,所以使用numpy向量来计算,使用log函数,相乘等于各元素相加,sum()将要预测分类的向量含有可能的概率加起来,再加上分类概率就是最终概率。
testingNB()是测试函数,加载词汇表和标签,然后将词汇表合并成一个词条向量,把训练词汇数据标记为向量的形式存在trainMat中,然后计算类别1类别0等的概率,输入要预测的词汇向量,转换为向量形式,然后使用classifyNB进行预测。
运行效果:
在这里插入图片描述

4.项目完整代码

'''简单的朴素贝叶斯分类器'''
from numpy import *

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']
    ]
    # 1代表侮辱性文字,0代表正常言论
    classVec = [0, 1, 0, 1, 0, 1]
    return postingList, classVec

def createVocaList(dataSet):
    # 创建一个空集
    vocabSet = set([])
    # 创建并返回两个集合的并集
    for document in dataSet:
        vocabSet = vocabSet | set(document)
    return list(vocabSet)

# 词集模型,每个词的出现与否作为一个特征
def setOfWords2Vec(vocabList, inputSet):
    # 创建一个其中所含元素都为0的向量
    returnVec = [0] * len(vocabList)
    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

# 词袋模型,每个词出现的次数做为特征
def setOfWords2Vec1(vocabList, inputSet):
    # 创建所含元素都为0的向量
    returnVec = [0] * len(vocabList)
    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

# 朴素贝叶斯分类器
def trainNB0(trainMatrix, trainCategory):
    """
    :param trainMatrix: 对应标签的词组向量类似于[0,1,0,1,0,0,1]这种,记录每个标签对应的词条出现情况
    :param trainCategory: 文档标签
    :return:标签1的log(概率),标签2的log(概率),类别1的概率
    """
    numTrainDocs = len(trainMatrix)
    numWords = len(trainMatrix[0])
    pAbusive = sum(trainCategory)/float(numTrainDocs)
    # p0Num = zeros(numWords); p1Num = zeros(numWords)
    # p0Denom = 0.0; p1Denom = 0.0
    # 上面两个公式是假设每个特征独立,如果有个特征为0,则全部特征为0,为避免这种情况,将所有词出现数初始化为1,将分母初始化为2
    p0Num = ones(numWords); p1Num = ones(numWords)
    p0Denom = 2.0; p1Denom = 2.0
    for i in range(numTrainDocs):
        if trainCategory[i] == 1:
            p1Num += trainMatrix[i]
            p1Denom += sum(trainMatrix[i])
        else:
            p0Num += trainMatrix[i]
            p0Denom += sum(trainMatrix[i])

    # 另一个碰到的问题就是下溢出问题,可以取对数避免下一处或者浮点数舍入导致的错误,
    p1Vect = log(p1Num/p1Denom)
    p0Vect = log(p0Num/p0Denom)
    return p0Vect, p1Vect, pAbusive

# listOPosts, listClasses = loadDataSet()
# myVocablist = createVocaList(listOPosts)

# 创建列表包含所有词
# trainMat = []
# for postinDoc in listOPosts:
#     trainMat.append(setOfWords2Vec(myVocablist, postinDoc))
#
# p0v, p1v, pAb=trainNB0(trainMat, listClasses)
# print(pAb)
# print(p0v)
# print(p1v)

# 朴素贝叶斯分类函数
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 = createVocaList(listOPosts)
    trainMat = []
    for postinDoc in listOPosts:
        trainMat.append(setOfWords2Vec(myVocabList, postinDoc))
    p0V, p1V, pAb = trainNB0(array(trainMat), array(listClasses))
    testEntry = ['love', 'my', 'dalmation']
    thisDoc = array(setOfWords2Vec(myVocabList, testEntry))
    print(testEntry, 'classified as: ', classifyNB(thisDoc, p0V, p1V, pAb))
    testEntry = ['stupid', 'garbage']
    thisDoc = array(setOfWords2Vec(myVocabList, testEntry))
    print(testEntry, 'classified as: ', classifyNB(thisDoc, p0V, p1V, pAb))

testingNB()

总结与反思

这个例子主要是来讲解朴素贝叶斯怎么进行文本情感分类,基于条件概率和特征独立来计算词条向量的情感概率值,有很多地方都进行了简化,像开始例子就提供了词汇表,不需要分词,自己在做文本情感分析的时候上来就需要分词,建立词汇向量表,预测文本也得做分词处理形成分词向量,这里简化了。
如有错误欢迎指正,加油,时间不等人。

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值