朴素贝叶斯(Native Bayesian)

1. 基本环境介绍

  • 系统 Ubuntu Kylin

  • IDE   Pycharm Community

  • 语言 Python3.4


2. 朴素贝叶斯简介

朴素贝叶斯是一种基于概率论的分类方法,利用条件概率对类别进行判决。 —— [ 维基百科 ]

以二类分类为例:假设现在我们有一个数据集,它有两类数据组成,并且使用 (x,y) 表示任一数据点的坐标。现在,用 p1(x,y) 表示数据点属于类别1的概率,用 p2(x,y) 表示数据点属于类别2的概率。那么对于一个新的数据点,可以使用下面的规则来判断它的类别

  • 如果 p1(x,y) > p2(x,y) ,那么类别为 1

  • 如果 p2(x,y) > p1(x,y) ,那么类别为 2

也就是说,我们会选择高概率对应的类别,这就是贝叶斯决策理论的思想。

但这两个准则并不是贝叶斯决策理论的所有内容,真正需要计算和比较的是 p(c1|x,y) p(c2|x,y) ,其所代表的具体意义是:给定某个由 x y 表示的数据点,那么该数据来自类别 c1 的概率是多少,来自 c2 的概率是多少?

具体的,应用贝叶斯准则得到:

p(ci|x,y)=p(x,y|ci)p(ci)p(x,y)

其中, ci 表示类别; p(x,y|ci) 是后验概率,表示已知为 ci 类别下,为 (x,y) 数据点的概率; p(ci|x,y) 是先验概率,表示已知数据点 (x,y) 的前提下,为类别 ci 的概率。

  • 如果 p(c1|x,y) > p(c2|x,y) ,那么属于类别 c1

  • 如果 p(c2|x,y) > p(c1|x,y) ,那么属于类别 c2


3. 具体实现

下面,我们以文本分类的例子来说明朴素贝叶斯的具体实现:以在线社区的留言板为例,为了不影响社区的发展,我们要屏蔽侮辱性言论,所以要构建一个快速过滤器,如果某条留言使用了负面或侮辱性语言,那么就将该留言标示为内容不当。对此问题建立两个类别:侮辱类和非侮辱类,使用1和0分别表示。

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']]
    # 是否含有侮辱性词汇: 0-未含有 1-含有
    classVec = [0, 1, 0, 1, 0, 1]
    return postingList, classVec

# 构造不含有重复词汇的集合vocabSet
def createVocabList(dataSet):
    vocabSet = set([])
    for document in dataSet:
        # 取并集
        vocabSet = vocabSet | set(document)
    return list(vocabSet)

# 输入参数为:词汇表,输入文档
def setOfWords2Vec(vocabList, inputSet):
    # 输出文档向量,表示词汇表中的单词是否出现:1-出现 0-未出现
    returnVec = [0] * len(vocabList)
    # 对于输入文档中的每个词汇
    for word in inputSet:
        if word in vocabList:
            returnVec[vocabList.index(word)] = 1
        else:
            print("the word: %s is not in my Vocabulary!" % word)
    return returnVec

# ============测试文档===========
listOPosts, listClasses = loadDataSet()
myVocabList = createVocabList(listOPosts)
print("VocabSet:", myVocabList, "\n")
print(setOfWords2Vec(myVocabList, listOPosts[0]))

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

# 训练算法
from numpy import *

# 二类分类问题
# 该函数未考虑相乘概率值为0(即p0Num, p1Num, p0Denom, p1Denom的初始化)和下溢出(p1Vect和p0Vect)的问题
def trainNB0(trainMatrix, trainCategory):
    numTrainDocs = len(trainMatrix)
    numWords = len(trainMatrix[0])
    pAbusive = sum(trainCategory)/float(numTrainDocs)

    '''p0Num = zeros(numWords)
    p1Num = zeros(numWords)
    p0Denom = 0.0
    p1Denom = 0.0'''

    # 以上四行代码修改为:
    p0Num = ones(numWords)
    p1Num = ones(numWords)
    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 = p1Num/p1Denom
    p0Vect = p0Num/p0Denom'''

    # 以上两行代码修改为:
    p1Vect = log(p1Num/p1Denom)
    p0Vect = log(p0Num/p0Denom)

    return p0Vect, p1Vect, pAbusive

# 多类分类问题(以3类为例,其在trainCategory中的分类为0,1,2)
def trainNB4(trainMatrix, trainCategory):
    # 取矩阵行数,作为文档数
    numTrainDocs = len(trainMatrix)
    # 取矩阵第一行的列数,作为词汇数
    numWords = len(trainMatrix[0])
    # 提取训练类别中的非重复元素(0,1,2)
    mySet = set(trainCategory)
    # 计算不同类别的发生概率,并存储在类别p中
    p = []
    for item in mySet:
        p.append((trainCategory.count(item))/float(numWords))

    # 构造向量用来存放对应三种类别的词汇出现的频次
    # 不使用zeros的原因是:
    # 当使用公式 p(w0,w1,w2,...,wN|Ci)=p(w0|Ci)*p(w1|Ci)*...*p(wN|Ci) 计算条件概率时,避免出现计算结果为0的情况
    p0Num = ones(numWords)
    p1Num = ones(numWords)
    p2Num = ones(numWords)

    # 以下三个变量用来存储三种类别下总词汇数,作为分母,为避免出现0值,因此初始化为2
    # 不能初始化为1,因为分子初始化为1
    p0Denom = 2.0
    p1Denom = 2.0
    p2Denom = 2.0

    # 取每一个训练文本
    for i in range(numTrainDocs):
        if trainCategory[i] == 0:
            # p0Num是一个长度为numWords的向量,p0Num += trainMatrix[i]表示对应词汇个数+1
            p0Num += trainMatrix[i]
            # 计算总的词汇个数,计算概率时作为分母
            p0Denom += sum(trainMatrix[i])
        elif trainCategory[i] == 1:
            p1Num += trainMatrix[i]
            p1Denom += sum(trainMatrix[i])
        else:
            p2Num += trainMatrix[i]
            p2Denom = sum(trainMatrix[i])

    # 计算概率,取对数的原因是避免下溢出
    # 下溢出:大量很小的浮点数相乘,四舍五入后得到0
    p0Vect = log(p0Num/p0Denom)
    p1Vect = log(p1Num/p1Denom)
    p2Vect = log(p2Num/p2Denom)

    return p, p0Vect, p1Vect, p2Vect

#=======测试代码=======


'''randMat = mat(random.random_integers(0,1,[6,32]))
用该代码生成一个6X32的伯努利矩阵,但不符合trainNB的规范,因此使用原书中的矩阵合并方式
# print(randMat)
Classes = [0,1,2,0,1,1]
pp, p0V, p1V, p2V = trainNB4(randMat, Classes)'''

# 测试二类分类器
trainMat = []
for postinDoc in listOPosts:
    trainMat.append(setOfWords2Vec(myVocabList, postinDoc))
# print(trainMat)
p0Vec, p1Vec, pAb = trainNB0(trainMat, listClasses)

# 测试三类分类器
Classes = [0,1,2,0,1,1]
pp, p0V, p1V, p2V = trainNB4(trainMat, Classes)
print(pp,"\n",p0V,"\n",p1V,"\n",p2V,"\n")

3. 测试算法:朴素贝叶斯分类函数

# 朴素贝叶斯分类函数
# 输入参数:测试文本列表,文档分类为0的概率向量(对数),文档分类为1的概率向量,全部文档分类为1的概率
def classifyNB(vec2Classify, p0Vec, p1Vec, pClass1):
    # 概率相乘,因为是对数形式,所以为求和形式
    p1 = sum(vec2Classify * p1Vec) + log(pClass1)
    p0 = sum(vec2Classify * p0Vec) + 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(array(trainMat), array(listClasses))
    testEntry = ['love', 'my', 'dalmation']
    thisDoc = array(setOfWords2Vec(myVocabList, testEntry))
    print(testEntry, 'classified as: ', classifyNB(thisDoc, p0V, p1V, pAb))
    testEntry = ['stupid', 'garbage']
    thisDoc = array(setOfWords2Vec(myVocabList, testEntry))
    print(testEntry, 'classified as: ', classifyNB(thisDoc, p0V, p1V, pAb))

testingNB()

# bag-of-words model:使用词带替换set
# 该模型考虑了词出现多次的情况
def bagOfWords2VecMN(vocabList, inputSet):
    returnVec = [0]*len(vocabList)
    for word in inputSet:
        if word in vocabList:
            returnVec[vocabList.index(word)] += 1
    return returnVec

4. 垃圾邮件过滤

有了文本分类器以后,我们可以将其应用于垃圾邮件的过滤,并测试错误率。

#=======测试样例1:垃圾邮件过滤=======
import re

def textParse(bigString):
    # 使用正则表达式去除文本中的空格和标点符号
    listOfTokens = re.split(r'\\W*',bigString)
    # 词汇全部小写,并且仅保留长度大于2的词汇
    return[tok.lower() for tok in listOfTokens if len(tok) > 2]

# 交叉验证
def spamTest():
    docList = []; classList = []; fullText = []
    for i in range(1,26):
        data = open('email/spam/%d.txt' % i,'r+',encoding = 'iso-8859-15').read()
        wordList = textParse(data)
        # wordList = textParse(open('email/spam/%d.txt' % i).read())
        docList.append(wordList)
        fullText.extend(wordList)
        classList.append(1)
        data = open('email/ham/%d.txt' % i,'r+',encoding = 'iso-8859-15').read()
        wordList = textParse(data)
        # wordList = textParse(open('email/ham/%d.txt' % i).read())
        docList.append(wordList)
        fullText.extend(wordList)
        classList.append(0)
    vocabList = createVocabList(docList)
    trainingSet = list(range(50)); testSet = []
    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(setOfWords2Vec(vocabList, docList[docIndex]))
        trainClasses.append(classList[docIndex])
    p0V, p1V, pAb = trainNB0(array(trainMat), array(trainClasses))
    errorCount = 0
    for docIndex in testSet:
        wordVector = setOfWords2Vec(vocabList, docList[docIndex])
        if classifyNB(array(wordVector), p0V, p1V, pAb) != classList[docIndex]:
            errorCount += 1
    print("The error rate is: ", float(errorCount)/len(testSet))

spamTest()

测试结果为:The error rate is: 0.3

当然,由于是随机选取的训练序列,因此每次运行结果不同。另外,我们在上述代码中还需要注意的是 python2.X python3.X 的不同。在 python2.X 中,.txt文档的读取是可以直接使用

wordList = textParse(open('email/spam/%d.txt' % i).read())

但是在 python3.X 中,情况则略有不同,此时的.txt文档是以默认的UTF-8格式读取的,而我们所使用的.txt文档则并不是以UTF-8格式进行编码的,因此运行程序时会报错。调试时我使用了各种编码格式包括utf-8, gbk, asicc,发现都有问题。最后在stack overflow中找到了解决方法,就是使用如下代码进行读取即可完成

data = open('email/ham/%d.txt' % i,'r+',encoding = 'iso-8859-15').read()
wordList = textParse(data)

还需要说明的一点是:在 python3.X 中,要使用list(range(50))代替range(50)来生成列表。

发布了21 篇原创文章 · 获赞 24 · 访问量 1万+

猜你喜欢

转载自blog.csdn.net/fIsh1220Fish/article/details/74905849