机器学习-朴素贝叶斯分类

今天在图书馆学习了一天,把朴素贝叶斯的基本原理给弄明白了。故写篇文章加深印象。
当然还是要推荐一发博客的:ACdreamers-朴素贝叶斯分类

一、贝叶斯定理

条件概率:

P(c|x⃗ )=P(x⃗ c)P(x⃗ )

贝叶斯定理:


P(c|x⃗ )=P(c)P(x⃗ |c)P(x⃗ )

证明如下:


P(c|x⃗ )=P(x⃗ c)P(x⃗ )


P(x⃗ |c)=P(x⃗ c)P(c)

联立两式,消去

P(x⃗ c) 得到贝叶斯定理。

二、朴素贝叶斯分类器

不难发现,基于贝叶斯定理来估计概率 P(c|x⃗ ) 的主要困难在于,类条件概率 P(x⃗ |c) 是所有属性上的联合概率,难以从有限的训练样本直接估计而得。为了避开这个障碍,朴素贝叶斯分类器采用了“属性条件独立性假设”:对已知类别,假设所有属性相互独立。

基于属性条件独立性假设,贝叶斯定理可重写为:

P(c|x⃗ )=P(c)P(x⃗ |c)P(x⃗ )=P(c)P(x⃗ )i=1dP(xi|c)

其中 d 是属性数目, xi x 在第 i 个属性上的取值。

朴素贝叶斯分类原理

对于给定待分类项,求解在此项出现的条件下,各个类别出现的概率,待分类项属于概率最大的那一类

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

定义事件:
c0 : 不是垃圾邮件
c1 : 是垃圾邮件
w⃗  : 词向量,表示一篇邮件中出现的词汇
P(w⃗ |ci) : 在出现事件 ci 的情况下,出现的是 w⃗  的概率
P(ci|w⃗ ) : 在出现的是词向量 w⃗  的情况下,出现的是事件 ci 的概率

由上可知,我们需要求得所有的 P(ci|w⃗ ) 并找出最大的,其事件 ci 即是分类结果

训练算法

根据朴素贝叶斯分类器:

P(ci|w⃗ )=P(ci)P(w⃗ |ci)P(w⃗ )=P(ci)P(w⃗ )j=1dP(wj|ci)

我们可以对于每个待分类的数据,已知

P(ci)

P(w⃗ ) 。我们需通过训练算法得到得是

dj=1P(wj|ci)

测试算法

计算 P(ci|w⃗ ) 的值,取其中最大的,得到分类结果。与测试样本集中的结果进行比对。最后求出错误率

代码

import numpy as np
import re, os, random

# 将inputset转换为一个长度为len(vocabList)的列表
def setOfWord2Vec(vocabList, inputset):
    returnVec = [0] * len(vocabList)
    for word in inputset:
        if word in vocabList:
            returnVec[vocabList.index(word)] = 1
    return returnVec

# 读取文件filename中的所有单词,并以全部小写返回
def file2vec(filename):
    returnVec = []
    fr = open(filename, 'r')
    regEx = re.compile("\W+")
    text = fr.read()
    returnVec.extend(regEx.split(text))
    fr.close()
    return [word.lower() for word in returnVec if len(word) > 2]

# 创建词汇表
def createVocabList(dataSet):
    returnVec = set([])
    for word in dataSet:
        returnVec |= set(word)
    return list(returnVec)

# 训练数据
def trainNB0(trainMatrix, trainCategory):
    numTrainMatrix = len(trainMatrix)
    numTrainWord = len(trainMatrix[0])
    pAbvious = sum(trainCategory) / float(numTrainMatrix)
    p0Num, p1Num = np.ones(numTrainWord), np.ones(numTrainWord)
    p0Denom, p1Denom = 2.0, 2.0
    for i in range(numTrainMatrix):
        # 如果是侮辱性文档,则增加p1Num
        if trainCategory[i] == 1:
            p1Num += trainMatrix[i]
            p1Denom += sum(trainMatrix[i])
        else:
            p0Num += trainMatrix[i]
            p0Denom += sum(trainMatrix[i])
    p0Vect, p1Vect = np.log(p0Num / p0Denom), np.log(p1Num / p1Denom)
    return p0Vect, p1Vect, pAbvious

# 验证
def classifyNB(testSet, p0Vec, p1Vec, pAbvious):
    p0 = sum(testSet * p0Vec) + np.log(1.0 - pAbvious)
    p1 = sum(testSet * p1Vec) + np.log(pAbvious)
    if p0 > p1:
        return 0
    else:
        return 1


def spamTest():
    # 所有词汇集合,结果集合,
    docList, classList = [], []
    for i in range(1, 26):
        wordList = file2vec("spam/%d.txt" % i)
        docList.append(wordList)
        classList.append(1)
        wordList = file2vec("ham/%d.txt" % i)
        docList.append(wordList)
        classList.append(0)
    vocabList = createVocabList(docList)
    trainingSet, testSet = list(range(50)), []
    # 随机构造测试集
    for i in range(10):
        randIndex = int(random.uniform(0, len(trainingSet)))
        testSet.append(trainingSet[randIndex])
        del(trainingSet[randIndex])
    trainMat, trainClasses = [], []
    for docIndex in trainingSet:
        trainMat.append(setOfWord2Vec(vocabList, docList[docIndex]))
        trainClasses.append(classList[docIndex])
    p0V, p1V, pSpam = trainNB0(np.array(trainMat), trainClasses)
    errorCount = 0
    for index in testSet:
        word = setOfWord2Vec(vocabList, docList[index])
        if classifyNB(word, p0V, p1V, pSpam) != classList[index]:
            errorCount += 1
    print ("the error rate is:", float(errorCount) / len(docList))


if __name__ == '__main__':
    spamTest()

计算的时候考虑上溢出和下溢出的问题,分别做了相应的处理。具体参加《机器学习实战》4.5.3节

根据这个算法,进行了多次测试(相同数据集),因为数据集划分的是随机的,随机结果最大错误率是4%(不一定准确)

总结

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

说句题外话

为什么CSDN的markdown代码块在预览的时候感觉就挺好看的…为什么发表出来之后就感觉挺难看的了….加载的样式表不一样么….

点赞