本文首发于“生信补给站”公众号 https://mp.weixin.qq.com/s/HI92_O9uUiFgo_Gl_H6EXw
1.3 数组性质
还记得 Python 里面「万物皆对象」么?numpy 数组也不例外,那么我们来看看数组有什么属性 (attributes) 和方法 (methods)。
一维数组
用按步就班的 np.array() 带列表生成数组 arr
arr = np.array([3.5, 5, 2, 8, 4.2])arr
现在你应该会用 dir(arr) 来查看数组的属性了吧,看完之后我们对 type, ndim, len(), size, shape, stride, dtype 几个感兴趣,一把梭打印出来看看:
print( 'The type is', type(arr) )print( 'The dimension is', arr.ndim )print( 'The length of array is', len(arr) )print( 'The number of elements is', arr.size )print( 'The shape of array is', arr.shape )print( 'The stride of array is', arr.strides )print( 'The type of elements is', arr.dtype )
The type is <class 'numpy.ndarray'> The dimension is 1 The length of array is 5 The number of elements is 5 The shape of array is (5,) The stride of array is (8,) The type of elements is float64
根据结果我们来看看上面属性到底是啥:
- type:数组类型,当然是 numpy.ndarray
- ndim:维度个数是 1
- len():数组长度为 5 (注意这个说法只对一维数组有意义)
- size:数组元素个数为 5
- shape:数组形状,即每个维度的元素个数 (用元组来表示),只有一维,元素个数为 5,写成元组形式是 (5,)
- strides:跨度,即在某一维度下为了获取到下一个元素需要「跨过」的字节数 (用元组来表示),float64 是 8 个字节数 (bytes),因此跨度为 8
- dtype:数组元素类型,是双精度浮点 (注意和 type 区分)
注意我黄色高亮了 strides,这个概念对于解决引言的「转置高维数组」问题很重要。一图胜千言。
咦,为什么有个 Python View 和 Memory Block 啊?这两个不是一样的么?对一维数组来说,「Python 视图」看它和「内存块」存储它的形式是一样的,但对二维数组甚至高维数组呢?
二维数组
还是用按步就班的 np.array() 带二维列表生成二维数组 arr2d
l2 = [[1, 2, 3], [4, 5, 6]]arr2d = np.array(l2)arr2d
array([[1, 2, 3], [4, 5, 6]])
一把梭打印属性出来看看:
print( 'The type is', type(arr2d) )print( 'The dimension is', arr2d.ndim )print( 'The length of array is', len(arr2d) )print( 'The number of elements is', arr2d.size )print( 'The shape of array is', arr2d.shape )print( 'The stride of array is', arr2d.strides )print( 'The type of elements is', arr2d.dtype )
The type is <class 'numpy.ndarray'> The dimension is 2 The length of array is 2 The number of elements is 6 The shape of array is (2, 3) The stride of array is (12, 4) The type of elements is int32
同样,我们来分析一下上面属性:
- type:数组类型 numpy.ndarray
- ndim:维度个数是 2
- len():数组长度为 2 (严格定义 len 是数组在「轴 0」的元素个数)
- size:数组元素个数为 6
- shape:数组形状 (2, 3)
- strides:跨度 (12, 4) 看完下图再解释
- dtype:数组元素类型 int32
对于二维数组,Python 视图」看它和「内存块」存储它的形式是不一样的,如下图所示:
在 numpy 数组中,默认的是行主序 (row-major order),意思就是每行的元素在内存块中彼此相邻,而列主序 (column-major order) 就是每列的元素在内存块中彼此相邻。
回顾跨度 (stride) 的定义,即在某一维度下为了获取到下一个元素需要「跨过」的字节数。注:每一个 int32 元素是 4 个字节数。对着上图:
- 第一维度 (轴 0):沿着它获取下一个元素需要跨过 3 个元素,即 12 = 3×4 个字节
- 第二维度 (轴 1):沿着它获取下一个元素需要跨过 1 个元素,即 4 = 1×4 个字节
因此该二维数组的跨度为 (12, 4)。
n 维数组
用 np.random.random() 来生成一个多维数组:
arr4d = np.random.random( (2,2,2,3) )
里面具体元素是什么不重要,一把梭 arr4d 的属性比较重要:
print( 'The type is', type(arr4d) )print( 'The dimension is', arr4d.ndim )print( 'The length of array is', len(arr4d) )print( 'The number of elements is', arr4d.size )print( 'The shape of array is', arr4d.shape )print( 'The stride of array is', arr4d.strides )print( 'The type of elements is', arr4d.dtype )
The type is <class 'numpy.ndarray'> The dimension is 4 The length of array is 2 The number of elements is 24 The shape of array is (2, 2, 2, 3) The stride of array is (96, 48, 24, 8) The type of elements is float64
除了 stride,都好理解,请根据下图好好想想为什么 stride 是 (96, 48, 24, 8)?[Hint: 一个 float64 的元素占 8 个字节]
算了还是分析一下吧 (免得掉粉 )。回顾跨度 (stride) 的定义,即在某一维度下为了获取到下一个元素需要「跨过」的字节数。注:每一个 float64 元素是 8 个字节数
- 第一维度 (轴 0):沿着它获取下一个元素需要跨过 12 个元素,即 96 = 12×8 个字节
- 第二维度 (轴 1):沿着它获取下一个元素需要跨过 6 个元素,即 48 = 6×8 个字节
- 第三维度 (轴 2):沿着它获取下一个元素需要跨过 3 个元素,即 24 = 3×8 个字节
- 第四维度 (轴 3):沿着它获取下一个元素需要跨过 1 个元素,即 8 = 1×8 个字节
因此该四维数组的跨度为 (96, 48,24, 8)。
留一道思考题,strides 和 shape 有什么关系?
strides = (96, 48, 24, 8)
shape = (2, 2, 2, 3)
总不能每个高维数组都用可视化的方法来算 strides 把。
2数组的存载
本节讲数组的「保存」和「加载」,我知道它们没什么技术含量,但是很重要。假设你已经训练完一个深度神经网络,该网络就是用无数参数来表示的。比如权重都是 numpy 数组,为了下次不用训练而重复使用,将其保存成 .npy 格式或者 .csv 格式是非常重要的。
numpy 自身的 .npy 格式
用 np.save 函数将 numpy 数组保存为 .npy 格式,具体写法如下:
np.save( ‘’文件名”,数组 )
arr_disk = np.arange(8)np.save("arr_disk", arr_disk)arr_disk
arr_disk.npy 保存在 Jupyter Notebook 所在的根目录下。要加载它也很简单,用 np.load( "文件名" ) 即可:
np.load("arr_disk.npy")
array([0, 1, 2, 3, 4, 5, 6, 7])
文本 .txt 格式
用 np.savetxt 函数将 numpy 数组保存为 .txt 格式,具体写法如下:
np.save( ‘’文件名”,数组 )
arr_text = np.array([[1., 2., 3.], [4., 5., 6.]])np.savetxt("arr_from_text.txt", arr_text)
arr_from_text.txt 保存在 Jupyter Notebook 所在的根目录下,用 Notepad 打开看里面确实存储着 [[1,2,3], [4,5,6]]。
用 np.loadtxt( "文件名" ) 即可加载该文件
np.loadtxt("arr_from_text.txt")
array([[1., 2., 3.], [4., 5., 6.]])
文本 .csv 格式
另外,假设我们已经在 arr_from_csv 的 csv 文件里写进去了 [[1,2,3], [4,5,6]],每行的元素是由「分号 ;」来分隔的,展示如下:
用 np.genfromtxt( "文件名" ) 即可加载该文件
np.genfromtxt("arr_from_csv.csv")
array([nan, nan])
奇怪的是数组里面都是 nan,原因是没有设定好「分隔符 ;」,那么函数 genfromtxt 读取的两个元素是
- 1;2;3
- 4;5;6
它们当然不是数字拉,Numpy 只能用两个 nan (Not a Number) 来代表上面的四不像了。
带上「分隔符 ;」再用 np.genfromtxt( "文件名",分隔符 ) 即可加载该文件
np.genfromtxt("arr_from_csv.csv", delimiter=";")
array([[1., 2., 3.], [4., 5., 6.]])