Cálculo de divergencia de KL en Python

22

Soy bastante nuevo en esto y no puedo decir que tengo una comprensión completa de los conceptos teóricos detrás de esto. Estoy tratando de calcular la divergencia de KL entre varias listas de puntos en Python. Estoy usando http://scikit-learn.org/stable/modules/generated/sklearn.metrics.mutual_info_score.html para intentar hacer esto. El problema con el que me encuentro es que el valor devuelto es el mismo para 2 listas de números (es 1.3862943611198906). Tengo la sensación de que estoy cometiendo algún tipo de error teórico aquí, pero no puedo detectarlo.

values1 = [1.346112,1.337432,1.246655]
values2 = [1.033836,1.082015,1.117323]
metrics.mutual_info_score(values1,values2)

Ese es un ejemplo de lo que estoy ejecutando, solo que estoy obteniendo la misma salida para cualquier entrada 2. Cualquier consejo / ayuda sería apreciada!

Nanda
fuente
Por KL, ¿te refieres a la divergencia Kullback-Leibler?
Dawny33
Sí, exactamente eso!
Nanda
Al correr sklearn.metrics.mutual_info_score([1.346112,1.337432,1.246655], [1.033836,1.082015,1.117323]), obtengo el valor 1.0986122886681096.
Dawny33
Lo siento, estaba usando valores1 como [1, 1.346112,1.337432,1.246655] y valores2 como valores2 como [1,1.033836,1.082015,1.117323] y, por lo tanto, el valor de diferencia.
Nanda

Respuestas:

18

En primer lugar, sklearn.metrics.mutual_info_scoreimplementa información mutua para evaluar los resultados de la agrupación, ¡no pura divergencia de Kullback-Leibler!

Esto es igual a la divergencia Kullback-Leibler de la distribución conjunta con la distribución del producto de los marginales.

La divergencia KL (y cualquier otra medida de este tipo) espera que los datos de entrada tengan una suma de 1 . De lo contrario, son no apropiados distribuciones de probabilidad . Si sus datos no tienen una suma de 1, ¡lo más probable es que no sea apropiado usar la divergencia KL! (En algunos casos, puede ser admisible tener una suma de menos de 1, por ejemplo, en el caso de datos faltantes).

También tenga en cuenta que es común usar logaritmos de base 2. Esto solo produce un factor de escala constante en la diferencia, pero los logaritmos de base 2 son más fáciles de interpretar y tienen una escala más intuitiva (0 a 1 en lugar de 0 a log2 = 0.69314 ..., midiendo la información en bits en lugar de nats).

> sklearn.metrics.mutual_info_score([0,1],[1,0])
0.69314718055994529

Como podemos ver claramente, el resultado de MI de sklearn se escala utilizando logaritmos naturales en lugar de log2. Esta es una elección desafortunada, como se explicó anteriormente.

La divergencia de Kullback-Leibler es frágil, desafortunadamente. En el ejemplo anterior no está bien definido: KL([0,1],[1,0])causa una división por cero y tiende al infinito. También es asimétrico .

Anony-Mousse
fuente
Tenga en cuenta que cuando scipy.stats.entropyse usa, normalizará las probabilidades a uno. De los documentos ( scipy.github.io/devdocs/generated/scipy.stats.entropy.html ): "Esta rutina normalizará pk y qk si no suman 1."
Itamar Mushkin
15

La función de entropía de Scipy calculará la divergencia de KL si alimenta dos vectores p y q, cada uno representa una distribución de probabilidad. Si los dos vectores no son archivos PDF, se normalizarán primero.

La información mutua está relacionada, pero no es lo mismo que KL Divergence.

"Esta información mutua ponderada es una forma de divergencia KL ponderada, que se sabe que toma valores negativos para algunas entradas, y hay ejemplos en los que la información mutua ponderada también toma valores negativos".

jamesmf
fuente
6

No estoy seguro con la implementación de ScikitLearn, pero aquí hay una implementación rápida de la divergencia de KL en Python:

import numpy as np

def KL(a, b):
    a = np.asarray(a, dtype=np.float)
    b = np.asarray(b, dtype=np.float)

    return np.sum(np.where(a != 0, a * np.log(a / b), 0))


values1 = [1.346112,1.337432,1.246655]
values2 = [1.033836,1.082015,1.117323]

print KL(values1, values2)

Salida: 0.775279624079

Puede haber conflictos de implementación en algunas bibliotecas, así que asegúrese de leer sus documentos antes de usarlos.

Dawny33
fuente
1
Intenté esto también, pero esto estaba devolviendo valores negativos que, creo, no es un valor válido. Un poco de investigación me llevó a este resultado mathoverflow.net/questions/43849/... que habla de cómo la entrada tiene que ser una distribución de probabilidad. Supongo que es donde cometí mi error.
Nanda
@Nanda Gracias por el enlace. La mina devuelve 0.775279624079sus entradas y las métricas de sklearn regresan 1.3862943611198906. Confundido todavía! Pero, parece que incluir esas comprobaciones de valor de acuerdo con el qn, en el script debería hacer :)
Dawny33
1
¡Yo sé lo que quieres decir! He intentado 3 funciones diferentes para obtener 3 valores diferentes, con lo único común entre ellos es que el resultado no se "sintió" bien. ¡Los valores de entrada son definitivamente un error lógico, por lo que cambiar mi enfoque por completo!
Nanda
@Nanda Ahh, eso está claro ahora :) Gracias por explicar
Dawny33
2

Este truco evita el código condicional y, por lo tanto, puede proporcionar un mejor rendimiento.

import numpy as np

def KL(P,Q):
""" Epsilon is used here to avoid conditional code for
checking that neither P nor Q is equal to 0. """
     epsilon = 0.00001

     # You may want to instead make copies to avoid changing the np arrays.
     P = P+epsilon
     Q = Q+epsilon

     divergence = np.sum(P*np.log(P/Q))
     return divergence

# Should be normalized though
values1 = np.asarray([1.346112,1.337432,1.246655])
values2 = np.asarray([1.033836,1.082015,1.117323])

# Note slight difference in the final result compared to Dawny33
print KL(values1, values2) # 0.775278939433
Johann
fuente
¡Buen truco! Me interesaría ver cómo esto se compara con la otra solución en un punto de referencia de tiempo.
seguramente estás disfrutando
0

Considere las tres siguientes muestras de una (s) distribución (es).

values1 = np.asarray([1.3,1.3,1.2])
values2 = np.asarray([1.0,1.1,1.1])
values3 = np.array([1.8,0.7,1.7])

Claramente, los valores1 y los valores2 están más cerca, por lo que esperamos que la medida de la surpriseentropía sea menor en comparación con los valores3.

from scipy.stats import entropy
print("\nIndividual Entropy\n")
print(entropy(values1))
print(entropy(values2))
print(entropy(values3))

print("\nPairwise Kullback Leibler divergence\n")
print(entropy(values1, qk=values2))
print(entropy(values1, qk=values3))
print(entropy(values2, qk=values3))

Vemos el siguiente resultado:

Individual Entropy

1.097913446793334
1.0976250611902076
1.0278436769863724 #<--- this one had the lowest, but doesn't mean much.

Pairwise Kullback Leibler divergence

0.002533297351606588
0.09053972625203921 #<-- makes sense
0.09397968199352116 #<-- makes sense

Vemos que esto tiene sentido porque los valores entre los valores 1 y los valores 3 y los valores 2 y los valores 3 son simplemente más drásticos en el cambio que los valores 1 a los valores 2. Esta es mi validación para comprender KL-D y los paquetes que se pueden aprovechar para ello.

bmc
fuente