菜鸟学《机器学习实战》(三)——朴素贝叶斯

今天我们来看看另外一种分类方式——基于概率论的朴素贝叶斯

1、补充概率论知识——条件概率

假装所有人概率论都很6,条件概率什么的简单。重点是一个叫“先验概率”的,用正常人听得懂的话说就是,以往的数据都可以参考,可以用和这个数据集里不同点的“相似”程度来分类。这里的“相似”和之前的k均值、决策树不一样。k均值是“相差”小,决策树是根据不同的特征递归下降分类判断。而贝叶斯的“相似”指的是根据用“贝叶斯统计”推断出来的相似程度最大的那个分类作为预测分类。好吧,“贝叶斯统计”简单地说就是:我想通过特征去分类,直观上做不到,但我可以用贝叶斯公式,即用条件概率算出某特征与某分类同时成立的概率,再将它除以该特征的概率即可以得到“通过该特征用先验概率判断为某分类”的概率,数学公式大概长这样:

再提一下“朴素”吧,个人理解是这些点之间没有关联,即最“朴素”的办法。

2、朴素贝叶斯分类的文本分类器

《机器学习实战》书上这个例子,大概意思是,比如一个文档,通过里面一些词组出现在“侮辱集合”和“正常集合”里的频率来预测这个文档或者这句话的类别(侮辱类还是正常类)

先创建bayes.py文件,并引入numpy以备不时之需

from numpy import *

第一步,先创建loadDataSet()函数加载数据

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

postingList是不同类型的序列,classVec是标签

接着写createVocabList(dataSet)用于将待检测分类的文本通过set类型生成元素不重复的序列

def createVocabList(dataSet):
    vocabSet = set([])  # 创建一个空集
    for document in dataSet:
        vocabSet = vocabSet | set(document)  # 创造两个集合的并集
    return list(vocabSet)

接着,创建setOfWords2Vec(vocalList,inputSet)用于将在待分类inputSet中出现的vocalList集合里的元素的数量对应自增。

def setOfWords2Vec(vocalList, inputSet):
    returnVec = [0] * len(vocalList)  # 创建一个其中所含元素都为0的向量
    for word in inputSet:
        if word in vocalList:
            returnVec[vocalList.index(word)] = 1
        else:
            print("the word: %s is not in my Vocabulary!" % word)
    return returnVec

在命令行中测试:

>>> from numpy import *
>>> import bayes
>>> listOPosts,listClasses=bayes.loadDataSet()
>>> myVocabList=bayes.createVocabList(listOPosts)
>>> myVocabList
['dalmation', 'how', 'flea', 'problems', 'ate', 'to', 'steak', 'has', 'is', 'take',
'stupid', 'love', 'please', 'food', 'posting', 'cute', 'maybe', 'my', 'not', 'park',
 'licks', 'stop', 'mr', 'dog', 'quit', 'garbage', 'so', 'I', 'him', 'help', 'buying'

, 'worthless']

然后我们来通过词向量计算概率,为了便于理解,借用书上第60页的伪代码:

计算每个类别中的文档数目:

对每篇训练文档:

    对每个类别:

        如果词条出现在文档中->增加该词条的计数值

        增加所有词条的计数值

    对每个类别:

        对每个词条:

            将该词条的数目除以总词条数目得到条件概率

    返回每个类别的条件概率

于是,我们继续在bayes.py中书写朴素贝叶斯分类器训练函数 trainNB0(trainMatrix,trainCategory)

# 朴素贝叶斯分类器训练函数
# trainMatrix 文档矩阵
# trainCategory 由每篇文档类别标签所构成的向量
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


    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
    return p0Vect, p1Vect, pAbusive

在命令行中测试如下:

>>> from numpy import *
>>> from imp import reload
>>> reload(bayes)
<module 'bayes' from 'C:\\Users\\dell\\PycharmProjects\\untitled\\bayes.py'>
>>> listOPosts,listClasses=bayes.loadDataSet()
>>> myVocabList=bayes.createVocabList(listOPosts)
>>> trainMat=[]
>>> for postinDoc in listOPosts:
...     trainMat.append(bayes.setOfWords2Vec(myVocabList,postinDoc))
...
>>> p0V,p1V,pAb=bayes.trainNB0(trainMat,listClasses)
>>> pAb
0.5
>>> p0V
array([0.04166667, 0.04166667, 0.04166667, 0.04166667, 0.04166667,
       0.04166667, 0.04166667, 0.04166667, 0.04166667, 0.        ,
       0.        , 0.04166667, 0.04166667, 0.        , 0.        ,
       0.04166667, 0.        , 0.125     , 0.        , 0.        ,
       0.04166667, 0.04166667, 0.04166667, 0.04166667, 0.        ,
       0.        , 0.04166667, 0.04166667, 0.08333333, 0.04166667,
       0.        , 0.        ])
>>> p1V
array([0.        , 0.        , 0.        , 0.        , 0.        ,
       0.05263158, 0.        , 0.        , 0.        , 0.05263158,
       0.15789474, 0.        , 0.        , 0.05263158, 0.05263158,
       0.        , 0.05263158, 0.        , 0.05263158, 0.05263158,
       0.        , 0.05263158, 0.        , 0.10526316, 0.05263158,
       0.05263158, 0.        , 0.        , 0.05263158, 0.        ,

       0.05263158, 0.10526316])

结果正确。

现在我们遇到了两个问题:

(1)如果一个概率的结果为零,则其乘积也是零

(2)太多小数相乘会造成下溢出

针对第一个问题,我们将所有词的出现数初始化为1,并将坟墓初始化为2.

将bayes.py中的trainNB0()的第四行和第五行改为:

p0Num = ones(numWords)
p1Num = ones(numWords)
p0Denom = 2.0
p1Denom = 2.0

针对第二个问题,我们运用对数工具解决,修改return前的两行代码如下:

p1Vect = log(p1Num / p1Denom)
p0Vect = log(p0Num / p0Denom)

接着书写朴素贝叶斯分类函数

# 参数解释
# vec2Classify 要分类的向量
# p0Vec, p1Vec, pClass1 使用函数trainNB0()计算得到的三个概率
def classifyNB(vec2Classify, p0Vec, p1Vec, pClass1):
    # 使用NumPy的数组来计算两个向量相乘的结果
    # 这里跌相乘是指对应元素的相乘,即两个向量中的第i个对应相乘,i=1....n
    p1 = sum(vec2Classify * p1Vec) + log(pClass1)
    p0 = sum(vec2Classify * p0Vec) + log(1.0 - pClass1)
    if p1 > p0:
        return 1
    else:
        return 0


# 遍历函数(convenience function)节省调用之前代码的时间
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))

然后接着在命令行中测试:

>>> reload(bayes)
<module 'bayes' from 'C:\\Users\\dell\\PycharmProjects\\untitled\\bayes.py'>
>>> bayes.testingNB()
['love', 'my', 'dalmation'] classified as:  0

['stupid', 'garbage'] classified as:  1

结果正确。

最后,我们拓展一下思路。我们现在只是将存在与否用0、1分别表示,即词集;但是我们在实际需要中往往需要统计频数,即需要记录每个词出现的次数,即词袋,下面简单列出朴素贝叶斯词袋模型的代码:

# 词集模型:每个词的出现与否作为一个特征
# 词袋模型:每个词出现的次数
# 文档词袋模型
def bagOfWordsVecMV(vocabList, inputSet):
    returnVec = [0] * len(vocabList)
    for word in inputSet:
        if word in vocabList:
            # 和setOfWords2Vec()的唯一不同之处:
            # 每当遇到一个单词时,它会增加词向量中的对应值
            # 而不只是将对应的数值设为1
            returnVec[vocabList.index(word)] += 1
    return returnVec

感兴趣的话,就可以利用这个词袋模型替代之前的词集模型进行类似“垃圾邮件过滤”的工作了,就不再赘述了。

欢迎看到最后,发现错误欢迎指正。

猜你喜欢

转载自blog.csdn.net/Zjhao666/article/details/80486727