作者:Minkyung Kang
譯者:知源覓流
原文鏈接:
https://github.com/mkang32/python-basics/blob/master/numpy/dot_vs_multiply_vs_matmul_vs_at.ipynb
![](https://news.xinpengboligang.com/upload/keji/a114c053ca2aab702f3cba05c084e856.jpeg)
3. NumPy數組有哪些可用的功能?
我們的目標是在 NumPy 中找到執行點積或矩陣乘法的最佳方法。我比較了三個不同類別中的五種不同選項:
- 元素乘法(element-wise multiplication):* 或 np.multiply 加上 np.sum
- 點積:np.dot
- 矩陣乘法:np.matmul, @
我們將根據向量/矩陣的維度來探討不同的情況,並理解每種方法的優缺點(the pros and cons of each method)。要在接下來的部分中運行代碼,我們首先需要導入 numpy。
import numpy as np
(1) 元素乘法:*和sum
首先,我們可以嘗試將元素乘法作為基本方法來實現點積:將兩個向量中的對應元素相乘,然後將所有輸出值相加。這種方法的缺點是你需要分別進行乘法和加法運算,導致它比我們稍後將討論的其他方法慢。
這是一個使用兩個1-D數組計算點積的示例。
a = np.array([1, 2, 3])
b = np.array([4, 5, 6])
>>> a*b
array([ 4, 10, 18])
>>> sum(a*b)
32
>>> np.sum(a*b) #譯者添加
32
讓我們看看2-D數組矩陣乘法的示例。
c = np.array([[1, 2, 3], [4, 5, 6]])
d = np.array([1, 1, 1])
>>> c*d
array([[1, 2, 3],
[4, 5, 6]])
在這裡,二維數組 c 的每一行都被視為矩陣的一個元素,並與第二個數組 d 進行逐元素相乘。如下所示。
如果我們想要的是矩陣乘法的話,結果應該是這樣:
因此,為了得到想要的輸出,你需要對初始輸出應用 np.sum。請註意,你應該傳遞參數 axis=1,它會對同一行中的元素求和。否則,因為默認值是axis=None,它對數組中的所有元素求和(譯者訂)。(譯者註:axis=0表示跨行(Y軸)的方向,axis=1表示跨列(X軸)的方向)
>>> np.sum(c*d, axis=1)
array([ 6, 15])
譯者註:
你可能會問,為什麼不用sum了呢?這是因為如果你繼續用剛才用過的sum函數,就得不到想要的結果了。
>>> sum(c*d)
array([5, 7, 9])
此時,你可能被sum和np.sum繞暈了。從下面的簡介可以看出,sum是Python內置的函數,用於求和,功能有限。np.sum是numpy提供的求和函數,功能相對強大。所以,一般建議用np.sum。
對sum的簡介。
sum(iterable, /, start=0)
Return the sum of a 'start' value (default: 0) plus an iterable of numbers
對np.sum的簡介。
sum(a, axis=None, dtype=None, out=None, keepdims=<no value>, initial=<no value>, where=<no value>)
Sum of array elements over a given axis.
(2) 元素乘法:np.multiply和sum
np.multiply 和 * 基本上是一樣的。它是NumPy的元素乘法版本,而不是Python的本地運算符。你需要 sum 函數求和才能得到最終的標量輸出。
a = np.array([1, 2, 3])
b = np.array([4, 5, 6])
>>> np.multiply(a, b)
array([ 4, 10, 18])
>>> np.sum(np.multiply(a, b))
32
(3) 點積:np.dot
在Numpy中有一種更優雅和簡單的方法來計算點積,它就是np.dot(a, b) 或 a.dot(b)。它可以同時處理元素乘法和求和。簡單易用。
a = np.array([1, 2, 3])
b = np.array([4, 5, 6])
>>> np.dot(a, b)
32
然而,當它是一個更高維度的數組時,你需要小心。如果數組的維度為2-D或更高,請確保第一個數組的列數與第二個數組的行數相匹配。
a = np.array([[1, 2, 3]]) # shape (1, 3)
b = np.array([[4, 5, 6]]) # shape (1, 3)
>>> np.dot(a, b)
# ValueError: shapes (1,3) and (1,3) not aligned: 3 (dim 1) != 1 (dim 0)
為了讓上述示例運行,你需要轉置第二個數組,以便形狀對齊:(1, 3) x (3, 1)。請註意,這將返回形為(1, 1)的數組,這是一個2-D數組。
a = np.array([[1, 2, 3]]) # shape (1, 3)
b = np.array([[4, 5, 6]]) # shape (1, 3)
>>> np.dot(a, b.T)
array([[32]])
如果第二個數組是形狀為(3,)的1-D數組,那麼輸出的數組也會是1-D數組。
a = np.array([[1, 2, 3]]) # shape (1, 3)
b = np.array([4, 5, 6]) # shape (3, )
>>> np.dot(a, b)
array([32])
還要註意輸入數組的順序。如果順序相反,你會得到外積(outer product)而不是內積(inner product)(點積)。(譯者註:一個行向量乘以一個列向量稱作向量的內積,又叫作點積,結果是一個標量;一個列向量乘以一個行向量稱作向量的外積,結果是一個矩陣)
a = np.array([[1, 2, 3]]) # shape (1, 3)
b = np.array([[4, 5, 6]]) # shape (1, 3)
>>> np.dot(a.T, b) # (3, 1) x (1, 3)
array([[ 4, 5, 6],
[ 8, 10, 12],
[12, 15, 18]])
那麼np.dot方法也適用於2-D數組×2-D數組嗎?現在讓我們嘗試一個2D x 2D的例子。
c = np.array([[1, 2, 3], [4, 5, 6]]) # shape (2, 3)
d = np.array([[1], [1], [1]]) # shape (3, 1)
>>> np.dot(c, d)
array([[ 6],
[15]])
它起作用了!即使它被稱為點積,根據其定義,這表示輸入是1-D向量,輸出是標量,但它對2-D或更高維度的矩陣也起作用,就像它是矩陣乘法一樣。上面例子的計算過程如下所示。
*或np.multiply是不支持這樣計算的,所以np.dot絕對是一個改進。那麼,我們應該把np.dot用於所有的點積和矩陣乘法嗎?
從技術上講,可以,但並不推薦使用np.dot進行矩陣乘法,因為“點積”這個名稱有特定的含義,可能會讓讀者感到困惑,尤其是數學傢!
此外,對於高維矩陣(3-D或更高),不推薦使用 np.dot,因為它的行為與普通矩陣乘法不同。我們將在本文的後面部分討論這個問題。
因此,np.dot 既適用於點積也適用於矩陣乘法,但僅建議用於點積。
(4) 矩陣乘法:np.matmul
下一個選項是 np.matmul。它專為矩陣乘法而設計,名字也是由此得來(MATrix MULtiplication)。盡管名稱說的是矩陣乘法,但它也適用於 1-D 數組,就像 np.dot 一樣。下面讓我們嘗試一下之前測試 np.dot 的例子。可以看出,對於1-D和2-D數組,np.matmul 與 np.dot 的功能是一樣的。
# 1D array
a = np.array([1, 2, 3]) # shape (1, 3)
b = np.array([4, 5, 6]) # shape (1, 3)
>>> np.matmul(a, b)
32
# 2D array with values in 1 axis
a = np.array([[1, 2, 3]]) # shape (1, 3)
b = np.array([[4, 5, 6]]) # shape (1, 3)
>>> np.dot(a, b.T)
array([[32]])
# 2D arrays
c = np.array([[1, 2, 3], [4, 5, 6]]) # shape (2, 3)
d = np.array([[1], [1], [1]]) # shape (3, 1)
>>> np.dot(c, d)
array([[ 6],
[15]])
太好了!因此,這意味著np.dot和np.matmul都可以完美地用於點積和矩陣乘法。然而,正如我們之前所說,建議使用np.dot進行點積運算,使用np.matmul進行2-D或更高維度的矩陣乘法。
(5 ) 矩陣乘法:@
最後一個選項來了!@是自Python 3.5以來引入的新運算符,其名稱來自mATrices。它基本上與 np.matmul 相同,並旨在執行矩陣乘法。但是,如果我們已經有了完美的 np.matmul,為什麼還需要新的中綴運算符呢?
向stdlib添加新運算符的主要動機是矩陣乘法是一個非常常見的運算,它應該擁有自己的中綴運算符。例如,運算符 // 遠不如矩陣乘法常見,但仍擁有自己的中綴。要了解此添加的背景,請查看PEP 465 (
https://www.python.org/dev/peps/pep-0465/)。
# 1D array
a = np.array([1, 2, 3]) # shape (1, 3)
b = np.array([4, 5, 6]) # shape (1, 3)
>>> a @ b
32
# 2D array with values in 1 axis
a = np.array([[1, 2, 3]]) # shape (1, 3)
b = np.array([[4, 5, 6]]) # shape (1, 3)
>>> a @ b.T
array([[32]])
# 2D arrays
c = np.array([[1, 2, 3], [4, 5, 6]]) # shape: (2, 3)
d = np.array([[1], [1], [1]]) # shape: (3, 1)
>>> c @ d
array([[ 6],
[15]])
因此,@ 的工作原理和 np.matmul 完全一樣。但是在 np.matmul 和@ 之間應該使用哪一個呢?盡管這是你的偏好,但在代碼中 @ 看起來比np.matmul 更幹凈。例如,如果你想對三個不同的矩陣 x,y,z 執行矩陣乘法。那麼下面是不同的方式:
# `np.matmul` version
np.matmul(np.matmul(x, y), z)
# `@` version
x @ y @ z
如你所見,@ 操作符更為簡潔、易讀。然而,由於該操作符僅在Python 3.5及以上版本可用,如果你使用的是更早的Python版本,你必須使用np.matmul。
薈萃知識,滋養你我。