web-dev-qa-db-ja.com

ベクトル化されたユークリッド距離を実行するためのNumpyBroadcast

2 x4と3x 4の行列があります。行全体のユークリッド距離を見つけて、最後に2 x3の行列を取得したいと思います。これは、すべてのb行ベクトルに対してaのすべての行ベクトルのユークリッド距離を計算する1つのforループを持つコードです。 forループを使用せずに同じことを行うにはどうすればよいですか?

 import numpy as np
a = np.array([[1,1,1,1],[2,2,2,2]])
b = np.array([[1,2,3,4],[1,1,1,1],[1,2,1,9]])
dists = np.zeros((2, 3))
for i in range(2):
      dists[i] = np.sqrt(np.sum(np.square(a[i] - b), axis=1))
9
user1835351

適切な場所でnp.newaxisを使用するだけです。

 np.sqrt((np.square(a[:,np.newaxis]-b).sum(axis=2)))
12
gg349

元の入力変数は次のとおりです。

A = np.array([[1,1,1,1],[2,2,2,2]])
B = np.array([[1,2,3,4],[1,1,1,1],[1,2,1,9]])
A
# array([[1, 1, 1, 1],
#        [2, 2, 2, 2]])
B
# array([[1, 2, 3, 4],
#        [1, 1, 1, 1],
#        [1, 2, 1, 9]])

Aは2x4アレイです。 Bは3x4アレイです。

ユークリッド距離行列演算を1つの完全にベクトル化された演算で計算したいと思います。ここで、dist[i,j]にはAのi番目のインスタンスとBのj番目のインスタンスの間の距離が含まれます。したがって、この例ではdistは2x3です。

距離

enter image description here

表面上はnumpyで次のように書くことができます

dist = np.sqrt(np.sum(np.square(A-B))) # DOES NOT WORK
# Traceback (most recent call last):
#   File "<stdin>", line 1, in <module>
# ValueError: operands could not be broadcast together with shapes (2,4) (3,4)

ただし、上記のように、問題は、要素ごとの減算演算A-Bに互換性のない配列サイズ、具体的には1次元の2と3が含まれることです。

A has dimensions 2 x 4
B has dimensions 3 x 4

要素ごとの減算を行うには、numpyのブロードキャストルールを満たすためにAまたはBのいずれかをパディングする必要があります。 Aを2x 1 x 4になるように追加の寸法で埋めることを選択します。これにより、アレイの寸法をブロードキャスト用に揃えることができます。 numpyブロードキャストの詳細については、 scipyマニ​​ュアルのチュートリアル および このチュートリアル の最後の例を参照してください。

パディングは、np.newaxis値またはnp.reshapeコマンドのいずれかを使用して実行できます。以下に両方を示します。

# First approach is to add the extra dimension to A with np.newaxis
A[:,np.newaxis,:] has dimensions 2 x 1 x 4
B has dimensions                     3 x 4

# Second approach is to reshape A with np.reshape
np.reshape(A, (2,1,4)) has dimensions 2 x 1 x 4
B has dimensions                          3 x 4

ご覧のとおり、どちらのアプローチを使用しても、寸法を揃えることができます。 np.newaxisを使用した最初のアプローチを使用します。これで、2x3x4配列であるA-Bを作成できます。

diff = A[:,np.newaxis,:] - B
# Alternative approach:
# diff = np.reshape(A, (2,1,4)) - B
diff.shape
# (2, 3, 4)

これで、その差分式をdist方程式ステートメントに入れて、最終結果を得ることができます。

dist = np.sqrt(np.sum(np.square(A[:,np.newaxis,:] - B), axis=2))
dist
# array([[ 3.74165739,  0.        ,  8.06225775],
#        [ 2.44948974,  2.        ,  7.14142843]])

sumaxis=2を超えていることに注意してください。これは、2x3x4配列の3番目の軸(軸IDが0で始まる)の合計を取得することを意味します。

配列が小さい場合は、上記のコマンドで問題なく動作します。ただし、大きな配列がある場合は、メモリの問題が発生する可能性があります。上記の例では、numpyはブロードキャストを実行するために2x3x4配列を内部的に作成したことに注意してください。 Aの次元をa x zに、Bの次元をb x zに一般化すると、numpyはブロードキャスト用にa x b x z配列を内部的に作成します。

数学的な操作を行うことで、この中間配列の作成を回避できます。ユークリッド距離を二乗和の差として計算しているので、二乗和の差を書き換えることができるという数学的事実を利用できます。

enter image description here

中間項には、要素ごとの乗算の合計が含まれることに注意してください。乗算に対するこの合計は、内積としてよく知られています。 AとBはそれぞれ行列であるため、この演算は実際には行列の乗算です。したがって、上記を次のように書き直すことができます。

enter image description here

次に、次のnumpyコードを記述できます。

threeSums = np.sum(np.square(A)[:,np.newaxis,:], axis=2) - 2 * A.dot(B.T) + np.sum(np.square(B), axis=1)
dist = np.sqrt(threeSums)
dist
# array([[ 3.74165739,  0.        ,  8.06225775],
#        [ 2.44948974,  2.        ,  7.14142843]])

上記の答えは、前の実装とまったく同じであることに注意してください。ここでも、ブロードキャスト用に中間の2x3x4アレイを作成する必要がないという利点があります。

完全を期すために、threeSumsの各被加数の次元がブロードキャストを許可していることを再確認しましょう。

np.sum(np.square(A)[:,np.newaxis,:], axis=2) has dimensions 2 x 1
2 * A.dot(B.T) has dimensions                               2 x 3
np.sum(np.square(B), axis=1) has dimensions                 1 x 3

したがって、予想どおり、最終的なdist配列の次元は2x3です。

要素ごとの乗算の合計の代わりにドット積を使用する方法については、 このチュートリアル でも説明しています。

22

最近ディープラーニング(スタンフォードcs231n、Assignment1)で作業しているときに同じ問題が発生しましたが、

 np.sqrt((np.square(a[:,np.newaxis]-b).sum(axis=2)))

エラーが発生しました

MemoryError

つまり、メモリが不足したことを意味します(実際、中央に500 * 5000 * 1024の配列が生成されました。非常に巨大です!)

そのエラーを防ぐために、式を使用して単純化することができます。

コード:

import numpy as np
aSumSquare = np.sum(np.square(a),axis=1);
bSumSquare = np.sum(np.square(b),axis=1);
mul = np.dot(a,b.T);
dists = np.sqrt(aSumSquare[:,np.newaxis]+bSumSquare-2*mul)
21
Han Qiu

この機能はすでに scipyの空間モジュール に含まれており、内部でベクトル化され、高度に最適化されるため、使用することをお勧めします。しかし、他の答えから明らかなように、これを自分で行う方法はいくつかあります。

import numpy as np
a = np.array([[1,1,1,1],[2,2,2,2]])
b = np.array([[1,2,3,4],[1,1,1,1],[1,2,1,9]])
np.sqrt((np.square(a[:,np.newaxis]-b).sum(axis=2)))
# array([[ 3.74165739,  0.        ,  8.06225775],
#       [ 2.44948974,  2.        ,  7.14142843]])
from scipy.spatial.distance import cdist
cdist(a,b)
# array([[ 3.74165739,  0.        ,  8.06225775],
#       [ 2.44948974,  2.        ,  7.14142843]])
3
Oliver W.

numpy.linalg.norm を使用すると、ブロードキャストでもうまく機能します。 axisに整数値を指定すると、ベクトルノルムが使用されます。デフォルトはユークリッドノルムです。

import numpy as np

a = np.array([[1,1,1,1],[2,2,2,2]])
b = np.array([[1,2,3,4],[1,1,1,1],[1,2,1,9]])
np.linalg.norm(a[:, np.newaxis] - b, axis = 2)

# array([[ 3.74165739,  0.        ,  8.06225775],
#       [ 2.44948974,  2.        ,  7.14142843]])
1
merv