概率论是许多机器学习算法的基础。本篇文章将会使用概率论的方法进行分类,首先从一个简单的概率论分类器开始,然后给出假设来学习朴素贝叶斯分类器。之所以称其为朴素,是因为整个形式化过程,只做最原始、最简单的假设。充分利用Python的文本处理能力将文档切分成词向量,然后利用词向量对文档进行分类。

贝叶斯公式

学贝叶斯之前要知道条件概率公式:
P(A|B)=P(AB)/P(B)

贝叶斯公式:

python中按照结果寻找可能比例的方法 python按概率选择结果_数组

贝叶斯决策理论

假如用p1(x,y)代表数据(x,y)属于类别1,p2(x,y)代表数据(x,y)属于类别。那么对于一个新的数据点(x,y),可以用下边的规则来判断它的类别:

  • 如果p1(x,y) > p2(x,y),则为类别1,
    如果p2(x,y) > p1(x,y),则为类别2,

贝叶斯决策理论的核心思想就是选择具有最高概率的决策

但这并不是贝叶斯决策理论的所有内容,真正需要计算和比较的是p(c1|x,y)和p(c2|x,y):

  • 如果p(c1|x,y) > p(c2|x,y),则为类别c1,
    如果p(c2|x,y) >p(c1|x,y),则为类别c2。

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

# 创建实验样本
import numpy as np

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

# 将inputSet向量化,向量的每个元素为1或0
def setOfWords2Vec(vocabList, inputSet):
    returnVec = [0] * len(vocabList)                        #创建一个其中所含元素都为0的向量
    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                                               #返回文档向量

# 将切分的实验样本词条整理成不重复的词条列表,也就是词汇表
def createVocabList(dataSet):
    vocabSet = set([])                      #创建一个空的不重复列表
    for document in dataSet:
        vocabSet = vocabSet | set(document) 
    return list(vocabSet)
    
#朴素贝叶斯分类器训练函数
def trainNB0(trainMatrix,trainCategory):
    numTrainDocs = len(trainMatrix)                       #计算训练的文档数目
    numWords = len(trainMatrix[0])                        #计算每篇文档的词条数
    pAbusive = sum(trainCategory)/float(numTrainDocs)     #文档属于侮辱类的概率
    p0Num = np.zeros(numWords); p1Num = np.zeros(numWords)
    p0Denom = 0.0; p1Denom = 0.0    #分母初始化为0
    for i in range(numTrainDocs):
        if trainCategory[i] == 1:   #统计属于侮辱类的条件概率所需的数据P(w0|1),P(w1|1)···
            p1Num += trainMatrix[i]
            p1Denom += sum(trainMatrix[i])
        else:                #统计属于非侮辱类的条件概率所需的数据P(w0|0),P(w1|0)···
            p0Num += trainMatrix[i]
            p0Denom += sum(trainMatrix[i])
    p1Vect = p1Num/p1Denom
    p0Vect = p0Num/p0Denom
    return p0Vect,p1Vect,pAbusive#返回属于侮辱类的条件概率数组,属于非侮辱类的条件概率数组,文档属于侮辱类的概率


if __name__ == '__main__':
    postingList, classVec = loadDataSet()
    myVocabList = createVocabList(postingList)
    print('myVocabList:\n', myVocabList)
    trainMat = []
    for postinDoc in postingList:
        trainMat.append(setOfWords2Vec(myVocabList, postinDoc))
    p0V, p1V, pAb = trainNB0(trainMat, classVec)
    print('p0V:\n', p0V)
    print('p1V:\n', p1V)
    print('classVec:\n', classVec)
    print('pAb:\n', pAb)

运行结果PAb为0.5,证明分类正确。

['help', 'ate', 'quit', 'park', 'maybe', 'cute', 'how', 'take', 'buying', 'dog', 'flea', 'my', 'worthless', 'not', 'please', 'I', 'mr', 'steak', 'love', 'stop', 'licks', 'food', 'is', 'so', 'him', 'has', 'problems', 'stupid', 'posting', 'to', 'garbage', 'dalmation']
p0V:
 [0.04166667 0.04166667 0.         0.         0.         0.04166667
 0.04166667 0.         0.         0.04166667 0.04166667 0.125
 0.         0.         0.04166667 0.04166667 0.04166667 0.04166667
 0.04166667 0.04166667 0.04166667 0.         0.04166667 0.04166667
 0.08333333 0.04166667 0.04166667 0.         0.         0.04166667
 0.         0.04166667]
p1V:
 [0.         0.         0.05263158 0.05263158 0.05263158 0.
 0.         0.05263158 0.05263158 0.10526316 0.         0.
 0.10526316 0.05263158 0.         0.         0.         0.
 0.         0.05263158 0.         0.05263158 0.         0.
 0.05263158 0.         0.         0.15789474 0.05263158 0.05263158
 0.05263158 0.        ]
classVec:
 [0, 1, 0, 1, 0, 1]
pAb:
 0.5

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

import numpy as np
import random
import re

# 将切分的实验样本词条整理成不重复的词条列表,也就是词汇表
def createVocabList(dataSet):
    vocabSet = set([])                      #创建一个空的不重复列表
    for document in dataSet:
        vocabSet = vocabSet | set(document) #取并集
    return list(vocabSet)

#根据vocabList词汇表,将inputSet向量化,向量的每个元素为1或0
def setOfWords2Vec(vocabList, inputSet):
    returnVec = [0] * len(vocabList)                               #创建一个其中所含元素都为0的向量
    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                                               #返回文档向量

#根据vocabList词汇表,构建词袋模型
def bagOfWords2VecMN(vocabList, inputSet):
    returnVec = [0]*len(vocabList)                          #创建一个其中所含元素都为0的向量
    for word in inputSet:                                   #遍历每个词条
        if word in vocabList:                               #如果词条存在于词汇表中,则计数加一
            returnVec[vocabList.index(word)] += 1
    return returnVec                                        #返回词袋模型

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

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

#接收一个大字符串并将其解析为字符串列表
def textParse(bigString):                                       #将字符串转换为字符列表
    listOfTokens = re.split(r'\W*', bigString)                  #将特殊符号作为切分标志进行字符串切分,即非字母、非数字
    return [tok.lower() for tok in listOfTokens if len(tok) > 2]#除了单个字母,例如大写的I,其它单词变成小写

#测试朴素贝叶斯分类器
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.append(wordList)
        classList.append(1)                                            #标记垃圾邮件,1表示垃圾文件
        wordList = textParse(open('email/ham/%d.txt' % i, 'r').read()) #读取每个非垃圾邮件,并字符串转换成字符串列表
        docList.append(wordList)
        fullText.append(wordList)
        classList.append(0)                    		#标记非垃圾邮件,1表示垃圾文件
    vocabList = createVocabList(docList)       		#创建词汇表,不重复
    trainingSet = list(range(50)); testSet = []		#创建存储训练集的索引值的列表和测试集的索引值的列表
    for i in range(10):#从50个邮件中,随机挑选出40个作为训练集,10个做测试集
        randIndex = int(random.uniform(0, len(trainingSet)))              #随机选取索索引值
        testSet.append(trainingSet[randIndex])                            #添加测试集的索引值
        del(trainingSet[randIndex])                                       #在训练集列表中删除添加到测试集的索引值
    trainMat = []; trainClasses = []                                      #创建训练集矩阵和训练集类别标签系向量
    for docIndex in trainingSet:                                          #遍历训练集
        trainMat.append(setOfWords2Vec(vocabList, docList[docIndex]))     #将生成的词集模型添加到训练矩阵中
        trainClasses.append(classList[docIndex])                          #将类别添加到训练集类别标签系向量中
    p0V, p1V, pSpam = trainNB0(np.array(trainMat), np.array(trainClasses))#训练朴素贝叶斯模型
    errorCount = 0                                                        #错误分类计数
    for docIndex in testSet:                                              #遍历测试集
        wordVector = setOfWords2Vec(vocabList, docList[docIndex])         #测试集的词集模型
        if classifyNB(np.array(wordVector), p0V, p1V, pSpam) != classList[docIndex]:#如果分类错误
            errorCount += 1                                               #错误计数加1
            print("分类错误的测试集:",docList[docIndex])
    print('错误率:%.2f%%' % (float(errorCount) / len(testSet) * 100))


if __name__ == '__main__':
    spamTest()