本文为初学者简要介绍了 NumPy 库的使用与规则,通过该科学计算库,我们能构建更加高效的数值计算方法。此外,因为机器学习存在着大量的矩阵运算,所以 NumPy 允许我们在 Python 上实现高效的模型。
NumPy 是 Python 语言的一个扩充程序库。支持高效的多数组与矩阵运算,此外也针对数组运算提供大量的数学函数库。NumPy 的科学计算十分高效,因此弥补了 Python 在运算效率上的不足。
在本文中,我们将简单介绍在机器学习和数据科学中应用最广的科学计算库,可以说它的高效令使用 Python 开发机器学习算法成为了可能。此外,我们也常认为正是因为 NumPy,Python 才可以像 MATLAB 那样高效地执行矩阵运算。
以下将开启我们的 NumPy 之旅:
import numpy as np
如上在 Python 内导入 NumPy 库,「np」简写即我们调用 NumPy 时约定俗成的命名。下面,我们分别创建了一个 Python 数组和 NumPy 数组:
# python array
a = [1,2,3,4,5,6,7,8,9]
# numpy array
A = np.array([1,2,3,4,5,6,7,8,9])
以下分别打印了这两个变量的值与类型:
print(a)
print(A)
print(type(a))
print(type(A))
====================================================================
[1, 2, 3, 4, 5, 6, 7, 8, 9]
[1 2 3 4 5 6 7 8 9]
<class 'list'>
<class 'numpy.ndarray'>
那么我们为什么要使用 NumPy 数组而不使用标准的 Python 数组呢?原因可能是 NumPy 数组远比标准数组紧密,在使用同样单精度变量下,NumPy 数组所需要的内存较小。此外,NumPy 数组是执行更快数值计算的优秀容器。
np.arange()
下面是另一种定义数组元素的方式:
np.arange(0,10,2)
====================================================================
array([0, 2, 4, 6, 8])
其中 arange([start],stop,[step]) 声明了该数组元素起始与终止的值,而 step 定义了给定区间内采样的步幅大小。在以上代码中,我们生成一个从零开始到 10 结束(不包含 10),并且每次加 2 的数组。注意数组元素取值服从左闭右开原则,即取 0 而不取 10,停止数值并不能取到。
下面是另一个案例:
np.arange(2,29,5)
====================================================================
array([ 2, 7, 12, 17, 22, 27])
其实在 NumPy 中的数组可以等价的称之为矩阵或向量。所以当我们称矩阵的维度是 2×3 时,这并没有错误,我们同样还是在描述一个多维数组。如下展示了一个 2×3 阶矩阵:
array([ 2, 7, 12,],
[17, 22, 27])
现在我们可以讨论默认 NumPy 数组的形状(shape),即等同于讨论矩阵的维度。形状是 np 数组一个非常重要的属性,下面使用 shape 方法调用变量 A 的形状:
A = np.array([1, 2, 3, 4, 5, 6, 7, 8, 9])
A.shape
====================================================================
(9,)
这是一个秩为 1 的矩阵,因此我们看到输出的形状只有一个元素。我们可以使用 reshape() 函数将该数组转化为我们想要的维度,如下,我们将 B 的形状转化为 3×3,reshape() 方法将会返回一个多维数组,因此它的左右分别有两个方括号。
因为 Python 定义的列表没有 reshape() 方法,该博客给出的标准数组会报错。我们只能对 NumPy 数组执行 reshape。此外,执行 reshape 方法要求转化前和转化后的元素数量是一样的。
B = np.array([1, 2, 3, 4, 5, 6, 7, 8, 9])
B.reshape(3,3)
====================================================================
array([[1, 2, 3],
[4, 5, 6],
[7, 8, 9]])
我们可以如下输出 B 的形状:
B.shape
====================================================================
(3,3)
np.zeros()
下面,我们可以使用 np.zero() 函数生成一个元素全是零的矩阵。如下在给定需要生成矩阵的形状后,其就能自动填充零值:
np.zeros((4,3))
====================================================================
array([[ 0., 0., 0.],
[ 0., 0., 0.],
[ 0., 0., 0.],
[ 0., 0., 0.]])
np.zeros((n,m)) 将返回一个 n*m 阶矩阵,其中每个值都为零。
np.eye()
eye() 方法将生成一个单位矩阵:
np.eye(5)
====================================================================
array([[ 1., 0., 0., 0., 0.],
[ 0., 1., 0., 0., 0.],
[ 0., 0., 1., 0., 0.],
[ 0., 0., 0., 1., 0.],
[ 0., 0., 0., 0., 1.]])
np.eye(n) 将生成一个 n 阶单位方阵,即一个 n 阶矩阵,其主对角线元素都为 1,其它元素都为 0。
np.dot()
矩阵乘法在机器学习中十分重要,以下展示了怎样使用 NumPy 执行矩阵乘法。我们一般使用 np.dot() 执行矩阵乘法,即点积。执行该乘法的前提是左边矩阵的列数(每行的元素)必须等于右边矩阵的行数,否则就会报错。此外,根据矩阵乘法的定义,左乘和右乘也不一样,这一点我们需要注意。
若 A=(2,3),而 B=(3,2),那么 A 左乘 B 就要求 A 的列数 3 等于 B 的函数 3。下面展示了 NumPy 矩阵乘法:
# generate an identity matrix of (3 x 3)
I = np.eye(3)
I
====================================================================
array([[ 1., 0., 0.],
[ 0., 1., 0.],
[ 0., 0., 1.]])
# generate another (3 x 3) matrix to be multiplied.
D = np.arange(1,10).reshape(3,3)
D
====================================================================
array([[1, 2, 3],
[4, 5, 6],
[7, 8, 9]])
以上定义了两个矩阵,下面则执行矩阵 D 左乘矩阵 I:
# perform actual dot product.
M = np.dot(D,I)
M
====================================================================
array([[ 1., 2., 3.],
[ 4., 5., 6.],
[ 7., 8., 9.]])
np.sum()
np.sum() 会将整个矩阵的所有元素加和为一个标量值:
# add all the elements of matrix.
sum_val = np.sum(M)
sum_val
====================================================================
45.0
此外,我们还可以提供参数以确定到底是沿矩阵的行累加还是沿矩阵的列累加。如下我们给定参数 axis=1,其代表将每一行的元素累加为一个标量值。
# sum along the rows
np.sum(M,axis=1)
====================================================================
array([ 6., 15., 24.])
第一行累加为 6、第二行累加为 15、第三行累加为 24。此外,给定参数 axis=0 则表示沿列累加:
# sum along the cols
np.sum(M,axis=0)
====================================================================
array([ 12., 15., 18.])
第一列累加为 12、第二列累加为 15、第三列累加为 18。
np.random.rand()
我们可以使用 np.random.rand() 随机生成矩阵,即给定矩阵的形状,其中每个元素都是随机生成的。如下随机生成了一个 2×2 矩阵:
# generate random values in a 2 x 3 matrix form
np.random.rand(2,3)
====================================================================
array([[ 0.2248368 , 0.49652272, 0.76189091],
[ 0.73520939, 0.48107188, 0.3883801 ]])
当然我们也能扩展到随机生成更高维度的矩阵:
# generate random values in a 12 x 13 matrix form
np.random.rand(12,13)
====================================================================
array([[ 0.43385691, 0.15503296, 0.19860119, 0.65346609, 0.16774261,0.56058978, 0.84974275, 0.05887681, 0.27276929, 0.88750259,0.25141674, 0.05663906, 0.54186252],
[ 0.2635477 , 0.88291404, 0.42043263, 0.83565607, 0.92982761,0.79879409, 0.91323242, 0.37954769, 0.60198588, 0.44773903,0.70699903, 0.3892703 , 0.94314732],
[ 0.12593268, 0.97838364, 0.81297353, 0.3368167 , 0.33501746,0.99619471, 0.22476839, 0.93321408, 0.41301684, 0.01808732,0.61321647, 0.22462791, 0.468457 ],
[ 0.63765001, 0.13884884, 0.67648642, 0.65589694, 0.80931411,0.46202022, 0.40819602, 0.03863341, 0.16494124, 0.69603883,0.96849077, 0.19150476, 0.8968954 ],
[ 0.25646945, 0.21928867, 0.70952192, 0.80569537, 0.84562245,0.54595757, 0.00684613, 0.19142737, 0.94387805, 0.80871064,0.73648968, 0.80105002, 0.16716087],
[ 0.3894393 , 0.61933361, 0.41088568, 0.88781578, 0.40932049,0.90947387, 0.71984125, 0.81259019, 0.69020009, 0.56480145,0.43041522, 0.02650665, 0.7738148 ],
[ 0.21326808, 0.2036178 , 0.30368209, 0.51081501, 0.64345557,0.99061654, 0.96805793, 0.19446453, 0.25974565, 0.74033622,0.37379014, 0.67444828, 0.82899251],
[ 0.47571066, 0.82012796, 0.50881338, 0.3900192 , 0.34356749,0.36440024, 0.58048805, 0.74650051, 0.24974157, 0.70129048,0.99920892, 0.29142188, 0.09263266],
[ 0.4140815 , 0.25578684, 0.5485647 , 0.07581615, 0.28539059,0.93805043, 0.56897052, 0.23606972, 0.78568646, 0.609795,0.70741831, 0.51003452, 0.53791667],
[ 0.53967367, 0.78513565, 0.94739241, 0.03891731, 0.15962705,0.45470422, 0.56172944, 0.49735169, 0.35216862, 0.87391629,0.43953245, 0.18160601, 0.78307107],
[ 0.1725005 , 0.89132449, 0.05287284, 0.2113003 , 0.69802999,0.12609322, 0.83490382, 0.34199806, 0.90740966, 0.33934554,0.02015816, 0.13498658, 0.06695927],
[ 0.14066135, 0.34828447, 0.0780561 , 0.00126867, 0.57958087,0.93641585, 0.70294758, 0.21712057, 0.24902555, 0.53284372,0.19795993, 0.69817631, 0.71156616]])
np.append()
如果我们需要手动地给一个数组添加一个或多个元素,那么我们可以使用 np.append()。
# generate an array using np.arange()
A = np.arange(5,15,2)
A
====================================================================
array([ 5, 7, 9, 11, 13])
下面使用 np.append() 添加一个元素到数组 A 中:
A = np.append(A,19)
A
====================================================================
array([ 5, 7, 9, 11, 13, 19])
np.append() 同样可以将一个具体的数组添加到已有的数组中:
A = np.append(A,[3,55,34,553])
A
====================================================================
array([ 5, 7, 9, 11, 13, 19, 3, 55, 34, 553])
如上我们将一个列表及其元素添加到了 np 数组中。
np.diff()
若给定一个数组,我们该如何求取该数组两个元素之间的差?NumPy 提供了 np.diff() 方法以求 A[n+1]-A[n] 的值,该方法将输出一个由所有差分组成的数组。
A=np.array([5, 7, 9, 11, 13, 19, 3, 55, 34, 553])
B = np.diff(A,n=1)
B
====================================================================
array([ 2, 2, 2, 2, 6, -16, 52, -21, 519])
我们需要注意执行差分运算后的数组要比原数组的元素少 1 位。其中 n=1 代表执行一次求差分,并返回差分的数组。而 n=2 代表执行两次差分,并返回第二次求差分后的数组。第二次求差分是在第一次差分结果数组上进行的。如下对 A 求两次差分等价于对上文 B 再求一次差分。
# parameter n indicates that this diff() must be run twice.
np.diff(A,n=2)
===================================================================
array([ 0, 0, 0, 4, -22, 68, -73, 540])
np.vstack() 和 np.column_stack()
若我们希望将多个向量或矩阵按一定的方法堆叠成新的矩阵,那么 np.vstack() 和 np.column_stack() 方法将帮助我们实现这一操作。以下定义三个行向量:
# lets define 3 lists.
a = [1,2,3]
b = [4,5,6]
c = [7,8,9]
堆叠一共有两种变体,即按行堆叠还是按列堆叠。按行堆叠即将需要的向量或矩阵作为新矩阵的一个行,按列堆叠即一个向量作为新矩阵的一列。以下展示了 np.vstack((a,b,c)) 如何将向量 a、b、c 分别作为新矩阵的第一行、第二行和第三行:
# directly stack with lists passed in the same order.
np.vstack((a,b,c))
===================================================================
array([[1, 2, 3],
[4, 5, 6],
[7, 8, 9]])
np.vstack((b,a,c))
===================================================================
array([[4, 5, 6],
[1, 2, 3],
[7, 8, 9]])
此外,np.column_stack() 可以将每个元素作为一列,例如 np.column_stack((a,b,c)) 就将向量 a 作为第一列、b 作为第二列、c 作为第三列:
np.column_stack((a,b,c))
===================================================================
array([[1, 4, 7],
[2, 5, 8],
[3, 6, 9]])
np.column_stack((b,a,c))
===================================================================
array([[4, 1, 7],
[5, 2, 8],
[6, 3, 9]])
np 数组索引
NumPy 数组的索引方式和 Python 列表的索引方式是一样的,从零索引数组的第一个元素开始我们可以通过序号索引数组的所有元素。例如 A[i] 索引数组 A 中的第 i+1 个元素。此外,我们还能索引一串元素:
A=np.array([5, 7, 9, 11, 13, 19, 3, 55, 34, 553])
A[2:5]
===================================================================
array([ 9, 11, 13])
如上 A[2:5] 索引了数组 A 中第 3 到第 5 个元素,注意 Python 列表和数组的索引都是左闭右开,即 A 中包含 2 索引的元素而不包含 5 索引的元素:
A[lowerbound(inclusive): upperbound(exclusive)]
广播操作
广播操作是 NumPy 非常重要的一个特点,它允许 NumPy 扩展矩阵间的运算。例如它会隐式地把一个数组的异常维度调整到与另一个算子相匹配的维度以实现维度兼容。所以将一个维度为 [3,2] 的矩阵与一个维度为 [3,1] 的矩阵相加是合法的,NumPy 会自动将第二个矩阵扩展到等同的维度。
为了定义两个形状是否是可兼容的,NumPy 从最后开始往前逐个比较它们的维度大小。在这个过程中,如果两者的对应维度相同,或者其一(或者全是)等于 1,则继续进行比较,直到最前面的维度。若不满足这两个条件,程序就会报错。
如下展示了一个广播操作:
a = np.array([1.0,2.0,3.0,4.0, 5.0, 6.0]).reshape(3,2)
b = np.array([3.0])
a * b
====================================================================
array([[ 3., 6.],
[ 9., 12.],
[ 15., 18.]])
严格数学意义上,a 和 b 是不能执行矩阵乘法的,因为它们的维度不符合要求。但在 NumPy 的广播机制下,维度为 1 的项何以扩展到相应的维度,所以它们就能够执行运算。
矩阵的运算
以下执行了矩阵的转置操作:
a = np.array([[1,0],[2,3]])
>>> print a
[[1 0]
[2 3]]
>>> print a.transpose()
[[1 2]
[0 3]]
运算矩阵的迹:
>>> print np.trace(a)
4
此外,numpy.linalg 模块中有很多关于矩阵运算的方法,如下据算矩阵的特征值与特征向量:
>>> import numpy.linalg as nplg
>>> print nplg.eig(a)
(array([ 3., 1.]), array([[ 0. , 0.70710678],
[ 1. , -0.70710678]]))