机器学习实战3(朴素贝叶斯篇)

目录

1、朴素贝叶斯

2、朴素贝叶斯算法实例1--文档分类

3、朴素贝叶斯算法实例2--过滤垃圾邮件


1、朴素贝叶斯

        朴素贝叶斯算法是有监督的学习算法,解决的是分类问题,如客户是否流失、是否值得投资、信用等级评定等多分类问题。该算法的优点在于简单易懂、学习效率高、在某些领域的分类问题中能够与决策树、神经网络相媲美。但由于该算法以自变量之间的独立(条件特征独立)性和连续变量的正态性假设为前提,就会导致算法精度在某种程度上受影响。        

        朴素贝叶斯是贝叶斯决策理论的一部分,所以讲述朴素贝叶斯之前有必要快速了解一下贝叶斯决策理论。

        假设现在我们有一个数据集,它由两类数据组成,数据分布如图4-1所示。

机器学习实战3(朴素贝叶斯篇)_第1张图片

         假设有位读者找到了描述图中两类数据的统计参数。我们现在用p1(x,y)表示数据点(x,y)属于类别1(图中用圆点表示的类别)的概率,用p2(x, y)表示数据点(x,y)属于类别2(图中用三角形表示的类别)的概率,那么对于一个新数据点(x,y),可以用下面的规则来判断它的类别:

        ①如果p1 (x,y) > p2(x,y),那么类别为1。

        ②如果p2(x,y) > p1(x,y),那么类别为2。

        也就是说,我们会选择高概率对应的类别。这就是贝叶斯决策理论的核心思想,即选择具有最高概率的决策。回到图4-1,如果该图中的整个数据使用6个浮点数"来表示,并且计算类别概率的Python代码只有两行,那么你会更倾向于使用下面哪种方法来对该数据点进行分类?

        ①使用第1章的kNN,进行1000次距离计算;

        ②使用第2章的决策树,分别沿x轴、y轴划分数据;

        ③计算数据点属于每个类别的概率,并进行比较。

        使用决策树不会非常成功,而和简单的概率计算相比,kNN的计算量太大。因此,对于上述问题,最佳选择是使用刚才提到的概率比较方法。

        提到贝叶斯决策理论要求计算两个概率p1(x, y)和p2(x,y)。如果p1(x,y) > p2(x, y),那么属于类别1;如果p2(x,y) > p1(x,y),那么属于类别2。

        但这两个准则并不是贝叶斯决策理论的所有内容。使用p1( )和p2( )只是为了尽可能简化描述,而真正需要计算和比较的是p(c_{1}|x,y)p(c_{2}|x,y)。这些符号所代表的具体意义是:给定某个由x、y表示的数据点,那么该数据点来自类别c_{1}的概率是多少?数据点来自类别c_{2}的概率又是多少?注意这些概率与刚才给出的概率p(x,y|c)并不一样,不过可以使用贝叶斯准则来交换概率中条件与结果。具体地,应用贝叶斯准则得到:

         使用这些定义,可以定义贝叶斯分类准则为:

       ①如果p(c_{1}|x,y)>p(c_{2}|x,y),那么属于类别c_{1}

        ②如果p(c_{2}|x,y)>p(c_{1}|x,y),那么属于类别c_{2}

        使用贝叶斯准则,可以通过已知的三个概率值来计算未知的概率值。后面就会给出利用贝叶斯准则来计算概率并对数据进行分类的代码。现在介绍了一些概率理论,你也了解了基于这些理论构建分类器的方法,接下来就要将它们付诸实践。

2、朴素贝叶斯算法实例1--文档分类

        机器学习的一个重要应用就是文档的自动分类。在文档分类中,整个文档(如一封电子邮件)是实例,而电子邮件中的某些元素则构成特征。虽然电子邮件是一种会不断增加的文本,但我们同样也可以对新闻报道、用户留言、政府公文等其他任意类型的文本进行分类。我们可以观察文档中出现的词,并把每个词的出现或者不出现作为一个特征,这样得到的特征数目就会跟词汇表中的词目一样多。朴素贝叶斯是上节介绍的贝叶斯分类器的一个扩展,是用于文档分类的常用算法。

        便用每个词作为特征并观察它们是否出现,这样得到的特征数目会有多少呢?针对的是哪一种人类语言呢?当然不止一种语言。据估计,仅在英语中,单词的总数就有500 000之多。为了能进行英文阅读,估计需要掌握数千单词。

        假设词汇表中有1000个单词。要得到好的概率分布,就需要足够的数据样本,假定样本数为N。前面讲到的约会网站示例中有1000个实例,手写识别示例中每个数字有200个样本,而决策树示例中有24个样本。其中,24个样本有点少,200个样本好一些,而1000个样本就非常好了。约会网站例子中有三个特征。由统计学知,如果每个特征需要N个样本,那么对于10个特征将需要N^{10}个样本,对于包含1000个特征的词汇表将需要N^{1000}个样本。可以看到,所需要的样本数会随着特征数目增大而迅速增长。

        如果特征之间相互独立,那么样本数就可以从N^{1000}减少到1000×N。所谓独立指的是统计意义上的独立,即一个特征或者单词出现的可能性与它和其他单词相邻没有关系。举个例子讲,假设单词bacon出现在unhealthy后面与出现在delicious后面的概率相同。当然,我们知道这种假设并不正确,bacon常常出现在delicious附近,而很少出现在unhealthy附近,这个假设正是朴素贝叶斯分类器中朴素( naive)一词的含义。朴素贝叶斯分类器中的另一个假设是,每个特征相同重要。

        以在线社区的留言板为例。为了不影响社区的发展,我们要屏蔽侮辱性的言论,所以要构建一个快速过滤器,如果某条留言使用了负面或者侮辱性的语言,那么就将该留言标识为内容不当。过滤这类内容是一个很常见的需求。对此问题建立两个类别:侮辱类和非侮辱类,使用1和0分别表示。

1、准备数据

        我们将把文本看成单词向量或者词条向量,也就是说将句子转换为向量。考虑出现在所有文档中的所有单词,再决定将哪些词纳入词汇表或者说所要的词汇集合,然后必须要将每一篇文档转换为词汇表上的向量。接下来我们正式开始。打开文本编辑器,创建一个叫bayes.py的新文件,然后将下面的程序清单添加到文件中。

#数据集
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)
    return list(vocabSet)
#根据vocabList词汇表,将inputSet向量化,向量的每个元素为1或0
def setOfWordVex(vocabList,inputSet):
    returnVec = [0]*len(vocabList)
    for word in inputSet:
        if word in vocabList:
            returnVec[vocabList.index(word)] = 1
        else:
            print("%s  is not in vocalList",word)
    return returnVec

        我们来测试一下代码:

DataSet,classVec = loadDataSet()
vocabSet = createVocabList(DataSet)
ans = setOfWordVex(vocabSet,DataSet[0])

        得到的输出为:

 2、朴素贝叶斯分类训练函数

        前面介绍了如何将一组单词转换为一组数字,接下来看看如何使用这些数字计算概率。

#朴素贝叶斯分类器训练函数
def trainNBO(trainMatrix,trainCategroy):
    numTrainDocs = len(trainMatrix)     #计算训练的文档数目
    numWords = len(trainMatrix[0])     #计算每篇文档的词条数
    pAbusive = sum(trainCategroy)/float(numTrainDocs)      #文档属于侮辱类的概率

    #初始化概率
    p0num = np.ones(numWords)   #拉普拉斯平滑
    p1num = np.ones(numWords)
    p0Denom = 2.0
    p1Denom = 2.0

    for i in range(numTrainDocs):
        if trainCategroy[i] == 1:   #统计属于侮辱类的条件概率所需的数据,即P(w0|1),P(w1|1),P(w2|1)···
            p1num += trainMatrix[i]
            p1Denom += sum(trainMatrix[i])
        else:   #统计属于非侮辱类的条件概率所需的数据,即P(w0|0),P(w1|0),P(w2|0)···
            p0num += trainMatrix[i]
            p0Denom += sum(trainMatrix[i])

    p1Vect = log(p1num/p1Denom) #取对数,取值不同,但是不影响最终结果。
    p0Vect = log(p0num/p0Denom) #可以防止下溢出,这是由于太多太小的数相乘造成的。
    return p0Vect,p1Vect,pAbusive #返回属于侮辱类的条件概率数组,属于非侮辱类的条件概率数组,文档属于侮辱类的概率

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

        另一个遇到的问题是下溢出,这是由于太多很小的数相乘造成的。当计算乘积p(w_{0}|1)p(w_{2}|1)p(w_{3}|1)时,由于大部分因子都非常小,所以程序会下溢出或者得到不正确的答案。(读者可以用Python尝试相乘许多很小的数,最后四舍五入后会得到0。)一种解决办法是对乘积取自然对数。在代数中有1n(a*b) = ln (a)+1n(b),于是通过求对数可以避免下溢出或者浮点数舍入导致的错误。同时,采用自然对数进行处理不会有任何损失。

3、朴素贝叶斯分类函数

        使用NumPy的数组来计算两个向量相乘的结果。这里的相乘是指对应元素相乘,即先将两个向量中的第1个元素相乘,然后将第2个元素相乘,以此类推。接下来将词汇表中所有词的对应值相加,然后将该值加到类别的对数概率上。最后,比较类别的概率返回大概率对应的类别标签。这一切不是很难,对吧?

def classifyNB(vecClassify,p0Vec,p1Vec,pClass1):
    p1 = sum(vecClassify*p1Vec)+log(pClass1)
    p0 = sum(vecClassify*p0Vec)+log(1-pClass1)
    if p1>p0:
        return 1
    else :
        return 0
def testingNB():
    DataSet,classVec = loadDataSet()
    vocabSet = createVocabList(DataSet)
    trainMat = []
    for postinDoc in DataSet:
        trainMat.append(setOfWordVex(vocabSet,postinDoc))
    p0V,p1V,pAb = trainNBO(trainMat,classVec)
    testEntry = ['love','my','dalmation']
    thisDoc = array(setOfWordVex(vocabSet,testEntry))
    print(classifyNB(thisDoc,p0V,p1V,pAb))

        我们测试一下代码,得到:

 4、词袋模型

        目前为止,我们将每个词的出现与否作为一个特征,这可以被描述为词集模型。如果一个词在文档中出现不止一次,这可能意味着包含该词是否出现在文档中所不能表达的某种信息,这种方法被称为词袋模型。在词袋中,每个单词可以出现多次,而在词集中,每个词只能出现一次。为适应词袋模型,需要对函数setofWordsvec()稍加修改,修改后的函数称为bagofwordsvec ()。

        下面的函数给出了基于词袋模型的朴素贝叶斯代码。它与函数setofwordsvec()几乎完全相同,唯一不同的是每当遇到一个单词时,它会增加词向量中的对应值,而不只是将对应的数值设为1。

#词带模型
def bagOfWordVecMN(vocabList,inputSet):
    returnVec = [0]*len(vocabList)
    for word in inputSet:
        if word in vocabList:
            returnVec[vocabList.index(word)] += 1   #与词集模型不同之处
    return returnVec

        现在分类器已经构建好了,下面我们将利用该分类器来过滤垃圾邮件。

5、完整代码

        见下个实例吧。哈。

3、朴素贝叶斯算法实例2--过滤垃圾邮件

        在前面那个简单的例子中,我们引人了字符串列表。使用朴素贝叶斯解决一些现实生活中的问题时,需要先从文本内容得到字符串列表,然后生成词向量。下面这个例子中,.我们将了解朴素贝叶斯的一个最著名的应用:电子邮件垃圾过滤。首先看一下如何使用通用框架来解决该问题。

1、准备数据,切分文本

        前一节介绍了如何创建词向量,并基于这些词向量进行朴素贝叶斯分类的过程。前一节中的词向量是预先给定的,下面介绍如何从文本文档中构建自己的词列表。

        数据:数据在这!         提取码:kfpg

#切分文本
def textParse(bigString):
    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个txt文件
        wordList = textParse(open('email/spam/%d.txt' % i, 'r').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)#标记非垃圾邮件
    # print(docList)
    vocabList = createVocabList(docList) #创建词汇表,不重复
    trainingSet = list(range(50))   #训练集
    print(trainingSet)
    testSet = []    # 测试集
    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:
        trainMat.append(setOfWordVex(vocabList,docList[docIndex]))
        trainClasses.append(classList[docIndex])
    p0V,p1V,pSpam = trainNBO(array(trainMat),array(trainClasses))
    errorCount = 0
    for docIndex in testSet:
         wordVector = setOfWordVex(vocabList,docList[docIndex])
         if classifyNB(array(wordVector),p0V,p1V,pSpam) != classList[docIndex]:
            errorCount += 1

    print('the error is %f',errorCount/float(len(testSet)))

        测试一下得到:

        函数spamTest ()会输出在10封随机选择的电子邮件上的分类错误率。既然这些电子邮件是随机选择的,所以每次的输出结果可能有些差别。如果发现错误的话,函数会输出错分文档的词表,这样就可以了解到底是哪篇文档发生了错误。如果想要更好地估计错误率,那么就应该将上述过程重复多次,比如说10次,然后求平均值。我这么做了一下,获得的平均错误率为6%。

        这里一直出现的错误是将垃圾邮件误判为正常邮件。相比之下,将垃圾邮件误判为正常邮件要比将正常邮件归到垃圾邮件好。为避免错误,有多种方式可以用来修正分类器,这些将在第7章中进行讨论。

 2、完整代码

from numpy import  *
import numpy as np
import random
import re

#数据集
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)
    return list(vocabSet)

#根据vocabList词汇表,将inputSet向量化,向量的每个元素为1或0
def setOfWordVex(vocabList,inputSet):
    returnVec = [0]*len(vocabList)
    for word in inputSet:
        if word in vocabList:
            returnVec[vocabList.index(word)] = 1
        else:
            print("%s  is not in vocalList",word)
    return returnVec


#词带模型
def bagOfWordVecMN(vocabList,inputSet):
    returnVec = [0]*len(vocabList)
    for word in inputSet:
        if word in vocabList:
            returnVec[vocabList.index(word)] += 1   #与词集模型不同之处
    return returnVec


#朴素贝叶斯分类器训练函数
def trainNBO(trainMatrix,trainCategroy):
    numTrainDocs = len(trainMatrix)     #计算训练的文档数目
    numWords = len(trainMatrix[0])     #计算每篇文档的词条数
    pAbusive = sum(trainCategroy)/float(numTrainDocs)      #文档属于侮辱类的概率

    #初始化概率
    p0num = np.ones(numWords)   #拉普拉斯平滑
    p1num = np.ones(numWords)
    p0Denom = 2.0
    p1Denom = 2.0

    for i in range(numTrainDocs):
        if trainCategroy[i] == 1:   #统计属于侮辱类的条件概率所需的数据,即P(w0|1),P(w1|1),P(w2|1)···
            p1num += trainMatrix[i]
            p1Denom += sum(trainMatrix[i])
        else:   #统计属于非侮辱类的条件概率所需的数据,即P(w0|0),P(w1|0),P(w2|0)···
            p0num += trainMatrix[i]
            p0Denom += sum(trainMatrix[i])

    p1Vect = log(p1num/p1Denom) #取对数,取值不同,但是不影响最终结果。
    p0Vect = log(p0num/p0Denom) #可以防止下溢出,这是由于太多太小的数相乘造成的。
    return p0Vect,p1Vect,pAbusive #返回属于侮辱类的条件概率数组,属于非侮辱类的条件概率数组,文档属于侮辱类的概率


def classifyNB(vecClassify,p0Vec,p1Vec,pClass1):
    p1 = sum(vecClassify*p1Vec)+log(pClass1)
    p0 = sum(vecClassify*p0Vec)+log(1-pClass1)
    if p1>p0:
        return 1
    else :
        return 0


def testingNB():
    DataSet,classVec = loadDataSet()
    vocabSet = createVocabList(DataSet)
    trainMat = []
    for postinDoc in DataSet:
        trainMat.append(setOfWordVex(vocabSet,postinDoc))
    p0V,p1V,pAb = trainNBO(trainMat,classVec)
    testEntry = ['love','my','dalmation']
    thisDoc = array(setOfWordVex(vocabSet,testEntry))
    print(classifyNB(thisDoc,p0V,p1V,pAb))


#切分文本
def textParse(bigString):
    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个txt文件
        wordList = textParse(open('email/spam/%d.txt' % i, 'r').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)#标记非垃圾邮件
    # print(docList)
    vocabList = createVocabList(docList) #创建词汇表,不重复
    trainingSet = list(range(50))   #训练集
    print(trainingSet)
    testSet = []    # 测试集
    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:
        trainMat.append(setOfWordVex(vocabList,docList[docIndex]))
        trainClasses.append(classList[docIndex])
    p0V,p1V,pSpam = trainNBO(array(trainMat),array(trainClasses))
    errorCount = 0
    for docIndex in testSet:
         wordVector = setOfWordVex(vocabList,docList[docIndex])
         if classifyNB(array(wordVector),p0V,p1V,pSpam) != classList[docIndex]:
            errorCount += 1

    print('the error is %f',errorCount/float(len(testSet)))




# DataSet,classVec = loadDataSet()
# vocabSet = createVocabList(DataSet)
# ans = setOfWordVex(vocabSet,DataSet[0])
# print(ans)
# trainMat = []
# for postinDoc in DataSet:
#     trainMat.append(setOfWordVex(vocabSet,postinDoc))
#
# p0V,p1V,pAb = trainNBO(trainMat,classVec)

# testingNB()

spamTest()


你可能感兴趣的:(#,机器学习实战,算法,机器学习,python,计算机视觉,人工智能)