TL;DR

Way2AI系列,确保出发去"改变世界"之前,我们已经打下了一个坚实的基础。

本文简单介绍了NumPy这个科学计算扩展包的必备知识。

Set up

首先我们需要导入NumPy包,做实验的时候可以设置随机种子以实现可重复性。

1
2
3
4
import numpy as np

# Set seed for reproducibility
np.random.seed(seed=1024)

接下来分别示例 0D(标量)、1D(向量)、2D(矩阵)、3D(3维张量)。

tensors

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
# Scalar
x = np.array(6)
print ("x: ", x)
print ("x ndim: ", x.ndim) # number of dimensions
print ("x shape:", x.shape) # dimensions
print ("x size: ", x.size) # size of elements
print ("x dtype: ", x.dtype) # data type

# Output
# x: 6
# x ndim: 0
# x shape: ()
# x size: 1
# x dtype: int64


# Vector
x = np.array([1.3 , 2.2 , 1.7])
print ("x: ", x)
print ("x ndim: ", x.ndim)
print ("x shape:", x.shape)
print ("x size: ", x.size)
print ("x dtype: ", x.dtype) # notice the float datatype

# Output
# x: [1.3 2.2 1.7]
# x ndim: 1
# x shape: (3,)
# x size: 3
# x dtype: float64


# Matrix
x = np.array([[1, 2], [3, 4]])
print ("x:\n", x)
print ("x ndim: ", x.ndim)
print ("x shape:", x.shape)
print ("x size: ", x.size)
print ("x dtype: ", x.dtype)

# Output
# x:
# [[1 2]
# [3 4]]
# x ndim: 2
# x shape: (2, 2)
# x size: 4
# x dtype: int64


# 3D Tensor
x = np.array([[[1, 2], [3, 4]], [[5, 6], [7, 8]]])
print ("x:\n", x)
print ("x ndim: ", x.ndim)
print ("x shape:", x.shape)
print ("x size: ", x.size)
print ("x dtype: ", x.dtype)

# Output
# x:
# [[[1 2]
# [3 4]]
#
# [[5 6]
# [7 8]]]
# x ndim: 3
# x shape: (2, 2, 2)
# x size: 8
# x dtype: int64

NumPy当然也提供了几个函数,可以快速创建张量。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
# Functions
print ("np.zeros((2, 2)):\n", np.zeros((2, 2)))
print ("np.ones((2, 2)):\n", np.ones((2, 2)))
print ("np.eye((2)):\n", np.eye((2))) # identity matrix
print ("np.random.random((2, 2)):\n", np.random.random((2, 2)))

# Output
# np.zeros((2, 2)):
# [[0. 0.]
# [0. 0.]]
# np.ones((2, 2)):
# [[1. 1.]
# [1. 1.]]
# np.eye((2)):
# [[1. 0.]
# [0. 1.]]
# np.random.random((2, 2)):
# [[0.64769123 0.99691358]
# [0.51880326 0.65811273]]

Indexing

我们可以使用索引从张量中提取指定的值。
请记住,索引从0开始。与使用列表进行索引一样,我们也可以使用负数索引(其中-1是最后一个项目)。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
# Indexing
x = np.array([1, 2, 3])
print ("x: ", x)
print ("x[0]: ", x[0])
x[0] = 0
print ("x: ", x)

# Output
# x: [1 2 3]
# x[0]: 1
# x: [0 2 3]


# Slicing
x = np.array([[1, 2, 3, 4], [5, 6, 7, 8], [9, 10, 11, 12]])
print (x)
print ("x column 1: ", x[:, 1])
print ("x row 0: ", x[0, :])
print ("x rows 0,1 & cols 1,2: \n", x[0:2, 1:3])

# Output
# [[ 1 2 3 4]
# [ 5 6 7 8]
# [ 9 10 11 12]]
# x column 1: [ 2 6 10]
# x row 0: [1 2 3 4]
# x rows 0,1 & cols 1,2:
# [[2 3]
# [6 7]]


# Integer array indexing
print (x)
rows_to_get = np.array([0, 1, 2])
print ("rows_to_get: ", rows_to_get)
cols_to_get = np.array([0, 2, 1])
print ("cols_to_get: ", cols_to_get)
# Combine sequences above to get values to get
print ("indexed values: ", x[rows_to_get, cols_to_get]) # (0, 0), (1, 2), (2, 1)

# Output
# [[ 1 2 3 4]
# [ 5 6 7 8]
# [ 9 10 11 12]]
# rows_to_get: [0 1 2]
# cols_to_get: [0 2 1]
# indexed values: [ 1 7 10]


# Boolean array indexing
x = np.array([[1, 2], [3, 4], [5, 6]])
print ("x:\n", x)
print ("x > 2:\n", x > 2)
print ("x[x > 2]:\n", x[x > 2])

# Output
# x:
# [[1 2]
# [3 4]
# [5 6]]
# x > 2:
# [[False False]
# [ True True]
# [ True True]]
# x[x > 2]:
# [3 4 5 6]

Arithmetic 运算

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
# Basic math
x = np.array([[1, 2], [3, 4]], dtype=np.float64)
y = np.array([[1, 2], [3, 4]], dtype=np.float64)
print ("x + y:\n", np.add(x, y)) # or x + y
print ("x - y:\n", np.subtract(x, y)) # or x - y
print ("x * y:\n", np.multiply(x, y)) # or x * y

# Output
# x + y:
# [[2. 4.]
# [6. 8.]]
# x - y:
# [[0. 0.]
# [0. 0.]]
# x * y:
# [[ 1. 4.]
# [ 9. 16.]]

Dot product 点积

在机器学习中,我们最常使用的NumPy操作之一是使用点积进行矩阵乘法。
假设我们需要取一个2x3的矩阵a和一个3x2的矩阵b的点积,我们将得到矩阵a的行及矩阵b的列作为点积的输出,也就是得到一个2x2的矩阵。点积能够正确运行需要满足的条件便是内部维度匹配,即示例中,矩阵a有3列,矩阵b有3行。

1
2
3
4
5
6
7
8
9
10
11
# Dot product
a = np.array([[1, 2, 3], [4, 5, 6]], dtype=np.float64) # we can specify dtype
b = np.array([[7, 8], [9, 10], [11, 12]], dtype=np.float64)
c = a.dot(b)
print (f"{a.shape} · {b.shape} = {c.shape}")
print (c)

# Output
# (2, 3) · (3, 2) = (2, 2)
# [[ 58. 64.]
# [139. 154.]]

Axis operations

我们还可以沿着特定的轴进行操作。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
# Sum across a dimension
x = np.array([[1, 2], [3, 4]])
print (x)
print ("sum all: ", np.sum(x)) # adds all elements
print ("sum axis=0: ", np.sum(x, axis=0)) # sum across rows
print ("sum axis=1: ", np.sum(x, axis=1)) # sum across columns

# Output
# [[1 2]
# [3 4]]
# sum all: 10
# sum axis=0: [4 6]
# sum axis=1: [3 7]


# Min/Max
x = np.array([[1, 2, 3], [4, 5, 6]])
print ("min: ", x.min())
print ("max: ", x.max())
print ("min axis=0: ", x.min(axis=0))
print ("min axis=1: ", x.min(axis=1))

# Output
# min: 1
# max: 6
# min axis=0: [1 2 3]
# min axis=1: [1 4]

Broadcast

当我们尝试使用看似不兼容的张量形状进行操作时会发生什么?
它们的维度不兼容,但是NumPy为何仍然给出了结果?这就是广播的作用。

1
2
3
4
5
6
7
8
9
# Broadcasting
x = np.array([1, 2]) # vector
y = np.array(3) # scalar
z = x + y
print ("z:\n", z)

# Output
# z:
# [4 5]

Gotchas

在下面的情况中,c的值是多少,它的形状是什么?

1
2
3
4
5
6
7
8
9
10
11
12
13
a = np.array((3, 4, 5))
b = np.expand_dims(a, axis=1)
c = a + b

a.shape # (3,)
b.shape # (3, 1)
c.shape # (3, 3)
print (c)

# Output
# array([[ 6, 7, 8],
# [ 7, 8, 9],
# [ 8, 9, 10]])

如果我们不想出现意外的广播行为,就需要小心确保 矩阵a 和 矩阵b 的形状相同。

1
2
3
4
5
6
7
8
9
10
a = a.reshape(-1, 1)
a.shape # (3, 1)
c = a + b
c.shape # (3, 1)
print (c)

# Output
# [[ 6]
# [ 8]
# [10]]

Transpose 转置

我们经常需要改变张量的维度,以进行诸如点积之类的操作。如果我们需要交换两个维度,可以对张量进行转置。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
# Transposing
x = np.array([[1, 2, 3], [4, 5, 6]])
print ("x:\n", x)
print ("x.shape: ", x.shape)
y = np.transpose(x, (1, 0)) # flip dimensions at index 0 and 1
print ("y:\n", y)
print ("y.shape: ", y.shape)

# Output
# x:
# [[1 2 3]
# [4 5 6]]
# x.shape: (2, 3)
# y:
# [[1 4]
# [2 5]
# [3 6]]
# y.shape: (3, 2)

Reshape

reshape是另一种改变张量形状的办法。
如下面所示,我们reshape后的张量与原始张量具有相同数量的值。我们还可以在一个维度上使用-1,NumPy会根据输入张量自动推断该维度的大小。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
# Reshaping
x = np.array([[1, 2, 3, 4, 5, 6]])
print (x)
print ("x.shape: ", x.shape)
y = np.reshape(x, (2, 3))
print ("y: \n", y)
print ("y.shape: ", y.shape)
z = np.reshape(x, (2, -1))
print ("z: \n", z)
print ("z.shape: ", z.shape)

# Output
# [[1 2 3 4 5 6]]
# x.shape: (1, 6)
# y:
# [[1 2 3]
# [4 5 6]]
# y.shape: (2, 3)
# z:
# [[1 2 3]
# [4 5 6]]
# z.shape: (2, 3)

reshape函数的工作原理是查看新张量的每个维度,并将原始张量分成相应数量的单元。因此,在这里,新张量index 0处的维度为2,因此我们将原始张量分成2个单元,每个单元都有3个值。

Joining

我们还可以使用concatenate和stack来合并张量。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
x = np.random.random((2, 3))
print (x)
print (x.shape)

# Output
# [[0.14950888 0.698439 0.59335256]
# [0.89991535 0.44445739 0.316785 ]]
# (2, 3)


# Concatenation
y = np.concatenate([x, x], axis=0) # concat on a specified axis
print (y)
print (y.shape)

# Output
# [[0.14950888 0.698439 0.59335256]
# [0.89991535 0.44445739 0.316785 ]
# [0.14950888 0.698439 0.59335256]
# [0.89991535 0.44445739 0.316785 ]]
# (4, 3)


# Stacking
z = np.stack([x, x], axis=0) # stack on new axis
print (z)
print (z.shape)

# Output
# [[[0.14950888 0.698439 0.59335256]
# [0.89991535 0.44445739 0.316785 ]]
#
# [[0.14950888 0.698439 0.59335256]
# [0.89991535 0.44445739 0.316785 ]]]
# (2, 2, 3)

Expanding / Reducing

我们还可以轻松地向张量中添加和删除维度,这样做是为了使张量能够兼容某些操作。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
# Adding dimensions
x = np.array([[1, 2, 3], [4, 5, 6]])
print ("x:\n", x)
print ("x.shape: ", x.shape)
y = np.expand_dims(x, axis=1)
print ("y: \n", y)
print ("y.shape: ", y.shape) # notice extra set of brackets are added

# Output
# x:
# [[1 2 3]
# [4 5 6]]
# x.shape: (2, 3)
# y:
# [[[1 2 3]]
#
# [[4 5 6]]]
# y.shape: (2, 1, 3)


# Removing dimensions
x = np.array([[[1, 2, 3]], [[4, 5, 6]]])
print ("x:\n", x)
print ("x.shape: ", x.shape)
y = np.squeeze(x, axis=1)
print ("y: \n", y)
print ("y.shape: ", y.shape) # notice extra set of brackets are gone

# Output
# x:
# [[[1 2 3]]
#
# [[4 5 6]]]
# x.shape: (2, 1, 3)
# y:
# [[1 2 3]
# [4 5 6]]
# y.shape: (2, 3)

Citation

1
2
3
4
5
6
7
@article{madewithml,
author = {Goku Mohandas},
title = { NumPy - Made With ML },
howpublished = {\url{https://madewithml.com/}},
year = {2022}
}

Ending

到这里,便拥有了Way2AI路上需要的NumPy的必备知识。

但我们不应该止步于此。NumPy官网 上有关于NumPy的全部知识。