数组基础
导入 NumPy 时,通常给其一个别名“np”,即 import numpy as np。NumPy 库中的函数,要在函数名前加上导入的库名 np. 才能使用。
数据类型
- 整数型数组与浮点型数组
为克服列表的缺点,一个 NumPy 数组只容纳一种数据类型,以节约内存。为方便起见,可将 NumPy 数组简单分为整数型数组与浮点型数组。
import numpy as np
# 创建整数型数组
arr1 = np.array( [1, 2, 3] ) # 元素若都是整数,则为整数型数组
print(arr1) #OUT:[1 2 3]
# 创建浮点型数组
arr2 = np.array( [1.0, 2, 3] ) # 内含浮点数,则为浮点型数组
print(arr2) #OUT:[1. 2. 3.]
PS:使用 print 输出 NumPy 数组后,元素之间没有逗号,这有两个好处,一是可以将之与 Python 列表区分开来,二是避免逗号与小数点之间的混淆。
2. 同化定理:
一个人的力量是无法改变全体的,在实际操作中要注意:
- 往整数型数组里插入浮点数,该浮点数会自动被截断为整数;
- 往浮点型数组里插入整数,该整数会自动升级为浮点数;
import numpy as np
# 整数型数组
arr1 = np.array( [1, 2, 3] )
arr1[0] = 100.9 # 插入浮点数,被截断,数组仍为整数型
print(arr1) #OUT:[100 2 3]
# 浮点型数组
arr2 = np.array( [1.0, 2, 3] )
arr2[1] = 10 # 插入整数型,被升级,数组仍为浮点型
print(arr2) #OUT:[ 1. 10. 3.]
- 共同改变定理:
同化定理告诉我们,整数型数组和浮点型数组之间的界限十分严格,那么如何将这两种数据类型的数组进行相互转化呢?既然某一个人容易被集体所同化,那只要全体共同改变,自然就可以成功,整数型数组和浮点型数组相互转换,规范的方法是使用 .astype( ) 方法。
import numpy as np
# 创建整数型数组
arr1 = np.array( [1, 2, 3] )
print(arr1) #OUT:[1 2 3]
# 整数型数组 ——> 浮点型数组
arr2 = arr1.astype(float)
print(arr2) #OUT:[1. 2. 3.]
# 浮点型数组 ——> 整数型数组
arr3 = arr2.astype(int)
print(arr3) #OUT:[1 2 3]
除了上述方法,只要满足共同改变定理,整数型数组和浮点型数组仍然可以互相转换。最常见的是整数型数组在运算过程中升级为浮点型数组,示例如下。
import numpy as np
# 整数型数组
arr = np.array( [1, 2, 3] )
print(arr) #OUT:[1 2 3]
# 整数型数组与浮点数做运算
print( arr + 0.0 ) #OUT:[1. 2. 3.]
print( arr * 1.0 ) #OUT:[1. 2. 3.]
# 整数型数组遇到除法(即便是除以整数)
print( arr / 1 ) #OUT:[1. 2. 3.]
# 整数型数组与浮点型数组做运算
int_arr = np.array( [1, 2, 3] )
float_arr = np.array( [1.0, 2, 3] )
print( int_arr + float_arr ) #OUT:[2. 4. 6.]
PS:整数型数组很好升级,但浮点型数组在运算过程中一般不会降级。
数组维度
考虑到深度学习中三维及其以上的数组出现次数少,我们后续主要讲解NumPy 中的一维数组和二维数组,学了一维和二维后,很好类推到三维。
- 一维数组和二维数组
- 不同维度的数组之间,从外形上的本质区别是:
一维数组使用 1 层中括号表示;
二维数组使用 2 层中括号表示;
三维数组使用 3 层中括号表示。 - 有些函数需要传入数组的形状参数,不同维度数组的形状参数为
一维数组的形状参数形如: x 或 (x,) ;
二维数组的形状参数形如: (x,y) ;
三维数组的形状参数形如: (x,y,z) 。 - 现在以同一个序列进行举例
当数组有 1 层中括号,如[1 2 3],则其为一维数组,其形状是 3 或 (3,) ;
当数组有 2 层中括号,如[[1 2 3]],则其为二维数组,其形状是 (1,3) ;
当数组有 3 层中括号,如[[[1 2 3]]],则其为三维数组,其形状是 (1,1,3) ;
这里用后面要写的 np.ones( ) 函数进行演示,只因其刚好需要传入形状参数。
import numpy as np
arr1 = np.ones(3) # 传入形状 3
print(arr1) # 造出一维数组 OUT:[1. 1. 1.]
arr2 = np.ones((1,3)) # 传入形状(1,3)
print(arr2) # 造出二维数组 OUT:[[1. 1. 1.]]
arr3 = np.ones((1,1,3)) # 传入形状(1,1,3)
print(arr3) # 造出三维数组 OUT:[[[1. 1. 1.]]]
同时,我们还可以使用数组的 .shape 属性查看 arr1 和 arr2 的形状。
print( arr1.shape ) #OUT:(3,)
print( arr2.shape ) #OUT:(1, 3)
print( arr3.shape ) #OUT:(1, 1, 3)
大家可以随时留意一下数组的维度(通过中括号的数量),后面有些函数(比如数组的拼接函数)需要两个数组是同维度的.
2. 不同维度数组之间的转化
一维数组转二维数组,还是二维数组转一维数组,均要使用的是数组的重塑方法 .reshape( ) ,该方法需要传入重塑后的形状(shape)参数。这个方法神奇的是,给定了其他维度的数值,剩下一个维度可以填-1,让它自己去计算。比如把一个 5 行 6 列的矩阵重塑为 3 行 10 列的矩阵,当列的参数10 告诉它,行的参数直接可以用-1 来替代,它会自己去用 30 除以 10 来计算。
首先,演示将一维数组升级为二维数组。
import numpy as np
# 创建一维数组
arr1 = np.arange(10)
print(arr1) #OUT:[0 1 2 3 4 5 6 7 8 9]
# 升级为二维数组
arr2 = arr1.reshape( (1,-1) )
print(arr2) #OUT:[[0 1 2 3 4 5 6 7 8 9]]
这里给.reshape( )传入的形状参数是(1,-1),正常情况下我们都肯定是(1,10),这里(1,-1)的含义是:行的参数是 1 行,列的参数-1 自己去除着算吧。
接着,演示将二维数组降级为一维数组。
# 创建二维数组
arr2 = np.arange(10).reshape(2,5)
print(arr2) #OUT:[[0 1 2 3 4] [5 6 7 8 9]]
# 降级为一维数组
arr1 = arr2.reshape( -1 )
print(arr1) #OUT:[0 1 2 3 4 5 6 7 8 9]
这里给.reshape( )传入的形状参数是 -1 ,正常情况应该是 10,这里-1 的含义是:反正是一维数组,形状直接自己去算吧。
PS:通常一维数组称为向量,二维数组称为矩阵
数组的创建
创建指定数组
当明确知道数组每一个元素的具体数值时,可以使用 np.array( ) 函数,将Python 列表转化为 NumPy 数组。
import numpy as np
# 创建一维数组——向量
arr1 = np.array( [1,2,3] )
print(arr1) #OUT:[1 2 3]
# 创建二维数组——行矩阵
arr2 = np.array( [ [1,2,3] ] )
print(arr2) #OUT:[[1 2 3]]
# 创建二维数组——列矩阵
arr3 = np.array( [ [1],[2],[3] ] )
print(arr3) #OUT:[[1]
[2]
[3]]
# 创建二维数组——矩阵
arr4 = np.array( [ [1,2,3],[4,5,6] ] )
print(arr4) #OUT:[[1 2 3]
[4 5 6]]
创建递增数组
递增数组使用 np.arange( ) 函数进行创建(arange 全称是 array_range)
import numpy as np
# 递增数组
arr1 = np.arange(10) # 从 0 开始,到 10 之前结束
print(arr1) #OUT:[0 1 2 3 4 5 6 7 8 9]
# 递增数组
arr2 = np.arange(10,20) # 从 10 开始,到 20 之前结束
print(arr2) #OUT:[10 11 12 13 14 15 16 17 18 19]
# 递增数组
arr3 = np.arange(1,21,2) # 从 1 开始,到 21 之前结束,步长为 2
print(arr3) #OUT:[ 1 3 5 7 9 11 13 15 17 19]
创建同值数组
需要创建同值数组时,使用 np.zeros( ) 函数以及 np.ones( ) 函数,如示例。
import numpy as np
# 全 0 数组
arr1 = np.zeros( 3 ) # 形状为 3 的向量
print(arr1) #OUT:[0. 0. 0.]
# 全 1 数组
arr2 = np.ones( (1,3) ) # 形状为(1,3)的矩阵
print(arr2) #OUT:[[1. 1. 1.]]
# 全 3.14 数组
arr3 = 3.14 * np.ones( (2,3) ) # 形状为(2,3)的矩阵
print(arr3) #OUT:[[3.14 3.14 3.14]
[3.14 3.14 3.14]]
示例中隐藏了一个细节——两个函数输出的并不是整数型的数组,这可能是为了避免插进去的浮点数被截断,所以将其设定为浮点型数组。
创建随机数组
有时需要创建随机数组,那么可以使用 np.random 系列函数,如示例所示。
import numpy as np
# 0-1 均匀分布的浮点型随机数组
arr1 = np.random.random( 5 ) # 形状为 5 的向量
print( arr1 ) #OUT:[0.59699399 0.89113584 0.00695752 0.49089431 0.32050609]
如果想创建 60-100 范围内均匀分布的 3 行 3 列随机数组,可输入 (100-60)*np.random.random( (3,3) ) + 60。
# 整数型随机数组
arr2 = np.random.randint( 10,100,(1,15) ) # 形状为(1,15)的矩阵
print( arr2 ) #OUT:[[78 35 47 50 28 17 10 73 84 54 95 51 27 81 31]]
该函数需要额外输入范围参数,本例中范围是 10-100。
# 服从正态分布的随机数组
arr3 = np.random.normal( 0,1,(2,3) ) # 形状为(2,3)的二维矩阵
print( arr3 ) #OUT:[[ 1.25383473 -1.90002476 -0.02013353]
[-0.24418935 -0.60632212 -0.70993194]]
PS:该函数需要额外输入正态参数,本例中均值为 0、标准差为 1,这种情况可直接使用 np.random.randn( )函数,只需要传入形状参数即可。
数组的索引
访问数组元素
与 Python 列表一致,访问 NumPy 数组元素时使用中括号,索引由 0 开始。
- 访问向量
import numpy as np
# 创建向量
arr1 = np.arange( 1,10 )
print( arr1 ) #OUT: [1 2 3 4 5 6 7 8 9]
# 访问元素
print( arr1[3] ) # 正着访问 #OUT:4
print( arr1[-1] ) # 倒着访问 #OUT:9
# 修改数组元素
arr1[3] = 100;
print( arr1 ) #OUT:[ 1 2 3 100 5 6 7 8 9]
- 访问矩阵
# 创建矩阵
arr2 = np.array( [ [1,2,3],[4,5,6] ] )
print(arr2) #OUT:[[1 2 3]
[4 5 6]]
# 访问元素
print( arr2[0,2] ) #OUT:3
print( arr2[1,-2] ) #OUT:5
# 修改元素
arr2[1,1] = 100.9
print( arr2 ) #OUT:[[ 1 2 3]
[ 4 100 6]]
在最后修改元素中,浮点数 100.9 插入到整数型数组时被截断了。
花式索引
上一小节访问单个元素时,向量用 arr1[x],矩阵用 arr2[x,y]。逗号在矩阵里用于区分行与列,这一小节,逗号新增一个功能,且不会与矩阵里的逗号混淆。普通索引用一层中括号,花式索引用两层中括号。
- 向量的花式索引
import numpy as np
# 创建向量
arr1 = np.arange(0,90,10)
print(arr1) #OUT:[ 0 10 20 30 40 50 60 70 80]
# 花式索引
print( arr1[ [0,2] ] ) #OUT:[ 0 20]
花式索引需要两层中括号,即[ [行] ]或[ [行] ,[列] ]
2. 矩阵的花式索引
# 创建矩阵
arr2 = np.arange(1,17).reshape(4,4)
print(arr2) #OUT:[[ 1 2 3 4]
[ 5 6 7 8]
[ 9 10 11 12]
[13 14 15 16]]
# 花式索引
print( arr2[ [0,1] , [0,1] ] ) #OUT:[1 6]
print( arr2[ [0,1,2] , [2,1,0] ] ) #OUT:[3 6 9]
# 修改数组元素
arr2[ [0,1,2,3] , [3,2,1,0] ] = 100
print(arr2) #OUT:[[ 1 2 3 100]
[ 5 6 100 8]
[ 9 100 11 12]
[100 14 15 16]]
根据以上实例,花式索引输出的仍然是一个向量
| 索引方式 | 向量 | 矩阵 |
|---|---|---|
| 普通索引 | arr1[x1] | arr2[x1,y1] |
| 花式索引 | arr1[x1,x2,x3,…,xn] | arr2[[x1,x2,x3,…xn],[y1,y2,y3,…ym]] |
访问数组切片
- 向量的切片
向量与列表切片的操作完全一致,因此本页内容在Python基础中均有涉及。
import numpy as np
arr1 = np.arange(10)
print( arr1 ) #OUT:[0 1 2 3 4 5 6 7 8 9]
print( arr1[ 1 : 4 ] ) #OUT:[1 2 3] 从索引[1]开始,切到索引[4]之前
print( arr1[ 1 : ] ) #OUT:[1 2 3 4 5 6 7 8 9] 从索引[1]开始,切到结
print( arr1[ : 4 ] ) #OUT:[0 1 2 3] 从数组开头开始,切到索引[4]之前
print( arr1 )
print( arr1[ 2 : -2 ] ) #OUT:[2 3 4 5 6 7] 切除开头 2 个和结尾 2 个
print( arr1[ 2 : ] ) #OUT:[2 3 4 5 6 7 8 9] 切除开头 2 个
print( arr1[ : -2 ] ) #OUT:[0 1 2 3 4 5 6 7] 切除结尾 2 个
print( arr1 )
print( arr1[ : : 2 ] ) #OUT:[0 2 4 6 8] 每 2 个元素采样一次
print( arr1[ : : 3 ] ) #OUT:[0 3 6 9] 每 3 个元素采样一次
print( arr1[1:-1:2]) #OUT:[1 3 5 7] 切除一头一尾后,每 2 个元素采样一次
- 矩阵的切片
arr2 = np.arange(1,21).reshape(4,5)
print( arr2 ) #OUT:[[ 1 2 3 4 5]
[ 6 7 8 9 10]
[11 12 13 14 15]
[16 17 18 19 20]]
print( arr2[ 1:3 , 1:-1 ] ) #OUT:[[ 7 8 9]
[12 13 14]]
print( arr2[ ::3 , ::2 ] ) #OUT:[[ 1 3 5]
[16 18 20]]
- 提取矩阵的行
基于矩阵的切片功能,我们可以提取其部分行,如示例所示。
arr3 = np.arange(1,21).reshape(4,5)
print(arr3) #OUT:[[ 1 2 3 4 5]
[ 6 7 8 9 10]
[11 12 13 14 15]
[16 17 18 19 20]]
print( arr3[ 2 , : ] ) #OUT:[11 12 13 14 15] 提取第 2 行
print( arr3[ 1:3 , : ] ) #OUT:[[ 6 7 8 9 10] 提取 1 至 2 行
[11 12 13 14 15]]
#考虑代码的简洁,当提取矩阵的某几行时可简写(但提取列的时候不可简写)。
print( arr3[ 2 , : ] ) #OUT:[11 12 13 14 15] 规范的提取行
print( arr3[2] ) #OUT:[11 12 13 14 15] 简便的提取行
所以,有时你可能看到诸如 arr[1][2] 这样的语法,不必吃惊,其实这只是先提取了第 1 行,再提取该行中第 2 个元素。
4. 提取矩阵的列
基于矩阵的切片功能,我们可以提取其部分行,如示例所示
import numpy as np
arr4 = np.arange(1,21).reshape(4,5)
print( arr4 ) #OUT:[[ 1 2 3 4 5]
[ 6 7 8 9 10]
[11 12 13 14 15]
[16 17 18 19 20]]
print( arr4[ : , 2 ] ) #OUT:[ 3 8 13 18] 提取第 2 列(注意,输出的是向量)
print( arr4[ : , 1:3 ] ) #OUT:[[ 2 3] 提取 1 至 2 列
[ 7 8]
[12 13]
[17 18]]
值得注意的是,提取某一个单独的列时,出来的结果是一个向量。其实这么做只是为了省空间,我们知道,列矩阵必须用两层中括号来存储,而形状为 1000的向量,自然比形状为(1000,1)的列矩阵更省空间(节约了 1000 对括号)。
如果你真的想要提取一个列矩阵出来,示例如下。
arr5 = np.arange(1,16).reshape(3,5)
print( arr5 ) #OUT:[[ 01 02 03 04 05]
[06 07 08 09 10]
[11 12 13 14 15]]
cut = arr5[ : , 2 ] # 提取第 2 列为向量
print( cut ) #OUT:[ 3 8 13]
cut = cut.reshape( (-1,1) ) # 升级为列矩阵
print(cut) #OUT:[[ 3]
[ 8]
[13]]
数组切片仅是视图
- 数组切片仅是视图
与 Python 列表和 Matlab 不同,NumPy 数组的切片仅仅是原数组的一个视图。换言之,NumPy 切片并不会创建新的变量,示例如下。
import numpy as np
arr = np.arange(10) #创建原数组 arr
print(arr) #OUT:[0 1 2 3 4 5 6 7 8 9]
cut = arr[ : 3 ] # 创建 arr 的切片 cut
print(cut) #OUT:[0 1 2]
cut[0] = 100 #对切片的数值进行修改
print(cut) #OUT:[100 1 2]
print(arr) #OUT:[100 1 2 3 4 5 6 7 8 9] 原数组也被修改
习惯 Matlab 的用户可能无法理解,但其实这正是 NumPy 的精妙之处。试想一下,一个几百万条数据的数组,每次切片时都创建一个新变量,势必造成大量的内存浪费。因此,NumPy 的切片被设计为原数组的视图是极好的。
深度学习中为节省内存,将多次使用 arr[:] =<表达式> 来替代 arr = <表达式>。
- 备份切片为新变量
如果真的需要为切片创建新变量(这种情况很稀少),使用 .copy( ) 方法。
import numpy as np
arr = np.arange(10) #创建原数组 arr
print(arr) #OUT:[0 1 2 3 4 5 6 7 8 9]
copy = arr[ : 3 ] .copy() # 创建 arr 的拷贝切片
print(copy) #OUT:[0 1 2]
copy [0] = 100 #对拷贝切片的数值进行修改
print(copy) #OUT:[100 1 2]
print(arr) #OUT:[0 1 2 3 4 5 6 7 8 9] 原数组不为所动
数组赋值仅是绑定
- 数组的赋值仅是绑定
与 NumPy 数组的切片一样,NumPy 数组完整的赋值给另一个数组,也只是绑定。换言之,NumPy 数组之间的赋值并不会创建新的变量,示例如下。
import numpy as np
arr1 = np.arange(10) # 创建一个 0 到 10 的数组变量 arr
print(arr1) #OUT:[0 1 2 3 4 5 6 7 8 9]
arr2 = arr1 # 把数组 1 赋值给另一个数组 2
print(arr2) #OUT:[0 1 2 3 4 5 6 7 8 9]
arr2[0] = 100 # 修改数组 2
print(arr2) #OUT:[100 1 2 3 4 5 6 7 8 9]
print(arr1) #OUT:[100 1 2 3 4 5 6 7 8 9] 原数组也被修改
此特性的出现仍然是为了节约空间,破局的方法仍然与前面相同。
2. 复制数组为新变量
如果真的需要赋给一个新数组,使用 .copy( ) 方法。
import numpy as np
arr1 = np.arange(10) #创建一个 0 到 10 的数组变量 arr
print(arr1) #OUT:[0 1 2 3 4 5 6 7 8 9]
arr2 = arr1.copy() # 把数组 1 的拷贝赋值给另一个数组 2
print(arr2) #OUT:[0 1 2 3 4 5 6 7 8 9]
arr2[0] = 100 # 修改数组 2
print(arr2) #OUT:[100 1 2 3 4 5 6 7 8 9]
print(arr1) #OUT:[0 1 2 3 4 5 6 7 8 9]
数组变形
数组的转置
数组的转置方法为 .T,其只对矩阵有效,因此遇到向量要先将其转化为矩阵。
- 向量的转置
import numpy as np
arr1 = np.arange(1,4) # 创建向量
print(arr1) #OUT:[1 2 3]
arr2 = arr1.reshape( (1,-1) ) # 升级为矩阵
print(arr2) #OUT:[[1 2 3]]
arr3 = arr2.T # 行矩阵的转置
print(arr3) #OUT:[[1]
[2]
[3]]
- 矩阵的转置
arr1 = np.arange(4).reshape(2,2) # 创建矩阵
print(arr1) #OUT:[[0 1]
[2 3]]
arr2 = arr1.T # 矩阵的转置
print(arr2) #OUT:[[0 2]
[1 3]]
数组的翻转
数组的翻转方法有两个,一个是上下翻转的 np.flipud( ) ,表示 up-down;一个是左右翻转的 np.fliplr( ),表示 left-right。其中,向量只能使用 np.flipud( ),在数学中,向量并不是横着排的,而是竖着排的。
- 向量的翻转
import numpy as np
# 创建向量
arr1 = np.arange(10)
print( arr1 ) #OUT:[0 1 2 3 4 5 6 7 8 9]
# 翻转向量
arr_ud = np.flipud(arr1)
print( arr_ud ) #OUT:[9 8 7 6 5 4 3 2 1 0]
- 矩阵的翻转
# 创建矩阵
arr2 = np.arange(1,21).reshape(4,5)
print( arr2 ) #OUT:[[ 1 2 3 4 5]
[ 6 7 8 9 10]
[11 12 13 14 15]
[16 17 18 19 20]]
# 左右翻转
arr_lr = np.fliplr(arr2)
print( arr_lr ) #OUT:[[ 5 4 3 2 1]
[10 9 8 7 6]
[15 14 13 12 11]
[20 19 18 17 16]]
# 上下翻转
arr_ud = np.flipud(arr2)
print( arr_ud ) #OUT:[[16 17 18 19 20]
[11 12 13 14 15]
[ 6 7 8 9 10]
[ 1 2 3 4 5]]
数组的重塑
想要重塑数组的形状,需要用到 .reshape( ) 方法。
前面说过,给定了其他维度的数值,剩下一个维度可以填-1,让它自己去计算。比如把一个 5 行 6 列的矩阵重塑为 3 行 10 列的矩阵,当列的参数 10 告诉它,行的参数直接可以用-1 来替代,它会自己去用 30 除以 10 来计算。
- 向量的变形
import numpy as np
arr1 = np.arange(1,10) # 创建向量
print(arr1) #OUT:[1 2 3 4 5 6 7 8 9]
arr2 = arr1.reshape(3,3) # 变形为矩阵
print(arr2) #OUT:[[1 2 3]
[4 5 6]
[7 8 9]]
- 矩阵的变形
arr1 = np.array( [ [1,2,3],[4,5,6] ] ) # 创建矩阵
print(arr1) #OUT:[[1 2 3]
[4 5 6]]
arr2 = arr1.reshape(6) # 变形为向量
print(arr2) #OUT:[1 2 3 4 5 6]
arr3 = arr1.reshape(1,6) # 变形为矩阵
print(arr3) #OUT:[[[1 2 3 4 5 6]]
数组的拼接
- 向量的拼接
两个向量拼接,将得到一个新的加长版向量。
import numpy as np
# 创建向量 1
arr1 = np.array( [1,2,3] )
print(arr1) #OUT:[1 2 3]
# 创建向量 2
arr2 = np.array( [4,5,6] )
print(arr2) #OUT:[4 5 6]
# 拼接
arr3 = np.concatenate( [arr1,arr2] )
print(arr3) #OUT:[1 2 3 4 5 6]
- 矩阵的拼接
两个矩阵可以按不同的维度进行拼接,但拼接时必须注意维度的吻合。
import numpy as np
# 创建数组 1
arr1 = np.array( [[1,2,3],[ 4,5,6]] )
print(arr1) #OUT:[[1 2 3]
[4 5 6]]
# 创建数组 2
arr2 = np.array( [[7,8,9],[10,11,12]] )
print(arr2) #OUT:[[ 7 8 9]
[10 11 12]]
# 按第一个维度(行)拼接
arr3 = np.concatenate( [arr1,arr2] ) # 默认参数 axis=0
print(arr3) #OUT:[[ 1 2 3]
[ 4 5 6]
[ 7 8 9]
[10 11 12]]
# 按第二个维度(列)拼接
arr4 = np.concatenate( [arr1,arr2] ,axis=1 )
print(arr4) #OUT:[[ 1 2 3 7 8 9]
[ 4 5 6 10 11 12]]
最后要说明的是,向量和矩阵不能进行拼接,必须先把向量升级为矩阵。
数组的分裂
- 向量的分裂
向量分裂,将得到若干个更短的向量。
import numpy as np
# 创建向量
arr = np.arange(10,100,10)
print(arr) #OUT:[10 20 30 40 50 60 70 80 90]
# 分裂数组
arr1,arr2,arr3 = np.split( arr , [2,8] )
print(arr1) #OUT:[10 20]
print(arr2) #OUT:[30 40 50 60 70 80]
print(arr3) #OUT:[90]
np.split()函数中,给出的第二个参数[2,8]表示在索引[2]和索引[8]的位置截断。
2. 矩阵的分裂
import numpy as np
# 创建矩阵
arr = np.arange(1,9).reshape(2,4)
print(arr) # OUT: [[1 2 3 4]
#
# [5 6 7 8]]
# 按第一个维度(行)分裂
arr1,arr2 = np.split(arr,[1]) # 默认参数 axis=0
print(arr1 , '\n\n' , arr2) # OUT: [[1 2 3 4]]
# [[5 6 7 8]] 注意输出的是矩阵
# 按第二个维度(列)分裂
arr1,arr2,arr3=np.split(arr,[1,3],axis=1)
print(arr1,'\n\n',arr2,'\n\n',arr3) #[[1]
#[5]]
#[[2 3]
#[6 7]]
#[[4]
#[8]]
数组的运算
数组与系数之间的运算
Python 基础中,常用的运算符如表 5-1 所示,NumPy 的运算符与之相同。
| 运算符 | 含义 | 输入 | 输出 |
|---|---|---|---|
| +,-,*,/ | 加,减,乘,除 | 1*2+3/4 | 2.75 |
| ** | 幂 | 2**4 | 16 |
| () | 修正运算次序 | 1*(2+3)/4 | 1.25 |
| // | 取整 | 28//5 | 5 |
| % | 取余 | 28%5 | 3 |
这里仅以矩阵为例,向量与系数的操作与之相同。
import numpy as np
# 创建矩阵
arr = np.arange(1,9).reshape(2,4)
print( arr ) #OUT:[[1 2 3 4][5 6 7 8]]
print( arr + 10 ) #OUT:[[11 12 13 14][15 16 17 18]] 加法
print( arr - 10 ) #OUT:[[-9 -8 -7 -6][-5 -4 -3 -2]] 减法
print( arr * 10 ) #OUT:[[10 20 30 40][50 60 70 80]] 乘法
print( arr / 10 ) #OUT:[[0.1 0.2 0.3 0.4][0.5 0.6 0.7 0.8]] 除法
print( arr ** 2) #OUT:[[ 1 4 9 16][25 36 49 64]] 平方
print( arr // 6) #OUT:[[0 0 0 0][0 1 1 1]] 取整
print( arr % 6) #OUT:[[1 2 3 4][5 0 1 2]] 取余
数组与数组之间的运算
同维度数组间的运算即对应元素之间的运算,这里仅以矩阵为例,向量与向量的操作与之相同。
import numpy as np
# 创建矩阵
arr1 = np.arange(-1,-9,-1).reshape(2,4)
arr2 = -arr1
print( arr1 ) #OUT:[[-1 -2 -3 -4][-5 -6 -7 -8]]
print( arr2 ) #OUT:[[1 2 3 4][5 6 7 8]]
print( arr1 + arr2 ) #OUT:[[0 0 0 0][0 0 0 0]] 加法
print( arr1 - arr2 ) #OUT:[[ -2 -4 -6 -8][-10 -12 -14 -16]] 减法
print( arr1 * arr2 ) #OUT:[[ -1 -4 -9 -16][-25 -36 -49 -64]] 乘法
print( arr1 / arr2 ) #OUT:[[-1. -1. -1. -1.][-1. -1. -1. -1.]] 除法
print( arr1 ** arr2) #OUT:[[-1 4 -27 256][-3125 46656 -823543 16777216]] 幂方
PS:乘法是对应元素相乘那么如何个实现线代中的矩阵乘法运算呢?在后文会提及。
广播
上一小节是同形状数组之间的逐元素运算,本节讲解不同形状的数组之间的运算(仅讨论二维数组之内的情况,其他可类推),不同形状的数组之间的运算有以下规则:
- 如果是向量与矩阵之间做运算,向量自动升级为行矩阵
- 如果某矩阵是行矩阵或列矩阵,则其被广播,以适配另一个矩阵的形状。
- 向量被广播
当一个形状为(x,y)的矩阵与一个向量做运算时,要求该向量的形状必须为 y,运算时向量会自动升级成形状为(1,y)的行矩阵,该形状为(1,y)的行矩阵再自动被广播为形状为(x,y)的矩阵,这样就与另一个矩阵的形状适配了。
import numpy as np
# 向量
arr1 = np.array([-100,0,100])
print(arr1) #OUT:[-100 0 100]
# 矩阵
arr2 = np.random.random( (10,3) )
print(arr2) #OUT:[[0.26592484 0.53767196 0.86381747]
# [0.90687228 0.78514476 0.70691145]
# [0.60496898 0.11755883 0.60226676]
# [0.07044602 0.01873469 0.73172121]
# [0.29854992 0.83437886 0.9719179 ]
# [0.95204343 0.10850882 0.10503768]
# [0.18581981 0.32118187 0.07710718]
# [0.35619064 0.63949164 0.49440055]
# [0.07840112 0.55260857 0.68193771]
# [0.90238687 0.72680633 0.27281749]]
# 广播
print(arr1*arr2) #OUT:[[-26.59248403 0. 86.38174731]
# [-90.68722796 0. 70.69114477]
# [-60.4968978 0. 60.22667606]
# [ -7.04460178 0. 73.17212139]
# [-29.85499248 0. 97.19179009]
# [-95.20434269 0. 10.50376786]
# [-18.58198146 0. 7.71071833]
# [-35.61906385 0. 49.44005483]
# [ -7.84011193 0. 68.19377136]
# [-90.23868668 0. 27.28174897]]
- 列矩阵被广播
当一个形状为(x,y)的矩阵与一个列矩阵做运算时,要求该列矩阵的形状必须为(x,1),该形状为(x,1)的列矩阵再自动被广播为形状为(x,y)的矩阵,这样就与另一个矩阵的形状适配了。
import numpy as np
# 列矩阵
arr1 = np.arange(3).reshape(3,1)
print(arr1) #OUT:[[0]
# [1]
# [2]]
# 矩阵
arr2 = np.ones( (3,5) )
print(arr2) #OUT:[[1. 1. 1. 1. 1.]
# [1. 1. 1. 1. 1.]
# [1. 1. 1. 1. 1.]]
# 广播
print(arr1*arr2) #OUT:[[0. 0. 0. 0. 0.]
# [1. 1. 1. 1. 1.]
# [2. 2. 2. 2. 2.]]
- 行矩阵和列矩阵同时被广播
当一个形状为(1,y)的行矩阵与一个形状为(x,1) 的列矩阵做运算时,这俩矩阵都会被自动广播为形状为(x,y)的矩阵,这样就互相适配了。
import numpy as np
# 向量
arr1 = np.arange(3)
print(arr1) #OUT:[0 1 2]
# 列矩阵
arr2 = np.arange(3).reshape(3,1)
print(arr2) #OUT:[[0]
# [1]
# [2]]
# 广播
print(arr1*arr2) #OUT:[[0 0 0]
# [0 1 2]
# [0 2 4]]
数组的函数
矩阵乘积
- 上一章中的乘法都是“逐元素相乘”,这里介绍线性代数中的矩阵乘积,本节只需要使用 np.dot( ) 函数。
- 当矩阵乘积中混有向量时,根据需求,其可充当行矩阵,也可充当列矩阵,
但混有向量时输出结果必为向量。
- 向量与向量的乘积
import numpy as np
# 创建向量
arr1 = np.arange(5)
arr2 = np.arange(5)
print(arr1) #OUT:[0 1 2 3 4]
print(arr2) #OUT:[0 1 2 3 4]
# 矩阵乘积
print( np.dot(arr1,arr2) ) #OUT:30
- 向量与矩阵的乘积
import numpy as np
# 创建数组
arr1 = np.arange(5)
arr2 = np.arange(15).reshape(5,3)
print(arr1) #OUT:[0 1 2 3 4]
print(arr2) #OUT:[[ 0 1 2]
# [ 3 4 5]
# [ 6 7 8]
# [ 9 10 11]
# [12 13 14]]
# 矩阵乘积
print( np.dot(arr1,arr2) ) #OUT:[ 90 100 110]
- 矩阵与向量的乘积
import numpy as np
# 创建数组
arr1 = np.arange(15).reshape(3,5)
arr2 = np.arange(5)
print(arr1) #OUT:[[ 0 1 2 3 4]
# [ 5 6 7 8 9]
# [10 11 12 13 14]]
print(arr2) #OUT:[0 1 2 3 4]
# 矩阵乘积
print( np.dot(arr1,arr2)) #OUT:[ 30 80 130]
- 矩阵与矩阵的乘积
import numpy as np
# 创建数组 1
arr1 = np.arange(10).reshape(5,2)
print(arr1) #OUT:[[0 1]
# [2 3]
# [4 5]
# [6 7]
# [8 9]]
# 创建数组 2
arr2 = np.arange(16).reshape(2,8)
print(arr2) #OUT:[[ 0 1 2 3 4 5 6 7]
# [ 8 9 10 11 12 13 14 15]]
# 矩阵乘积
print( np.dot(arr1,arr2)) #OUT:[[ 8 9 10 11 12 13 14 15]
# [ 24 29 34 39 44 49 54 59]
# [ 40 49 58 67 76 85 94 103]
# [ 56 69 82 95 108 121 134 147]
# [ 72 89 106 123 140 157 174 191]]
数学函数
NumPy 设计了很多数学函数,这里列举其中最重要、最常见的几个。
import numpy as np
# 绝对值函数
arr_v = np.array( [-10, 0, 10] )
abs_v = np.abs(arr_v)
print( '原数组是:' , arr_v ) #OUT:原数组是: [-10 0 10]
print( '绝对值是:' , abs_v ) #OUT:绝对值是: [10 0 10]
# 三角函数
theta = np.arange(3) * np.pi / 2
sin_v = np.sin(theta)
cos_v = np.cos(theta)
tan_v = np.tan(theta)
print( '原数组是:' , theta ) #OUT:原数组是: [0. 1.57079633 3.14159265]
print( '正弦值是:' , sin_v ) #OUT:正弦值是: [0.0000000e+00 1.0000000e+00 1.2246468e-16]
print( '余弦值是:' , cos_v ) #OUT:余弦值是: [ 1.000000e+00 6.123234e-17 -1.000000e+00]
print( '正切值是:' , tan_v ) #OUT:正切值是: [ 0.00000000e+00 1.63312394e+16 -1.22464680e-16]
# 指数函数
x = np.arange(1,4)
print( 'x =' , x ) #OUT:x = [1 2 3]
print( 'e^x =' , np.exp(x) ) #OUT:e^x = [ 2.71828183 7.3890561 20.08553692]
print( '2^x =' , 2**x ) #OUT:2^x = [2 4 8]
print( '10^x = ' , 10**x ) #OUT:10^x = [ 10 100 1000]
# 对数函数
x = np.array( [1,10,100,1000] )
print( 'x =' , x ) #OUT:x = [ 1 10 100 1000]
print( 'ln(x) =' , np.log(x) ) #OUT:ln(x) = [0. 2.30258509 4.60517019 6.90775528]
print( 'log2(x) =' , np.log(x) / np.log(2) ) #OUT:log2(x) = [0. 3.32192809 6.64385619 9.96578428] 换底公式
print( 'log10(x) =' , np.log(x) / np.log(10) ) #OUT:log10(x) = [0. 1. 2. 3.] 换底公式
聚合函数
聚合很有用,这里用矩阵演示。向量与之一致,但没有 axis 参数。以下在注释中介绍了 6 个最重要的聚合函数,其用法完全一致,仅演示其中 3 个。
import numpy as np
# 最大值函数 np.max( )与最小值函数 np.min( )
arr = np.random.random((2,3))
print( arr ) #OUT:[[0.43309379 0.12401625 0.04624397]
# [0.47370456 0.74120719 0.57005044]]
print( '按维度一求最大值:' , np.max(arr,axis=0)) #OUT:按维度一求最大值: [0.47370456 0.74120719 0.57005044]
print( '按维度二求最大值:' , np.max(arr,axis=1)) #OUT:按维度二求最大值: [0.43309379 0.74120719]
print( '整体求最大值:' , np.max(arr) ) #OUT:整体求最大值: 0.7412071889150869
# 求和函数 np.sum( )与求积函数 np.prod( )
arr = np.arange(10).reshape(2,5)
print( arr ) #OUT:[[0 1 2 3 4]
# [5 6 7 8 9]]
print( '按维度一求和:' , np.sum(arr,axis=0) ) #OUT:按维度一求和: [ 5 7 9 11 13]
print( '按维度二求和:' , np.sum(arr,axis=1) ) #OUT:按维度二求和: [10 35]
print( '整体求和:' , np.sum(arr) ) #OUT:整体求和: 45
# 均值函数 np.mean( )与标准差函数 np.std( )
arr = np.arange(10).reshape(2,5)
print( arr ) #OUT:[[0 1 2 3 4]
# [5 6 7 8 9]]
print( '按维度一求平均:' , np.mean(arr,axis=0) ) #OUT:按维度一求平均: [2.5 3.5 4.5 5.5 6.5]
print( '按维度二求平均:' , np.mean(arr,axis=1) ) #OUT:按维度二求平均: [2. 7.]
print( '整体求平均:' , np.mean(arr) ) #OUT:整体求平均: 4.5
- 当 axis=0 时,最终结果与每一行的元素个数一致;
当 axis=1 时,最终结果与每一列的元素个数一致。 - 考虑到大型数组难免有缺失值,以上聚合函数碰到缺失值时会报错,因此出现了聚合函数的安全版本,即计算时忽略缺失值:np.nansum( )、np.nanprod( ) 、np.nanmean( )、np.nanstd( )、np.nanmax( )、np.nanmin( )。
布尔型数组
除了整数型数组和浮点型数组,还有一种有用的数组类型——布尔型数组。
创建布尔型数组
由于 NumPy 的主要数据类型是整数型数组或浮点型数组,因此布尔型数组的产生离不开:大于>、大于等于>=、等于==、不等号!=、小于<、小于等于<=。
import numpy as np
# 数组与系数作比较,以产生布尔型数组,示例如下
# 创建数组
arr = np.arange(1,7).reshape(2,3)
print(arr) #OUT:[[1 2 3]
# [4 5 6]]
# 数组与数字作比较
print( arr >= 4 ) #OUT:[[False False False]
# [ True True True]]
# 同维数组作比较,以产生布尔型数组,示例如下
import numpy as np
# 创建同维数组
arr1 = np.arange(1,6)
arr2 = np.flipud(arr1)
print(arr1) #OUT:[1 2 3 4 5]
print(arr2) #OUT:[5 4 3 2 1]
# 同维度数组作比较
print( arr1 > arr2) #OUT:[False False False True True]
还可以同时比较多个条件。Python 基础里,同时检查多个条件使用的与、或、非是 and、or、not。但 NumPy 中使用的与、或、非是 & 、 | 、 ~ 。
import numpy as np
# 创建数组
arr = np.arange(1,10)
print(arr) #OUT:[1 2 3 4 5 6 7 8 9]
# 多个条件
print((arr < 4)|(arr > 6)) #OUT:[ True True True False False False True True True]
布尔型数组中True的数量
有三个关于 Ture 数量的有用函数,分别是 np.sum( )、np.any( )、np.all( )。
- np.sum( ):统计布尔型数组里 True 的个数。示例如下。
import numpy as np
# 创建一个形状为 10000 的标准正态分布数组
arr = np.random.normal( 0,1,10000 )
# 统计该分布中绝对值小于 1 的元素个数
num = np.sum( np.abs(arr) < 1 )
print( num ) #OUT:6827
np.abs(arr) <1 可以替换为 (arr>-1) & (arr<1) 。此外,最终统计的数量为 6814,其概率近似为 0.6827,这符合统计学中的 3σ 准则。
- np.any( ):只要布尔型数组里含有一个及其以上的 True,就返回 True。
import numpy as np
# 创建同维数组
arr1 = np.arange(1,10)
arr2 = np.flipud(arr1)
print(arr1) #OUT:[1 2 3 4 5 6 7 8 9]
print(arr2) #OUT:[9 8 7 6 5 4 3 2 1]
# 统计这两个数组里是否有共同元素
print( np.any( arr1 == arr2 ) ) #OUT:True
从结果来看,arr1 与 arr2 里含有共同元素,那就是 5。
- np.all( ):当布尔型数组里全是 True 时,才返回 True,示例如下。
import numpy as np
# 模拟英语六级的成绩,创建 100000 个样本
arr = np.random.normal( 500,70,100000 )
# 判断是否所有考生的分数都高于 250
print( np.all( arr > 250 ) ) #OUT:False
布尔型数组作为掩码
若一个普通数组和一个布尔型数组的维度相同,可以将布尔型数组作为普通数组的掩码,这样可以对普通数组中的元素作筛选。给出两个示例。
import numpy as np
# 创建数组
arr = np.arange(1,13).reshape(3,4)
print(arr)) #OUT:[[ 1 2 3 4]
# [ 5 6 7 8]
# [ 9 10 11 12]]
# 数组与数字作比较
print( arr > 4 )) #OUT:[[False False False False]
# [ True True True True]
# [ True True True True]]
# 筛选出 arr > 4 的元素
print( arr[ arr > 4 ] )) #OUT:[ 5 6 7 8 9 10 11 12]
注意,这个矩阵进行掩码操作后,退化为了向量。
第二个示例,筛选出数组逐元素比较的结果。
import numpy as np
# 创建同维数组
arr1 = np.arange(1,10)
arr2 = np.flipud(arr1)
print(arr1) #OUT:[1 2 3 4 5 6 7 8 9]
print(arr2) #OUT:[9 8 7 6 5 4 3 2 1]
# 同维度数组作比较
print( arr1 > arr2) #OUT:[False False False False False True True True True]
# 筛选出 arr1 > arr2 位置上的元素
print( arr1[ arr1 > arr2 ] ) #OUT:[6 7 8 9]
print( arr2[ arr1 > arr2 ] ) #OUT:[4 3 2 1]
满足条件的元素所在位置
现在我们来思考一种情况:假设一个很长的数组,我想知道满足某个条件的元素们所在的索引位置,此时使用 np.where( )函数。
import numpy as np
# 模拟英语六级成绩的随机数组,取 10000 个样本
arr = np.random.normal( 500,70,1000 )
# 找出六级成绩超过 650 的元素所在位置
print( np.where( arr > 650 ) ) #OUT:(array([ 21, 40, 318, 336, 338, 513, 659, 667, 725, 743, 772, 933, 946],dtype=int64),)
# 找出六级成绩最高分的元素所在位置
print( np.where( arr == np.max(arr) ) ) #OUT:(array([338], dtype=int64),)
np.where( )函数的输出看起来比较怪异,它是输出了一个元组。元组第一个元素是“满足条件的元素所在位置”;第二个元素是数组类型,可忽略掉。
print( np.where( arr > 650 )[0] ) #OUT:[ 21 40 318 336 338 513 659 667 725 743 772 933 946]
print(np.where(arr == np.max(arr))[0]) #OUT:[338]
从数组到张量
数组与张量
- PyTorch 作为当前首屈一指的深度学习库,其将 NumPy 的语法尽数吸收,作为自己处理数组的基本语法,且运算速度从使用 CPU 的数组进步到使用 GPU 的张量。
- NumPy 和 PyTorch 的基础语法几乎一致,具体表现为:
np 对应 torch;
数组 array 对应张量 tensor;
NumPy 的 n 维数组对应着 PyTorch 的 n 阶张量。 - 数组与张量之间可以相互转换:
数组 arr 转为张量 ts:ts = torch.tensor(arr);
张量 ts 转为数组 arr:arr = np.array(ts)。
语法的不同点(未写出的完全一样)
| 章节 | NumPy函数 | PyTorch函数 | 用法区别 |
|---|---|---|---|
| 1.1 数据类型 | .astype( ) | .type( ) | 无 |
| 2.4 随机数组 | np.random.random( ) | torch.rand( ) | 无 |
| 2.4 随机数组 | np.random.randint( ) | torch.randint( ) | 不接纳一维张量 |
| 2.4 随机数组 | np.random.normal( ) | torch.normal( ) | 不接纳一维张量 |
| 2.4 随机数组 | np.random.randn( ) | torch.randn( ) | 无 |
| 3.4 数组切片 | .copy( ) | .clone( ) | 无 |
| 4.4 数组拼接 | np.concatenate( ) | torch.cat( ) | 无 |
| 4.5 数组分裂 | np.split( ) | torch.split( ) | 参数含义优化 |
| 6.1 矩阵乘积 | np.dot( ) | torch.dot( ) | 无 |
| 6.1 矩阵乘积 | np.dot(v,v) | torch.dot( ) | 无 |
| 6.1 矩阵乘积 | np.dot(m,v) | torch.mv( ) | 无 |
| 6.1 矩阵乘积 | np.dot(m,m) | torch.mm( ) | 无 |
| 6.2 数学函数 | np.exp( ) | torch.exp( ) | 必须传入张量 |
| 6.2 数学函数 | np.log( ) | torch.log( ) | 必须传入张量 |
| 6.3 聚合函数 | np.mean( ) | torch.mean( ) | 必须传入浮点型张量 |
| 6.3 聚合函数 | np.std( ) | torch.std( ) | 必须传入浮点型张量 |
3711

被折叠的 条评论
为什么被折叠?



