<机器学习实战>--朴素贝叶斯实战(二)

  

一 前言

  上一篇文章介绍了朴素贝叶斯的基本原理, 现在就来实践一下吧, 阅读了部分<机器学习实战>上的代码, 自己也敲了一遍, 做了一下验证, 现在就在这里分享一下.
  环境:
  Ubuntu 16.04
  Python 3.5.2

  

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


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

   加载数据

'''
加载训练数据, postingList是所有的训练集, 每一个列表代表一条言论, 一共有8条言论
            classVec代表每一条言论的类别, 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', 'hime'], 
                  ['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]
    return postingList, classVec

看一下运行结果:
这里写图片描述

  统计文档中的单词, 生成词汇表, 词汇表中每一个单词只出现一次, 没有重复的. (就是把文档中的所有单词放在一块, 然后去重.)

'''
创建词汇表, 就是把这个文档中所有的单词不重复的放在一个列表里面
'''
def createVocabList(dataSet):
    vocabSet = set([])           # 新建一个set集合, 保证里面的数据不重复
    for document in dataSet:     # 获得每一个文档
        vocabSet = vocabSet | set(document)   # 文档去重之后和词汇表求并集
    return list(vocabSet)                     # 词汇表转换为列表

这样我们就生成了一个词汇表. 看一下词汇表:
这里写图片描述

  文档转换为词向量, 对于每一个文档, 我们都要把他转换为词向量, 也就是由数字组成的一个向量, 此处的转换很简单. 上一步我们已经创建了一个词汇表, 对于一个文档, 首先我们生成一个和该文档长度一致的全0列表returnVec, 然后遍历该文当中的每一个单词, 如果这个单词在词汇表中出现过, 就在returnVec中相应位置变为1, 如果没出现过, 就仍然保持为0. 最后返回这个列表returnVec.

'''
vocabList是由createVocabList产生的词汇表
inputSet是输入新的文档
'''
def setOfWords2Vec(vocabList, inputSet):
    returnVec = [0] * len(vocabList)  # 生成一个全0列表, 个数为输入文档的长度
    for word in inputSet:      # 遍历输入文档中的每一个单词
        if word in vocabList:  # 如果这个单词在词汇表中
            returnVec[vocabList.index(word)] = 1   # 置1
        else:                               # 否则依然为0
            print("the word %s is not in my Vocabulary" % word) 
    return returnVec

看一下第一个文档转换为词向量后是什么样子:
这里写图片描述
词向量就是由0和1组成的数组.

2.2 计算先验概率

  计算先验概率, 接下来就是计算各种先验概率了, 还记得甚麽是先验概率吗? 不记得的同学可以去前面文章里面看看甚麽是先验概率. P(x1|Y=ck),P(x2|Y=ck)...P(Y=ck) , 在此处我们使用了拉普拉斯平滑, 注意看代码里面的初始化.

  首先统计一共有多少个文档, 然后统计词向量的长度, 接着计算侮辱性文档的先验概率, 再初始化p0Num, p0Denom, p0Num就是一个array(numpy), 大小是词向量的长度, 它用于记录当前文档的每一个单词是否在词向量中存在, 当然它是初始化为全1, 也就是拉普拉斯平滑. 请看for循环, 遍历每一个文档, 首先判断当前文档的label是否为侮辱性的, 是侮辱性的就执行p1, 不是就执行p0. 我们看
p1Num += trainMatrix[i] , 这句话是两个array之间的相加, 也就是下图这种情况:
这里写图片描述
p1Denom += 1这个就是统计当前文档中有多少是属于侮辱性的, <机器学习实战>上写的是p1Denom += sum(trainMatrix[i]), 但是按照计算先验概率的公式, 我认为是加一即可. 最后p1Vect = log(p1Num / p1Denom), 取log是为了防止多个小数相乘出现下溢. 这样就计算出了每一个单词的先验概率, 以及每一个类别的先验概率.

'''
计算先验概率
trainMatrix: 词向量矩阵
trainCategory: 每一个词向量的类别
返回每一个单词属于侮辱性和非侮辱性词汇的先验概率, 以及训练集包含侮辱性文档的概率
'''
def trainNB0(trainMatrix, trainCategory):
    numTrainDocs = len(trainMatrix)    # 由训练集生成的词向量矩阵
    numWords = len(trainMatrix[0])     # 每一个词向量的长度
    pAbusive  = sum(trainCategory) / float(numTrainDocs)    # 计算侮辱性文档的先验概率
    p0Num = ones(numWords)          # 生成全1 array, 长度为词向量的长度, 用于统计每个单词在整个矩阵中出现的次数(分子)
    p1Num = ones(numWords)
    p0Denom = 2.0                   # 初始化为2(分母), 拉普拉斯平滑
    p1Denom = 2.0
    for i in range(numTrainDocs):   # 遍历每一个词向量
        if trainCategory[i] == 1:   # 如果该词向量的类别为1
            p1Num += trainMatrix[i] # 计算P(x0)..P(xn)
            p1Denom += 1            # 统计侮辱性文档的个数
        else:
            p0Num += trainMatrix[i] # 计算P(x0)..P(xn)
            p0Denom += 1            # 统计非侮辱性文档个数
    p0Vect = log(p0Num / p0Denom)   # 计算P(x0|0)P(xn|0)
    p1Vect = log(p1Num / p1Denom)   # 计算P(x0|1) P(x1|1) P(xn|1)   取对数是防止多个小数相乘出现下溢
    return p0Vect, p1Vect, pAbusive

  我们看一下计算结果:

p0V, p1V, pAb = trainNB0(trainMat, listClasses)
p0V, p1V, pAb

这里写图片描述

  接下来就是将训练集里面的文档转换为词向量了. 代码很简单:

'''
制作词向量矩阵
将每一个文档转换为词向量, 然后放入矩阵中
'''
trainMat = []
for postinDoc in listOPosts:
    trainMat.append(setOfWords2Vec(myVocabList, postinDoc))

2.3 制作分类器, 测试

  接下来就是根据上面计算出来的每一个单词的先验概率, 来预测一个未知文档是否具有侮辱性了. 代码如下:

'''
制作贝叶斯分类器
vec2Classify: 测试样本的词向量
p0Vec: P(x0|Y=0) P(x1|Y=0) P(xn|Y=0)
p1Vec: P(x0|Y=1) P(x1|Y=1) P(xn|Y=1)
pClass1: P(y)
# log(P(x1|1)*P(x2|1)*P(x3|1)P(1))=log(P(x1|1))+log(P(x2|1))+log(P(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

注释部分已经解释了为什么要加上log(pClass1)了.
  检验效果的时候到了, 读入一个文档, 根据我们计算的先验概率, 分别计算他属于侮辱性文档的概率和属于非侮辱性文档的概率, 比较两个概率的大小, 大的那一类就是该文档所属于的类.

'''
测试贝叶斯分类器
'''
def testingNB():
    listOPosts, listClasses =  loadDataSet()   # 加载数据
    myVocabList = createVocabList(listOPosts)  # 词汇表
    trainMat = []                          # 训练集词向量
    for postinDoc in listOPosts:
        trainMat.append(setOfWords2Vec(myVocabList, postinDoc))
    p0V, p1V, pAb = trainNB0(trainMat, listClasses) # 计算先验概率
    testEntry = ['love', 'my', 'dalmation']   # 测试文档1
    thisDoc = array(setOfWords2Vec(myVocabList, testEntry))
    print(testEntry, 'classified as :', classifyNB(thisDoc, p0V, p1V, pAb))
    testEntry = ['stupid', 'garbage', 'stupid'] # 测试文档2
    thisDoc = array(setOfWords2Vec(myVocabList, testEntry))
    print(testEntry, 'classified as : ', classifyNB(thisDoc, p0V, p1V, pAb))

  看一下结果吧,
这里写图片描述

可以看出, 第一个属于非侮辱性的文档, 第二个属于侮辱性的文档. 因为文档中存在stupid这样的单词, 也就是骂人是傻子的意思.所以被判定为侮辱性的.
  文章主要参考<机器学习实战>和<统计学习方法>这两本书, 自己也是一个初学者, 文中有纰漏或者不当的地方, 欢迎各位朋友指出来, 咱们共同进步. 谢谢

猜你喜欢

转载自blog.csdn.net/qq_18293213/article/details/78241279