python Numpy数组 frombuffer python numpy数组转置_python list转置


python Numpy数组 frombuffer python numpy数组转置_python list转置_02


Numpy 是 Python 专门处理高维数组 (high dimensional array) 的计算的包,每次使用它遇到问题都会它的官网 (www.numpy.org). 去找答案。 在使用 numpy 之前,需要引进它,语法如下:

import numpy

这样你就可以用 numpy 里面所有的内置方法 (build-in methods) 了,比如求和与均值。

numpy.sum()numpy.mean()

但是每次写 numpy 字数有点多,通常我们给 numpy 起个别名 np,用以下语法,这样所有出现 numpy 的地方都可以用 np 替代。

import numpy as np

为什么要专门学习数组呢?看下面「numpy 数组」和「列表」之间的计算效率对比:两个大小都是 1000000,把每个元素翻倍,运行 10 次用 %time 记时。

my_arr = np.arange(1000000)my_list = list(range(1000000))
%time for _ in range(10): my_arr2 = my_arr * 2Wall time: 48.9 ms
%time for _ in range(10): my_list2 = [x * 2 for x in my_list]Wall time: 1.33 s

我们发现「numpy 数组」效率是「列表」效率的 27 (1.33*1000/48.9) 倍左右。如果元素全是数值型变量 (numerical variable),那么 numpy 数组明显是个很好的数据结构。

学习 numpy 还是遵循的 Python 里「万物皆对象」的原则,既然把数组当对象,我们就按着数组的创建、数组的存载、数组的获取、数组的变形、和数组的计算来盘一盘 NumPy,目录如下:


python Numpy数组 frombuffer python numpy数组转置_2d_03


有些读者可能会说,NumPy 都什么好学的,数组都弄不懂的人还能干什么,那我来问你个问题,知道「转置操作」吧,那么下面这个二维数组转置后是什么?

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

太简单了,是 [[1,4], [2,5], [3,6]],来看看是不是。

arr.Tarray([[1, 4], [2, 5], [3, 6]])

答对了,你牛,再看一道转置的题


python Numpy数组 frombuffer python numpy数组转置_python list转置_04


arr = np.arange(16).reshape((2, 2, 4))arrarray([[[ 0, 1, 2, 3], [ 4, 5, 6, 7]], [[ 8, 9, 10, 11], [12, 13, 14, 15]]])

等等,现在有三维,转置通常不是转两个维度吗?转三个维度也可以?当然,比如把第 1, 2, 3 维度转置到第 2, 1, 3 维度,可以用 transpose 函数。

arr.transpose(1,0,2)array([[[ 0, 1, 2, 3], [ 8, 9, 10, 11]], [[ 4, 5, 6, 7], [12, 13, 14, 15]]])

如果不知道上面答案怎么来的,我觉得你还是有必要看看本帖的。由于篇幅原因,NumPy 系列也分两贴,上贴讲前三节的内容,下帖讲后两节的内容。

1数组的创建

1.1初次印象

数组 (array) 是相同类型的元素 (element) 的集合所组成数据结构 (data structure)。numpy数组中的元素用的最多是「数值型」元素,平时我们说的一维、二维、三维数组长下面这个样子 (对应着线、面、体)。四维数组很难被可视化。


python Numpy数组 frombuffer python numpy数组转置_2d_05


注意一个关键字 axis,中文叫「轴」,一个数组是多少维度就有多少根轴。由于 Python 计数都是从 0 开始的,那么

  • 第 1 维度 = axis 0
  • 第 2 维度 = axis 1
  • 第 3 维度 = axis 2

但这些数组只可能在平面上打印出来,那么它们 (高于二维的数组) 的表现形式稍微有些不同。


python Numpy数组 frombuffer python numpy数组转置_数组_06


分析上图各个数组的在不同维度上的元素:

  • 一维数组:轴 0 有 3 个元素
  • 二维数组:轴 0 有 2 个元素,轴 1 有 3 个元素
  • 三维数组:轴 0 有 2 个元素 (2 块),轴 1 有 2 个元素,轴 2 有 3 个元素
  • 四维数组:轴 0 有 2 个元素 (2 块),轴 1 有 2 个元素 (2 块),轴 2 有 2 个元素,轴 3 有 3 个元素

2.1创建数组

带着上面这个对轴的认识,接下来我们用代码来创建 numpy 数组,有三种方式:

  1. 按步就班的 np.array() 用在列表和元组上
  2. 定隔定点的 np.arange() 和 np.linspace()
  3. 一步登天的 np.ones(), np.zeros(), np.eye() 和 np.random.random()

按步就班法

给了「列表」和「元组」原材料,用 np.array() 包装一下便得到 numpy 数组。

l = [3.5, 5, 2, 8, 4.2]np.array(l)array([3.5, 5. , 2. , 8. , 4.2])
t = (3.5, 5, 2, 8, 4.2)np.array(t)array([3.5, 5. , 2. , 8. , 4.2])

注意,numpy 数组的输出都带有 array() 的字样,里面的元素用「中括号 []」框住。

定隔定点法

更常见的两种创建 numpy 数组方法:

  • 定隔的 arange:固定元素大小间隔
  • 定点的 linspace:固定元素个数

先看 arange 例子:

print( np.arange(8) )print( np.arange(2,8) )print( np.arange(2,8,2))[0 1 2 3 4 5 6 7][2 3 4 5 6 7][2 4 6]

函数 arange 的参数为起点 , 终点 , 间隔

arange(start , stop , step)

其中 stop 必须要有,start 和 step 没有的话默认为 1。对着这个规则看看上面各种情况的输出。

注:用函数 print 打印 numpy 数组就没有 array() 的字样了,只用其内容,而且元素之间的「逗号」也没有了。

再看 linspace 的例子:

print( np.linspace(2,6,3) )print( np.linspace(3,8,11) )[2. 4. 6.][3. 3.5 4. 4.5 5. 5.5 6. 6.5 7. 7.5 8. ]

函数 linspace 的参数为起点 , 终点 , 点数

linspace (start , stop , num)

其中 start 和 stop 必须要有,num 没有的话默认为 50。对着这个规则看看上面各种情况的输出。

一步登天法

NumPy 还提供一次性

  • 用 zeros() 创建全是 0 的 n 维数组
  • 用 ones() 创建全是 1 的 n 维数组
  • 用 random() 创建随机 n 维数组
  • 用 eye() 创建对角矩阵 (二维数组)

对于前三种,由于输出是 n 为数组,它们的参数是一个「标量」或「元组类型的形状」,下面三个例子一看就懂了:

print( np.zeros(5) ) # 标量5代表形状(5,)print( np.ones((2,3)) )print( np.random.random((2,3,4)) )[0. 0. 0. 0. 0.][[1. 1. 1.] [1. 1. 1.]][[[0.15684866 0.33684519 0.85095027 0.67827412] [0.58041935 0.12639616 0.33509142 0.99561644] [0.59581471 0.92043399 0.56731046 0.76811703]] [[0.74276133 0.85278489 0.32392871 0.40553182] [0.7718898 0.35496469 0.20061144 0.00351225] [0.49957334 0.48449498 0.62835324 0.29610557]]]

对于函数 eye(),它的参数就是一个标量,控制矩阵的行数或列数:

np.eye(4)array([[1., 0., 0., 0.], [0., 1., 0., 0.], [0., 0., 1., 0.], [0., 0., 0., 1.]])

此外还可以设定 eye() 里面的参数 k

  • 默认设置 k = 0 代表 1 落在对角线上
  • k = 1 代表 1 落在对角线右上方
  • k = -1 代表 1 落在对角线左下方
np.eye(4, k=1)array([[0., 1., 0., 0.], [0., 0., 1., 0.], [0., 0., 0., 1.], [0., 0., 0., 0.]])

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 The dimension is 1The length of array is 5The number of elements is 5The 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 Numpy数组 frombuffer python numpy数组转置_ide_07


咦,为什么有个 Python View 和 Memory Block 啊?这两个不是一样的么?对一维数组来说,「Python 视图」看它和「内存块」存储它的形式是一样的,但对二维数组甚至高维数组呢?

二维数组

还是用按步就班的 np.array() 带二维列表生成二维数组 arr2d

l2 = [[1, 2, 3], [4, 5, 6]]arr2d = np.array(l2)arr2darray([[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 The dimension is 2The length of array is 2The number of elements is 6The 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 视图」看它和「内存块」存储它的形式是不一样的,如下图所示:


python Numpy数组 frombuffer python numpy数组转置_数组_08


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 The dimension is 4The length of array is 2The number of elements is 24The 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 个字节]


python Numpy数组 frombuffer python numpy数组转置_ide_09


算了还是分析一下吧 (免得掉粉


python Numpy数组 frombuffer python numpy数组转置_2d_10


)。回顾跨度 (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