Cálculo da divergência de Jensen-Shannon para distribuições de 3 prob: Tudo bem?

12

Gostaria de calcular a divergência de Jensen-Shannon para ele após três distribuições. O cálculo abaixo está correto? (Eu segui a fórmula JSD da wikipedia ):

P1  a:1/2  b:1/2    c:0
P2  a:0    b:1/10   c:9/10
P3  a:1/3  b:1/3    c:1/3
All distributions have equal weights, ie 1/3.

JSD(P1, P2, P3) = H[(1/6, 1/6, 0) + (0, 1/30, 9/30) + (1/9,1/9,1/9)] - 
                 [1/3*H[(1/2,1/2,0)] + 1/3*H[(0,1/10,9/10)] + 1/3*H[(1/3,1/3,1/3)]]

JSD(P1, P2, P3) = H[(1/6, 1/5, 9/30)] - [0 + 1/3*0.693 + 0] = 1.098-0.693 = 0.867

Desde já, obrigado...

EDIT Aqui está um código Python simples e sujo que calcula isso também:

    def entropy(prob_dist, base=math.e):
        return -sum([p * math.log(p,base) for p in prob_dist if p != 0])

    def jsd(prob_dists, base=math.e):
        weight = 1/len(prob_dists) #all same weight
        js_left = [0,0,0]
        js_right = 0    
        for pd in prob_dists:
            js_left[0] += pd[0]*weight
            js_left[1] += pd[1]*weight
            js_left[2] += pd[2]*weight
            js_right += weight*entropy(pd,base)
        return entropy(js_left)-js_right

usage: jsd([[1/2,1/2,0],[0,1/10,9/10],[1/3,1/3,1/3]])
kanzen_master
fonte
2
Bom código Python por sinal!
precisa saber é o seguinte

Respostas:

13

(5/18,28./90,37./90)(1/6,1/5,9/30)

Vou dar os detalhes de uma computação:

H(1/2,1/2,0 0)=-1/2registro(1/2)-1/2registro(1/2)+0 0=0.6931472

De maneira semelhante, os outros termos são 0,325083 e 1,098612. Portanto, o resultado final é 1.084503 - (0.6931472 + 0.325083 + 1.098612) / 3 = 0.378889

gui11aume
fonte
3
+1. Cálculo R rápido e sujo: h <- function(x) {h <- function(x) {y <- x[x > 0]; -sum(y * log(y))}; jsd <- function(p,q) {h(q %*% p) - q %*% apply(p, 2, h)}. Argumento pé uma matriz cujas linhas são as distribuições e o argumento qé o vetor de pesos. Por exemplo,p <- matrix(c(1/2,1/2,0, 0,1/10,9/10, 1/3,1/3,1/3), ncol=3, byrow=TRUE); q <- c(1/3,1/3,1/3); jsd(p,q)0,378889334/1551/92-13/457-14/4537.-37./90
1
Não tão sujo ... ;-)
gui11aume
4
(1) Refaça a matemática. (2) A entropia pode ser medida usando qualquer base de logaritmo que você quiser, desde que seja consistente. Logs naturais, comuns e de base 2 são todos convencionais. (3) É realmente uma discrepância média entre as distribuições e sua média. Se você pensa em cada distribuição como um ponto, elas formam uma nuvem. Você está olhando para a "distância" média entre o centro da nuvem e seus pontos, como um raio médio. Intuitivamente, ele mede o tamanho da nuvem.
whuber
1
@ Legend Acho que você está certo. Não testei suficientemente depois de descobrir que um resultado concordava com a resposta que obtive de outra maneira (com o Mathematica ).
whuber
1
@dmck De fato, existem erros de digitação no meu comentário: (1) a frase h <- function(x) {foi colada duas vezes. Basta excluí-lo: tudo o resto funciona e produz os resultados que cito. Em seguida, modifique o apply(p, 2, h)para apply(p, 1, h)como indicado no comentário da legenda .
whuber
6

Pitão:

import numpy as np
# @author: jonathanfriedman

def jsd(x,y): #Jensen-shannon divergence
    import warnings
    warnings.filterwarnings("ignore", category = RuntimeWarning)
    x = np.array(x)
    y = np.array(y)
    d1 = x*np.log2(2*x/(x+y))
    d2 = y*np.log2(2*y/(x+y))
    d1[np.isnan(d1)] = 0
    d2[np.isnan(d2)] = 0
    d = 0.5*np.sum(d1+d2)    
    return d

jsd(np.array([0.5,0.5,0]),np.array([0,0.1,0.9]))

Java:

/**
 * Returns the Jensen-Shannon divergence.
 */
public static double jensenShannonDivergence(final double[] p1,
        final double[] p2) {
    assert (p1.length == p2.length);
    double[] average = new double[p1.length];
    for (int i = 0; i < p1.length; ++i) {
        average[i] += (p1[i] + p2[i]) / 2;
    }
    return (klDivergence(p1, average) + klDivergence(p2, average)) / 2;
}

public static final double log2 = Math.log(2);

/**
 * Returns the KL divergence, K(p1 || p2).
 * 
 * The log is w.r.t. base 2.
 * <p>
 * *Note*: If any value in <tt>p2</tt> is <tt>0.0</tt> then the
 * KL-divergence is <tt>infinite</tt>. Limin changes it to zero instead of
 * infinite.
 */
public static double klDivergence(final double[] p1, final double[] p2) {
    double klDiv = 0.0;
    for (int i = 0; i < p1.length; ++i) {
        if (p1[i] == 0) {
            continue;
        }
        if (p2[i] == 0.0) {
            continue;
        } // Limin

        klDiv += p1[i] * Math.log(p1[i] / p2[i]);
    }
    return klDiv / log2; // moved this division out of the loop -DM
}
Renaud
fonte
0

Você deu uma referência da Wikipedia. Aqui, dou a expressão completa da divergência de Jensen-Shannon com múltiplas distribuições de probabilidade:

JSmetrEuc(p1,...,pm)=H(p1+...+pmm)-j=1mH(pj)m

A pergunta original foi publicada sem expressão matemática da divergência JS de multi-distribuição, o que levou a uma confusão no entendimento da computação fornecida. Além disso, weightfoi utilizado o termo que novamente causa confusão quanto à maneira como você seleciona pesos apropriados para multiplicação. A expressão acima esclarece essas confusões. Como claramente acima da expressão, os pesos são escolhidos automaticamente, dependendo do número de distribuição.

Olá Mundo
fonte
Isso está sendo marcado automaticamente como baixa qualidade, provavelmente porque é muito curto. Atualmente, é mais um comentário do que uma resposta de nossos padrões. Você pode expandir isso? Também podemos transformá-lo em um comentário.
gung - Restabelece Monica
Isso soa como um comentário esclarecedor, em vez de uma resposta. Isso deve ser uma edição da pergunta?
gung - Restabelece Monica
@ Gung, modificou minha resposta. Espero que ajude.
Olá Mundo
0

Versão Scala da divergência JS de duas seqüências arbitrárias de comprimento:

def entropy(dist: WrappedArray[Double]) = -(dist.filter(_ != 0.0).map(i => i * Math.log(i)).sum)


val jsDivergence = (dist1: WrappedArray[Double], dist2: WrappedArray[Double]) => {
    val weights = 0.5 //since we are considering inly two sequences
    val left = dist1.zip(dist2).map(x => x._1 * weights + x._2 * weights)
    // println(left)
    // println(entropy(left))
    val right = (entropy(dist1) * weights) + (entropy(dist2) * weights)
    // println(right)
    entropy(left) - right

}

jsDivergence(Array(0.5,0.5,0), Array(0,0.1,0.9))

res0: Double = 0.557978817900054

Verifique esta resposta com o código na seção de edição de perguntas:

jsd([np.array([0.5,0.5,0]), np.array([0,0.1,0.9])])
0.55797881790005399
Mageswaran
fonte
0

Uma versão geral, para n distribuições de probabilidade, em python com base na fórmula da Wikipedia e comentários neste post com vetor de pesos ( pi ) como parâmetro e base de log personalizada :

import numpy as np
from scipy.stats import entropy as H


def JSD(prob_distributions, weights, logbase=2):
    # left term: entropy of mixture
    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
    # wentropies = np.dot(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

alemol
fonte