广播机制

*、np.multiply()、np.matmul() 或 @、np.dot()的异同
In [1]: import numpy as np

1、 尺寸相同的两个1-D array

In [2]: a=np.array([1,2])

In [3]: b=np.array([3,4])

In [4]: a*b
Out[4]: array([3, 8])

In [5]: np.multiply(a,b)
Out[5]: array([3, 8])

In [6]: np.matmul(a,b)
Out[6]: 11

In [7]: np.dot(a,b)
Out[7]: 11

In [8]: a@b
Out[8]: 11

结论:

np.multiply()*:array对应元素相乘

np.matmul()@np.dot():向量点积

备注:在numpy中可以使用@来替代matmul,即:

np.matmul(a, b)
# 可以被替代为:
(a @ b)

下文只考虑np.matmul(a,b)

2、 两个2-D array

对于尺寸不同但符合矩阵乘法规则的两个2-D array:

In [17]: a = np.array([i for i in range(6)]).reshape([3,2])

In [18]: a
Out[18]: 
array([[0, 1],
       [2, 3],
       [4, 5]])

In [19]: b=np.array([[0],[1]])

In [20]: b
Out[20]: 
array([[0],
       [1]])

In [21]: a*b
---------------------------------------------------------------------------
ValueError: operands could not be broadcast together with shapes (3,2) (2,1)

In [22]: np.multiply(a,b)
---------------------------------------------------------------------------
ValueError: operands could not be broadcast together with shapes (3,2) (2,1)

In [23]: np.matmul(a,b)
Out[23]: 
array([[1],
       [3],
       [5]])

In [24]: np.dot(a,b)
Out[24]: 
array([[1],
       [3],
       [5]])

对于尺寸相同的两个2-D array:

In [29]: a=np.array([[1,2],[3,4]])

In [30]: b=np.array([[0,1],[2,3]])

In [31]: a
Out[31]: 
array([[1, 2],
       [3, 4]])

In [32]: b
Out[32]: 
array([[0, 1],
       [2, 3]])

In [33]: a*b
Out[33]: 
array([[ 0,  2],
       [ 6, 12]])

In [34]: np.multiply(a,b)
Out[34]: 
array([[ 0,  2],
       [ 6, 12]])

In [35]: np.matmul(a,b)
Out[35]: 
array([[ 4,  7],
       [ 8, 15]])

In [36]: np.dot(a,b)
Out[36]: 
array([[ 4,  7],
       [ 8, 15]])

结论:

np.multiply()*:array对应元素相乘。不满足广播的条件则出错。

np.matmul()@np.dot():矩阵乘法

3、 两个 matrix

对于尺寸不同但符合矩阵乘法规则的两个 matrix:

In [48]: a = np.matrix([i for i in range(6)]).reshape([3,2])
Out[48]: 
matrix([[0, 1],
        [2, 3],
        [4, 5]])

In [49]: b=np.matrix([[0],[1]])
Out[49]: 
matrix([[0],
        [1]])

In [50]: a*b
Out[50]: 
matrix([[1],
        [3],
        [5]])

In [51]: np.multiply(a,b)
---------------------------------------------------------------------------
ValueError: operands could not be broadcast together with shapes (3,2) (2,1)

In [52]: np.matmul(a,b)
Out[52]: 
matrix([[1],
        [3],
        [5]])

In [53]: np.dot(a,b)
Out[53]: 
matrix([[1],
        [3],
        [5]])

对于尺寸相同的两个matrix:

In [56]: a=np.matrix([[1,2],[3,4]])

In [57]: b=np.matrix([[0,1],[2,3]])

In [58]: a
Out[58]: 
matrix([[1, 2],
        [3, 4]])

In [59]: b
Out[59]: 
matrix([[0, 1],
        [2, 3]])

In [60]: a*b
Out[60]: 
matrix([[ 4,  7],
        [ 8, 15]])

In [61]: np.multiply(a,b)
Out[61]: 
matrix([[ 0,  2],
        [ 6, 12]])

In [62]: np.matmul(a,b)
Out[62]: 
matrix([[ 4,  7],
        [ 8, 15]])

In [63]: np.dot(a,b)
Out[63]: 
matrix([[ 4,  7],
        [ 8, 15]])

结论:

np.multiply():matrix对应元素相乘。不满足广播的条件则出错。

*np.matmul()@np.dot():矩阵乘法

4、 np.matmul() vs. np.dot()

上面三种情况np.matmul()和. np.dot()的结果都是相同的,我们来看看它们之间的差别。

考虑2维以上array:
np.multiply()*依然是array对应元素相乘,不满足广播的条件则出错。这里略过相应代码,我们主要来看看np.matmul()np.dot()

In [95]: a = np.array([i for i in range(12)]).reshape([2,2,3])

In [96]: b = np.array([i for i in range(6)]).reshape([1,3,2])

In [97]: a
Out[97]: 
array([[[ 0,  1,  2],
        [ 3,  4,  5]],

       [[ 6,  7,  8],
        [ 9, 10, 11]]])

In [98]: b
Out[98]: 
array([[[0, 1],
        [2, 3],
        [4, 5]]])

In [99]: np.dot(a,b)
Out[99]: 
array([[[[10, 13]],

        [[28, 40]]],


       [[[46, 67]],

        [[64, 94]]]])
 
In [100]: np.dot(a,b).shape
Out[100]: (2, 2, 1, 2)

In [101]: np.matmul(a,b)
Out[101]: 
array([[[10, 13],
        [28, 40]],

       [[46, 67],
        [64, 94]]])
        
In [102]: np.matmul(a,b).shape
Out[102]: (2, 2, 2)


In [103]: a = np.array([i for i in range(12)]).reshape([2,2,3])

In [104]: b = np.array([i for i in range(12)]).reshape([2,3,2])

In [105]: a
Out[105]: 
array([[[ 0,  1,  2],
        [ 3,  4,  5]],

       [[ 6,  7,  8],
        [ 9, 10, 11]]])

In [106]: b
Out[106]: 
array([[[ 0,  1],
        [ 2,  3],
        [ 4,  5]],

       [[ 6,  7],
        [ 8,  9],
        [10, 11]]])

In [107]: np.dot(a,b)
Out[107]: 
array([[[[ 10,  13],
         [ 28,  31]],

        [[ 28,  40],
         [100, 112]]],


       [[[ 46,  67],
         [172, 193]],

        [[ 64,  94],
         [244, 274]]]])
         
In [118]: np.dot(a,b).shape
Out[118]: (2, 2, 2, 2)

In [109]: np.matmul(a,b)
Out[109]: 
array([[[ 10,  13],
        [ 28,  40]],

       [[172, 193],
        [244, 274]]])

In [110]: np.matmul(a,b).shape
Out[110]: (2, 2, 2)



结论[1]:

np.matmul() 分别把数组的最后两个维度(组成矩阵)视为一个元素,并对其进行广播。
如上文中b.shape:[1,3,2]广播为[2,3,2]再与a相乘。

广播后两个数组matmul 即两个数组对应位置的矩阵相乘。在上面的例子中,两个数组分别由两个 2 × 3 2\times 3 2×3 3 × 2 3\times 2 3×2的矩阵组成。通过对应位置矩阵进行矩阵乘法,得到结果为两个 2 × 2 2\times 2 2×2的矩阵。

np.dot() 则对第一个数组的最后一个维度(行向量)以及第二个数组的倒数第二个维度(列向量)进行点积。在上面的例子中,a可以看作 2 × 2 2\times 2 2×2个行向量。b可以看作 2 × 2 2\times 2 2×2个列向量。np.dot会将a的每一个行向量与b的全部列向量进行点积,每一个行向量都能得到一个二维数组,占据结果的后两维。属于同一个二维数组的行向量,得到的结果又合并为一个三维数组。这个三维数组又与其他二维数组的行向量得到的三维数组合并成四维数组。

np.matmul()np.dot() 还有一个区别是np.matmul() 无法用于标量与array相乘

In [113]: a=np.array([1,2,3])

In [114]: np.dot(a,2)
Out[114]: array([2, 4, 6])

In [115]: np.matmul(a,2)
---------------------------------------------------------------------------
ValueError: Scalar operands are not allowed, use '*' instead

参考:

[1]https://www.delftstack.com/howto/numpy/numpy-dot-vs-matmul/