python中的数据操作基本都用numpy来做,在做深度学习的过程一定也绕不过numpy。这篇分几个部分介绍numpy · numpy array 的基本属性,包括 shape, size, dim, data type · 通过 index 获取 numpy array 的数据 · 分割 numpy array,获取 sub array · 变换 numpy array 的维度 · 合并 numpy array,合并多个数组
numpy array 有几个基本属性,分别是
shape, size, dim, data type
这里通过几个不同维度的数组来了解这几个属性。首先我们先随机生成几个不同维度的numpy array
import numpy as np
np.random.seed(0) # seed for reproducibility
x1 = np.random.randint(10, size=6) # One-dimensional array
x2 = np.random.randint(10, size=(3, 4)) # Two-dimensional array
x3 = np.random.randint(10, size=(3, 4, 5)) # Three-dimensional array
数组的三个属性分别表示的含义 ndim: 维度数量 shape: 每个维度的数量 size: 数组的总数量
print("x3 ndim: ", x3.ndim)
print("x3 shape:", x3.shape)
print("x3 size: ", x3.size)
得到 x3 ndim: 3 x3 shape: (3, 4, 5) x3 size: 60
另一个比较有用的属性是 dtype,代表数据的类型
print("dtype:", x3.dtype)
dtype: int64
numpy的数组下标和其他语言的下标语法差不多,取对应index的值可以用过中括号来获取。假设 array([5, 0, 3, 3, 7, 9]) 为 x1
x1[0]
得到 5
numpy提供了一种方便的逆序index,用负值来获取从后往前的Index的数据
x1[-1]
得到 9. 需要注意逆序的起始位置不是0(这个很明显)
对于二维数组的情况,假设我们有个 x2,
array([[3, 5, 2, 4],
[7, 6, 8, 8],
[1, 6, 7, 7]])
[0, 0] 位置的值是3,通过
x2[0, 0]
可以获得。
x2[2, -1]
得到 7
修改数组的值也可以通过下标来操作
x2[0, 0] = 2
print(x2)
得到
array([[12, 5, 2, 4],
[ 7, 6, 8, 8],
[ 1, 6, 7, 7]])
但numpy的数据类型在初始化的时候就确定了,意味着当你想给它赋值不同的数据类型,numpy会自动静默转换。比如我们想把一个浮点类型赋值给 int 类型的 numpy
x1[0] = 3.14159
print(x1)
此时的 x1是
array([3, 0, 3, 3, 7, 9])
中括号指定下标的方式可以获取某个下标对应的值,获取子串也可以通过这种方式,通过指定[起始点:终点:步进]可以得到对应子串,(:) 冒号符是用来分割起始点和终点和步进的。下面是具体的语法
x[start:stop:step]
这三个的默认值是 start = 0 stop = 维度对应的size step = 1
先定义一个一维数组
x = np.arange(10)
现在x是
array([0, 1, 2, 3, 4, 5, 6, 7, 8, 9])
想获取前5个元素
x[:5] -> array([0, 1, 2, 3, 4])
获取从5到最后的元素
x[5:] -> array([5, 6, 7, 8, 9])
获取index 4 到 7 的元素,不包含[7]这个元素
x[4:7] -> array([4, 5, 6])
按步进为2获取元素
x[::2] -> array([0, 2, 4, 6, 8])
步进2,获取奇数元素
x[1::2] -> array([1, 3, 5, 7, 9])
除此之外,Numpy的子串操作还可以进行逆序操作,如从后向前步进n步取子串
x[::-1] -> array([9, 8, 7, 6, 5, 4, 3, 2, 1, 0])
这是取逆序
x[5::-2] -> array([5, 3, 1])
二维数组的子串操作类似,下面举几个例子 首先定义个二维数组
x2 -> array([[12, 5, 2, 4],
[ 7, 6, 8, 8],
[ 1, 6, 7, 7]])
取第一维的前两个维度,每个维度取前三个元素做子串
x2[:2, :3] -> array([[12, 5, 2],
[ 7, 6, 8]])
取第一维度前三个,每个维度步进2取子串
x2[:3, ::2] -> array([[12, 2],
[ 7, 8],
[ 1, 7]])
二维数组同样也可以反转
x2[::-1, ::-1] -> array([[ 7, 7, 6, 1],
[ 8, 8, 6, 7],
[ 4, 2, 5, 12]])
通过子串操作拿到的子串实际上元素还处于原始数据维度中,如果对子串的数据进行修改,同样也会影响到原来的数据
x2 -> [[12 5 2 4]
[ 7 6 8 8]
[ 1 6 7 7]]
现在取个子串
x2_sub = x2[:2, :2] -> [[12 5]
[ 7 6]]
然后修改子串的值
x2_sub[0, 0] = 99
x2_sub -> [[99 5]
[ 7 6]]
此时原始数组的数据也会发生改变
x2 -> [[99 5 2 4]
[ 7 6 8 8]
[ 1 6 7 7]]
那么如何在不影响原始数据的情况下生成一个新的子串来修改呢
我们先创建一个原始二维数据
x2 -> [[99 5 2 4]
[ 7 6 8 8]
[ 1 6 7 7]]
现在还是取 [:2, :2] 作为子串,但这里用 copy 的方式生成一个独立副本
x2_sub_copy = x2[:2, :2].copy()
x2_sub_copy -> [[99 5]
[ 7 6]]
然后对它进行修改,
x2_sub_copy[0, 0] = 42
x2_sub_copy ->[[42 5]
[ 7 6]]
现在查看原始数据,
x2 -> [[99 5 2 4]
[ 7 6 8 8]
[ 1 6 7 7]]
x2的[0, 0]并没有发生改变,还是99
维度变换是 numpy 数组中用的最多的一个操作,经常用来对输入数据进行维度转换,比如我们有一个三通道的5x5图片,用numpy表示是
image.shape = (5, 5, 3)
如果想把三个通道合并为一个,可以通过reshape进行操作
image.reshape((15, 5, 1))
结果会把三通道的数据压缩成单通道。
再一个例子,先定义个3x3的1~9数组
grid = np.arange(1, 10)
grid -> array([1, 2, 3, 4, 5, 6, 7, 8, 9])
grid = grid.reshape((3, 3))
grid -> [[1 2 3]
[4 5 6]
[7 8 9]]
定义的时候数组长度也就是size,必须能够塞进所定义的维度里也就是3x3, 不然reshape的时候会失败。
另一种转换维度的方法是用上面切割子串的 (:) 操作符, 比如我们有一个一维数组,想转换成二维数组,
x = np.array([1,2,3])
numpy提供了个关键词 newaxis,可以在进行子串切割的时候指定新增维度,如使用reshape可以这么写
x = x.reshape((1,3))
x.shape -> (1, 3)
使用 newaxis 可以这么写,也有一样的效果
x = x[np.newaxis, :]
x.shape -> (1, 3)
数组拼接也是常见操作之一,一般有两种情况,同样维度的数组合并,和维度不一样的数组合并。对于这两种情况,有两种不同的合并操作 · np.concatenate · np.vstack , np.hstack vstack和hstack分别表示 vertical和 horizontal,在两个不同维度上合并。
grid = np.arange(0, 6)
grid = grid.reshape((2,3))
grid -> array([[0, 1, 2],
[3, 4, 5]])
用concatenate合并
grid = np.concatenate([grid,grid])
grid -> array([[0, 1, 2],
[3, 4, 5],
[0, 1, 2],
[3, 4, 5]])
grid.shape = (4,3)
合并一个一维数组和一个二维数组,
x = np.array([1, 2, 3])
grid = np.array([[9, 8, 7],
[6, 5, 4]])
合并不同维度数组的前提是在长度一样的维度上进行合并,上面这个需要在 vstack上合并(有点别扭,verticale是垂直方向,跟数组的书写方向习惯不一样)
merge = np.vstack([x, grid])
merge -> array([[1, 2, 3],
[9, 8, 7],
[6, 5, 4]])
在hstack方向合并
y = np.array([[99],[99]])
merge = np.hstack([grid, y])
merge -> array([[ 9, 8, 7, 99],
[ 6, 5, 4, 99]])
既然能合并数组,那么数组也可以拆分,以一个一维数组为例
x = np.arange(0,7)
x -> array([0, 1, 2, 3, 4, 5, 6])
在index为3和5进行拆分
x1,x2,x3 = np.split(x, [3,5])
print(x1,x2,x3)
会得到
>>> x1
array([0, 1, 2])
>>> x2
array([3, 4])
>>> x3
array([5, 6])
同样也可以对多维数组在不同的维度上拆分,
grid = np.arange(16).reshape((4, 4))
grid -> array([[ 0, 1, 2, 3],
[ 4, 5, 6, 7],
[ 8, 9, 10, 11],
[12, 13, 14, 15]])
用 vsplit 拆分
upper, lower = np.vsplit(grid, [2])
upper -> [[0 1 2 3]
[4 5 6 7]]
lower -> [[ 8 9 10 11]
[12 13 14 15]]
用 hstack 进行拆分
left, right = np.hsplit(grid, [2])
left -> [[ 0 1]
[ 4 5]
[ 8 9]
[12 13]]
right -> [[ 2 3]
[ 6 7]
[10 11]
[14 15]]