《机器学习实战》学习笔记(三)之朴素贝叶斯(下)过滤垃圾邮件、获取区域倾向、搜狗新闻分类 (sklearn)

本文介绍如何使用朴素贝叶斯算法进行垃圾邮件过滤、个人广告区域倾向分析及新闻分类,并探讨其优缺点。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

转载请注明作者和出处:http://blog.youkuaiyun.com/john_bh/
运行平台: Windows
Python版本: Python3.6
IDE: Sublime text3

一、前言

上篇文章讲解了朴素贝叶斯的基础理论,并且通过使用朴素贝叶斯进行文档分类的实例进行了实战演示,那么,接下来这文章基于上篇文章的基础,进行讲解三个实例:使用朴素贝叶斯算法过滤垃圾邮件、使用朴素贝叶斯分类器从个人广告中获取区域倾向、使用朴素贝叶斯算法进行新浪新闻分类 (sklearn)。

二、使用朴素贝叶斯算法过滤垃圾邮件

在上篇文章那个简单的例子中,我们引入了字符串列表。使用朴素贝叶斯解决一些现实生活中的问题时,需要先从文本内容得到字符串列表,然后生成词向量。下面这个例子中,我们将了解朴素贝叶斯的一个最著名的应用:电子邮件垃圾过滤。

使用朴素贝叶斯对电子邮件进行分类的步骤:

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

2.1 准备数据:切分文本

数据地址:
有两个文件夹ham和spam,spam文件下的txt文件为垃圾邮件。
上一篇文章介绍了如何创建词向量,并基于这些词向量进行朴素贝叶斯分类的过程。之前的词向量是预先给定的,这里我们将学习如何从文本文档中构建自己的词列表。
对于一个文本字符串,可使用python的string.split()方法将其切分,新建文件FilterEmail.py,编写代码如下:

# -*- coding: UTF-8 -*-
import re

"""
函数说明:创建实验样本
Parameters:
    无
Returns:
    docList - 整理的样本数据集
    classList - 类别标签
"""
def loadDataSet():
    docList = []; classList = []
    for i in range(1, 26):        #遍历25个txt文件
        wordList = textParse(open('email/spam/%d.txt' % i, 'r').read())  #读取每个垃圾邮件,并字符串转换成字符串列表
        docList.append(wordList)
        classList.append(1)     #标记垃圾邮件,1表示垃圾文件
        wordList = textParse(open('email/ham/%d.txt' % i, 'r').read())   #读取每个非垃圾邮件,并字符串转换成字符串列表
        docList.append(wordList)
        classList.append(0)     #标记非垃圾邮件,1表示垃圾文件  
    return docList,classList

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

"""
函数说明:将切分的实验样本词条整理成不重复的词条列表,也就是词汇表
Parameters:
    docList - 整理的样本数据集
Returns:
    vocabList - 返回不重复的词条列表,也就是词汇表
"""
def createVocabList(dataSet):
    vocabSet = set([])     #创建一个空的不重复列表
    for document in dataSet: 
        vocabSet = vocabSet | set(document) #取并集
    return list(vocabSet)

if __name__ == '__main__':
    docList,classList=loadDataSet()
    #print (docList)
    #print (classList)
    vocabList = createVocabList(docList)    #创建词汇表,不重复
    print(vocabList)

运行结果如下图2.1 所示:


这里写图片描述
图2.1 运行结果

2.2 测试算法:使用朴素贝叶斯进行交叉验证

# -*- coding: UTF-8 -*-
import re
import numpy as np
import random
"""
函数说明:创建实验样本
Parameters:
    无
Returns:
    docList - 整理的样本数据集
    classList - 类别标签
"""
def loadDataSet():
    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.extend(wordList)
        classList.append(1)     #标记垃圾邮件,1表示垃圾文件

        wordList = textParse(open('email/ham/%d.txt' % i, 'r').read())   #读取每个非垃圾邮件,并字符串转换成字符串列表
        docList.append(wordList)
        fullText.extend(wordList)
        classList.append(0)     #标记非垃圾邮件,1表示垃圾文件  
    return docList,classList,fullText

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

"""
函数说明:将切分的实验样本词条整理成不重复的词条列表,也就是词汇表
Parameters:
    docList - 整理的样本数据集
Returns:
    vocabList - 返回不重复的词条列表,也就是词汇表
"""
def createVocabList(dataSet):
    vocabSet = set([])     #创建一个空的不重复列表
    for document in dataSet: 
        vocabSet = vocabSet | set(document) #取并集
    return list(vocabSet)

"""
函数说明:根据vocabList词汇表,将inputSet向量化,向量的每个元素为1或0
Parameters:
    vocabList - createVocabList返回的列表
    inputSet - 切分的词条列表
Returns:
    returnVec - 文档向量,词集模型
"""
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词汇表,构建词袋模型
Parameters:
    vocabList - createVocabList返回的列表
    inputSet - 切分的词条列表
Returns:
    returnVec - 文档向量,词袋模型
"""
def bagOfWords2Vec(vocabList,inputSet):
    returnVec = [0] * len(vocabList) #创建一个和词汇表等长的向量,其中所含元素都为0
    for word in inputSet:    #遍历文档中的每个词条
        if word in vocabList:    #如果词条存在于词汇表中,则计数加一
            returnVec[vocabList.index(word)]+=1
    return returnVec    #返回词袋模型

"""
函数说明:朴素贝叶斯分类器训练函数
Parameters:
    trainMatrix - 训练文档矩阵,即setOfWords2Vec返回的returnVec构成的矩阵
    trainCategory - 训练类别标签向量,即loadDataSet返回的classList
Returns:
    p0Vect - 垃圾邮件类的条件概率数组
    p1Vect - 非垃圾邮件类的条件概率数组
    pAbusive - 文档属于垃圾邮件类的概率
"""
def trainNB0(trainMatrix,trainCategory):
    numTrainDocs = len(trainMatrix)      #计算训练的文档数目
    numWords = len(trainMatrix[0])     #计算每篇文档的词条数
    pAbusive = sum(trainCategory)/float(numTrainDocs)     #文档属于垃圾邮件类的概率
    p0Num = np.ones(numWords)  #创建numpy.ones数组,词条出现数初始化为1,拉普拉斯平滑
    p1Num = np.ones(numWords)
    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    #返回属于垃圾邮件类的条件概率数组,属于非垃圾邮件类的条件概率数组,文档属于垃圾邮件类的概率

"""
函数说明:朴素贝叶斯分类器分类函数
Parameters:
    vec2Classify - 待分类的词条数组
    p0Vec - 垃圾邮件类的条件概率数组
    p1Vec -非垃圾邮件类的条件概率数组
    pClass1 - 文档属于垃圾邮件类的概率
Returns:
    0 - 属于非垃圾邮件类
    1 - 属于垃圾邮件类
"""
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

"""
函数说明:对贝叶斯垃圾邮件分类器进行自动化处理
Parameters:
    vec2Classify - 待分类的词条数组
    p0Vec - 垃圾邮件类的条件概率数组
    p1Vec -非垃圾邮件类的条件概率数组
    pClass1 - 文档属于垃圾邮件类的概率
Returns:
    0 - 属于非垃圾邮件类
    1 - 属于垃圾邮件类
"""
def spamTest():
    docList,classList,fullText=loadDataSet()
    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__':
    docList,classList,fullText=loadDataSet()
    #print (docList)
    #print (classList)
    vocabList = createVocabList(docList)    #创建词汇表,不重复
    #print(vocabList)
    spamTest()

运行结果如下图2.2 所示:


这里写图片描述
图2.2 运行结果

函数是spamTest()对贝叶斯垃圾邮件分类器进行自动化处理,函数spamTest()会输出在10封随机选择的电子邮件上的分类错误概率。既然这些电子邮件是随机选择的,所以每次的输出结果可能有些差别。如果发现错误的话,函数会输出错误的文档的此表,这样就可以了解到底是哪篇文档发生了错误。如果想要更好地估计错误率,那么就应该将上述过程重复多次,比如说10次,然后求平均值。相比之下,将垃圾邮件误判为正常邮件要比将正常邮件归为垃圾邮件好。

三、使用朴素贝叶斯分类器从个人广告中区域倾向

使用朴素贝叶斯来发现地域相关的用词算法实现步骤:

  1. 收集数据:从RSS源收集内容,这里需要对RSS源构建一个接口
  2. 准备数据:将文本解析成词条向量
  3. 分析数据:检查词条确保解析的正确性
  4. 训练算法:使用之前创建的trainNB0()函数
  5. 测试算法:观察错误率,确保分类器可用,可以修改切分程序,以降低错误率,提高分类结果
  6. 使用算法:构建一个完整的程序,封装所有内容,给定两个RSS源,该程序会显示最常用的公共词。

四、使用朴素贝叶斯算法进行搜狗新闻分类 (sklearn)

4.1 项目思路

一、读取文件,对文件进行处理:
    1.遍历每一个文件夹下的每一个文件,利用结巴分词,构造原始的特征数据集dataList和标签数据集classList
    2.通过zip压缩合并,将数据与标签对应压缩的到data_class_list数据集,并且打乱数据集的顺序
    3.切分训练数据集和测试数据集,得到80%的数据为训练数据集trainList和20%的数据为测试数据集testList
    4.训练集解压缩,得到训练特征数据集train_data_list,训练分类标签train_class_list  测试集解压缩,得到测试特征数据集test_data_list, 测试分类标签test_class_list 
    5.对训练集中的所有单词进行词频统计,并按降序排序。即将出现次数多的词语在前,出现次数少的词语在后进行排序。得到按词频降序排序的训练集列表all_words_list

二、选取文本特征
    all_words_list包含了很多标点符号,这些标点符号是不能作为新闻分类的特征的。
    至于去掉多少个高频词,我们可以通过观察去掉高频词个数和最终检测准确率的关系来确定。除此之外,去除数字,不把数字作为分类特征。同时,去除一些特定的词语,介词、代词、连词。
    通过方法words_dict()得到一个文本的特征集合feature_words,可以称作是词集。

三、将文本向量化
    通过feature_words将文本向量化,通过方法得到向量化的训练向量文本train_feature_list和测试向量文本test_feature_list

四、利用朴素贝叶斯分类器对文本进行分类,这里我们使用的sklearn中的MultinomialNB()进行分类

具体实现代码:

4.2 使用Sklearn构建朴素贝叶斯分类器

官方英文文档地址:http://scikit-learn.org/dev/modules/classes.html#module-sklearn.naive_bayes

这里写图片描述

在scikit-learn中,一共有3个朴素贝叶斯的分类算法类。分别是GaussianNB,MultinomialNB和BernoulliNB。其中GaussianNB就是先验为高斯分布的朴素贝叶斯,MultinomialNB就是先验为多项式分布的朴素贝叶斯,BernoulliNB就是先验为伯努利分布的朴素贝叶斯。

对于新闻分类,属于多分类问题。我们可以使用MultinamialNB()完成我们的新闻分类问题。MultinomialNB假设特征的先验概率为多项式分布,即如下式

P(Xj=Xjl|Y=Ck)=Xjl+λmk+nλ(61) (61) P ( X j = X j l | Y = C k ) = X j l + λ m k + n λ

其中, P(Xj=Xjl|Y=Ck) P ( X j = X j l | Y = C k ) 是第k个类别的第j维特征的第l个取值条件概率。 mk m k 是训练集中输出为第k类的样本个数。λ为一个大于0的常数,尝尝取值为1,即拉普拉斯平滑,也可以取其他值。

接下来,我们看下MultinamialNB这个函数,只有3个参数
这里写图片描述
参数说明如下:

  • alpha:浮点型可选参数,默认为1.0,其实就是添加拉普拉斯平滑,即为上述公式中的λ ,如果这个参数设置为0,就是不添加平滑;
  • fit_prior:布尔型可选参数,默认为True。布尔参数fit_prior表示是否要考虑先验概率,如果是false,则所有的样本类别输出都有相同的类别先验概率。否则可以自己用第三个参数class_prior输入先验概率,或者不输入第三个参数class_prior让MultinomialNB自己从训练集样本来计算先验概率,此时的先验概率为 P(Y=Ck)=mk/m P ( Y = C k ) = m k / m 。其中m为训练集样本总数量,mk为输出为第k类别的训练集样本数。
  • class_prior:可选参数,默认为None。

除此之外,MultinamialNB也有一些方法供我们使用:
这里写图片描述
MultinomialNB一个重要的功能是有partial_fit方法,这个方法的一般用在如果训练集数据量非常大,一次不能全部载入内存的时候。这时我们可以把训练集分成若干等分,重复调用partial_fit来一步步的学习训练集,非常方便。

GaussianNB和BernoulliNB也有类似的功能。 在使用MultinomialNB的fit方法或者partial_fit方法拟合数据后,我们可以进行预测。此时预测有三种方法,包括predict,predict_log_proba和predict_proba。predict方法就是我们最常用的预测方法,直接给出测试集的预测类别输出。predict_proba则不同,它会给出测试集样本在各个类别上预测的概率。容易理解,predict_proba预测出的各个类别概率里的最大值对应的类别,也就是predict方法得到类别。predict_log_proba和predict_proba类似,它会给出测试集样本在各个类别上预测的概率的一个对数转化。转化后predict_log_proba预测出的各个类别对数概率里的最大值对应的类别,也就是predict方法得到类别。

五、总结

朴素贝叶斯推断的一些优点:

  • 生成式模型,通过计算概率来进行分类,可以用来处理多分类问题。
  • 对小规模的数据表现很好,适合多分类任务,适合增量式训练,算法也比较简单。

朴素贝叶斯推断的一些缺点:

  • 对输入数据的表达形式很敏感。
  • 由于朴素贝叶斯的“朴素”特点,所以会带来一些准确率上的损失。
  • 需要计算先验概率,分类决策存在错误率。

适用数据类型:标称型数据

对于分类来讲,适用概率有时候要比适用硬规则更为有效贝叶斯概率以及贝叶斯准则提供了一种不敢利用已知值来估计未知概率的有效方法。

可以通过特征之间的条件独立假设,降低对数据量的而需求,独立性假设是指一个词的出现概率并不依赖于文档中的其他词,当然这假设在实际中有些简单,这也是称之为朴素贝叶斯的原因。

通过编程实现朴素贝叶斯算法,需要考虑很多实际因素,下移除就是其中一个问题,这里可以通过对概率取对数来解决。另外一个就是当训练样本不充分导致概率估值为0的问题,这里采用拉普拉斯平滑解决方法。词袋模型在解决文档分类问题上比词集模型有所提高,还有其他一些改进的方法,比如说移除停用词等。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值