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)

創建規則向量,可以用 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))

矩陣,也就是二維陣列,因此也就是很多個一維陣列排在一起:

import numpy as np

a = np.array([[1, 2, 3], [4, 5, 6]])

print(a)
print(a.shape)

Numpy 可以幫你創建各種具有某種規則的一維或二維陣列,以下介紹零矩陣和單位矩陣;如果你把 zeros 換成 ones,則可以產生全一矩陣:

import numpy as np

print(np.zeros(5))
print(np.zeros((2, 3)))
print(np.zeros((5, 1)))

print(np.eye(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]))

需要注意的是,如果其中一方是空矩陣的話,可能會產生問題,所以必須對其 size 屬性進行判斷,例如下列程式片段中,已知 a 有可能為空時,可能會出問題的寫法,以及適當的對應方法如下:

import numpy as np

a = np.array([])
b1 = np.array([1, 2, 3])
b2 = np.array([[1], [2], [3]])

c = np.hstack([a, b1])  # 空的一維向量接非空的一維向量,可以執行
c = np.vstack([b1, b1]) # 只是垂直疊兩個 b1,也可以執行
c = np.vstack([a, b1])  # 這行會產生錯誤
c = np.hstack([a, b2])  # 這行會產生錯誤

c = np.vstack([a, b1]) if(a.size) else b1
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(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(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(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('------------')

c = a[:, np.newaxis]
print(c)
print(c.shape)

print('------------')

d = a.reshape(a.shape[0], 1)
print(d)
print(d.shape)

print('------------')

e = a.reshape(1, a.shape[0])
print(e)
print(e.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 等分解,都有相對應的函式,若有興趣可以自行上網搜尋。