Розрахунок розбіжності Дженсена-Шеннона для 3-х розподільних задач: Це нормально?


12

Я хотів би порахувати розбіжність Дженсен-Шеннона для наступних 3 розподілів. Чи правильний розрахунок нижче? (Я дотримувався формули JSD з Вікіпедії ):

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

Спасибі заздалегідь...

EDIT Ось декілька простих брудних кодів Python, який також обчислює це:

    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
Приємний код Python, до речі!
gui11aume

Відповіді:


13

У розподілі суміші є помилка. Він повинен бути замість що не дорівнює 1. Ентропія (з природним журналом), що становить 1,084503 . Ваші інші умови ентропії неправильні.( 1 / 6 , 1 / 5 , 9 / 30 )(5/18,28/90,37/90)(1/6,1/5,9/30)

Я наведу детальну інформацію про одне обчислення:

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

Аналогічним чином інші терміни складають 0,325083 та 1,0868612. Отже, кінцевий результат - 1,084503 - (0,6931472 + 0,325083 + 1,0868612) / 3 = 0,378889


3
+1. Швидкий і брудний розрахунок 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)}. Аргумент p- це матриця, рядки якої є розподілами, а аргументом qє вектор зважувань. Наприклад, 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/9213/45714/453737/90
whuber

1
Не так брудно ... ;-)
gui11aume

4
(1) Повторити математику. (2) Ентропію можна виміряти, використовуючи будь-яку основу логарифму, який вам подобається, якщо ви послідовні. Природні, загальні і колоди-2 колоди є загальноприйнятими. (3) Це справді середня розбіжність між розподілами та їх середнім рівнем. Якщо ви вважаєте кожен розподіл як точку, вони утворюють хмару. Ви дивитесь на середню "відстань" між центром хмари та її точками, на зразок середнього радіуса. Інтуїтивно він вимірює розмір хмари.
whuber

1
@Legend Я думаю, ти маєш рацію. Я не перевірив достатньо, виявивши, що один результат погодився з відповіддю, який я отримав іншим способом (з Mathematica ).
whuber

1
@dmck У моєму коментарі справді є помилки на помилки: (1) фраза h <- function(x) {була вставлена ​​двічі. Просто видаліть: все інше працює і дає результати, які я цитую. Потім змініть apply(p, 2, h)на, apply(p, 1, h)як зазначено в коментарі Legend .
whuber

6

Пітон:

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

Ви дали посилання на Вікіпедію. Тут я даю повний вираз для розбіжності Дженсена-Шеннона з множинними розподілами ймовірностей:

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

Оригінальне запитання було розміщено без математичного вираження розбіжності JS з різними розподілами, що призводить до плутанини в розумінні наданих обчислень. Також weightвикористовувався термін, який знову викликає плутанину в тому, як ви вибираєте відповідні ваги для множення. Вище вираз роз'яснює ці плутанини. Як зрозуміло з наведеного виразу, ваги автоматично вибираються залежно від кількості розподілу.


Це автоматично позначається як низька якість, ймовірно, тому, що воно таке коротке. В даний час це скоріше коментар, ніж відповідь за нашими стандартами. Чи можете ви розширити його? Ми також можемо перетворити це на коментар.
gung - Відновіть Моніку

Це звучить як уточнюючий коментар, а не відповідь. Це має бути редагуванням питання?
gung - Відновити Моніку

@ gung, змінив мою відповідь. Сподіваюся, це допомагає.
Hello World

0

Версія Scala розбіжності JS двох послідовностей довільної довжини:

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

Перехресно перевірте цю відповідь із кодом у розділі редагування запитання:

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

0

Загальна версія, для n розподілів ймовірностей, у python, що базується на формулі Вікіпедії та коментарі у цій публікації з вектором ваг ( pi ) як параметром та користувацькою базою даних :

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

Використовуючи наш веб-сайт, ви визнаєте, що прочитали та зрозуміли наші Політику щодо файлів cookie та Політику конфіденційності.
Licensed under cc by-sa 3.0 with attribution required.