《Machine Learning in action》- (笔记)之naive Bayes(1_基础篇)

《Machine Learning in action》,机器学习实战(笔记)之naive Bayes

使用工具

- Python3.7
- pycharm
- anaconda
- jupyter notebook

Chapter one -前言:

  • 朴素贝叶斯(naive Bayes)法是基于贝叶斯定理和特征条件独立假设的分类方法,对于给定的训练数据集,首先基于特征条件独立假设学习输入/输出的联合概率分布,然后基于该模型,对于给定的输入x,利用贝叶斯定定理求出后验概率的最大输出y,朴素贝叶斯法实现简单,学习与预测效率很高,是一种很常用的方法。
  • 同时朴素贝叶斯算法是有监督(supervised learning)的学习算法,主要解决的是分类问题,二分类,或者是多分类的问题。该算法的优点在于简单易懂、学习效率高、在某些领域的分类问题中能够与决策树、神经网络相媲美。但由于该算法以自变量之间的独立(条件特征独立)性和连续变量的正态性假设为前提,就会导致算法精度在某种程度上受影响。

Chapter two-朴素贝叶斯理论:

朴素贝叶斯决策理论:如下面的图片所示

figure
the figure :Two probability distributions with known parameters describing the distribution

  • 上面的图像就是描述分布型的两个已知参数的两个概率分布

我们现在用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
      贝叶斯的理论核心就是选择概率高的决策,下面我们将探索怎么去确定和计算p1、p2的概率

概率论基础

1.条件概率

  • 条件概率(Conditional probability),就是在一件事情发生的情况下,另一件事情发生的概率,我们记前面时间为A,记另一件事件为B,测条件概率的表达就是,在A发生的情况下,B发生的概率,用p(A|B)表示:
    • 我们可以运用以前的公式计算在这里就不详细叙述了,直接看公式: 在这里插入图片描述
  • 可以变形:
    在这里插入图片描述
  • 当然还有另外的两种,都是一样的变换,这里就不一一显示了
  • 便会到条件概率公式:
    在地这里插入图片描述

2.全概率公式

  • 在这里插入图片描述
  • 同样条件概率可以改写为:在这里插入图片描述
  • 贝叶斯公式就是将全概率公式带入到条件概率公式中所形成的。

3. 贝叶斯推断

  • 对条件概率分式变形,可得下面的式子:

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

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

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

  • 可以将上面的理解为 后验概率 = 先验概率 * 调整因子 这就是贝叶斯推断的含义。我们先预估一个"先验概率",然后加入实验结果,看这个实验到底是增强还是削弱了"先验概率",由此得到更接近事实的"后验概率"。

  • 下面就是一个例题:

  • 小明有两只一模一样的袋子A,B编号,A中有30个红球,10的黑球,B中有20个红球,20个黑球,先从中间去取一个球,问这个球是红色的概率是多少?

    • 有全概率公式可以得到(记该事件为E):
      p(E) = p(E|A) P(A) + p(E|B) P(B)
      ** P(E) = (30/40)
      0.5 + (20/40) 0.5 = 60.625
    • 问这颗球来自A袋子的概率是?
      ** 由条件概率可得:P(A|E) = [P(A) P(E|A)] / P(E)
      可得:P(A|E) = (0.5 0.75)/ 0.625 = 0.6
    • 由此可见,当只要是取出来一颗球,那么是从A袋子中的概率就增大了,这就是后验概率

4. 朴素贝叶斯推论

  • 朴素贝叶斯推论就是在贝叶斯理论的基础上,对贝叶斯概率的条件做了一些客观上的要求,既是做条件独立性的假设。
  • 特征条件独立假设:在分类问题当中,常常把是把一个分类到每一个类别当中,而一个事物又有许多的属性(特征)即X=(x1,x2,⋅⋅⋅,xn) ,当然类别也是很多的了,即y=(y1,y2,⋅⋅⋅,yk)。所以我们可以得到公式:
    在这里插入图片描述
  • 当每一个特征独立是,即Xi是独立的时候,就可以得到朴素贝叶斯公式了:

在这里插入图片描述

代码实现《Machine Learning inaction》

书中的例子,对社区的评论留言,将所有的烟柳分化两类,用0 表示带有侮辱性质的留言,用1表示没有带侮辱性质的言论。

第一步准备: making word vectors from text(从文本制作单词向量)
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

if __name__ == '__main__':
    # 实例化
    postingList, classVec = loadDataSet()
    # 遍历PostingList中的单词
    for each in postingList:
        print(each)
    # 为了区分上下的内容
    print("*" * 50)
    print(classVec)

下面就是运行的结果:
在这里插入图片描述

如果想要清楚地知道你说所处理的词汇是否在所创建的词汇表当中,就做词条向量
# 这就是标记化文档,vocabulary
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


# 函数createVocabList()将创建所有文档中所有唯一单词的列表
def createVocabList(dataSet):
    # 创建一个空的不重复的列表
    vocabSet = set([])
    for document in dataSet:
        # Create the union of two sets创建两个列表的联合,就是取并集
        vocabSet = vocabSet | set(document)
    return list(vocabSet)

# 遍历词汇列表,输出数字向量(1代表存在在词汇列表中,0表示不在vocaSet
def setOfWords2Vec(vocabList, inputSet):
    # 创建于vocabList维度一样的列表,只是全部都是0
    returnVec = [0] * len(vocabList)
    # 遍历每一个词汇
    for word in inputSet:
        # 如果词条存在于词汇表当中,就变为1
        if word in vocabList:
            returnVec[vocabList.index(word)] = 1
        else: print("the word: {0}is not in my Vocabulary!".format(word))
    # 返回一个文档向量
    return returnVec

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)

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

  • 输出的结果:
    在这里插入图片描述

下面就是训练朴素贝叶斯分类器

import numpy as np

# 这就是标记化文档,vocabulary
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


# 函数createVocabList()将创建所有文档中所有唯一单词的列表
def createVocabList(dataSet):
    # 创建一个空的不重复的列表
    vocabSet = set([])
    for document in dataSet:
        # Create the union of two sets创建两个列表的联合,就是取并集
        vocabSet = vocabSet | set(document)
    return list(vocabSet)

# 遍历词汇列表,输出数字向量(1代表存在在词汇列表中,0表示不在vocaSet
def setOfWords2Vec(vocabList, inputSet):
    # 创建于vocabList维度一样的列表,只是全部都是0
    returnVec = [0] * len(vocabList)
    # 遍历每一个词汇
    for word in inputSet:
        # 如果词条存在于词汇表当中,就变为1
        if word in vocabList:
            returnVec[vocabList.index(word)] = 1
        else: print("the word: {0}is not in my Vocabulary!".format(word))
    # 返回一个文档向量
    return returnVec


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

Parameters:
    trainMatrix - 训练文档矩阵,即setOfWords2Vec返回的returnVec构成的矩阵
    trainCategory - 训练类别标签向量,即loadDataSet返回的classVec
Returns:
    p0Vect - 侮辱类的条件概率数组
    p1Vect - 非侮辱类的条件概率数组
    pAbusive - 文档属于侮辱类的概率
  
'''
# 朴素贝叶斯分类器-训练函数
def trainNB0(trainMatrix,trainCategory):
    # trainMatrix 训练文档矩阵,就是扇面函数返回的returnVec构成的矩阵
    # 计算训练的文档数目
    numTrainDocs = len(trainMatrix)
    # 计算每一篇文档的词条数目
    numWords = len(trainMatrix[0])
    # 所有文档属中,属于侮辱类的概率
    pAbusive = sum(trainCategory)/float(numTrainDocs)
    # 创建numpy.zero数组,表示词条出现的次数,并且初始化为0
    # numerator 分子,denominator分母,都初始化为0
    p0Num = np.zeros(numWords); p1Num = np.zeros(numWords)
    p0Denom = 0.0; p1Denom = 0.0
    for i in range(numTrainDocs):
        #统计属于侮辱类的条件概率所需的数据,即P(w0|1),P(w1|1),P(w2|1)···
        #这里统计的都是数字不是具体的数据
        if trainCategory[i] == 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就是属于侮辱类的条件概率数组
    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)

并且现实结果:

在这里插入图片描述

  • 当我们只是针对其中的一个数据来分析是就会发现我们的结果还是有一定的问题的,这里是书中的原文When we attempt to classify a document, we multiply a lot of probabilities together to get the probability that a document belongs to a given class. This will look something like p(w0|1)p(w1|1)p(w2|1). If any of these numbers are 0, then when we multiply them together we get 0. To lessen the impact of this, we’ll initialize all of our occurrence counts to 1, and we’ll initialize the denominators to 2
  • 就是说要将我们的p0NUm,P1Num,初始化为1,将p0Denom,p1Denom初始化为2,
  • 将上面的代码,初始化值改为一下的内容
p0Num = ones(numWords); p1Num = ones(numWords)
p0Denom = 2.0; p1Denom = 2.0

这种做法就是我们所说的拉普拉斯平滑laLaplacian Smoothing,又被称为+1平滑,这是常用的平滑手段,就是为了解决0概率问题。

  • 这里还有另外一个问题就是下溢,这是由于太多很小的数相乘造成的。学过数学的人都知道,两个小数相乘,越乘越小,这样就造成了下溢出。在程序中,在相应小数位置进行四舍五入,计算结果可能就变成0了。为了解决这个问题,对乘积结果取自然对数。通过求对数可以避免下溢出或者浮点数舍入导致的错误。同时,采用自然对数进行处理不会有任何损失。下图给出函数f(x)和ln(f(x))的曲线。
  • 观察线面的两个函数f(x)he lnf(X)
    在这里插入图片描述
    检查这两条曲线,就会发现它们在相同区域内同时增加或者减少,并且在相同点上取到极值。它们的取值虽然不同,但不影响最终结果。
    所以我们对其前面的函数TrianNB0中的代码进行小小的改动
p1Vect = np.log(p1Num / p1Denom)
p0Vect = np.log(p0Num / p0Denom)

Naive Bayes classify function 利用改进的朴素贝叶斯分类器进行分类

  • 代码实现:
import numpy as np


# 这就是标记化文档,vocabulary
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


# 函数createVocabList()将创建所有文档中所有唯一单词的列表
def createVocabList(dataSet):
    # 创建一个空的不重复的列表
    vocabSet = set([])
    for document in dataSet:
        # Create the union of two sets创建两个列表的联合,就是取并集
        vocabSet = vocabSet | set(document)
    return list(vocabSet)


# 遍历词汇列表,输出数字向量(1代表存在在词汇列表中,0表示不在vocaSet
def setOfWords2Vec(vocabList, inputSet):
    # 创建于vocabList维度一样的列表,只是全部都是0
    returnVec = [0] * len(vocabList)
    # 遍历每一个词汇
    for word in inputSet:
        # 如果词条存在于词汇表当中,就变为1
        if word in vocabList:
            returnVec[vocabList.index(word)] = 1
        else:
            print("the word: {0}is not in my Vocabulary!".format(word))
    # 返回一个文档向量
    return returnVec


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

Parameters:
    trainMatrix - 训练文档矩阵,即setOfWords2Vec返回的returnVec构成的矩阵
    trainCategory - 训练类别标签向量,即loadDataSet返回的classVec
Returns:
    p0Vect - 侮辱类的条件概率数组
    p1Vect - 非侮辱类的条件概率数组
    pAbusive - 文档属于侮辱类的概率

'''


# 朴素贝叶斯分类器-训练函数
def trainNB0(trainMatrix, trainCategory):
    # trainMatrix 训练文档矩阵,就是扇面函数返回的returnVec构成的矩阵
    # 计算训练的文档数目
    numTrainDocs = len(trainMatrix)
    # 计算每一篇文档的词条数目
    numWords = len(trainMatrix[0])
    # 所有文档属中,属于侮辱类的概率
    pAbusive = sum(trainCategory) / float(numTrainDocs)
    # 创建numpy.zero数组,表示词条出现的次数,并且初始化为0
    # numerator 分子,denominator分母,都初始化为0
    '''
    使用拉普拉斯平滑将下面的初始化进行改进
    p0Num = np.zeros(numWords);p1Num = np.zeros(numWords)
    p0Denom = 0.0;p1Denom = 0.0
    '''
    p0Num = np.ones(numWords); p1Num = np.ones(numWords)
    p0Denom = 2.0; p1Denom = 2.0
    for i in range(numTrainDocs):
        # 统计属于侮辱类的条件概率所需的数据,即P(w0|1),P(w1|1),P(w2|1)···
        # 这里统计的都是数字不是具体的数据
        if trainCategory[i] == 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就是属于侮辱类的条件概率数组
    '''
    利用取对数的方式处理概率的下溢的问题解决:
    p1Vect = p1Num / p1Denom
    # 属于非侮辱类的条件概率数组
    p0Vect = p0Num / p0Denom
    '''
    p1Vect = np.log(p1Num / p1Denom)
    # 属于非侮辱类的条件概率数组
    p0Vect = np.log(p0Num / p0Denom)
    # #返回属于侮辱类的条件概率数组,属于非侮辱类的条件概率数组,文档属于侮辱类的概率
    return p0Vect, p1Vect, pAbusive

'''
这就是朴素贝叶斯分类器分类函数
vec2Classify - 待分类的词条数组
p0Vec - 侮辱类的条件概率数组
p1Vec -非侮辱类的条件概率数组
pClass1 - 文档属于侮辱类的概率
'''
def classifyNB(vec2Classify, p0Vec, p1Vec, pClass1):
    # 对应元素相乘。logA * B = logA + logB,所以这里加上log(pClass1)
    p1 = sum(vec2Classify * p1Vec) + np.log(pClass1)
    p0 = sum(vec2Classify * p0Vec) + np.log(1.0 - pClass1)
    if p1 > p0:
        return 1
    else:
        return 0

'''
这就是最后的测试函数,测试朴素贝叶斯分类器

'''
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))
    # 测试样本1
    testEntry = ['love', 'my', 'dalmation']
    # 将测试样本向量化
    thisDoc = np.array(setOfWords2Vec(myVocabList, testEntry))
    if classifyNB(thisDoc,p0V,p1V,pAb):
        # 执行分类并打印分类结果
        print(testEntry,'属于侮辱类')
    else:
        # 同上,都是执行分类
        print(testEntry,'属于非侮辱类')
        # 测试样本2
    testEntry = ['stupid', 'garbage']
    # 同样,将样本向量化
    thisDoc = np.array(setOfWords2Vec(myVocabList, testEntry))
    if classifyNB(thisDoc,p0V,p1V,pAb):
        print(testEntry,'属于侮辱类')
    else:
        print(testEntry,'属于非侮辱类')


if __name__ == '__main__':
    # 调用函数、
    testingNB()

下面就是实验的结果:
在这里插入图片描述
看的出来我们的结果还是很理想的了,在下面的一篇当中,将会就书中的实例进行实践

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

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值