静かなる名辞

pythonとプログラミングのこと



【python】距離・非類似度行列を計算する

記事概要

 非類似度行列(距離行列)の計算方法について説明する。

計算方法

対象データと使う非類似度

 とりあえず、データを5つ作る。irisの先頭5要素を抽出する。

from sklearn.datasets import load_iris
iris = load_iris()
data = iris.data[:5]

 5*5の非類似度行列を作りたいというシチュエーションである。

リストとループを使った方法

 scipy.spatial.distanceを使うと距離(非類似度)の計算は簡単にできる。

scipy.spatial.distance.pdist — SciPy v1.0.0 Reference Guide

 euclideanとcosineを使ってみることにする。

 愚直にループを回して行列にしたのが以下のプログラム。

import numpy as np
from scipy.spatial.distance import euclidean, cosine 
from sklearn.datasets import load_iris

iris = load_iris()
data = iris.data[:5]
result = []
for dist in [euclidean, cosine]:
    tmp1 = []
    for v1 in data:
        tmp2 = []
        for v2 in data:
            tmp2.append(dist(v1, v2))
        tmp1.append(tmp2)
    a = np.array(tmp1)
    print(a)
    result.append(a)

 実行結果。

[[0.         0.53851648 0.50990195 0.64807407 0.14142136]
 [0.53851648 0.         0.3        0.33166248 0.60827625]
 [0.50990195 0.3        0.         0.24494897 0.50990195]
 [0.64807407 0.33166248 0.24494897 0.         0.64807407]
 [0.14142136 0.60827625 0.50990195 0.64807407 0.        ]]
[[0.00000000e+00 1.42083650e-03 1.26527175e-05 8.99393151e-04 2.42323318e-04]
 [1.42083650e-03 0.00000000e+00 1.20854727e-03 1.20606955e-03 2.75920410e-03]
 [1.26527175e-05 1.20854727e-03 0.00000000e+00 7.83016618e-04 3.31860741e-04]
 [8.99393151e-04 1.20606955e-03 7.83016618e-04 0.00000000e+00 1.28295812e-03]
 [2.42323318e-04 2.75920410e-03 3.31860741e-04 1.28295812e-03 0.00000000e+00]]

 基本的にはこれでできるということ。問題点としては、

  • 対角成分とその上下は同じなのに二回計算するのは無駄
  • オーバーヘッドが気になるかもしれない
  • 冗長

 ということが主に挙げられるだろう。自分でループの条件を工夫したり速い書き方になるよう努力して対処しても良いが、それすらscipyを使うと簡単にできる。

scipyの関数を使った方法

 行列にするところまでscipyで処理することもできる。

 やり方は恐らく一つではないが、今回はscipy.spatial.distance.pdistとscipy.spatial.distance.squareformが目についたので使ってみる。pdistは対角成分で分けた行列の半分を計算する。これは一次元配列で結果を返すので、squareformで二次元配列に変換する(squareformは適当にコピーして形を変えるだけで、それほど本質的な処理はしていない)。

from scipy.spatial.distance import pdist, squareform
from sklearn.datasets import load_iris

iris = load_iris()
data = iris.data[:5]
for dist in ["euclidean", "cosine"]:
    print(squareform(pdist(data, metric=dist)))

 結果は上と同じなので省略。短く書けるのが素晴らしい。

まとめ

 できた。

おまけ(非類似度・距離と類似度の関係について)

 たまに頭がこんがらがるのでまとめておくと、次のような関係になる。あまり厳密ではないので注意。

  • 類似度とは、sim(a, a) = 1となるmetric
  • 距離とはdist(a, a) = 0となるmetric
  • 非類似度とはdissim(a, a) = 0となるmetric

 同じもの同士が0になるか1になるかという観点からだけ見れば、距離と非類似度は同じものである。ただし、非類似度は普通は0~1の値を取るものに対して言うのではないだろうか。

 また、0~1の値を取る場合、類似度に変換するにはよく見る次の式が使える。

 sim(a,b) = 1 - dissim(a, b)