- 深度学习的数学:使用Python语言
- (美)罗纳德·T.纽塞尔
- 2571字
- 2024-04-22 11:54:55
1.2 NumPy
前面已经安装了NumPy。现在我来介绍一下NumPy的一些基本概念和运算方法。你如果有兴趣,也可以自行查找完整的技术手册。
启动Python,尝试执行以下代码:
>>> import numpy as np >>> np.__version__ '1.16.2'
第一行代码导入numpy模块并将其重命名为np。这种用简称来重命名模块的方式虽然不是必需的,却几乎成了通用做法。第二行代码则输出版本号,以确保安装的NumPy版本满足前面所说的最低要求。
1.2.1 定义数组
NumPy以数组为运算对象,它可以方便地将列表转换为数组。想想看,与C和Java等语言中的数组类型相比,Python中的列表类型虽然使用起来非常优雅,但是当使用列表模拟数组进行科学计算的时候,效率还是非常低的。NumPy在这方面很有优势,使用NumPy的数组类型时实际上效率很高。下面的例子首先把列表转换为数组,然后展示了一些数组属性:
>>> a = np.array([1,2,3,4]) >>> a array([1, 2, 3, 4]) >>> a.size 4 >>> a.shape (4,) >>> a.dtype dtype('int64')
上面的例子将一个包含4个元素的列表传给了np.array函数,得到一个NumPy数组。数组最基本的属性包括size和shape。这个数组的size属性为4,表示包含4个元素;shape属性则是包含4的一个元组,表示这是一个包含4个元素的一维数组或者说一维向量。如果数组是二维的,那么其shape属性将包含两个元素,分别对应每一维的大小。在下面的例子中,数组b的shape属性为(2, 4),这表示它是一个2行4列的数组。
>>> b = np.array([[1,2,3,4],[5,6,7,8]]) >>> print(b) [[1 2 3 4] [5 6 7 8]] >>> b.shape (2, 4)
1.2.2 数据类型
Python中的数据类型大体分为两种:取值几乎可以是任意大小的整型以及浮点型。NumPy数组则支持更多的数据类型。由于NumPy的底层是用C语言实现的,因此NumPy支持C语言中所有的数据类型。前面的例子向np.array函数传入了一个各元素为整数的列表,结果得到一个各元素为64位有符号整数的数组。表1-1展示了NumPy支持的数据类型。我们既可以让NumPy替我们选择数据类型,也可以显式指定数据类型。
表1-1 NumPy支持的数据类型,C语言中等价的数据类型以及取值范围
我们来看一些为数组指定类型的例子:
>>> a = np.array([1,2,3,4], dtype="uint8") >>> a.dtype dtype('uint8') >>> a = np.array([1,2,3,4], dtype="int16") >>> a = np.array([1,2,3,4], dtype="uint32") >>> b = np.array([1,2,3,4.0]) >>> b.dtype dtype('float64') >>> b = np.array([1,2,3,4.0], dtype="float32") >>> c = np.array([111,222,333,444], dtype="uint8") >>> c array([111, 222, 77, 188], dtype=uint8)
在上面的例子中,数组a的元素为整型,而数组b的元素为浮点型。注意在第一个关于数组b的例子中,Python自动为数组b的元素选择了64位浮点型。之所以会这样,是因为输入的列表里有一个浮点数4.0。
关于数组c的例子看起来似乎是错误的,其实不然。如果给定的数据超出指定类型的表示范围,NumPy并不会报错。在这个例子中,我们指定的8位整型只能表示[0, 255]取值范围内的整数。前面的两个数111和222属于这个范围;但后面两个数333和444都太大,NumPy默认只保留这两个数的最后8位,分别是77和188。NumPy用这个例子给我们上了一课,让我们明白了该指定什么数据类型。虽然这类问题不常出现,但我们仍须牢记于心。
1.2.3 二维数组
如果说把列表转换为数组后得到的是一维向量,那么我们可以猜测,如果把一个列表的列表转换为数组,那么得到的将是一个二维向量。事实的确如此,我们猜对了。
>>> d = np.array([[1,2,3],[4,5,6],[7,8,9]]) >>> d.shape (3, 3) >>> d.size 9 >>> d array([[1, 2, 3], [4, 5, 6], [7, 8, 9]])
可以看到,一个由三个子列表构成的列表被映射成了一个3 × 3的向量(即矩阵)。由于NumPy数组从0开始对元素编号,因此引用d[1,2]返回的是6。
1.2.4 全0数组和全1数组
NumPy有两个非常有用的函数:np.zeros和np.ones。它们都用于定义指定大小的数组。前者用0作为数组全部元素的初始值,而后者则将数组元素全部初始化为1。这是NumPy从头创建数组的主要方式。
>>> a = np.zeros((3,4), dtype="uint32") >>> a[0,3] = 42 >>> a[1,1] = 66 >>> a array([[ 0, 0, 0, 42], [ 0, 66, 0, 0], [ 0, 0, 0, 0]], dtype=uint32) >>> b = 11*np.ones((3,1)) >>> b array([[11.], [11.], [11.]])
这两个函数的第一个参数都是元组,用于指定各个维度的大小。如果传入标量,那么默认定义的是一个一维向量。以数组b为例,它是初始值为1、大小为3 × 1的数组,通过与标量11相乘,可以使数组b的每个元素都为11。
1.2.5 高级索引
上面的例子介绍了访问单个元素的简单索引方式。NumPy支持更复杂的索引形式,常用的一种就是用单个索引查询整个子数组,举个例子:
>>> a = np.arange(12).reshape((3,4)) >>> a array([[ 0, 1, 2, 3], [ 4, 5, 6, 7], [ 8, 9, 10, 11]]) >>> a[1] array([4, 5, 6, 7]) >>> a[1] = [44,55,66,77] >>> a array([[ 0, 1, 2, 3], [44, 55, 66, 77], [ 8, 9, 10, 11]])
这个例子用到了np.arange函数,它等价于Python中的range函数。注意这里使用reshape方法将一个大小为12的一维向量转换成了一个3 × 4的矩阵。另外请注意,a[1]返回的是整个子数组,索引是从第一维开始进行的。a[1]其实是a[1, :]的简化形示,其中的“:”表示某一维的全部元素。这种简化形式也可以用于赋值操作。
NumPy还支持Python列表的所有切片索引方式,继续上面的例子:
>>> a[:2] array([[ 0, 1, 2, 3], [44, 55, 66, 77]]) >>> a[:2,:] array([[ 0, 1, 2, 3], [44, 55, 66, 77]]) >>> a[:2,:3] array([[ 0, 1, 2], [44, 55, 66]]) >>> b = np.arange(12) >>> b array([ 0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11]) >>> b[::2] array([ 0, 2, 4, 6, 8, 10]) >>> b[::3] array([0, 3, 6, 9]) >>> b[::-1] array([11, 10, 9, 8, 7, 6, 5, 4, 3, 2, 1, 0])
首先,a[:2]返回数组a中前两行的全部元素,这里隐含了用“:”索引第二维元素,a[:2]与a[:2,:]等价。关于数组a的第三个例子对两个维度都进行了索引,通过a[:2, :3]返回了数组a的前两行和前三列元素。关于数组b的例子展示了如何每隔一个或两个元素进行查询。最后一个例子非常有用,这个例子使用一个负的增量实现了索引的倒序。当增量为−1时,表示对所有元素倒序排列。如果增量为−2,则表示以倒序每隔一个元素进行一次查询。
NumPy使用“:”来表示查询某一维的全部元素。NumPy还支持用英文省略号来表示“尽可能多的‘:’符号”。为了举例说明,下面我们先定义一个三维数组:
>>> a = np.arange(24).reshape((4,3,2)) >>> a array([[[ 0, 1], [ 2, 3], [ 4, 5]], [[ 6, 7], [ 8, 9], [10, 11]], [[12, 13], [14, 15], [16, 17]], [[18, 19], [20, 21], [22, 23]]])
可以把数组a看成4个3 × 2的子数组。如果要更新其中的第2个子数组,我们可以这么做:
>>> a[1,:,:] = [[11,22],[33,44],[55,66]] >>> a array([[[ 0, 1], [ 2, 3], [ 4, 5]], [[11, 22], [33, 44], [55, 66]], [[12, 13], [14, 15], [16, 17]], [[18, 19], [20, 21], [22, 23]]])
这里我们显式地用“:”进行各个维度的索引,可以看到“:”的使用让NumPy很有兼容性,NumPy能够自动将列表的列表识别为数组并且执行相应的更新操作。接下来我们可以看到,用英文省略号也可以实现同样的效果。
>>> a[2,...] = [[99,99],[99,99],[99,99]] >>> a array([[[ 0, 1], [ 2, 3], [ 4, 5]], [[11, 22], [33, 44], [55, 66]], [[99, 99], [99, 99], [99, 99]], [[18, 19], [20, 21], [22, 23]]])
这里对第3个3 × 2的子数组也进行了更新。
1.2.6 读写磁盘
NumPy数组可以通过调用np.save写到磁盘上,并通过调用np.load从磁盘上加载,比如:
>>> a = np.random.randint(0,5,(3,4)) >>> a array([[4, 2, 1, 3], [4, 0, 2, 4], [0, 4, 3, 1]]) >>> np.save("random.npy",a) >>> b = np.load("random.npy") >>> b array([[4, 2, 1, 3], [4, 0, 2, 4], [0, 4, 3, 1]])
我们先用np.random.randint创建了一个大小为3 × 4,元素取值在0和5之间(包含0和5)的随机整型数组(NumPy有很多关于随机数的函数)。然后我们将该数组写到磁盘上,命名为random.npy。数组文件必须以“.npy”为后缀,如果没有指定,NumPy会自动添加该后缀。最后,我们通过调用np.load从磁盘上加载了保存的NumPy数组。
本书还会涉及其他的NumPy函数,我会在遇到时详细介绍这些函数。接下来,我们快速了解一下SciPy。