Numpy 是 Python 的一個函式庫,支援了非常多種的向量和矩陣等等的運算,本篇將對部分功能進行重點式的介紹。要安裝 numpy,你必須先在 cmd 當中執行「pip install numpy」或「python -m pip install numpy」(根據系統設定的不同,可能在指令細節上會有些許差異);安裝完成後,在 Python 程式中 import,就可以使用。
首先來看一些基本的數學運算。由於常見的部分在 math 當中也有提供很多,故以下僅任意列出其中一些,作為語法的介紹:
import numpy as np print(np.abs(-123.45)) print(np.log2(1024)) print(np.log10(2))Numpy 當中,最基本與常見的資料結構,就是多維陣列(ndarray);而矩陣和向量,當然就是二維或一維陣列。以下先對向量的建立和使用作示範:
import numpy as np a = np.array([]) # 空陣列 b = np.array([3, 4, 2, 1, 5]) c = np.array([6, 8, 7, 5, 9]) print(b + c) print(b - c) print(b * c) # 元素對元素計算 print(b / c) # 元素對元素計算 print(a.shape, b.shape, c.shape)在上述範例中:
- 你當然可以使用原生的 list 來進行操作,只是這樣就必須自己撰寫迴圈,對個別元素都親自去處理。
- 在程式上提到軸的個數或是元素的個數時,都是使用「維度」這個詞,如果你需要確認當下是在指涉何者的話,需要自己閱讀上下文來釐清。
- 在程式的最後,印出了資料的形狀。在使用 numpy 的過程中,清楚你的資料形狀是很重要的事情。
要創建有規則的向量,可以用 arange,它相當於 range 的 numpy 版本;如果在你的應用當中,知道點的個數比起知道幾點取一點來的方便的話,也可以使用 linspace:
import numpy as np print(np.arange(10)) print(np.arange(1, 10)) print(np.arange(1, 10, 2)) print(np.linspace(0, 1, 11))矩陣,也就是二維陣列,因此也就是很多個一維陣列排在一起。以下範例,會由一個外層長度 2,內層長度每個都是 3 的 list,轉換成一個 2-by-3 的矩陣:
import numpy as np a = [[1, 2, 3], [4, 5, 6]] print(a[0][2]) print(a[0, 2]) # 這行會產生錯誤 b = np.array(a) print(b.shape) print(b[0][2]) print(b[0, 2]) # 換成 numpy array 就可以這樣用Numpy 可以幫你創建各種具有某種規則的一維或二維陣列,以下介紹零矩陣和單位矩陣;如果你把 zeros 換成 ones,則可以產生全一矩陣:
import numpy as np print(np.zeros(5)) # Shape: (5,) print(np.zeros((2, 3))) print(np.zeros((5, 1))) print(np.eye(5)) # Shape: (5, 5) print(np.eye(5, M=3))Question: 如何產生全部都是某個任意實數的矩陣?
矩陣的合併則有各種「stack」系列的方法,或者「concatenate」可以使用。以下介紹其中兩種:
import numpy as np a = np.array([[1, 2, 3], [4, 5, 6]]) b = np.array([[7, 8, 9], [0, 0, 0]]) print(a) print(b) print('=====') print(np.vstack([a, b])) print(np.hstack([a, b]))在上述範例中,把兩個 2-by-3 的矩陣垂直(上下)的疊起來時,成品會是一個 (2+2)-by-3 的矩陣,而若是改成水平(左右)的來疊,則成品會是 2-by-(3+3) 的矩陣。
需要注意的是,如果其中一方是空矩陣的話,可能會產生問題,所以必須對其 size 屬性進行判斷,例如下列程式片段中,已知 a 有可能為空時,可能會出問題的寫法,以及適當的對應方法如下:
import numpy as np a = np.array([]) # Shape: (0,) b1 = np.array([1, 2, 3]) # Shape: (3,) b2 = np.array([[1], [2], [3]]) # Shape: (3, 1) c = np.hstack([a, b1]) # 空的一維向量水平接非空的一維向量,可以執行 c = np.vstack([b1, b1]) # 垂直疊兩個 b1,也可以執行(Question: shape 為何?) c = np.vstack([a, b1]) # 這行會產生錯誤 c = np.hstack([a, b2]) # 這行會產生錯誤 c = np.vstack([a, b2]) # 這行會產生錯誤 c = np.vstack([a, b1]) if a.size else b1 c = np.hstack([a, b2]) if a.size else b2 c = np.vstack([a, b2]) if a.size else b2利用 Numpy 進行取極值、排序、平均的方法如下:
import numpy as np a = np.array([[1, 5, 3], [4, 2, 6]]) print(a) print(np.max(a)) # 在所有元素裡面找 print(np.max(a, axis=0)) # 沿著 row index 的方向找 print(np.max(a, axis=1)) # 沿著 column index 的方向找 b = np.array([[4, 2, 5, 3, 1], [8, 6, 7, 9, 0]]) print(b) print(np.sort(b)) # 預設沿著最後一個軸(axis=-1)的方向排序 print(np.sort(b, axis=0)) # 沿著 row index 的方向排序 c = np.array([[4, 2, 5, 3, 1], [8, 6, 7, 9, 0]]) print(c) print(np.mean(c)) # 所有元素平均 print(np.mean(c, axis=0)) # 沿著 row index 的方向平均 print(np.mean(c, axis=1)) # 沿著 column index 的方向平均你當然還可以進行更多比較複雜的統計運算,例如第幾百分位數等等,若有興趣請自行上網搜尋。而關於取極值或排序時,如果是需要找出位置或順序再做進一步利用時,可以使用 argmax 或 argsort(此處僅以向量示範):
import numpy as np a = np.array([4, 2, 3, 1, 5]) print(np.argmax(a)) print(np.argsort(a))邏輯運算與尋找特定值的方法如下,此處亦僅以向量示範:
import numpy as np a = np.array([4, 3, 1, 6, 9, 2, 5, 0, 8, 7]) print(a >= 5) print(np.where(a >= 5)[0]) print(np.logical_or(a >= 7, a <= 3)) print(np.where(np.logical_or(a >= 7, a <= 3))[0])矩陣的轉置,可以直接使用「.T」:
import numpy as np a = np.array([[1, 5, 3], [4, 2, 6]]) print(a) print(a.shape) print(a.T) print(a.T.shape) print('------------') b = np.array([[4], [2], [3]]) print(b) print(b.shape) print(b.T) print(b.T.shape) print('------------') c = np.array([[4, 2, 3]]) print(c) print(c.shape) print(c.T) print(c.T.shape)但一維的向量若也需要轉置,則需要透過各種方法先變成二維。以下介紹將一維轉成二維的相關方法:
import numpy as np a = np.array([4, 2, 3, 5, 1]) print(a) print(a.shape) print('------------') b = a[np.newaxis, :] print(b) print(b.shape) print('------------') b = a.reshape(1, a.shape[0]) print(b) print(b.shape) print('------------') b = a[:, np.newaxis] print(b) print(b.shape) print('------------') b = a.reshape(a.shape[0], 1) print(b) print(b.shape)如前所述,對矩陣使用一般的乘號,是在做元素對元素的相乘,呼叫 np.multiply 亦同。若是需要做矩陣相乘,則必須使用 np.matmul,或者「@」運算子:
import numpy as np a = np.array([[1, 3], [4, 2]]) print(a * a) print(np.multiply(a, a)) print('------------') a = np.array([[1, 5, 3], [4, 2, 6]]) b = np.array([ [3, 5, 3, 6, 7], [8, 6, 4, 7, 1], [9, 4, 2, 7, 1] ]) print(a) print(b) print(np.matmul(a, b)) print(a @ b)前述的矩陣乘法,如果你要用迴圈自己寫的話,根據公式 Ci, j = Σk A i, k * B k, j來說,是需要三層迴圈的;而作為對 numpy array 存取方式以及一些函式的了解,以下範例也將同時示範兩層及一層迴圈的寫法:
import numpy as np a = np.array([[1, 2, 3], [4, 6, 6]]) b = np.array([[7, 8], [9, 0], [1, 2]]) print(a @ b) print('------------') c = np.zeros((a.shape[0], b.shape[1])) for i in range(a.shape[0]): for j in range(b.shape[1]): for k in range(a.shape[1]): # Or b.shape[0] c[i, j] += a[i, k] * b[k, j] print(c) print('------------') c = np.zeros((a.shape[0], b.shape[1])) for i in range(a.shape[0]): for j in range(b.shape[1]): c[i, j] = np.dot(a[i, :], b[:, j]) print(c) print('------------') c = np.zeros((a.shape[0], b.shape[1])) for i in range(a.shape[0]): c[i] = a[i] @ b print(c)在上述範例中的三層迴圈版本,當然也可以用單純的串列來進行,但此處為了範例撰寫的方便,故皆使用 numpy array。
如果你需要做一些進階的線性代數運算,則需要使用 np.linalg。例如,計算行列式值與反矩陣的方法如下:
import numpy as np a = np.array([[4, 3, 5], [2, 6, 1], [0, 5, 8]]) print(np.linalg.det(a)) print('------------') b = np.linalg.inv(a) print(b) print(a @ b) # Should be very closed to identity若需要進行線性方程求解,則可以使用 np.linalg.lstsq:
import numpy as np A = np.array([[1, 4, 2], [3, 1, 8], [9, 4, 1]]) b = np.array([10, 11, 100]) # Ax = b x, _, _, _ = np.linalg.lstsq(A, b, rcond=None) print(x) print(A @ x) print('------------------') # x'A = b x, _, _, _ = np.linalg.lstsq(A.T, b.T, rcond=None) print(x) print(x @ A)其餘如 LU、QR 等分解,都有相對應的函式,若有興趣可以自行上網搜尋。