朴素贝叶斯(上)

朴素贝叶斯算法是有监督的学习算法,解决的是分类问题,如客户是否流失、是否值得投资、信用等级评定等多分类问题。

该算法的优点在于简单易懂、学习效率高、在某些领域的分类问题中能够与决策树、神经网络相媲美。但由于该算法以自变量之间的独立(条件特征独立)性和连续变量的正态性假设为前提,就会导致算法精度在某种程度上受影响

朴素贝叶斯理论

假设现在我们有一个数据集,它由两类数据组成,数据分布如下图所示:
在这里插入图片描述

我们现在用p1(x,y)表示数据点(x,y)属于类别1(图中红色圆点表示的类别)的概率,用p2(x,y)表示数据点(x,y)属于类别2(图中蓝色三角形表示的类别)的概率,那么对于一个新数据点(x,y),可以用下面的规则来判断它的类别:

  • 如果p1(x,y) > p2(x,y),那么类别为1
  • 如果p1(x,y) < p2(x,y),那么类别为2
    也就是说,我们会选择高概率对应的类别。这就是贝叶斯决策理论的核心思想,即选择具有最高概率的决策
条件概率

条件概率(Condittional probability),就是指在事件B发生的情况下,事件A发生的概率,用P(A|B)来表示。
在这里插入图片描述

根据韦恩图,可以清楚地看到在事件B发生地情况下,事件A发生地概率就是 P ( A ∩ B ) / P ( B ) P(A∩B)/P(B) P(AB)/P(B),即
P ( A ∣ B ) = P ( A ∩ B ) P ( B ) P(A \mid B)=\frac{P(A \cap B)}{P(B)} P(AB)=P(B)P(AB)

因此
P ( A ∩ B ) = P ( A ∣ B ) P ( B ) P(A \cap B)=P(A \mid B) P(B) P(AB)=P(AB)P(B)
P ( A ∩ B ) = P ( B ∣ A ) P ( A ) P(A \cap B)=P(B \mid A) P(A) P(AB)=P(BA)P(A)

所以
P ( A ∣ B ) P ( B ) = P ( B ∣ A ) P ( A ) P(A \mid B) P(B)=P(B \mid A) P(A) P(AB)P(B)=P(BA)P(A)

可得条件概率计算公式
P ( A ∣ B ) = P ( B ∣ A ) P ( A ) P ( B ) P(A \mid B)=\frac{P(B \mid A) P(A)}{P(B)} P(AB)=P(B)P(BA)P(A)

全概率公式

除了条件概率以外,在计算p1和p2的时候,还要用到全概率公式

假定样本空间S,是两个事件A与A’的和
在这里插入图片描述

上图中,红色部分是事件A,绿色部分是事件A’,它们共同构成了样本空间S。
在这种情况下,事件B又可以划分成两个部分
在这里插入图片描述


P ( B ) = P ( B ∩ A ) + P ( B ∩ A ′ ) P(B)=P(B \cap A)+P\left(B \cap A^{\prime}\right) P(B)=P(BA)+P(BA)

从上面我们可以得到
P ( A ∩ B ) = P ( B ∣ A ) P ( A ) P(A \cap B)=P(B \mid A) P(A) P(AB)=P(BA)P(A)

所以
P ( B ) = P ( B ∣ A ) P ( A ) + P ( B ∣ A ′ ) P ( A ′ ) P(B)=P(B \mid A) P(A)+P\left(B \mid A^{\prime}\right) P\left(A^{\prime}\right) P(B)=P(BA)P(A)+P(BA)P(A)

这就是全概率公式。它的含义是,如果A和A’构成样本空间的一个划分,那么事件B的概率,就等于A和A’的概率分别乘以B对这两个事件的条件概率之和。

将这个公式代入上一节的条件概率公式,就得到了条件概率的另一种写法:

P ( A ∣ B ) = P ( B ∣ A ) P ( A ) P ( B ∣ A ) P ( A ) + P ( B ∣ A ′ ) P ( A ′ ) P(A \mid B)=\frac{P(B \mid A) P(A)}{P(B \mid A) P(A)+P\left(B \mid A^{\prime}\right) P\left(A^{\prime}\right)} P(AB)=P(BA)P(A)+P(BA)P(A)P(BA)P(A)

把分母代换为P(B)就可以得到贝叶斯推断公式
P ( A ∣ B ) = P ( A ) P ( B ∣ A ) P ( B ) P(A \mid B)=P(A) \frac{P(B \mid A)}{ P(B)} P(AB)=P(A)P(B)P(BA)

我们把

  • P ( A ) P(A) P(A)称为"先验概率"(Prior probability),即在B事件发生之前,我们对A事件概率的一个判断。

  • P ( A ∣ B ) P(A|B) P(AB)称为"后验概率"(Posterior probability),即在B事件发生之后,我们对A事件概率的重新评估。

  • P ( B ∣ A ) / P ( B ) P(B|A)/P(B) P(BA)/P(B)称为"可能性函数"(Likelyhood),这是一个调整因子,使得预估概率更接近真实概率。

所以,条件概率可以理解成下面的式子:

后 验 概 率   =   先 验 概 率 x 调 整 因 子 后验概率 = 先验概率 x 调整因子   

这就是贝叶斯推断的含义。我们先预估一个"先验概率",然后加入实验结果,看这个实验到底是增强还是削弱了"先验概率",由此得到更接近事实的"后验概率"。

在这里,如果"可能性函数"P(B|A)/P(B)>1,意味着"先验概率"被增强,事件A的发生的可能性变大;如果"可能性函数"=1,意味着B事件无助于判断事件A的可能性;如果"可能性函数"<1,意味着"先验概率"被削弱,事件A的可能性变小。

示例

离散型数据案例
在这里插入图片描述

针对这个问题,我们先确定一共有 3 个属性,一共有两个类别

  • 假设我们用 A 代表属性,用 A1, A2, A3 分别为身高、体重、鞋码。
  • 假设用 C 代表类别,那么 C1,C2 分别是:男、女,在未知的情况下我们用 Cj 表示。

假设 A 1 ( 身 高 ) = 高 A1(身高)=高 A1()=, A 2 ( 体 重 ) = 重 A2(体重)=重 A2()=, A 3 ( 鞋 码 ) = 重 A3(鞋码)=重 A3()=事件下,求Cj的概率,用条件概率表示为 P ( C j ∣ A 1 A 2 A 3 ) P(Cj|A1A2A3) P(CjA1A2A3)
根据上面讲的贝叶斯的公式,我们可以得出:
P ( C j ∣ A 1 A 2 A 3 ) = P ( A 1 A 2 A 3 ∣ C j ) P ( C j ) P ( A 1 A 2 A 3 ) P\left(C_{j} \mid A_{1} A_{2} A_{3}\right)=\frac{P\left(A_{1} A_{2} A_{3} \mid C_{j}\right) P\left(C_{j}\right)}{P\left(A_{1} A_{2} A_{3}\right)} P(CjA1A2A3)=P(A1A2A3)P(A1A2A3Cj)P(Cj)

因为一共有 2 个类别,所以我们只需要求得 P ( C 1 ∣ A 1 A 2 A 3 ) P(C1|A1A2A3) P(C1A1A2A3) P ( C 2 ∣ A 1 A 2 A 3 ) P(C2|A1A2A3) P(C2A1A2A3)的概率即可,然后比较下哪个分类的可能性大,就是哪个分类结果。



在这个公式里,因为 P ( A 1 A 2 A 3 ) P(A1A2A3) P(A1A2A3)都是固定的,我们想要寻找使得 P ( C j ∣ A 1 A 2 A 3 ) P(Cj|A1A2A3) P(CjA1A2A3)的最大值,就等价于求 P ( A 1 A 2 A 3 ∣ C j ) P ( C j ) P(A1A2A3|Cj)P(Cj) P(A1A2A3Cj)P(Cj)最大值。

我们假设 A i Ai Ai之间是相互独立的,那么
P ( A 1 A 2 A 3 ∣ C j ) = P ( A 1 ∣ C j ) P ( A 2 ∣ C j ) P ( A 3 ∣ C j ) \mathrm{P}(\mathrm{A} 1 \mathrm{A} 2 \mathrm{A} 3 \mid \mathrm{Cj})=\mathrm{P}(\mathrm{A} 1 \mid \mathrm{Cj}) \mathrm{P}(\mathrm{A} 2 \mid \mathrm{Cj}) \mathrm{P}(\mathrm{A} 3 \mid \mathrm{Cj}) P(A1A2A3Cj)=P(A1Cj)P(A2Cj)P(A3Cj)

然后我们需要从 A i Ai Ai C j Cj Cj中计算出 P ( A i ∣ C j ) P(Ai|Cj) P(AiCj)的概率,带入到上面的公式得出 P ( A 1 A 2 A 3 ∣ C j ) P(A1A2A3|Cj) P(A1A2A3Cj),最后找到使得 P ( A 1 A 2 A 3 ∣ C j ) P(A1A2A3|Cj) P(A1A2A3Cj)最大的类别 C j Cj Cj

我分别求下这些条件下的概率:
P ( A 1 ∣ C 1 ) = 1 / 2 , P ( A 2 ∣ C 1 ) = 1 / 2 , P ( A 3 ∣ C 1 ) = 1 / 4 , P ( A 1 ∣ C 2 ) = 0 , P ( A 2 ∣ C 2 ) = 1 / 2 t P ( A 3 ∣ C 2 ) = 1 / 2 ,  所以  P ( A 1 A 2 A 3 ∣ C 1 ) = 1 / 16 , P ( A 1 A 2 A 3 ∣ C 2 ) = 0 \begin{array}{l} \mathrm{P}(\mathrm{A} 1 \mid \mathrm{C} 1)=1 / 2, \mathrm{P}(\mathrm{A} 2 \mid \mathrm{C} 1)=1 / 2, \mathrm{P}(\mathrm{A} 3 \mid \mathrm{C} 1)=1 / 4, \mathrm{P}(\mathrm{A} 1 \mid \mathrm{C} 2)=0, \mathrm{P}(\mathrm{A} 2 \mid \mathrm{C} 2)=1 / 2_{t} \\ \mathrm{P}(\mathrm{A} 3 \mid \mathrm{C} 2)=1 / 2, \text { 所以 } \mathrm{P}(\mathrm{A} 1 \mathrm{A} 2 \mathrm{A} 3 \mid \mathrm{C} 1)=1 / 16, \mathrm{P}(\mathrm{A} 1 \mathrm{A} 2 \mathrm{A} 3 \mid \mathrm{C} 2)=0 \end{array} P(A1C1)=1/2,P(A2C1)=1/2,P(A3C1)=1/4,P(A1C2)=0,P(A2C2)=1/2tP(A3C2)=1/2, 所以 P(A1A2A3C1)=1/16,P(A1A2A3C2)=0

因为 P ( A 1 A 2 A 3 ∣ C 1 ) P ( C 1 ) P(A1A2A3|C1)P(C1) P(A1A2A3C1)P(C1)> P ( A 1 A 2 A 3 ∣ C 2 ) P ( C 2 ) P(A1A2A3|C2)P(C2) P(A1A2A3C2)P(C2),所以应该是 C 1 C1 C1类别,即男性。

连续数据案例
在这里插入图片描述

那么如果给你一个新的数据,身高 180、体重 120,鞋码 41,请问该人是男是女呢?

公式还是上面的公式,这里的困难在于,由于身高、体重、鞋码都是连续变量,不能采用离散变量的方法计算概率。而且由于样本太少,所以也无法分成区间计算。怎么办呢?

这时,可以假设男性和女性的身高、体重、鞋码都是正态分布,通过样本计算出均值和方差,也就是得到正态分布的密度函数。有了密度函数,就可以把值代入,算出某一点的密度函数的值。比如,男性的身高是均值 179.5、标准差为 3.697 的正态分布。

所以调用正态分布函数可得

  • 男性的身高为 180 180 180的概率为 0.1069 0.1069 0.1069

同理

我们可以计算得出

  • 男性体重为 120 120 120的概率为 0.000382324 0.000382324 0.000382324

  • 男性鞋码为 41 41 41号的概率为 0.120304111 0.120304111 0.120304111

所以我们可以计算得出:

P ( A 1 A 2 A 3 ∣ C 1 ) = P ( A 1 ∣ C 1 ) P ( A 2 ∣ C 1 ) P ( A 3 ∣ C 1 ) = 0.1069 ∗ 0.000382324 ∗ 0.120304111 = 4.9169 e − 6 \begin{array}{l} P(A1A2A3 \mid C 1)=P(A 1 \mid C 1) P(A 2 \mid C 1) P(A 3 \mid C 1)=0.1069 * 0 .000382324 * 0 .120304111=4.9169e-6 \end{array} P(A1A2A3C1)=P(A1C1)P(A2C1)P(A3C1)=0.10690.0003823240.120304111=4.9169e6

同理我们也可以按照正态分布计算出来该人为女的可能性

  • 女性的身高为 180 180 180的概率为 0.00000147489 0.00000147489 0.00000147489

  • 女性体重为 120 120 120的概率为 0.015354144 0 .015354144 0.015354144

  • 女性鞋码为 41 41 41号的概率为 0.120306074 0 .120306074 0.120306074

P ( A 1 A 2 A 3 ∣ C 2 ) = P ( A 1 ∣ C 2 ) P ( A 2 ∣ C 2 ) P ( A 3 ∣ C 2 ) = 0.00000147489 ∗ 0.015354144 ∗ 0.120306074 = 2.7244 e − 9 \begin{array}{l} \mathrm{P}(\mathrm{A} 1 \mathrm{A} 2 \mathrm{A} 3 \mid \mathrm{C} 2)=\mathrm{P}(\mathrm{A} 1 \mid \mathrm{C} 2) \mathrm{P}(\mathrm{A} 2 \mid \mathrm{C} 2) \mathrm{P}(\mathrm{A} 3 \mid \mathrm{C} 2)=0.00000147489 * 0 .015354144 * 0 .120306074 = 2.7244 \mathrm{e}-9 \end{array} P(A1A2A3C2)=P(A1C2)P(A2C2)P(A3C2)=0.000001474890.0153541440.120306074=2.7244e9

朴素贝叶斯推断

理解了贝叶斯推断,那么让我们继续看看朴素贝叶斯。贝叶斯和朴素贝叶斯的概念是不同的,区别就在于“朴素”二字,朴素贝叶斯对条件个概率分布做了条件独立性的假设。 比如下面的公式,假设有n个特征:

P ( a ∣ X ) = p ( X ∣ a ) p ( a ) = p ( x 1 , x 2 , x 3 , … x n ∣ a ) p ( a ) \mathrm{P}(\mathrm{a} \mid \mathrm{X})=\mathrm{p}(\mathrm{X} \mid \mathrm{a}) \mathrm{p}(\mathrm{a})=\mathrm{p}\left(x_{1}, x_{2}, x_{3}, \ldots x_{n} \mid \mathrm{a}\right) \mathrm{p}(\mathrm{a}) P(aX)=p(Xa)p(a)=p(x1,x2,x3,xna)p(a)

由于每个特征都是独立的,我们可以进一步拆分公式

p ( a ∣ X ) = p ( X ∣ a ) p ( a ) = { p ( x 1 ∣ a ) ∗ p ( x 2 ∣ a ) ∗ p ( x 3 ∣ a ) ∗ … ∗ p ( x n ∣ a ) } p ( a ) \begin{array}{l} p(a \mid X)=p(X \mid a) p(a) \\ =\left\{p\left(x_{1} \mid a\right) * p\left(x_{2} \mid a\right) * p\left(x_{3} \mid a\right) * \ldots * p\left(x_{n} \mid a\right)\right\} p(a) \end{array} p(aX)=p(Xa)p(a)={p(x1a)p(x2a)p(x3a)p(xna)}p(a)

这样我们就可以进行计算了实现贝叶斯分类器。

某个医院早上来了六个门诊的病人,他们的情况如下表所示:

症状职业疾病
打喷嚏护士感冒
打喷嚏农夫过敏
头痛建筑工人脑震荡
头痛建筑工人感冒
打喷嚏教师感冒
头痛教师脑震荡

现在又来了第七个病人,是一个打喷嚏的建筑工人。请问他患上感冒的概率有多大?

根据贝叶斯定理:
P ( A ∣ B ) = P ( B ∣ A ) P ( A ) P ( B ) P(A \mid B)=\frac{P(B \mid A) P(A)}{P(B)} P(AB)=P(B)P(BA)P(A)
根据朴素贝叶斯条件独立性的假设可知,"打喷嚏"和"建筑工人"这两个特征是独立的,

P (  感冒|打喷量 X 建筑工人)  = P (  打喷量|感冒  ) × P (  建筑工人|感冒  ) × P (  感冒  ) P (  打喷量  ) × P (  建筑工人  ) \begin{aligned} &\mathrm{P}(\text { 感冒|打喷量 X 建筑工人) }\\ &=\frac{\mathrm{P}(\text { 打喷量|感冒 }) \times \mathrm{P}(\text { 建筑工人|感冒 }) \times \mathrm{P}(\text { 感冒 })}{\mathrm{P}(\text { 打喷量 }) \times \mathrm{P}(\text { 建筑工人 })} \end{aligned} P( 感冒|打喷量 X 建筑工人=P( 打喷量 )×P( 建筑工人 )P( 打喷量|感冒 )×P( 建筑工人|感冒 )×P( 感冒 )

这里可以计算:
P (  感冒|打喷量  x  建筑工人  ) = 0.66 × 0.33 × 0.5 0.5 × 0.33 = 0.66 P(\text { 感冒|打喷量 } x \text { 建筑工人 })=\frac{0.66 \times 0.33 \times 0.5}{0.5 \times 0.33}=0.66 P( 感冒|打喷量 x 建筑工人 )=0.5×0.330.66×0.33×0.5=0.66

因此,这个打喷嚏的建筑工人,有66%的概率是得了感冒。同理,可以计算这个病人患上过敏或脑震荡的概率。比较这几个概率,就可以知道他最可能得什么病。

这就是贝叶斯分类器的基本方法:在统计资料的基础上,依据某些特征,计算各个类别的概率,从而实现分类。

朴素贝叶斯实战

以在线社区留言为例。为了不影响社区的发展,我们要屏蔽侮辱性的言论,所以要构建一个快速过滤器,如果某条留言使用了负面或者侮辱性的语言,那么就将该留言标志为内容不当。过滤这类内容是一个很常见的需求。对此问题建立两个类型:侮辱类和非侮辱类,使用1和0分别表示。

我们把文本看成单词向量或者词条向量,也就是说将句子转换为向量。考虑出现所有文档中的单词,再决定将哪些单词纳入词汇表或者说所要的词汇集合,然后必须要将每一篇文档转换为词汇表上的向量。

import numpy as np
from functools import reduce
# -*- coding: UTF-8 -*-

"""
函数说明:创建实验样本

Parameters:
    无
Returns:
    postingList - 实验样本切分的词条
    classVec - 类别标签向量
"""
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

if __name__ == '__main__':
    postingLIst, classVec = loadDataSet()
    for each in postingLIst:
        print(each)
    print(classVec)

['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']
[0, 1, 0, 1, 0, 1]

从运行结果可以看出,我们已经将postingList是存放词条列表中,classVec是存放每个词条的所属类别,1代表侮辱类 ,0代表非侮辱类。

继续,前面我们已经说过我们要先创建一个词汇表,并将切分好的词条转换为词条向量。

"""
函数说明:根据vocabList词汇表,将inputSet向量化,向量的每个元素为1或0

Parameters:
    vocabList - createVocabList返回的列表
    inputSet - 切分的词条列表
Returns:
    returnVec - 文档向量,词集模型
"""
def setOfWords2Vec(vocabList, inputSet):
    returnVec = [0] * len(vocabList)                                    #创建一个其中所含元素都为0的向量
    for word in inputSet:                                                #遍历每个词条
        if word in vocabList:                                            #如果词条存在于词汇表中,则置1
            returnVec[vocabList.index(word)] = 1
        else: print("the word: %s is not in my Vocabulary!" % word)
    return returnVec                                                    #返回文档向量

"""
函数说明:将切分的实验样本词条整理成不重复的词条列表,也就是词汇表

Parameters:
    dataSet - 整理的样本数据集
Returns:
    vocabSet - 返回不重复的词条列表,也就是词汇表
"""
def createVocabList(dataSet):
    vocabSet = set([])                      #创建一个空的不重复列表
    for document in dataSet:               
        vocabSet = vocabSet | set(document) #取并集
    return list(vocabSet)

if __name__ == '__main__':
    postingList, classVec = loadDataSet()
    print('postingList:\n',postingList)
    myVocabList = createVocabList(postingList)
    print('myVocabList:\n',myVocabList)
    trainMat = []
    for postinDoc in postingList:
        trainMat.append(setOfWords2Vec(myVocabList, postinDoc))
    print('trainMat:\n', trainMat)

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']]
myVocabList:
 ['ate', 'love', 'steak', 'quit', 'problems', 'dog', 'buying', 'my', 'stupid', 'is', 'how', 'so', 'maybe', 'I', 'mr', 'to', 'dalmation', 'stop', 'help', 'posting', 'garbage', 'take', 'please', 'worthless', 'has', 'flea', 'him', 'licks', 'food', 'cute', 'park', 'not']
trainMat:
 [[0, 0, 0, 0, 1, 1, 0, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 0, 0, 0, 1, 0, 1, 1, 0, 0, 0, 0, 0, 0], [0, 0, 0, 0, 0, 1, 0, 0, 1, 0, 0, 0, 1, 0, 0, 1, 0, 0, 0, 0, 0, 1, 0, 0, 0, 0, 1, 0, 0, 0, 1, 1], [0, 1, 0, 0, 0, 0, 0, 1, 0, 1, 0, 1, 0, 1, 0, 0, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 0, 0, 1, 0, 0], [0, 0, 0, 0, 0, 0, 0, 0, 1, 0, 0, 0, 0, 0, 0, 0, 0, 1, 0, 1, 1, 0, 0, 1, 0, 0, 0, 0, 0, 0, 0, 0], [1, 0, 1, 0, 0, 0, 0, 1, 0, 0, 1, 0, 0, 0, 1, 1, 0, 1, 0, 0, 0, 0, 0, 0, 0, 0, 1, 1, 0, 0, 0, 0], [0, 0, 0, 1, 0, 1, 1, 0, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 0, 0, 0, 0, 1, 0, 0, 0]]

从运行结果可以看出,postingList是原始的词条列表,myVocabList是词汇表。myVocabList是所有单词出现的集合,没有重复的元素。

词汇表是用来干什么的?

它是用来将词条向量化的,一个单词在词汇表中出现过一次,那么就在相应位置记作1,如果没有出现就在相应位置记作0。

trainMat是所有的词条向量组成的列表。它里面存放的是根据myVocabList向量化的词条向量。

我们已经得到了词条向量。接下来,我们就可以通过词条向量训练朴素贝叶斯分类器

"""
函数说明:朴素贝叶斯分类器训练函数

Parameters:
    trainMatrix - 训练文档矩阵,即setOfWords2Vec返回的returnVec构成的矩阵
    trainCategory - 训练类别标签向量,即loadDataSet返回的classVec
Returns:
    p0Vect - 侮辱类的条件概率数组
    p1Vect - 非侮辱类的条件概率数组
    pAbusive - 文档属于侮辱类的概率
"""
def trainNB0(trainMatrix,trainCategory):
    numTrainDocs = len(trainMatrix)                            #计算训练的文档数目
    numWords = len(trainMatrix[0])                            #计算每篇文档的词条数
    pAbusive = sum(trainCategory)/float(numTrainDocs)        #文档属于侮辱类的概率
    p0Num = np.zeros(numWords); p1Num = np.zeros(numWords)    #创建numpy.zeros数组,词条出现数初始化为0
    p0Denom = 0.0; p1Denom = 0.0                            #分母初始化为0
    for i in range(numTrainDocs):
        if trainCategory[i] == 1:                            #统计属于侮辱类的条件概率所需的数据,即P(w0|1),P(w1|1),P(w2|1)···
            p1Num += trainMatrix[i]
            p1Denom += sum(trainMatrix[i])
        else:                                                #统计属于非侮辱类的条件概率所需的数据,即P(w0|0),P(w1|0),P(w2|0)···
            p0Num += trainMatrix[i]
            p0Denom += sum(trainMatrix[i])
    p1Vect = p1Num/p1Denom                                      
    p0Vect = p0Num/p0Denom         
    return p0Vect,p1Vect,pAbusive                            #返回属于侮辱类的条件概率数组,属于非侮辱类的条件概率数组,文档属于侮辱类的概率

if __name__ == '__main__':
    postingList, classVec = loadDataSet()
    myVocabList = createVocabList(postingList)
    print('myVocabList:\n', myVocabList)
    trainMat = []
    for postinDoc in postingList:
        trainMat.append(setOfWords2Vec(myVocabList, postinDoc))
    p0V, p1V, pAb = trainNB0(trainMat, classVec)
    print('p0V:\n', p0V)
    print('p1V:\n', p1V)
    print('classVec:\n', classVec)
    print('pAb:\n', pAb)

myVocabList:
 ['ate', 'love', 'steak', 'quit', 'problems', 'dog', 'buying', 'my', 'stupid', 'is', 'how', 'so', 'maybe', 'I', 'mr', 'to', 'dalmation', 'stop', 'help', 'posting', 'garbage', 'take', 'please', 'worthless', 'has', 'flea', 'him', 'licks', 'food', 'cute', 'park', 'not']
p0V:
 [0.04166667 0.04166667 0.04166667 0.         0.04166667 0.04166667
 0.         0.125      0.         0.04166667 0.04166667 0.04166667
 0.         0.04166667 0.04166667 0.04166667 0.04166667 0.04166667
 0.04166667 0.         0.         0.         0.04166667 0.
 0.04166667 0.04166667 0.08333333 0.04166667 0.         0.04166667
 0.         0.        ]
p1V:
 [0.         0.         0.         0.05263158 0.         0.10526316
 0.05263158 0.         0.15789474 0.         0.         0.
 0.05263158 0.         0.         0.05263158 0.         0.05263158
 0.         0.05263158 0.05263158 0.05263158 0.         0.10526316
 0.         0.         0.05263158 0.         0.05263158 0.
 0.05263158 0.05263158]
classVec:
 [0, 1, 0, 1, 0, 1]
pAb:
 0.5

p0V存放的是每个单词属于类别0,也就是非侮辱类词汇的概率。比如p0V的倒数第6个概率,就是stupid这个单词属于非侮辱类的概率为0。



同理,p1V的倒数第6个概率,就是stupid这个单词属于侮辱类的概率为0.15789474,也就是约等于15.79%的概率。



pAb是所有侮辱类的样本占所有样本的概率,从classVec中可以看出,一用有3个侮辱类,3个非侮辱类。所以侮辱类的概率是0.5。



因此p0V存放的就是P(him|非侮辱类) = 0.0833、P(is|非侮辱类) = 0.0417,一直到P(dog|非侮辱类) = 0.0417,这些单词的条件概率.



同理,p1V存放的就是各个单词属于侮辱类的条件概率。pAb就是先验概率。

已经训练好分类器,接下来,使用分类器进行分类。

def classifyNB(vec2Classify, p0Vec, p1Vec, pClass1):
    p1 = reduce(lambda x,y:x*y, vec2Classify * p1Vec) * pClass1    #对应元素相乘
    p0 = reduce(lambda x,y:x*y, vec2Classify * p0Vec) * (1.0 - pClass1)
    print('p0:',p0)
    print('p1:',p1)
    if p1 > p0:
        return 1
    else: 
        return 0

"""
函数说明:测试朴素贝叶斯分类器

Parameters:
    无
Returns:
    无
"""
def testingNB():
    listOPosts,listClasses = loadDataSet()									#创建实验样本
    myVocabList = createVocabList(listOPosts)								#创建词汇表
    trainMat=[]
    for postinDoc in listOPosts:
        trainMat.append(setOfWords2Vec(myVocabList, postinDoc))				#将实验样本向量化
    p0V,p1V,pAb = trainNB0(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()

p0: 0.0
p1: 0.0
['love', 'my', 'dalmation'] 属于非侮辱类
p0: 0.0
p1: 0.0
['stupid', 'garbage'] 属于非侮辱类

我们测试了两个词条,在使用分类器前,也需要对词条向量化,然后使用classifyNB()函数,用朴素贝叶斯公式,计算词条向量属于侮辱类和非侮辱类的概率。

朴素贝叶斯总结

朴素贝叶斯推断的一些优点:

  • 生成式模型,通过计算概率来进行分类,可以用来处理多分类问题。
  • 对小规模的数据表现很好,适合多分类任务,适合增量式训练,算法也比较简单。

朴素贝叶斯推断的一些缺点:

  • 对输入数据的表达形式很敏感。
  • 由于朴素贝叶斯的“朴素”特点,所以会带来一些准确率上的损失。
  • 需要计算先验概率,分类决策存在错误率。

其它:

  • 存在一定的问题,需要进行改进,下篇文章会讲解改进方法;(概率为0的情况)
  • 没有进行前期的文本切分

朴素贝叶斯的准确率,其实是比较依赖于训练语料的,机器学习算法就和纯洁的小孩一样,取决于其成长(训练)条件,“吃的是草挤的是奶”

评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值