机器学习:NavieBayes练习

实验介绍

1.实验内容

本实验包括:

  • 基于朴素贝叶斯算法的言论过滤器
  • 基于朴素贝叶斯算法实现垃圾邮件过滤

2.实验目标

通过本实验掌握朴素贝叶斯算法原理,熟悉朴素贝叶斯算法的简单应用。

3.实验知识点

  • 朴素贝叶斯算法

4.实验环境

  • python 3.6.5
  • numpy 1.13.3
  • matplotlib 2.2.3

实验准备

点击屏幕右上方的下载实验数据模块,选择下载bayes_email.tgz到指定目录下,然后再依次选择点击上方的File->Open->Upload,上传刚才下载的数据集压缩包,再使用如下命令解压:

!tar -zxvf bayes_email.tgz
bayes_email/
bayes_email/spam/
bayes_email/spam/1.txt
bayes_email/spam/10.txt
bayes_email/spam/11.txt
bayes_email/spam/12.txt
bayes_email/spam/13.txt
bayes_email/spam/14.txt
bayes_email/spam/15.txt
bayes_email/spam/16.txt
bayes_email/spam/17.txt
bayes_email/spam/18.txt
bayes_email/spam/19.txt
bayes_email/spam/2.txt
bayes_email/spam/20.txt
bayes_email/spam/21.txt
bayes_email/spam/22.txt
bayes_email/spam/23.txt
bayes_email/spam/24.txt
bayes_email/spam/25.txt
bayes_email/spam/3.txt
bayes_email/spam/4.txt
bayes_email/spam/5.txt
bayes_email/spam/6.txt
bayes_email/spam/7.txt
bayes_email/spam/8.txt
bayes_email/spam/9.txt
bayes_email/ham/
bayes_email/ham/1.txt
bayes_email/ham/10.txt
bayes_email/ham/11.txt
bayes_email/ham/12.txt
bayes_email/ham/13.txt
bayes_email/ham/14.txt
bayes_email/ham/15.txt
bayes_email/ham/16.txt
bayes_email/ham/17.txt
bayes_email/ham/18.txt
bayes_email/ham/19.txt
bayes_email/ham/2.txt
bayes_email/ham/20.txt
bayes_email/ham/21.txt
bayes_email/ham/22.txt
bayes_email/ham/23.txt
bayes_email/ham/24.txt
bayes_email/ham/25.txt
bayes_email/ham/3.txt
bayes_email/ham/4.txt
bayes_email/ham/5.txt
bayes_email/ham/6.txt
bayes_email/ham/7.txt
bayes_email/ham/8.txt
bayes_email/ham/9.txt
# -*- coding: UTF-8 -*-
import re
import os
import numpy as np
import random
import matplotlib.pyplot as plt

【言论过滤器】

实验步骤:【言论过滤器】- 概述

以在线社区留言为例。为了不影响社区的发展,我们要屏蔽侮辱性的言论,所以要构建一个快速过滤器,如果某条留言使用了负面或者侮辱性的语言,那么就将该留言标志为内容不当。过滤这类内容是一个很常见的需求。对此问题建立两个类型:侮辱类和非侮辱类,使用1和0分别表示。

实验步骤:【言论过滤器】- 词条切分和词性标注

我们把文本看成单词向量或者词条向量,也就是说将句子转换为向量。考虑出现所有文档中的单词,再决定将哪些单词纳入词汇表或者说所要的词汇集合,然后必须要将每一篇文档转换为词汇表上的向量。简单起见,我们先假设已经将本文切分完毕,存放到列表中,并对词汇向量进行分类标注。编写代码如下:

def loadDataSet():
    """
    函数说明:创建实验样本
    Parameters:
        无
    Returns:
        postingList - 实验样本切分的词条
        classVec - 类别标签向量
    """
    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 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)

实验步骤:【言论过滤器】-训练朴素贝叶斯分类器

通过词条向量训练朴素贝叶斯分类器。

Tips:

  • 利用贝叶斯分类器对文档进行分类时,要计算多个概率的乘积以获得文档属于某个类别的概率。如果其中有一个概率值为0,那么最后的乘积也为0。为降低这种影响,可以将所有词的出现数初始化为1, 并将分母初始化为2。这种做法就叫做拉普拉斯平滑(Laplace Smoothing),是比较常用的平滑方法。

  • 下溢问题。由于太多很小的数相乘会导致下溢问题。两个小数相乘,越乘越小,这样就造成了下溢出。在程序中,许多很小的数相乘,最后四舍五入计算结果可能就变成0。为了解决这个问题,对乘积结果取自然对数。通过求对数可以避免下溢出或者浮点数舍入导致的错误。

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数组,
    p0Denom = 0.0; p1Denom = 0.0                            #分母初始化为0.0
    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 = p1Num/p1Denom                                  #相除        
    p0Vect = p0Num/p0Denom          
    return p0Vect,p1Vect,pAbusive                           #返回属于侮辱类的条件概率数组,属于非侮辱类的条件概率数组,文档属于侮辱类的概率

实验步骤:【言论过滤器】- 使用分类器进行分类

def classifyNB(vec2Classify, p0Vec, p1Vec, pClass1):
    """
    朴素贝叶斯分类器分类函数

    Parameters:
        vec2Classify - 待分类的词条数组
        p0Vec - 非侮辱类的条件概率数组
        p1Vec - 侮辱类的条件概率数组
        pClass1 - 文档属于侮辱类的概率

    Returns:
        0 - 属于非侮辱类
        1 - 属于侮辱类
    """
    # 计算文档属于侮辱类的概率
    p0 = sum(vec2Classify * p0Vec) + np.log(1 - pClass1)  # P(D|0) * P(0)
    p1 = sum(vec2Classify * p1Vec) + np.log(pClass1)       # P(D|1) * P(1)

    # 比较两个类别的概率
    if p1 > p0:
        return 1  # 属于侮辱类
    else:
        return 0  # 属于非侮辱类


def testingNB():
    """
    测试朴素贝叶斯分类器
    测试样本1:['love', 'my', 'dalmation']
    测试样本2:['stupid', 'garbage']
    """
    # 加载数据集
    postingList, classVec = loadDataSet()

    # 创建词汇表
    vocabList = createVocabList(postingList)

    # 将每篇文档转换为词汇表的向量
    trainMat = []
    for post in postingList:
        trainMat.append(setOfWords2Vec(vocabList, post))

    # 训练朴素贝叶斯分类器
    p0Vec, p1Vec, pAbusive = trainNB0(np.array(trainMat), np.array(classVec))

    # 测试样本1:['love', 'my', 'dalmation']
    testEntry1 = ['love', 'my', 'dalmation']
    testVec1 = setOfWords2Vec(vocabList, testEntry1)
    print("Test sample 1 classified as:", classifyNB(np.array(testVec1), p0Vec, p1Vec, pAbusive))

    # 测试样本2:['stupid', 'garbage']
    testEntry2 = ['stupid', 'garbage']
    testVec2 = setOfWords2Vec(vocabList, testEntry2)
    print("Test sample 2 classified as:", classifyNB(np.array(testVec2), p0Vec, p1Vec, pAbusive))

if __name__ == '__main__':
    testingNB()
Test sample 1 classified as: 0
Test sample 2 classified as: 1

【垃圾邮件过滤】

实验步骤:【垃圾邮件过滤】- 概述

使用朴素贝叶斯解决一些现实生活中的问题时,需要先从文本内容得到字符串列表,然后生成词向量。本实验中,我们将了解朴素贝叶斯的一个最著名的应用:电子邮件垃圾过滤。首先看一下使用朴素贝叶斯对电子邮件进行分类的步骤:

  • 收集数据:提供文本文件。
  • 准备数据:将文本文件解析成词条向量。
  • 分析数据:检查词条确保解析的正确性。
  • 训练算法:使用我们之前建立的trainNB()函数。
  • 测试算法:使用classifyNB()函数,并构建一个新的测试函数来计算文档集的错误率。
  • 使用算法:构建一个完整的程序对一组文档进行分类,将错分的文档输出到屏幕上。

数据集路径为:bayes_email,该目录下有两个文件夹:ham和spam,其中spam文件下的txt文件为垃圾邮件。

数据示例

实验步骤:【垃圾邮件过滤】- 文本切分

对于英文文本,我们可以以非字母、非数字作为符号进行切分,使用split函数即可。编写代码如下:

# 邮件裁词
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)

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)  # 删除训练集中对应的测试集的索引值,以确保不会重复选择作为测试集。

# 朴素贝叶斯词袋模型
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) + np.log(pClass1)
    p0 = sum(vec2Classify * p0Vec) + np.log(1.0 - pClass1)
    if p1 > p0:
        return 1
    else:
        return 0


实验步骤:【垃圾邮件过滤】- 分类

根据词汇表,将每个文本向量化。将数据集分为训练集和测试集,使用交叉验证的方式测试朴素贝叶斯分类器的准确性。编写代码如下:

import re

def textParse(bigString):
    """
    函数说明:接收一个字符串并将其解析为字符串列表

    Parameters:
        bigString - 字符串
    Returns:
        字符串列表(除了单个字母,例如大写的I,其它单词变成小写,同时过滤长度小于3的字符串)
    """
    # 使用正则表达式只保留字母和数字
    listOfTokens = re.split(r'\W+', bigString)  # \W+ 匹配非字母和非数字的字符

    # 将所有单词转化为小写,并过滤掉长度小于3的单词
    return [token.lower() for token in listOfTokens if len(token) > 2]

import numpy as np  # Import numpy

def trainNB(trainMatrix, trainCategory):
    """
    函数说明: 朴素贝叶斯分类器训练函数

    Parameters:
        trainMatrix - 训练文档矩阵,即setOfWords2Vec返回的returnVec构成的矩阵
        trainCategory - 训练类别标签向量,即loadDataSet返回的classVec
    Returns:
        p0Vect - 非侮辱类的条件概率数组
        p1Vect - 侮辱类的条件概率数组
        pAbusive - 文档属于侮辱类的概率
    """
    # 文档总数
    numDocs = len(trainMatrix)
    # 侮辱类的文档数量
    numAbusive = sum(trainCategory)
    
    # 计算文档属于侮辱类和非侮辱类的概率
    pAbusive = numAbusive / float(numDocs)
    
    # 词汇表大小(trainMatrix 是一个二维数组,每一行表示一个文档,每一列表示一个词汇表中的单词。)
    vocabSize = len(trainMatrix[0]) 
    
    # 初始化条件概率的分子和分母,就是上面提到的卡普拉斯平滑
    p0Num = np.ones(vocabSize)  # 非侮辱类词汇计数,初始化为1
    p1Num = np.ones(vocabSize)  # 侮辱类词汇计数,初始化为1
    p0Denom = 2.0  # 非侮辱类的总词数,初始化为词汇表大小
    p1Denom = 2.0  # 侮辱类的总词数
    
    # 遍历所有的训练数据
    for i in range(numDocs):
        if trainCategory[i] == 1:
            # 侮辱类
            p1Num += trainMatrix[i]  # 对应单词出现的次数加到侮辱类计数
            p1Denom += sum(trainMatrix[i])  # 侮辱类总词数
        else:
            # 非侮辱类
            p0Num += trainMatrix[i]  # 对应单词出现的次数加到非侮辱类计数
            p0Denom += sum(trainMatrix[i])  # 非侮辱类总词数
    
    # 计算每个词汇的条件概率
    p0Vect = np.log(p0Num / p0Denom)  # 对数似然
    p1Vect = np.log(p1Num / p1Denom)  # 对数似然
    
    return p0Vect, p1Vect, pAbusive

import os
from random import shuffle

def loadDataSet():
    """
    函数说明: 创建实验样本
    返回:postingList(切分的词条)、classVec(类别标签)
    """
    postingList = []
    classVec = []
    
    # 加载 spam 文件夹的邮件内容
    spamFiles = os.listdir('bayes_email/spam')
    for fileName in spamFiles:
        with open(f'bayes_email/spam/{fileName}', 'r', encoding='ISO-8859-1') as f:
            text = f.read()
            words = textParse(text)
            postingList.append(words)
            classVec.append(1)  # spam 的标签是 1
    
    # 加载 ham 文件夹的邮件内容
    hamFiles = os.listdir('bayes_email/ham')
    for fileName in hamFiles:
        with open(f'bayes_email/ham/{fileName}', 'r', encoding='ISO-8859-1') as f:
            text = f.read()
            words = textParse(text)
            postingList.append(words)
            classVec.append(0)  # ham 的标签是 0
    
    return postingList, classVec


def spamTest():
    """
    函数说明: 将数据集分为训练集和测试集,使用交叉验证的方式测试朴素贝叶斯分类器的准确性
    """
    # 加载数据集
    postingList, classVec = loadDataSet()
    
    # 创建词汇表
    vocabList = createVocabList(postingList)
    
    # 划分数据集,使用交叉验证
    data = list(zip(postingList, classVec))
    shuffle(data)  # 随机打乱数据集
    fold_size = len(data) // 5  # 5折交叉验证
    accuracies = []

    for i in range(5):
        # 分割训练集和测试集
        test_set = data[i*fold_size : (i+1)*fold_size]
        train_set = data[:i*fold_size] + data[(i+1)*fold_size:]
        
        # 提取训练集和测试集的内容和标签
        train_data, train_labels = zip(*train_set)
        test_data, test_labels = zip(*test_set)
        
        # 将训练集和测试集转化为词条向量
        train_matrix = [setOfWords2Vec(vocabList, doc) for doc in train_data]
        test_matrix = [setOfWords2Vec(vocabList, doc) for doc in test_data]
        
        # 训练朴素贝叶斯分类器
        p0Vec, p1Vec, pSpam = trainNB(train_matrix, list(train_labels))
        
        # 测试分类器的准确性
        correct_count = 0
        for i in range(len(test_matrix)):
            predicted_label = classifyNB(test_matrix[i], p0Vec, p1Vec, pSpam)
            if predicted_label == test_labels[i]:
                correct_count += 1
        
        # 计算当前折的准确率
        accuracy = correct_count / len(test_matrix)
        accuracies.append(accuracy)
    
    # 输出交叉验证的平均准确率
    avg_accuracy = sum(accuracies) / 5
    print(f'5-fold cross-validation average accuracy: {avg_accuracy:.2f}')
if __name__ == '__main__':
    spamTest()
5-fold cross-validation average accuracy: 0.92


posted @ 2025-01-16 15:16  hjdssj  阅读(57)  评论(0)    收藏  举报