Calcul de la divergence KL en Python

22

Je suis plutôt nouveau dans ce domaine et je ne peux pas dire que j'ai une compréhension complète des concepts théoriques derrière cela. J'essaie de calculer la divergence KL entre plusieurs listes de points en Python. J'utilise http://scikit-learn.org/stable/modules/generated/sklearn.metrics.mutual_info_score.html pour essayer de le faire. Le problème que je rencontre est que la valeur retournée est la même pour 2 listes de nombres (son 1.3862943611198906). J'ai le sentiment que je fais une sorte d'erreur théorique ici, mais je ne peux pas la repérer.

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

C'est un exemple de ce que j'exécute - juste que j'obtiens la même sortie pour n'importe quelle entrée. Tout conseil / aide serait apprécié!

Nanda
la source
Par KL, voulez-vous dire la divergence Kullback-Leibler?
Dawny33
Oui, exactement ça!
Nanda
En courant sklearn.metrics.mutual_info_score([1.346112,1.337432,1.246655], [1.033836,1.082015,1.117323]), j'obtiens la valeur 1.0986122886681096.
Dawny33
Désolé, j'utilisais values1 as [1, 1.346112,1.337432,1.246655] and values2 as values2 as [1,1.033836,1.082015,1.117323] et donc la valeur de différence.
Nanda

Réponses:

18

Tout d'abord, sklearn.metrics.mutual_info_scoreimplémente des informations mutuelles pour évaluer les résultats de clustering, et non une divergence Kullback-Leibler pure !

Ceci est égal à la divergence de Kullback-Leibler de la distribution conjointe avec la distribution des produits des marginaux.

La divergence KL (et toute autre mesure de ce type) s'attend à ce que les données d'entrée aient une somme de 1 . Sinon, ce ne sont pas des distributions de probabilité appropriées . Si vos données n'ont pas une somme de 1, il n'est probablement pas approprié d'utiliser la divergence KL! (Dans certains cas, il peut être admissible d'avoir une somme inférieure à 1, par exemple en cas de données manquantes.)

Notez également qu'il est courant d'utiliser des logarithmes en base 2. Cela ne donne qu'un facteur d'échelle constant dans la différence, mais les logarithmes en base 2 sont plus faciles à interpréter et ont une échelle plus intuitive (0 à 1 au lieu de 0 à log2 = 0,69314 ..., mesurant les informations en bits au lieu de nats).

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

comme nous pouvons le voir clairement, le résultat MI de sklearn est mis à l'échelle en utilisant des logarithmes naturels au lieu de log2. C'est un choix malheureux, comme expliqué ci-dessus.

La divergence Kullback-Leibler est fragile, malheureusement. Dans l'exemple ci-dessus, il n'est pas bien défini: KL([0,1],[1,0])provoque une division par zéro et tend vers l'infini. Il est également asymétrique .

Anony-Mousse -Reinstate Monica
la source
Notez que lorsqu'il scipy.stats.entropyest utilisé, il normalisera les probabilités à un. À partir des documents ( scipy.github.io/devdocs/generated/scipy.stats.entropy.html ): "Cette routine normalisera pk et qk si elles ne totalisent pas 1."
Itamar Mushkin
15

La fonction d'entropie de Scipy calculera la divergence KL si alimentent deux vecteurs p et q, chacun représentant une distribution de probabilité. Si les deux vecteurs ne sont pas des fichiers PDF, il se normalisera ensuite en premier.

Les informations mutuelles sont liées, mais pas identiques, à KL Divergence.

"Ces informations mutuelles pondérées sont une forme de divergence KL pondérée, qui est connue pour prendre des valeurs négatives pour certaines entrées, et il existe des exemples où les informations mutuelles pondérées prennent également des valeurs négatives"

jamesmf
la source
6

Je ne suis pas sûr de l'implémentation de ScikitLearn, mais voici une implémentation rapide de la divergence 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)

Sortie: 0.775279624079

Il peut y avoir un conflit d'implémentation dans certaines bibliothèques, alors assurez-vous de lire leurs documents avant de les utiliser.

Dawny33
la source
1
J'ai essayé cela aussi mais cela renvoyait des valeurs négatives qui, je pense, ne sont pas une valeur valide. Un peu de recherche m'a ensuite amené à ce résultat mathoverflow.net/questions/43849/… qui explique comment l'entrée doit être une distribution de probabilité. Je suppose que c'est là que j'ai fait mon erreur.
Nanda
@Nanda Merci pour le lien. Le mien revient 0.775279624079pour vos entrées et les métriques sklearn reviennent 1.3862943611198906. Toujours confus! Mais, semble inclure ces vérifications de valeur selon le qn, dans le script devrait faire :)
Dawny33
1
Je vois ce que tu veux dire! J'ai essayé 3 fonctions différentes pour obtenir 3 valeurs différentes, la seule chose commune entre elles étant que le résultat ne "se sentait" pas bien. Les valeurs d'entrée sont définitivement une erreur logique, donc je change complètement mon approche!
Nanda
@Nanda Ahh, c'est clair maintenant :) Merci d'avoir expliqué
Dawny33
2

Cette astuce évite le code conditionnel et peut donc fournir de meilleures performances.

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
la source
Joli tour! Je serais intéressé de voir comment cela se compare avec l'autre solution sur un repère de temps.
sûrementourejoking
0

Considérez les trois échantillons suivants d'une distribution (s).

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

De toute évidence, les valeurs1 et les valeurs2 sont plus proches, nous nous attendons donc à ce que la mesure de l' surpriseentropie soit plus faible par rapport aux valeurs3.

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

Nous voyons la sortie suivante:

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

Nous voyons que cela a du sens parce que les valeurs entre les valeurs1 et les valeurs3 et les valeurs 2 et les valeurs 3 sont tout simplement plus drastiques dans le changement que les valeurs1 vers les valeurs 2. C'est ma validation pour comprendre KL-D et les packages qui peuvent être exploités pour cela.

bmc
la source