NumPyでnp.linalg.normを使うよりもnp.einsumを使うほうが速い

プログラミング

3次元空間の2点間の距離は

$$
\sqrt{(x_2 – x_1)^2 + (y_2 – y_1)^2 + (z_2 – z_1)^2}
$$

で計算できますが、2点ではなく多数の点があり、その相互の距離を計算したい場合などは、計算方法によって速度に差が出てきます。それを検証してみます。

スポンサーリンク

比較するソースコード

3通りの計算方法を比較します。

  1. NumPyで配列を2乗してsumしてsqrtする
  2. NumPyでnp.linalg.normを使う
  3. NumPyでeinsumしてsqrtする

なお、距離の計算をするのが目的ですがプログラム内では「3成分を2乗して足し合わせてルートする」というノルム計算を行います。

配列を2乗してsumしてsqrtするコード

1億個の3次元ベクトルを用意してノルムを計算します。
まずは数式をそのまま配列計算するように書くパターン。

import numpy as np

try:
    xyz = np.load("xyz.npy")
except:
    N = 100000000
    xyz = np.random.rand(N, 3)
    np.save("xyz.npy", xyz)

norm = np.sqrt(np.sum(xyz**2, axis=1))

NumPyでnp.linalg.normを使うコード

np.linalg.normを使います。ベクトルのノルム計算で調べるとこれが出てくると思います。

import numpy as np

try:
    xyz = np.load("xyz.npy")
except:
    N = 100000000
    xyz = np.random.rand(N, 3)
    np.save("xyz.npy", xyz)

norm = np.linalg.norm(xyz, axis=1)

NumPyでeinsumしてsqrtするコード

このコードでは、行列要素を2乗してsumしてsqrtに入れるという処理を行いますが、書き方がちょっと特殊です。”ij,ij->i”の部分の意味はアインシュタインの縮約記法で検索してみてください。

import numpy as np

try:
    xyz = np.load("xyz.npy")
except:
    N = 100000000
    xyz = np.random.rand(N, 3)
    np.save("xyz.npy", xyz)

norm = np.sqrt(np.einsum("ij,ij->i", xyz, xyz))
スポンサーリンク

結果

以下のような結果になりました。

コード実行時間(3回平均)
np.sqrt(np.sum(xyz**2, axis=1))2.30秒
np.linalg.norm(xyz, axis=1)2.45秒
np.sqrt(np.einsum(“ij,ij->i”, xyz, xyz))1.43秒

einsumを使うパターンが他よりも2倍くらい速くなりました。一方で、他2つに差がほとんどないというか、むしろnp.linalg.normを使わないほうが若干速いのは意外でした。
距離計算が多数ある場合はこの処理を何度も繰り返すことになるので、そこで高速化ができるのは嬉しいですね。

コメント

タイトルとURLをコピーしました