我已经在很多地方(例如here和here)阅读了有关log-sum-exp技巧,但从未见过将其专门应用于Naive Bayes分类器的示例(例如,具有离散功能和两个类)
使用该技巧如何完全避免数字下溢的问题?
我已经在很多地方(例如here和here)阅读了有关log-sum-exp技巧,但从未见过将其专门应用于Naive Bayes分类器的示例(例如,具有离散功能和两个类)
使用该技巧如何完全避免数字下溢的问题?
Answers:
在
分母和分子都可以变得非常小,通常是因为可以接近0,并且我们将它们彼此相乘。为了防止下溢,可以简单地获取分子的对数,但是需要对分母使用log-sum-exp技巧。
更具体地说,为了防止下溢:
如果我们只关心知道哪些类输入(X = X 1,... ,X ñ)最有可能属于与最大后验(MAP)决策规则,我们没有适用的对数sum-exp技巧,因为在这种情况下我们不必计算分母。对于分子,可以简单地取日志以防止下溢:l o g (p (x | Y = C )p (Y = C ))。进一步来说:
在获取日志后变为:
如果要计算类概率,则需要计算分母:
元素可能会下溢,因为可能很小:这与分子中的问题相同,但是这次我们在对数内进行求和,这使我们无法转换 p (x i | C k))(可以是接近0)转换成日志( p (X 我 | C ^ ķ))(阴性和不接近0了,因为0 ≤ p (X 我 | C ^ ķ)≤ 1)。为了解决这个问题,我们可以使用的事实来获得:
在这一点上,一个新的问题出现了:可能相当负面,这意味着EXP (日志( p (X | Ÿ = Ç ķ)p (Y = C k)))可能变得非常接近0,即下溢。这是我们使用log-sum-exp技巧的地方:
与:
我们可以看到引入变量可以避免下溢。例如k = 2 ,a 1 = − 245 ,a 2 = − 255,我们有:
使用对数和-EXP特技我们避免了下溢,与: 日志Σ ķ Ë 一个ķ
我们避免了下溢,因为 为0比要远得多3.96143 × 10 - 107或1.798486 × 10 - 111。
从这个答案中我们可以看到,Python中最小的数字(例如,以它为例)是5e-324
由于IEEE754引起的,硬件原因也适用于其他语言。
In [2]: np.nextafter(0, 1)
Out[2]: 5e-324
任何小于该值的浮点数都将导致0。
In [3]: np.nextafter(0, 1)/2
Out[3]: 0.0
让我们with discrete features and two classes
根据需要查看Naive Bayes的功能:
让我通过下面的简单NLP任务实例化该函数。
In [1]: import numpy as np
In [2]: from sklearn.naive_bayes import BernoulliNB
# let's train our model with 200 samples
In [3]: X = np.random.randint(2, size=(200, 5000))
In [4]: y = np.random.randint(2, size=(200, 1)).ravel()
In [5]: clf = BernoulliNB()
In [6]: model = clf.fit(X, y)
In [7]: (np.nextafter(0, 1)*2) / (np.nextafter(0, 1)*2)
Out[7]: 1.0
In [8]: (np.nextafter(0, 1)/2) / (np.nextafter(0, 1)/2)
/home/lerner/anaconda3/bin/ipython3:1: RuntimeWarning: invalid value encountered in double_scalars
#!/home/lerner/anaconda3/bin/python
Out[8]: nan
In [9]: l_cpt = model.feature_log_prob_
In [10]: x = np.random.randint(2, size=(1, 5000))
In [11]: cls_lp = model.class_log_prior_
In [12]: probs = np.where(x, np.exp(l_cpt[1]), 1-np.exp(l_cpt[1]))
In [13]: np.exp(cls_lp[1]) * np.prod(probs)
Out[14]: 0.0
我们可以在sklearn中看到官方实现:
jll = self._joint_log_likelihood(X)
# normalize by P(x) = P(f_1, ..., f_n)
log_prob_x = logsumexp(jll, axis=1)
return jll - np.atleast_2d(log_prob_x).T
对于分子,它将概率的乘积转换为对数似然之和;对于分母,它使用scipy中的logumexp:
out = log(sum(exp(a - a_max), axis=0))
out += a_max
这是推导:
return jll - np.atleast_2d(log_prob_x).T
希望能有所帮助。
参考:
1. Bernoulli朴素贝叶斯分类器
2. 朴素贝叶斯的垃圾邮件过滤–哪些朴素贝叶斯?