一、贝叶斯定理
贝叶斯定理由英国数学家贝叶斯 ( Thomas Bayes 1702-1761 ) 发展,用来描述两个条件概率之间的关系,比如 P(A|B) 和 P(B|A)。按照乘法法则,可以立刻导出:
P(A∩B) = P(A)P(B|A)=P(B)P(A|B)。如上公式也可变形为:P(A|B)=P(B|A)*P(A)/P(B)。
二、朴素贝叶斯分类器
先验概率P(X):先验概率是指根据以往经验和分析得到的概率。
后验概率P(Y|X):事情已发生,要求这件事情发生的原因是由某个因素引起的可能性的大小,后验分布P(Y|X)表示事件X已经发生的前提下,事件Y发生的概率,称事件X发生下事件Y的条件概率。
后验概率P(X|Y):在已知Y发生后X的条件概率,也由于知道Y的取值而被称为X的后验概率。
朴素:朴素贝叶斯算法是假设各个特征之间相互独立,也是朴素这词的意思,那么贝叶斯公式中的P(X|Y)可写成:

朴素贝叶斯公式:

朴素贝叶斯分类器:朴素贝叶斯分类器(Naïve Bayes Classifier)采用了“属性条件独立性假设” ,即每个属性独立地对分类结果发生影响。为方便公式标记,不妨记P(C=c|X=x)为P(c|x),基于属性条件独立性假设,贝叶斯公式可重写为:

三、朴素贝叶斯分类的优缺点:
优点:
1、对待预测样本进行预测,过程简单速度快。
2、对于多分类问题也同样很有效,复杂度也不会有大程度上升。
3、在分布独立这个假设成立的情况下效果好。
4、对于类别类的输入特征变量效果好。
缺点:
1、对于测试集中的一个类别变量特征,如果在训练集里没见过,直接算的话概率就是0了,预测功能就失效了。
2、朴素贝叶斯有分布独立的假设前提。
四、拉普拉斯修正
由于若某个属性值在训练集中没有与某个类同时出现过,则训练后的模型会出现 over-fitting 现象,为了避免其他属性携带的信息,被训练集中未出现的属性值“抹去”,在估计概率值时通常要进行“拉普拉斯修正”:令 N 表示训练集 D 中可能的类别数,𝑁_𝑖表示第i个属性可能的取值数,则贝叶斯公式可修正为:

五、实现垃圾邮件分类
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']]
    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)       #创建一个元素都为0的向量
    #遍历数据集单词
    for word in inputSet:
        #存在单词在词袋中则
        if word in vocabList:
            #index用于找到第一个与之匹配的下标
            returnVec[vocabList.index(word)] = 1
        else:
            print ("the word: %s is not in my Vocabulary!" % word)
    return returnVec

2、训练算法:从词向量计算概率
伪代码如下:
计算每个类别中的文档数目
对每篇训练文档:
对每个类别:
如果词条出现在文档中->增加该词条的计数值
增加所有词条的计数值
对每个类别:
对每个词条:
将该词条的数目除以总词条的数目得到条件概率
返回每个类别的条件概率
代码如下:

点击查看代码
def trainNB0(trainMatrix,trainCategory):
    numTrainDocs = len(trainMatrix)
    numWords = len(trainMatrix[0])
    pAbusive = sum(trainCategory)/float(numTrainDocs)
    #  初始化概率
    p0Num = zeros(numWords); p1Num = zeros(numWords)
    p0Denom = 0.0; p1Denom = 0.0
    #   遍历文档,向量相加
    for i in range(numTrainDocs):
        if trainCategory[i] == 1:           #  侮辱类文档,向量相加
            p1Num += trainMatrix[i]
            p1Denom += sum(trainMatrix[i])
        else:                               #  非侮辱类文档向量相加
            p0Num += trainMatrix[i]
            p0Denom += sum(trainMatrix[i])
 
    p1Vect = p1Num/p1Denom         #  各个单词在侮辱类中出现的概率
    p0Vect = p0Num/p0Denom         #  各个单词在非侮辱类中出现的概率
    return p0Vect,p1Vect,pAbusive
3、朴素贝叶斯分类函数
点击查看代码
def classifyNB(vec2Classify, p0Vec, p1Vec, pClass1):
    #计算abusive的概率
    p1 = sum(vec2Classify * p1Vec) + log(pClass1)
    #计算not abusive概率
    p0 = sum(vec2Classify * p0Vec) + log(1.0 - pClass1)
    #看哪个概率大
    if p1 > p0:
        return 1
    else:
        return 0
def testingNB():
    # test_list=[]; test_class=[0,1]
    #加载数据集
    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 (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 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所有文档词汇
    docList=[]; classList = []; fullText =[]
    #遍历文件夹内文件
    for i in range(1,26):
        # 定义并读取垃圾邮件文件的词汇分割列表
        wordList = textParse(open('D:\\email/spam/%d.txt' % i).read())
        # 将词汇列表加到文档列表中
        docList.append(wordList)
        # 将所有词汇列表汇总到fullText中
        fullText.extend(wordList)
        # 文档类别为1,spam
        classList.append(1)
        # 读取非垃圾邮件的文档
        wordList = textParse(open('D:\\email/ham/%d.txt' % i).read())
        # 添加到文档列表中,注意append和extend的区别,append直接加列表,extend加元素
        docList.append(wordList)
        # 添加到所有词汇列表中
        fullText.extend(wordList)
        # 类别为0,非垃圾邮件
        classList.append(0)
 
    # 创建词汇列表
    vocabList = createVocabList(docList)
    # 定义训练集的索引和测试集
    trainingSet = list(range(50)); testSet=[]
    # 随机的选择10个作为测试集
    for i in range(10):
        #随机索引
        randIndex = int(random.uniform(0,len(trainingSet)))
        #将随机选择的文档加入测试集
        testSet.append(trainingSet[randIndex])
        #从训练集中删除随机选择的文档
        del(trainingSet[randIndex])
    #定义训练集的矩阵和类别
    trainMat=[]; trainClasses = [];
    # test_list=[];test_class=[]
    #遍历训练集,求先验概率和条件概率
    for docIndex in trainingSet:
        #将词汇列表变成向量放到trainList中
        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])   #bagOfWords2VecMN函数将词汇向量化和计算次数    setOfWords2Vec将词汇向量化
        # 对测试数据进行分类
        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))
    print('错误率:%.2f%%' % (float(errorCount) / len(testSet) * 100))
    return vocabList,fullText
效果如下: