《机器学习实战》第四章:朴素贝叶斯(1)基本概念和文本分类

1. CH2-kNN(1)        
2. CH2-kNN(2)
3. CH2-kNN(3)
4. CH3-决策树(1)    
5. CH3-决策树(2)
6. CH3-决策树(3)
7. CH4-朴素贝叶斯(1)
8. CH4-朴素贝叶斯(2)
9. CH5-Logistic回归(1)
10. CH5-Logistic回归(2)
======== No More ========

 

背景是这样的,我们之前在KNN和决策树里面,都是要求分类器给出“该数据属于哪一类”的明确答案。这样的逻辑有点儿类似于“非黑即白”。现在我们希望分类器给出一个最优的类别猜测,这个猜测就是基于概率估计值所做出的。

朴素贝叶斯(Naive Beyes)

(1)是一种基于概率论的分类方法。

(2)核心思想:利用贝叶斯定理,计算数据属于各个类别的概率,选择概率最高的类别作为其分类。

(3)优点:在数据较少的情况下仍然有效。

 缺点:对于输入数据的准备方式较为敏感。

 适用数据类型:离散型

-------------------------------------------------------------------------------------

使用条件概率进行分类

条件概率不讲了,给条公式:

由此可得到上面给出的贝叶斯定理

现在拿一个简单的“分类”问题来举例:

假设我们有一个数据集。每条数据都x和y两个属性和1个类别(标签)。标签的种类有2种。我们的目标是把一条新的数据分到2种标签之一里面。

我们用p1(x,y)表示数据(x,y)属于类别1的概率,p2(x,y)表示数据(x,y)属于类别2的概率。那么,用条件概率公式来表示这两个概率,就分别是:P(c1|x,y)和P(c2|x,y),具体意义就是:由x和y这两个属性值确定的数据,其属于类别1、类别2的概率。

由贝叶斯定理,可得到:

解释一下等号右边三项的意思。

p(x,y|ci) 指的是:基于一个样本数据集,在ci这个类中,数据(x,y)出现的概率。

p(ci)指的是:基于这个样本数据集,属于c1这个类别的数据,占所有数据的数量。

p(x,y)指的是:在所有样本数据中,数据(x,y)出现的概率。

 

-------------------------------------------------------------------------------------

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

好。我们现在看一个具体一点的例子。

我们现在要把一篇文档进行分类,比如一封邮件是不是垃圾邮件,一条帖子是否是侮辱类的评论。

我们可以观察文档中出现的词,并且把每个词的 出现/不出现,或者 出现次数 作为一个特征。这样,特征的数量就会是样本数据集中所有出现过的单词的数量。(我们把所有这些单词组成的集合称为“词汇表”)

这时候问题来了:如果要得到好的概率分布(也就说我们想得到包含所有情况的样本数据),那么就得把每个单词的出现/不出现都组合起来(如果用出现次数作为特征,那会更加够呛)。如果词汇表有1000个单词,那么就得有2的1000次方条样本。要命了。

可是!如果特征之间是相互独立的,所需样本的数量就会大大减少。

所谓“独立”就是指一个单词(或特征)的出现与否,与其他单词(特征)出现与否没有半毛钱关系。

这显然是一个“天真”的假设,而正是因为这个假设,朴素贝叶斯被称为是“朴素”的。是谓naive。

记得《高级数据库》的老师是这样说的,“这个假设虽然看起来很不靠谱,但在实际运用的时候,往往能达到比较好的效果。” 

 

这里还要补充一下的是,朴素贝叶斯分类器通常有两种实现方式:

(1)基于伯努利分布:不考虑单词在文档中出现的次数。只要出现就是1,不出现就是0.

(2)基于多项式模型:考虑此在文档中出现的次数。出现几次就是几,不出现就是0.

 

-------------------------------------------------------------------------------------

用Python进行文本分类

这一节要上代码了。好激动!

例子就是文本分类。

我们拿到一条一条的文本之后,肯定需要把文本拆开,获得一个一个单词。显然,处理英文文本就比中文文本方便得多,毕竟人家单词之间都是用空格隔开的,咱们的词语都是粘在一起的,老半天才出现一个标点符号。中文文本需要采用一些算法来进行分词,现有的软件包括【jieba分词】等等(我只是联想到了最近在做一个相关的项目,所以多扯了几句)。

我们的例子是英文的。我们的目标是构建一个快速过滤器,如果某条帖子使用了侮辱性的语言,我们就把它识别出来,然后和谐掉它。微笑于是我们的数据集里面,每条数据的标签就是“侮辱类”和非侮辱类”中的一种。 

这里我还是想再多扯几句。有人也许会想,如果判断一条帖子是否是侮辱性的,那么只要看它里面包不包含侮辱性的词语不就行了吗?我想说,这样一来的话,分类器的工作模式就是“不分青红皂白,一棒子打死”。而我们采用的贝叶斯分类器,是基于概率比较的,他的工作模式就是:你出现了这个侮辱性的词(或特征),我不能马上给把你拉去枪毙,而只是把你的“嫌疑”提升了,我变得更加怀疑你了。等到看完你所有单词(特征),我才会做出最终的判决。这就减少了错判的可能性。

这是我自己的理解。

 

我们首先做的是把每条样本文本作转换为词向量

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) #union of the two sets
    return list(vocabSet)

def setOfWords2Vec(vocabList, inputSet):
    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

loadDataSet:样本数据集和标签

createVocabList创建词汇表,| 的作用是两个集合取并集。其他集合操作看这里

setOfWords2VecinputSet中检查词汇表vocabList中的单词,有的话改词特征值标为1,否则标为0.

测试一下,这里选的是第二条帖子['maybe', 'not', 'take', 'him', 'to', 'dog', 'park', 'stupid']:

    listOPosts, listClasses = loadDataSet()
    myVocabList = createVocabList(listOPosts)
    #print myVocabList
    print setOfWords2Vec(myVocabList, listOPosts[1])


 

现在,我们要从词向量计算概率。套用贝叶斯定理:

其中,w是词向量(也就是特征向量)。如果将w展开为一个个独立的特征,p(w|ci)可以写成p(w0, w1, w2, ..., wn | ci) 

我们这里运用朴素贝叶斯假设,于是 p(w0, w1, w2, ..., wn | ci) = p(w0|ci) p(w1|ci) p(w2|ci) ... p(wn|ci) 

以下就是朴素贝叶斯分类器的训练函数:

def trainNB0(trainMatrix, trainCategory):  #trainMatrix是训练文档列表,trainCategory是训练文档标签列表
    numTrainDocs = len(trainMatrix)  #训练文档的数量
    numWords = len(trainMatrix[0])  #每篇训练文档中单词的数量(词汇表单词数量)
    pAbusive = sum(trainCategory)/float(numTrainDocs)  #概率:标签为abusive的文档占总文档数的比例
    p0Num = zeros(numWords); p1Num = zeros(numWords)
    p0Denom = 0.0; p1Denom = 0.0
    for i in range(numTrainDocs):
        if trainCategory[i] == 1:
            p1Num += trainMatrix[i]  #到目前为止,标签为abusive的文档中,词汇表的单词出现过多少次
            p1Denom += sum(trainMatrix[i])  #到目前为止,标签为abusive的文档中出现过单词的总量
        else:
            p0Num += trainMatrix[i]  #到目前为止,标签为normal的文档中,词汇表的单词出现过多少次
            p0Denom += sum(trainMatrix[i])  #到目前为止,标签为normal的文档中出现过单词的总量
    p1Vect = p1Num/p1Denom       #标签为abusive的文档中,词汇表的各个单词的出现概率
    p0Vect = p0Num/p0Denom       #标签为normal的文档中,词汇表的各个单词的出现概率
    return p0Vect, p1Vect, pAbusive

第4行:trainCategory里面,abusive(侮辱类)是1,否则是0,所以sum(trainCategory)计算的是标签为abusive的文档数。pAbusive 是标签为abusive的文档占总文档数的比例。那么1-pAbusive 就是标签为非abusive的文档占总文档数的比例。当然,如果分类数大于2的话,就必须要做额外的计算了。

第5行:p0Num 和 p1Num 都初始化为长度为numWords 的零向量。用来统计到目前为止,标签为abusive/非abusive的文档中,词汇表的单词各出现过多少次。由于这个例子里面是2个分类,所以只有2项这个。有n个分类就有n项pnNum。

第6行:p0Denom 和p1Denom用来统计到目前为止,标签为abusive/非abusive的文档中出现过单词的总量。这里基于的是伯努利分布,出现了就是1,没出现就是0。同样,有n个分类就有n项pnDenom。

第14、15行:这里用一个向量除以一个数,就是把向量中的每一项除以了这个数。p1Vect和p0Vect分别是标签为abusive/非abusive的文档中,词汇表的各个单词的出现概率。

测试一下:

    listOPosts, listClasses = loadDataSet()
    myVocabList = createVocabList(listOPosts)

    trainMat = []
    for post in listOPosts:
        trainMat.append(setOfWords2Vec(myVocabList, post))

    p0V, p1V, pAb = trainNB0(trainMat, listClasses)
    print 'pAb=',pAb, '\n', 'p0V=',p0V, '\n', 'p1V=',p1V

 

现在有一个问题:在使用贝叶斯分类器的时候,我们需要计算多个概率的乘积,例如p(w0|ci) p(w1|ci) p(w2|ci) ... p(wn|ci) 

如果其中有某项的值是0,那么乘积也变成了0,计算就无意义了。

解决办法是把训练函数的第5行,p0Num 和 p1Num由0向量改为1向量;p0Denom 和 p1Denom 也由0.0的初始值改为2.0的初始值。

 

另外一个问题是向下溢出(underflow):如果多个很小的数相乘,可能四舍五入最后会得到0。解决办法当然是用log把乘法变成加法,把训练函数的第14、15行,改为:

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

这样一来,训练函数就变成了:

def trainNB0(trainMatrix, trainCategory):  #trainMatrix是训练文档列表,trainCategory是训练文档标签列表
    numTrainDocs = len(trainMatrix)  #训练文档的数量
    numWords = len(trainMatrix[0])  #每篇训练文档中单词的数量
    pAbusive = sum(trainCategory)/float(numTrainDocs)  #概率:标签为abusive的文档占总文档数的比例
    p0Num = ones(numWords); p1Num = ones(numWords)
    p0Denom = 2.0; p1Denom = 2.0
    for i in range(numTrainDocs):
        if trainCategory[i] == 1:
            p1Num += trainMatrix[i]  #到目前为止,标签为abusive的文档中,词汇表的单词出现过多少次
            p1Denom += sum(trainMatrix[i])  #到目前为止,标签为abusive的文档中出现过单词的总量
        else:
            p0Num += trainMatrix[i]  #到目前为止,标签为normal的文档中,词汇表的单词出现过多少次
            p0Denom += sum(trainMatrix[i])  #到目前为止,标签为normal的文档中出现过单词的总量
    p1Vect = log(p1Num/p1Denom)
    p0Vect = log(p0Num/p0Denom)
    return p0Vect, p1Vect, pAbusive

分类器就此构建好了,接下来可以拿新数据来分类了。分类函数:

def classifyNB(vec2Classify, p0Vec, p1Vec, pClass1):
    p1 = sum(vec2Classify * p1Vec) + log(pClass1)    #element-wise mult
    p0 = sum(vec2Classify * p0Vec) + log(1.0 - pClass1)
    if p1 > p0:
        return 1
    else: 
        return 0

vec2Classify:待分类的词向量

p0Vec:样本数据集中,标签为abusive的文档中,词汇表的各个单词的出现概率。

p0Vec:样本数据集中,标签为非abusive的文档中,词汇表的各个单词的出现概率。

pClass1:样本数据集中,abusive文档出现的概率。那么1-pClass1就是非abusive文档出现的概率。

第2行:计算 p1,即p(vec2Classify | c1)。vec2Classify * p1Vec是向量相乘:得出vec2Classify中单词的概率的向量,之所以概率间用sum求和,是因为之前已经通过log把乘法变成了加法。之后加上log(pClass1)也是这原因。至于式子里为什么没有出现贝叶斯定理中的分母,是因为计算p1和p2的时候会除以一个相同的概率 p(vec2Classify),(用了log之后除法变成减法)对比较大小无影响,所以省略了。

第3行:计算 p2,即p(vec2Classify | c2)。

 

分别用两个向量测试一下:

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)

 

 

最后的最后,我们再做一点点改进。

我们之前按照伯努利分布实现了文本分类的贝叶斯分类器,每个词出现与否作为一个特征,也可称为“词集模型(set-of-words model)。现在我们将每个词出现的次数作为一个特征,称为“词带模型”(bag-of-words model)。

要改动代码的话很简单,改一下setOfWords2Vec函数即可,改为bagOfWords2Vec:

def bagOfWords2VecMN(vocabList, inputSet):
    returnVec = [0]*len(vocabList)
    for word in inputSet:
        if word in vocabList:
            returnVec[vocabList.index(word)] += 1  #改动这里即可
    return returnVec


呼。内容有点多。下一篇博客会讲2个更大一些的实例。回见~

 

  • 2
    点赞
  • 9
    收藏
    觉得还不错? 一键收藏
  • 2
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值