Cálculo da divergência KL em Python

22

Eu sou bastante novo nisso e não posso dizer que tenho uma compreensão completa dos conceitos teóricos por trás disso. Estou tentando calcular a divergência KL entre várias listas de pontos em Python. Estou usando http://scikit-learn.org/stable/modules/generated/sklearn.metrics.mutual_info_score.html para tentar fazer isso. O problema que eu estou enfrentando é que o valor retornado é o mesmo para quaisquer 2 listas de números (seu 1.3862943611198906). Sinto que estou cometendo algum tipo de erro teórico aqui, mas não consigo identificá-lo.

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

Esse é um exemplo do que estou executando - apenas que estou obtendo a mesma saída para qualquer 2 entradas. Qualquer conselho / ajuda seria apreciada!

Nanda
fonte
Por KL, você quer dizer divergência Kullback-Leibler?
Dawny33
Sim exatamente isso!
Nanda
Ao executar sklearn.metrics.mutual_info_score([1.346112,1.337432,1.246655], [1.033836,1.082015,1.117323]), recebo o valor 1.0986122886681096.
Dawny33
Desculpe, eu estava usando valores1 como [1, 1.346112,1.337432,1.246655] e valores2 como valores2 como [1,1.033836,1.082015,1.117323] e, portanto, o valor da diferença.
Nanda

Respostas:

18

Em primeiro lugar, sklearn.metrics.mutual_info_scoreimplementa informações mútuas para avaliar os resultados do agrupamento, não a divergência pura de Kullback-Leibler!

Isso é igual à divergência de Kullback-Leibler da distribuição conjunta com a distribuição do produto dos marginais.

A divergência de KL (e qualquer outra medida) espera que os dados de entrada tenham uma soma de 1 . Caso contrário, elas não são distribuições de probabilidade adequadas . Se seus dados não tiverem a soma de 1, provavelmente não é apropriado usar a divergência de KL! (Em alguns casos, pode ser admissível ter uma soma menor que 1, por exemplo, no caso de dados ausentes.)

Observe também que é comum usar logaritmos da base 2. Isso gera apenas um fator de escala constante na diferença, mas os logaritmos da base 2 são mais fáceis de interpretar e têm uma escala mais intuitiva (0 a 1 em vez de 0 a log2 = 0,69314 ..., medindo as informações em bits em vez de nats).

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

como podemos ver claramente, o resultado do MI do sklearn é escalado usando logaritmos naturais em vez de log2. Esta é uma escolha infeliz, como explicado acima.

Infelizmente, a divergência entre Kullback e Leibler é frágil. No exemplo acima, não está bem definido: KL([0,1],[1,0])causa uma divisão por zero e tende ao infinito. Também é assimétrico .

Anony-Mousse
fonte
Observe que, quando scipy.stats.entropyusado, normalizará as probabilidades para um. Nos documentos ( scipy.github.io/devdocs/generated/scipy.stats.entropy.html ): "Esta rotina normalizará pk e qk se não somarem 1."
Itamar Mushkin
15

A função de entropia de Scipy calculará a divergência de KL se alimentar dois vetores peq, cada um representando uma distribuição de probabilidade. Se os dois vetores não forem PDFs, ele será normalizado primeiro.

As informações mútuas estão relacionadas, mas não são iguais , à KL Divergence.

"Essas informações mútuas ponderadas são uma forma de divergência KL ponderada, que é conhecida por receber valores negativos para algumas entradas, e há exemplos em que as informações mútuas ponderadas também assumem valores negativos"

jamesmf
fonte
6

Não tenho certeza da implementação do ScikitLearn, mas aqui está uma rápida implementação da divergência de KL no 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)

Saída: 0.775279624079

Pode haver conflito de implementação em algumas bibliotecas, portanto, leia os documentos antes de usar.

Dawny33
fonte
1
Eu tentei isso também, mas isso estava retornando valores negativos que, eu acho, não são válidos. Um pouco de pesquisa me levou a esse resultado mathoverflow.net/questions/43849/…, que fala sobre como a entrada deve ser uma distribuição de probabilidade. Acho que foi onde eu cometi o meu erro.
Nanda
@Nanda Obrigado pelo link. A mina retorna 0.775279624079para suas entradas e as métricas do sklearn retornam 1.3862943611198906. Confuso ainda! Mas, parece que incluindo os cheques de valor de acordo com o qn, no script deve fazer :)
Dawny33
1
Eu sei o que você quer dizer! Eu tentei 3 funções diferentes para obter 3 valores diferentes, com a única coisa comum entre eles: o resultado não "parecia" certo. Os valores de entrada são definitivamente um erro lógico, portanto, mudei completamente minha abordagem!
Nanda
@Nanda Ahh, isso está claro agora :) Obrigado por explicar
Dawny33
2

Esse truque evita código condicional e, portanto, pode fornecer melhor desempenho.

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
fonte
Bom truque! Eu ficaria interessado em ver como isso se compara com a outra solução em uma referência de tempo.
surelyourejoking
0

Considere as três seguintes amostras de uma (s) distribuição (õ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, os valores1 e os valores2 estão mais próximos, por isso esperamos que a medida surpriseou a entropia seja menor quando comparado aos 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 a seguinte saída:

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 isso faz sentido porque os valores entre os valores1 e os valores3 e os valores 2 e os valores 3 são simplesmente mais drásticos na mudança do que os valores1 para os valores 2. Essa é minha validação para entender o KL-D e os pacotes que podem ser aproveitados para isso.

bmc
fonte