《machine learning in action》机器学习 算法学习笔记 基于概率论的分类方法:朴素贝叶斯

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

优点:在数据较少的情况下任然有效(但是准确率也属于一言难尽),可以处理多类别问题。
缺点:对于输入数据的方式较为敏感。
适用数据类型:标称型数据。

前置知识:条件概率,贝叶斯决策理论,相互独立

相互独立:相互独立是设A,B是两事件,如果满足等式 P ( A B ) = P ( A ) P ( B ) P(AB)=P(A)P(B) P(AB)=P(A)P(B),则称事件A,B相互独立,简称A,B独立。

条件概率: P ( g r e y ∣ b u c k e t B ) P(grey|bucketB) P(grey∣bucketB)表示的是在该球取自B桶的条件下,该球为灰色的概率。

条件概率计算方法
P ( B ∣ A ) = P ( A B ) P ( A ) P(B|A)=\frac{P(AB)}{P(A)} P(B∣A)=P(A)P(AB)​
贝叶斯准则:

通过条件概率公式代换,得到 P ( A ∣ B ) P(A|B) P(A∣B)​和 P ( B ∣ A ) P(B|A) P(B∣A)​之间的转换方法:
p ( A ∣ B ) = P ( B ∣ A ) P ( A ) P ( B ) p(A|B)=\frac{P(B|A)P(A)}{P(B)} p(A∣B)=P(B)P(B∣A)P(A)​
贝叶斯决策理论

核心思想:选择概率最高的决策

  • 如果 p 1 ( x , y ) > p 2 ( x , y ) p1(x,y)>p2(x,y) p1(x,y)>p2(x,y)​​,则(x,y)属于1类

  • 如果 p 1 ( x , y ) < p 2 ( x , y ) p1(x,y)<p2(x,y) p1(x,y)<p2(x,y),则(x,y)属于2类

综合以上的内容,最终的到贝叶斯分类准则

  • 如果 p ( c 1 ∣ x , y ) > p ( c 2 ∣ x , y ) p(c_1|x,y)>p(c_2|x,y) p(c1​∣x,y)>p(c2​∣x,y),那么(x,y)属于1类
  • 如果 p ( c 1 ∣ x , y ) < p ( c 2 ∣ x , y ) p(c_1|x,y)<p(c_2|x,y) p(c1​∣x,y)<p(c2​∣x,y)​,那么(x,y)属于2类

样本数:

在进行分类是,特征数决定着样本数,由统计学知,如果每个特征需要N个样本,那么对于M个特征需要 N M N^M NM个样本,该数量是指数爆炸的。

假如特征之间相互独立,那么所需要的样本数可以从 N M N^M NM下降到 N ∗ M N*M N∗M。

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

该方法中将单词作为特征,特征数巨大。

朴素“:

单词的分布实际上是相互影响的,一些特定语法,一些固定搭配。该方法采用以特征之间相互独立为前提,对文档进行分类,故为朴素,目的也是为了使用更少的样本。

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

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

根据贝叶斯分类准则,我们需要求得 P ( c 0 ∣ w ) P(c_0|w) P(c0​∣w)和 P ( c 1 ∣ w ) P(c_1|w) P(c1​∣w)​,其中w是由多个单词(特征)组成的词向量。

首先,由贝叶斯准则,的到:
P ( c 0 ∣ w ) = P ( w ∣ c 0 ) P ( C 0 ) P ( w ) P(c_0|w)=\frac{P(w|c_0)P(C_0)}{P(w)} P(c0​∣w)=P(w)P(w∣c0​)P(C0​)​
其次,由”朴素“假设得
P ( w ∣ c 0 ) = P ( w 1 , w 2 , … , w n ∣ c 0 ) = P ( w 1 ∣ c 0 ) P ( w 2 ∣ c 0 ) … P ( w n ∣ c 0 ) P(w|c_0)=P(w_1,w_2,\dots,w_n|c_0)=P(w_1|c_0)P(w_2|c_0)\dots P(w_n|c_0) P(w∣c0​)=P(w1​,w2​,…,wn​∣c0​)=P(w1​∣c0​)P(w2​∣c0​)…P(wn​∣c0​)
便可以得到以下伪代码:

计算每个类别中的文档数目
对每篇训练文档:
   	对每个类别:
    	如果词条出现文档中->增加该词条的计数值
    	增加所有词条的计数值
 对每个类别:
 	对每个词条:
 		将该词条的数目除以总词条数目得到条件概率
 返回每个类别的条件概率

该代码在实现过程中运用了numpy的特性。

并且本书中的一些地方也没有讲清楚:

如:

def classifyNB(vec2Classify, p0Vec, p1Vec, pClass1):
    p1 = sum(vec2Classify * p1Vec) + log(pClass1)  # element-wise mult
    p0 = sum(vec2Classify * p0Vec) + log(1.0 - pClass1)
    if p1 > p0:
        return 1
    else:
        return 0

计算p1,p2时为何要加log(),为什么是概率相加,不应该是相乘吗?

CSDN大佬给出了答案,对其进行吸收和整理:

在解决下溢问题(多个极小数相乘时,会出现精度消失)时,所以我们可以使用 l o g ( x ∗ y ) = l o g ( x ) + l o g ( y ) log(x*y)=log(x)+log(y) log(x∗y)=log(x)+log(y)这个特性。

之所以可以使用,是因为log函数,和概率函数的分布形态近视:

《machine learning in action》机器学习 算法学习笔记 基于概率论的分类方法:朴素贝叶斯

那么以上的代码就被很好的解释了。

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

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

利用正则表达式切分文本

import re
mySent='This book is the best book on python or M.L. i have ever laid eyes upon.'
regEx = re.compile('\\W')#\W表示非单词数字的字符
listOfTkens = regEx.split(mySent)#split()表示以mySent作为分割标志切割字符
to=[tok.lower() for tok in listOfTkens if len(tok)>0]#去掉长度为0的空串,且大小写不影响侮辱性
print(listOfTkens)
print(to)

留存交叉验证:从数据集中选择一定数量的作为测试集,剩余的用于训练。

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

收集数据:从RSS源收集内容,这里需要对RSS源构建一个接口
准备数据:将文本文件解析成词条向量。
分析数据:检查词条确保解析的正确性。
训练算法:使用我们之前建立的trainNB0()函数。
测试算法:观察错误率,确保分类器可用。
使用算法:构建一个完整的程序对一组文档进行分类,将错分的文档输出到屏幕上。

使用了去除出现次数最多的30个词的方式降低错误率,这是由于语言中大部分的冗余造成的。

bayes.py

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)  # union of the two sets,集合取并
    return list(vocabSet)

#-------------------检查待分类的词向量的特征是否都出现在词典中------------------------
def setOfWords2Vec(vocabList, inputSet):
    returnVec = [0] * len(vocabList)#列表的复制操作
    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):#前者为在字典中出现的文档词向量,后者为标签向量
    numTrainDocs = len(trainMatrix)
    numWords = len(trainMatrix[0])
    pAbusive = sum(trainCategory) / float(numTrainDocs)#P(c1)
    p0Num = ones(numWords)
    p1Num = ones(numWords)  # change to ones()
    p0Denom = 2.0
    p1Denom = 2.0  # 优化,由于是概率相乘,但有一个为0时,整体概率会为0,防止出现该现象,故将所有词的出现次数初始化为1,分母初始化为2
    for i in range(numTrainDocs):
        if trainCategory[i] == 1:
            p1Num += trainMatrix[i]#array类型,存放的是每个单词出现的次数,整体相加,方便
            p1Denom += sum(trainMatrix[i])
        else:
            p0Num += trainMatrix[i]#array类型,存放的是每个单词出现的次数,整体相加,方便
            p0Denom += sum(trainMatrix[i])
    p1Vect = log(p1Num / p1Denom)  # 解决下溢问题,当多个较小的数相乘时,数值会很小,会出现精度丢失问题,利用激活函数调整分布
    p0Vect = log(p0Num / p0Denom)  # change to log(),log函数与概率密度函数特征类似,且不会出现下溢问题。
    return p0Vect, p1Vect, pAbusive


def classifyNB(vec2Classify, p0Vec, p1Vec, pClass1):
    p1 = sum(vec2Classify * p1Vec) + log(pClass1)  # element-wise mult
    p0 = sum(vec2Classify * p0Vec) + log(1.0 - 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(thisDoc)
    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))

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

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).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)  # create vocabulary
    trainingSet = 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 = []
    for docIndex in trainingSet:  # train the classifier (get probs) trainNB0
        trainMat.append(bagOfWords2VecMN(vocabList, docList[docIndex]))
        trainClasses.append(classList[docIndex])
    p0V, p1V, pSpam = trainNB0(array(trainMat), array(trainClasses))
    errorCount = 0
    for docIndex in testSet:  # classify the remaining items
        wordVector = bagOfWords2VecMN(vocabList, docList[docIndex])
        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


def calcMostFreq(vocabList, fullText):
    import operator
    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):
    import feedparser
    docList = []
    classList = []
    fullText = []
    minLen = min(len(feed1['entries']), len(feed0['entries']))
    for i in range(minLen):
        wordList = textParse(feed1['entries'][i]['summary'])
        docList.append(wordList)
        fullText.extend(wordList)
        classList.append(1)  # NY is class 1
        wordList = textParse(feed0['entries'][i]['summary'])
        docList.append(wordList)
        fullText.extend(wordList)
        classList.append(0)
    vocabList = createVocabList(docList)  # create vocabulary
    top30Words = calcMostFreq(vocabList, fullText)  # remove top 30 words
    for pairW in top30Words:
        if pairW[0] in vocabList: vocabList.remove(pairW[0])
    trainingSet = range(2 * minLen)
    testSet = []  # create test set
    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:  # train the classifier (get probs) trainNB0
        trainMat.append(bagOfWords2VecMN(vocabList, docList[docIndex]))
        trainClasses.append(classList[docIndex])
    p0V, p1V, pSpam = trainNB0(array(trainMat), array(trainClasses))
    errorCount = 0
    for docIndex in testSet:  # classify the remaining items
        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


def getTopWords(ny, sf):
    import operator
    vocabList, 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**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**NY**NY**NY**")
    for item in sortedNY:
        print(item[0])

main.py

import re
import bayes
listOPosts,listClasses=bayes.loadDataSet()
myVocabList=bayes.createVocabList(listOPosts)
print(myVocabList)
print(bayes.setOfWords2Vec(myVocabList,listOPosts[0]))
trainMat = []
for postinDoc in listOPosts:
    trainMat.append(bayes.setOfWords2Vec(myVocabList,postinDoc))
print(trainMat)#得到的是每一句话在字典中的分布
p0v,p1v,pAb=bayes.trainNB0(trainMat,listClasses)
print(pAb)
print(p0v)
print(p1v)
bayes.testingNB()
mySent='This book is the best book on python or M.L. i have ever laid eyes upon.'
regEx = re.compile('\\W')#\W表示非单词数字的字符
listOfTkens = regEx.split(mySent)#split()表示以mySent作为分割标志切割字符
to=[tok for tok in listOfTkens if len(tok)>0]#去掉长度为0的空串
print(listOfTkens)
print(to)

结语:

  • 利用概率论的知识对事物进行分类是一种很自然的想法,基于统计学的知识,取得出一个结果,这也是对数理知识的一种应用。
  • 再改模型中使用了特征之间的条件独立的假设,这也是能够快速求得概率的原因,虽然与事实不符,但在特定的数据集上表现十分好,可以将该方法作为模型化简方法之一。
  • 根据语言的冗余性,移除出现频次高的词汇,反而降低了错误率,是一种很好的数据预处理的方法。
  • 通过使用log( )函数来解决乘数下溢问题十分有技巧性。
上一篇:CSAPP阅读笔记(二):比特、字节和整数


下一篇:【功能开发】查看程序循环时间