这篇文章除了给出HanLPv1.7.6的对数概率转概率的计算过程,同时给出sklearn中的转概率计算过程。
计算文本属于China和不属于China的概率
P(c=China| Chinese Chinese Chinese Tokyo Japn)=
P(c=China)*P(Chinese|c=China)*P(Chinese|c=China)*
P(Chinese|c=China)*P(Tokyo|c=China)*P(Japan|c=China)
=0.75*0.429*0.429*0.429*0.071*0.071=0.000299
P(c!=China| Chinese Chinese Chinese Tokyo Japn)=
P(c!=China)*P(Chinese|c!=China)*P(Chinese|c!=China)*
P(Chinese|c!=China)*P(Tokyo|c!=China)*P(Japan|c!=China)
=0.25*0.222*0.222*0.222*0.222*0.222=0.000135
我们用sklearn计算得到的概率为[0.68894009, 0.31105991],实际上sklearn就是对概率值做了归一化操作,也就是先对[0.000299, 0.000135]求总和,之后再用[0.000299, 0.000135]除以总和。
我们计算一下,看看怎么从[0.000299, 0.000135]得到[0.68894009, 0.31105991],这里为了防止溢出,我们也是先对概率[0.000299, 0.000135]取对数。
In [1]: import numpy as np
In [2]: log_prob = np.array([np.log(0.000299), np.log(0.000135)])
In [3]: prob = np.exp(log_prob)/np.sum(np.exp(log_prob))
In [4]: prob
Out[4]: array([ 0.68894009, 0.31105991])
显然,对数概率p(y_k|x)的转换公式为:
以上为sklearn中对数概率转为概率的计算公式,接下来我们看看 HanLP中的转换公式。
由于概率值相乘会造成溢出,因此HanLP在计算一篇文本属于某个类目的概率时,对预测概率公式两端取对数,这样就将概率值相乘变成取对数后的概率值相加,用朴素贝叶斯预测类目的公式如下:
如果用户要求输出概率值而不是对数概率值,则对上边等式的两边做指数运算,于是
为了防止指数运算溢出,HanLP首先求对数概率最大值,然后将每个对数概率值都减掉最大值,之后再进行指数运算,即
最后做归一化操作:
综上,HanLP与sklearn计算的唯一区别就是在指数运算之前减掉了max。
HanLP中实现上述计算的代码在com/hankcs/hanlp/utility/MathUtility.java类中的normalizeExp方法
/**
* 使用log-sum-exp技巧来归一化一组对数值
*
* @param predictionScores
*/
public static void normalizeExp(Map<String, Double> predictionScores)
{
Set<Map.Entry<String, Double>> entrySet = predictionScores.entrySet();
double max = Double.NEGATIVE_INFINITY;
for (Map.Entry<String, Double> entry : entrySet)
{
max = Math.max(max, entry.getValue());
}
double sum = 0.0;
//通过减去最大值防止浮点数溢出
for (Map.Entry<String, Double> entry : entrySet)
{
Double value = Math.exp(entry.getValue() - max);
entry.setValue(value);
sum += value;
}
if (sum != 0.0)
{
for (Map.Entry<String, Double> entry : entrySet)
{
predictionScores.put(entry.getKey(), entry.getValue() / sum);
}
}
}