机器学习——分类算法3:朴素贝叶斯(Bayes) 思想 和 代码解释

思想:

1、朴素贝叶斯方法,是指:

朴素:特征为条件独立假设----各个事件条件独立

贝叶斯:基于贝叶斯定理:P(A|B) = P(AB) / P(B)    ---变形--->     P(AB) = P(A|B) * P(B) = P(B|A) * P(A)

根据贝叶斯定理,对一个分类问题,给定样本特征 X ,样本属于类别 Y 的概率是

P(Y|X)是后验概率,是事件X发生以后发生事件Y的概率

P(Y)就是先验概率,是事件X发生之前Y事件的概率

P(X|Y)是条件概率,是事件Y发生以后发生事件X的概率

P(X)是全概率公式:P(X)=∑p(yi)p(X|yi)

解释:

1.全概公式:首先建立一个完备事件组的思想,且各事件独立,其实全概就是已知第一阶段求第二阶段,比如第一阶段分A B C三种,然后A B C中均有D发生的概率,最后让你求D的概率
P(D) = P(AD)+P(BD)+P(CD) = P(A)*P(D/A)+P(B)*P(D/B)+P(C)*P(D/C),理解为在不同条件ABC下,共同发生D的概率


2.贝叶斯公式,其实原本应该叫逆概公式,为了纪念贝叶斯这样取名而已.在全概公式理解的基础上,贝叶斯其实就是已知第二阶段反推第一阶段,这时候关键是利用条件概率公式做个乾坤大挪移,跟上面建立的A B C D模型一样,已知P(D),求是在A发生下D发生的概率,这就是贝叶斯
P(A/D)=P(AD)/P(D)=P(A)*P(D/A)/P(D)

2、对于全概率:P(X) = P(y1 X)+P(y2 X)+...(这个是原始式子)

当然按照条件概率或者乘法公式展开: P(X)=P(y1)P(X|y1)+P(y2)P(X|y2)+...---就是他---->∑p(yi)p(X|yi)

所以一旦X确定,我们的任务只是比较大小,P(X)这个全概率就是个定值,写分类算法就不需要计算了。

你要是想算的话,看这里进行理解,还有对朴素贝叶斯和全概率都可以看。

3、分类:

综上,写程序你就只需要计算下面公式:

公式一:P(Y | X) = P(X | Y) * P(Y),然后对不同的   P(yi | X)    进行比大小,谁大就是那个分类

对于计算P(X | Y),由条件独立性假设可知:

公式二:P(X | Y)=p(X | yi) = p(x0,x1,x2...xN | yi) = p(x0 | yi) * p(x1 | yi) * p(x2 | yi).....p(xN | yi)

对于贝叶斯公式的推导:

总的来说,就是用别的条件概率来求这一个条件概率

以下是推导过程,只有三步

Step 1: 条件概率公式,表示在B_j发生的条件下,事件发生A_i的概率

下式: 分子表示事件A_i B_j同时发生的概率,分母表示事件B_j发生的概率

P(A_i \vert B_j )=P(A_i B_j )/P(B_j )

Step 2: 把分子P(A_i B_j )变一下

由step1的式子,P(A_i B_j )= P(A_i |B_j)×P(B_j )

同理,

P(B_j |A_i)=P(B_j A_i ) / P(A_i ), i.e. P(B_j A_i )=P(A_i )P(B_j A_i )
\because P(B_j A_i )=P(A_i B_j )
\therefore P(A_i B_j )=P(A_i )×P(B_j |A_i)

Step 3: 把分母P(B_j)变一下,

将事件B进行分割的时候,不是直接对B进行分割,而是先找到样本空间Ω的一个个划分为A_1,A_2,...,A_n,这样事件A就被事件BA_1,BA_2,BA_3,...,BA_n分解成了n部分,即

B=BA_1+BA_2+BA_3+⋯+BA_n
\therefore P(B)=P(BA_1)+P(BA_2)+P(BA_3)+⋯+P(BA_n)

Step 4: 整合Step1、2、3,完工

P(A_i |B_j)=P(A_i B_j )/P(B_j ) =(P(A_i )×P(B_j |A_i ))/(P(BA_1 )+P(BA_2 )+P(BA_3 )+⋯+P(BA_n ) )

代码:

代码是关于文本分类的:

python3版本代码(对于小白,在每次测试时候,可以打断点到测试那里,更容易理解,对于print过多,自行删除):

from numpy import *


#加载文本向量数据集
def loadDataSet():
    postingList = [['my', 'dog', 'has', 'flea', 'problems', 'help', '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]  #0表示正常言论1表示侮辱性言论
    return postingList, classVec

dataSet, calssVec = loadDataSet()       #数据集和分类
################################################################################################################################
#构建词汇表,即创建一个不重复词的列表(对于所有),运用set的属性,构造
def creatVocabList(dataSet):
    """
    :param dataSet: 数据集
    :return: 不重复词的词汇表
    """
    vocabSet = set([])      #初始化set列表集合
    for document in dataSet:
        vocabSet = vocabSet | set(document)     # 操作符|用于求两个集合的并集,而set会返回无序的,不重复的集合
    #将set集合转化成list,因为set无序,不能知道其索引,要将单词的位置定死,而后面要他的索引index,所以转化为list
    return list(vocabSet)
#测试
vocabulary = creatVocabList(dataSet)        #词汇表,你每次测试它都会变动,转化为list后他就不会变动了,后面程序就不会影响了
print("词汇表:", vocabulary)
print("------------------"*10)
#################################################################################################################################
#将一篇文章(句子)转化为词向量,将一组单词转化为数字
def setOfWords2Vec(vocabList, inputSet):
    """
    :param vocabList: 不重复词的词汇表
    :param inputSet: 某个文档
    :return: 文档向量,向量每个元素都为0,1,表示输入的文档是否出现在词汇表中
    """
    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


#测试,输入的是词汇表和原本数据集的第三行数据
print("原始数据集其中一个测试词向量:", setOfWords2Vec(vocabulary, dataSet[3]))
print("------------------"*10)
###########################################################################################################################
#获取所有词向量
trainMat = []
for Doc in dataSet:
    trainMat.append(setOfWords2Vec(vocabulary, Doc))
#测试
print("原始数据集的所有词向量:", trainMat)
print("------------"*10)
###########################################################################################################################
#构造朴素贝叶斯分类器,即为条件概率p(X|yi),此处为正常的Normal
"""
首先看公式:p(yi|X)=p(X|yi)*p(yi)/p(X)
p(yi|X):就是X表示文档,yi表示文档分类,总体表示输入文档看是什么分类,即我输入文档,这篇文档时正常言论还是侮辱性言论
p(X|yi):条件概率:表示文档在yi分类条件下的概率,下面会说怎么算。
p(yi):先验概率:该分类占所有分类的概率
p(X):该文档占所有文档的概率(这里面算的是单词)
计算条件概率的步骤:
1、计算在某个分类下,每个词出现次数(比如在侮辱性言论下,每个词出现的次数)
2、计算在某个分类下,词(不论重复,就是重复词也包括)的总数
3、计算在某个分类下,每个词出现次数除以总次数,就得到了条件概率:P(X|yi),X表示词向量,yi表示词分类(此处有两个分类:正常言论、侮辱性言论)
"""
def trainNBNormal(trainMatrix, trainCategory):
    """
    :param trainMatrix: 词向量
    :param trainCategory: 对应分类的标签
    :return:1、在正常言论下词向量的条件概率、2、在侮辱性言论下词向量的条件概率、3、侮辱性言论概率(先验概率)
    """
    numTrainDocs = len(trainMatrix)     #词向量个数
    numWords = len(trainMatrix[0])      #词向量词汇表词汇个数
    pAbusive = sum(trainCategory)/float(numTrainDocs)       #侮辱性文档的概率(先验概率)
    p0Num = zeros(numWords)     #正常言论类别语句总数初始化为0,长度为词汇表词汇个数
    p1Num = zeros(numWords)     #侮辱性言论类别语句总数初始化为0,长度为词汇表词汇个数
    p0Denom = 0.0       #正常言论类别语句词汇总数
    p1Denom = 0.0       #侮辱性言论类别语句词汇总数
    for i in range(numTrainDocs):
        if trainCategory[i] == 1:       #如果为侮辱性言论
            p1Num += trainMatrix[i]     #侮辱性言论语句总数
            p1Denom += sum(trainMatrix[i])      #侮辱性言论语句里面词汇的总数
            print("Normal第%d次进入  侮辱性言论单词出现次数:p1Num="%i, p1Num)
            print("Normal第%d次进入  侮辱性言论总单词数:p1Denom="%i, p1Denom)
        else:
            p0Num += trainMatrix[i]
            p0Denom += sum(trainMatrix[i])
    p1Vect = p1Num / p1Denom      #对每个元素除以该类别中总词数,获得在侮辱性言论下(条件概率)
    p0Vect = p0Num / p0Denom        #获得在正常言论下(条件概率)
    return p0Vect, p1Vect, pAbusive

#测试,可以手算一下
print("Normal开始:")
Normalp0v, Normalp1v, Normalpab = trainNBNormal(trainMat, calssVec)
print("----------"*10)
print("Normal侮辱性言论概率:Normalpab = ", Normalpab)
print("----------"*10)
print("Normal该词在正常言论中占的概率:Normalp0v")
print(Normalp0v)
print("----------"*10)
print("Normal该词在侮辱性言论中占的概率:Normalp1v")
print(Normalp1v)
print("Normal结束!!!!")
print("----------"*10)
###########################################################################################################################
#构造朴素贝叶斯分类器,即为条件概率,此处为修改后的分类器,此处为重点理解
"""
首先看公式:p(yi|X)=p(X|yi)*p(yi)/p(X)
p(yi|X):就是X表示文档,yi表示文档分类,总体表示输入文档看是什么分类,即我输入文档,这篇文档时正常言论还是侮辱性言论
p(X|yi):条件概率:表示文档在yi分类条件下的概率,下面会说怎么算。
p(yi):先验概率:该分类占所有分类的概率
p(X):该文档占所有文档的概率(这里面算的是单词)
修改原因和处理:
1、因为要计算多个概率乘积以获得文档属于某个类别的概率,即计算p(w0|1)*p(w1|1)*p(w2|1),即p(w0|1)表示单词w0在侮辱性言论中占的概率
如果p(w0|1)、p(w1|1)、p(w2|1)中有一个概率是0,其乘积也为0,为了降低这种影响可以将所有词的出现数初始化为1,并将其分母初始化为2。
p0Num = ones(numWords)     
p1Num = ones(numWords)     
p0Denom = 2.0       
p1Denom = 2.0 
2、另一个问题是下溢出,这是因为太多很小的数相乘造成的,导致程序会出现下溢出(比如在0附近会有一小段区间为溢出区间)或不正确的答案
(比如四舍五入后得0),于是想到对数。大家都知道在代数中有:In(a * b)=In(a)+In(b),于是通过对数可以避免下溢出或浮点数舍入导致
的错误,同时采用自然对数进行处理不会有任何损失(比如f(x)和In(f(x)),他们会在相同区间递增或递减,在相同点上取到极值)。做如下修改:
p1Vect = log(p1Num / p1Denom)        
p0Vect = log(p0Num / p0Denom)
"""
def trainNB(trainMatrix, trainCategory):
    """
    :param trainMatrix: 词向量
    :param trainCategory: 对应分类的标签
    :return:1、在正常言论下各个词语的条件概率、2、在侮辱性言论下各个词语的条件概率、3、侮辱性言论概率(先验概率)
    """
    numTrainDocs = len(trainMatrix)     #词向量个数
    numWords = len(trainMatrix[0])      #词向量词汇表词汇个数
    pAbusive = sum(trainCategory)/float(numTrainDocs)       #侮辱性文档的概率
    p0Num = ones(numWords)     #正常言论类别语句总数初始化为0,长度为词汇表词汇个数
    p1Num = ones(numWords)     #侮辱性言论类别语句总数初始化为0,长度为词汇表词汇个数
    p0Denom = 2.0       #正常言论类别语句词汇总数
    p1Denom = 2.0       #侮辱性言论类别语句词汇总数
    for i in range(numTrainDocs):
        if trainCategory[i] == 1:       #如果为侮辱性言论
            p1Num += trainMatrix[i]     #侮辱性言论语句总数
            p1Denom += sum(trainMatrix[i])      #侮辱性言论语句里面词汇的总数
            print("第%d次进入  侮辱性言论单词出现次数:p1Num="%i, p1Num)
            print("第%d次进入  侮辱性言论总单词数:p1Denom="%i, p1Denom)
        else:
            p0Num += trainMatrix[i]
            p0Denom += sum(trainMatrix[i])
    p1Vect = log(p1Num / p1Denom)        #对每个元素除以该类别中总词数
    p0Vect = log(p0Num / p0Denom)
    return p0Vect, p1Vect, pAbusive

p0v, p1v, pab = trainNB(trainMat, calssVec)
#测试,可以手算一下
print("----------"*10)
print("侮辱性言论概率:", pab)
print("----------"*10)
print("该词在正常言论中占的概率:")
print(p0v)
print("----------"*10)
print("该词在侮辱性言论中占的概率:")
print(p1v)
#############################################################################################################
#朴素贝叶斯分类
"""
公式:p(yi|X)=p(X|yi)*p(yi)/p(X)
1、首先计算条件概率p(X|yi):将向量形式转化为普通形式:p(X|yi) = p(x0,x1,x2...xN|yi)
这里用到了条件独立性假设:一个词的出现概率并不依赖于文档中的其他词。
由条件独立性假设可知:p(X|yi) = p(x0,x1,x2...xN|yi) = p(x0|yi) * p(x1|yi) * p(x2|yi).....p(xN|yi)
上面已经把他们换成log形式所以就变成了:p(X|yi) = log(p(x0|yi))+log(p(x1|yi))....= log(p(x0|yi)*p(x1|yi)....)
2、输入文档的条件概率 = 输入的文档词向量 * 所有单词的条件概率
3、对于p(X),不论分类怎么变化,对于同一篇文档,他的固定的,因此可以不用计算,因此也不用去除
4、所以计算的公式就是:p(yi|X) = 输入文档的条件概率(log形式)之和 + p(yi)(log形式) -------相当于相乘
5、比较大小,谁的概率大就是那个分类
"""
def classifyNB(vec2Classify, p0v, p1v, pab):
    """
    :param vec2Classify: 输入的词向量
    :param p0v: 正常言论条件概率
    :param p1v: 侮辱性言论条件概率
    :param pab: 先验概率:分类概率
    :return:该文档是否是侮辱性言论和正常言论
    """
    p1 = sum(vec2Classify * p1v) + log(pab)
    p0 = sum(vec2Classify * p0v) + log(1.0-pab)
    if p1 > p0:
        return "侮辱性言论"
    else:
        return "正常言论"
###########################################################################################################
#测试
thisDoc = setOfWords2Vec(vocabulary, ['love', 'my', 'dalmation'])
print("--------------"*10)
print("词向量1:", thisDoc)
print("分类为:", classifyNB(thisDoc, p0v, p1v, pab))#分类
#测试
thisDoc = setOfWords2Vec(vocabulary, ['stupid', 'garbage'])
print("--------------"*10)
print("词向量2:", thisDoc)
print("分类为:", classifyNB(thisDoc, p0v, p1v, pab))#分类


 

猜你喜欢

转载自blog.csdn.net/xiao1_1bing/article/details/79208402
今日推荐