การคำนวณ divergence ของ Jensen-Shannon สำหรับ 3 ดิสทริบิวชันโพรไบชัน: มันโอเคไหม?


12

ฉันต้องการคำนวณ divergence ของเซ่น - แชนนอนสำหรับเขาหลังจากการกระจาย 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

ขอบคุณล่วงหน้า...

แก้ไขนี่คือบางส่วนของรหัสหลามสกปรกที่คำนวณได้เช่นกัน:

    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
Nice Python code ตามวิธี!
gui11aume

คำตอบ:


13

มีความผิดพลาดในการกระจายการผสม มันควรจะเป็น แทนซึ่งไม่รวมถึง 1 เอนโทรปี (ที่มีบันทึกธรรมชาติ) ที่ 1.084503 . ข้อกำหนดเอนโทรปีอื่น ๆ ของคุณผิด(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.098612 ดังนั้นผลลัพธ์สุดท้ายคือ 1.084503 - (0.6931472 + 0.325083 + 1.098612) / 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)เป็นแหลมออกในความคิดเห็นโดยตำนาน
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

คุณให้การอ้างอิงถึง Wikipedia ที่นี่ฉันให้การแสดงออกที่สมบูรณ์สำหรับการแตกต่าง Jensen-Shannon กับการแจกแจงความน่าจะเป็นหลาย:

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

มีการโพสต์คำถามต้นฉบับโดยไม่มีการแสดงออกทางคณิตศาสตร์ของการกระจาย JS ที่หลากหลายซึ่งนำไปสู่ความสับสนในการทำความเข้าใจการคำนวณที่ให้ไว้ นอกจากนี้ยังใช้คำweightซึ่งทำให้เกิดความสับสนอีกครั้งว่าวิธีการเลือกน้ำหนักที่เหมาะสมสำหรับการคูณ การแสดงออกข้างต้นชี้แจงความสับสนเหล่านี้ ชัดเจนจากการแสดงออกด้านบนน้ำหนักจะถูกเลือกโดยอัตโนมัติขึ้นอยู่กับจำนวนการกระจาย


สิ่งนี้กำลังถูกตั้งค่าสถานะโดยอัตโนมัติว่ามีคุณภาพต่ำอาจเป็นเพราะมันสั้นมาก ในปัจจุบันมันเป็นความเห็นมากกว่าคำตอบตามมาตรฐานของเรา คุณสามารถขยายมันได้หรือไม่ เราสามารถเปลี่ยนเป็นความคิดเห็นได้
gung - Reinstate Monica

ฟังดูเหมือนความคิดเห็นที่ชัดเจนมากกว่าคำตอบ นี่ควรเป็นการแก้ไขคำถามหรือไม่
gung - Reinstate Monica

@ gung แก้ไขคำตอบของฉัน หวังว่ามันจะช่วย
สวัสดีชาวโลก

0

รุ่น Scala ของ JS divergence ของสองความยาวตามลำดับ:

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การแจกแจงความน่าจะเป็นในหลามอยู่ในสูตรวิกิพีเดียและความคิดเห็นในโพสต์นี้กับเวกเตอร์ของน้ำหนัก ( Pi ) เป็นพารามิเตอร์ที่กำหนดเองและ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

โดยการใช้ไซต์ของเรา หมายความว่าคุณได้อ่านและทำความเข้าใจนโยบายคุกกี้และนโยบายความเป็นส่วนตัวของเราแล้ว
Licensed under cc by-sa 3.0 with attribution required.