Pynote

Python、機械学習、画像処理について

Python - データの各サンプル同士の距離行列を作成する方法について

キーワード

  • 距離行列 (distance matrix)

距離行列

 m 個のデータがあるとき、(i, j) 成分がサンプル i とサンプル j の距離 d(i, j) である m \times m 行列を距離行列 (distance matrix) という。

  • 距離関数の定義より、d(i, j) = d(j, i) であるから、この行列は対称行列である。
  • 距離関数の定義より、d(i, i) = 0 であるから、この行列の対角成分は0である。

データを作成する。

データとして、2次元の点を5つ作成し、matplotlib で可視化する。

import matplotlib.pyplot as plt
import numpy as np

data = np.array([[6, 3],
                 [5, 7],
                 [4, 6],
                 [4, 4],
                 [6, 7]])

fig, ax = plt.subplots()
ax.set_xlim(2, 8), ax.set_ylim(2, 8)
# 点を描画する。
ax.scatter(data[:, 0], data[:, 1])
for i, (x, y) in enumerate(points):
    ax.text(x + 0.1, y, '{} ({}, {})'.format(i, x, y))


距離行列を作成する。

今回はユークリッドノルムの距離行列を作成する。

numpy の場合、(i, j) 成分が data[i] - data[j] である行列を作成するには、data[:, np.newaxis] - data とすればよい。
第1項 data[:, np.newaxis] の形状は (N, 1, 2)、第2項の形状は (N, 2) であるため、(N, N, 2) にブロードキャストが行われた上で計算されることを利用している。その差に対して、numpy.linalg.norm() をとることで、各サンプル間のユークリッド距離が計算できる。

dist_matrix = np.linalg.norm(data[:, np.newaxis] - data, axis=-1)
print(dist_matrix)
# [[0.         4.12310563 3.60555128 2.23606798 4.        ]
#  [4.12310563 0.         1.41421356 3.16227766 1.        ]
#  [3.60555128 1.41421356 0.         2.         2.23606798]
#  [2.23606798 3.16227766 2.         0.         3.60555128]
#  [4.         1.         2.23606798 3.60555128 0.        ]]

上記の距離行列の計算は scipy.spatial.distance_matrix でも行える。

scipy.spatial.distance_matrix

from scipy.spatial import distance_matrix

dist_matrix = distance_matrix(data, data)
print(dist_matrix)
# [[0.         4.12310563 3.60555128 2.23606798 4.        ]
#  [4.12310563 0.         1.41421356 3.16227766 1.        ]
#  [3.60555128 1.41421356 0.         2.         2.23606798]
#  [2.23606798 3.16227766 2.         0.         3.60555128]
#  [4.         1.         2.23606798 3.60555128 0.        ]]

距離行列を可視化する。

seaborn のヒートマップで距離行列を可視化する。

pynote.hatenablog.com

import matplotlib.pyplot as plt
import seaborn as sns
sns.set()

# 可視化する。
plt.figure(facecolor='w', figsize=(5, 5))
ax = sns.heatmap(dist_matrix, annot=True, fmt='.2f', cmap='Reds', cbar=False)
plt.show()


距離行列からわかること

入力及び距離関数について

上記の例では、2次元の点同士の距離行列を作成したが、n 次元でもよい。
例えば、n 次元の特徴量で表される各サンプルがあるとき、距離が近いサンプルを調べる際に活用できる。
またユークリッド距離に限らず、距離関数であればなんでもよい。

各サンプルから距離が最も近いサンプルを調べる。

# サンプル自身との距離を表す対角成分には inf を入れておく。
np.fill_diagonal(dist_matrix, np.inf)
nearest = np.argmin(dist_matrix, axis=0)
for i, j in enumerate(nearest):
    print('{}と最も近いサンプル: {}'.format(i, j))
# 0と最も近いサンプル: 3
# 1と最も近いサンプル: 4
# 2と最も近いサンプル: 1
# 3と最も近いサンプル: 2
# 4と最も近いサンプル: 1

各サンプルから距離が最も遠いサンプルを調べる。

# サンプル自身との距離を表す対角成分には -inf を入れておく。
np.fill_diagonal(dist_matrix, -np.inf)
farthest = np.argmax(dist_matrix, axis=0)
for i, j in enumerate(farthest):
    print('{}と最も遠いサンプル: {}'.format(i, j))
# 0と最も遠いサンプル: 1
# 1と最も遠いサンプル: 0
# 2と最も遠いサンプル: 0
# 3と最も遠いサンプル: 4
# 4と最も遠いサンプル: 0