读书笔记:机器学习实战【第4章:朴素贝叶斯】

来源:互联网 发布:淘宝认证 编辑:程序博客网 时间:2024/05/17 09:11

读书笔记:机器学习实战【第4章:朴素贝叶斯】

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

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

朴素贝叶斯是贝叶斯决策理论的一部分,贝叶斯决策理论的核心思想是选择具有最高概率的决策。

4.2 条件概率

4.3 使用条件概率分类

基本都是最基本的概率论知识,跳过。

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

由统计学知,如果每个特征需要N个样本,则对于10个特征就需要N 10  个样本,对于包含1000个特征的词汇表需要N 1000  个样本。可以看到,所需要的样本数量,会随着特征数目的增大而迅速增长。

而如果特征之间相互独立,样本数就可以从N 1000  减少到1000*N,所谓独立指的是统计意义上的独立,即一个特征或者单词出现的可能性与其他特征或单词没有关系,这个假设正是朴素贝叶斯分类器中朴素一词的含义,而朴素贝叶斯分类器中的另一个假设是,每个特征同等重要。

4.5 使用Python进行文档归类

要从文本中获取特征,就需要先拆分文本,这里的特征是来自文本的词条,一个词条是字符的任意组合,可以把词条想象成单词,也可以使用非单词的词条(如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,classVecdef createVocabList(dataSet): #创建词汇集合    vocabSet = set([]) #创建一个空集    for document in dataSet:        vocabSet = vocabSet | set(document) #对每行创建集合,计算这一行和vocabSet的并集,即把每一行的都算进去。    return list(vocabSet)def setOfWords2Vec(vocabList,inputSet): ##把词汇列表(其实就是文档)转变为向量    returnVec = [0]*len(vocabList) ##创建词汇表长度的全0向量    for word in inputSet:#对文档中的所有单词        if word in vocabList: #如果单词曾经出现在词汇表中:            returnVec[vocabList.index(word)] = 1 ##vocabList.index(word)是该word在vocabList中出现的位置,因为在该位置出现,则向量returnVec的对应位置表示为1。说明这个位置的单词,在已有词汇表出现。        else: print "the word %s is not in my Vocabulary!" % word    return returnVec #最终返回的是一组根据词汇表产生的向量

接下来,监测函数的执行效果:

In [2]: list0Posts,listClasses = loadDataSet()In [3]: myVocabList = createVocabList(list0Posts)In [4]: myVocabListOut[4]: ['cute', 'love', 'help', 'garbage', 'quit', 'I', 'problems', 'is', 'park', 'stop', 'flea', 'dalmation', 'licks', 'food', 'not', 'him', 'buying', 'posting', 'has', 'worthless', 'ate', 'to', 'maybe', 'please', 'dog', 'how', 'stupid', 'so', 'take', 'mr', 'steak', 'my']

可以看到,获得的是一组不重复的单词表。

接下来,再看看生成词汇向量的运行效果:

In [6]: setOfWords2Vec(myVocabList,list0Posts[0])Out[6]: [0, 0, 1, 0, 0, 0, 1, 0, 0, 0, 1, 0, 0, 0, 0, 0, 0, 0, 1, 0, 0, 0, 0, 1, 1, 0, 0, 0, 0, 0, 0, 1]

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

根据贝叶斯准则:

p(c i |w)=p(w|c i )p(c i )p(w)  

我们根据上述公式,我们对每个类计算该概率值,然后比较概率的大小。具体计算方法是这样:首先通过类别i(即0或1,侮辱或非侮辱)中文档的数量除以总的文档数量来计算概率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):##输入:文档矩阵,类别标签0-1向量    numTrainDocs = len(trainMatrix) #文档数    numWords = len(trainMatrix[0]) #每个文档的单词数,其实既然输入了矩阵,应该就是特征向量长度    pAbusive = sum(trainCategory)/float(numTrainDocs) ##这个其实就是类别为1的频率,即侮辱性语言出现的概率    p0Num = zeros(numWords);p1num = zeros(numWords) ##初始化0类别和1类别各个词汇的数目为全0    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 ##获得了两个类别的概率向量,以及类别1的概率

下面尝试验证代码:

In [9]: trainMat = []In [10]: for postinDoc in list0Posts:    ...:     trainMat.append(setOfWords2Vec(myVocabList,postinDoc))    ...:     

这样,我们获得的是一组文档的特征向量,即处理完成的数据。

In [14]: p0V,p1V,pAb=trainNB0(trainMat,listClasses)In [15]: pAbOut[15]: 0.5In [16]: p0VOut[16]: array([ 0.04166667,  0.04166667,  0.04166667,  0.        ,  0.        ,        0.04166667,  0.04166667,  0.04166667,  0.        ,  0.04166667,        0.04166667,  0.04166667,  0.04166667,  0.        ,  0.        ,        0.08333333,  0.        ,  0.        ,  0.04166667,  0.        ,        0.04166667,  0.04166667,  0.        ,  0.04166667,  0.04166667,        0.04166667,  0.        ,  0.04166667,  0.        ,  0.04166667,        0.04166667,  0.125     ])In [17]: p1VOut[17]: array([ 0.        ,  0.        ,  0.        ,  0.05263158,  0.05263158,        0.        ,  0.        ,  0.        ,  0.05263158,  0.05263158,        0.        ,  0.        ,  0.        ,  0.05263158,  0.05263158,        0.05263158,  0.05263158,  0.05263158,  0.        ,  0.10526316,        0.        ,  0.05263158,  0.05263158,  0.        ,  0.10526316,        0.        ,  0.15789474,  0.        ,  0.05263158,  0.        ,        0.        ,  0.        ])

使用该函数进行分类之前,还需要解决函数中的一些缺陷

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

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

因此,对trainNB0()中的第4、5行代码进行修改:

另一个要处理的问题是下溢出,这是由于太多很小的数相乘所造成的,为了解决下溢出问题可以采用对数,把return前的两行代码修改为:

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-pClass1)  ##加对数概率等于乘以概率    if p1 > p0:        return 1    else :        return 0def testingNB():    list0Posts,listClasses = loadDataSet()    myVocabList = createVocabList(list0Posts)    trainMat = []    for postinDec in list0Posts:        trainMat.append(setOfWords2Vec(myVocabList,postinDec))    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)

第一个函数是分类函数,第二个函数是方便起见的封装函数。

效果如下:

In [31]: testingNB()['love', 'my', 'dalmation'] classified as: 0['stupid', 'garbage'] classified as: 1

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

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

这里,对函数setOfWords2Vec稍加修改:

def bagOfWords2VecMN(vocabList,inputSet):    returnVec = [0]*len(vocabList)    for word in inputSet:        if word in vocabList:            returnVec[vocabList.index(word)] += 1    return returnVec

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

4.6.1 准备数据:切分文本

下面介绍如何从文本文档中构建自己的词列表。

对于一个文本字符串,可以使用Python的string.split()方法切分,下面看看实际的运行效果:

In [35]: mySent = 'This book is the best book on Python or M.L. I have ever laid eyes upon.'In [36]: mySent.split()Out[36]: ['This', 'book', 'is', 'the', 'best', 'book', 'on', 'Python', 'or', 'M.L.', 'I', 'have', 'ever', 'laid', 'eyes', 'upon.']

可以看到切分的效果不错,但问题在于,标点符号也被当成了词的一部分。
为此,可以使用正则表达式切分,把分隔符定位除了单词数字外的任意字符串:

In [37]: import reIn [38]: regEx = re.compile('\\W*')In [39]: listOfTokens = regEx.split(mySent)In [40]: listOfTokensOut[40]: ['This', 'book', 'is', 'the', 'best', 'book', 'on', 'Python', 'or', 'M', 'L', 'I', 'have', 'ever', 'laid', 'eyes', 'upon', '']

关于正则表达式可以看:http://blog.csdn.net/drdairen/article/details/51134816

现在得到了一系列词组成的词表,但是里面的空字符串需要去掉,可以计算每个字符串的长度,只返回长度大于0的字符串:

In [41]: [tok for tok in listOfTokens if len(tok)>0]Out[41]: ['This', 'book', 'is', 'the', 'best', 'book', 'on', 'Python', 'or', 'M', 'L', 'I', 'have', 'ever', 'laid', 'eyes', 'upon']

最后,我们发现句子中的第一个单次是大写的,如果目的是句子查找,那么这个特点会很有用,但是这里的文本只看成词袋,所以我们希望所有词的形势都是统一的。

Python有一些内嵌的方法可以将字符串全部转换成大写或小写,于是,这里使用.lower():

In [42]: [tok.lower() for tok in listOfTokens if len(tok)>0]Out[42]: ['this', 'book', 'is', 'the', 'best', 'book', 'on', 'python', 'or', 'm', 'l', 'i', 'have', 'ever', 'laid', 'eyes', 'upon']

4.6.2 使用朴素贝叶斯进行交叉验证

下面将文本解析器集成到一个完整的分类器中。

def textParse(bigString): #文本拆分    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).read())        docList.append(wordList)        fullText.extend(wordList)        classList.append(1)        wordList = textParse(open('email/ham/%d.txt' % i).read())        docList.append(wordList)        fullText.extend(wordList)        classList.append(0)         vocabList = createVocabList(docList)    trainingSet = range(50); testSet=[] #生成训练集编号list和测试集编号list(为空)    for i in range(10): ##进行10次筛选,每次选出一个样本加入测试集,从训练集剔除        randIndex = int(random.uniform(0,len(trainingSet))) #随机生成一个序号        test.append(trainingSet[randIndex])        del(trainingSet[randIndex]) ##被选中序号加入测试集,从训练集开除    trainMat = [];trainingClasses = []    for docIndex in trainingSet:        trainMat.append (setOfWords2Vec(vocabList,docList[docIndex]))        trainingClasses.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 'Classification error doc:', docList[docIndex]    print 'the error rate is :',float(errorCount)/len(testSet)

实验结果如下:

In [95]: spamTest()the error rate is : 0.0In [96]: spamTest()Classification error doc: ['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']
阅读全文
0 0
原创粉丝点击