资讯专栏INFORMATION COLUMN

【数据科学系统学习】机器学习算法 # 西瓜书学习记录 [6] 朴素贝叶斯实践

leanxi / 2776人阅读

摘要:本篇内容为机器学习实战第章基于概率论的分类方法朴素贝叶斯程序清单。朴素贝叶斯优点在数据较少的情况下仍然有效,可以处理多类别问题。参考链接机器学习实战笔记之四基于概率论的分类方法朴素贝叶斯不足之处,欢迎指正。

本篇内容为《机器学习实战》第 4 章 基于概率论的分类方法:朴素贝叶斯程序清单。所用代码为 python3。


朴素贝叶斯

优点:在数据较少的情况下仍然有效,可以处理多类别问题。
缺点:对于输入数据的准备方式较为敏感。
适用数据类型:标称型数据。


使用 Python 进行文本分类

简单描述这个过程为:从文本中获取特征,构建分类器,进行分类输出结果。这里的特征是来自文本的词条 (token),需要将每一个文本片段表示为一个词条向量,其中值为 1 表示词条出现在文档中,0 表示词条未出现。

接下来给出将文本转换为数字向量的过程,然后基于这些向量来计算条件概率,并在此基础上构建分类器。

下面我们以在线社区的留言板为例,给出一个用来过滤的例子。
为了不影响社区的发展,我们需要屏蔽侮辱性的言论,所以要构建一个快速过滤器,如果某条留言使用来负面或者侮辱性的语言,就将该留言标识为内容不当。对此问题建立两个类别:侮辱类和非侮辱类,分别使用 1 和 0 来表示。
准备数据:从文本中构建词向量 程序清单 4-1 词表到向量的转换函数
"""
Created on Sep 10, 2018

@author: yufei
"""

# coding=utf-8
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 代表正常言论

    """
    变量 postingList 返回的是进行词条切分后的文档集合。
    留言文本被切分成一些列词条集合,标点符号从文本中去掉
    变量 classVec 返回一个类别标签的集合。
    这些文本的类别由人工标注,标注信息用于训练程序以便自动检测侮辱性留言。
    """
    return postingList, classVec

"""
创建一个包含在所有文档中出现的不重复词的列表
是用python的 Set 数据类型
将词条列表输给 Set 构造函数,set 就会返回一个不重复词表
"""
def createVocabList(dataSet):
    # 创建一个空集合
    vocabSet = set([])
    # 将每篇文档返回的新词集合添加进去,即创建两个集合的并集
    for document in dataSet:
        vocabSet = vocabSet | set(document)
    # 获得词汇表
    return list(vocabSet)

# 参数:词汇表,某个文档
def setOfWords2Vec(vocabList, inputSet):
    # 创建一个和词汇表等长的向量,将其元素都设置为 0
    returnVec = [0] * len(vocabList)
    # 遍历文档中所有单词
    for word in inputSet:
        # 如果出现词汇表中的单词,将输出的文档向量中的对应值设为 1
        if word in vocabList:
            returnVec[vocabList.index(word)] = 1
        else:
            print("the word: %s is not in my Vocabulary!" % word)
    # 输出文档向量,向量元素为 1 或 0
    return returnVec

在 python 提示符下,执行代码并得到结果:

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

即可得到的一个不会出现重复单词的词表myVocabList,目前该词表还没有排序。

继续执行代码:

>>> bayes.setOfWords2Vec(myVocabList, list0Posts[3])
[0, 0, 0, 1, 0, 1, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1]
>>> bayes.setOfWords2Vec(myVocabList, list0Posts[0])
[0, 0, 1, 0, 0, 0, 0, 1, 0, 0, 0, 0, 1, 0, 0, 0, 1, 0, 0, 0, 0, 0, 0, 0, 1, 0, 1, 1, 0, 0, 0, 0]

函数setOfWords2Vec使用词汇表或者说想要检查的所有单词作为输入,然后为其中每一个单词构建一个特征。一旦给定一篇文章(本例中指一条留言),该文档就会被转换为词向量。

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

函数伪代码如下:

··· 计算每个类别中的文档数目
··· 对每篇训练文档:
······ 对每个类别:
········· 如果词条出现在文档中—>增加该词条的计数值
········· 增加所有词条的计数值
······ 对每个类别:
········· 对每个词条:
············ 将该词条对数目除以总词条数目得到条件概率
······ 返回每个类别对条件概率
程序清单 4-2 朴素贝叶斯分类器训练函数
"""
Created on Sep 11, 2018

@author: yufei
"""
# 参数:文档矩阵 trainMatrix,每篇文档的类别标签所构成的向量 trainCategory
def trainNB0(trainMatrix, trainCategory):
    numTrainDocs = len(trainMatrix) #文档的个数
    numWords = len(trainMatrix[0])  #获取第一篇文档的单词长度

    """
    计算文档属于侮辱性文档的概率
    用类别为1的个数除以总篇数
    sum([0,1,0,1,0,1])=3,也即是 trainCategory 里面 1 的个数
    """
    pAbusive = sum(trainCategory) / float(numTrainDocs)

    """
    初始化概率
    当利用贝叶斯分类器对文档分类时,计算多个概率的乘积以获得属于某个类别的概率
    把所有词出现次数初始化为1,分母初始化为2,用log避免数太小被约掉
    """
    p0Num = ones(numWords)
    p1Num = ones(numWords)

    p0Denom = 2.0
    p1Denom = 2.0

    # 遍历训练集 trainMatrix 中的所有文档
    for i in range(numTrainDocs):
        # 侮辱性词语在某个文档中出现
        if trainCategory[i] == 1:
            # 该词对应个数加一,即分子把所有的文档向量按位置累加
            # trainMatrix[2] = [1,0,1,1,0,0,0];trainMatrix[3] = [1,1,0,0,0,1,1]
            p1Num += trainMatrix[i]
            # 文档总词数加一,即对于分母
            # 把trainMatrix[2]中的值先加起来为3,再把所有这个类别的向量都这样累加起来,这个是计算单词总数目
            p1Denom += sum(trainMatrix[i])
        # 正常词语在某个文档中出现,同上
        else:
            p0Num += trainMatrix[i]
            p0Denom +=sum(trainMatrix[i])

    """
    对每个元素除以该类别的总词数,得条件概率
    防止太多的很小的数相乘造成下溢。对乘积取对数
    # p1Vect = log(p1Num / p1Denom)
    # p0Vect = log(p0Num / p0Denom)
    """
    
    p1Vect = p1Num / p1Denom
    p0Vect = p0Num / p0Denom

    """
    函数返回两个向量和一个概率
    返回每个类别的条件概率,是一个向量
    在向量里面和词汇表向量长度相同
    每个位置代表这个单词在这个类别中的概率
    """
    return p0Vect, p1Vect, pAbusive

在 python 提示符下,执行代码并得到结果:

>>> from numpy import *
>>> importlib.reload(bayes)

>>> list0Posts, listClasses = bayes.loadDataSet()
>>> myVocabList = bayes.createVocabList(list0Posts)

以上,调入数据后构建了一个包含所有词的列表myVocabList

>>> trainMat = []
>>> for postinDoc in list0Posts:
...     trainMat.append(bayes.setOfWords2Vec(myVocabList, postinDoc))

这个for循环使用词向量来填充trainMat列表。

继续给出属于侮辱性文档的概率以及两个类别的概率向量。

>>> p0V, p1V, pAb = bayes.trainNB0(trainMat, listClasses)

查看变量的内部值

>>> pAb
0.5
>>> p0V
array([0.03846154, 0.07692308, 0.03846154, 0.07692308, 0.07692308,
       0.07692308, 0.07692308, 0.03846154, 0.03846154, 0.03846154,
       0.07692308, 0.07692308, 0.15384615, 0.07692308, 0.07692308,
       0.07692308, 0.03846154, 0.07692308, 0.07692308, 0.07692308,
       0.07692308, 0.07692308, 0.03846154, 0.07692308, 0.11538462,
       0.07692308, 0.07692308, 0.03846154, 0.03846154, 0.03846154,
       0.07692308, 0.03846154])
>>> p1V
array([0.0952381 , 0.04761905, 0.0952381 , 0.0952381 , 0.14285714,
       0.04761905, 0.04761905, 0.0952381 , 0.0952381 , 0.14285714,
       0.04761905, 0.04761905, 0.04761905, 0.04761905, 0.04761905,
       0.04761905, 0.0952381 , 0.04761905, 0.04761905, 0.04761905,
       0.0952381 , 0.04761905, 0.0952381 , 0.04761905, 0.0952381 ,
       0.04761905, 0.04761905, 0.19047619, 0.0952381 , 0.0952381 ,
       0.04761905, 0.0952381 ])

我们发现文档属于侮辱类的概率pAb为 0.5,查看pV1的最大值 0.19047619,它出现在第 27 个下标位置,查看myVocabList的第 27 个下标位置该词为 stupid,说明这是最能表征类别 1 的单词。

测试算法:根据现实情况修改分类器 程序清单 4-3 朴素贝叶斯分类函数
"""
Created on Sep 11, 2018

@author: yufei
"""
# vec2Classify: 要分类的向量
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

def  testingNB():
    list0Posts, listClasses = loadDataSet()
    myVocabList = createVocabList(list0Posts)
    trainMat = []
    for posinDoc in list0Posts:
        trainMat.append(setOfWords2Vec(myVocabList, posinDoc))
    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))

在 python 提示符下,执行代码并得到结果:

>>> importlib.reload(bayes)

>>> bayes.testingNB()
["love", "my", "dalmation"] classified as:  0
["stupid", "garbage"] classified as:  1

分类器输出结果,分类正确。

准备数据:文档词袋模型

词集模型:将每个词的出现与否作为一个特征。即我们上面所用到的。
词袋模型:将每个词出现次数作为一个特征。每遇到一个单词,其词向量对应值 +1,而不是全设置为 1。

对函数setOfWords2Vec()进行修改,修改后的函数为bagOfWords2VecMN

程序清单 4-4 朴素贝叶斯词袋模型
def bagOfWords2VecMN(vocabList, inputSet):
    returnVec = [0] * len(vocabList)
    for word in inputSet:
        if word in inputSet:
            returnVec[vocabList.index(word)] += 1
    return returnVec

修改的地方为:每当遇到一个单词时,它会增加词向量中的对应值,而不只是将对应的数值设为 1。

下面我们将利用该分类器来过滤垃圾邮件。


示例:使用朴素贝叶斯过滤垃圾邮件 测试算法:使用朴素贝叶斯进行交叉验证 程序清单 4-5 文件解析及完整的垃圾邮件测试函数
"""
Created on Sep 11, 2018

@author: yufei
"""

"""
接受一个大字符串并将其解析为字符串列表
"""
def textParse(bigString):    #input is big string, #output is word list
    import re
    listOfTokens = re.split(r"W*", bigString)
    # 去掉小于两个字符的字符串,并将所有字符串转换为小写
    return [tok.lower() for tok in listOfTokens if len(tok) > 2]

"""
对贝叶斯垃圾邮件分类器进行自动化处理
"""
def spamTest():
    docList=[]; classList = []; fullText =[]
    #导入并解析文本文件为词列表
    for i in range(1,26):
        wordList = textParse(open("email/spam/%d.txt" % i, encoding="ISO-8859-1").read())
        docList.append(wordList)
        fullText.extend(wordList)
        classList.append(1)
        
        wordList = textParse(open("email/ham/%d.txt" % i, encoding="ISO-8859-1").read())
        docList.append(wordList)
        fullText.extend(wordList)
        classList.append(0)
    vocabList = createVocabList(docList)#create vocabulary
    trainingSet = list(range(50)); testSet=[]           #create test set
    
    for i in range(10):
        randIndex = int(random.uniform(0,len(trainingSet)))
        testSet.append(trainingSet[randIndex])
        del(trainingSet[randIndex])
        
    trainMat=[]; trainClasses = []
    
    # 遍历训练集的所有文档,对每封邮件基于词汇表并使用 bagOfWords2VecMN 来构建词向量
    for docIndex in trainingSet:#train the classifier (get probs) trainNB0
        trainMat.append(bagOfWords2VecMN(vocabList, docList[docIndex]))
        trainClasses.append(classList[docIndex])
    # 用上面得到的词在 trainNB0 函数中计算分类所需的概率
    p0V,p1V,pSpam = trainNB0(array(trainMat),array(trainClasses))
    errorCount = 0
    
    # 对测试集分类
    for docIndex in testSet:        #classify the remaining items
        wordVector = bagOfWords2VecMN(vocabList, docList[docIndex])
        # 如果邮件分类错误,错误数加 1 
        if classifyNB(array(wordVector),p0V,p1V,pSpam) != classList[docIndex]:
            errorCount += 1
            print ("classification error",docList[docIndex])
    # 给出总的错误百分比
    print ("the error rate is: ",float(errorCount)/len(testSet))
    #return vocabList,fullText

在 python 提示符下,执行代码并得到结果:

>>> importlib.reload(bayes)

>>> bayes.spamTest()
classification error ["home", "based", "business", "opportunity", "knocking", "your", "door", "don", "rude", "and", "let", "this", "chance", "you", "can", "earn", "great", "income", "and", "find", "your", "financial", "life", "transformed", "learn", "more", "here", "your", "success", "work", "from", "home", "finder", "experts"]
the error rate is:  0.1

函数spamTest()会输出在 10 封随机选择的电子邮件上的分类错误率。由于是随机选择的,所以每次的输出结果可能有些差别。如果想要更好地估计错误率,那么就应该将上述过程重复多次求平均值。

这里的代码需要注意的两个地方是:

1、直接使用语句 wordList = textParse(open("email/spam/%d.txt" % i).read()) 报错 UnicodeDecodeError: "utf-8" codec can"t decode byte 0x92 in position 884: invalid start byte。这是因为在文件里可能存在不是以 utf-8 格式保存的字符,需改为wordList = textParse(open("email/spam/%d.txt" % i, encoding="ISO-8859-1").read())

2、将随机选出的文档添加到测试集后,要同时将其从训练集中删除,使用语句 del(trainingSet[randIndex]),此时会报错 TypeError: "range" object doesn"t support item deletion,这是由于 python2 和 python3 的不同而导致的。在 python2 中可以直接执行,而在 python3 中需将 trainingSet 设为 trainingSet = list(range(50)),而不是 trainingSet = range(50),即必须让它是一个 list 再进行删除操作。


以上,我们就用朴素贝叶斯对文档进行了分类。


参考链接:
《机器学习实战》笔记之四——基于概率论的分类方法:朴素贝叶斯
UnicodeDecodeError: "utf-8" codec can"t decode byte 0x92 in position 884: invalid start byte

不足之处,欢迎指正。

文章版权归作者所有,未经允许请勿转载,若此文章存在违规行为,您可以联系管理员删除。

转载请注明本文地址:https://www.ucloud.cn/yun/42429.html

相关文章

  • 数据学系学习机器学习算法 # 西瓜学习记录 [5] 支持向量机实践

    摘要:本篇内容为机器学习实战第章支持向量机部分程序清单。支持向量机优点泛化错误率低,计算开销不大,结果易解释。注以上给出的仅是简化版算法的实现,关于完整的算法加速优化并应用核函数,请参照机器学习实战第页。 本篇内容为《机器学习实战》第 6 章 支持向量机部分程序清单。所用代码为 python3。 支持向量机优点:泛化错误率低,计算开销不大,结果易解释。 缺点:对参数调节和核函数的选择敏感,...

    RebeccaZhong 评论0 收藏0

发表评论

0条评论

leanxi

|高级讲师

TA的文章

阅读更多
最新活动
阅读需要支付1元查看
<