机器学习实战 -- Task03. 朴素贝叶斯


一、算法概述

算法原理:

1.贝叶斯理论:

朴素贝叶斯是贝叶斯决策理论的一部分,所以讲述朴素贝叶斯之前有必要快速了解一下贝叶斯决策理论。

假设现在我们有一个数据集,它由两类数据组成,数据分布如图所示。

在这里插入图片描述
我们现在用p1(x,y)表示数据点(x,y)属于类别1(图中红色圆点表示的类别)的概率,用p2(x,y)表示数据点(x,y)属于类别2(图中蓝色三角形表示的类别)的概率,那么对于一个新数据点(x,y),可以用下面的规则来判断它的类别:

如果p1(x,y) > p2(x,y),那么类别为1
如果p1(x,y) < p2(x,y),那么类别为2

也就是说,我们会选择高概率对应的类别。这就是贝叶斯决策理论的核心思想,即选择具有最高概率的决策。已经了解了贝叶斯决策理论的核心思想,那么接下来,就是学习如何计算p1和p2概率。

2.条件概率:

p ( A ) : A 发 生 的 概 率 p(A):A发生的概率 p(A):A

p ( B ) : B 发 生 的 概 率 p(B):B发生的概率 p(B):B

p ( A ∣ B ) : B 条 件 下 , A 发 生 的 概 率 p(A|B):B条件下,A发生的概率 p(AB):BA

P ( A ∣ B ) = P ( A ∩ B ) P ( B ) P(A|B)={\frac{P(A\cap{B})}{P(B)}} P(AB)=P(B)P(AB)

因此:

P ( A ∩ B ) = P ( A ∣ B ) P ( B ) P(A\cap{B})=P(A|B)P(B) P(AB)=P(AB)P(B)

同理可得:

P ( A ∩ B ) = P ( B ∣ A ) P ( A ) P(A\cap{B})=P(B|A)P(A) P(AB)=P(BA)P(A)

所以:

P ( A ∣ B ) P ( B ) = P ( B ∣ A ) P ( A ) P(A|B)P(B)=P(B|A)P(A) P(AB)P(B)=P(BA)P(A)

即:

P ( A ∣ B ) = P ( B ∣ A ) P ( A ) P ( B ) P(A|B)={\frac{P(B|A)P(A)}{P(B)}} P(AB)=P(B)P(BA)P(A)
或:
P ( A i ∣ B ) = P ( B ∣ A i ) P ( A i ) P ( B ) P(A_i|B)={\frac{P(B|A_i)P(A_i)}{P(B)}} P(AiB)=P(B)P(BAi)P(Ai)

这就是条件概率的计算公式。

3.全概率公式:

P ( B ) = ∑ i = 1 N P ( B ∣ A i ) P ( A i ) P(B)=\sum_{i=1}^NP(B|A_i)P(A_i) P(B)=i=1NP(BAi)P(Ai)

故,贝叶斯公式也可写成如下形式:

P ( A ∣ B ) = P ( B ∣ A ) P ( A ) ∑ i = 1 N P ( B ∣ A i ) P ( A i ) P(A|B)={\frac{P(B|A)P(A)}{\sum_{i=1}^NP(B|A_i)P(A_i)}} P(AB)=i=1NP(BAi)P(Ai)P(BA)P(A)

4.贝叶斯推断:

对条件概率公式进行变形,可以得到如下形式:

P ( A ∣ B ) = P ( A ) P ( B ∣ A ) P ( B ) P(A|B)=P(A){\frac{P(B|A)}{P(B)}} P(AB)=P(A)P(B)P(BA)

我们把P(A)称为"先验概率"(Prior probability),即在B事件发生之前,我们对A事件概率的一个判断。

P(A|B)称为"后验概率"(Posterior probability),即在B事件发生之后,我们对A事件概率的重新评估。

P(B|A)/P(B)称为"可能性函数"(Likelyhood),这是一个调整因子,使得预估概率更接近真实概率。

所以,条件概率可以理解成下面的式子:

  • 后验概率 = 先验概率 x 调整因子

这就是贝叶斯推断的含义。我们先预估一个"先验概率",然后加入实验结果,看这个实验到底是增
强还是削弱了"先验概率",由此得到更接近事实的"后验概率"。

二、朴素贝叶斯算法简介

我们称之为“朴素”,是因为整个形式化过程只做最原始、最简单的假设。

优缺点:

  • 优点:在数据较少的情况下仍然有效,可以处理多类别问题。
  • 缺点:对于输入数据的准备方式较为敏感;由于朴素贝叶斯的“朴素”特点,所以会带来一些准确率上的损失。

适用数据类型:

  • 标称型数据

朴素贝叶斯的一般过程:

  • 收集数据:可以使用任何方法。本章使用RSS源。
  • 准备数据:需要数值型或者布尔型数据。
  • 分析数据:有大量特征时,绘制特征作用不大,此时使用直方图效果更好。
  • 训练算法:计算不同的独立特征的条件概率。
  • 测试算法:计算错误率。
  • 使用算法:一个常见的朴素贝叶斯应用是文档分类。可以在任意的分类场景中使用朴素贝叶斯分类器,不一定非要是文本。

Step1:收集数据

从文本中构建词向量

需要先拆分文本,然后将每一个文本片段表示为一个词条向量,其中值为1表示词条出现在文档中,0
表示词条未出现。

建立两个类别:侮辱类和非侮辱类,使用1和0分别表示

def loadDataSet():
    """
    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']]
    # #类别标签向量,代表侮辱性文字,0代表正常言论
    classVec = [0, 1, 0, 1, 0, 1]
    return postingList,classVec

输出结果:

在这里插入图片描述

Step2:准备数据

def createVocabList(dataSet):
    """
    将切分的实验样本词条整理成不重复的词条列表
    - - - -
    dataSet - 样本数据集
    """
    # 创建一个空集
    vocabSet=set([])
    for document in dataSet:
        # 取并集
        vocabSet=vocabSet | set(document)
    return  list(vocabSet)


def setOfWords2Vec(vocabList,inputSet):
    """
    根据词汇表,将inputSet向量化(变为0和1组成的向量)
    - - - -
    vocabList - 词汇表
    inputSet - 切分的词条列表
    """
    returnVec=[0]*len(vocabList)
    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

Step3:分析数据

if __name__=="__main__":
    postingList, classVec=loadDataSet()
    myVocabList=createVocabList(postingList)
    print("myVocabList:\n",myVocabList)
    print(setOfWords2Vec(myVocabList,postingList[0]))
    print(setOfWords2Vec(myVocabList,postingList[1]))
    print(setOfWords2Vec(myVocabList,postingList[2]))
    print(setOfWords2Vec(myVocabList,postingList[3]))

输出结果:

在这里插入图片描述

Step4:训练算法

从词向量计算概率

  • 利用上述贝叶斯公式:

P ( A i ∣ B ) = P ( B ∣ A i ) P ( A i ) P ( B ) P(A_i|B)={\frac{P(B|A_i)P(A_i)}{P(B)}} P(AiB)=P(B)P(BAi)P(Ai)

若朴素的假设所有词都相互独立:

P ( B 0 , B 1 , B 1 , B 2 , . . . , B N ∣ A i ) = P ( B 0 ∣ A i ) P ( B 1 ∣ A i ) P ( B 2 ∣ A i ) . . . P ( B n ∣ A i ) P(B_0,B_1,B_1,B_2,...,B_N | A_i)=P(B_0|A_i)P(B_1|A_i)P(B_2|A_i)...P(B_n|A_i) P(B0,B1,B1,B2,...,BNAi)=P(B0Ai)P(B1Ai)P(B2Ai)...P(BnAi)

首先,计算文档属于侮辱性文档(class=1)的概率,即P(1)。
因为这是一个二类分类问题,所以可以通过1-P(1)得到P(0)。
然后,在for循环中,要遍历训练集trainMatrix中的所有文档。一旦某个词语(侮辱性或正常词语)在某一文档中出现,则该词对应的个数(p1Num或者p0Num)就加1,而且在所有的文档中,该文档的总词数也相应加1。
最后,对每个元素除以该类别中的总词数。

def trainNB0(trainMatrix,trainCategory):
    # 计算训练的文档数目
    numTrainDocs = len(trainMatrix)
    # 计算每篇文档的词条数
    numWords = len(trainMatrix[0])
    # 文档属于侮辱类的概率
    pAbusive = sum(trainCategory)/float(numTrainDocs)
    # 创建numpy.zeros数组,词条出现数初始化为0
    p0Num = np.zeros(numWords);
    p1Num = np.zeros(numWords)
    # 分母初始化为0
    p0Denom = 0.0;
    p1Denom = 0.0
    for i in range(numTrainDocs):
        # 统计属于侮辱类的条件概率所需的数据,即P(B0|1),P(B1|1),P(B2|1)···
        if trainCategory[i] == 1:
            p1Num += trainMatrix[i]
            p1Denom += sum(trainMatrix[i])
        # 统计属于非侮辱类的条件概率所需的数据,即P(B0|0),P(B1|0),P(B2|0)···
        else:
            p0Num += trainMatrix[i]
            p0Denom += sum(trainMatrix[i])
    p1Vect = p1Num/p1Denom
    p0Vect = p0Num/p0Denom
    return p0Vect,p1Vect,pAbusive
if __name__=="__main__":
    postingList, classVec=loadDataSet()
    myVocabList=createVocabList(postingList)
    trainMat=[]
    for postinDoc in postingList:
        trainMat.append(setOfWords2Vec(myVocabList,postinDoc))
    p0V,p1V,pAb=trainNB0(trainMat,classVec)
    print('p0V:\n',p0V)
    print('p1V:\n',p1V)
    print('classVec:\n',classVec)
    print('pAb:\n',pAb)

输出结果:

在这里插入图片描述

Step5:测试算法

利用贝叶斯分类器对文档进行分类时,要计算多个概率的乘积以获得文档属于某个类别的概率,如果其中一个概率值为0,那么最后的乘积也为0。
为降低这种影响,可以将所有词的出现数初始化为1,并将分母初始化为2
将trainNB0()对应部分修改为:

 p0Num = np.ones(numWords)
    p1Num = np.ones(numWords)
    # 分母初始化为0
    p0Denom = 2.0
    p1Denom = 2.0

另一个遇到的问题是下溢出,这是由于太多很小的数相乘造成的。
一种解决办法是对乘积取自然对数。在代数中有ln(a*b) = ln(a)+ln(b),于是通过求对数可以避免下溢出或者浮点数舍入导致的错误。
将trainNB0()对应部分修改为:

 p1Vect = np.log(p1Num/p1Denom)
    p0Vect = np.log(p0Num/p0Denom)
def testingNB():
    # 创建实验样本
    listOPosts,listClass=loadDataSet()
    # 创建词汇表
    myVocabList=createVocabList(listOPosts)
    trainMat=[]
    # 将实验样本向量化
    for postinDoc in listOPosts:
        trainMat.append(setOfWords2Vec(myVocabList,postinDoc))
    # 训练朴素贝叶斯分类器
    p0V,p1V,pAb=trainNB0(np.array(trainMat),np.array(listClass))
    # 测试样本1
    testEntry=['love','my','dalmation']
    thisDoc = np.array(setOfWords2Vec(myVocabList, testEntry))
    print(testEntry, 'classified as:', classifyNB(thisDoc, p0V, p1V, pAb))
    # 测试样本2
    testEntry = ['stupid', 'garbage']
    thisDoc = np.array(setOfWords2Vec(myVocabList, testEntry))
    print(testEntry, 'classified as:', classifyNB(thisDoc, p0V, p1V, pAb))

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

文档词袋模型
目前为止,我们将每个词的出现与否作为一个特征,这可以被描述为词集模型(set-of-wordsmodel)。
如果一个词在文档中出现不止一次,这可能意味着包含该词是否出现在文档中所不能表达的某种信息,这种方法被称为词袋模型(bag-of-words model)。

下面给出基于词袋模型的朴素贝叶斯代码。它与函数setOfWords2Vec()几乎完全相同,唯一不同的是每当遇到一个单词时,它会增加词向量中的对应值,而不只是将对应的数值设为1。

#朴素贝叶斯词袋模型
def bagOfWords2VecMN(vocabList,inputSet):
    """
    根据词汇表,将inputSet向量化(词袋化)
    - - - -
    vocabList - 词汇表
    inputSet - 切分的词条列表
    """
    returnVec = [0] * len(vocabList)
    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

三、项目案例

项目概述

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

开发流程

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

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

def textParse(bigString):
    """
    将字符串转换为字符列表
    - - - -
    bigString - 一个含大写字母的字符串
    """
    # 将特殊符(非字母数字)作为切分标志进行字符串切分
    listOfTokens=re.split(r'\w*',bigString)
    # 除了单个字母,例如大写的I,其它单词变成小写
    return [tok.lower() for tok in listOfTokens if len(tok)>2]
2.测试算法:使用朴素贝叶斯进行交叉验证

本例中共有50封电子邮件,其中的10封电子邮件被随机选择为测试集。分类器所需要的概率计算只利用训练集中的文档来完成。

import random
def spamTest():
    """
   垃圾邮件分类测试函数
    """
    docList=[];classList=[];fullText=[]
    #遍历25个txt文件
    for i in range(1,26):
        # 读取每个垃圾邮件,并字符串转换成字符串列表
        worfList=textParse(open('C:/Users/answer/Desktop/机器学习/数据集/4.NaiveBayes/email/ham/%d.txt' % i, encoding='ISO-8859-1').read())
        docList.append(worfList)
        fullText.append(worfList)
        # 标记垃圾邮件,1表示垃圾文件
        classList.append(1)
        worfList = textParse(open('C:/Users/answer/Desktop/机器学习/数据集/4.NaiveBayes/email/spam/%d.txt' % i,encoding='ISO-8859-1').read())
        docList.append(worfList)
        fullText.append(worfList)
        # 用0标记非垃圾邮件
        classList.append(0)
    # 词汇表
    vocabList=createVocabList(docList)
    # 存储训练集的索引值的列表和测试集的索引值的列表
    trainingSet=list(range(50));testSet=[]
    # 从50个邮件中,随机挑选出10个做测试集
    for i in range(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.0
    for index in testSet:
        wordVector=setOfWords2Vec(vocabList,docList[docIndex])
        if classifyNB(np.array(wordVector),p0v,p1v,pSpam)!=classList[docIndex]:
            errorCount+=1
    print('错误率:%.2f%%' % (float(errorCount) / len(testSet) * 100))

输出结果:

在这里插入图片描述

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

1.中文语句切分

考虑一个问题,英文的语句可以通过非字母和非数字进行切分,但是汉语句子呢?就比如我打的这一堆字,该如何进行切分呢?我们自己写个规则?

幸运地是,这部分的工作不需要我们自己做了,可以直接使用第三方分词组件,即jieba,没错就是”结巴”。

jieba已经兼容Python2和Python3,使用如下指令直接安装即可:

pip install jieba

准备完数据集,切分中文语句
在这里插入图片描述

# author:answer   time:2021/3/29

import numpy as mp
import os
import jieba

def TextProcessing(folder_path):
    folder_list=os.listdir(folder_path)  #查看folder_path下的文件
    data_list=[]   #训练集
    class_list=[]

    #遍历每个子文件夹
    for folder in folder_list:
        new_folder_path=os.path.join(folder_path,folder)  #根据子文件夹生成新的路径
        files=os.listdir(new_folder_path)                 #存放子文件夹下的txt文件列表

        j=1
        #遍历每个txt文件
        for file in files:
            if j>100:                                     #每类最多100个样本
                break
            with open(os.path.join(new_folder_path,file),'r',encoding='utf-8') as f:  #打开txt文件
                raw=f.read()

            word_out=jieba.cut(raw,cut_all=False)         #精简模式,返回一个可迭代的generator
            word_list=list(word_out)                      #generator转换为list

            data_list.append(word_list)
            class_list.append(folder)
            j+=1
            print(data_list)
            print(class_list)

if __name__=='__main__':
    #文本预处理
    folder_path='C:\\Users\\answer\\Desktop\\机器学习\\machine-learning-master\\Naive Bayes\\SogouC\\Sample'
    TextProcessing(folder_path)

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

2.文本特征选择

将所欲文本分成训练集和测试集,并按词频降序排序

# author:answer   time:2021/3/29

import numpy as mp
import os
import jieba
import random

def TextProcessing(folder_path,test_size=0.2):
    folder_list=os.listdir(folder_path)  #查看folder_path下的文件
    data_list=[]   #训练集
    class_list=[]  #标签集

    #遍历每个子文件夹
    for folder in folder_list:
        new_folder_path=os.path.join(folder_path,folder)  #根据子文件夹生成新的路径
        files=os.listdir(new_folder_path)                 #存放子文件夹下的txt文件列表

        j=1
        #遍历每个txt文件
        for file in files:
            if j>100:                                     #每类最多100个样本
                break
            with open(os.path.join(new_folder_path,file),'r',encoding='utf-8') as f:  #打开txt文件
                raw=f.read()

            word_out=jieba.cut(raw,cut_all=False)         #精简模式,返回一个可迭代的generator
            word_list=list(word_out)                      #generator转换为list

            data_list.append(word_list)
            class_list.append(folder)
            j+=1

    data_class_list=list(zip(data_list,class_list))   #zip压缩合并,将数据与标签对应压缩
    random.shuffle(data_class_list)                   #将data_clsaa_list乱序
    index=int(len(data_class_list)*test_size)+1       #训练集和测试集切分的索引值
    train_list=data_class_list[index:]                #训练集
    test_list=data_class_list[:index]                 #测试集
    train_data_list,train_class_list=zip(*train_list) #训练集压缩
    test_data_list,test_class_list=zip(*test_list)    #测试集压缩

    all_words_dict={}
    for word_list in train_data_list:
        for word in word_list:
            if word in all_words_dict.keys():
                all_words_dict[word]+=1
            else:
                all_words_dict[word]=1

    #根据键的值倒序排序
    all_words_tuple_list=sorted(all_words_dict.items(),key=lambda f:f[1],reverse=True)
    all_words_list,all_words_nums=zip(*all_words_tuple_list)   #解压缩
    all_words_list=list(all_words_list)                        #转换成列表
    return all_words_list,train_data_list,test_data_list,train_class_list,test_class_list



if __name__=='__main__':
    #文本预处理
    folder_path='C:/Users/answer/Desktop/机器学习/machine-learning-master/Naive Bayes./SogouC/Sample'  #训练集存放地址
    TextProcessing(folder_path)
    all_words_list, train_data_list, test_data_list, train_class_list, test_class_list=TextProcessing(folder_path,test_size=0.2)
    print(all_words_list)

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

检测结果存在大量无关且高频字词,例如:“的”,“在”,“了”,“是”…然后对这些字词进行去重。

# author:answer   time:2021/3/29

import numpy as mp
import os
import jieba
import random

def TextProcessing(folder_path,test_size=0.2):
    folder_list=os.listdir(folder_path)  #查看folder_path下的文件
    data_list=[]   #训练集
    class_list=[]  #标签集

    #遍历每个子文件夹
    for folder in folder_list:
        new_folder_path=os.path.join(folder_path,folder)  #根据子文件夹生成新的路径
        files=os.listdir(new_folder_path)                 #存放子文件夹下的txt文件列表

        j=1
        #遍历每个txt文件
        for file in files:
            if j>100:                                     #每类最多100个样本
                break
            with open(os.path.join(new_folder_path,file),'r',encoding='utf-8') as f:  #打开txt文件
                raw=f.read()

            word_out=jieba.cut(raw,cut_all=False)         #精简模式,返回一个可迭代的generator
            word_list=list(word_out)                      #generator转换为list

            data_list.append(word_list)
            class_list.append(folder)
            j+=1

    data_class_list=list(zip(data_list,class_list))   #zip压缩合并,将数据与标签对应压缩
    random.shuffle(data_class_list)                   #将data_clsaa_list乱序
    index=int(len(data_class_list)*test_size)+1       #训练集和测试集切分的索引值
    train_list=data_class_list[index:]                #训练集
    test_list=data_class_list[:index]                 #测试集
    train_data_list,train_class_list=zip(*train_list) #训练集压缩
    test_data_list,test_class_list=zip(*test_list)    #测试集压缩

    all_words_dict={}
    for word_list in train_data_list:
        for word in word_list:
            if word in all_words_dict.keys():
                all_words_dict[word]+=1
            else:
                all_words_dict[word]=1

    #根据键的值倒序排序
    all_words_tuple_list=sorted(all_words_dict.items(),key=lambda f:f[1],reverse=True)
    all_words_list,all_words_nums=zip(*all_words_tuple_list)   #解压缩
    all_words_list=list(all_words_list)                        #转换成列表
    return all_words_list,train_data_list,test_data_list,train_class_list,test_class_list

def MakeWordSet(word_file):
    """
    读取文件里的内容,并去重
    :param word_file: 文件路径
    :return: words_set 读取的内容的set集合
    """
    words_set=set()                                            #创建set集合
    with open(word_file,'r',encoding='utf-8') as f:            #打开文件
        for line in f.readlines():                             #一行一行读取
            word=line.strip()                                  #去回车
            if len(word)>0:                                    #有文本,添加到words_set中
                words_set.add(word)
    return words_set                                           #返回处理结果

def words_dict(all_words_list,deleteN,stopwords_set=set()):
    """
    文本特征提取
    :param all_words_list: 训练集所有文本列表
    :param deleteN: 删除词频最高的deleteN个词
    :param stopwords_set: 指定结束语
    :return feature_words:特征集
    """
    feature_words=[]                               #特征列表
    n=1
    for t in range(deleteN,len(all_words_list),1):
        if n>1000:                                 #feature_word的维度为1000
            break
        #如果这个词不是数字,并且不是指定的结束语,并且单词长度大于1小于5,那么这个词可以作为特征词
        if not all_words_list[t].isdigit() and all_words_list[t] not in stopwords_set and 1<len(all_words_list[t])<5:
            feature_words.append(all_words_list[t])
        n+=1
    return feature_words

if __name__=='__main__':
    #文本预处理
    folder_path='C:/Users/answer/Desktop/机器学习/machine-learning-master/Naive Bayes./SogouC/Sample'  #训练集存放地址
    all_words_list, train_data_list, test_data_list, train_class_list, test_class_list=TextProcessing(folder_path,test_size=0.2)

    #生成stopwords_set
    stopwords_file='C:/Users/answer/Desktop/机器学习/machine-learning-master/Naive Bayes/stopwords_cn.txt'
    stopwords_set=MakeWordSet(stopwords_file)

    feature_words=words_dict(all_words_list,100,stopwords_set)
    print(feature_words)

执行结果:
在这里插入图片描述
从结果可以看出,已经过滤了大量高频且无关的字词,筛选出来的feature_words就是用于新闻分类的特征词,接下来将特征文本向量化,然后用于训练朴素贝叶斯分类器。

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

在scikit-learn中,一共有三个朴素贝叶斯的分类算法类:

  • GaussianNB(先验为高斯分布的朴素贝叶斯)
  • MultinomialNB(先验为多项式分布的朴素贝叶斯)
  • BernoulliNB(先验为伯努利分布的朴素贝叶斯)

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

p ( X j = x j l ∣ Y = C k ) = x j l + λ m k + n λ p(X_{j}=x_{jl}|Y=C_k)=\frac{x_{jl}+λ}{m_k+nλ}\qquad p(Xj=xjlY=Ck)=mk+nλxjl+λ

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

通过观察取不同的去掉前deleteN个高频词的个数与最终检测准确率的关系,确定deleteN的取值:

# author:answer   time:2021/3/29

import numpy as mp
import os
import jieba
import random
from sklearn.naive_bayes import MultinomialNB
import matplotlib.pyplot as plt

def TextProcessing(folder_path,test_size=0.2):
    """
    中文文本处理
    :param folder_path: 文本存放路径
    :param test_size: 测试集占比,默认展所有数据的百分之20
    :return: 
        all_words_list - 按词频降序排序的训练集列表
    train_data_list - 训练集列表
    test_data_list - 测试集列表
    train_class_list - 训练集标签列表
    test_class_list - 测试集标签列表
    """
    folder_list=os.listdir(folder_path)  #查看folder_path下的文件
    data_list=[]   #训练集
    class_list=[]  #标签集

    #遍历每个子文件夹
    for folder in folder_list:
        new_folder_path=os.path.join(folder_path,folder)  #根据子文件夹生成新的路径
        files=os.listdir(new_folder_path)                 #存放子文件夹下的txt文件列表

        j=1
        #遍历每个txt文件
        for file in files:
            if j>100:                                     #每类最多100个样本
                break
            with open(os.path.join(new_folder_path,file),'r',encoding='utf-8') as f:  #打开txt文件
                raw=f.read()

            word_out=jieba.cut(raw,cut_all=False)         #精简模式,返回一个可迭代的generator
            word_list=list(word_out)                      #generator转换为list

            data_list.append(word_list)
            class_list.append(folder)
            j+=1

    data_class_list=list(zip(data_list,class_list))   #zip压缩合并,将数据与标签对应压缩
    random.shuffle(data_class_list)                   #将data_clsaa_list乱序
    index=int(len(data_class_list)*test_size)+1       #训练集和测试集切分的索引值
    train_list=data_class_list[index:]                #训练集
    test_list=data_class_list[:index]                 #测试集
    train_data_list,train_class_list=zip(*train_list) #训练集压缩
    test_data_list,test_class_list=zip(*test_list)    #测试集压缩

    all_words_dict={}
    for word_list in train_data_list:
        for word in word_list:
            if word in all_words_dict.keys():
                all_words_dict[word]+=1
            else:
                all_words_dict[word]=1

    #根据键的值倒序排序
    all_words_tuple_list=sorted(all_words_dict.items(),key=lambda f:f[1],reverse=True)
    all_words_list,all_words_nums=zip(*all_words_tuple_list)   #解压缩
    all_words_list=list(all_words_list)                        #转换成列表
    return all_words_list,train_data_list,test_data_list,train_class_list,test_class_list

def MakeWordSet(word_file):
    """
    读取文件里的内容,并去重
    :param word_file: 文件路径
    :return: words_set 读取的内容的set集合
    """
    words_set=set()                                            #创建set集合
    with open(word_file,'r',encoding='utf-8') as f:            #打开文件
        for line in f.readlines():                             #一行一行读取
            word=line.strip()                                  #去回车
            if len(word)>0:                                    #有文本,添加到words_set中
                words_set.add(word)
    return words_set                                           #返回处理结果

def TextFeatures(train_data_list,test_data_list,feature_words):
    """
    根据feature_words将文本向量化
    :param train_data_list: 训练集
    :param test_data_list: 测试集
    :param feature_words: 特征集
    :return:
        train_feature_list 训练集向量化列表
        test_feature_list  测试集向量化列表
    """
    def text_feature(text,feature_words):
        text_words=set(text)                  #出现在特征集中则置1
        features=[1 if word in text_words else 0 for word in feature_words]
        return features
    train_feature_list=[text_feature(text,feature_words) for text in train_data_list]
    test_feature_list=[text_feature(text,feature_words) for text in test_data_list]
    return train_feature_list,test_feature_list


def words_dict(all_words_list,deleteN,stopwords_set=set()):
    """
    文本特征提取
    :param all_words_list: 训练集所有文本列表
    :param deleteN: 删除词频最高的deleteN个词
    :param stopwords_set: 指定结束语
    :return feature_words:特征集
    """
    feature_words=[]                               #特征列表
    n=1
    for t in range(deleteN,len(all_words_list),1):
        if n>1000:                                 #feature_word的维度为1000
            break
        #如果这个词不是数字,并且不是指定的结束语,并且单词长度大于1小于5,那么这个词可以作为特征词
        if not all_words_list[t].isdigit() and all_words_list[t] not in stopwords_set and 1<len(all_words_list[t])<5:
            feature_words.append(all_words_list[t])
        n+=1
    return feature_words

def TextClassifier(train_feature_list,test_feature_list,train_class_list,test_class_list):
    """
    新闻分类器
    :param train_feature_list: 训练集向量化的特征文本
    :param test_feature_list: 测试集向量化的特征文本
    :param train_class_list: 训练集分类标签
    :param test_class_list: 测试集分类标签
    :return: test_accuracy:分类器精度
    """
    classifier=MultinomialNB().fit(train_feature_list,train_class_list)
    test_accuracy=classifier.score(test_feature_list,test_class_list)
    return test_accuracy

if __name__=='__main__':
    #文本预处理
    folder_path='C:/Users/answer/Desktop/机器学习/machine-learning-master/Naive Bayes./SogouC/Sample'  #训练集存放地址
    all_words_list, train_data_list, test_data_list, train_class_list, test_class_list=TextProcessing(folder_path,test_size=0.2)

    #生成stopwords_set
    stopwords_file='C:/Users/answer/Desktop/机器学习/machine-learning-master/Naive Bayes/stopwords_cn.txt'
    stopwords_set=MakeWordSet(stopwords_file)

    test_accuracy_list=[]
    deleteNs=range(0,1000,20)
    for deleteN in deleteNs:
        feature_words=words_dict(all_words_list,deleteN,stopwords_set)
        train_feature_list,test_feature_list=TextFeatures(train_data_list,test_data_list,feature_words)
        test_accuracy=TextClassifier(train_feature_list,test_feature_list,train_class_list,test_class_list)
        test_accuracy_list.append(test_accuracy_list)

    plt.figure()
    plt.plot(deleteNs,test_accuracy_list)
    plt.title('Relationship of deleteNs and test_accuracy')
    plt.xlabel('delateNs')
    plt.ylabel('test_accuracy')
    plt.show()

五、总结

  • 在训练朴素贝叶斯分类器之前,要处理好训练集,文本的清洗还是有很多需要学习的东西。
  • 根据提取的分类特征将文本向量化,然后训练朴素贝叶斯分类器。
  • 可以通过特征之间的条件独立性假设,降低对数据量的需求
  • 利用现代编程语言来实现朴素贝叶斯时需要考虑很多实际因素。

https://jackcui.blog.csdn.net/article/details/77500679
https://github.com/Jack-Cherish/Machine-Learning

  • 1
    点赞
  • 2
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值