AI算法工程师 | 09机器学习-概率图模型(二)朴素贝叶斯算法与文本分类(1)

机器学习 - 概率图模型 之 朴素贝叶斯算法

一、贝叶斯与朴素贝叶斯算法

1、相关概念

贝叶斯公式

贝叶斯公式最早是由英国神学家贝叶斯提出来的,用来描述两个条件概率之间的关系。

贝叶斯公式

朴素贝叶斯算法

用示例理解朴素贝叶斯:

朴素贝叶斯

朴素贝叶斯的各特征间相互独立假设:

相互独立假设1

相互独立假设2

2、朴素贝叶斯分类的工作流程

朴素贝叶斯分类

朴素贝叶斯分类的工作流程

3、朴素贝叶斯的优缺点

朴素贝叶斯优点

  • 算法逻辑简单,易于实现(算法思路很简单,只要使用贝叶斯公式转化即可)
  • 分类过程中,时空开销小(假设特征相互独立,只会涉及到二维存储)

朴素贝叶斯缺点

  • 理论上,朴素贝叶斯模型与其他分类方法相比具有最小的误差率。但是实际上并非总是如此。
    • 这是因为朴素贝叶斯模型假设属性之间相互独立,这个假设在实际应用中往往是不成立的,在属性个数比较多或者属性之间相关性较大时,分类效果不好(改善:可先做数据的预处理,如 PCA,以减小相关性)。

:既然相互独立假设在实际应用中往往不成立,为何还需要朴素贝叶斯模型?

  • 朴素贝叶斯模型(Naive Bayesian Model,简称 NB) 的朴素(Naive)的含义是:“很简单很天真” 地假设样本特征彼此独立。

  • 这个假设现实中基本上不存在,但特征相关性很小的实际情况还是很多的,所以这个模型仍然能够工作得很好。

二、文本分类

在处理文本分类之前,引入两个概念:one-hot 编码、TF-IDF

1、one-hot 编码

one-hot 编码

one-hot 表达:

  • 说明:one-hot 表达是一种稀疏的表达方式。
  • 特点:相互独立地表示语料中的每个词。
    • 词与词在句子中的相关性被忽略了,这正符合朴素贝叶斯对文本的假设。

关于 one-hot 编码,推荐参考的文章有:

2、TF-IDF 词频-逆文档频率

概念的引入:(one-hot 编码存在一定的不足,下面对其进行改进)
TF-IDF1
TF-IDF2

TF-IDF 权重策略:
TF-IDF 权重策略

TF-IDF:
TF-IDF

3、代码:基于朴素贝叶斯实现文本分类(使用 Python 手动实现)

回顾

  • 在进行算法实现前,先来回顾一下朴素贝叶斯分类的工作流程:

朴素贝叶斯的工作流程

  • 代码实现思路:

思路

代码:( 工具:PyCharm,基于:python3 )

import numpy as np

"""
对 朴素贝叶斯模型(Naive Bayesian Model,简称 NB) 的实现,并做文本分类
⭐ 学习时间:2023.1.4;再次梳理:2023.1.25
"""

def loadDataSet():
    """
    一个给定的数据集(此处:未使用文本分词,而是直接给出了每句话分好词后的内容)
    · postingList:一个二维数组(x),每一个列表(每行)代表一句话分完词后的情况;
    · classVec:一维数组(y),代表某文章是否是'stupid'(1:该篇文章是;0:否)
    """
    postingList = [['my', 'dog', 'has', 'flea', 'problems', 'help', 'please'],
                   ['maybe', 'not', 'take', 'him', 'to', 'dog', 'park', 'stupid'],
                   ['my', 'dalmatian', 'is', 'so', 'cute', 'I', 'love', 'him', 'my'],
                   ['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]  # 1表示该文章属于辱骂性的('stupid'),0不是
    return postingList, classVec  # 相当于 返回 x, y


# 定义了一个类:朴素贝叶斯模型(Naive Bayesian Model,简称 NB)
class NBayes(object):
    def __init__(self):  # 构造函数
        self.vocabulary = []  # 词典
        self.idf = []
        self.tf = []
        self.tdm = 0  # p(x|yi)
        self.Pcates = {}  # p(yi)是一个类别词典
        self.labels = []  # 存放类别号
        self.doclength = 0  # 文档个数(文档长度)
        self.vocablen = 0  # 词典个数(词典长度)
        self.testset = 0  # 测试集

    def cate_prob(self, classVec):
        """
        计算在数据集中每个分类的概率 p(yi)
        """
        self.labels = classVec
        labeltemps = set(self.labels)  # 去重
        for labeltemp in labeltemps:  # 分别对每一种类别(分类)求概率,并赋值给 Pcates
            # p(yi) = 某类别的个数/类别总个数。其中:统计列表中重复的分类 self.labels.count(labeltemp) —— 这是 python 原生的一个方法
            self.Pcates[labeltemp] = float(self.labels.count(labeltemp)) / float(len(self.labels))

    def calc_wordfreq(self, trainset):
        """
        生成普通的词频向量(用的整型计数方式,未进行归一化,不是概率分布形式)
        """
        self.tf = np.zeros([self.doclength, self.vocablen])
        self.idf = np.zeros([1, self.vocablen])
        for index in range(self.doclength):
            for word in trainset[index]:
                self.tf[index, self.vocabulary.index(word)] += 1
            for signleword in set(trainset[index]):
                self.idf[0, self.vocabulary.index(signleword)] += 1

    def calc_tfidf(self, trainset):
        """
        TF-IDF (词频-逆文档频率)【目的:生成每篇文章的向量】
        """
        self.tf = np.zeros([self.doclength, self.vocablen])  # 定义 TF(文章行*词典列):初始化所有全为0,有多少篇文章就有多少行,列数为词典长度。
        self.idf = np.zeros([1, self.vocablen])  # 定义 IDF(1行*词典列):1 行,词典多长就有多少列。
        for index in range(self.doclength):  # 遍历每篇文章,取到每篇文章的索引号
            for word in trainset[index]:  # 取到每篇文章所对应的词的列表(遍历每篇文章的每个词)
                # 统计每篇文章中每个词所对应的在一篇文章中出现的次数。其中,self.vocabulary.index(word) 得到每个词所在词典中的索引号
                self.tf[index, self.vocabulary.index(word)] += 1
            # 消除不同句子长度导致的偏差。【求 TF 词频:一篇文章对应的向量(某词在文章中的出现次数) ÷ 一篇文章的长度 = 某一个给定的词语(某词)在该文章中出现的频率】
            self.tf[index] = self.tf[index] / float(len(trainset[index]))
            for singleword in set(trainset[index]):  # 把一篇文章内容拿出来后,做去重,得到一篇文章中不同的词
                # 统计整个语料库中每个词在几篇文章(文档)中出现
                self.idf[0, self.vocabulary.index(singleword)] += 1  # 对一篇文章中出现不同的词进行累加,加到 idf 中
        self.idf = np.log(float(self.doclength) / self.idf)  # IDF:逆文档频率。分子:语料库中文章的总数;分母:包含给定词语(某一个词)的文章数目; 之后取 log
        self.tf = np.multiply(self.tf, self.idf)  # 求的是 TF-IDF(= TF × IDF):词频-逆文档频率。

    def build_tdm(self):
        """
        按分类(类别)累计向量空间的每维值:p(x|yi)
        """
        self.tdm = np.zeros([len(self.Pcates), self.vocablen])  # 初始化,构建一个 类别行*词典列 的矩阵
        sumlist = np.zeros([len(self.Pcates), 1])  # 类别行(有多少类就有多少行)*1列
        for index in range(self.doclength):  # 遍历每篇文章
            # 将同一个类别的词向量空间值加和(相当于加权求和)
            self.tdm[self.labels[index]] += self.tf[index]  # labels[index]:某篇文章的类别;tf[index]:某篇文章的TF-IDF向量(包含每个词对应的权重)
            # 统计每个分类的总值,是一个标量
            sumlist[self.labels[index]] = np.sum(self.tdm[self.labels[index]])
        self.tdm = self.tdm / sumlist  # 生成 p(x|yi)

    def train_set(self, trainset, classVec):
        self.cate_prob(classVec)  # 计算每个分类在数据集中的概率:p(yi)
        self.doclength = len(trainset)  # 语料库中文档的个数
        tempset = set()  # 创建空的 set 是为了去重,生成词典
        # 生成词典
        # 下面是 python 列表生成式,其含义为:
        #   ·(双重for循环)先遍历 trainset 中的每篇文章doc,再遍历 每篇doc 得到里面的词word,最后将每个word 加入 tempset 中。
        #   · 添加完后,set 集合(tempset)中会有所有词取值完后的结果。由于生成完后列表本身是不需要的,所以未用变量去接它。
        [tempset.add(word) for doc in trainset for word in doc]
        self.vocabulary = list(tempset)  # 强转为 list 列表(列表带有顺序,我们可根据索引号去查询到列表中具体某个元素的值)
        self.vocablen = len(self.vocabulary)  # 求词典的个数
        # 计算词频数据集
        # self.calc_wordfreq(trainset) # 可用词频表达
        self.calc_tfidf(trainset)  # 也可用 TF-IDF 表达每篇文章中每个词的权重
        # 按分类累计向量空间的每维值 p(x|yi)
        self.build_tdm()

    def map2vocab(self, testdata):
        """
        将测试集映射到当前词典 【说明:此处是简化的方式,由于我们的测试集中只有一条样本,所以只计算了 count 值(词出现的次数)】
        (待改进之处:训练集中计算得是 TF-IDF,测试集中也应该计算 TF-IDF)
        """
        self.testset = np.zeros([1, self.vocablen])
        for word in testdata:  # 遍历文本中每个词,进行索引化赋值(累加:得到词在文本中出现的次数)
            self.testset[0, self.vocabulary.index(word)] += 1  # self.vocabulary.index(word):找到某词在词典中的索引号

    def predict(self, testset):
        """
        预测分类结果,输出预测的分类类别号
        """
        if np.shape(testset)[1] != self.vocablen:
            # 如果向量化的测试集长度与词典不相等,退出程序
            print('输入错误')
            exit(0)
        predvalue = 0
        preclass = ''  # 类别号
        for tdm_vect, keyclass in zip(self.tdm, self.Pcates):  # 相当于遍历字典
            # 遍历每个类别
            # P(x|yi) P(yi)
            print(testset, testset.shape)
            print(tdm_vect, len(tdm_vect))
            print(self.Pcates[keyclass])
            # temp 是对应位置的某个类别的一些概率乘积【P(x|yi)*P(yi)】的累加值(此处:可看成对数似然——原公式的连乘可变成添加log后的连加 又∵log是单调递增 ∴ 可不用管log,直接求累加值)
            temp = np.sum(testset * tdm_vect * self.Pcates[keyclass])
            # 计算最大分类值
            if temp > predvalue:
                predvalue = temp
                preclass = keyclass
        return preclass, temp


if __name__ == '__main__':
    dataSet, listClasses = loadDataSet()  # 加载数据,相当于 返回 x, y
    nb = NBayes()  # 通过构造函数构造了 nb 对象
    nb.train_set(dataSet, listClasses)  # 训练模型。调用对象中的 train_set 方法,并传入 x, y
    nb.map2vocab(dataSet[0])  # 使用模型,传入第一句话
    print(nb.predict(nb.testset))  # 预测

运行结果:

结果

补充:下面是上述代码中涉及的部分 python 知识点梳理

  • 在 NBayes 类里 train_set 函数中,[tempset.add(word) for doc in trainset for word in doc] 属于 python 的列表生成式

    • 含义:
      # 列表生成式:
      #(双重for循环)先遍历 trainset 中的每篇文章doc,再遍历 每篇doc 得到里面的词word;
      # 最后将每个word 加入 tempset(创建的set 集合)中。
      [tempset.add(word) for doc in trainset for word in doc]
      
    • 示例:列表生成式
      示例
  • 在 NBayes 类里 cate_prob 函数中,统计列表中重复的分类 self.labels.count(labeltemp) 涉及 python 的一个原生方法 count()

    • 含义:
      self.labels.count(labeltemp) # 统计列表(self.labels)中 labeltemp 类别 的个数
      
    • 示例: count() 用于统计某个元素在列表中出现的次数
      原生python

—— 说明:本文写于 2023.1.3、2023.1.24~1.25,文中内容基于 python3,使用工具 PyCharm 编写的代码

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值