3. 朴素贝叶斯分类

朴素贝叶斯分类算法的核心是贝叶斯公式:


贝叶斯公式

‘朴素’二字的含义是指,某一特征取值的概率相对于其他特征的取值独立,也就是实例的各个特征的分布相对独立。
朴素贝叶斯分类器的另一个假设是,每个特征同等重要。

关于朴素贝叶斯讲的清晰易懂的文章可见这里

朴素贝叶斯是用于文档分类的常用算法,本章也是使用文档分类来讲解的。

  • 从文本中构建词向量
  • 使用词向量训练分类器
  • 使用分类器来分类

1. 从文本中构建词向量

想要对文本进行分类,一个问题是不同文本的长度并不相同,该如何操作呢?可以先构建一个词典(词汇表),其中包含n个词。对于一篇文章将其表示为一个长为n的词典向量,其中值为1表示词典中的对应词在文章中出现过(不论次数),0表示未出现。这样对于每篇文章都可以得到一个对应的词向量,可用于训练分类器,以及进行分类。

那么该如何构造词典呢?可以使用训练集,或者通过对大量文章的统计来构建词典。注意词典中的词都是唯一的。书中使用了一个小小的训练集来讲解构造词典的过程。

"""
实验样本,注意这里给的例子只是二分类问题。
"""
def loadDataSet():
    # 每一行为一个进行了词条切分的句子
    # 可用正则表达式来处理文本。
    postingList = [['my', 'dog', 'has', 'flea', 'problems', 'help', 'please'],
                   ['maybe', 'not', 'take', 'him', 'to', 'dog', 'park', 'stupid'],
                   ['my', 'dalmation', 'is' , 'so', 'sute', '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]
    return postingList, classVec
"""
根据输入数据集构造词典,注意这里没有排序
"""
# dataSet即为上面postingList的形式。
def createVocabList(dataSet):
    vocabSet = set([])
    for document in dataSet:
        # 或,即为集合的并。。。
        vocabSet = vocabSet | set(document)
    return list(vocabSet)
>>> listOPosts, listClasses = loadDataSet()
# myVocabList就是根据数据集构造好的词典
# 其中的单词是随机排列的
>>> myVocabList = createVocabList(listOPosts)

下面代码根据上面构造好的词典对于属于文章构造对应的词向量:

"""
根据构造好的字典,对于输入文档,构造该文档的词向量
"""
# vocabList为构造好的词典
# inputSet为进行了词条切分的文章,形式如postingList的各个行。
# 返回returnVec为对应的词向量。
def setOfWords2Vec(vocabList, inputSet):
    returnVec = [0] * len(vocabList)
    for word in inputSet:
        if word in vocabList:
            returnVec[vocabList.index(word)] = 1
        else:
            pirnt("the word: %s is not in my Vocabulary!"%word)
    return returnVec
# 得到第一个句子对应的词向量
>>> setOfWords2Vec(myVocabList, listOPosts[0])

2. 使用词向量训练分类器

对于朴素贝叶斯分类器,训练分类器时到底是使分类器学习什么呢?
已知输入实例为ww=[w_1, w_2, w_3, ..., w_n](有n个特征,w_j为特征wj的取值),要预测其类别,即找到类别Ci,使P(Ci | w)最大。

而根据贝叶斯公式可得到:
P(Ci | w) = P(w | Ci ) * P(Ci) / P(w)。

又由于朴素贝叶斯的假设,即各个特征的分布独立,则可得到:
P(w | Ci) = P(w1 | Ci) * P(w2 | Ci) * P(w3 | Ci) * ... * P(wn | Ci)
也就是对于各个类别Ci,在该类别中,实例w各特征取值的概率。

P(w) = P(w1) * P(w2) * P(w3) * ... * P(wn)
也就是实例各特征取值的概率。

所以,学习的就是三个东西,对应公式中的三项(注意,都是根据训练集来学习的这些概率,如果训练集分布越全面,那么学到的效果也就越好),知道了这三项就可以对新实例进行分类:

  • P(wj | Ci) :j为特征索引(1<= j <= n),i即为类别索引。该概率也就是指,对于各个类别i,在该类别中,求出各特征j取各个值的概率。
  • P(Ci) :求训练集中各类别出现的概率,很简单,用各类别数量除以样本数量即可。
  • P(wj) :就是特征j的各个取值,在样本中出现的频率。注意,该项与类别无关,训练集确定了,该项的值也就确定了,所以在进行预测时不需要计算该项,只需计算上面两项即可。

下面为书中给的训练分类器的代码,只适用于二分类的问题,如果要用于多分类则需要重写代码喽:

"""
该代码针对应于两种特殊情况写的:
1. 这是一个二分类问题,label为0或1.
2. 由于要对文本分类,且使用的是词向量,那么每个特征的取值只有两种:
1或0(该词出现与否)。
由于都是0或1的取值,所有代码中有取巧的部分。
如果是特征值有多种取值情况,或多分类,那么该代码不适用了。
"""
import numpy as np
# trianMatrix为文档矩阵,每一行都是词向量
# trainCategory对应于trainMatrix中每一行的类别
# 这里代码为二分类,所以trainCategory为0、1组成的向量。
def trainNB0(trainMatrix, trainCategory):
    # 样本数量
    numTrainDocs = len(trainMatrix)
    # 特征个数
    numWords = len(trainMatrix[0])
    # 类别1的数量/总样本数量=类别1出现的概率
    pAbusive = np.sum(trainCategory) / float(numTrainDocs)
    # 用于存储两个类别下各个特征出现的次数。
    p0Num = np.zeros(numWords); p1Num = np.zeros(numWords)
    # 按理来说P(wj | Ci)为类别Ci时,特征wj取各个值的概率
    # 应等于wj为某一特征值的数量处理该类别Ci中样本数量的。
    # 但这里的做法是除以所有单词出现的次数了,这样条件概率的值就小得很多了。
    p0Denom = 0.0; p1Denom = 0.0
    for i in range(numTrainDocs):
        if trainCategory[i] == 1:
            # 由于词向量为0、1组成的向量,所以直接向量相加可得该
            # 类别下某一特征值出现的次数(词出现的次数)。
            p1Num += trainMatrix[i]
            # 这里p1Denom为类别1时所有词出现的次数的和。
            # 然后用pqNum / p1Denom。
            # 我感觉这是不对的,应改为p1Denom += 1
            # 使p1Denom统计类别1时的样本数量,因为各特征值只有
            # 两种取值,所以这样pqNum / p1Denom就为类别1时
            # 特征值为1的频率(也就是该词出现的频率)。
            p1Denom += np.sum(trainMatrix[i])
        else:
            # 同上,不过是类别0
            p0Num += trainMatrix[i]
            p0Denom += np.sum(trainMatrix[i])
    
    # 如上注释所示。
    p1Vect = p1Num / p1Denom
    p0Vect = p0Num / p0Denom
    # p0Vect各列应为类别0时,各特征为1的概率(也就是各词出现的概率)
    return p0Vect, p1Vect, pAbusive

文中对上述代码的改进有下面两点:

  • 由于在进行预测时,进行的计算P(w | Ci) = P(w1 | Ci) * P(w2 | Ci) * P(w3 | Ci) * ... * P(wn | Ci),当特征过多时,很容易造成下溢出问题。所以这里应对p0Vect和p1Vect取log,那么就变为:
    log(P(w | Ci) * P(Ci)) = log(P(w | Ci) ) + log(Ci))
    log(P(w | Ci) )= log(P(w1 | Ci)) + log(P(w2 | Ci)) + log(P(w3 | Ci)) + ... + log(P(wn | Ci))

  • 另一点,对于某一i和j,P(wj | Ci)可能为0,也就是在该类别中该词从未出现过。这很正常,如骂人的词就只能出现在脏话中。而对0取log会得到负无穷,这可不行,所以修改了上面代码,假设每个词在每一类中都出现过一次:p0Num = np.ones(numWords); p1Num = np.ones(numWords)
    文中的另一项修改是p0Denom = 2.0; p1Denom = 2.0,这我就没看懂了,感觉改的毫无意义。。。

以上就为训练过程了,很简单,对于文本的二分类是适用的。更复杂的分类就要重写代码了。过程不重要,重要的是思路。

3. 使用分类器来分类

"""
很简单,思路就如上所示。
"""
# vec2Classify为待分类的词向量
# p0Vec和p1Vec各列为条件概率log(P(wj | Ci))
# pClass1为P(C1)
def classifyNB(vec2Classify, p0Vec, p1Vec, pClass1):
    # log(P(w | C1) * P(C1))
    p1 = np.sum(vec2Classify * p1Vec) + np.log(pClass1)
    # log(P(w | C2) * P(C2))
    p0 = np.sum(vec2Classify * p0Vec) + np.log(1.0 - pClass1)
    if p1 > p0:
        return 1
    else:
        return 0
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 204,732评论 6 478
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 87,496评论 2 381
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 151,264评论 0 338
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 54,807评论 1 277
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 63,806评论 5 368
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 48,675评论 1 281
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 38,029评论 3 399
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 36,683评论 0 258
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 41,704评论 1 299
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 35,666评论 2 321
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 37,773评论 1 332
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 33,413评论 4 321
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 39,016评论 3 307
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 29,978评论 0 19
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 31,204评论 1 260
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 45,083评论 2 350
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 42,503评论 2 343

推荐阅读更多精彩内容