Jensen-Shannon散度

24

我有另一个问题,希望有人能够帮助我。

我正在使用Jensen-Shannon-Divergence来测量两个概率分布之间的相似性。如果使用基于2的对数,相似度得分看起来是正确的,因为它们在1到0之间,其中0表示分布相等。

但是,我不确定是否实际上存在错误,想知道是否有人能够说“是的,这是正确的”或“不,你做错了什么”。

以下是代码:

from numpy import zeros, array
from math import sqrt, log


class JSD(object):
    def __init__(self):
        self.log2 = log(2)


    def KL_divergence(self, p, q):
        """ Compute KL divergence of two vectors, K(p || q)."""
        return sum(p[x] * log((p[x]) / (q[x])) for x in range(len(p)) if p[x] != 0.0 or p[x] != 0)

    def Jensen_Shannon_divergence(self, p, q):
        """ Returns the Jensen-Shannon divergence. """
        self.JSD = 0.0
        weight = 0.5
        average = zeros(len(p)) #Average
        for x in range(len(p)):
            average[x] = weight * p[x] + (1 - weight) * q[x]
            self.JSD = (weight * self.KL_divergence(array(p), average)) + ((1 - weight) * self.KL_divergence(array(q), average))
        return 1-(self.JSD/sqrt(2 * self.log2))

if __name__ == '__main__':
    J = JSD()
    p = [1.0/10, 9.0/10, 0]
    q = [0, 1.0/10, 9.0/10]
    print J.Jensen_Shannon_divergence(p, q)

问题在于当比较两个文本文件时,我感觉分数不够高,但这纯粹是主观感受。

如常,任何帮助都将不胜感激。


1
也许可以尝试将输出与这个Matlab脚本进行比较?或者在Octave中运行它。 - Josiah Hester
if p[x] != 0.0 or p[x] != 0 看起来很奇怪。 - Janne Karila
如果使用if p[x] != 0.0 or p[x] != 0,可以确保我们不考虑值为零的条目,无论它们是浮点数还是整数,这是您所指的吗?还是您的意思是这行代码本身很奇怪?非常感谢。 - Martyn
3
“p[x] != 0”与“0.0 == 0”相同,这就是我怀疑那里可能有错别字的原因。 - Janne Karila
5个回答

27

请注意,下面的scipy熵调用是Kullback-Leibler散度。

参见:http://en.wikipedia.org/wiki/Jensen%E2%80%93Shannon_divergence

#!/usr/bin/env python
from scipy.stats import entropy
from numpy.linalg import norm
import numpy as np

def JSD(P, Q):
    _P = P / norm(P, ord=1)
    _Q = Q / norm(Q, ord=1)
    _M = 0.5 * (_P + _Q)
    return 0.5 * (entropy(_P, _M) + entropy(_Q, _M))

还要注意,问题中的测试案例似乎有误? p分布的总和不等于1.0。

参见:http://www.itl.nist.gov/div898/handbook/eda/section3/eda361.htm


1
不需要导入和使用 norm,因为如果分布之和不等于1,则 entropy 将对其进行归一化(请参见 http://docs.scipy.org/doc/scipy-dev/reference/generated/scipy.stats.entropy.html)。但是,要像那样计算 _M_P_Q 需要是 numpy.ndarray 对象。 - Tur1ng
4
注意,需要规范化是因为计算 _M 需要 _P_Q 是概率分布(已经被归一化了)。此外请注意,列表会被强制转换为 NumPy 数组,所以这样做是可以的:[2, 4] / np.array([1, 2]) - Doug Shore
@DougShore 实际上,由于 scipy.stats.entropy 对分布进行了归一化处理,因此您不需要对 _P_Q 进行归一化处理来计算 _M,您只需要使它们总和相等即可,这样可以节省一些计算。然而,像这样编写代码更易读。另一方面,我更喜欢不执行不必要计算的函数,并假设输入是已经归一化的概率。 - emem
那么,在@Doug Shore的代码中,我需要在我的场合中拥有P,Q频率列表(list_alist_b)吗:list_a = [1, 100, 40, 1200, 0, 4]list_b = [23, 5600, 11, 0, 40, 340],就像您在上面看到的那样未经归一化?还是应该在将它们馈送到JSD(P,Q)函数之前对它们进行归一化处理? - just_learning
1
@just_learning JSD函数将输入归一化为概率分布,因此JSD(list_a, list_b)可以正常工作。 - Doug Shore

19

自从Jensen-Shannon距离distance.jensenshannon)被添加到Scipy 1.2中,Jensen-Shannon差异可以通过Jensen-Shannon距离的平方获得:

from scipy.spatial import distance

distance.jensenshannon([1.0/10, 9.0/10, 0], [0, 1.0/10, 9.0/10]) ** 2
# 0.5306056938642212

9
获取已知发散的分布数据,并将您的结果与这些已知值进行比较。
顺便说一下:KL_divergence中的总和可以使用zip内置函数进行重写,如下所示:
sum(_p * log(_p / _q) for _p, _q in zip(p, q) if _p != 0)

这样做可以减少很多“噪音”,也更符合“Pythonic”的风格。不必进行两次比较,即0.00的比较。

4
在Python中,针对n个概率分布的一般版本。
import numpy as np
from scipy.stats import entropy as H


def JSD(prob_distributions, weights, logbase=2):
    # left term: entropy of misture
    wprobs = weights * prob_distributions
    mixture = wprobs.sum(axis=0)
    entropy_of_mixture = H(mixture, base=logbase)

    # right term: sum of entropies
    entropies = np.array([H(P_i, base=logbase) for P_i in prob_distributions])
    wentropies = weights * entropies
    sum_of_entropies = wentropies.sum()

    divergence = entropy_of_mixture - sum_of_entropies
    return(divergence)

# From the original example with three distributions:
P_1 = np.array([1/2, 1/2, 0])
P_2 = np.array([0, 1/10, 9/10])
P_3 = np.array([1/3, 1/3, 1/3])

prob_distributions = np.array([P_1, P_2, P_3])
n = len(prob_distributions)
weights = np.empty(n)
weights.fill(1/n)

print(JSD(prob_distributions, weights))
#0.546621319446

2
明确地遵循维基百科文章中的数学公式:
def jsdiv(P, Q):
    """Compute the Jensen-Shannon divergence between two probability distributions.

    Input
    -----
    P, Q : array-like
        Probability distributions of equal length that sum to 1
    """

    def _kldiv(A, B):
        return np.sum([v for v in A * np.log2(A/B) if not np.isnan(v)])

    P = np.array(P)
    Q = np.array(Q)

    M = 0.5 * (P + Q)

    return 0.5 * (_kldiv(P, M) +_kldiv(Q, M))

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接