朴素贝叶斯分类算法分析与实践

1.分析

1.1 背景意义:

        KNN和分类决策树都是要求分类器能够给出实例明确属于哪一类?但很多时候我们没法判断准确是哪一类时候,这时候起码能够给出最优可能的那一类和有多大概率属于该类。朴素贝叶斯分类算法就可以干这事。

1.2 原理

         首先概述整个朴素贝叶斯分类算法:对于给定的训练数据集,首先基于“特征条件独立假设”学习输入输出的联合概率分布;然后基于此模型,对于给定的输入x(实例),利用贝叶斯定理求出后验概率最大的输出y(类别)。

        所以,我们的目标是,根据样本x,算出该样本属于每个类别y的后验概率,比较得出最大值对应的那个y就是最有可能的类别,由贝叶斯定理有公式如下:

         P(y|x)=\frac{P(x|y)P(y)}{P(x)}
        由于条件概率P(x|y)难算:x是一个d维向量,那么x就有d次方的指数级别种可能的组合,远大于样本的数量,说明一些组合是没有的,因此用样本出现的频数来估计条件概率P(x|y)既难算又不合理。所以用到了“特征条件独立假设”,该假设是说,用来分类的特征,在类确定的条件下是条件独立的,就是说每个特征对分类的影响独立。这是一个较强的假设(因为特征之间可能有关联性),使得该分类方法变得简单,损失一定准确性,所以称之为"朴素"。
         由“特征条件独立假设”有公式如下:
          P(x|y)=\prod_{i=1}^{d}P(x_i|y)
        并且由于P(x)对于每一个类别y都是一样的,所以朴素贝叶斯分类器的表达式为:

         \widehat{y}=argmaxP(y)\prod_{i=1}^{d}P(x_i|y)

        参数估计用极大似然估计,其实也就是用频数来代表概率。
        算法流程如下:

图来源于https://zhuanlan.zhihu.com/p/36545000

       直接使用极大似然估计法时,需要注意,若某个属性值在训练集中没有与某个类同时出现,则直接基于之前的公式进行概率估计,再进行判别将出现问题。不能因为没有出现该样本,而直接把该样本的出现概率设为0。为了避免其他属性携带的信息被训练集中未出现的属性值 “抹去”,在估计概率值时通常需要进行 “平滑” (smoothing),我们常用 “拉普拉斯修正”。公式具体看李航书P51。

 

2.实践(《机器学习实战》第四章代码解析)

使用python来文本分类

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']]
    #0-非侮辱性,1-侮辱性
    classVec = [0,1,0,1,0,1]    
    return postingList,classVec

def createVocabList(dataset):#获取词汇表:把文本集装换成一个不含重复词的列表
    vocabSet = set([])#创建一个空集,可以进行集合操作
    for document in dataset:
        vocabSet = vocabSet |set(document)#每次把文件中的不重复元素放入
    return list(vocabSet)

def setOfWords2Vec(vocabList, inputSet):#转化成向量:参数是词汇表和某个文档
    returnVec = [0]*len(vocabList)#构建vocabList长度的0向量
    for word in inputSet:  #把该文档中在词汇表中有该单词的位置标位1(one-hot向量)
        if word in vocabList:
            returnVec[vocabList.index(word)]=1 
        else:
             print("the word: %s is not in my Vocabulary!" % word)
    return returnVec

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

def trainNB0(trainMatrix, trainGategory):
    numTrainDocs = len(trainMatrix)   #返回训练的文档数目
    numWords = len(trainMatrix[0])   #返回每一篇文档的词条数
    pAbusive = sum(trainGategory)/float(numTrainDocs)  #文档属于侮辱类的概率
    #用拉普拉斯修正”来平滑数据
    p0Num = np.ones(numWords); p1Num = np.ones(numWords)  #创建numpy.zeros数组,词条出现数初始化为0
    p0Denom = 2.0; p1Denom = 2.0   #分母初始化为2
    for i in range(numTrainDocs):
        if trainGategory[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 = np.log(p1Num/p1Denom)#log防止下溢出
    p0Vect = np.log(p0Num/p0Denom)
    return p0Vect,p1Vect,pAbusive  #返回属于非侮辱类的条件概率数组,属于侮辱类的条件概率数组,文档属于侮辱类的概率

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

def classifyNB(vec2Classify, p0Vec, p1Vec, pClass1):
    p1 = sum(vec2Classify*p1Vec)+np.log(pClass1)# #对应元素相乘。logA * B = logA + logB,所以这里加上log(pClass1)
    p0 = sum(vec2Classify*p0Vec)+np.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(np.array(trainMat),np.array(listClasses))
    testEntry = ['love','my','dalmation']
    thisDoc = np.array(setOfWords2Vec(myVocabList,testEntry))
    print(testEntry,'classified as:',classifyNB(thisDoc,p0V,p1V,pAb))
    testEntry = ['stupid','garbage']
    thisDoc = np.array(setOfWords2Vec(myVocabList,testEntry))
    print(testEntry,'classified as:',classifyNB(thisDoc,p0V,p1V,pAb))

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

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

def textParse(bigString):
    """
    输入很长的字符串,转换为向量
    参数:
        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 =[]
    #遍历垃圾邮件和正常邮件,各25个
    for i in range(1,26):
        #读取垃圾邮件
        wordList = textParse(open("email/spam/{}.txt".format(i), errors = 'ignore').read())
        #添加到列表
        docList.append(wordList)
        fullText.extend(wordList)
        #添加到类
        classList.append(1)
        #读取正常邮件
        #ham中的23.txt总是报错有不能解读的字节,选择忽略该错误
        wordList = textParse(open("email/ham/{}.txt".format(i), errors = 'ignore').read())
        docList.append(wordList)
        fullText.extend(wordList)
        classList.append(0)
    #创建词汇表
    vocabList = createVocabList(docList)
    #训练集和测试集序号集
    trainingSet = list(range(50)); testSet=[]
    #随机抽取训练集中的10个序号,放入测试集
    for i in range(10):
        #生成随机序号
        randIndex = np.int(np.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(np.array(trainMat),np.array(trainClasses))
    #错误计数器初始化为0
    errorCount = 0
    #对于测试集
    for docIndex in testSet:
        #得到词袋向量
        wordVector = bagOfWords2VecMN(vocabList, docList[docIndex])
        #判断结果
        if classifyNB(np.array(wordVector),p0V,p1V,pSpam) != classList[docIndex]:
            #统计错误
            errorCount += 1
            #打印错误信息
            print("错误序号为:{}".format(docList[docIndex]))
    print("总准确率为:{}".format(1 - np.float(errorCount)/len(testSet)))

参考

【1】https://zhuanlan.zhihu.com/p/36545000
【2】《统计学习方法》李航
【3】《机器学习实战》

猜你喜欢

转载自blog.csdn.net/Findingxu/article/details/83932906