朴素贝叶斯算法优化与 sklearn 实现

1. 引言

上一篇日志中,我们主要介绍了贝叶斯算法,并提供了 python 实践:
朴素贝叶斯算法的推导与实践

但运行上一篇日志中的示例,我们发现出现了下面的结果:

['love', 'my', 'dalmation'] 属于非侮辱类
['stupid', 'garbage'] 属于非侮辱类

这显然是不正确的,本文,我们就来解决这个问题,同时对算法进行优化并使用 sklearn 来实现算法的实践。

2. 拉普拉斯平滑

上一篇文章中,我们利用贝叶斯分类器对文档进行分类时,需要算多个概率的乘积以获得文档属于某个类别的概率,即计算 p(w0|1)  p(w1|1)  p(w2|1),只要有一个概率值为0,那么最终的结果就会随之变成 0,这就是上一篇文章中,算法运行结果两个测试用例都是非侮辱类的原因。
要降低这种影响,可以讲所有词的出现数初始化为 1,并将分母初始化为 2,这个做法就是拉普拉斯平滑。
我们将上一篇日志中代码的 trainNB0 方法中的 p0Num、p1Num、p0Denom、p1Denom 赋值语句改为:

p0Num = np.ones(vocabularysNum)
p1Num = np.ones(vocabularysNum)
p0Denom = 2.0
p1Denom = 2.0

3. 下溢出问题的解决

进行拉普拉斯平滑运算后,我们运行程序,仍然得出了两个测试样本均属于非侮辱类的结果,这是为什么呢?
我们查看最终计算出的 p0 和 p1 会发现,他们的结果都是 0,这又是为什么呢?
这是因为出现了另一个问题 – 下溢出。
我们的概率运算中,所有参与运算的概率都太小了,小数相乘会使运算的积进一步减小,最终结果向下溢出超出了计算机浮点数的精度,就都会变成 0。
解决办法很自然的可以想到 – 将乘法运算转换为加法运算,但如何在保证算法正确性的前提下进行转换呢?
在代数中,ln(a * b) = ln(a) + ln(b),同时,自然对数可以保证运算趋势的正确性:
#此处有图片

因此我们通过对数运算优化训练函数 trainNB0 与测试函数 classifyNB:

def trainNB0(trainMap, results):
    """
    朴素贝叶斯分类器训练函数

    :param trainMap: 训练文档矩阵
    :param results: 训练类别标签向量
    :return:
        p0Vect - 侮辱类的条件概率数组
        p1Vect - 非侮辱类的条件概率数组
        pAbusive - 文档属于侮辱类的概率
    """

    dataListNum = len(trainMap)
    vocabularysNum = len(trainMap[0])

    """ 计算文档属于侮辱词概率 """
    pAbusive = sum(results) / float(dataListNum)

    p0Num = np.ones(vocabularysNum)
    p1Num = np.ones(vocabularysNum)
    p0Denom = 2.0
    p1Denom = 2.0

    """ 将所有行按是否是侮辱类分别叠加,统计各个词出现的次数 """
    for i in range(dataListNum):
        if results[i] == 1:
            p1Num += trainMap[i]
            p1Denom += sum(trainMap[i])
        else:
            p0Num += trainMap[i]
            p0Denom += sum(trainMap[i])

    """ 计算概率 """
    p1Vect = np.log(p1Num / p1Denom)
    p0Vect = np.log(p0Num / p0Denom)
    return p0Vect, p1Vect, pAbusive


def classifyNB(vec2Classify, p0Vec, p1Vec, pClass1):
    """
    朴素贝叶斯分类器分类函数

    :param vec2Classify: 待分类的词条数组
    :param p0Vec: 侮辱类的条件概率数组
    :param p1Vec: 非侮辱类的条件概率数组
    :param pClass1: 文档属于侮辱类的概率
    :return: 是否属于侮辱类,0. 不属于,1. 属于
    """
    p0 = sum(vec2Classify * p0Vec) + np.log(1.0 - pClass1)
    print("p0: ", p0)
    p1 = sum(vec2Classify * p1Vec) + np.log(pClass1)
    print("p1: ", p1)
    if p1 > p0:
        return 1
    else:
        return 0

最终我们得到了正确的结果:

p0:  -7.694848072384611
p1:  -9.826714493730215
['love', 'my', 'dalmation'] 属于非侮辱类
p0:  -7.20934025660291
p1:  -4.702750514326955
['stupid', 'garbage'] 属于侮辱类

4. 朴素贝叶斯算法的优缺点

通过上一篇日志的介绍和本文的优化,我们了解了朴素贝叶斯算法的原理和应用,他是一种基于概率的分类器算法,可以用来处理不相干因子的多分类问题,例如根据词频进行文本分类等问题。
那么他又具有哪些优缺点呢?

4.1. 优点

  1. 算法原理和实现简单,通过概率分类
  2. 对小规模数据表现很好,适合多分类增量式训练任务

4.2. 缺点

  1. 对输入数据的表达形式很敏感
  2. 需要计算先验概率,分类决策存在错误率
  3. 要求样本之间相互独立,这就是“朴素”的意思,这个限制有时很难做到,或使用者误以为符合而造成错误的结果

5. 使用 sklearn 实现朴素贝叶斯算法

sklearn 提供了朴素贝叶斯算法的实现类 – sklearn.naive_bayes.MultinomialNB。
下面的列表中,我们将分类数称为 nc,将特征数称为 nf。

5.1. 构造参数

sklearn.naive_bayes.MultinomialNB 类构造参数
参数名类型可选参数默认值说明
alphafloat非负浮点数1拉普拉斯平滑系数
fit_priorbooleanTrue/FalseTrue是否使用先验分类概率
class_priorarrayNone 或array(nc*1)None如果指定 fit_prior 为 True,该参数用来提供先验概率

5.2. 类属性

sklearn.naive_bayes.MultinomialNB 类属性
属性名类型说明
class_log_prior_array(nc*1)每个分类的平滑对数先验概率
intercept_array(nc*1)将多项式朴素贝叶斯理解为线性模型时,与 class_log_prior_ 相同
feature_log_prob_array(nc*nf)每个分类的每个特征的对数先验概率(P(x_i|y))
coef_array(nc*nf)将多项式朴素贝叶斯理解为线性模型时,与 feature_log_prob_ 相同
class_count_array(nc*1)在拟合过程中每个分类的样本数
feature_count_array(nc*nf)在拟合过程中每个分类的每个特征的样本数

5.3. 类方法

  • fit(X, y[, sample_weight]) – 训练朴素贝叶斯模型
  • get_params([deep]) – 获取参数
  • set_params(**params) – 设置参数
  • partial_fit(X, y[, classes, sample_weight]) – 部分样本上的增量拟合
  • predict(X) – 预测
  • predict_log_proba(X) – 返回测试向量X的对数概率估计
  • predict_proba(X) – 返回测试向量X的概率估计
  • score(X, y[, sample_weight]) – 返回模型的平均精度

5.4. 示例

import numpy as np
from sklearn.naive_bayes import MultinomialNB

if __name__ == '__main__':
    X = np.random.randint(5, size=(6, 100))
    y = np.array([1, 2, 3, 4, 5, 6])
    clf = MultinomialNB()
    clf.fit(X, y)
    print(clf.predict(X[2:3]))

上面的示例,我们通过随机数创建了一个 6*100 的矩阵,其中每个元素都是0到5的随机数,我们用这个矩阵的每一行分别对应 1、2、3、4、5、6,最终,我们用第三行来测试这个模型,果然得到了预期的数字:3。

6. 后记

对于相互独立的样本来说,朴素贝叶斯是一个非常不错的分类器,在自然语言处理和文本特征分析、过滤等领域有着广泛的应用。
事实上,朴素贝叶斯共有三种模型,他们的区别在于计算条件概率的公式不同:

  1. 高斯朴素贝叶斯 – 用于符合高斯分布(正态分布)的连续样本数据的分类
  2. 多项式朴素贝叶斯 – 我们已经介绍的内容就是多项式朴素贝叶斯模型
  3. 伯努利朴素贝叶斯 – 每个特征的取值为0或1,即计算特征是否存在的概率,他是唯一将样本中不存在的特征也引入计算概率的朴素贝叶斯模型

7. 参考资料

Peter Harrington 《机器学习实战》。
李航 《统计学习方法》。
https://zh.wikipedia.org/wiki/朴素贝叶斯分类器。
https://scikit-learn.org/dev/modules/generated/sklearn.naive_bayes.MultinomialNB.html。

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值