本系列代码托管于:https://github.com/chintsan-code/machine-learning-tutorials
本篇使用的项目为:bayes_class

在上一篇中,我们使用朴素贝叶斯时做了一个假设: 假设所有词都互相独立,该假设也称作条件独立性假设,它意味着可以使用 \( p(w_0|c_i)p(w_1|c_i)p(w_2|c_i)…p(w_N|c_i) \) 来计算\(p(w|c_i)\)。单词相互独立怎么理解呢?比如”I am handsome.”这句话,我们可以假设的就是I后面出现am或出现handsome的概率是一样的,这样就叫做单词相互独立,即I后面出现的单词和I本身没有关系,实际上当然不可能,I后面出现am比出现handsome的概率高多了。

既然如此我们的分类器应该这样写:

def classifyNB(vec2Classify, p0Vec, p1Vec, pClass1):
    p1 = pClass1 * multi(vec2Classify * p1Vec)  # multi:累乘
    p0 = (1.0 - pClass1) * multi(vec2Classify * p0Vec)
    if p1 > p0:
        return 1
    else:
        return 0

其中:
$$pClass1=p(c_1)$$
$$ p(w|c_1) = p(w_0|c_i)p(w_1|c_i)p(w_2|c_i)…p(w_N|c_i) = multi(vec2Classify * p1Vec)$$
$$p(c_0|w)=p(c_0)\frac{ (p(w|c_0) }{p(w)}$$
$$p(c_1|w)=p(c_1)\frac{ (p(w|c_0) }{p(w)}$$
由于对同一句话来说,\( p(w) \)都是一样的,因此我们计算的时候可以简化掉,也就是只需要计算并比较下面两个概率的大小即可:
$$p0= p(c_0)p(w|c_0)=pClass1 * multi(vec2Classify * p1Vec)$$
$$p1= p(c_1)(p(w|c_1)=(1.0 – pClass1) * multi(vec2Classify * p1Vec)$$

在上一篇中,我们在最后说到构造的贝叶斯分类器有一个缺陷,本篇就是介绍缺陷是什么以及如何进行改进的。

既然我们准备使用 \( p(w_0|c_i)p(w_1|c_i)p(w_2|c_i)…p(w_N|c_i) \) 来计算\(p(w|c_i)\) ,那么就要意识到下面两个问题:
1. 如果有任意一个\( p(w_k|c_i) \) 的概率值为0,那么最后的乘积也为0;
2. 如果太小的数值相乘,有可能导致下溢出。(可以用Python尝试相乘许多很小的数,最后四舍五入后会得到0)

针对上面这两个问题,我们可以通过下面的方法进行改进:
1. 可以将所有词的出现数初始化为1,并将分母初始化为2;
2. 可以对乘积取自然对数。在代数中有ln(a*b) = ln(a)+ln(b),于是通过求对数可以 避免下溢出或者浮点数舍入导致的错误。同时,采用自然对数进行处理不会有任何损失。

下面的改进后的贝叶斯分类器的代码:

def trainNB0mprovement(trainMatrix, trainCategory):
    numTrainDocs = len(trainMatrix)
    numWords = len(trainMatrix[0])
    pAbusive = sum(trainCategory) / float(numTrainDocs)
    # 要计算多个概率的乘积以获得文档属于某个类别的概率不为0,故用1初始化
    p0Num = ones(numWords);
    p1Num = ones(numWords)  # change to ones()
    p0Denom = 2.0
    p1Denom = 2.0  # 将分母的初始值改为2
    for i in range(numTrainDocs):
        if trainCategory[i] == 1:
            p1Num += trainMatrix[i]
            p1Denom += sum(trainMatrix[i])
        else:
            p0Num += trainMatrix[i]
            p0Denom += sum(trainMatrix[i])
    p1Vect = log(p1Num / p1Denom)  # change to log()
    p0Vect = log(p0Num / p0Denom)  # change to log()
    return p0Vect, p1Vect, pAbusive
def classifyNB(vec2Classify, p0Vec, p1Vec, pClass1):
    p1 = sum(vec2Classify * p1Vec) + log(pClass1)  # 元素相乘转为对数相加,p1Vec在trainNB0mprovement中已经取对数,因此在这里不用再做一次
    p0 = sum(vec2Classify * p0Vec) + log(1.0 - pClass1)
    if p1 > p0:
        return 1
    else:
        return 0