机器学习——朴素贝叶斯算法

目录

一、朴素贝叶斯理论

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

1.2条件概率

​编辑 

1.3全概率公式

​编辑 

 1.4朴素贝叶斯分类器

 二、贝叶斯分类器简单应用

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

3.1准备数据:从文本中构建词向量

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

3.3测试算法:根据现实情况修改分类器

3.4准备数据:文档词袋模型

 四、使用朴素贝叶斯过滤垃圾邮件

4.1准备数据:切分文本

4.2测试算法:使用朴素贝叶斯进行交叉验证

 ​编辑

五、总结

 


一、朴素贝叶斯理论

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

        朴素贝叶斯是经典的机器学习算法之一,也是为数不多的基于概率论的分类算法。对于大多数的分类算法,在所有的机器学习分类算法中,朴素贝叶斯和其他绝大多数的分类算法都不同。比如决策树,KNN,逻辑回归,支持向量机等,他们都是判别方法,也就是直接学习出特征输出Y和特征X之间的关系,要么是决策函数,要么是条件分布。但是朴素贝叶斯却是生成方法,该算法原理简单,也易于实现。

        朴素贝叶斯算法以贝叶斯理论为理论基础,通过计算样本属于不同类别的概率来进行分类,是一种经典的分类算法。朴素贝叶斯是贝叶斯分类器里的一种方法,之所以称它朴素,原因在于它采用了特征生命条件全部独立的假设。

        贝叶斯理论是指基于能获得的最好证据(观察、数据和信息等),来计算信念度(或者假说、主张、命题)的有效方法。信念度即为对事物的真实性和正确性所具有的信心。朴素贝叶斯的核心便是朴素贝叶斯公式,在了解朴素贝叶斯公式之前不妨先了解条件概率、全概率公式、先验概率和后验概率等相关概念。

 

        我们现在用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,如果该图中的整个数据使用6个浮点数来表示,并且计算类别概率的Python代码只有两行,那么我们应该使用下面哪种方法来对该数据点进行分类?

1.使用kNN算法,则需要进行1000次距离计算,和简单的概率计算相比,kNN的计算量太大;
2.使用决策树,则需要分别沿x轴、y轴划分数据,似乎效果并不是很好;
3.还有就是计算数据点属于每个类别的概率,并进行比较,这是最佳选择。
 

1.2条件概率

 

如上图,可以知道在事件B发生的情况下,事件A发生的概率是A\cap B部分占B部分的比例,即 :

P(A|B) = \frac{P(A\cap B)}{P(B)}

因此:P(A\cap B) = P(A|B)P(B)

同理可得:P(A\cap B) = P(B|A)P(A)

所以:P(A|B)P(B) = P(B|A)P(A)

通过变换上面的等式,可以得到条件概率的公式:P(A|B) = \frac{P(B|A)P(A)}{P(B)}

1.3全概率公式

 

假设事件A和事件A'共同构成样本空间S,在这种情况下,事件B可以划分成两部分,即:

P(B) = P(B\cap A) + P(B\cap A')

因为:P(B\cap A) = P(B|A)P(A)

结合上面两个式子,可以得到全概率公式:P(B) = P(B|A)P(A) + P(B|A')P(A')

 1.4朴素贝叶斯分类器

先验概率P(X):先验概率是指根据以往经验和分析得到的概率。

后验概率P(Y|X):事情已发生,要求这件事情发生的原因是由某个因素引起的可能性的大小,后验分布P(Y|X)表示事件X已经发生的前提下,事件Y发生的概率,称事件X发生下事件Y的条件概率。

基于属性条件独立性假设,P(c|x) = \frac{P(c)P(x|c)}{p(x)} = \frac{P(c)}{P(x)}\sum_{i=1}^{d}P(x_{i}|c),其中d为属性数目,x_{i}为x在第i个属性上的取值。

由于对所有类别来说P(x)相同,因此贝叶斯判断准则有

h_{nb}(x) = arg maxP(c)\prod_{i=1}^{d}P(x_{i}|c)

这就是朴素贝叶斯分类器的表达式

朴素贝叶斯分类器的训练器的训练过程就是基于训练集D估计类先验概率 P(c) ,并为每个属性估计条件概率  P(x_i|c) 。

令  D_c  表示训练集D中第c类样本组合的集合,则类先验概率:   P(c)=\frac{|D_c|}{D}

拉普拉斯修正:若某个属性值在训练集中没有与某个类同时出现过,则训练后的模型会出现 over-fitting 现象。比如训练集中没有该样例,因此连乘式计算的概率值为0,这显然不合理。因为样本中不存在(概率为0),不代该事件一定不可能发生。所以为了避免其他属性携带的信息,被训练集中未出现的属性值“ 抹去” ,在估计概率值时通常要进行“拉普拉斯修正”。

P(c|x) = \frac{P(x|c)P(c)}{P(x)} = \frac{P(c)}{P(x)}\prod_{i=1}^{d}P(x_{i}|c),我们要修正P(x_{i}|c)的值。

令N表示训练集D中可能的类别数,N_{i}表示第i个属性可能的取值数,则贝叶斯公式可修正为:\hat{P}(C) = \frac{\left | D_{c} \right |+1}{\left | D \right |+N}        \hat{P}(x_{i}|c) = \frac{\left | D_{c,x_{i}} \right |+1}{\left | D_{c} \right |+N_{i}}

 二、贝叶斯分类器简单应用

在超市挑选苹果,一般颜色红润,形状较圆的为好果。根据之前购买的苹果的大小、颜色、形状这三个特征来区分好坏:

编号

大小颜色形状好果
1青色非规则
2红色非规则
3红色圆形
4青色圆形
5青色非规则
6红色圆形
7青色非规则
8红色非规则
9青色圆形
10红色圆形

现在一个苹果的特征如下:

大小颜色形状好果
红色圆形

先验概率P(c),简化的求解方法:c类样本的个数除以样本总数

P(c=好果)=4/10        P(c=一般)=6/10

每个属性的类条件概率,可以初步这么求解:这个类别下的样本中对应这个属性的样本个数除以这个类别下的样本个数,因此:

P(大小=大 | c=好果) =   3/4

P(颜色=红色 | c=好果) = 4/4

P(形状=圆形 | c=好果) = 3/4

P(大小=大 | c=一般) =  3/6

P(颜色=红色 | c=一般) = 1/6

P(形状=圆形 | c=一般) =  2/6

因此:  

P(c=好果) * P(大小=大 | c=好果) * P(颜色=红色 | c=好果) * P(形状=圆形 | c=好果)  

= 4/10 * 3/4 * 4/4 * 3/4 

= 0.225

P(c=一般) * P(大小=大 | c=一般) * P(颜色=红色 | c=一般) * P(形状=圆形 | c=一般)  

= 6/10 * 3/6 * 1/6 * 2/6

= 0.0167

 0.225>0.0167,所以这个苹果为好果

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

3.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']]
    classVec = [0, 1, 0, 1, 0, 1]  # 标签向量,1表示侮辱性文字,0表示正常言论
    return postingList, classVec
# 创建不重复词的列表 ———— 词汇表
def createVocabList(dataSet):
    vocabSet = set([])                       # 创建一个空集
    for document in dataSet:
        vocabSet = vocabSet | set(document)  # 创建两个集合的并集
    return list(vocabSet)                    # 返回不重复的词条列表


# 输出文档向量
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("单词 %s 不在词汇表中!" % word)
    return returnVec



# 测试函数效果
 
# 创建实验样本
listPosts, listClasses = loadDataSet()
print('数据集\n', listPosts)
 
# 创建词汇表
myVocabList = createVocabList(listPosts)  
print('词汇表:\n', myVocabList)
 
# 输出文档向量
print(setOfWords2Vec(myVocabList, listPosts[5]))   

一个单词在词汇表中出现过一次,那么就在相应位置记作1,如果没有出现就在相应位置记作0。

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

# 朴素贝叶斯分类器训练函数
from gettext import npgettext


def trainNB0(trainMatrix, trainCategory):
    numTrainDocs = len(trainMatrix)                      # 获得训练的文档总数
    numWords = len(trainMatrix[0])                       # 获得每篇文档的词总数
    pAbusive = sum(trainCategory) / float(numTrainDocs)  # 计算文档是侮辱类的概率
    p0Num = ones(numWords)                              # 创建numpy.ones数组,词条出现次数初始化为1,拉普拉斯平滑
    p1Num = ones(numWords)                              
    p0Denom = 2.0                                        # 分母初始化为2,拉普拉斯平滑
    p1Denom = 2.0                                       
    for i in range(numTrainDocs):
        if trainCategory[i] == 1:
            p1Num += trainMatrix[i]                      # 向量相加,统计侮辱类的条件概率的数据,即P(w0|1),P(w1|1),P(w2|1)···
            p1Denom += sum(trainMatrix[i])
        else:
            p0Num += trainMatrix[i]                      # 向量相加,统计非侮辱类的条件概率的数据,即P(w0|0),P(w1|0),P(w2|0)···
            p0Denom += sum(trainMatrix[i])
    p1Vect = np.log(p1Num / p1Denom)                          # 侮辱类,每个元素除以该类别中的总词数
    p0Vect = np.log(p0Num / p0Denom)                           # 非侮辱类,每个元素除以该类别中的总词数
    return p0Vect, p1Vect, pAbusive                      # p0Vect非侮辱类的条件概率数组、p1Vect侮辱类的条件概率数组、pAbusive文档属于侮辱类的概率
# 测试代码
listPosts, listClasses = loadDataSet()    # 创建实验样本
print('数据集\n', listPosts)
myVocabList = createVocabList(listPosts)  # 创建词汇表
print('词汇表:\n', myVocabList)
trainMat = []
for postinDoc in listPosts:               # for循环使用词向量来填充trainMat列表
        trainMat.append(setOfWords2Vec(myVocabList, postinDoc))
p0V, p1V, pAb = trainNB0(trainMat, listClasses)
print('p0V:\n', p0V)
print('p1V:\n', p1V)
print('classVec:\n',listClasses)
print('pAb:\n', pAb)

运行结果如图:p0V存放的是每个单词属于类别0,也就是非侮辱类词汇的概率。 p1V存放的是每个单词属于类别1,也就是侮辱类词汇的概率。例如词汇表中第五个stupid,非侮辱类的概率就是-3.25809654,侮辱类概率为-1.65822808。pAb是所有侮辱类的样本占所有样本的概率,从classVec中可以看出,一共有三个侮辱类,所以pAb概率为0.5。
 

3.3测试算法:根据现实情况修改分类器

# 测试朴素贝叶斯分类器
def testingNB():
    listOPosts, listClasses = loadDataSet()                           # 创建实验样本
    myVocabList = createVocabList(listOPosts)                         # 创建词汇表
    trainMat = []                                                     # 文档矩阵
    for postinDoc in listOPosts:                                      # for循环使用词向量来填充trainMat列表
        trainMat.append(setOfWords2Vec(myVocabList, postinDoc))
    p0V, p1V, pAb = trainNB0(array(trainMat), array(listClasses))     # 获得概率数组及先验概率
    testEntry = ['love', 'my', 'dalmation']                           # 输入测试1
    thisDoc = array(setOfWords2Vec(myVocabList, testEntry))
    print(testEntry, 'classified as: ', classifyNB(thisDoc, p0V, p1V, pAb))
    if classifyNB(thisDoc,p0V,p1V,pAb):
        print(testEntry,'属于侮辱类')
    else:
        print(testEntry,'属于非侮辱类')
    testEntry = ['stupid', 'garbage']                                 # 输入测试2
    thisDoc = array(setOfWords2Vec(myVocabList, testEntry))
    print(testEntry, 'classified as: ', classifyNB(thisDoc, p0V, p1V, pAb))
    if classifyNB(thisDoc,p0V,p1V,pAb):
        print(testEntry,'属于侮辱类')
    else:
        print(testEntry,'属于非侮辱类')

if __name__ == '__main__':
    testingNB()

3.4准备数据:文档词袋模型

# 朴素贝叶斯词袋模型
def bagOfWords2VecMN(vocabList, inputSet):
    returnVec = [0] * len(vocabList)
    for word in inputSet:
        if word in vocabList:
            returnVec[vocabList.index(word)] += 1  # 加1
        else:
            print("the word: %s is not in my vocabulary!" % word)
    return returnVec

 四、使用朴素贝叶斯过滤垃圾邮件

普通邮件

垃圾邮件

 

数据集:链接: https://pan.baidu.com/s/14evrKNZQ6h0mmJCwsRGRhA?pwd=q4p6 提取码: q4p6 

4.1准备数据:切分文本

# 例子
mySent = 'This book is the best book on Python or M.L. I have ever laid  eyes upon.'
print(mySent.split())

4.2测试算法:使用朴素贝叶斯进行交叉验证

# 文件解析
def textParse(bigString):  # 输入字符串, 输出单词列表
    listOfTokens = re.split(r'[\W*]', bigString)                    # 字符串切分,去掉除单词、数字外的任意字符串
    return [tok.lower() for tok in listOfTokens if len(tok) > 2]    # 除了单个字母外,其他字符串全部转换成小写



# 完整的垃圾邮件测试函数
def spamTest():
    docList = []                 # 文档列表
    classList = []               # 文档标签
    fullText = []                # 全部文档内容集合
    for i in range(1, 22):                                           # 遍历垃圾邮件和非垃圾邮件各21个
        wordList = textParse(open('email/spam/%d.txt' % i).read())   # 读取垃圾邮件,将大字符串并将其解析为字符串列表
        docList.append(wordList)                                     # 垃圾邮件加入文档列表
        fullText.extend(wordList)                                    # 把当前垃圾邮件加入文档内容集合
        classList.append(1)                                          # 1表示垃圾邮件,标记垃圾邮件
        wordList = textParse(open('email/ham/%d.txt' % i).read())    # 读非垃圾邮件,将大字符串并将其解析为字符串列表
        docList.append(wordList)                                     # 非垃圾邮件加入文档列表
        fullText.extend(wordList)                                    # 把当前非垃圾邮件加入文档内容集合
        classList.append(0)                                          # 0表示垃圾邮件,标记非垃圾邮件,
 
    vocabList = createVocabList(docList)                             # 创建不重复的词汇表
    trainingSet = list(range(42))                                    # 为训练集添加索引
    testSet = []                                                     # 创建测试集
    for i in range(8):                                              # 目的为了从42个邮件中,随机挑选出34个作为训练集,8个做测试集
        randIndex = int(random.uniform(0, len(trainingSet)))         # 随机产生索引
        testSet.append(trainingSet[randIndex])                       # 添加测试集的索引值
        del (trainingSet[randIndex])                                 # 在训练集中,把加入测试集的索引删除
 
    trainMat = []                                                    # 创建训练集矩阵训练集类别标签系向量
    trainClasses = []    
    #print(vocabList)
    #print(docList[docIndex])                                            # 训练集类别标签
    for docIndex in trainingSet:                                     # for循环使用词向量来填充trainMat列表t
        trainMat.append(bagOfWords2VecMN(vocabList, docList[docIndex]))  # 把词集模型添加到训练矩阵中
        trainClasses.append(classList[docIndex])                     # 把类别添加到训练集类别标签中
    p0V, p1V, pSpam = trainNB0(array(trainMat), array(trainClasses)) # 朴素贝叶斯分类器训练函数
    
    #print('词表:\n', vocabList)
    #print('p0V:\n', p0V)
    #print('p1V:\n', p1V)
    #print('pSpam:\n', pSpam)
 
    errorCount = 0                                                   # 用于计数错误分类
    for docIndex in testSet:                                         # 循环遍历训练集
        wordVector = bagOfWords2VecMN(vocabList, docList[docIndex])  # 获得测试集的词集模型
        if classifyNB(array(wordVector), p0V, p1V, pSpam) != classList[docIndex]:
            errorCount += 1                                          # 预测值和真值不一致,则错误分类计数加1
            print("分类错误集", docList[docIndex])
    print('错误率: ', float(errorCount) / len(testSet))

 

函数spamTest()会输出在8封随机选择的电子邮件上的分类错误概率。既然这些电子邮件是随机选择的,所以每次的输出结果可能有些差别。如果发现错误的话,函数会输出错误的文档的此表,这样就可以了解到底是哪篇文档发生了错误。如果想要更好地估计错误率,那么就应该将上述过程重复多次,比如说10次,然后求平均值。相比之下,将垃圾邮件误判为正常邮件要比将正常邮件归为垃圾邮件好。

五、总结

  • 优点:在数据较少的情况下仍然有效,可以处理多类别问题
  • 缺点:对于输入数据的准备方式较为敏感
  • 适用数据类型:标称型数据 

 

完整代码:链接: https://pan.baidu.com/s/1wAN8wFmuAjU8YDZgewtUuQ?pwd=swcr 提取码: swcr

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

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值