Calcul de la divergence de Jensen-Shannon pour 3 distributions de prob: est-ce correct?


12

Je voudrais calculer la divergence jensen-shannon pour il suivant 3 distributions. Le calcul ci-dessous est-il correct? (J'ai suivi la formule 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

Merci d'avance...

EDIT Voici un code Python sale simple qui calcule cela également:

    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]])

2
Bon code Python d'ailleurs!
gui11aume

Réponses:


13

Il y a une erreur dans la distribution du mélange. Il devrait être au lieu de qui ne se résume pas à 1. L'entropie (avec logarithme naturel) de celle-ci est 1.084503 . Vos autres termes d'entropie sont incorrects.( une / six , une / cinq , 9 / 30 )(5/18,28/90,37/90)(1/6,1/5,9/30)

Je vais donner le détail d'un calcul:

H(1/2,1/2,0)=1/2log(1/2)1/2log(1/2)+0=0.6931472

De la même manière, les autres termes sont 0,325083 et 1,098612. Le résultat final est donc 1,084503 - (0,6931472 + 0,325083 + 1,098612) / 3 = 0,378889


3
+1. Calcul R rapide et sale: 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)}. L'argument pest une matrice dont les lignes sont les distributions et l'argument qest le vecteur des poids. Par exemple, 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)renvoie (qui correspond approximativement au journal de ). 3 34 / 15 5 1 / neuf 2 - treize / 45 sept - quatorze / 45 37 - 37 / 900.378889334/1551/9213/45714/453737/90
whuber

1
Pas si sale ... ;-)
gui11aume

4
(1) Refaire le calcul. (2) L'entropie peut être mesurée en utilisant n'importe quelle base de logarithme que vous aimez, tant que vous êtes cohérent. Les grumes naturelles, communes et de base 2 sont toutes conventionnelles. (3) C'est vraiment un écart moyen entre les distributions et leur moyenne. Si vous considérez chaque distribution comme un point, elles forment un nuage. Vous regardez la "distance" moyenne entre le centre du nuage et ses points, un peu comme un rayon moyen. Intuitivement, il mesure la taille du nuage.
whuber

1
@ Légende, je pense que vous avez raison. Je n'ai pas testé suffisamment après avoir constaté qu'un résultat concordait avec la réponse que j'avais obtenue d'une autre manière (avec Mathematica ).
whuber

1
@dmck Il y a en effet des fautes de frappe dans mon commentaire: (1) la phrase a h <- function(x) {été collée deux fois. Supprimez-le simplement: tout le reste fonctionne et produit les résultats que je cite. Modifiez ensuite le apply(p, 2, h)to apply(p, 1, h)comme indiqué dans le commentaire de Legend .
whuber

6

Python:

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
}

0

Vous avez donné une référence à Wikipedia. Ici, je donne l'expression complète de la divergence de Jensen-Shannon avec plusieurs distributions de probabilité:

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

La question d'origine a été publiée sans expression mathématique de divergence JS multi-distribution, ce qui a conduit à une confusion dans la compréhension du calcul fourni. En outre, le terme a weightété utilisé, ce qui crée à nouveau une confusion sur la façon dont vous sélectionnez les poids appropriés pour la multiplication. L'expression ci-dessus clarifie ces confusions. Comme le montre clairement l'expression ci-dessus, les poids sont automatiquement choisis en fonction du nombre de distribution.


Cela est automatiquement signalé comme de faible qualité, probablement parce qu'il est si court. À l'heure actuelle, il s'agit davantage d'un commentaire que d'une réponse selon nos normes. Pouvez-vous développer cela? Nous pouvons également en faire un commentaire.
gung - Réintégrer Monica

Cela ressemble à un commentaire de clarification plutôt qu'à une réponse. Cela devrait-il être une modification de la question?
gung - Réintégrer Monica

@ gung, a modifié ma réponse. J'espère que cela aide.
Hello World le

0

Version Scala de la divergence JS de deux séquences de longueur arbitraire:

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

Vérifiez cette réponse avec le code dans la section d'édition des questions:

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

0

Une version générale, pour n distributions de probabilités, en python basée sur la formule Wikipédia et les commentaires dans cet article avec un vecteur de poids ( pi ) comme paramètre et une base de données personnalisée :

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

En utilisant notre site, vous reconnaissez avoir lu et compris notre politique liée aux cookies et notre politique de confidentialité.
Licensed under cc by-sa 3.0 with attribution required.