Obliczanie dywergencji KL w Pythonie

22

Jestem raczej nowy i nie mogę powiedzieć, że mam pełne zrozumienie teoretycznych pojęć. Próbuję obliczyć dywergencję KL między kilkoma listami punktów w Pythonie. Korzystam z http://scikit-learn.org/stable/modules/generated/sklearn.metrics.mutual_info_score.html, aby spróbować to zrobić. Problem, na który napotykam, polega na tym, że zwracana wartość jest taka sama dla 2 dowolnych list liczb (jej 1.3862943611198906). Mam wrażenie, że popełniam tutaj jakiś teoretyczny błąd, ale nie mogę go dostrzec.

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

To jest przykład tego, co uruchamiam - tylko to, że otrzymuję takie same dane wyjściowe dla 2 dowolnych danych wejściowych. Wszelkie porady / pomoc będą mile widziane!

Nanda
źródło
Mówiąc KL, masz na myśli rozbieżność Kullbacka-Leiblera?
Dawny33
Tak, dokładnie to!
Nanda,
Uruchamiając sklearn.metrics.mutual_info_score([1.346112,1.337432,1.246655], [1.033836,1.082015,1.117323]), otrzymuję wartość 1.0986122886681096.
Dawny33
Przepraszam, używałem wartości 1 jako [1, 1.346112,1.337432,1.246655] i wartości2 jako wartości2 jako [1,1.033836,1.082015,1.117323], a zatem wartości różnicy.
Nanda,

Odpowiedzi:

18

Przede wszystkim sklearn.metrics.mutual_info_scoreimplementuje wzajemne informacje do oceny wyników grupowania, a nie czystą dywergencję Kullbacka-Leiblera!

Jest to równe rozbieżności Kullback-Leibler rozłożenia łącznej dystrybucji z rozkładem produktów marginesów.

Rozbieżność KL (i każda inna tego rodzaju miara) oczekuje, że dane wejściowe będą miały sumę 1 . W przeciwnym razie nie są to właściwe rozkłady prawdopodobieństwa . Jeśli twoje dane nie mają sumy 1, najprawdopodobniej zwykle nie jest właściwe stosowanie dywergencji KL! (W niektórych przypadkach dopuszczalna może być suma mniejsza niż 1, np. W przypadku brakujących danych.)

Zauważ również, że często stosuje się logarytmy podstawowe 2. Daje to tylko stały współczynnik skalowania w różnicy, ale logarytmy bazowe 2 są łatwiejsze do interpretacji i mają bardziej intuicyjną skalę (0 do 1 zamiast 0 do log2 = 0,69314 ..., mierząc informacje w bitach zamiast w natach).

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

jak możemy wyraźnie zobaczyć, wynik MI sklearn jest skalowany przy użyciu logarytmów naturalnych zamiast log2. Jest to niefortunny wybór, jak wyjaśniono powyżej.

Rozbieżność Kullbacka-Leiblera jest niestety delikatna. W powyższym przykładzie nie jest dobrze zdefiniowany: KL([0,1],[1,0])powoduje podział przez zero i dąży do nieskończoności. Jest również asymetryczny .

Anony-Mus
źródło
Zauważ, że gdy scipy.stats.entropyzostanie użyty, znormalizuje prawdopodobieństwo do jednego. Z dokumentów ( scipy.github.io/devdocs/generated/scipy.stats.entropy.html ): „Ta procedura normalizuje pk i qk, jeśli nie sumują się do 1.”
Itamar Mushkin
15

Funkcja entropii Scipy'ego obliczy dywergencję KL, jeśli zasilą dwa wektory p i q, z których każdy reprezentuje rozkład prawdopodobieństwa. Jeśli dwa wektory nie są plikami pdf, normalizuje się najpierw.

Wzajemne informacje są powiązane, ale nie takie same jak KL Divergence.

„Ta ważona wzajemna informacja jest formą ważonej dywergencji KL, o której wiadomo, że przyjmuje wartości ujemne dla niektórych danych wejściowych, a istnieją przykłady, w których ważona wzajemna informacja przyjmuje również wartości ujemne”

jamesmf
źródło
6

Nie jestem pewien co do implementacji ScikitLearn, ale oto szybka implementacja rozbieżności KL w Pythonie:

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)

Wydajność: 0.775279624079

W niektórych bibliotekach może wystąpić konflikt implementacji , dlatego przed użyciem należy przeczytać ich dokumentację.

Dawny33
źródło
1
Próbowałem tego też, ale zwracało to wartości ujemne, które moim zdaniem nie są prawidłową wartością. Trochę badań doprowadziło mnie do tego wyniku mathoverflow.net/questions/43849/..., który mówi o tym, jak dane wejściowe muszą być rozkładem prawdopodobieństwa. Zgadnij, gdzie popełniłem błąd.
Nanda,
@Nanda Dzięki za link. Moje zwroty 0.775279624079dla twoich danych wejściowych i metryki sklearn zwracają 1.3862943611198906. Wciąż zdezorientowany! Wygląda jednak na to, że włączenie do skryptu sprawdzania wartości według qn powinno
wystarczyć
1
Wiem co masz na myśli! Próbowałem 3 różnych funkcji, aby uzyskać 3 różne wartości, przy czym jedyną wspólną cechą było to, że wynik nie wydawał się „właściwy”. Wartości wejściowe są zdecydowanie logicznym błędem, więc całkowicie zmieniłem moje podejście!
Nanda,
@Nanda Ahh, to już jasne :) Dzięki za wyjaśnienie
Dawny33
2

Ta sztuczka pozwala uniknąć kodu warunkowego i dlatego może zapewnić lepszą wydajność.

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
źródło
Niezła sztuczka! Byłbym zainteresowany, aby zobaczyć, jak to porównuje się z innym rozwiązaniem na podstawie testu czasu.
pewno żartujesz
0

Rozważ trzy następujące próbki z dystrybucji (-ów).

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

Oczywiście wartości1 i wartości2 są bliższe, więc oczekujemy, że miara surpriselub entropia będą niższe w porównaniu z wartościami3.

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

Widzimy następujące dane wyjściowe:

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

Widzimy, że ma to sens, ponieważ wartości między wartościami 1 a wartościami 3 oraz wartościami 2 i wartościami 3 są po prostu bardziej drastyczne w zmianie niż wartości1 do wartości 2. Jest to moje potwierdzenie zrozumienia KL-D i pakietów, które można do tego wykorzystać.

bmc
źródło