图像处理总是与多维数组、多维矩阵离不开,python中用于处理数组的高效数据库之一是Numpy,因此在此对Numpy的一些概念进行巩固,记录numpy常见的操作,最后将广播机制的常见使用进行记录。
Numpy的本质
numpy可以提供快速高效的多维数组对象ndarray,可以用于读写硬盘上基于数组的数据集工具,可以对数组进行元素级别的计算;Numpy在数据分析方便便另一个重要作用就是成为算法之间传递数据的容器。
Numpy的ndarray底层实现是一个数据块,dtype类型,表示数组形状的元组以及一个跨度元组。
比如一个二维的的int32 的ndarray,因为int32占据4字节,因此这个数组底层一个60字节的内存空间的数据块,dtype类型为np.int32,表示数组形状的元组(3,5),以及一个跨度元组(20,4).
数据类型决定了ndarray将一块内存解释为特定数据类型所需要的信息。数据类型是numpy强大灵活的原因,
如何理解跨度元组的概念?跨度元组中的每个整数是指为了前进到当前维度下一个元素需要跨过的字节数。一个 的数组,在第一个维度中有三个元素,每个元素是一个长度为5的int32型整数,因此从当前元素移动到下个元素需要跨过字节;而对于第二个维度,其移动到下个元素,只需要移动一个整数长度,也就是4.
常用的数组操作
创建数组
如果没有指定dtype,默认为float64
从已有的python数组生成
data1= [0,1,2,3,4,5]
arr1 = np.array(data1)
创建全1或者全0数组,empty,顺序数组
np.zeros(10)
np.zeros((3,6))
np.ones((3,6))
np.empty((2,3,3))
np.arange(15)
获得数组的属性
arr.dtype
arr.shape
arr.ndim
改变数据类型
float转为int会直接截断小数部分
int_arr = arr.astype(np.int32)
字符串转为整数,可以优雅的进行
numeric_string = np.array(['1.25','9.6'],dtype=np.string_)
numeric_string.astype(float)
改变数组形状
reshape操作不会复制任何数据,仅仅是改变其表示数组形状的元组
arr.reshape((2,5))
将多维数组扁平化有两个方法,其中ravel不会产生元数据的副本,而flatten则会返回数据的副本
arr.ravel()
arr2 = arr.flatten()
数组和数组之间的算数运算
相同大小的数组和数组的算数运算都是元素级别的。数组和标量的运算会将标量值传播到各个元素上。大小不同的数组之间的运算叫广播。
广播是一个值得单独提出来的模块。放在后面仔细讲。
数组的合并和拆分
numpy.concatenate按照指定轴将数组连接在一起,vstack等价于np.concatenate([arr1,arr2],axis=0),hstack等价于np.concatenate([arr1,arr2],axis=1)
arr1 = np.array([[1,2,3],[4,5,6]])
arr2 = np.array([7,8,9],[10,11,12])
np.concatenate([arr1,arr2],axis=0)
#将会得到一个(4,3)形状的数组
np.concatenate([arr1,arr2],axis=1)
#将会得到一个(2,6)形状的数组
数组的复制
想要得到数组的复制而不是视图,需要用copy()函数
arr[5:8].copy()
基本索引和切片
数组的切片反应的是原始数组的视图,数据并不会被复制,因此对切片上的所有改动都会直接反应到原始数组上。
arr = np.arange(9).reshape((3,3))
# 索引
arr[2]
#得到的是np.array([7,8,9])
arr[0][2]
arr[0,2]
# 切片索引
#对于二维数组,可以进行这些类型的切片索引
arr[:2] #默认axis=0
arr[:2,1:]
arr[2,:1]
arr[:,:1]
# 布尔型索引
#bool型数组长度必须和被索引的轴长度一致。一般切片默认axis=0.
#通过bool型选取的数组总是创建数组的副本,即使是返回一模一样的数组也是如此。
names = np.array(['bob','Joe','Will','Bob']
data = np.randn(4,4)
data[names=='bob']
data[-(names=='bob')]
data[names=='bob',2:]
mask = (names=='bob')|(names=='Will')
data[mask]
#和上面的bool索引不同,通过bool型索引设置值会直接在原始的数组上进行改动。
data[data<0]=0
# 花式索引
# 花式索引是利用整数数组进行索引
#花式索引总是复制数据到新的数组
array = np.arange(32).reshape((8,4))
arr[[4,3,0,6]] #得到第4,3,0,6行
arr[[-5,-3,-7]]
arr[[1,5,7,2],[0,3,1,2]] #得到的是长度为4的一维数组
arr[[1,5,7,2],[:,0,3,1,2]] #得到的一个二维数组
数组的转置和轴对换
转置返回的是元数据的视图,并不会进行任何复制操作
# 属性方式
arr.T
# 计算内积
np.dot(arr.T,arr)
#高维情况
arr = np.arange(16).reshape((2,2,4))
arr.transpose((1,0,2))
arr.swapaxes(1,2)
通用函数
主要摘自参考文献中的《通用函数:快速的元素级数组函数》
一元通用函数
abs丶fabs 计算整数丶浮点数或复数的绝对值。对于非复数值,可以使用更快的fabs。
sqrt:计算各元素的平方根。
In [36]: np.sqrt(4)
square:计算各元素的平方。
In [38]: np.square(4)
exp:计算各元素的指数(e^x)
In [40]: np.exp(2)
#python3还有个函数为exp2,计算的是2的指数
In [41]: np.exp2(5)
Out[41]: 32.0
log丶log10丶log2丶log1p 分别为自然对数(底数为e)丶底数为10的log丶底数为2的log丶log(1+x)
In [42]: np.log(2)
Out[42]: 0.6931471805599453
sign:计算各元素的正负号:1(正数)丶0(零)丶-1(负数)
In [44]: np.sign(12)
ceil:计算各元素的ceiling值,即大于等于该值的最小整数
In [47]: np.ceil(15.971)
Out[47]: 16.0
floor:计算各元素的floor值,即小于等于该值的最大整数
In [48]: np.floor(-1.564)
Out[48]: -2.0
rint:将各元素四舍五入到最接近的整数,保留dtype
In [50]: np.rint(1.485)
Out[50]: 1.0
modf:将数组的小数和整数部分以两个独立数组的形式返回
In [52]: np.modf([1.5,2.9])
Out[52]: (array([0.5, 0.9]), array([1., 2.]))
isnan:返回一个表示“哪些值是NaN(这不是一个数字)”的布尔型数组
In [56]: np.isnan(NaN)
Out[56]: True
isfinite丶isinf:分别返回一个表示“哪些元素是有穷的(非inf,非NaN)”或“哪些元素是无穷的”的布尔型数组
复制代码
In [61]: np.isfinite(1/3)
Out[61]: True
cos丶cosh丶sin丶sinh:普通型和双曲型三角函数
In [65]: np.sin(30)
Out[65]: -0.9880316240928618
tan丶tanh丶arccos丶arccosh丶arcsin丶arcsinh丶arctan丶arctanh:反三角函数
logical_not:计算各元素not x的真值。相当于-arr
In [71]: np.logical_not(15)
Out[71]: False
二元通用函数ufunc
add:将数组中对应的元素相加
subtract:从第一个数组中减去第二个数组中的元素
multiply:数组元素相乘
divide丶floor_divide:除法或向下圆整除法(丢弃余数)
power:对第一个数组中的元素A,根据第二个数组中的相应元素B,计算A^B
maximum丶fmax:元素级的最大值计算。fmax将忽略NAN
minimum丶fmin:元素级的最小值计算。fmax将忽略NAN
mod:元素级的求模计算(除法的余数)
copysign:将第二个数组中的值的符号复制给第一个数组中的值。
greater丶greater_equal丶less丶less_equal丶equal丶not_equal:执行元素级的比较运算,最终产生布尔型数组。相当于运算符>丶>=丶<丶<=丶==丶!=
logical_and丶logical_or丶logical_xor:执行元素级的真值逻辑运算。相当于运算符&丶|丶^(与或异)
条件逻辑判断转为数组运算 where
#cond需要得到true/false的bool型数组
# x,y可以是大小相同的数组,也可以是标量+数组
res = np.where(cond,x,y)
arr=np.random.randn(4,4)
np.where(arr>0,2,arr)
np.where(arr>0,2,-2)
数学统计方法
这个很常用mean/sum,std标准差,var方差,min,max,argmin,argmax,cumsum所有元素的累计和,cumprod所有元素的累计积
arr=np.random.randn(4,3)
arr.mean()
arr.sum()
arr.mean(axis=1)
#表示沿着该轴计算均值,比如这里沿着1轴计算,最终是4个个元素
------>axis=1 mean
1, 2, 3 2
4, 5, 6 5
7, 8, 9 8
10,11,12 11
#cumsum和cumprod返回的是一个由中间结果组成数组
arr.cumsum(0)
| 1, 2, 3 2
| 4, 5, 6 5
| 7, 8, 9 8
\/ 10,11,12 11
axis=0
cumsum(0)
| 1, 2, 3
| 5, 7, 9
| 12,15,18
\/ 22,26,30
bool型数组可以使用sum()得到True的数量,可以使用any()和all()函数得到是否存在ture和是否都是True.
bools = np.array([True,False,True,False])
(bools>0).sum()
# 这两个方法也可以用于非bools型数值数组,非0元素将被认为是True
bools.any()
True
bools.all()
False
arr.sort()函数用于就地排序,还可以传入axis沿着指定轴排序.顶级方法np.sort()函数用于返回排序副本,并不会改变原始数组的结果
唯一化函数和其他集合逻辑函数
np.unique(arr)得到数组中的唯一元素的排序数组
intersect1d(x,y) 得到x,y中得到公共元素的排序数组
union1d(x,y) 得到xy的并集有序数组
in1d(x,y) 得到x每个元素是否包含于y的bool数组
setdiff1d(x,y)元素在x中不在y中的元素集合
setor1d(x,y)得到存在于一个数组但是不存在于另一个数组的元素集合,理解为异或
数组输入输出
np.save和np.load,存放在npy后缀的文件中。
线性代数函数
直接对两个数组*得到的对应元素的积,当需要计算矩阵点积的时候需要使用代数函数。
z = np.dot(x,y)
z = x.dot(y)
mat = x.T.dot(x)
from numpy.linalg import inv,qr
diag 方阵的对角线元素,一位数组方式
trace 对角线元素之和
dot 矩阵点积
det 矩阵行列式
eig 矩阵特征值和特征向量
inv 矩阵的逆
qr 矩阵的QR分解
svd矩阵的奇异值分解
广播机制
广播是值不同形状的数组之间进行算数运算的执行方式。将标量和数组合并行就会发生最简单的广播——将标量数值广播到所有的元素上。
广播看起来很简单,但是也是有原则的。原则就是:
- 如果两个数组维度不一致,但是后缘维度的轴长相符,则可以广播
- 如果两个数组维度数量一致(比如都是三维),其中有一个轴为1,则含有1的这个数组会沿着1这个轴进行广播
具体来看,下面两个数组的维度不一致(4,3),(3,),但是从最后一个维度开始,arr1的第二维长度为3,和arr2的维度相同。arr1和arr2的shape并不一样,但是它们可以执行相加操作,这就是通过广播完成的,在这个例子当中是将arr2沿着0轴进行扩展。
arr1 = np.array([[0, 0, 0],[1, 1, 1],[2, 2, 2], [3, 3, 3]]) #arr1.shape = (4,3)
arr2 = np.array([1, 2, 3]) #arr2.shape = (3,)
arr_sum = arr1 + arr2
同理,如果有一个数组是(3,4,2),另一个数组是(4,2),那么虽然(3,4,2)和(4,2)的维度是不相同的,前者为3维,后者为2维。但是它们后缘维度的轴长相同,都为(4,2),所以可以沿着0轴进行广播。
但是如果一个数组是(4,3),另一个数组是(4,),那么因为后缘维度不一致,所以会报错。这种情况需要将第二个数组reshape(4,1),让其变成第二种情况。
对于一个数组是(4,3),另一个数组是(4,1)的,因为其第一轴长度相同,第二轴上有1,因此第二个数组会沿着轴1进行广播。
arr = np.range(12).reshape((4,3))
mean = arr.mean(1) #mean.shape = (4,)
mean = mean.reshape((4,1))
demean = arr-mean
如何比较优雅的广播:上面的代码中,使用了reshape()函数改变了数组的形状,numpy还有另一种通过索引机制插入周的特殊语法,通过np.newaxis属性和全切片方式插入
arr = np.zeros((4,4))
arr_3d = arr[:,np.newaxis,:]
通过这样发的方式,我们就可以根据需要广播的轴定制需要广播的数组。比如数组形状(8,5,3),第二个数组为(8,5),想要沿着轴2广播,则先将其形状扩充为(8,5,1);第二个数组为(8,3),想要沿着轴1广播,则需要将其形状扩充为(8,1,3);第二个数组为(5,3),想要沿着轴0进行广播,则需要将其形状扩充为(1,5,3).perfect,感觉自己棒棒哒!
到这里大致介绍了广播的机制。
我们平时看见的标量和数组的广播中,标量没有维度,因此会全部被广播到各个维度。
如果有一个数组是(3,4,2),另一个数组是(1,2),同时满足了维度不一致和存在长度为1的维度,因此也可以进行广播,沿着轴0和轴1进行广播。
arr= np.arange(24).reshape((2,3,4))
mean = np.array([1,2,3,4]).reshape((1,4))
arr-mean
在tensorflow中,会遇到对于三通道的图片,需要对每个通道减去通道均值,这时候用到的就是这种广播机制。
主要参考资料
《利用python进行数据分析》书籍第4章和第12章