最近Python 3.5に移動し、 新しい行列乗算演算子(@) が numpyドット 演算子と異なる動作をすることがあることに気付きました。たとえば、3D配列の場合:
import numpy as np
a = np.random.Rand(8,13,13)
b = np.random.Rand(8,13,13)
c = a @ b # Python 3.5+
d = np.dot(a, b)
@
演算子は、形状の配列を返します。
c.shape
(8, 13, 13)
np.dot()
関数は以下を返します:
d.shape
(8, 13, 8, 13)
Numpyドットで同じ結果を再現するにはどうすればよいですか?他に大きな違いはありますか?
@
演算子は、dot
ではなく、配列の__matmul__
メソッドを呼び出します。このメソッドは、APIにも関数 np.matmul
として存在しています。
>>> a = np.random.Rand(8,13,13)
>>> b = np.random.Rand(8,13,13)
>>> np.matmul(a, b).shape
(8, 13, 13)
ドキュメントから:
matmul
は、2つの重要な点でdot
と異なります。
- スカラーによる乗算は許可されていません。
- マトリックスのスタックは、マトリックスが要素であるかのように一緒にブロードキャストされます。
最後の点は、dot
メソッドとmatmul
メソッドが3D(またはより高次元の)配列を渡されたときに異なる動作をすることを明確にします。ドキュメントからさらに引用:
matmul
の場合:
いずれかの引数がN-D、N> 2の場合、最後の2つのインデックスにあるマトリックスのスタックとして扱われ、それに応じてブロードキャストされます。
np.dot
の場合:
2次元配列の場合、これは行列の乗算に相当し、1次元配列の場合、ベクトルの内積(複素共役なし)に相当します。 N次元の場合、aの最後の軸とbの最後から2番目の軸の合計積です
@ajcrによる答えは、dot
とmatmul
(@
シンボルによって呼び出される)の違いを説明しています。簡単な例を見ると、「行列のスタック」またはテンソルを操作するときに2つの動作が異なることが明確にわかります。
違いを明確にするために、4x4の配列を取り、2x4x3の「行列のスタック」またはテンソルを持つdot
productとmatmul
productを返します。
import numpy as np
fourbyfour = np.array([
[1,2,3,4],
[3,2,1,4],
[5,4,6,7],
[11,12,13,14]
])
twobyfourbythree = np.array([
[[2,3],[11,9],[32,21],[28,17]],
[[2,3],[1,9],[3,21],[28,7]],
[[2,3],[1,9],[3,21],[28,7]],
])
print('4x4*4x2x3 dot:\n {}\n'.format(np.dot(fourbyfour,twobyfourbythree)))
print('4x4*4x2x3 matmul:\n {}\n'.format(np.matmul(fourbyfour,twobyfourbythree)))
各操作の製品を以下に示します。内積がどのようであるかに注目してください。
... aの最後の軸とbの最後から2番目の軸上の積
マトリックスを一緒にブロードキャストすることにより、マトリックス製品がどのように形成されるか。
4x4*4x2x3 dot:
[[[232 152]
[125 112]
[125 112]]
[[172 116]
[123 76]
[123 76]]
[[442 296]
[228 226]
[228 226]]
[[962 652]
[465 512]
[465 512]]]
4x4*4x2x3 matmul:
[[[232 152]
[172 116]
[442 296]
[962 652]]
[[125 112]
[123 76]
[228 226]
[465 512]]
[[125 112]
[123 76]
[228 226]
[465 512]]]
数学では、numpyのdotがより理にかなっていると思います
dot(a、b)_ {i、j、k、a、b、c} =\sum_m a_ {i、j、k、m } b_ {a、b、m、c}
aとbがベクトルの場合は内積を、aとbが行列の場合は行列乗算を与えるため
Numpyでのmatmul演算は、dot結果の一部で構成されます、それは次のように定義できます
matmul(a、b)_ {i、j、k、c} =\sum_m a_ {i、j、k、m} b_ {i 、j、m、c}
そのため、matmul(a、b)は小さな形状の配列を返し、メモリ消費量が少なく、アプリケーションでより理にかなっていることがわかります。特に、 broadcasting と組み合わせると、次のようになります
matmul(a、b)_ {i、j、k、l} =\sum_m a_ {i、j、k、m} b_ {j 、m、l}
例えば。
上記の2つの定義から、これら2つの操作を使用するための要件を確認できます。 a.shape =(s1、s2、s3、s4)およびb.shape =(t1、t2 、t3、t4)
dot(a、b)を使用するには、必要です
1. **t3=s4**;
matmul(a、b)を使用するには、必要です
次のコードを使用して、自分を納得させてください。
import numpy as np
for it in xrange(10000):
a = np.random.Rand(5,6,2,4)
b = np.random.Rand(6,4,3)
c = np.matmul(a,b)
d = np.dot(a,b)
#print 'c shape: ', c.shape,'d shape:', d.shape
for i in range(5):
for j in range(6):
for k in range(2):
for l in range(3):
if not c[i,j,k,l] == d[i,j,k,j,l]:
print it,i,j,k,l,c[i,j,k,l]==d[i,j,k,j,l] #you will not see them