Jensen-Shannon-Divergenzberechnung für 3 Prob-Verteilungen: Ist das in Ordnung?


12

Ich möchte die Jensen-Shannon-Divergenz für die folgenden 3 Verteilungen berechnen. Ist die unten stehende Berechnung korrekt? (Ich folgte der JSD-Formel aus 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

Danke im Voraus...

BEARBEITEN Hier ist ein einfacher, schmutziger Python-Code, der dies ebenfalls berechnet:

    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
Netter Python-Code übrigens!
gui11aume

Antworten:


13

Es liegt ein Fehler in der Gemischverteilung vor. Es sollte anstelle von ( 1 / 6 , 1 / 5 , 9 / 30 ) , die nicht auf 1 ist es zusammenzufassen , die Entropie (mit natürlichen log) von dem ist 1,084503 . Ihre anderen Entropiebegriffe sind falsch.(5/18,28/90,37/90)(1/6,1/5,9/30)

Ich werde das Detail einer Berechnung geben:

H(1/2,1/2,0)=-1/2Log(1/2)-1/2Log(1/2)+0=0,6931472

In ähnlicher Weise lauten die anderen Begriffe 0,325083 und 1,098612. Das Endergebnis ist also 1,084503 - (0,6931472 + 0,325083 + 1,098612) / 3 = 0,378889


3
+1. Quick and dirty R Berechnung: 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)}. Argument pist eine Matrix, deren Zeilen die Verteilungen und Argument qder Vektor der Gewichte sind. Beispiel 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)kehrt (das das Protokoll des approximiert 3 34 / 15 5 1 / 9 2 - 13 / 45 7 - 14 / 45 37 - 37 / 90 ). 0,378889334/1551/92-13/457-14/4537-37/90
whuber

1
Nicht so dreckig ... ;-)
gui11aume

4
(1) Wiederhole die Rechnung. (2) Die Entropie kann mit jedem beliebigen Logarithmus gemessen werden, solange Sie konsistent sind. Natural-, Common- und Base-2-Protokolle sind alle konventionell. (3) Es ist wirklich eine mittlere Diskrepanz zwischen den Verteilungen und ihrem Durchschnitt. Wenn Sie sich jede Verteilung als Punkt vorstellen, bilden sie eine Wolke. Sie sehen die durchschnittliche "Entfernung" zwischen dem Mittelpunkt der Wolke und ihren Punkten, ähnlich einem mittleren Radius. Intuitiv misst es die Größe der Wolke.
whuber

1
@Legend Ich denke du hast recht. Nachdem ich festgestellt hatte, dass ein Ergebnis mit der auf andere Weise erhaltenen Antwort (mit Mathematica ) übereinstimmt, habe ich nicht ausreichend getestet .
Whuber

1
@ dmck Es gibt in der Tat Tippfehler in meinem Kommentar: (1) Die Phrase h <- function(x) {wurde zweimal eingefügt. Löschen Sie es einfach: alles andere funktioniert und liefert die Ergebnisse, die ich zitiere. Ändern Sie dann das apply(p, 2, h)to, apply(p, 1, h)wie im Kommentar von Legend angegeben .
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

Sie haben eine Wikipedia-Referenz angegeben. Hier gebe ich den vollständigen Ausdruck für die Jensen-Shannon-Divergenz mit mehreren Wahrscheinlichkeitsverteilungen:

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

Die ursprüngliche Frage wurde ohne mathematischen Ausdruck einer JS-Divergenz mit mehreren Verteilungen gestellt, was zu einer Verwirrung beim Verständnis der bereitgestellten Berechnung führte. Es wurde auch ein Begriff weightverwendet, der wiederum zu Verwirrung darüber führt, wie Sie geeignete Gewichtungen für die Multiplikation auswählen. Der obige Ausdruck verdeutlicht diese Verwirrungen. Wie aus dem obigen Ausdruck hervorgeht, werden Gewichte automatisch in Abhängigkeit von der Anzahl der Verteilungen ausgewählt.


Dies wird automatisch als minderwertig markiert, wahrscheinlich weil es so kurz ist. Derzeit ist es eher ein Kommentar als eine Antwort nach unseren Maßstäben. Können Sie es erweitern? Wir können daraus auch einen Kommentar machen.
gung - Wiedereinsetzung von Monica

Das klingt eher nach einem klarstellenden Kommentar als nach einer Antwort. Sollte dies eine Bearbeitung der Frage sein?
gung - Wiedereinsetzung von Monica

@gung, änderte meine Antwort. Ich hoffe es hilft.
Hallo Welt

0

Scala-Version der JS-Divergenz zweier Sequenzen beliebiger Länge:

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

Kreuzen Sie diese Antwort mit dem Code im Abschnitt zum Bearbeiten von Fragen an:

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

0

Eine allgemeine Version für n Wahrscheinlichkeitsverteilungen in Python basierend auf der Wikipedia-Formel und den Kommentaren in diesem Beitrag mit dem Gewichtungsvektor ( pi ) als Parameter und benutzerdefinierter 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

Durch die Nutzung unserer Website bestätigen Sie, dass Sie unsere Cookie-Richtlinie und Datenschutzrichtlinie gelesen und verstanden haben.
Licensed under cc by-sa 3.0 with attribution required.