目录

引言

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

二、条件概率

三、使用条件概率来分类 

四、使用朴素贝叶斯进行文档归类

五、利用python进行文本分类

5.1 准备数据:从文本中构建词向量

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

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

六、 使用朴素贝叶斯过滤垃圾邮件 

6.1 准备数据:切分文本

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


引言

        概率论是许多机器学习算法的基础,在之前的学习中计算特征值取某个值的概率时就涉及到了一些概率知识,先统计特征在数据集中取某个特定值的次数,然后除以数据集的实例总数,就得到特征取该值的概率。

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

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

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

        适用数据类型:标称型数据

贝叶斯决策理论 

        朴素贝叶斯是贝叶斯决策理论的一部分,假设有一个数据集由两类数据组成如图1:

图1  两个参数已知的概率分布,参数决定了分布的形状

        假设找到了描述图中的两类数据的统计参数,利用p1(x,y)表示数据点(x,y)属于类别1(图1中用原点表示)的概率,用p2(x,y)表示数据点(x,y)属于类别2(图中用三角形表示)的概率。对于新的数据点(x,y),用以下规则判定其类别:

                p1(x,y) > p2(x,y),类别为1                        p2(x,y) > p1(x,y),类别为2 

        贝叶斯决策理论的核心就如上面两个的判断一样,选择具有最高概率的决策。

二、条件概率

        条件概率p(x,y|c_{1}),借图2中的例子加以解析:

图2  黑白球置于两桶,计算概率

        记黑球和白球的取出概率分别为P_{1},P_{2},假设计算的是从B桶中取到白色球记为P(白|B),也可以称为在已知球出自B桶的条件下取出白球的概率,我们不难得出P(白|A) = 1/2,P(白|B) = 1/3,条件概率的计算方式为:

P(白|B) = P(白B)/P(B)

        其中P(白B)为B桶中白球个数除以两个桶中总的球数,为1/7,P(B)为B桶中球的个数占总数的比例为3/7。故可得P(白|B) = 1/3。

        另一种有效的计算条件概率的方法为贝叶斯准则,若已知p(x|c),要求p(c|x),使用以下计算公式:

三、使用条件概率来分类 

        先前提及了贝叶斯决策理论计算两个概率p1(x,y),p2(x,y)只是尽可能简化的描述,实际上需要计算和比较的p(c_{1}|x,y),p(c_{2}|x,y)。符号指代给定某个由x、y表示的数据点由此就要对该数据点来自类别c1和c2的概率分别是多少产生讨论,具体地,应用贝叶斯准则得到:

        由该准则就能对之前的对比进行改进:

p(c_{1}|x,y)>p(c_{2}|x,y),属于类别c_{1}p(c_{1}|x,y)<p(c_{2}|x,y),属于类别c_{2} 

我们可以通过已知的三个概率值计算未知的概率值。

四、使用朴素贝叶斯进行文档归类

        机器学习的一个重要应用就是文档的自动分类,在文档分类中整个文档是实例,电子邮件中的某些元素则构成特征。虽然电子邮件是一种会不断增加的文本,但可以对类似新闻报道、用户留言、政府公文等其他任意类型的文本进行分类。通过观察文档中出现的词,并把每个词的出现或不出现作为一个特征,这样得到的特征数目就会跟词汇表中的词目一样多。

        朴素贝叶斯的一般过程:

        1、收集数据:可以使用任何方法。这里使用的是机器学习实战给出的RSS源

        2、准备数据:需要数值型或布尔型数据     

        3、分析数据:有大量特征时,绘制特征作用不大,此时使用直方图效果会更好

        4、训练算法:计算不同的独立特征的条件概率

        5、使用算法:一个常见的朴素贝叶斯应用是文档分类。当然朴素贝叶斯分类可以适用于任意的分类场景并非一定是文本

        对于有1000个单词的词汇表,若想得到好的概率分布就要足够多的样本数据,以N为样本数,由统计学可以知道如果每个特征需要N个样本,那么对于10个特征将需要N^{10}个样本,当特征数越来越大时,其所需要的样本数将迅速增长。

        当特征之间相互独立时样本数会随之减少,以1000个特征值为例,若特征之间不相互独立样本数就将从N^{1000}减少到1000*N。这里所说的独立是指一个特征出现的可能性与任何事物都无关。朴素贝叶斯分类器的另一个假设就是每个特征同等重要。

五、利用python进行文本分类

        从文本中获取特征,首先需要先拆分文本,特征来源于文本中的词条,一个词条是字符的任意组合,可以将词条假设为单词,也可以使用非单词词条,例如URL、IP地址或者其他的字符串。将每一个文本片段表示为一个词条向量,其中值为1时表示词条出现在文本中,0时则表示词条未出现。

5.1 准备数据:从文本中构建词向量

        将文本中的句子转换成向量,考虑出现在所有文档中的所有单词,再决定将哪些词纳入词汇或者说所要的词汇集合。必须要将每一篇文档转换成词汇表上的向量。创建bayes.py,添加函数:

def loadDataSet():
    postingList = [['my', 'dog', 'has', 'flea', 'problem', 'help', 'please'],
                   ['maybe', 'not', 'take', 'him', 'to', 'dog', 'park', 'stupid'],
                   ['my', 'delamation', '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([])  # set集合中元素互不相同,例如set("123")输出就为{‘1’,‘2’,‘3’}或set[1,2,3]输出就为{1,2,3}
    for doccument in dataSet:
        vocabSet = vocabSet | set(doccument)  # vocabSet返回的是其与set(doucument)之间的并集
    return list(vocabSet)  # 返回列表


def setOfWords2Vec(vocabList, inputSet):
    returnVec = [0] * len(vocabList)  # 返回以vocabList的长度为个数的0列表
    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()创建了一些实验样本,这个函数返回的第一个变量是进行词条切分后的文档集合,loadDataSet()函数返回的第二个变量是一个类别标签的集合,有两个类别:侮辱性和非侮辱性,文本的类别由人工标注,这些标注信息用于训练程序以便自动检测侮辱性留言。

        createVocabList()会创建一个包含再所有文档中出现的不重复词的列表,set()可以很方便的返回一个不重复的词表。创建一个空集合,将每篇文档返回的新词集合添加到该集合中。

        在获得词汇表后,使用setOfWords2Vec(),这个函数实现的是输入参数为词汇表及某个文档,输出的是文档向量,向量中的元素为0或1,用以表示词汇表中单词是否出现在输入文档中。

测试:

import bayes

listofposts, listClasses = bayes.loadDataSet()
myvocablist = bayes.createVocabList(listofposts)
print(myvocablist)
test1 = bayes.setOfWords2Vec(myvocablist, listofposts[0])
test2 = bayes.setOfWords2Vec(myvocablist, listofposts[3])
print(test1)
print(test2)

执行框中的结果显示如下: 

        使用词汇或者想要检查的所有单词作为输入,然后为其中的每一个单词构建一个特征,一旦给定一篇文档,该文档就会被转换成为词向量。

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

        重写贝叶斯准则,将之前的x,y替换成w,这里的w进行了加粗就代表着一个向量,由多个数组组成。

        使用上述公式,对每一个类计算该值,并比较这两个概率值的大小,通过类别i中文档数除以总的文档数来计算概率p(c_{1}),之后就是计算p(w|c_{1}),这里需要用到朴素贝叶斯假设。将w展开成为一个个独立特征则可以将上述的概率调整为:p(w_{0},w_{1},w_{2}..w_{N}|c_{1})。而各个特征之间又是相互独立的所以也可以用p(w_{0}|c_{1}),p(w_{1}|c_{1})...p(w_{N}|c_{1})来计算上述概率。

利用python实现上述过程:

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
            

        输入参数为文档矩阵trainmatrix,以及由文档类别标签所构成的向量trainCategory。计算文档属于侮辱性文档的概率即p1,这里只有两个类别因此计算中就可以通过概率和为1解出另一个类的出现概率,对于多于两类的问题则要另外的代码修饰。

        测试:

list0posts, listclasses = bayes.loadDataSet()  # 将bayes.py中的测试数据赋值给list0posts,listclasses
myvocablist = bayes.createVocabList(list0posts)  # 以list0posts为变量创建字母表
trainmat = []  # 创建空列表用以存放数据
for postinDoc in list0posts:  # 遍历list0posts中的数据,存放出现文档内容与出现在字母表中的数据
    trainmat.append(bayes.setOfWords2Vec(myvocablist,postinDoc))
p0v,p1v,pAb = bayes.trainNB0(trainmat,listclasses)  # 对符合条件的文档进行分类并计算
print(p0v,"\n",p1v)
图3 概率计算

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

        利用贝叶斯分类器对文档进行分类时,要计算多个概率的乘积以获得文档属于某个类别的概率,也就是计算p(w_{0}|1),p(w_{1}|1)p(w_{2}|1),若其中一个的概率为0,那么最后的乘积也是0,因此为了降低这种影响将所有词的出现数初始化为1,并将分母初始化为2,更改代码为:

    p0num = ones(numwords); p1num = ones(numwords)  
    p0Denom = 2.0; p1Denom = 2.0

还有一个问题就是从上面图3的计算结果我们可以看到其结果都很小,容易导致下溢出或者得不到正确的答案,采用对乘积取自然对数的方式是解决该问题的一种方法。

    p1Vect = log(p1num / p1Denom)
    p0Vect = log(p0num / p0Denom)

构建朴素贝叶斯分类函数:

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 postindoc in list0posts:
        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))

第一个函数有4个输入,分别为要分类的向量vec2Classify,使用函数trainNB0()计算出来的三个概率。利用数组计算两个向量相乘的结果,相乘为对应元素相乘。 第二个函数为便利函数,封装所有操作,以节省时间:

测试:

bayes.testingNB()

得出结果:

将每个词的出现与否作为一个特征,这可以被描述为词集模型。如果一个词在文档中出现了不止一次这可能意味着包含该词的=是否出现在文档中所不能表达的某种信息,这种方法被称为词袋模型,词袋中每个单词可以出现多次,而词集中,每个词只能出现一次。词袋模型的代码如下:

def bagofwords2vecmn(vocablist, inputset):
    returnvec = [0] * len(vocablist)
    for word in inputset:
        if word in inputset:
            returnvec[vocablist.index(word)] += 1
    return returnvec

六、 使用朴素贝叶斯过滤垃圾邮件 

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

        1、收集数据:提供文本文件

        2、准备数据:将文本文件解析成词条向量

        3、分析数据:检查词条确保解析的正确性

        4、训练算法:使用之前建立的trainNB0()函数

        5、测试算法:使用classifyNB(),并且构建一个新的测试函数来计算文档集的错误率

        6、使用算法:构建一个完整的程序对一组文档进行分类,将错分的文档输出到屏幕上。

6.1 准备数据:切分文本

        python中可以使用split()方法对文本字符串进行切分。例如:

mysent = 'this is the best book on python or m.l. i have ever laid eyes upon.'
print(mysent.split())

从结果上看切分的不错,但标点符号也被当成了词的一部分,使用正则表示式来切分句子,分隔符是除单词、数字外的任意字符串。机器学习实战一书中给出的代码为:

mysent = 'this is the best book on python or m.l. i have ever laid eyes upon.'

regex = re.compile('\\W*')
listoftokens = regex.split(mysent)
print(listoftokens)

由于python版本的问题,输出的是一个一个字符而非单词,需要将代码改为:

mysent = 'this is the best book on python or m.l. i have ever laid eyes upon.'

regex = re.compile('\\W+')
listoftokens = regex.split(mysent)
print(listoftokens)

此时的输出结果是:

出现的新问题就是空字符也被列了出来,要将空字符串去掉可以计算每个字符串的长度,只返回长度大于0的字符串。

mysent = 'this is the best book on python or m.l. i have ever laid eyes upon.'

regex = re.compile('\\W+')
listoftokens = regex.split(mysent)
result = [tok.lower() for tok in listoftokens if len(tok) > 0]
print(result)

结果为:

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

def textParse(bigString):  # 将字符串转换为字符列表
    listOfTokens = re.split(r'\W+', bigString)  # 将特殊符号作为切分标志进行字符串切分,即非字母、非数字
    return [tok.lower() for tok in listOfTokens if len(tok) > 2]  # 除了单个字母,例如大写的I,其它单词变成小写,同时对长度大于2的字符串进行切分


# 函数说明:测试朴素贝叶斯分类器
def spamTest():
    docList = []; classList = []; fullText = []
    for i in range(1, 26):  # 遍历25个txt文件
        wordList = textParse(open('email/spam/%d.txt' % i, 'r').read())  # 读取每个垃圾邮件,并字符串转换成字符串列表
        docList.append(wordList)
        fullText.append(wordList)
        classList.append(1)  # 标记垃圾邮件,1表示垃圾文件
        wordList = textParse(open('email/ham/%d.txt' % i, 'r').read())  # 读取每个非垃圾邮件,并字符串转换成字符串列表
        docList.append(wordList) 
        fullText.append(wordList)
        classList.append(0)  # 标记非垃圾邮件,1表示垃圾文件
    vocabList = createVocabList(docList)  # 创建词汇表,不重复
    trainingSet = list(range(50)); testSet = []	 # 创建存储训练集的索引值的列表和测试集的索引值的列表
    for i in range(10):  # 从50个邮件中,随机挑选出40个作为训练集,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(np.array(trainMat), np.array(trainClasses))  # 训练朴素贝叶斯模型
    errorCount = 0  # 错误分类计数
    for docIndex in testSet:  # 遍历测试集
        wordVector = setOfWords2Vec(vocabList, docList[docIndex])  # 测试集的词集模型
        if classifyNB(np.array(wordVector), p0V, p1V, pSpam) != classList[docIndex]:  # 如果分类错误
            errorCount += 1                                               # 错误计数加1
            print("分类错误的测试集:",docList[docIndex])
    print('错误率:%.2f%%' % (float(errorCount) / len(testSet) * 100))

        第一个函数用以接受一个大字符串并将其解析为字符串列表,该函数去掉少于两个字符的字符串,并将所有的字符转换成小写,第二个函数对贝叶斯垃圾邮件分类器进行自动化处理,并导入spam和ham下的文本文件,解析为词列表,构建一个测试集与一个训练集,两个集合中的邮件均为随机选出。

        对上述过程进行尝试:

print(bayes.spamTest())

因为是随机抽取10封电子邮件,因此其结果也会出现不同情况,以下为出现的两种情况:

当出现错误时,函数会输出错分文档的此表,可以便于了解到哪篇文档出现了错误。

Logo

技术共进,成长同行——讯飞AI开发者社区

更多推荐