Cálculo de la divergencia de Jensen-Shannon para 3 distribuciones de problemas: ¿está bien?

12

Me gustaría calcular la divergencia jensen-shannon para él después de 3 distribuciones. ¿Es correcto el siguiente cálculo? (Seguí la fórmula JSD de 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

Gracias por adelantado...

EDITAR Aquí hay un código Python sucio que también calcula esto:

    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
fuente
2
Buen código Python por cierto!
gui11aume

Respuestas:

13

(5 5/ /18 años,28/ /90,37/ /90)(1/ /6 6,1/ /5 5,9 9/ /30)

Daré el detalle de un cálculo:

H(1/ /2,1/ /2,0 0)=-1/ /2Iniciar sesión(1/ /2)-1/ /2Iniciar sesión(1/ /2)+0 0=0.6931472

De manera similar, los otros términos son 0.325083 y 1.098612. Entonces el resultado final es 1.084503 - (0.6931472 + 0.325083 + 1.098612) / 3 = 0.378889

gui11aume
fuente
3
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)}pqp <- 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/ /155 51/ /9 92-13/ /457 7-14/ /4537-37/ /90
1
No tan sucio ... ;-)
gui11aume
44
(1) Rehacer las matemáticas. (2) La entropía se puede medir utilizando cualquier base de logaritmo que desee, siempre que sea consistente. Los registros naturales, comunes y de base 2 son todos convencionales. (3) Es realmente una discrepancia media entre las distribuciones y su promedio. Si piensa en cada distribución como un punto, forman una nube. Estás mirando la "distancia" promedio entre el centro de la nube y sus puntos, algo así como un radio medio. Intuitivamente, mide el tamaño de la nube.
whuber
1
@Leyenda, creo que tienes razón. No probé lo suficiente después de encontrar que un resultado estaba de acuerdo con la respuesta que obtuve de otra manera (con Mathematica ).
whuber
1
@dmck De hecho, hay errores tipográficos en mi comentario: (1) la frase h <- function(x) {se pegó dos veces. Simplemente elimínelo: todo lo demás funciona y produce los resultados que cito. Luego modifique el apply(p, 2, h)a apply(p, 1, h)como se señala en el comentario de Legend .
whuber
6

Pitón:

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
fuente
0

Diste una referencia de Wikipedia. Aquí doy la expresión completa para la divergencia de Jensen-Shannon con múltiples distribuciones de probabilidad:

JSmetromitryoC(pag1,...,pagmetro)=H(pag1+...+pagmetrometro)-j=1metroH(pagj)metro

La pregunta original se publicó sin una expresión matemática de divergencia JS de distribución múltiple, lo que conduce a una confusión en la comprensión del cálculo proporcionado. Además, weightse utilizó el término que nuevamente causa confusión sobre cómo seleccionar los pesos apropiados para la multiplicación. La expresión anterior aclara estas confusiones. Como se desprende de la expresión anterior, los pesos se eligen automáticamente según el número de distribución.

Hola Mundo
fuente
Esto se marca automáticamente como de baja calidad, probablemente porque es muy corto. En la actualidad es más un comentario que una respuesta según nuestros estándares. ¿Puedes ampliarlo? También podemos convertirlo en un comentario.
gung - Restablece a Monica
Eso suena como un comentario aclaratorio, en lugar de una respuesta. ¿Debería ser esto una edición de la pregunta?
gung - Restablece a Monica
@ Gung, modificó mi respuesta. Espero eso ayude.
Hola Mundo
0

Versión Scala de la divergencia JS de dos secuencias de longitud arbitrarias:

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 respuesta con el código en la sección de edición de preguntas:

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

Una versión general, para n distribuciones de probabilidad, en pitón basado en la fórmula y los comentarios en este post con el vector de pesos (Wikipedia pi ) como parámetro y la costumbre logbase :

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
fuente