Calcolo della divergenza di KL in Python


22

Sono piuttosto nuovo in questo e non posso dire di avere una completa comprensione dei concetti teorici alla base di questo. Sto cercando di calcolare la divergenza KL tra diversi elenchi di punti in Python. Sto usando http://scikit-learn.org/stable/modules/generated/sklearn.metrics.mutual_info_score.html per provare a farlo. Il problema che sto incontrando è che il valore restituito è lo stesso per qualsiasi 2 elenchi di numeri (è 1.3862943611198906). Ho la sensazione che sto facendo una sorta di errore teorico qui, ma non riesco a individuarlo.

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

Questo è un esempio di ciò che sto eseguendo, solo che sto ottenendo lo stesso output per qualsiasi input 2. Qualsiasi consiglio / aiuto sarebbe apprezzato!


Per KL, intendi la divergenza di Kullback-Leibler?
Dawny33

Sì, esattamente quello!
Nanda,

Correndo sklearn.metrics.mutual_info_score([1.346112,1.337432,1.246655], [1.033836,1.082015,1.117323]), ottengo il valore 1.0986122886681096.
Dawny33

Spiacente, stavo usando i valori1 come [1, 1.346112,1.337432,1.246655] e i valori2 come valori2 come [1,1,033836,1,082015,1,117323] e quindi il valore della differenza.
Nanda,

Risposte:


18

Innanzitutto, sklearn.metrics.mutual_info_scoreimplementa le informazioni reciproche per valutare i risultati del clustering, non la pura divergenza di Kullback-Leibler!

Ciò equivale alla divergenza di Kullback-Leibler della distribuzione congiunta con la distribuzione del prodotto dei marginali.

La divergenza di KL (e qualsiasi altra misura simile) prevede che i dati di input abbiano una somma di 1 . In caso contrario, essi sono non corretti distribuzioni di probabilità . Se i tuoi dati non hanno una somma di 1, molto probabilmente non è corretto usare la divergenza KL! (In alcuni casi, può essere ammissibile avere una somma inferiore a 1, ad esempio nel caso di dati mancanti.)

Si noti inoltre che è comune utilizzare i logaritmi di base 2. Ciò produce solo un fattore di ridimensionamento costante nella differenza, ma i logaritmi di base 2 sono più facili da interpretare e hanno una scala più intuitiva (da 0 a 1 invece di 0 a log2 = 0,69314 ..., misurando le informazioni in bit anziché in nat).

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

come possiamo vedere chiaramente, il risultato MI di sklearn viene ridimensionato usando logaritmi naturali anziché log2. Questa è una scelta sfortunata, come spiegato sopra.

La divergenza di Kullback-Leibler è sfortunatamente fragile. Nell'esempio sopra non è ben definito: KL([0,1],[1,0])causa una divisione per zero e tende all'infinito. È anche asimmetrico .


Si noti che quando scipy.stats.entropyviene utilizzato, normalizzerà le probabilità a una. Dai documenti ( scipy.github.io/devdocs/generated/scipy.stats.entropy.html ): "Questa routine normalizzerà pk e qk se non si sommano a 1."
Itamar Mushkin,

15

La funzione entropia di Scipy calcolerà la divergenza di KL se alimenta due vettori p e q, ognuno dei quali rappresenta una distribuzione di probabilità. Se i due vettori non sono pdf, si normalizzerà prima.

Le informazioni reciproche sono correlate, ma non uguali, a KL Divergence.

"Questa informazione reciproca ponderata è una forma di divergenza KL ponderata, che è noto per assumere valori negativi per alcuni input, e ci sono esempi in cui l'informazione reciproca ponderata assume anche valori negativi"


6

Non sono sicuro dell'implementazione di ScikitLearn, ma ecco una rapida implementazione della divergenza di KL in 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)

Produzione: 0.775279624079

Potrebbero esserci conflitti di implementazione in alcune librerie, quindi assicurati di leggere i loro documenti prima di utilizzarli.


1
Ho provato anche questo, ma questo stava restituendo valori negativi che, a mio avviso, non sono validi. Un po 'di ricerca mi ha portato a questo risultato mathoverflow.net/questions/43849/… che parla di come l'input deve essere una distribuzione di probabilità. Immagino che sia lì che ho fatto il mio errore.
Nanda,

@Nanda Grazie per il link. I miei ritorni 0.775279624079per i tuoi input e il ritorno delle metriche sklearn 1.3862943611198906. Ancora confuso! Ma sembra che includere nello script quei controlli di valore secondo qn, dovrebbe fare :)
Dawny33

1
So cosa vuoi dire! Ho provato 3 diverse funzioni per ottenere 3 diversi valori con l'unica cosa comune tra loro che il risultato non "sentirsi" giusto. I valori di input sono sicuramente un errore logico, quindi ho cambiato del tutto il mio approccio!
Nanda,

@Nanda Ahh, adesso è chiaro :) Grazie per aver spiegato
Dawny33

2

Questo trucco evita il codice condizionale e può quindi fornire prestazioni migliori.

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

Bel trucco! Sarei interessato a vedere come questo si confronta con l'altra soluzione su un benchmark temporale.
sicuramente scherzo

0

Considera i tre seguenti campioni da una o più distribuzioni.

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])

Chiaramente, i valori 1 e valori2 sono più vicini, quindi prevediamo che la misura surpriseo l'entropia siano inferiori rispetto ai valori3.

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))

Vediamo il seguente output:

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

Vediamo che questo ha senso perché i valori tra valori1 e valori3 e valori 2 e valori 3 sono semplicemente più drastici nel cambiamento rispetto ai valori1 ai valori 2. Questa è la mia convalida per comprendere KL-D e i pacchetti che possono essere sfruttati per questo.

Utilizzando il nostro sito, riconosci di aver letto e compreso le nostre Informativa sui cookie e Informativa sulla privacy.
Licensed under cc by-sa 3.0 with attribution required.