Calcolo della divergenza di Jensen-Shannon per 3 distribuzioni prob: Va bene?


12

Vorrei calcolare la divergenza tra Jensen e Shannon per le seguenti 3 distribuzioni. Il calcolo di seguito è corretto? (Ho seguito la formula 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

Grazie in anticipo...

EDIT Ecco un semplice codice Python sporco che calcola anche questo:

    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
Bel codice Python a proposito!
gui11aume,

Risposte:


13

C'è un errore nella distribuzione della miscela. Dovrebbe essere anziché che non si somma a 1. L'entropia (con log naturale) di questo è 1.084503 . I tuoi altri termini di entropia sono sbagliati.( 1 / 6 , 1 / 5 , 9 / 30 )(5/18,28/90,37/90)(1/6,1/5,9/30)

Darò il dettaglio di un calcolo:

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

In modo simile, gli altri termini sono 0,325083 e 1,098612. Quindi il risultato finale è 1.084503 - (0.6931472 + 0.325083 + 1.098612) / 3 = 0.378889


3
+1. Veloce e sporco di calcolo R: 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'argomento pè una matrice le cui righe sono le distribuzioni e l'argomento qè il vettore dei pesi. Ad esempio, 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)restituisce (che approssima il registro di ). 3 34 / 15 5 1 / 9 2 - 13 / 45 7 - 14 / 45 37 - 37 / 900.378889334/1551/9213/45714/453737/90
whuber

1
Non così sporco ... ;-)
gui11aume

4
(1) Ripeti la matematica. (2) L'entropia può essere misurata usando qualsiasi base di logaritmo che ti piace, purché tu sia coerente. I log naturali, comuni e base-2 sono tutti convenzionali. (3) È davvero una discrepanza media tra le distribuzioni e la loro media. Se si considera ogni distribuzione come un punto, formano una nuvola. Stai osservando la "distanza" media tra il centro della nuvola e i suoi punti, un po 'come un raggio medio. Intuitivamente, misura la dimensione del cloud.
whuber

1
@Legend Penso che tu abbia ragione. Non ho testato sufficientemente dopo aver scoperto che un risultato concordava con la risposta che avevo ottenuto in un altro modo (con Mathematica ).
whuber

1
@dmck Ci sono davvero errori di battitura nel mio commento: (1) la frase è h <- function(x) {stata incollata due volte. Basta cancellarlo: tutto il resto funziona e produce i risultati che cito. Quindi modifica il apply(p, 2, h)to apply(p, 1, h)come indicato nel commento di Legend .
whuber

6

Pitone:

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

Giava:

/**
 * 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

Hai fornito un riferimento a Wikipedia. Qui do l'espressione completa per la divergenza di Jensen-Shannon con distribuzioni di probabilità multiple:

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

La domanda originale è stata pubblicata senza espressione matematica della divergenza JS multi-distribuzione che porta a una confusione nella comprensione del calcolo fornito. Inoltre, è weightstato usato il termine che crea nuovamente confusione sul modo in cui si selezionano i pesi appropriati per la moltiplicazione. L'espressione sopra chiarisce queste confusioni. Come si evince dall'espressione sopra, i pesi vengono scelti automaticamente in base al numero di distribuzione.


Questo viene automaticamente contrassegnato come di bassa qualità, probabilmente perché è così corto. Al momento è più un commento che una risposta dai nostri standard. Puoi espanderci? Possiamo anche trasformarlo in un commento.
gung - Ripristina Monica

Sembra un commento chiarificatore, piuttosto che una risposta. Dovrebbe essere una modifica alla domanda?
gung - Ripristina Monica

@ gung, ho modificato la mia risposta. Spero che sia d'aiuto.
Ciao mondo,

0

Versione Scala della divergenza JS di due sequenze di lunghezza arbitrarie:

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

Verifica incrociata questa risposta con il codice nella sezione di modifica della domanda:

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

0

Una versione generale, per n distribuzioni di probabilità, in pitone basata sulla formula di Wikipedia e commenti in questo post con vettore di pesi ( pi ) come parametro e logbase personalizzato :

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

,546621319446

Utilizzando il nostro sito, riconosci di aver letto e compreso le nostre Informativa sui cookie e Informativa sulla privacy.
Licensed under cc by-sa 3.0 with attribution required.