鴨川にあこがれる日々

軽い技術っぽい記事かいてます

PythonでKullback-LeiblerダイバージェンスとJensen-Shannonダイバージェンス

11月5日追記

最下部に追記しました.

前置き

2つの確率分布の違いを表す指標にKullback-Leiblerダイバージェンス(以下KLダイバージェンス)とJensen-Shannonダイバージェンス(以下JSダイバージェンス)があります.

詳しいことはamzn.to
の1.6を参考にしてください.

本にもありますが,これらの指標が"距離"といわれることがありますが,厳密な距離の定義を満たしていないので気をつけましょう.


PythonからJSダイバージェンスを使う必要があり
「どうせ,scipyかnumpyかscikit-learnにあるっしょwwww」
とかいってたらJSダイバージェンスがなかったので,書きました.
(前者はscipyにあります)

環境

本題

肝心なのはscipyのentropy関数です.
scipy.stats.entropy — SciPy v0.17.0.dev0+729e5fb Reference Guide

引数がpkだけだとエントロピー,qkがあるとKLダイバージェンスを返します.
baseはlogの底です.

KLダイバージェンスは,このように書けます.
ちなみにノルム1で正規化する処理はentropy関数の内部でやってくれます.
計算例は上で挙げた本の例題を使っています.

import numpy as np
from scipy import stats


def kl(w1,w2):
    return stats.entropy(w1, w2, 2)

eat = np.array([0.9, 0.1])
devour = np.array([0.8, 0.2])
drink = np.array([0.1, 0.9])

print("kl divergence")
print(kl(devour, eat))
print(kl(devour, drink))
kl divergence
0.0640599988462
1.96601499971

KLダイバージェンスは違いを表す指標なので,devourの確率分布に対してdrinkの確率分布よりeatの確率分布が似ていると解釈できます.


JSダイバージェンスは,同じ事象のときの確率値の和qkを計算し,
2つの確率分布とqkのKLダイバージェンスの平均をとることで求められます.

import numpy as np
from scipy import stats


def js(w1, w2):
    r = (w1 + w2) / 2
    return 0.5 * (stats.entropy(w1, r, 2) + stats.entropy(w2, r, 2))


eat = np.array([0.9, 0.1])
devour = np.array([0.8, 0.2])
drink = np.array([0.1, 0.9])

print("js divergence")
print(js(devour, eat))
print(js(devour, drink))
js divergence
0.0143784604781
0.397312609749

ちなみにjuliaにJSダイバージェンスの関数があります.

以上です

追記分

javaでJSダイバージェンスを再実装していた時に気付いたんですが,
entropyを2回呼ぶのが無駄がありました(総和が同じなのでforでまとめられる)

ただしentorpy関数の中身に関係する話なので,別に定義する必要があります.
小規模データであればentorpy2回呼べばいいと思いますが,大規模になったときは自分で定義してやる方がいいかもしれません.
あと,並列化もさせられたら嬉しいと思いました(小並感)