適用於 MATLAB 使用者的 NumPy#

簡介#

MATLAB® 和 NumPy 有許多共通之處,但 NumPy 的創建是為了與 Python 協同運作,而非成為 MATLAB 的複製品。本指南將協助 MATLAB 使用者開始使用 NumPy。

一些主要差異#

在 MATLAB 中,即使是純量,基本類型也是多維陣列。除非您指定維度和類型,否則 MATLAB 中的陣列賦值會儲存為雙精度浮點數的 2D 陣列。在這些陣列的 2D 實例上的運算是以線性代數中的矩陣運算為模型。

在 NumPy 中,基本類型是多維 array。除非您指定維度和類型,否則 NumPy 中的陣列賦值通常會儲存為 n 維陣列,並使用儲存序列中物件所需的最小類型。NumPy 執行逐元素運算,因此使用 * 乘法 2D 陣列不是矩陣乘法,而是逐元素乘法。(自 Python 3.5 起可用的 @ 運算子可用於傳統矩陣乘法。)

MATLAB 從 1 開始編號索引;a(1) 是第一個元素。請參閱註解 INDEXING

NumPy 與 Python 一樣,從 0 開始編號索引;a[0] 是第一個元素。

MATLAB 的腳本語言是為線性代數而創建的,因此某些陣列操作的語法比 NumPy 更簡潔。另一方面,用於新增 GUI 和建立完整應用程式的 API 或多或少是事後才考慮的。

NumPy 基於通用語言 Python。NumPy 的優勢在於可以存取 Python 函式庫,包括:SciPyMatplotlibPandasOpenCV 等等。此外,Python 通常嵌入為腳本語言在其他軟體中,也允許在那裡使用 NumPy。

MATLAB 陣列切片使用傳值語義,並採用延遲寫入時複製方案,以防止在需要之前建立副本。切片操作會複製陣列的部分內容。

NumPy 陣列切片使用傳參考,這不會複製引數。切片操作是陣列的視圖。

大致對等項目#

下表列出了一些常見 MATLAB 表達式的大致對等項目。這些是相似的表達式,而非完全對等。如需詳細資訊,請參閱文件

在下表中,假設您已在 Python 中執行以下命令

import numpy as np
from scipy import io, integrate, linalg, signal
from scipy.sparse.linalg import cg, eigs

也假設在下方,如果註解談論「矩陣」,則引數是二維實體。

通用對等項目#

MATLAB

NumPy

註解

help func

info(func)help(func)func? (在 IPython 中)

取得函數 func 的說明

which func

請參閱註解 HELP

找出 func 的定義位置

type func

np.source(func)func?? (在 IPython 中)

印出 func 的原始碼(如果不是原生函數)

% comment

# comment

使用文字 comment 註解一行程式碼

for i=1:3
    fprintf('%i\n',i)
end
for i in range(1, 4):
   print(i)

使用 for 迴圈印出數字 1、2 和 3,使用 range

a && b

a and b

短路邏輯 AND 運算子 (Python 原生運算子);僅限純量引數

a || b

a or b

短路邏輯 OR 運算子 (Python 原生運算子);僅限純量引數

>> 4 == 4
ans = 1
>> 4 == 5
ans = 0
>>> 4 == 4
True
>>> 4 == 5
False

Python 中的布林物件TrueFalse,這與 MATLAB 的邏輯類型 10 相反。

a=4
if a==4
    fprintf('a = 4\n')
elseif a==5
    fprintf('a = 5\n')
end
a = 4
if a == 4:
    print('a = 4')
elif a == 5:
    print('a = 5')

建立 if-else 陳述式以檢查 a 是否為 4 或 5 並印出結果

1*i, 1*j, 1i, 1j

1j

複數

eps

np.finfo(float).epsnp.spacing(1)

從 1 到雙精度中下一個較大可表示實數的距離

load data.mat

io.loadmat('data.mat')

載入儲存到檔案 data.mat 的 MATLAB 變數。(注意:在 MATLAB/Octave 中將陣列儲存到 data.mat 時,請使用最新的二進位格式。scipy.io.loadmat 將會建立一個包含已儲存陣列和更多資訊的字典。)

ode45

integrate.solve_ivp(f)

使用 Runge-Kutta 4,5 積分 ODE

ode15s

integrate.solve_ivp(f, method='BDF')

使用 BDF 方法積分 ODE

線性代數對等項目#

MATLAB

NumPy

註解

ndims(a)

np.ndim(a)a.ndim

陣列 a 的維度數量

numel(a)

np.size(a)a.size

陣列 a 的元素數量

size(a)

np.shape(a)a.shape

陣列 a 的「大小」

size(a,n)

a.shape[n-1]

取得陣列 a 第 n 維的元素數量。(請注意,MATLAB 使用從 1 開始的索引,而 Python 使用從 0 開始的索引,請參閱註解 INDEXING

[ 1 2 3; 4 5 6 ]

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

定義一個 2x3 2D 陣列

[ a b; c d ]

np.block([[a, b], [c, d]])

從區塊 abcd 建構矩陣

a(end)

a[-1]

存取 MATLAB 向量 (1xn 或 nx1) 或 1D NumPy 陣列 a(長度 n)中的最後一個元素

a(2,5)

a[1, 4]

存取 2D 陣列 a 中第二列、第五行的元素

a(2,:)

a[1]a[1, :]

2D 陣列 a 的整個第二列

a(1:5,:)

a[0:5]a[:5]a[0:5, :]

2D 陣列 a 的前 5 列

a(end-4:end,:)

a[-5:]

2D 陣列 a 的最後 5 列

a(1:3,5:9)

a[0:3, 4:9]

2D 陣列 a 的第一到第三列和第五到第九行。

a([2,4,5],[1,3])

a[np.ix_([1, 3, 4], [0, 2])]

第 2、4 和 5 列以及第 1 和 3 行。這允許修改矩陣,且不需要規則切片。

a(3:2:21,:)

a[2:21:2,:]

陣列 a 的每隔一列,從第三列開始到第二十一列

a(1:2:end,:)

a[::2, :]

陣列 a 的每隔一列,從第一列開始

a(end:-1:1,:)flipud(a)

a[::-1,:]

列反轉順序的 a

a([1:end 1],:)

a[np.r_[:len(a),0]]

將第一列副本附加到末尾的 a

a.'

a.transpose()a.T

a 的轉置

a'

a.conj().transpose()a.conj().T

a 的共軛轉置

a * b

a @ b

矩陣乘法

a .* b

a * b

逐元素乘法

a./b

a/b

逐元素除法

a.^3

a**3

逐元素指數運算

(a > 0.5)

(a > 0.5)

其 i,jth 元素為 (a_ij > 0.5) 的矩陣。MATLAB 結果是一個邏輯值 0 和 1 的陣列。NumPy 結果是一個布林值 FalseTrue 的陣列。

find(a > 0.5)

np.nonzero(a > 0.5)

找出 (a > 0.5) 的索引

a(:,find(v > 0.5))

a[:,np.nonzero(v > 0.5)[0]]

擷取向量 v > 0.5 的 a

a(:,find(v>0.5))

a[:, v.T > 0.5]

擷取行向量 v > 0.5 的 a

a(a<0.5)=0

a[a < 0.5]=0

將元素小於 0.5 的 a 歸零

a .* (a>0.5)

a * (a > 0.5)

將元素小於 0.5 的 a 歸零

a(:) = 3

a[:] = 3

將所有值設定為相同的純量值

y=x

y = x.copy()

NumPy 以傳參考方式賦值

y=x(2,:)

y = x[1, :].copy()

NumPy 切片以傳參考方式進行

y=x(:)

y = x.flatten()

將陣列轉換為向量(請注意,這會強制複製)。若要取得與 MATLAB 相同的資料排序,請使用 x.flatten('F')

1:10

np.arange(1., 11.)np.r_[1.:11.]np.r_[1:10:10j]

建立遞增向量(請參閱註解 RANGES

0:9

np.arange(10.)np.r_[:10.]np.r_[:9:10j]

建立遞增向量(請參閱註解 RANGES

[1:10]'

np.arange(1.,11.)[:, np.newaxis]

建立行向量

zeros(3,4)

np.zeros((3, 4))

充滿 64 位元浮點零的 3x4 二維陣列

zeros(3,4,5)

np.zeros((3, 4, 5))

充滿 64 位元浮點零的 3x4x5 三維陣列

ones(3,4)

np.ones((3, 4))

充滿 64 位元浮點一的 3x4 二維陣列

eye(3)

np.eye(3)

3x3 單位矩陣

diag(a)

np.diag(a)

傳回 2D 陣列 a 的對角線元素向量

diag(v,0)

np.diag(v, 0)

傳回一個方形對角矩陣,其非零值是向量 v 的元素

rng(42,'twister')
rand(3,4)
from numpy.random import default_rng
rng = default_rng(42)
rng.random((3, 4))

或舊版本:random.rand((3, 4))

使用預設亂數產生器和種子 = 42 產生 3x4 隨機陣列

linspace(1,3,4)

np.linspace(1,3,4)

1 到 3 之間(含)的 4 個等距樣本

[x,y]=meshgrid(0:8,0:5)

np.mgrid[0:9.,0:6.]np.meshgrid(r_[0:9.],r_[0:6.])

兩個 2D 陣列:一個是 x 值,另一個是 y 值

ogrid[0:9.,0:6.]np.ix_(np.r_[0:9.],np.r_[0:6.]

在網格上評估函數的最佳方法

[x,y]=meshgrid([1,2,4],[2,4,5])

np.meshgrid([1,2,4],[2,4,5])

np.ix_([1,2,4],[2,4,5])

在網格上評估函數的最佳方法

repmat(a, m, n)

np.tile(a, (m, n))

建立 a 的 m by n 副本

[a b]

np.concatenate((a,b),1)np.hstack((a,b))np.column_stack((a,b))np.c_[a,b]

串聯 ab 的行

[a; b]

np.concatenate((a,b))np.vstack((a,b))np.r_[a,b]

串聯 ab 的列

max(max(a))

a.max()np.nanmax(a)

a 的最大元素(對於 MATLAB,ndims(a)<=2,如果存在 NaN,nanmax 將忽略這些並傳回最大值)

max(a)

a.max(0)

陣列 a 每列的最大元素

max(a,[],2)

a.max(1)

陣列 a 每行的最大元素

max(a,b)

np.maximum(a, b)

逐元素比較 ab,並從每對傳回最大值

norm(v)

np.sqrt(v @ v)np.linalg.norm(v)

向量 v 的 L2 範數

a & b

logical_and(a,b)

逐元素 AND 運算子 (NumPy ufunc) 請參閱註解 LOGICOPS

a | b

np.logical_or(a,b)

逐元素 OR 運算子 (NumPy ufunc) 請參閱註解 LOGICOPS

bitand(a,b)

a & b

位元 AND 運算子 (Python 原生和 NumPy ufunc)

bitor(a,b)

a | b

位元 OR 運算子 (Python 原生和 NumPy ufunc)

inv(a)

linalg.inv(a)

方形 2D 陣列 a 的反矩陣

pinv(a)

linalg.pinv(a)

2D 陣列 a 的偽反矩陣

rank(a)

np.linalg.matrix_rank(a)

2D 陣列 a 的矩陣秩

a\b

linalg.solve(a, b) 如果 a 是方形,則為 linalg.solve(a, b);否則為 linalg.lstsq(a, b)

求解 a x = b 的 x

b/a

改為求解 a.T x.T = b.T

求解 x a = b 的 x

[U,S,V]=svd(a)

U, S, Vh = linalg.svd(a); V = Vh.T

a 的奇異值分解

chol(a)

linalg.cholesky(a)

2D 陣列的 Cholesky 分解

[V,D]=eig(a)

D,V = linalg.eig(a)

a 的特徵值 \(\lambda\) 和特徵向量 \(v\),其中 \(\mathbf{a} v = \lambda v\)

[V,D]=eig(a,b)

D,V = linalg.eig(a, b)

ab 的特徵值 \(\lambda\) 和特徵向量 \(v\),其中 \(\mathbf{a} v = \lambda \mathbf{b} v\)

[V,D]=eigs(a,3)

D,V = eigs(a, k=3)

找出 2D 陣列 ak=3 個最大特徵值和特徵向量

[Q,R]=qr(a,0)

Q,R = linalg.qr(a)

QR 分解

[L,U,P]=lu(a) 其中 a==P'*L*U

P,L,U = linalg.lu(a) 其中 a == P@L@U

帶有部分樞軸的 LU 分解(注意:P(MATLAB) == transpose(P(NumPy)))

conjgrad

cg

共軛梯度求解器

fft(a)

np.fft.fft(a)

a 的傅立葉轉換

ifft(a)

np.fft.ifft(a)

a 的反傅立葉轉換

sort(a)

np.sort(a)a.sort(axis=0)

排序 2D 陣列 a 的每列

sort(a, 2)

np.sort(a, axis=1)a.sort(axis=1)

排序 2D 陣列 a 的每行

[b,I]=sortrows(a,1)

I = np.argsort(a[:, 0]); b = a[I,:]

將陣列 a 儲存為陣列 b,並依第一列排序

x = Z\y

x = linalg.lstsq(Z, y)

執行 \(\mathbf{Zx}=\mathbf{y}\) 形式的線性迴歸

decimate(x, q)

signal.resample(x, np.ceil(len(x)/q))

使用低通濾波進行降採樣

unique(a)

np.unique(a)

陣列 a 中的唯一值向量

squeeze(a)

a.squeeze()

移除陣列 a 的單例維度。請注意,MATLAB 將始終傳回 2D 或更高維度的陣列,而 NumPy 將傳回 0D 或更高維度的陣列

註解#

子矩陣:可以使用索引列表和 ix_ 命令來完成子矩陣的賦值。例如,對於 2D 陣列 a,可以執行以下操作:ind=[1, 3]; a[np.ix_(ind, ind)] += 100

HELP:沒有與 MATLAB 的 which 命令直接對應的命令,但 help 命令通常會列出函數所在檔案的檔名。Python 也有一個 inspect 模組(執行 import inspect),它提供了一個通常有效的 getfile

INDEXING:MATLAB 使用從 1 開始的索引,因此序列的初始元素索引為 1。Python 使用從 0 開始的索引,因此序列的初始元素索引為 0。由於每種索引都有優點和缺點,因此會產生混淆和爭論。從 1 開始的索引與常見的人類語言用法一致,其中序列的「第一個」元素索引為 1。從 0 開始的索引簡化了索引。另請參閱 prof.dr. Edsger W. Dijkstra 的文章

RANGES:在 MATLAB 中,0:5 可以同時用作範圍文字和「切片」索引(在括號內);但是,在 Python 中,0:5 之類的結構*只能*用作切片索引(在方括號內)。因此,創建了有點奇怪的 r_ 物件,以允許 NumPy 具有類似簡潔的範圍建構機制。請注意,r_ 不是像函數或建構子那樣呼叫,而是使用方括號進行*索引*,這允許在引數中使用 Python 的切片語法。

LOGICOPS:NumPy 中的 &| 是位元 AND/OR,而在 MATLAB 中,& 和 | 是邏輯 AND/OR。兩者看起來可能工作方式相同,但存在重要差異。如果您曾經使用過 MATLAB 的 &| 運算子,則應使用 NumPy ufunc logical_and/logical_or。MATLAB 和 NumPy 的 &| 運算子之間值得注意的差異是

  • 非邏輯 {0,1} 輸入:NumPy 的輸出是輸入的位元 AND。MATLAB 將任何非零值視為 1,並傳回邏輯 AND。例如,NumPy 中的 (3 & 4)0,而在 MATLAB 中,34 都被視為邏輯真,且 (3 & 4) 傳回 1

  • 優先順序:NumPy 的 & 運算子的優先順序高於 <> 等邏輯運算子;MATLAB 的則相反。

如果您知道您有布林引數,則可以使用 NumPy 的位元運算子,但請小心括號,例如:z = (x > 1) & (x < 2)。NumPy 運算子沒有 logical_andlogical_or 形式是 Python 設計的一個不幸後果。

RESHAPE 和線性索引:MATLAB 始終允許使用純量或線性索引存取多維陣列,而 NumPy 則不允許。線性索引在 MATLAB 程式中很常見,例如,矩陣上的 find() 會傳回它們,而 NumPy 的 find 行為則不同。轉換 MATLAB 程式碼時,可能需要先將矩陣重塑為線性序列,執行一些索引操作,然後再重塑回來。由於 reshape(通常)會產生對相同儲存體的視圖,因此應該可以相當有效率地執行此操作。請注意,NumPy 中 reshape 使用的掃描順序預設為 ‘C’ 順序,而 MATLAB 使用 Fortran 順序。如果您只是轉換為線性序列並返回,這並不重要。但是,如果您要從依賴掃描順序的 MATLAB 程式碼轉換 reshape,則此 MATLAB 程式碼:z = reshape(x,3,4); 應在 NumPy 中變為 z = x.reshape(3,4,order='F').copy()

「array」還是「matrix」?我應該使用哪個?#

從歷史上看,NumPy 提供了一種特殊的矩陣類型 np.matrix,它是 ndarray 的子類別,它使二元運算成為線性代數運算。您可能會在一些現有程式碼中看到它被使用,而不是 np.array。那麼,應該使用哪個呢?

簡短回答#

使用陣列.

  • 它們支援 MATLAB 中支援的多維陣列代數

  • 它們是 NumPy 的標準向量/矩陣/張量類型。許多 NumPy 函數傳回陣列,而不是矩陣。

  • 逐元素運算和線性代數運算之間有明顯的區別。

  • 如果您願意,可以使用標準向量或行/列向量。

在 Python 3.5 之前,使用陣列類型的唯一缺點是您必須使用 dot 而不是 * 來乘法(縮減)兩個張量(純量積、矩陣向量乘法等)。自 Python 3.5 以來,您可以使用矩陣乘法 @ 運算子。

鑑於以上所述,我們打算最終棄用 matrix

詳細回答#

NumPy 包含 array 類別和 matrix 類別。array 類別旨在作為適用於多種數值計算的通用 n 維陣列,而 matrix 則旨在專門促進線性代數計算。實際上,兩者之間只有少數幾個關鍵差異。

  • 運算子 *@、函數 dot()multiply()

    • 對於 array``*`` 代表逐元素乘法,而 ``@`` 代表矩陣乘法;它們具有相關的函數 multiply()dot()。(在 Python 3.5 之前,@ 尚不存在,必須使用 dot() 進行矩陣乘法)。

    • 對於 matrix``*`` 代表矩陣乘法,而對於逐元素乘法,則必須使用 multiply() 函數。

  • 向量(一維陣列)的處理

    • 對於 array向量形狀 1xN、Nx1 和 N 都是不同的事物。像是 A[:,1] 的操作會回傳形狀為 N 的一維陣列,而不是形狀為 Nx1 的二維陣列。對一維 array 進行轉置沒有作用。

    • 對於 matrix一維陣列總是向上轉換為 1xN 或 Nx1 矩陣(行向量或列向量)。A[:,1] 回傳形狀為 Nx1 的二維矩陣。

  • 高維陣列(ndim > 2)的處理

    • array 物件可以具有大於 2 的維度數量

    • matrix 物件總是恰好有兩個維度

  • 便利屬性

    • array 具有 .T 屬性,它會回傳資料的轉置。

    • matrix 也具有 .H、.I 和 .A 屬性,它們分別回傳矩陣的共軛轉置、逆矩陣和 asarray()

  • 便利建構子

    • array 建構子接受(巢狀)Python 序列作為初始化器。例如,array([[1,2,3],[4,5,6]])

    • matrix 建構子還接受便利的字串初始化器。例如 matrix("[1 2 3; 4 5 6]")

使用兩者各有優缺點

  • array

    • :) 逐元素乘法很簡單:A*B

    • :( 你必須記得矩陣乘法有自己的運算子 @

    • :) 你可以將一維陣列視為或列向量。A @ vv 視為行向量,而 v @ Av 視為列向量。這可以省去你輸入大量轉置的時間。

    • :) array 是 NumPy 的「預設」類型,因此它獲得最多的測試,並且是最有可能被使用 NumPy 的第三方程式碼回傳的類型。

    • :) 非常擅長處理任何維度數量的資料。

    • :) 如果你對張量代數很熟悉,那麼在語義上更接近張量代數。

    • :) 所有運算(*/+- 等)都是逐元素的。

    • :( 來自 scipy.sparse 的稀疏矩陣與陣列的互動性較差。

  • matrix

    • :\\ 行為更像 MATLAB 矩陣。

    • <:( 最大二維。為了容納三維資料,你需要 array 或許是 Python 的 matrix 列表。

    • <:( 最小二維。你不能有向量。它們必須轉換為單列或單行矩陣。

    • <:( 由於陣列是 NumPy 中的預設值,因此即使你給它們一個矩陣作為參數,某些函數也可能會回傳一個陣列。這不應該發生在 NumPy 函數中(如果發生了,那就是一個錯誤),但基於 NumPy 的第三方程式碼可能不會像 NumPy 那樣遵守類型保留。

    • :) A*B 是矩陣乘法,所以它看起來就像你在線性代數中寫的那樣(對於 Python >= 3.5,普通陣列使用 @ 運算子具有相同的便利性)。

    • <:( 逐元素乘法需要呼叫函數 multiply(A,B)

    • <:( 運算子多載的使用有點不合邏輯:* 不進行逐元素運算,但 / 卻進行。

    • scipy.sparse 的互動性稍微更乾淨。

array 因此更建議使用。實際上,我們打算最終棄用 matrix

自訂您的環境#

在 MATLAB 中,您可使用的主要工具是修改搜尋路徑,以包含您常用函數的位置。您可以將此類自訂設定放入 MATLAB 將在啟動時執行的啟動腳本中。

NumPy,或者更確切地說是 Python,也具有類似的功能。

  • 若要修改您的 Python 搜尋路徑以包含您自己模組的位置,請定義 PYTHONPATH 環境變數。

  • 若要在啟動互動式 Python 直譯器時執行特定的腳本檔案,請定義 PYTHONSTARTUP 環境變數以包含您的啟動腳本名稱。

不像 MATLAB,路徑上的任何東西都可以立即呼叫,使用 Python,您需要先執行 ‘import’ 語句才能使特定檔案中的函數可存取。

例如,您可以建立一個像這樣的啟動腳本(注意:這只是一個範例,並非「最佳實踐」的陳述)

# Make all numpy available via shorter 'np' prefix
import numpy as np
#
# Make the SciPy linear algebra functions available as linalg.func()
# e.g. linalg.lu, linalg.eig (for general l*B@u==A@u solution)
from scipy import linalg
#
# Define a Hermitian function
def hermitian(A, **kwargs):
    return np.conj(A,**kwargs).T
# Make a shortcut for hermitian:
#    hermitian(A) --> H(A)
H = hermitian

若要使用已棄用的 matrix 和其他 matlib 函數

# Make all matlib functions accessible at the top level via M.func()
import numpy.matlib as M
# Make some matlib functions accessible directly at the top level via, e.g. rand(3,3)
from numpy.matlib import matrix,rand,zeros,ones,empty,eye