第四章 基于概率论的分类方法:朴素贝叶斯

第4章 基于概率论的分类方法:朴素贝叶斯

前两章我们要求分类器做出艰难决策,给出 “该数据实例属于哪一类”这类问题的明确答案。不过,分类器有时会产生错误结果,这时可以要求分类器给出一个最优的类别猜测结果,同时给出这个猜测的概率估计值。

4.1 基于贝叶斯决策理论的分类方法

朴素贝叶斯法是基于贝叶斯定理与特征条件独立假设的分类方法。朴素贝叶斯分类器基于一个简单的假定:给定目标值时属性之间相互条件独立。

  • 优点:在数据较少的情况下仍然有效,可以处理多类别问题。

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

  • 适用数据范围:标称型数据。

假设现在我们有一个数据集,它由两类数据组成,数据分布如图4-1所示。
!在这里插入图片描述

用p1(x,y)表示数据点(x,y)属于类别1(图中用圆点表示的类别)的概率,用p2(x,y)表示数据点(x,y)属于类别2(图中用三角形表示的类别)的概率,那么对于一个新数据点(x,y),可以用下面的规则来判断它的类别:

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

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

4.2 条件概率

假设现在有一个装了7块石头的罐子,其中3块是灰色的,4块是黑色的。如果从罐子中随机取出一块石头,那么是灰色石头的可能性是多少?由于取石头有7种可能,其中3种为灰色,所以取出灰色石头的概率为3/7。那么取到黑色石头的概率又是多少呢?很显然,是4/7。我们使用P(gray)来表示取到灰色石头的概率,其概率值可以通过灰色石头数目除以总的石头数目来得到。

如果这7块石头放在两个桶中,A桶2块黑色和2块灰色,B桶2块黑色和1块灰色。那么上述概率应该如何计算?要计算P(gray)或者P(black),事先得知道石头所在桶的信息会不会改变结果?你有可能已经想到计算从B桶中取到灰色石头的概率的办法,这就是所谓的条件概率(conditionalprobability)。假定计算的是从B桶取到灰色石头的概率,这个概率可以记作P(gray|bucketB),我们称之为“在已知石头出自B桶的条件下,取出灰色石头的概率”。不难得到,P(gray|bucketA)值为2/4,P(gray|bucketB) 的值为1/3。

条件概率的计算公式如下所示:
p ( g r a y ∣ b u c k e t B ) = p ( g r a y & b u c k e t B ) p ( b u c k e t B ) p(gray|bucketB)=\frac{p(gray\&bucketB)}{p(bucketB)} p(graybucketB)=p(bucketB)p(gray&bucketB)
首先,用B桶中灰色石头的个数除以两个桶中总的石头数,得到P(gray and bucketB) = 1/7。 其次,由于B桶中有3块石头,而总石头数为7,于是P(bucketB)就等于3/7。于是有P(gray|bucketB) = P(gray and bucketB)/P(bucketB) =(1/7) / (3/7) = 1/3。

另一种有效计算条件概率的方法称为贝叶斯准则。贝叶斯准则告诉我们如何交换条件概率中的条件与结果,即如果已知P(x|c),要求P(c|x),那么可以使用下面的计算方法:
p ( c ∣ x ) = p ( x ∣ c ) p ( c ) p ( x ) p(c|x)=\frac{p(x|c)p(c)}{p(x)} p(cx)=p(x)p(xc)p(c)

4.3 使用条件概率来分类

4.1节提到贝叶斯决策理论要求计算两个概率p1(x, y)和p2(x, y)。但这两个准则并不是贝叶斯决策理论的所有内容。使用p1( )和p2( )只是为了尽可能简化描述,而真正需要计算和比较的是p(c1|x, y)和p(c2|x, y)。这些符号所代表的具体意义是:给定某个由x、y表示的数据点,那么该数据点来自类别c1的概率是多少?数据点来自类别c2的概率又是多少?注意这些概率与刚才给出的概率p(x, y|c1)并不一样,不过可以使用贝叶斯准则来交换概率中条件与结果。具体地,应用贝叶斯准则得到:
p ( c i ∣ x , y ) = p ( x , y ∣ c i ) p ( c i ) p ( x , y ) p(c_{i}|x,y)=\frac{p(x,y|c_{i})p(c_{i})}{p(x,y)} p(cix,y)=p(x,y)p(x,yci)p(ci)
使用这些定义,可以定义贝叶斯分类准则为:

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

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

在文档分类中,整个文档(如一封电子邮件)是实例,而电子邮件中的某些元素则构成特征。虽然电子邮件是一种会不断增加的文本,但我们同样也可以对新闻报道、用户留言、政府公文等其他任意类型的文本进行分类。我们可以观察文档中出现的词,并把每个词的出现或者不出现作为一个特征,这样得到的特征数目就会跟词汇表中的词目一样多。

朴素贝叶斯的一般流程

  1. 收集数据:可以使用任何方法。
  2. 准备数据:需要数值型或者布尔型数据。
  3. 分析数据:有大量特征时,绘制特征作用不大,此时使用直方图效果更好。
  4. 训练算法:计算不同的独立特征的条件概率。
  5. 测试算法:计算错误率。
  6. 使用算法:一个常见的朴素贝叶斯应用是文档分类。可以在任意的分类场景中使用朴素贝叶斯分类器,不一定非要是文本。

假设词汇表中有1000个单词。要得到好的概率分布,就需要足够的数据样本,假定样本数为N。前面讲到的约会网站示例中有1000个实例,手写识别示例中每个数字有200个样本,而决策树示例中有24个样本。其中,24个样本有点少,200个样本好一些,而1000个样本就非常好了。约会网站例子中有三个特征。由统计学知,如果每个特征需要N个样本,那么对于10个特征将需要N10个样本,对于包含1000个特征的词汇表将需要N1000个样本。可以看到,所需要的样本数会随着特征数目增大而迅速增长。

如果特征之间相互独立,那么样本数就可以从N1000减少到1000×N。所谓独立(independence)指的是统计意义上的独立,即一个特征或者单词出现的可能性与它和其他单词相邻没有关系。举个例子讲,假设单词bacon出现在unhealthy后面与出现在delicious后面的概率相同。当然,我们知道这种假设并不正确,bacon常常出现在delicious附近,而很少出现在unhealthy附近,这个假设正是朴素贝叶斯分类器中朴素(naive)一词的含义。朴素贝叶斯分类器中的另一个假设是,每个特征同等重要。其实这个假设也有问题。 如果要判断留言板的留言是否得当,那么可能不需要看完所有的1000个单词,而只需要看10~20个特征就足以做出判断了。尽管上述假设存在一些小的瑕疵,但朴素贝叶斯的实际效果却很好。

使用 Python 进行文本分类

要从文本中获取特征,需要先拆分文本。这里的特征是来自文本的词条(token),一个词条是字符的任意组合。可以把词条想象为单词,也可以使用非单词词条,如URL、IP地址或者任意其他字符串。然后将每一个文本片段表示为一个词条向量,其中值为1表示词条出现在文档中,0表示词条未出现。

以在线社区的留言板为例。为了不影响社区的发展,我们要屏蔽侮辱性的言论,所以要构建一个快速过滤器,如果某条留言使用了负面或者侮辱性的语言,那么就将该留言标识为内容不当。过滤这类内容是一个很常见的需求。对此问题建立两个类别:侮辱类和非侮辱类,使用1和0分别表示。

4.5.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']]
    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)
    return list(vocabSet)


def setOfWords2Vec(vocabList, inputSet):
    """
    :param vocabList: 词汇表
    :param inputSet: 某个文档
    :return: 文档向量,向量的每一元素为1或0,分别表示词汇表中的单词在输入文档中是否出现
    """
    # 创建一个其中所含元素都为0的向量
    returnVec = [0] * len(vocabList)
    # 遍历文档中的所有单词,如果出现了词汇表中的单词,则将输出的文档向量中的对应值设为1
    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

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

前面介绍了如何将一组单词转换为一组数字,接下来看看如何使用这些数字计算概率。现在已经知道一个词是否出现在一篇文档中,也知道该文档所属的类别。我们重写贝叶斯准则,将之前的x、y 替换为w。粗体w表示这是一个向量,即它由多个数值组成。在这个例子中,数值个数与词汇表中的词个数相同。
p ( c i ∣ w ) = p ( w ∣ c i ) p ( c i ) p ( w ) p(c_{i}|w)=\frac{p(w|c_{i})p(c_{i})}{p(w)} p(ciw)=p(w)p(wci)p(ci)
先可以通过类别i(侮辱性留言或非侮辱性留言)中文档数除以总的文档数来计算概率p(ci)。接下来计算p(w|ci),这里就要用到朴素贝叶斯假设。如果将w展开为一个个独立特征,那么就可以将上述概率写作p(w0,w1,w2…wN|ci)。这里假设所有词都互相独立,该假设也称作条件独立性假设,它意味着可以使用p(w0|ci)p(w1|ci)p(w2|ci)…p(wN|ci)来计算上述概率,这就极大地简化了计算的过程。

该函数的伪代码如下:

计算每个类别中的文档数目
对每篇训练文档:
  对每个类别:
    如果词条出现文档中→ 增加该词条的计数值
    增加所有词条的计数值
  对每个类别:
    对每个词条:
      将该词条的数目除以总词条数目得到条件概率
  返回每个类别的条件概率
# 朴素贝叶斯分类器训练函数
def trainNB0(trainMatrix, trainCategory):
    '''

    :param trainMatrix: 文档矩阵
    :param trainCategory: 由每篇文档类别标签所构成的向量
    :return:
    '''
    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):
        # 计算文档属于侮辱性文档(class=1)的概率
        if trainCategory[i] == 1:
            # 向量相加
            p1Num += trainMatrix[i]
            p1Denom += sum(trainMatrix[i])
        # 计算文档属于非侮辱性文档(class=0)的概率
        else:
            p0Num += trainMatrix[i]
            p0Denom += sum(trainMatrix[i])
    # 对每个元素做除法
    p1Vect = p1Num / p1Denom
    p0Vect = p0Num / p0Denom
    return p0Vect, p1Vect, pAbusive
# 创建一些实验样本
from numpy import *


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)
    return list(vocabSet)


def setOfWords2Vec(vocabList, inputSet):
    """
    :param vocabList: 词汇表
    :param inputSet: 某个文档
    :return: 文档向量,向量的每一元素为1或0,分别表示词汇表中的单词在输入文档中是否出现
    """
    # 创建一个其中所含元素都为0的向量
    returnVec = [0] * len(vocabList)
    # 遍历文档中的所有单词,如果出现了词汇表中的单词,则将输出的文档向量中的对应值设为1
    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


# 朴素贝叶斯分类器训练函数
def trainNB0(trainMatrix, trainCategory):
    '''

    :param trainMatrix: 文档矩阵
    :param trainCategory: 由每篇文档类别标签所构成的向量
    :return:
    '''
    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):
        # 计算文档属于侮辱性文档(class=1)的概率
        if trainCategory[i] == 1:
            # 向量相加
            p1Num += trainMatrix[i]
            p1Denom += sum(trainMatrix[i])
        # 计算文档属于非侮辱性文档(class=0)的概率
        else:
            p0Num += trainMatrix[i]
            p0Denom += sum(trainMatrix[i])
    # 对每个元素做除法
    p1Vect = p1Num / p1Denom
    p0Vect = p0Num / p0Denom
    return p0Vect, p1Vect, pAbusive


if __name__ == '__main__':
    myData, myClass = loadDataSet()
    myVocabList = createVocabList(myData)
    print(myVocabList)
    trainMat = []
    for postinDoc in myData:
        a = setOfWords2Vec(myVocabList, postinDoc)
        trainMat.append(a)
    p0V, p1V, pAb = trainNB0(trainMat, myClass)
    print('p0V=', p0V)
    print('p1V=\n', p1V)
    print('pAb=', pAb)
['flea', 'licks', 'cute', 'park', 'please', 'so', 'posting', 'is', 'maybe', 'stop', 'worthless', 'buying', 'not', 'has', 'dalmation', 'how', 'my', 'help', 'love', 'to', 'mr', 'ate', 'steak', 'garbage', 'stupid', 'quit', 'food', 'him', 'I', 'dog', 'take', 'problems']
p0V= [0.04166667 0.04166667 0.04166667 0.         0.04166667 0.04166667
 0.         0.04166667 0.         0.04166667 0.         0.
 0.         0.04166667 0.04166667 0.04166667 0.125      0.04166667
 0.04166667 0.04166667 0.04166667 0.04166667 0.04166667 0.
 0.         0.         0.         0.08333333 0.04166667 0.04166667
 0.         0.04166667]
p1V=
 [0.         0.         0.         0.05263158 0.         0.
 0.05263158 0.         0.05263158 0.05263158 0.10526316 0.05263158
 0.05263158 0.         0.         0.         0.         0.
 0.         0.05263158 0.         0.         0.         0.05263158
 0.15789474 0.05263158 0.05263158 0.05263158 0.         0.10526316
 0.05263158 0.        ]
pAb= 0.5

我们发现文档属于侮辱类的概率pAb为0.5,该值是正确的。接下来,看一看在给定文档类别条件下词汇表中单词的出现概率,看看是否正确。词汇表中的第3个词是cute,其在类别0中出现1次,而在类别1中从未出现。对应的条件概率分别为0.041 666 67与0.0。该计算是正确的。我们找找所有概率中的最大值,该值出现在P(1)数组第21个下标位置,大小为0.157 894 74。在myVocabList的第26个下标位置上可以查到该单词是stupid。这意味着stupid是最能表征类别1(侮辱性文档类)的单词。

4.5.3 测试算法:根据现实情况修改分类器

利用贝叶斯分类器对文档进行分类时,要计算多个概率的乘积以获得文档属于某个类别的概率,即计算p(w0|1)p(w1|1)p(w2|1)。如果其中一个概率值为0,那么最后的乘积也为0。为降低这种影响,可以将所有词的出现数初始化为1,并将分母初始化为2。

# 初始化概率
p0Num = ones(numWords)
p1Num = ones(numWords)
p0Denom = 2.0
p1Denom = 2.0

另一个遇到的问题是下溢出,这是由于太多很小的数相乘造成的。当计算乘积p(w0|ci)p(w1|ci)p(w2|ci)…p(wN|ci)时,由于大部分因子都非常小,所以程序会下溢出或者得到不正确的答案。(读者可以用Python尝试相乘许多很小的数,最后四舍五入后会得到0。)一种解决办法是对乘积取自然对数。在代数中有 ln(a*b) = ln(a)+ln(b),于是通过求对数可以避免下溢出或者浮点数舍入导致的错误。同时,采用自然对数进行处理不会有任何损失。

# 对每个元素做除法
p1Vect = log(p1Num / p1Denom)
p0Vect = log(p0Num / p0Denom)
def classifyNB(vec2Classify, p0Vec, p1Vec, pClass1):
    """
    :param vec2Classify: 要分类的向量
    :param p0Vec: 属于非侮辱性文档(class=0)的概率向量
    :param p1Vec: 属于侮辱性文档(class=1)的概率向量
    :param pClass1: 属于侮辱性文档的概率
    :return: 返回分类结果
    """
    # 元素相乘
    p1 = sum(vec2Classify * p1Vec) + log(pClass1)
    p0 = sum(vec2Classify * p0Vec) + log(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))

[‘love’, ‘my’, ‘dalmation’] classified as: 0
[‘stupid’, ‘garbage’] classified as: 1

4.5.4 准备数据:文档词袋模型

我们将每个词的出现与否作为一个特征,这可以被描述为词集模型(set-of-wordsmodel)。如果一个词在文档中出现不止一次,这可能意味着包含该词是否出现在文档中所不能表达的某种信息,这种方法被称为词袋模型(bag-of-words model)。在词袋中,每个单词可以出现多次,而在词集中,每个词只能出现一次。

def bagOfWords2VecMN(vocabList, inputSet):
    """
    朴素贝叶斯词袋模型
    :param vocabList: 词汇表
    :param inputSet: 某个文档
    :return: 文档向量,向量的每一元素表示词汇表中的单词在输入文档中出现次数
    """
    # 创建一个其中所含元素都为0的向量
    returnVec = [0] * len(vocabList)
    # 遍历文档中的所有单词,如果出现了词汇表中的单词,则将输出的文档向量中的对应值加1
    for word in inputSet:
        if word in vocabList:
            returnVec[vocabList.index(word)] += 1
    return returnVec

4.6 示例:使用朴素贝叶斯过滤垃圾邮件

使用朴素贝叶斯解决一些现实生活中的问题时,需要先从文本内容得到字符串列表,然后生成词向量。下面这个例子中,我们将了解朴素贝叶斯的一个最著名的应用:电子邮件垃圾过滤。

使用朴素贝叶斯对电子邮件进行分类:

  1. 收集数据:提供文本文件。
  2. 准备数据:将文本文件解析成词条向量。
  3. 分析数据:检查词条确保解析的正确性。
  4. 训练算法:使用我们之前建立的trainNB0()函数。
  5. 测试算法:使用classifyNB(),并且构建一个新的测试函数来计算文档集的错误率。
  6. 使用算法:构建一个完整的程序对一组文档进行分类,将错分的文档输出到屏幕上。

4.6.1 准备数据:切分文本

mysent = "This book is the best book on Python or M.L. I have ever laid eyes upon."
testSplit = mysent.split()
print(testSplit)
import re
regEx = re.compile('\\W+')
listOfSent = regEx.split(mysent)
print(listOfSent)
result = [tok.lower() for tok in listOfSent if len(tok) > 0]
print(result)
['This', 'book', 'is', 'the', 'best', 'book', 'on', 'Python', 'or', 'M.L.', 'I', 'have', 'ever', 'laid', 'eyes', 'upon.']
['This', 'book', 'is', 'the', 'best', 'book', 'on', 'Python', 'or', 'M', 'L', 'I', 'have', 'ever', 'laid', 'eyes', 'upon', '']
['this', 'book', 'is', 'the', 'best', 'book', 'on', 'python', 'or', 'm', 'l', 'i', 'have', 'ever', 'laid', 'eyes', 'upon']

4.6.2 测试算法:使用朴素贝叶斯进行交叉验证

随机选择数据的一部分作为训练集,而剩余部分作为测试集的过程称为留存交叉验证。

# 文件解析
def textParse(bigString):
    listOfToken = re.split(r'\W+', bigString)
    return [tok.lower() for tok in listOfToken if len(tok) > 2]


def spamTest():
    docList = []
    classList = []
    fullText = []
    # 导入并解析文本文件
    for i in range(1, 26):
        wordList = textParse(open('./data/email/spam/%d.txt' % i).read())
        docList.append(wordList)
        fullText.extend(wordList)
        classList.append(1)
        wordList = textParse(open('./data/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, pSpam = trainNB0(array(trainMat), array(trainClasses))
    errorCount = 0
    # 对测试集分类
    for docIndex in testSet:
        wordVector = setOfWords2Vec(vocabList, docList[docIndex])
        if classifyNB(array(wordVector), p0V, p1V, pSpam) != classList[docIndex]:
            errorCount += 1
    print('the error rate is: ', float(errorCount) / len(testSet))
    

if __name__ == '__main__':
    spamTest()

4.7 示例:使用朴素贝叶斯分类器从个人广告中获取区域倾向

我们将分别从美国的两个城市中选取一些人,通过分析这些人发布的征婚广告信息,来比较这两个城市的人们在广告用词上是否不同。如果结论确实是不同,那么他们各自常用的词是哪些?从人们的用词当中,我们能否对不同城市的人所关心的内容有所了解?

使用朴素贝叶斯来发现地域相关的用词:

  1. 收集数据:从RSS源收集内容,这里需要对RSS源构建一个接口。
  2. 准备数据:将文本文件解析成词条向量。
  3. 分析数据:检查词条确保解析的正确性。
  4. 训练算法:使用我们之前建立的trainNB0()函数。
  5. 测试算法:观察错误率,确保分类器可用。可以修改切分程序,以降低错误率,提高分类结果。
  6. 使用算法:构建一个完整的程序,封装所有内容。给定两个RSS源,该程序会显示最常用的公共词。

4.7.1 收集数据:导入 RSS 源

打开Craigslist上的RSS源

import feedparser
ny = feedparser.parse('http://newyork.craigslist.org/stp/index.rss')
# RSS源分类器
def calcMostFreq(vocabList, fullText):
    # 计算出现频率
    freqDict = {}
    for token in vocabList:
        freqDict[token] = fullText.count(token)
    sortedFreq = sorted(freqDict.items(), key=operator.itemgetter(1), reverse=True)
    return sortedFreq[:30]


# 高频词去除函数
def localWords(feed1, feed0):
    docList = []
    classList = []
    fullText = []
    minLen = min(len(feed1['entries']), len(feed0['entries']))
    # 每次访问一条RSS源
    for i in range(minLen):
        wordList = textParse(feed1['entries'][i]['summary'])
        docList.append(wordList)
        fullText.extend(wordList)
        classList.append(1)
        wordList = textParse(feed0['entries'][i]['summary'])
        docList.append(wordList)
        fullText.extend(wordList)
        classList.append(0)
    # 去掉出现次数最高的那些词
    vocabList = createVocabList(docList)
    top30Words = calcMostFreq(vocabList, fullText)
    for pairW in top30Words:
        if pairW[0] in vocabList:
            vocabList.remove(pairW[0])
    trainingSet = list(range(2 * minLen))
    testSet = []
    for i in range(20):
        randIndex = int(random.uniform(0, len(trainingSet)))
        testSet.append(trainingSet[randIndex])
        del(trainingSet[randIndex])
    trainMat = []
    trainClasses = []
    for docIndex in trainingSet:
        trainMat.append(bagOfWords2VecMN(vocabList, docList[docIndex]))
        trainClasses.append(classList[docIndex])
    p0V, p1V, pSpam = trainNB0(array(trainMat), array(trainClasses))
    errorCount = 0
    for docIndex in testSet:
        wordVector = bagOfWords2VecMN(vocabList, docList[docIndex])
        if classifyNB(array(wordVector), p0V, p1V, pSpam) != classList[docIndex]:
            errorCount += 1
    print('the error rate is: ', float(errorCount) / len(testSet))
    return vocabList, p0V, p1V

4.7.2 分析数据:显示地域相关的用词

# 最具表征性的词汇显示函数
def getTopWords(ny, sf):
    vacabList, p0V, p1V = localWords(ny, sf)
    topNY = []
    topSF = []
    for i in range(len(p0V)):
        if p0V[i] > -6.0:
            topSF.append((vocabList[i], p0V[i]))
        if p1V[i] > -6.0:
            topNY.append((vocabList[i], p1V[i]))
    sortedSF = sorted(topSF, key=lambda pair: pair[1], reverse=True)
    print("SF**SF**SF**SF**SF**SF**SF**SF**SF**SF**SF**SF**SF")
    for item in sortedSF:
        print(item[0])
    sortedNY = sorted(topNY, key=lambda pair: pair[1], reverse=True)
    print("NY**NY**NY**NY**NY**NY**NY**NY**NY**NY**NY**NY**NY")
    for item in sortedNY:
        print(item[0])

4.8 本章小结

对于分类而言,使用概率有时要比使用硬规则更为有效。贝叶斯概率及贝叶斯准则提供了一种利用已知值来估计未知概率的有效方法。

可以通过特征之间的条件独立性假设,降低对数据量的需求。独立性假设是指一个词的出现概率并不依赖于文档中的其他词。当然我们也知道这个假设过于简单。这就是之所以称为朴素贝叶斯的原因。尽管条件独立性假设并不正确,但是朴素贝叶斯仍然是一种有效的分类器。

利用现代编程语言来实现朴素贝叶斯时需要考虑很多实际因素。下溢出就是其中一个问题,它可以通过对概率取对数来解决。词袋模型在解决文档分类问题上比词集模型有所提高。还有其他一些方面的改进,比如说移除停用词,当然也可以花大量时间对切分器进行优化。

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

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值