机器学习实战4--朴素贝叶斯

分类器在进行分类的时候会给出一个最优的类别猜测结果,同时给出这个猜测的概率估计值。若p1>p2,那么属于类别1,反之属于类别2。
朴素贝叶斯属于贝叶斯决策理论的一部分:贝叶斯准则是计算条件概率的方法,若已知P(x|c),要求P(c|x),则p(c|x)=p(x|c)p(c)/p(x)。
p(c1|x,y)表示给定某个由x,y表示的数据点,那么该数据点来自c1的概率是多少。利用贝叶斯准则求得p(x,y|c1),就可以得到x,y表示的点属于c1的概率。若是大于属于c2的概率,则这个点就属于c1。
利用朴素贝叶斯进行文档分类一般过程:
收集数据,把数据做成数值型或者布尔型数据,分析数据时因为有大量的特征,绘制特征不好使,使用直方图比较好。计算不同的独立特征的条件概率,对测试数据计算错误率,得到文档分类。

独立的特征是指统计意义上的独立,即一个特征或者单词出现的可能性与它和其他单词相邻没有关系。若是1000个单词,则样本数就从N的1000次方,减少到1000xN。这也是朴素贝叶斯的一个假设,不同的特征同等重要。
1:要想从文本中获取特征,就要先拆分文本。这里首先给出将文本转换为数字向量的过程,将每一个文本片段表示为一个词条向量,值为1表示词条出现在文档中,0表示词条未出现。在此对类别标签设置为侮辱类和非侮辱类,使用0和1表示。

#-*- coding:utf-8 -*-
from numpy import *
#适用于标称型数据,要求分类器给出一个最优的类别猜测结果
def loadDataSet():#创建一个实验样本,以及这些样本的标签;用于训练
    postingList = [['my','dog','has','flea','problem','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

创建一些实验文本,返回的第一个变量是进行词条切分后的文档集合,文本被切分成一系列的词条集合,标点符号从文本中去掉,第二个是类别标签的集合。
2:创建一个包含在所有文档中出现的不重复词的列表,使用set数据类型,set将返回一个不重复词表。

def createVocabList(dataSet):#创建一个包含在所有文档中出现的不重复词的列表,使用set数据集合。
    vocabSet = set([])
    for document in dataSet:
        vocabSet = vocabSet | set(document)
    return list(vocabSet)#就是把原来的样本全部放入一个集合中,输出列表,就是词汇表

3:使用词汇表或者想要检查的所有单词作为输入,然后为其中每个单词构建一个特征,一旦给定一篇文档,该文档将自动转化为词向量。

def setOfWords2Vec(vocabList,inputSet):#输入第一个是词汇表,就是上面那个生成的,第二个是一个文档
    returnVec = [0]*len(vocabList)#初始化一个和词汇表相同大小的向量;
    for word in inputSet:#对文档中每个单词
        if word in vocabList:#如果这个单词在词汇表中,就使返回向量中这个单词在词汇表的位置的那个值为1
            returnVec[vocabList.index(word)] = 1
        else:print "the word:%s is not in my Vocabulary!" %word#否则打印不在列表中
    return returnVec

这里写图片描述
4:上面将一组单词转化为一组数字,接下来就是如何使用这些数字计算概率。由以上的程序可知,一个词是否出现在一篇文档中,也知道该文档所属的类别。
首先通过类别i(侮辱或非侮辱)中文档数除以总的文档数来计算概率p(ci),然后计算p(x|ci),这里用到朴素贝叶斯假设。如果将x展开为一个个独立的特征,那么就可以将上述概率写成p(x0,x1,x2…..xn|ci),所有词都互相独立,则称作条件独立性假设,意味着可以使用p(x0|ci)p(x1|ci)p(x2|ci)…..p(xn|ci)来计算上述概率。

#当利用贝叶斯分类器对文档分类时,计算多个概率的乘积以获得属于某个类别的概率,把所有词出现次数初始化为1,分母初始化为2,用log避免数太小被约掉
def trainNB0(trainMatrix,trainCategory):#输入参数是文档矩阵trainMatrix,以及每篇文档类别标签所构成的向量。
    numTrainDocs = len(trainMatrix)#获取在测试文档矩阵中有几篇文档
    numWords = len(trainMatrix[0])#获取第一篇文档的单词长度
    pAbusive = sum(trainCategory)/float(numTrainDocs)#类别为1的个数除以总篇数,就得到某一类文档在总文档数中所占的比例
    p0Num = ones(numWords);p1Num = ones(numWords)#初始化求概率的分子变量和分母变量,
    p0Denom = 2.0;p1Denom = 2.0  #这里防止有一个p(xn|1)为0,则最后的乘积也为0,所有将分子初始化为1,分母初始化为2。
    #p0Num = zeros(numWords);p1Num = zeros(numWords)
    #p0Denom = 0.0;p1Denom = 0.0
    for i in range(numTrainDocs):#对每一篇训练文档
        if trainCategory[i] == 1:#如果这篇文档的类别是1
            p1Num += trainMatrix[i]#分子就把所有的文档向量按位置累加,trainMatrix[2] = [1,0,1,1,0,0,0];trainMatrix[3] = [1,1,0,0,0,1,1]
            p1Denom += sum(trainMatrix[i])#这个是分母,把trainMatrix[2]中的值先加起来为3,再把所有这个类别的向量都这样累加起来,这个是计算单词总数目
        else:
            p0Num += trainMatrix[i]#
            p0Denom += sum(trainMatrix[i])
    #防止太多的很小的数相乘造成下溢。对乘积取对数。
    p1Vect =log(p1Num/p1Denom)#change to log()对每个类别的每个单词的数目除以该类别总数目得条件概率
    p0Vect =log(p0Num/p0Denom) #change to log()#返回每个类别的条件概率,不是常数,是向量,在向量里面是和词汇表向量长度相同,每个位置代表这个单词在这个类别中的概率
    return p0Vect,p1Vect,pAbusive #返回两个向量和一个概率

这里写图片描述
p0Vect是一个向量,元素是每个词在该类别中出现的概率。p1Vect同上。
pAbusive是文档属于1的概率。
5:构建完整的分类器,就是编写分类函数。

def classifyNB(vec2Classify,p0Vec,p1Vec,pClass1):#输入是要分类的向量,使用numpy数组计算两个向量相乘的结果,对应元素相乘,然后将词汇表中所有值相加,将该值加到类别的对数概率上。比较分类向量在两个类别中哪个概率大,就属于哪一类
    p1 = sum(vec2Classify*p1Vec) + log(pClass1)
    p0 = sum(vec2Classify*p0Vec) + log(1-pClass1)
    if p1 > p0:
        return 1
    else:
        return 0

def testingNB():#这是一个测试函数
    listOPosts,listClasses = loadDataSet()#1
    myVocabList = createVocabList(listOPosts)#2
    trainMat = []
    for postinDoc in listOPosts:#3
        trainMat.append(setOfWords2Vec(myVocabList,postinDoc))#这个是把训练文档中所有向量都转换成和词汇表类似的1,0结构
    p0V,p1V,pAb = trainNB0(array(trainMat),array(listClasses))#4,经过四步就把训练样本训练好了,得到了想要的概率
    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)

这里写图片描述

6:现在将每个词的出现与否作为一个特征,这被描述为词集模型,但一个词在文档中出现不止一次,就要使用词袋模型,在词袋中,每个单词可以出现多次。修改setOfWord2Vec():

def bagOfWords2VecMN(vocabList,inputSet):#这是词袋功能,就是若相同就加一,可以计算有几个相同的,上面那个只是相同就等于1,对一个文档中有相同词不好操作
    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

7:一个实例,过滤垃圾邮件:
在准备数据时,对一个文本字符串,可以使用string.split()将其切分成词向量。
regEx = re.compile(‘\W*’)
listOfTokens = regEx.split(mySent)可以去除标点符号,并计算每个字符串的长度,返回大于0的字符串,去除空格。
python中.lower()和.upper()将字符串全部转换成小写或大写。
下面是文件解析和完整的垃圾邮件测试函数:

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):#在文件夹中只有25个文件
        wordList = textParse(open('email/spam/%d.txt' %i).read())#读文本文档分割字符串,得到这个文档所有单词元素的列表
        docList.append(wordList)#把每个文本文档都加入到一个大的矩阵里面
        fullText.extend(wordList)#这个是把文本文档中所有元素都拉出来,做一个大向量
        classList.append(1)#在这个文件夹中,类别标签都是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=[]
    for i in range(10):#就是随机挑出10个做为测试文本,其余的作为训练样本
        randIndex = int(random.uniform(0,len(trainingSet)))
        testSet.append(trainingSet[randIndex])
        del(trainingSet[randIndex])
    trainMat=[];trainClasses = []
    for docIndex in trainingSet:#docIndex是0-50的数,去掉了作为测试文档的10个随机数字
        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 "error classify email is:%f.txt " % ((docIndex+1)/2.0)
            print docList[docIndex] 
    print 'the error rate is: ',float(errorCount)/len(testSet)

就是从50个文本中选择10个作为测试样本,其余作为训练样本。进行测试。

8:分析两个城市广告用于,分类这两个城市的特点用词。
安装RSS阅读器,下载文本。

#一下的程序需要下载RSS程序库feedparser
def calcMostFreq(vocabList,fullText):
    import operator
    freqDict = {}
    for token in vocabList:
        freqDict[token] = fullText.count(token)#计算每个单词出现的次数
    sortedFreq = sorted(freqDict.iteritems(),key=operator.itemgetter(1),reverse=True)#按照逆序从大到小对freqDict进行排序
    return sortedFreq[:30]#返回前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'])#每次访问一条RSS源
        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)#得到在两个源中出现次数最高的30个单词
    for pairW in top30Words:
        if pairW[0] in vocabList:vocabList.remove(pairW[0])#从词汇表中把高频的30个词移除
    trainingSet = range(2*minLen);testSet=[]#
    for i in range(20):#从两个rss源中挑出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

显示地域相关的用词:

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"
    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"
    for item in sortedNY:
        print item[0]

总结:对于分类而言,使用概率有时要比使用硬规则更为有效。贝叶斯概率及贝叶斯准则提供了一种利用已知值来估计未知概率的有效方法。朴素贝叶斯假设就是特征之间相互独立,降低了对数据量的需求。下溢出可以用对概率取对数来解决。词袋模型在解决文档分类上更有效。
优点:在数据较少的情况下仍然有效,可以处理多类别问题。
缺点:对于输入数据的准备方式较为敏感。
适用于数据类型:标称型数据。

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章