NumPy 常用数组和矩阵操作
numpy
本文字数:2.5k 字 | 阅读时长 ≈ 11 min

NumPy 常用数组和矩阵操作

numpy
本文字数:2.5k 字 | 阅读时长 ≈ 11 min

NumPy 中数组维度、拼接、筛选、排序、随机数和矩阵计算的方法比较多,之前我把这些方法分别记录在了多篇文章中,这里将它们合并到一起,方便后续查找。本文最后也会对比一下 NumPy 和 PyTorch 中常见的矩阵乘法。

NumPy ndarray 官方文档

1. 数组的 shape、ndim 和 size

对于一个 NumPy 数组:

import numpy as np

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

print(a.shape)
print(a.ndim)
print(a.size)
print(np.shape(a))
print(np.size(a, axis=0))
print(np.size(a, axis=1))

'''
(2, 3)
2
6
(2, 3)
2
3
'''

这里需要注意,NumPy 使用的是 ndim,不是 PyTorch 中常见的 dim()

1.1 np.newaxis

np.newaxis 可以在指定位置增加一个长度为 1 的维度,类似于 PyTorch 中的 unsqueeze

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

row = a[np.newaxis, :]
column = a[:, np.newaxis]

print(row.shape)
print(column.shape)

'''
(1, 3)
(3, 1)
'''

1.2 NumPy 和 Tensor 相互转换

import numpy as np
import torch

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

# NumPy array 转为 PyTorch Tensor
tensor = torch.from_numpy(array)

# PyTorch Tensor 转为 NumPy array
array_2 = tensor.numpy()

torch.from_numpy 得到的 Tensor 与原数组共享内存,因此修改其中一个时,另一个也可能发生变化。如果 Tensor 位于 GPU 或参与梯度计算,通常需要先执行:

array = tensor.detach().cpu().numpy()

2. 数组拼接和大小调整

2.1 np.concatenate

np.concatenate((a1, a2, ...), axis=0) 用于沿指定维度拼接数组。除了拼接维度之外,其他维度的大小必须相同。

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

print(np.concatenate((a, b), axis=0))
print(np.concatenate((a, b.T), axis=1))
print(np.concatenate((a, b), axis=None))

'''
[[1 2]
 [3 4]
 [5 6]]

[[1 2 5]
 [3 4 6]]

[1 2 3 4 5 6]
'''

axis=None 时,输入数组会先展平,再进行拼接。

2.2 np.resize 和 ndarray.resize

下面两个 resize 的行为并不相同:

a = np.array([[0, 1], [2, 3]])
b = np.resize(a, (3, 3))

print(b)

'''
[[0 1 2]
 [3 0 1]
 [2 3 0]]
'''

a.resize((3, 3))
print(a)

'''
[[0 1 2]
 [3 0 0]
 [0 0 0]]
'''

np.resize 会重复元素,因此它通常不适合图像缩放。图像缩放应该使用 OpenCV、Pillow 或其他插值方法。

3. 逻辑判断和元素筛选

3.1 np.all 和 np.any

a = np.array([[0, 0], [0, 0]])

print(np.all(a == 0))
print(np.any(a != 0))

'''
True
False
'''

它们也可以通过 axis 指定判断的维度:

a = np.array([[True, False], [True, True]])

print(np.all(a, axis=0))
print(np.any(a, axis=1))

'''
[ True False]
[ True  True]
'''

3.2 np.logical_and

np.logical_and(x1, x2) 对两个数组执行逐元素逻辑与运算,并且支持广播:

x1 = np.array([[True, True], [False, False]])
x2 = np.array([[True, False], [True, False]])

print(np.logical_and(x1, x2))

'''
[[ True False]
 [False False]]
'''

3.3 np.isin

np.isin(element, test_elements, invert=False) 用于判断 element 中的每个元素是否出现在 test_elements 中,返回值形状与 element 相同。

a = np.array([0, 1, 2, 5, 0])
values = [0, 2]

print(np.isin(a, values))
print(np.isin(a, values, invert=True))

'''
[ True False  True False  True]
[False  True False  True False]
'''

旧代码中常见的 np.in1d 从 NumPy 2.0 开始弃用,并在 NumPy 2.4 中移除,新代码应使用 np.isin

4. 排序和索引

4.1 np.argsort

np.argsort(a, axis=-1) 不直接返回排序后的值,而是返回能够将数组从小到大排列的索引。

x = np.array([[0, 3, 8],
              [2, 9, 2]])

index_0 = np.argsort(x, axis=0)
index_1 = np.argsort(x, axis=1)
index_last = np.argsort(x, axis=-1)
index_flat = np.argsort(x, axis=None)

print(index_0)
print(index_1)
print(index_last)
print(index_flat)

'''
[[0 0 1]
 [1 1 0]]

[[0 1 2]
 [0 2 1]]

[[0 1 2]
 [0 2 1]]

[0 3 5 1 2 4]
'''

对于形状为 (2, 3) 的二维数组:

如果相同元素之间的原始顺序很重要,可以使用稳定排序:

index = np.argsort(x, axis=1, stable=True)

4.2 np.flatnonzero

np.flatnonzero(a) 返回数组展平后所有非零元素的索引:

x = np.arange(-2, 3)
index = np.flatnonzero(x)

print(x)
print(index)
print(x[index])

'''
[-2 -1  0  1  2]
[0 1 3 4]
[-2 -1  1  2]
'''

结合布尔表达式,还可以查找指定元素的位置:

x = np.array([1, 3, 2, 3, 4, 3, 1, 2])
index = np.flatnonzero(x == 3)

print(index)

'''
[1 3 5]
'''

5. 随机数

NumPy 当前推荐先使用 np.random.default_rng() 创建一个 Generator,再通过该对象生成随机数:

rng = np.random.default_rng(seed=1)

给定相同的种子,可以复现同一组随机序列。需要注意,新的 Generator 与旧的 np.random.seed 使用方式和生成序列并不完全相同。

NumPy Random Generator 官方文档

5.1 均匀分布

rng.random(size) 从均匀分布 \([0, 1)\) 中采样:

rng = np.random.default_rng(seed=1)
x = rng.random((3, 2))
print(x.shape)

'''
(3, 2)
'''

5.2 正态分布

rng.standard_normal(size) 从标准正态分布 \(\mathcal{N}(0, 1)\) 中采样:

rng = np.random.default_rng(seed=1)
x = rng.standard_normal((2, 2))

如果需要从均值为 \(\mu\)、标准差为 \(\sigma\) 的正态分布中采样,可以直接使用:

x = rng.normal(loc=3, scale=2.5, size=(2, 4))

这里得到的是 \(\mathcal{N}(3, 2.5^2)\),第二个参数是标准差,不是方差。

5.3 随机整数

rng.integers(low, high=None, size=None) 从半开区间 [low, high) 中采样整数:

rng = np.random.default_rng(seed=1)

x1 = rng.integers(0, 2, size=10)
x2 = rng.integers(0, 5, size=(2, 4))

print(x1.shape)
print(x2.shape)

'''
(10,)
(2, 4)
'''

5.4 随机选择

rng.choice(a, size=None, replace=True, p=None) 从给定数组中随机选择元素:

rng = np.random.default_rng(seed=1)

x1 = rng.choice(5, size=3)
x2 = rng.choice(5, size=5, replace=False)
x3 = rng.choice(
    ['pooh', 'rabbit', 'piglet', 'Christopher'],
    size=5,
    p=[0.5, 0.1, 0.1, 0.3],
)

旧写法与新写法的对应关系如下:

旧写法 推荐写法
np.random.seed(1) rng = np.random.default_rng(1)
np.random.rand(3, 2) rng.random((3, 2))
np.random.randn(2, 2) rng.standard_normal((2, 2))
np.random.randint(0, 5, size=3) rng.integers(0, 5, size=3)
np.random.choice(a, size=3) rng.choice(a, size=3)

6. 线性代数

6.1 np.linalg.inv

np.linalg.inv(a) 用于计算方阵的逆矩阵。对于矩阵 \(A\) 和它的逆矩阵 \(A^{-1}\),满足:

\[ AA^{-1}=A^{-1}A=I \]

a = np.array([[1.0, 2.0],
              [3.0, 4.0]])

a_inv = np.linalg.inv(a)

print(a_inv)
print(np.allclose(a @ a_inv, np.eye(2)))

'''
[[-2.   1. ]
 [ 1.5 -0.5]]
True
'''

np.linalg.inv 也可以一次处理多个方阵:

a = np.array([
    [[1.0, 2.0], [3.0, 4.0]],
    [[1.0, 3.0], [3.0, 5.0]],
])

a_inv = np.linalg.inv(a)
print(a_inv.shape)

'''
(2, 2, 2)
'''

如果矩阵不是方阵或矩阵不可逆,会抛出 LinAlgError。实际求解线性方程 \(Ax=b\) 时,一般优先使用 np.linalg.solve(a, b),而不是先求逆再相乘。

6.2 np.linalg.norm

np.linalg.norm(x, ord=None, axis=None, keepdims=False) 用于计算向量或矩阵的范数。

a = np.arange(9)

print(np.linalg.norm(a, np.inf))
print(np.linalg.norm(a, -np.inf))
print(np.linalg.norm(a, 1))
print(np.linalg.norm(a, 2))

'''
8.0
0.0
36.0
14.2828568570857
'''

常见的 ord 参数如下:

ord 向量 矩阵
None 2 范数 Frobenius 范数
np.inf 最大绝对值 最大行绝对值之和
-np.inf 最小绝对值 最小行绝对值之和
0 非零元素个数 不支持
1 1 范数 最大列绝对值之和
2 2 范数 最大奇异值
'fro' 不支持 Frobenius 范数
'nuc' 不支持 核范数

7. NumPy 和 PyTorch 中的乘法

数组或 Tensor 的乘法主要分为两类:逐元素乘法和矩阵乘法。最容易混淆的是 *multiplydotmatmul

7.1 NumPy 逐元素乘法

a * bnp.multiply(a, b) 都是逐元素乘法,并且支持广播:

a = np.arange(9.0).reshape(3, 3)
b = np.arange(3.0)

result = np.multiply(a, b)
print(result)

'''
[[ 0.  1.  4.]
 [ 0.  4. 10.]
 [ 0.  7. 16.]]
'''

7.2 NumPy 矩阵乘法

a @ bnp.matmul(a, b) 等价,更适合明确表示矩阵乘法:

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

print(a @ b)

'''
[[22 28]
 [49 64]]
'''

当两个输入都是一维向量时,结果是内积:

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

print(a @ b)

'''
32
'''

np.matmul 不允许矩阵与标量相乘,标量乘法直接使用 a * scalar。对于三维及以上数组,matmul 会把最后两个维度视为矩阵,其余维度视为 batch,并按照广播规则计算。

np.dot 在一维和二维输入上的结果通常与上面相同,但它在高维数组上的规则与 matmul 不同。需要表达普通矩阵乘法时,优先使用 @,代码含义会更清楚。

NumPy matmul 官方文档

7.3 PyTorch 逐元素乘法

torch.mul(a, b)a * b 都是逐元素乘法,并且支持广播:

import torch

a = torch.tensor([[1.0], [2.0], [3.0]])
b = torch.tensor([[10.0, 20.0]])

print(torch.mul(a, b))

'''
tensor([[10., 20.],
        [20., 40.],
        [30., 60.]])
'''

7.4 torch.mm 和 torch.matmul

torch.mm 只支持两个二维矩阵相乘,不会对 batch 维度进行广播:

a = torch.randn(2, 3)
b = torch.randn(3, 4)
c = torch.mm(a, b)

print(c.shape)

'''
torch.Size([2, 4])
'''

torch.matmul@ 支持向量、矩阵以及带 batch 维度的矩阵乘法:

a = torch.randn(8, 2, 3)
b = torch.randn(8, 3, 4)

c1 = torch.matmul(a, b)
c2 = a @ b

print(c1.shape)
print(torch.equal(c1, c2))

'''
torch.Size([8, 2, 4])
True
'''

简单记忆:

操作 NumPy PyTorch
逐元素乘法 a * bnp.multiply(a, b) a * btorch.mul(a, b)
二维矩阵乘法 a @ bnp.matmul(a, b) a @ btorch.mm(a, b)torch.matmul(a, b)
Batch 矩阵乘法 a @ bnp.matmul(a, b) a @ btorch.matmul(a, b)
Sep 06, 2026
Aug 01, 2026