面向 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 为基础,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

请参阅注释帮助

找出定义 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)

使用 range 使用 for 循环打印数字 1、2 和 3

a && b

a 并且 b

短路逻辑 AND 运算符 (Python 本机运算符);仅适用于标量参数

a || b

a 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*i1*j1i1j

1j

复数

eps

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

双精度中从 1 到下一个可表示实数的距离

加载 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 的索引,请参阅备注 索引

[ 1 2 3; 4 5 6 ]

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

定义一个 2x3 的二维数组

[ 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]

访问二维数组 a 中第二行、第五列中的元素

a(2,:)

a[1]a[1, :]

二维数组 a 的整个第二行

a(1:5,:)

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

二维数组 a 的前 5 行

a(end-4:end,:)

a[-5:]

二维数组 a 的后 5 行

a(1:3,5:9)

a[0:3, 4:9]

二维数组 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))

3x4 满含 64 位浮点零的二维数组

zeros(3,4,5)

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

3x4x5 满含 64 位浮点零的三维数组

ones(3,4)

np.ones((3, 4))

3x4 满含 64 位浮点一的二维数组

eye(3)

np.eye(3)

3x3 单位矩阵

diag(a)

np.diag(a)

返回二维数组 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 个相等间隔样本,包含 1 和 3

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

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

两个二维数组:一个为 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 个 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.lstsq(a, b)

解关于 x 的 a x = b

b/a

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

解关于 x 的 x a = b

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

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

a 的奇异值分解

chol(a)

linalg.cholesky(a)

二维数组的 Cholesky 分解

[V,D]=eig(a)

D,V = linalg.eig(a)

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

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

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

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

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

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

查找二维数组 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

排序(a)

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

对 2D 数组 a 的每一列进行排序

sort(a, 2)

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

对 2D 数组 a 的每一行进行排序

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

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

用数组 b 保存数组 a,按第一列进行排序

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

帮助:没有 MATALB which 命令的直接等效命令,但命令 help 通常会列出函数所在的文件名。Python 还具有 inspect 模块(执行 import inspect),它提供一个通常可行的 getfile

索引:MATLAB 使用以一为基础的索引,所以序列的第一个元素有索引 1。Python 使用以零为基础的索引,所以序列的第一个元素有索引 0。这种差别引起了困惑和争论,因为每种方法都有各自的优点和缺点。以一为基础的索引与普通的人类语言用法一致,其中序列的“第一个”元素的索引为 1。以零为基础的索引 简化了索引。另请参阅 艾兹格·W·戴克斯特拉教授的文本

范围:在 MATLAB 中,0:5 可以用作范围文本和“切片”索引(在括号内);然而,在 Python 中,0:5 等结构只能用作切片索引(在方括号内)。因此,创建了一个有点怪异的 r_ 对象,允许 NumPy 具有一个简洁的范围构建机制。请注意,r_ 不是像函数或构造函数那样调用,而是使用方括号进行索引,这允许在参数中使用 Python 的切片语法。

逻辑运算: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 代码时,可能需要首先将矩阵重构为线性序列,执行一些索引操作,然后重构回来。由于重构(通常)会创建指向同一存储空间的视图,因此可以非常高效地执行此操作。请注意,NumPy 中重构使用的扫描顺序默认为“C”顺序,而 MATLAB 使用 Fortran 顺序。如果只是转换为线性序列并返回,那么这并不重要。但是,如果您正在转换依赖扫描顺序的 MATLAB 代码中的重构,那么这种 MATLAB 代码:z = reshape(x,3,4); 在 NumPy 中应变为 z = x.reshape(3,4,order='F').copy()

“数组”还是“矩阵”?我应该使用哪一个?#

从历史上看,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 的稀疏矩阵无法很好地与数组进行交互。

  • 矩阵

    • :\\ 行为更类似于 MATLAB 矩阵的行为。

    • <:( 二维的最大值。若要保存三维数据,你需要 array,或者可能是 matrix 的 Python 列表。

    • <:( 二维的最小值。无法使用向量。必须将其转换为单列或单行矩阵。

    • <:( 由于 array 是 NumPy 中的默认值,即使你传入 matrix 作为参数,某些函数也可能会返回 array。对于 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