查看原文
其他

第7.4节 多项式朴素贝叶斯原理与实现

空字符 月来客栈 2024-01-19

各位朋友大家好,欢迎来到月来客栈,我是掌柜空字符。

本期推送内容目录如下,如果本期内容对你有所帮助,欢迎点赞、转发支持!

  • 7.4 多项式朴素贝叶斯原理与实现
    • 7.4.1 算法思想
    • 7.4.2 算法原理
    • 7.4.3 计算示例
    • 7.4.4 多项式贝叶斯实现
    • 7.4.5 基于Multinomial的垃圾邮件分类
    • 7.4.6 小结
    • 引用

7.4 多项式朴素贝叶斯原理与实现

上一节内容中,笔者详细介绍了一种常见的朴素贝叶斯算法,也被称之为Categorical Naive Bayes。但实际上,”朴素贝叶斯“算法远不止这一种,而它们之间的主要区别在于对条件概率的处理上[1],即式(7.10)中的部分。因此在接下来的这节内容中,笔者将会介绍第2种基于朴素贝叶斯思想的分类模型,多项朴素贝叶斯(Multinomial Naive Bayes, MNB)。

7.4.1 算法思想

在通过Categorical NB来进行文本分类的场景中,在计算条件概率时都是将词表中的每个词以是否出现为标准进行类别化(Categorization)处理,因为如果将词频作为特征维度的取值类别,那么将会出现在测试集中特征维度的取值情况数大于训练集中的情况。

例如在训练集中“客栈”这个词出现的最大次数为10,那么模型在拟合过程中就会认为“客栈”这个维度的特征取值有10种情况,并以此进行建模;但是当测试集中的某个样本里“客栈”这个词出现的频次为11时,那么模型便会认为该维度多了一种取值情况,进而无法取到对应的条件概率。

通常,在利用词袋模型对文本进行向量化表示时词频也是一个重要的考量因素,而多项朴素贝叶斯算法在处理这一问题时则是将每个维度的词频在总词频中的占比来作为条件概率进行建模[2]。

7.4.2 算法原理

在MNB中,我们可以将类别下条件概率的分布参数化为这样的形式,其中表示训练集中的特征维度(例如在文本分类中则是词表的长度),则是类别下特征的条件概率。进一步,参数可以通过极大似然估计来计算得到[1]:

其中表示在整个训练集中样本属于这个类别下特征出现的频次,即表示类别下所有维度特征的总频次,即表示每个特征维度对应的平滑系数,表示所有平滑系数的总和[2]。但是在实际处理时通常会将每个维度的平滑系数设为相等,因此式(7.17)可以改写为:

其中表示每个特征维度的平滑系数。

在根据式(7.18)估计得到每个类别下各个特征的条件概率(词频占比)后,便可以通过式(7.10)来最大化后验概率以此确定样本的分类类别。可尽管如此这里依旧存在一个问题。通过式(7.10)可以知道,在最大化后验概率时各个特征维度的条件概率是进行的累乘操作,而在动则上千维的文本向量中,这样累乘计算得到的结果将会出现下溢的情况。

因此,常见的一种做法是在式(7.10)的两边同时取自然对数,且由于函数是单调的因此这并不影响最终的预测结果[3]。所以式(7.10)可以改写为如下形式

其中表示在类别下特征对应的条件概率。

进一步,根据,式(7.19)可以改写为

同时对于MNB算法来说,从式(7.18)可以看出,此时条件概率计算的是训练集中特征维度的词频占比(相当于模型参数),因此在最终计算后验概率时需要同时考虑到每个维度的词频情况,即[2]

其中表示特征维度对应的词频。

此时根据式(7.21)的形式来看,还可以将理解为特征在对应类别下重要性大小的权重,而先验概率则可以理解为数据集中各类别的相对频次(偏置),频次越大则当前样本越可能归属于该类别。因此,从这个角度看还可以将多项贝叶斯理解一个简单的线性模型。也正是因为这样的特性,MNB算法在处理TFIDF这类文本特征表示时依旧有着很好的效果[1]。

到此,对于多项贝叶斯算法的基本原理就介绍完了,下面笔者再来通过一个实际的计算示例来帮助大家更加清晰地理解。

7.4.3 计算示例

假设现在有一批基于词袋模型表示的文本数据,其一共包含有这3个特征维度,每个维度表示词表中相应词的词频,表示样本对应的所属类别,如表7-2所示。现需要预测这个样本的所属类别。

表 7-2. 示例计算数据

根据式(7.10),由表7-2易知,各个类别的先验概率的取值为

各类别下不同特征取值的条件概率的取值为(设,即拉普拉斯平滑)

其中分子中的表示在这个类别下,特征出现的总频次;分母分别表示在这个类别下所有特征维度的总频次,例如。同时,从这里也可以再次看出MNB算法在计算特征维度时考虑的是:类别中所有样本当前各个特征维度的频次总数在类别中所有样本的所有特征维度总频次中的占比,这样便能够计算得到对于类别来说哪个特征维度的重要性更高。

同理,对于其它情况来说,对应的条件概率的取值为

根据计算得到的先验概率、条件概率和式(7.20),样本的后验概率置信度为

同理,其它两个类别的后验概率置信度分别为

根据上述结果可知,样本应该属于类别

7.4.4 多项式贝叶斯实现

在有了前面Categorical贝叶斯算法的实现经验后,Multinomial贝叶斯的实现过程就非常容易理解了。下面,笔者依旧分步进行讲解实现。以下实现代码均参考自sklearn 0.24.0 中的MultinomialNB模块,只是对部分处理逻辑进行了修改与简化,完整代码见Book/Chapter07/C02_naive_bayes_multinomial.py文件。

1. 特征计数实现

过第7.4.2节的内容可知,不管是计算先验概率还是条件概率,在这之前都需要先统计训练集中各个样本及样本特征取值的分布情况。因此,这里首先需要初始化相关的计数器;然后再对样本和特征样本特征取值的分布情况进行统计。

如下代码所示便是对两个重要计数器初始化工作:

1 class MyMultinomialNB(object):
2         def __init__(self, alpha=0):
3         self.alpha = alpha
4         self._ALPHA_MIN = 1e-10
5     def _init_counters(self, ):
6         self.class_count_ = np.zeros(self.n_classes, dtype=np.float64)
7         self.feature_count_ = np.zeros((self.n_classes, self.n_features_),dtype=np.float64)

在上述代码中,第3~4行是初始化平滑项系数alpha。第6行class_count_被初始化成了一个形状为[n_classes,]的全零向量,其中n_classes表示分类的类别数量,而每个维度分别表示每个类别的样本数量(例如[2,2,3]表示0,1,2这三个类别的样本数分别是2,2,3),其目的是后续用于计算每个类别的先验概率。第7行feature_count_被初始化成了一个形状为[n_classes,n_features]的全零矩阵,其中feature_count_[i][j]表示,对于整个训练集来说,在第i个类别中第j个特征的出现频次。

例如:

1 feature_count_ = [[ 6. 31. 36.],[44. 22. 28.],[19. 10. 14.]]

那么feature_count_[i][j]表示,对于整个训练集来说,在第i个类别中第j个特征的出现频次为44。

在初始化完两个计数器之后,下面就可以来对样本类别和特征分布进行计数了,代码如下:

1     def _count(self, X, Y):
2         self.class_count_ += Y.sum(axis=0# shape [n_classes,]
3         # 计算得到每个类别下的样本数量
4         self.feature_count_ += np.dot(Y.T, X)  # [n_classes,n] @ [n,n_features_]

在上述代码中,第1行参数Y是原始标签经过one-hot编码后的形式,例如3分类问题中类别1会被编码成[0,1,0]的形式,因此Y的形状为[n,n_classes];第2行代码是计算得到每个类别对应的样本数量,形状为[n_classes,]。第4行则是统计每个类别下所有样本在各个特征维度下的取值频次。

例如:

1 Y = np.array([[100], 
2               [001]])  # [2,3]
3 X = np.array([[3729], 
4               [5788],
5               [2257]])
6 print(np.dot(Y.T,X))
7      [[1,0,1],    [[3729],  [[5 9 7 16]
8 ===>  [0,0,0],  @  [5788]] = [0 0 0 0 ]
9       [0,1,0]]     [2257]    [5 7 8 8 ]]

在上述代码中,等号右边第1行5的含义便是在训练样本中第0个类别下第0个特征总频次为3+2;第1行中的9则表示在训练样本中第0个类别下第1个特征总频次为7+2。

进一步,在计算得到训练集中样本及特征的分布情况后,便可以计算先验概率和条件概率。

2. 先验概率实现

先验概率实现较为简单,整体实现代码如下:

1     def _update_class_prior(self):
2         log_class_count = np.log(self.class_count_)
3         self.class_prior_ = log_class_count - np.log(self.class_count_.sum())

在上述代码中,第2~3便是用来计算各个类别的先验概率,并同时进行了取对数处理。

3. 条件概率实现

根据式(7.18)可知,条件概率实现过程如下:

继续滑动看下一个

第7.4节 多项式朴素贝叶斯原理与实现

空字符 月来客栈
向上滑动看下一个

您可能也对以下帖子感兴趣

文章有问题?点此查看未经处理的缓存