()

Qiita Articles

公開記事のミラー・バックアップ

プログラミング・可視化

【Python】距離行列の計算と実行時間

Qiita公開: · 元記事更新:
バックアップ取得:

筆者自身のQiita記事を転載しています。内容は取得時点の記録です。Qiitaの元記事 · 原文Markdownを保存

この記事の目次

距離行列(distance matrix)とは、データセットに含まれる各要素間の距離を表した行列のことです。データ解析、クラスタリング、機械学習の前処理など、さまざまな場面で広く用いられています。
本記事ではランダムに生成した3次元データを用いてPython環境で距離行列を作成し、実行時間を比較します。

ランダムに生成した3次元データ
import numpy as np

np.random.seed(42)
data = np.random.rand(1000, 3)

距離行列の作成例

1. scipyを用いた方法

from scipy.spatial import distance

dist_matrix = distance.cdist(data, data, metric='euclidean')

2. sklearnを用いた方法

from sklearn.metrics import pairwise_distances

dist_matrix = pairwise_distances(data, metric='euclidean')

3. numpyを用いた方法

import numpy as np

def numpy_distance_matrix(data):
    sum_sq = np.sum(np.square(data), axis=1)
    dist_matrix = np.sqrt(np.maximum(sum_sq[:, np.newaxis] + sum_sq - 2 * data.dot(data.T), 0))
    return dist_matrix

dist_matrix = numpy_distance_matrix(data)

numpyには距離行列を与える関数が組み込み関数としては実装されていないため、ここではベクトル計算として実装している。

4. numbaを用いた方法

from numba import jit
import numpy as np

@jit(nopython=True)
def numba_distance_matrix(data):
    n = data.shape[0]
    dist_matrix = np.zeros((n, n))
    for i in range(n):
        for j in range(n):
            dist_matrix[i, j] = np.sqrt(np.sum((data[i] - data[j])**2))
    return dist_matrix

dist_matrix = numba_distance_matrix(data)

5. 純粋なPythonによる実装(mathライブラリ)

純粋なPythonによる実装(mathライブラリ)
import math

def plain_Python(data):
    n = len(data)
    distance_matrix = [[0.0 for _ in range(n)] for _ in range(n)]
    for i in range(n):
        for j in range(n):
            tmp_distance_sq = 0.0
            for k in range(3):
                tmp_distance_sq += (data[i][k] - data[j][k]) ** 2
            distance_matrix[i][j] = math.sqrt(tmp_distance_sq)
    return distance_matrix

6. 純粋なPythonによる実装(numpyライブラリ)

純粋なPythonによる実装(numpyライブラリ)
import numpy as np

def plain_Python2(data):
    n = data.shape[0]
    dist_matrix = np.zeros((n, n))
    for i in range(n):
        for j in range(n):
            dist_matrix[i, j] = np.sqrt(np.sum((data[i] - data[j])**2))
    return dist_matrix

実行速度の比較(3次元データ)

各手法を同じデータ(1000個の3次元点列)に適用した際の平均実行速度を以下の表にまとめました。シード値を固定しない場合でも結果はあまり変わりません。

ライブラリ・手法 実行時間 (秒)
1. scipy 0.004471
2. sklearn 0.006393
3. numpy 0.010576
4. numba利用 0.095734
5. math + forループ 1.880585
6. numpy + forループ 4.866355

10回の平均値として算出。実行時間は環境やバージョンにより変動するので、あくまで目安として考えてください。(特にnumbaに関してはチューニング次第でさらに速くなる可能性があります)

実行環境(参考)
Python 3.9.12
CPU AMD Ryzen 7 5800X3D 8-Core Processor
RAM 32 GB (DDR4-3600)
scipy 1.7.3
sklearn 1.0.2
numpy 1.21.5
numba 0.55.1

まとめ

  • scipyとsklearnが最も高速かつ実用的。
  • numpyのベクトル演算も高速だが、大規模データでは注意が必要。
  • numbaは高速化が期待されるが、実際の速度は環境により大きく異なる。
  • 純粋Pythonのforループ実装は速度が非常に遅く、実用的ではない。

目的やデータの規模に応じて適切な手法を選択することが重要です。
C言語等で実装するのが速くて良いですが、Pythonで距離行列を何度も計算する必要がある場合はとりあえずscipyを使っておけば良さそうです。for文ベタ書きならJITコンパイラが実装されているJulia言語などでも速そうです。