【机器学习实战】朴素贝叶斯:过滤垃圾邮件

【机器学习实战】朴素贝叶斯:过滤垃圾邮件

0.收集数据

这里采用的数据集是《机器学习实战》提供的邮件文件,该文件有hamspam 两个文件夹,每个文件夹中各有25条邮件,分别代表着 正常邮件垃圾邮件

这里需要注意的是需要将 email 文件夹放在 py 代码文件同一级的地方。若放在其他地方,需要修改博主代码中的相对地址才可正常执行。
【机器学习实战】朴素贝叶斯:过滤垃圾邮件_第1张图片

1.解析数据

我们需要将我们的文本文件导入到程序里面,其中主要参数如下:

  • d o c L i s t : docList: docList 是一个二维数组,一共记录了50封邮件的内容。
  • f u l l T e x t : fullText: fullText是50封邮件中出现的所有单词的一个大的单词列表(有重复单词)
  • c l a s s L i s t : classList: classList用于记录当前邮件是 正常邮件 还是 垃圾邮件01 进行区分。
  • v o c a b L i s t : vocabList : vocabList是一个单词均不重复的词汇表。
# 导入并解析文本文件
    for i in range(1, 26):  # 因为每个文件夹各有文件25个
        wordList = textParse(open('email/spam/%d.txt' % i, encoding="ISO-8859-1").read())  # 读取文件
        docList.append(wordList)  # 用于存储所有文件的单词列表,二维数组
        fullText.extend(wordList)  # 这个列表用于存储所有文件的单词,将它们合并成一个大的单词列表
        classList.append(1)
        wordList = textParse(open('email/ham/%d.txt' % i, encoding="ISO-8859-1").read())
        docList.append(wordList)
        fullText.extend(wordList)
        classList.append(0)
    vocabList = createVocabList(docList)  # 创建不重复的词汇表

# 邮件裁词
def textParse(bigString):
    regEx = re.compile(r'\W')
    listOfTokens = regEx.split(bigString)
    var = [tok.lower() for tok in listOfTokens if len(tok) > 2]  # 只返回长度大于2的情况
    return var

# 创建词汇表
def createVocabList(dataSet):
    vocabSet = set([])  # 创建一个无序且不重复的集合对象
    for document in dataSet:
        vocabSet = vocabSet | set(document)  # 创建两个集合的并集
    return list(vocabSet)

ps:因为这里的邮件全是英文邮件,所以读取的时候编码是 encoding="ISO-8859-1"

2.区分“测试集”和“训练集”

这里我们一共创建了10个测试集,每取一个测试集就将其的索引从trainingSet 中删除。故trainingSet 最后剩下的40个索引就是训练集,testSet中的10个索引就是测试集。

  • 测试集:用于判断朴素贝叶斯分类是否有问题。
  • 训练集:用于训练朴素贝叶斯分类器的参数。
	trainingSet = list(range(50))  # 训练集的索引,0-49(因为ham和spam各有25个,故一共有50个)
    trainingSet = trainingSet
    testSet = []
    # 随机构造训练集
    for i in range(10):  # 表示只创建10个测试集、剩下的都是训练集
        # 函数生成一个0到len(trainingSet)之间的随机浮点数,然后取整,得到一个随机的索引值randIndex。
        randIndex = int(random.uniform(0, len(trainingSet)))
        testSet.append(trainingSet[randIndex])  # 将随机选择的索引值randIndex添加到测试集列表testSet中。
        trainingSet.pop(randIndex)  # 删除训练集中对应的测试集的索引值,以确保不会重复选择作为测试集。

3.训练算法

朴素贝叶斯算法往简单说,就是计算出当前参数为不同情况的概率,最后哪个可能性的概率大,那么他就是哪一类。

3.1 统计词汇出现的次数

# 朴素贝叶斯词袋模型
def bagOfWords2VecMN(vocabList, inputSet):
    returnVec = [0] * len(vocabList)  # 创建一个长度为vocabList的列表returnVec,并将列表中的每个元素都初始化为0
    for word in inputSet:
        if word in vocabList:
            returnVec[vocabList.index(word)] += 1  # 在词库里出现次数
    return returnVec

3.2.训练函数

将各个邮件中词汇出现次数的词袋,和最初记录每个邮件是 正常邮件 还是 垃圾邮件 的列表传入trainNB0计算出每个词汇是正常还是垃圾的可能性。

# 朴素贝叶斯分类器训练函数
def trainNB0(trainMatrix, trainCategory):
    numTrainDocs = len(trainMatrix)
    numWords = len(trainMatrix[0])
    pAbusive = sum(trainCategory) / float(numTrainDocs)  # 侮辱性单词出现的概率
    p0Num = ones(numWords)  # 为了防止出现 0 * x 的情况
    p1Num = ones(numWords)
    p0Denom = 2.0  # 为了防止分母为0的情况
    p1Denom = 2.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 = log(p1Num / p1Denom)  # 计算每个词语是侮辱性词汇的概率
    p0Vect = log(p0Num / p0Denom)  # log是为了防止数据太小导致下溢出
    return p0Vect, p1Vect, pAbusive

3.3分类器

根据我们确定的计算概率公式,计算出它各个分类的可能性然后进行比较,返回我们最好的决定。

# 朴素贝叶斯分类函数
def classifyNB(vec2Classify, p0Vec, p1Vec, pClass1):
    p1 = sum(vec2Classify * p1Vec) + log(pClass1)
    p0 = sum(vec2Classify * p0Vec) + log(1.0 - pClass1)
    if p1 > p0:
        return 1
    else:
        return 0

4.完整代码

import re
from numpy import *


# 创建词汇表
def createVocabList(dataSet):
    vocabSet = set([])  # 创建一个无序且不重复的集合对象
    for document in dataSet:
        vocabSet = vocabSet | set(document)  # 创建两个集合的并集
    return list(vocabSet)


# 朴素贝叶斯词袋模型 setOfWords2Vec 升级版本
def bagOfWords2VecMN(vocabList, inputSet):
    returnVec = [0] * len(vocabList)  # 创建一个长度为vocabList的列表returnVec,并将列表中的每个元素都初始化为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 = ones(numWords)  # 为了防止出现 0 * x 的情况
    p1Num = ones(numWords)
    p0Denom = 2.0  # 为了防止分母为0的情况
    p1Denom = 2.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 = log(p1Num / p1Denom)  # 计算每个词语是侮辱性词汇的概率
    p0Vect = log(p0Num / p0Denom)  # log是为了防止数据太小导致下溢出
    return p0Vect, p1Vect, pAbusive


# 朴素贝叶斯分类函数
def classifyNB(vec2Classify, p0Vec, p1Vec, pClass1):
    p1 = sum(vec2Classify * p1Vec) + log(pClass1)
    p0 = sum(vec2Classify * p0Vec) + log(1.0 - pClass1)
    if p1 > p0:
        return 1
    else:
        return 0


# 邮件裁词
def textParse(bigString):
    regEx = re.compile(r'\W')
    listOfTokens = regEx.split(bigString)
    var = [tok.lower() for tok in listOfTokens if len(tok) > 2]  # 只返回长度大于2的情况
    return var


def spamTest():
    docList = []
    classList = []
    fullText = []
    # 导入并解析文本文件
    for i in range(1, 26):  # 因为每个文件夹各有文件25个
        wordList = textParse(open('email/spam/%d.txt' % i, encoding="ISO-8859-1").read())  # 读取文件
        docList.append(wordList)  # 用于存储所有文件的单词列表,二维数组
        fullText.extend(wordList)  # 这个列表用于存储所有文件的单词,将它们合并成一个大的单词列表
        classList.append(1)
        wordList = textParse(open('email/ham/%d.txt' % i, encoding="ISO-8859-1").read())
        docList.append(wordList)
        fullText.extend(wordList)
        classList.append(0)
    vocabList = createVocabList(docList)  # 创建不重复的词汇表
    trainingSet = list(range(50))  # 训练集的索引,0-49(因为ham和spam各有25个,故一共有50个)
    trainingSet = trainingSet
    testSet = []
    # 随机构造训练集
    for i in range(10):  # 表示只创建10个测试集、剩下的都是训练集
        # 函数生成一个0到len(trainingSet)之间的随机浮点数,然后取整,得到一个随机的索引值randIndex。
        randIndex = int(random.uniform(0, len(trainingSet)))
        testSet.append(trainingSet[randIndex])  # 将随机选择的索引值randIndex添加到测试集列表testSet中。
        trainingSet.pop(randIndex)  # 删除训练集中对应的测试集的索引值,以确保不会重复选择作为测试集。
    trainMat = []
    trainClasses = []
    for docIndex in trainingSet:
        # 将选中的训练集索引的文本,和词汇表进行对比计算词汇出现的次数
        trainMat.append(bagOfWords2VecMN(vocabList, docList[docIndex]))
        trainClasses.append(classList[docIndex])  # 将文档的类别标签添加到trainClasses列表中
    # 调用trainNB0函数,传入训练数据的特征向量和类别标签,进行朴素贝叶斯训练,得到分类器的参数
    p0V, p1V, pSpam = trainNB0(array(trainMat), array(trainClasses))
    errorCount = 0  # 计算错误次数,初始值为0
    # 对测试集分类
    for docIndex in testSet:
        wordVector = bagOfWords2VecMN(vocabList, docList[docIndex])  # 计算出现次数
        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))


if __name__ == '__main__':
    spamTest()

结果截图:
在这里插入图片描述

5.总结

个人感觉该方法的分类准确率,对准备的数据以及概率计算方法很敏感,可能一点点的改变就会导致最后的统计结果天差地别。总的来说,朴素贝叶斯并不难,代码也相对比较好理解,这一章就顺利通过啦!

你可能感兴趣的:(机器学习,机器学习,人工智能)