一、朴素贝叶斯改进之拉普拉斯平滑

上篇文章提到过,算法存在一定的问题,需要进行改进。那么需要改进的地方在哪里呢?利用贝叶斯分类器对文档进行分类时,要计算多个概率的乘积以获得文档属于某个类别的概率,即计算p(w0|1)p(w1|1)p(w2|1)。如果其中有一个概率值为0,那么最后的成绩也为0。
Python3《机器学习实战》学习笔记(四):朴素贝叶斯实战篇之新浪新闻分类-LMLPHP
可以看到很多0
如果新实例文本,包含这种概率为0的分词,那么最终的文本属于某个类别的概率也就是0了。

为了降低这种影响,可以将所有词的出现数初始化为1,并将分母初始化为2。这种做法就叫做拉普拉斯平滑(Laplace Smoothing)又被称为加1平滑,是比较常用的平滑方法,它就是为了解决0概率问题。

另外一个遇到的问题就是下溢出,这是由于太多很小的数相乘造成的。学过数学的人都知道,两个小数相乘,越乘越小,这样就造成了下溢出。为了解决这个问题,对乘积结果取自然对数。通过求对数可以避免下溢出或者浮点数舍入导致的错误。同时,采用自然对数进行处理不会有任何损失。

'''
函数说明:朴素贝叶斯分类器训练函数
trainMatrix - 训练文档矩阵,即setOfWords2Vec返回的returnVec构成的矩阵
trainCategory - 训练类别标签向量,即loadDataSet返回的classVec    [0,1,0,1,0,1]
'''
def trainNB0(trainMatrix, trainCategory):
    numTrainDocs = len(trainMatrix) # 计算训练文档数目
    numWords = len(trainMatrix[0])  # 计算每篇文档词条数
    pAbusive = sum(trainCategory) / float(numTrainDocs) # 文档属于侮辱类的概率

    # p0Num = np.zeros(numWords)
    # p1Num = np.zeros(numWords)  # 创建numpy.zeros数组,词条出现数初始化为0
    #
    # p0Denom = 0.0
    # p1Denom = 0.0  # 分母初始化为0

    p0Num = np.ones(numWords);
    p1Num = np.ones(numWords)  # 创建numpy.ones数组,词条出现数初始化为1,拉普拉斯平滑
    p0Denom = 2.0;
    p1Denom = 2.0  # 分母初始化为2,拉普拉斯平滑

    # 其实这里就是一个分类    把trainMatrix的每一行,分为两种,并且将两种分别累加
    for i in range(numTrainDocs):
        if(trainCategory[i] == 1):  # 这个if就相当于在已知
            p1Num += trainMatrix[i]
            p1Denom += sum(trainMatrix[i])

        else:
            p0Num += trainMatrix[i]
            p0Denom += sum(trainMatrix[i])

    print('p1Num:\n', p1Num)
    print('p1Denom:\n', p1Denom)
    print('p0Num:\n', p0Num)
    print('p0Denom:\n', p0Denom)
    # p1Vect = p1Num / p1Denom
    # p0Vect = p0Num / p0Denom

    p1Vect = np.log(p1Num / p1Denom)  # 取对数,防止下溢出         
    p0Vect = np.log(p0Num / p0Denom)

    return p0Vect, p1Vect, pAbusive

Python3《机器学习实战》学习笔记(四):朴素贝叶斯实战篇之新浪新闻分类-LMLPHP

def classifyNB(wordVec, p0Vect, p1Vect, pAbusive):
    p0 = np.log(1.0 - pAbusive) + sum(wordVec * p0Vect)
    p1 = np.log(pAbusive) + sum(wordVec * p1Vect)
    if p0 > p1:
        return 0
    else:
        return 1

因为取自然对数了。logab = loga + logb。

这样,我们的朴素贝叶斯分类器就改进完毕了。

二、朴素贝叶斯之过滤垃圾邮件

2.1收集数据

有两个文件夹ham和spam,spam文件下的txt文件为垃圾邮件。

2.2完整代码

'''
@Project :MachineLearning 
@File    :emailBayes.py
@Author  :Kyrie Irving
@Date    :2022/10/27 10:41 
'''
import re
import numpy as np


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

'''
函数说明:将切分的实验样本词条整理成不重复的词条列表,也就是词汇表
'''
def createVocabList(dataSet):
    vocabSet = set([])
    for li in dataSet:
        vocabSet = vocabSet | set(li)   # 每一行去重复,再取交集,确保没重复行
    return list(vocabSet)

'''
函数说明:根据vocabList词汇表,将inputSet向量化,向量的每个元素为1或0
vocabList - createVocabList返回的列表
inputSet - 切分的词条列表,即原始数据的每一行
这里遍历每一行传入的每一个单词,如果单词在词汇表中出现了,在词汇表的相应下标位置置为1
'''
def setOfWords2Vec(vocabList, inputSet):
    returnVec = [0] * len(vocabList)    # 创建一个其中所含元素都为0的向量
    for word in inputSet:
        if word in vocabList:
            returnVec[vocabList.index(word)] = 1    # 如果词条存在于词汇表中,则置1
        else:
            print("the word: %s is not in my Vocabulary!" % word)
    return returnVec

'''
函数说明:根据vocabList词汇表,将inputSet向量化,向量的每个元素为1或0
vocabList - createVocabList返回的列表
inputSet - 切分的词条列表,即原始数据的每一行
这里遍历每一行传入的每一个单词,如果单词在词汇表中出现了,在词汇表的相应下标位置置为1
'''
def setOfWords2Vec(vocabList, inputSet):
    returnVec = [0] * len(vocabList)    # 创建一个其中所含元素都为0的向量
    for word in inputSet:
        if word in vocabList:
            returnVec[vocabList.index(word)] = 1    # 如果词条存在于词汇表中,则置1
        else:
            print("the word: %s is not in my Vocabulary!" % word)
    return returnVec

'''
函数说明:朴素贝叶斯分类器训练函数
trainMatrix - 训练文档矩阵,即setOfWords2Vec返回的returnVec构成的矩阵
trainCategory - 训练类别标签向量,即loadDataSet返回的classVec    [0,1,0,1,0,1]
'''
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,拉普拉斯平滑

    # 其实这里就是一个分类    把trainMatrix的每一行,分为两种,并且将两种分别累加
    for i in range(numTrainDocs):
        if(trainCategory[i] == 1):  # 这个if就相当于在已知
            p1Num += trainMatrix[i]
            p1Denom += sum(trainMatrix[i])

        else:
            p0Num += trainMatrix[i]
            p0Denom += sum(trainMatrix[i])


    p1Vect = np.log(p1Num / p1Denom)  # 取对数,防止下溢出
    p0Vect = np.log(p0Num / p0Denom)

    return p0Vect, p1Vect, pAbusive
'''
wordVec必须是np.array()
因为后边需要其矩阵相乘
'''
def classifyNB(wordVec, p0Vect, p1Vect, pAbusive):
    p0 = np.log(1.0 - pAbusive) + sum(wordVec * p0Vect)
    p1 = np.log(pAbusive) + sum(wordVec * p1Vect)
    if p0 > p1:
        return 0
    else:
        return 1

def spamTest():
    docList = []
    classList = []
    for i in range(1, 26):
        fileName = 'spam/' + str(i) + '.txt'
        bigString = open(fileName, 'r').read()
        # wordList = textParse(open(fileName, 'r').read())
        wordList = textParse(open('spam/%d.txt' % i, 'r').read())  # 读取每个垃圾邮件,并字符串转换成字符串列表
        docList.append(wordList)
        classList.append(1)  # 标记垃圾邮件,1表示垃圾文件
        # wordList = textParse(open('ham/%d.txt' % i, 'r').read())  # 读取每个垃圾邮件,并字符串转换成字符串列表
        wordList = textParse(open('ham/%d.txt' % i, 'r').read())  # 读取每个垃圾邮件,并字符串转换成字符串列表
        docList.append(wordList)
        classList.append(0)  # 标记垃圾邮件,1表示垃圾文件

    vocabList = createVocabList(docList)  # 创建词汇表,不重复
    print('不重复的词汇表:', vocabList)

    trainingSet = list(range(50))   #创建存储训练集的索引值的列表和测试集的索引值的列表
    testSet = []

    for i in range(10): #从50个邮件中,随机挑选出40个作为训练集,10个做测试集
        rangIndex = int(np.random.uniform(0, len(trainingSet))) #随机选取索索引值
        testSet.append(trainingSet[rangIndex])  #添加测试集的索引值
        del (trainingSet[rangIndex])    #在训练集列表中删除添加到测试集的索引值

    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
            print("分类错误的测试集:", docList[docIndex])
    print('错误率:%.2f%%' % (float(errorCount) / len(testSet) * 100))

if __name__ == '__main__':
    spamTest()

Python3《机器学习实战》学习笔记(四):朴素贝叶斯实战篇之新浪新闻分类-LMLPHP
Python3《机器学习实战》学习笔记(四):朴素贝叶斯实战篇之新浪新闻分类-LMLPHP
既然这些电子邮件是随机选择的,所以每次的输出结果可能有些差别。

三、朴素贝叶斯之新浪新闻分类(Sklearn)

后续链接: https://jackcui.blog.csdn.net/article/details/77500679

四、总结

  1. 在训练朴素贝叶斯分类器之前,要处理好训练集,文本的清洗还是有很多需要学习的东西。
  2. 根据提取的分类特征将文本向量化,然后训练朴素贝叶斯分类器。分类的时候也是一样的
  3. 去高频词汇数量的不同,对结果也是有影响的的。
  4. 拉普拉斯平滑对于改善朴素贝叶斯分类器的分类效果有着积极的作用。
10-28 15:01