【机器学习算法复现】sklrean朴素贝叶斯掉包实现文本分类,事件独立的苛刻条件下还是很厉害的

  • 贝叶斯分类是一类分类算法的总称,这类算法均以贝叶斯定理为基础,故统称为贝叶斯分类。而朴素朴素贝叶斯分类是贝叶斯分类中最简单,也是常见的一种分类方法。分类算法的内容是要求给定特征,让我们得出类别,这也是所有分类问题的关键。那么如何由指定特征,得到我们最终的类别,也是我们下面要讲的,每一个不同的分类算法,对应着不同的核心思想。​ 之所以称之为“朴素”,是因为贝叶斯分类只做最原始、最简单的假设:所有的特征之间是相对独立的

  • 在scikit-learn库,根据特征数据的先验分布不同,给我们提供了5种不同的朴素贝叶斯分类算法(sklearn.naive_bayes: Naive Bayes模块),分别是伯努利朴素贝叶斯(BernoulliNB),类朴素贝叶斯(CategoricalNB),高斯朴素贝叶斯(GaussianNB)、多项式朴素贝叶斯(MultinomialNB)、补充朴素贝叶斯(ComplementNB) 。sklearn.naive_bayes.MultinomialNB — scikit-learn 1.3.dev0 documentation,一般来说,如果样本特征的分布大部分是连续值,使用GaussianNB会比较好。如果如果样本特征的分大部分是多元离散值,使用MultinomialNB比较合适。而如果样本特征是二元离散值或者很稀疏的多元离散值,应该使用BernoulliNB。

  • 朴素贝叶斯分类的优点:算法逻辑简单,易于实现;分类过程中时空开销小。缺点:理论上,朴素贝叶斯模型与其他分类方法相比具有最小的误差率。但是实际上并非总是如此,这是因为朴素贝叶斯模型假设属性之间相互独立,这个假设在实际应用中往往是不成立的,在属性个数比较多或者属性之间相关性较大时,分类效果不好。而在属性相关性较小时,朴素贝叶斯性能最为良好。对于这一点,有半朴素贝叶斯之类的算法通过考虑部分关联性适度改进。

  • 监督学习分为生成模型 (generative model) 与判别模型 (discriminative model),贝叶斯方法正是生成模型的代表 (还有隐马尔科夫模型)。在概率论与统计学中,贝叶斯定理 (Bayes’ theorem)表达了一个事件发生的概率,而确定这一概率的方法是基于与该事件相关的条件先验知识 (prior knowledge)。而利用相应先验知识进行概率推断的过程为贝叶斯推断 (Bayesian inference)。

  • 例如,如果患癌症是与人的年龄相关的,那么使用贝叶斯方法,我们可以利用患癌症人群的年龄分布这个先验知识评判一个人患癌症的概率,这相比于不利用年龄信息去判断一个人是否患癌症会聪明得多。可见,上述过程是贝叶斯定理的一种实际应用,通常我们称之为:贝叶斯推断 (Bayesian inference)

  • 贝叶斯公式:

    • P ( B ∣ A ) = P ( A ∣ B ) P ( B ) P ( A ) P ( 类别 ∣ 特征 ) = P ( 特征 ∣ 类别 ) P ( 类别 ) P ( 特征 ) P(B|A)=\frac{P(A|B)P(B)}{P(A)}\\ P(类别|特征)=\frac{P(特征|类别)P(类别)}{P(特征)} P(BA)=P(A)P(AB)P(B)P(类别特征)=P(特征)P(特征类别)P(类别)

    • 条件概率 (conditional probability) 是指在事件 B 发生的情况下,事件 A 发生的概率。通常记为 P(A | B)。推导贝叶斯公式:

      • 条件概率: P ( A ∣ B ) = P ( A ∩ B ) P ( B ) 得到: P ( A ∩ B ) = P ( A ∣ B ) P ( B ) 同理: P ( A ∩ B ) = P ( B ∣ A ) P ( A ) 所以有: P ( A ∣ B ) P ( B ) = P ( B ∣ A ) P ( A ) 进一步有: P ( B ∣ A ) = P ( A ∣ B ) P ( B ) P ( A ) 条件概率:P(A|B)=\frac{P(A∩B)}{P(B)}\\ 得到:P(A∩B)=P(A|B)P(B)\\ 同理:P(A∩B)=P(B|A)P(A)\\ 所以有:P(A|B)P(B)=P(B|A)P(A)\\ 进一步有:P(B|A)=\frac{P(A|B)P(B)}{P(A)} 条件概率:P(AB)=P(B)P(AB)得到:P(AB)=P(AB)P(B)同理:P(AB)=P(BA)P(A)所以有:P(AB)P(B)=P(BA)P(A)进一步有:P(BA)=P(A)P(AB)P(B)

      • 贝叶斯公式中,P(B)称为"先验概率"(Prior probability),即在A事件发生之前,对B事件概率的一个判断。P(B|A)称为"后验概率"(Posterior probability),即在A事件发生之后,对B事件概率的重新评估。P(A|B)/P(A)称为"可能性函数"(Likelyhood),这是一个调整因子,使得预估概率更接近真实概率。所以,条件概率可以理解成下面的式子:后验概率=先验概率 x 调整因子这就是贝叶斯推断的含义。我们先预估一个"先验概率",然后加入实验结果,看这个实验到底是增强还是削弱了"先验概率",由此得到更接近事实的"后验概率"。因为在分类中,只需要找出可能性最大的那个选项,而不需要知道具体那个类别的概率是多少,所以为了减少计算量,全概率公式在实际编程中可以不使用

    • 而朴素贝叶斯推断,是在贝叶斯推断的基础上,对条件概率分布做了条件独立性的假设。因此可得朴素贝叶斯分类器的表达式。因为以自变量之间的独立(条件特征独立)性和连续变量的正态性假设为前提,就会导致算法精度在某种程度上受影响

  • 实际在机器学习的分类问题的应用中,朴素贝叶斯分类器的训练过程就是基于训练集 D 来估计类先验概率 P© ,并为每个属性估计条件概率 P(xi | c) 。这里就需要使用极大似然估计 (maximum likelihood estimation, 简称 MLE) 来估计相应的概率。算法流程:

    在这里插入图片描述

    • 若任务对预测速度要求较高,则对给定的训练集,可将朴素贝叶斯分类器涉及的所有概率估值事先计算好存储起来,这样在进行预测时只需要 “查表” 即可进行判别;

    • 若任务数据更替频繁,则可采用 “懒惰学习” (lazy learning) 方式,先不进行任何训练,待收到预测请求时再根据当前数据集进行概率估值;

    • 若数据不断增加,则可在现有估值的基础上,仅对新增样本的属性值所涉及的概率估值进行计数修正即可实现增量学习。

  • 用朴树贝叶斯实现一个文本分类

    • # %pip install jieba
      # %pip install re
      import pandas as pd
      import re  # 正则化匹配的包
      import jieba  # 中文分词的包
      from sklearn.feature_extraction.text import CountVectorizer # CountVectorizer是属于常见的特征数值计算类,是一个文本特征提取方法。对于每一个训练文本,它只考虑每种词汇在该训练文本中出现的频率。
      from sklearn.naive_bayes import MultinomialNB
      data_dir = './data/' # 数据所在目录
      stopwords_file = './data/stopwords.txt'
      df_train = pd.read_csv(data_dir+'train.txt', encoding='UTF-8', sep='\s', header=None,names=['label', 'content'], index_col=False,engine='python')
      df_train = df_train.dropna() # 过滤含有NaN的数据
      df_test = pd.read_csv(data_dir+'test.txt', encoding='UTF-8', sep='\s', header=None,names=['label', 'content'], index_col=False,engine='python')
      df_test = df_test.dropna() # 过滤含有NaN的数据
      print(df_train.head())
      print(df_train['label'].value_counts())
      print(df_train.shape, df_test.shape) # (300, 2), (80, 2)
      
    •    label                                            content
      # 数据集设计到zz敏感内容,不让发
      0    151
      1    149
      Name: label, dtype: int64
      (300, 2) (80, 2)
      
  • 数据说明:文本二分类,1和0,判断是否属于zz上的出访类事件ares5221/ALBERT_text_classification: 利用ALBERT实现文本二分类,判别是否属于政治上的出访类事件,提升模型训练和预测速度。 (github.com)

  • 文本预处理(清洗文本,分词,去除停用词)

    • # 保留文本中文、数字、英文、短横线
      def clear_text(text):
          p = re.compile(r"[^\u4e00-\u9fa5^0-9^a-z^A-Z\-、,。!?:;()《》【】,!\?:;[\]()]")  # 匹配不是中文、数字、字母、短横线的部分字符
          return p.sub('', text)  # 将text中匹配到的字符替换成空字符
      # 加载停用词表
      def load_stopwords_file(filename):
          print('加载停用词...')
          stopwords=pd.read_csv(filename, index_col=False, quoting=3, sep="\t", names=['stopword'], encoding='utf-8')
          #quoting:控制引用字符引用行为,QUOTE_MINIMAL (0), QUOTE_ALL (1), QUOTE_NONNUMERIC (2) or QUOTE_NONE (3).
          stopwords = set(stopwords['stopword'].values)  # 利用集合不重复元素的特征去重
          print('停用词表大小:', len(stopwords))
          return stopwords
      # 文本预处理:清洗,分词,并去除停用词
      def preprocess_text(df_content):
          stopwords_set = load_stopwords_file(stopwords_file) # 加载停用词表
          content_seg = []#分词后的content
          for i,text in enumerate(df_content): # 逐行处理
              text = clear_text(text.strip())  
              segs = jieba.lcut(text, cut_all=False)  # cut_all=False是精确模式,True是全模式;默认模式是False 返回分词后的列表
              segs = filter(lambda x: len(x.strip())>1, segs)  # 词长度要>1,没有保留标点符号
              segs = filter(lambda x: x not in stopwords_set, segs)
              # print(segs) # segs是一个filter object
              # segs = list(segs) # segs需要一次类似“持久化”的操作,否则每次被操作一次后segs就为空了
              content_seg.append(" ".join(segs))
          return content_seg
      df_train['content_seg'] = preprocess_text(df_train['content'])
      df_test['content_seg'] = preprocess_text(df_test['content'])
      print(df_train.head())  #
      print(df_train.shape)
      
    • Building prefix dict from the default dictionary ...
      加载停用词...
      停用词表大小: 2613
      Dumping model to file cache C:\Users\23105\AppData\Local\Temp\jieba.cache
      Loading model cost 0.884 seconds.
      Prefix dict has been built successfully.
      加载停用词...
      停用词表大小: 2613
         label                                            content  \
      # 数据集设计到zz敏感内容,csdn不让发 
                                               content_seg  
      # 数据集设计到zz敏感内容,csdn不让发
      (300, 3)
      
  • 抽取文本特征:使用词袋模型

    • vectorizer = CountVectorizer(analyzer='word', # 以词为粒度做ngram
                                   max_features=4000, # 保留最常见的4000个词
                                   )
      vectorizer.fit(df_train['content_seg'].tolist())  # 统计特征提取,抽取重复属性高的词语
      print('CountVectorizer train finished!')
      train_features = vectorizer.transform(df_train['content_seg'])
      
    • CountVectorizer train finished!
      
  • 训练贝叶斯模型(多项式贝叶斯)

    • bayes_classifier = MultinomialNB() # 使用多项式贝叶斯,实例化
      bayes_classifier.fit(train_features, df_train['label']) # 模型训练
      print('MultinomialNB train finished!')
      
    • MultinomialNB train finished!
      
  • 评价指标&测试

    • test_features = vectorizer.transform(df_test['content_seg'])
      result_num =bayes_classifier.score(test_features, df_test['label']) # Return the mean accuracy
      print(result_num)
      from sklearn.metrics import precision_score, recall_score, f1_score
      y_pred = bayes_classifier.predict(test_features)
      print('precision_score: %.3f' % precision_score(y_pred, df_test['label']))
      print('recall_score: %.3f' % recall_score(y_pred, df_test['label']))
      print('f1_score: %.3f' % f1_score(y_pred, df_test['label']))
      
    • 0.9375
      precision_score: 0.952
      recall_score: 0.930
      f1_score: 0.941
      
  • 交叉验证

    • df_data = df_train.append(df_test)
      print(df_data.shape)
      print(df_data.head())
      df_data = df_data.sample(frac=1.0) # shuffle 打乱顺序
      print(df_data.head())
      from sklearn.model_selection import StratifiedKFold # 导入多折验证的包
      import numpy as np
      def stratifiedkfold_cv(X, y, clf_class, shuffle=True, n_folds=5, **kwargs):
          skfold = StratifiedKFold(n_splits=n_folds, shuffle=shuffle, random_state=0)
          y_pred = y[:]
          for train_idx, test_idx in skfold.split(X, y):
              X_tr, y_tr = X[train_idx], y[train_idx] # 训练集
              X_te, y_te = X[test_idx], y[test_idx] # 测试集
              clf = clf_class(**kwargs)
              clf.fit(X_tr,y_tr)
              y_pred[test_idx] = clf.predict(X_te)
          return y_pred
      y_pred = stratifiedkfold_cv(vectorizer.transform(df_data['content_seg']),
                                  np.array(df_data['label']),
                                  MultinomialNB)
      print(precision_score(df_data['label'], y_pred))
      
    • (380, 3)
         label                                            content  \
      # 数据集设计到zz敏感内容,不让发
                                               content_seg  
      # 数据集设计到zz敏感内容,不让发 
           label                                            content  \
      # 数据集设计到zz敏感内容,不让发
                                                 content_seg  
      # 数据集设计到zz敏感内容,不让发
      0.8780487804878049
      
  • 贝叶斯网络又称信度网络,是Bayes方法的扩展,是目前不确定知识表达和推理领域最有效的理论模型之一。从1988年由Pearl提出后,已经成为近几年来研究的热点.。一个贝叶斯网络是一个有向无环图(Directed Acyclic Graph,DAG),由代表变量结点及连接这些结点有向边构成。结点代表随机变量,结点间的有向边代表了结点间的互相关系(由父结点指向其子结点),用条件概率进行表达关系强度,没有父结点的用先验概率进行信息表达。结点变量可以是任何问题的抽象,如:测试值,观测现象,意见征询等。适用于表达和分析不确定性和概率性的事件,应用于有条件地依赖多种控制因素的决策,可以从不完全、不精确或不确定的知识或信息中做出推理。

  • 【官方双语】贝叶斯定理,使概率论直觉化_哔哩哔哩_bilibili

  • 【官方双语】贝叶斯定理的简洁证明_哔哩哔哩_bilibili

例题分析,(网上流传很广的例子,自己也梳理一下)

  • 在这里插入图片描述

  • 现在给我们的问题是,如果一对男女朋友,男生想女生求婚,男生的四个特点分别是不帅,性格不好,身高矮,不上进,请你判断一下女生是嫁还是不嫁?

  • 问题转化:这是一个典型的分类问题,转为数学问题就是比较p(嫁|(不帅、性格不好、身高矮、不上进))与p(不嫁|(不帅、性格不好、身高矮、不上进))的概率,谁的概率大,我就能给出嫁或者不嫁的答案!

  • 这里我们联系到朴素贝叶斯公式:

    • P ( 嫁 ∣ ( 不帅、性格不好、身高矮、不上进 ) ) = P ( ( 不帅、性格不好、身高矮、不上进 ) ∣ 嫁 ) ∗ P ( 嫁 ) P ( 不帅、性格不好、身高矮、不上进 ) P(嫁|(不帅、性格不好、身高矮、不上进))=\frac{P((不帅、性格不好、身高矮、不上进)|嫁)*P(嫁)}{P(不帅、性格不好、身高矮、不上进)} P((不帅、性格不好、身高矮、不上进))=P(不帅、性格不好、身高矮、不上进)P((不帅、性格不好、身高矮、不上进))P()

    • 我们需要求p(嫁|(不帅、性格不好、身高矮、不上进),这是我们不知道的,但是通过朴素贝叶斯公式可以转化为好求的三个量,p(不帅、性格不好、身高矮、不上进|嫁)、p(不帅、性格不好、身高矮、不上进)、p(嫁)

    • **p(不帅、性格不好、身高矮、不上进|嫁) = p(不帅|嫁)*p(性格不好|嫁)p(身高矮|嫁)p(不上进|嫁),那么我就要分别统计后面几个概率,也就得到了左边的概率!

      • 为什么这个成立呢?这个等式成立的条件需要特征之间相互独立吧! \textcolor{red}{为什么这个成立呢?这个等式成立的条件需要特征之间相互独立吧!} 为什么这个成立呢?这个等式成立的条件需要特征之间相互独立吧!.对的!这也就是为什么朴素贝叶斯分类有朴素一词的来源,朴素贝叶斯算法是假设各个特征之间相互独立,那么这个等式就成立了!
  • 但是为什么需要假设特征之间相互独立呢?

    • 假如没有这个假设,那么我们对右边这些概率的估计其实是不可做的,这么说,我们这个例子有4个特征,其中帅包括{帅,不帅},性格包括{不好,好,爆好},身高包括{高,矮,中},上进包括{不上进,上进},那么四个特征的联合概率分布总共是4维空间,总个数为2*3*3*2=36个。计算机扫描统计还可以,但是现实生活中,往往有非常多的特征,每一个特征的取值也是非常之多,那么通过统计来估计后面概率的值,变得几乎不可做,这也是为什么需要假设特征之间独立的原因

    • 假如我们没有假设特征之间相互独立,那么我们统计的时候,就需要在整个特征空间中去找,比如统计p(不帅、性格不好、身高矮、不上进|嫁),就需要在嫁的条件下,去找四种特征全满足分别是不帅,性格不好,身高矮,不上进的人的个数,这样的话,由于数据的稀疏性,很容易统计到0的情况。 这样是不合适的。

  • 下面我将一个一个的进行统计计算(在数据量很大的时候,根据中心极限定理,频率是等于概率的,这里只是一个例子,所以我就进行统计即可)。

    • p(嫁) = 6/12(总样本数) = 1/2

    • p(不帅|嫁) = 3/6 = 1/2

    • p(性格不好|嫁)= 1/6

    • p(矮|嫁) = 1/6

    • p(不上进|嫁) = 1/6

    • p(不帅) = 4/12 = 1/3

    • p(性格不好) = 4/12 = 1/3

    • p(身高矮) = 7/12

    • p(不上进) = 4/12 = 1/3

  • 到这里,要求p(不帅、性格不好、身高矮、不上进|嫁)的所需项全部求出来了,下面我带入进去即可,

    • P ( 嫁 ∣ ( 不帅、性格不好、身高矮、不上进 ) ) = ( 1 / 2 ∗ 1 / 6 ∗ 1 / 6 ∗ 1 / 6 ∗ 1 / 2 ) / ( 1 / 3 ∗ 1 / 3 ∗ 7 / 12 ∗ 1 / 3 ) P(嫁|(不帅、性格不好、身高矮、不上进))= (1/2*1/6*1/6*1/6*1/2)/(1/3*1/3*7/12*1/3) P((不帅、性格不好、身高矮、不上进))=(1/21/61/61/61/2)/(1/31/37/121/3)
  • 求p(不嫁|不帅,性格不好,身高矮,不上进),完全一样的做法

    • p ( 不嫁 ∣ 不帅、性格不好、身高矮、不上进 ) = ( ( 1 / 6 ∗ 1 / 2 ∗ 1 ∗ 1 / 2 ) ∗ 1 / 2 ) / ( 1 / 3 ∗ 1 / 3 ∗ 7 / 12 ∗ 1 / 3 ) p (不嫁|不帅、性格不好、身高矮、不上进) = ((1/6*1/2*1*1/2)*1/2)/(1/3*1/3*7/12*1/3) p(不嫁不帅、性格不好、身高矮、不上进)=((1/61/211/2)1/2)/(1/31/37/121/3)
  • 于是有p (不嫁|不帅、性格不好、身高矮、不上进)>p (嫁|不帅、性格不好、身高矮、不上进),所以我们根据朴素贝叶斯算法可以给这个女生答案,是不嫁。

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

羞儿

写作是兴趣,打赏看心情

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值