朴素贝叶斯

概率论是许多机器学习算法的基础。贝叶斯算法是一类算法,这是一类以条件概率的计算为核心进行分类的算法,而朴素贝叶斯算法是其中最简单的概率分类器。之所以称之为朴素,是因为整个形式化过程只做最原始、最简单的假设,并且假设数据中的特征间都是不相关的。
我们现在有一个数据集,它由两类数据组成,数据分布如下图所示:


两个参数的概率分布

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

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

贝叶斯方法的基本概念

1. 事件

统计学中用“事件”表示有概率可言的任何事情,也就是说,事件是人们能指出发生可能性的任何事情。比如天气为晴天和非晴天,收到垃圾邮件和非垃圾邮件。

2. 概率

一个事件发生的概率可以通过观测到的数据来估计,即用该事件发生的试验的次数除以试验的总次数。通常用符号p(A)来表示事件A发生的概率。



例如,如果10天中有3天下雨了,那么就可以估计下雨的概率为30%。同样,如果50封电子邮件中有10封是垃圾邮件,那么可以估计垃圾邮件的概率为20%,即P(垃圾邮件)=0.2。

3. 联合概率

联合概率是指多个条件同时成立的概率。

特别的,如果这两个条件是独立的,如抛硬币结果和天气是否下雨可以认为是独立的,则


4. 贝叶斯定理

贝叶斯定理

p(c)称为先验概率,p(c|x)称为后验概率
如下图,有7个球,其中3个白球,4个黑球,随机取出一个球,那么取到白球的概率是3/7,取到黑球的概率是4/7。



可是,如果把这7个球放入两个桶中,等于是事先加了一个条件,又该怎么计算呢?



根据条件概率计算公式,P(黑球|桶A)= P(桶A ,黑球)/P(桶A)
易知,P(黑球)= 4/7,P(桶A)= 4/7(球在桶A中的概率)
P(桶A,黑球)是指黑球并且在桶A的概率,用桶A中黑球数除以所有球的总数=2/7
所以,可以求得P(黑球|桶A)= (2/7)/(4/7)=1/2,因此桶A中取到黑球的概率是1/2,这也与我们的观察一致。

也可以用贝叶斯定理来求:P(黑球|桶A)= P(桶A|黑球)* P(黑球)/ P(桶A)
P(桶A|黑球)是指黑球中在桶A的概率=2/4=1/2,P(黑球)= 4/7,P(桶A)= 4/7
所以P(黑球|桶A)= (1/2*4/7)/(4/7) = 1/2,结果和用条件概率求解的一样。

具体案例

按照书中所说,以在线社区留言板为例。为了不影响社区的发展,我们要屏蔽侮辱性的言论,所以要构建一个快速过滤器,如果某条留言使用了负面或者侮辱性的语言,那么就将该留言标识为内容不当。对此问题建立两个类别:侮辱类和非侮辱类,使用1和0分别表示。
现在需求是这样的,给我们一个留言,需要判断是否是侮辱性的。输入是一些预先分好类的留言和分类结果

加载数据
def loadDataSet():
    """
    创建数据集
    :return: 单词列表postingList, 所属类别classVec
    """
    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):
    """
    获取所有单词的集合
    :param dataSet: 数据集
    :return: 所有单词的集合(即不含重复元素的单词列表)
    """
    vocabSet = set([])
    for document in dataSet:
        # 求并集
        vocabSet = vocabSet|set(document)
    # 返回单位的列表
    return list(vocabSet)

dataSet,classVec = loadDataSet()
vocabList = createVocabList(dataSet)
print(vocabList)

输出

['licks', 'maybe', 'I', 'quit', 'posting', 'cute', 'dog', 'flea', 'help', 'park', 'is', 'worthless', 'take', 'my', 'so', 'ate', 'steak', 'has', 'dalmation', 'love', 'buying', 'not', 'to', 'problems', 'stop', 'mr', 'how', 'food', 'stupid', 'garbage', 'him', 'please']
使用词典把输入的留言转换成0,1向量
def setOfWords2Vec(vocabList,inputSet):
    """
    遍历查看该单词是否出现,出现该单词则将该单词置1
    :param vocabList: 所有单词集合列表
    :param inputSet: 输入数据集
    :return: 匹配列表[0,1,0,1...],其中 1与0 表示词汇表中的单词是否出现在输入的数据集中
    """
    # 创建一个和词汇表长度相同的列表,其元素全部赋为0
    returnVec = [0]*len(vocabList)
    # 遍历文档中的所有单词,如果出现了词汇表中的单词,则将输出的文档向量中的对应值设为1
    for word in inputSet:
        if word in vocabList:
            returnVec[vocabList.index(word)] = 1
        else:
            print("%s is not in the vocabList"%word)
    return returnVec
原版的训练函数,其实就是求概率
# 训练数据原版
def trainNB0(trainMatrix,trainCategory):
    """
    训练数据原版
    :param trainMatrix: 文件单词矩阵 [[1,0,1,1,1....],[],[]...]
    :param trainCategory: 文件对应的类别[0,1,1,0....],列表长度等于单词矩阵数,其中的1代表对应的文件是侮辱性文件,0代表不是侮辱性矩阵
    :return:
    """
    numTrainDocs = len(trainMatrix) # 文件数量
    words = len(trainMatrix[0]) # 文件中的单词数
    # 侮辱性文件的出现概率,即trainCategory中所有的1的个数,
    # 代表的就是多少个侮辱性文件,与文件的总数相除就得到了侮辱性文件的出现概率
    pAbusive = np.sum(trainCategory)/float(numTrainDocs)
    # 构造单词出现次数列表
    p0Num = zeros(words)  # [0,0,0,.....]
    p1Num = zeros(words)  # [0,0,0,.....]

    # 整个数据集单词出现总次数
    p0Denom = 0.0
    p1Denom = 0.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])
    # 统计在侮辱性文件的类别下,每个单词的出现次数。这里p1Num是一个数组,p1Denom是一个数字
    p1Vect = p1Num/p1Denom
    # 统计在正常文件的类别下
    p0Vect = p0Num/p0Denom
    return p0Vect,p1Vect,pAbusive
训练数据改进版

其实就是把p0Num和p1Num改成ones(words),p0Denom和p1Denom改成2.0,防止因为某一项为0,使得概率的乘积为0

# 改进的训练数据方法,其实就是取了对数
def trainNB(trainMatrix,trainCategory):
    """
    训练数据优化版本
    :param trainMatrix: 文件单词矩阵
    :param trainCategory: 文件对应的类别
    :return:
    """
    numTrainDocs = len(trainMatrix)
    words = len(trainMatrix[0])
    pAbusive = sum(trainCategory)/float(numTrainDocs)

    # 这里就和原版有区别了。避免单词列表中的任何一个单词为0,而导致最后的乘积为0,所以将每个单词的出现次数初始化为1
    # p(w|c),这里w是一个向量,因为是朴素贝叶斯,所以p(w|c)=p(w1|c)*p(w2|c)*...*p(wn|c)
    # 而最后要计算的是p(w|c)p(c)/p(w)其实就是p(w1|c)*p(w2|c)*...*p(wn|c)*p(c)/p(w),而p(w)对于两类来说都一样,比较大小时可以不考虑
    # p(w1|c)就是分类为c的类别中,w1出现的次数/所有词表中单词的出现次数,也就是这里的p1Num/p1Denom和p0Num/p0Denom
    p0Num = ones(words)
    p1Num = ones(words)

    # 这里设置p0Denom和p1Denom为2.0,主要是为了避免除法的分母为0,这个值可以随便设置
    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
实际的分类方法,就是比较那个概率大,概率大就取那个分类
# 分类方法
'''
    :param vec2Classify: 待测数据[0,1,1,1,1...],即要分类的向量
    :param p0Vec: 类别0,即正常文档的[log(P(F1|C0)),log(P(F2|C0)),log(P(F3|C0)),log(P(F4|C0)),log(P(F5|C0))....]列表
    :param p1Vec: 类别1,即侮辱性文档的[log(P(F1|C1)),log(P(F2|C1)),log(P(F3|C1)),log(P(F4|C1)),log(P(F5|C1))....]列表
    :param pClass1: 类别1,侮辱性文件的出现概率
    :return: 类别1 or 0
'''
def classNB(vec,p0Vect,p1Vect,pClass1):
    # 这里通过取对数把乘法转换成了加法
    '''
    将乘法转换为加法
    乘法:P(C | F1F2...Fn) = P(F1F2...Fn | C)P(C) / P(F1F2...Fn)
    加法:P(F1 | C) * P(F2 | C)....P(Fn | C)P(C) -> log(P(F1 | C)) + log(P(F2 | C)) + .... + log(P(Fn | C)) + log(P(C))
    '''
    p1 = sum(vec*p1Vect)+log(pClass1)
    p0 = sum(vec*p0Vect)+log(1.0-pClass1)
    if p1>p0:
        return 1
    else:
        return 0
测试方法
# 测试方法
def testNB():
    # 1.加载数据集
    listPosts,listClass = loadDataSet()
    # 2.创建单词集合
    myVocabList = createVocabList(listPosts)
    # 3.计算单词是否出现并创建数据矩阵
    trainMat = []
    for postinDoc in listPosts:
        # 返回m*len(myVocabList)的矩阵,元素都是0,1
        trainMat.append(setOfWords2Vec(myVocabList,postinDoc))
    # 4.训练数据
    pv0,pv1,pAb = trainNB(array(trainMat),array(listClass))
    # 5.测试数据
    testEntry = ['love','my','dalmation']
    thisDoc = array(setOfWords2Vec(myVocabList,testEntry))
    print("%s classified as: %s"%(testEntry,classNB(thisDoc,pv0,pv1,pAb)))
    testEntry = ['stupid','garbage']
    thisDoc = array(setOfWords2Vec(myVocabList, testEntry))
    print("%s classified as: %s" % (testEntry, classNB(thisDoc, pv0, pv1, pAb)))

testNB()
输出结果:
['love', 'my', 'dalmation'] classified as: 0
['stupid', 'garbage'] classified as: 1

你可能感兴趣的:(朴素贝叶斯)