Numpy
数组基础
数组类型
import numpy as np
(1)整数型数组与浮点型数组
为克服列表的缺点,一个 NumPy 数组只容纳一种数据类型,以节约内存。
为方便起见,可将 NumPy 数组简单分为整数型数组与浮点型数组。
# 整数型数组
arr1 = np.array( [1, 2, 3] )
print(arr1)
[1 2 3]
# 创建浮点型数组
arr2 = np.array( [1.0, 2, 3] ) # 内含浮点数,则为浮点型数组
print(arr2)
[1. 2. 3.]
(2)同化定理
- 一个人的力量是无法改变全体的,在实际操作中要注意:
- 往整数型数组里插入浮点数,该浮点数会自动被截断为整数;
- 往浮点型数组里插入整数,该整数会自动升级为浮点数;
# 整数型数组
arr1 = np.array( [1, 2, 3] )
arr1[0] = 100.9 # 插入浮点数,被截断,数组仍为整数型
print(arr1)
[100 2 3]
# 浮点型数组
arr2 = np.array( [1.0, 2, 3] )
arr2[1] = 10 # 插入整数型,被升级,数组仍为浮点型
print(arr2)
[ 1. 10. 3.]
(3)共同改变定理
同化定理告诉我们,整数型数组和浮点型数组之间的界限十分严格,那么如何将这两种数据类型的数组进行相互转化呢?既然某一个人容易被集体所同化,那只要全体共同改变,自然就可以成功。
整数型数组和浮点型数组相互转换,规范的方法是使用 .astype() 方法。
# 整数型数组
arr1 = np.array( [1, 2, 3] )
print(arr1)
[1 2 3]
# 整数型数组 ——> 浮点型数组
arr2 = arr1.astype(float)
print(arr2)
[1. 2. 3.]
# 浮点型数组 ——> 整数型数组
arr3 = arr2.astype(int)
print(arr3)
[1 2 3]
除了上述方法,只要满足共同改变定理,整数型数组和浮点型数组仍然可以互相转换。最常见的是整数型数组在运算过程中升级为浮点型数组,示例如下。
不经意间的转化。整数型数组很好升级,但浮点型数组在运算过程中一般不会降级。
# 整数型数组
arr = np.array( [1, 2, 3] )
print(arr)
[1 2 3]
# 整数型数组与浮点数做运算
print( arr + 0.0 )
print( arr * 1.0 )
[1. 2. 3.]
[1. 2. 3.]
# 整数型数组遇到除法(即便是除以整数)
print( arr / 1 )
[1. 2. 3.]
# 验证了一下弹幕说的整除,确实无变化,仍然是整数
print( arr // 1 )
[1 2 3]
# 整数型数组与浮点型数组做运算
int_arr = np.array( [1, 2, 3] )
float_arr = np.array( [1.0, 2, 3] )
print( int_arr + float_arr )
[2. 4. 6.]
数组维度
区分数组维数是看有几层中括号
现在以同一个序列进行举例
- 当数组有 1 层中括号,如
[1 2 3],则其为一维数组,其形状是 3 或 (3,) ; - 当数组有 2 层中括号,如
[[1 2 3]],则其为二维数组,其形状是 (1,3) ; - 当数组有 3 层中括号,如
[[[1 2 3]]],则其为三维数组,其形状是 (1,1,3) ;
可以看到形状是从最外层逐渐向内层研究的。
arr1 = np.ones(3) # 传入形状 3
print(arr1) # 造出一维数组
[1. 1. 1.]
arr2 = np.ones((1,3)) # 传入形状(1,3)
print(arr2) # 造出二维数组
[[1. 1. 1.]]
arr3 = np.ones((1,1,3)) # 传入形状(1,1,3)
print(arr3) # 造出三维数组
[[[1. 1. 1.]]]
print( arr1.shape )
print( arr2.shape )
print( arr3.shape )
(3,)
(1, 3)
(1, 1, 3)
大家可以随时留意一下数组的维度(通过中括号的数量),后面有些函数(比如数组的拼接函数)需要两个数组是同维度的。
不同维度数组之间的转换
一维数组转二维数组,还是二维数组转一维数组,均要使用的是数组的重塑方法 .reshape(),该方法需要传入重塑后的形状(shape)参数。
这个方法神奇的是,给定了其他维度的数值,剩下一个维度可以填-1,让它自己去计算。比如把一个 5 行 6 列的矩阵重塑为 3 行 10 列的矩阵,当列的参数 10 告诉它,行的参数直接可以用 -1 来替代,它会自己去用 30 除以 10 来计算。
# 创建一维数组
arr1 = np.arange(10)
print(arr1)
[0 1 2 3 4 5 6 7 8 9]
# 升级为二维数组
arr2 = arr1.reshape( (1,-1) )
print(arr2)
[[0 1 2 3 4 5 6 7 8 9]]
# 接着,演示将二维数组降级为一维数组。
# 创建二维数组
arr2 = np.arange(10).reshape(2,5)
print(arr2)
[[0 1 2 3 4]
[5 6 7 8 9]]
# 降级为一维数组
arr1 = arr2.reshape( -1 )
print(arr1)
[0 1 2 3 4 5 6 7 8 9]
现规定,本讲义中,将一维数组称为向量,二维数组称为矩阵。
数组的创建
创建指定数组
当明确知道数组每一个元素的具体数值时,可以使用 np.array() 函数,将 Python 列表转化为 NumPy 数组。
# 创建一维数组——向量
arr1 = np.array( [1,2,3] )
print(arr1)
[1 2 3]
# 创建二维数组——行矩阵
arr2 = np.array( [ [1,2,3] ] )
print(arr2)
[[1 2 3]]
# 创建二维数组——列矩阵
arr3 = np.array( [ [1],[2],[3] ] )
print(arr3)
[[1]
[2]
[3]]
# 创建二维数组——矩阵
arr4 = np.array( [ [1,2,3],[4,5,6] ] )
print(arr4)
[[1 2 3]
[4 5 6]]
创建递增矩阵
递增数组使用 np.arange() 函数进行创建(arange 全称是 array_range)。
# 递增数组
arr1 = np.arange(10) # 从 0 开始,到 10 之前结束
print(arr1)
[0 1 2 3 4 5 6 7 8 9]
# 递增数组
arr2 = np.arange(10,20) # 从 10 开始,到 20 之前结束
print(arr2)
[10 11 12 13 14 15 16 17 18 19]
# 递增数组
arr3 = np.arange(1,21,2) # 从 1 开始,到 21 之前结束,步长为 2
print(arr3)
[ 1 3 5 7 9 11 13 15 17 19]
创建同值数组
需要创建同值数组时,使用 np.zeros() 函数以及 np.ones() 函数。
# 全 0 数组
arr1 = np.zeros( 3 ) # 形状为 3 的向量
print(arr1)
[0. 0. 0.]
# 全 1 数组
arr2 = np.ones( (1,3) ) # 形状为(1,3)的矩阵
print(arr2)
[[1. 1. 1.]]
# 全 3.14 数组
arr3 = 3.14 * np.ones( (2,3) ) # 形状为(2,3)的矩阵
print(arr3)
[[3.14 3.14 3.14]
[3.14 3.14 3.14]]
示例中隐藏了一个细节——两个函数输出的并不是整数型的数组,这可能是为了避免插进去的浮点数被截断,所以将其设定为浮点型数组。
创建随机数组
有时需要创建随机数组,那么可以使用 np.random 系列函数。
# 0-1 均匀分布的浮点型随机数组
arr1 = np.random.random( 5 ) # 形状为 5 的向量
print( arr1 )
[0.17593128 0.60237827 0.56871741 0.40539453 0.07398691]
# 创建60-100范围内均匀分布的3行3列随机数组
print((100-60) * np.random.random( (3,3) ) + 60)
[[70.12378428 74.65833113 77.79110921]
[63.32638725 78.70237273 98.82988596]
[78.42306248 73.99888889 97.39609976]]
# 创建60-100范围内均匀分布的3行3列随机数组
print(40 * np.random.random( (3,3) ) + 60)
[[66.53729126 90.07350472 76.49816862]
[71.12735087 67.4131509 99.96389701]
[77.59699272 80.88787569 95.05363915]]
# 整数型随机数组,randint函数需要额外输入范围参数,本例中范围是 10-100。
arr2 = np.random.randint( 10,100,(1,15) ) # 形状为(1,15)的矩阵
print( arr2 )
[[35 23 48 90 61 89 61 97 32 13 42 38 79 82 42]]
# 服从正态分布的随机数组
arr3 = np.random.normal( 0,1,(2,3) ) # 形状为(2,3)的二维矩阵
print( arr3 )
[[ 0.17390151 0.36271589 -0.44114682]
[ 1.37977189 -0.035863 -0.4298722 ]]
# 服从正态分布的随机数组,该函数需要额外输入正态参数
# 本例中均值为 0、标准差为 1,这种情况可直接使用 np.random.randn( )函数,只需要传入形状参数即可。
arr3 = np.random.normal( 0,1,(2,3) ) # 形状为(2,3)的二维矩阵
print( arr3 )
[[-0.63295435 1.43790517 -1.78283098]
[ 0.19547158 -0.41980791 -0.74568034]]
数组的索引
前面我们规定,将一维数组称为向量,将二维数组称为矩阵。
访问数组元素
与 Python 列表一致,访问 NumPy 数组元素时使用中括号,索引由 0 开始。
访问向量
# 创建向量
arr1 = np.arange( 1,10 )
print( arr1 )
[1 2 3 4 5 6 7 8 9]
# 访问元素
print( arr1[3] ) # 正着访问
print( arr1[-1] ) # 倒着访问
4
9
# 修改数组元素
arr1[3] = 100;
print(arr1)
[ 1 2 3 100 5 6 7 8 9]
访问矩阵
# 创建矩阵
arr2 = np.array( [ [1,2,3],[4,5,6] ] )
print(arr2)
[[1 2 3]
[4 5 6]]
# 访问元素
print( arr2[0,2] )
print( arr2[1,-2] )
3
5
# 修改元素,浮点数 100.9 插入到整数型数组时被截断了。
arr2[1,1] = 100.9
print( arr2 )
[[ 1 2 3]
[ 4 100 6]]
花式索引
花式索引(Fancy indexing)又名“花哨的索引”,UP 认为不应该用“花哨”来形容,这里的 Fancy 应取“华丽的、巧妙的、奢华的、时髦的”之义。
上一小节访问单个元素时,向量用 arr1[x],矩阵用 arr2[x,y]。逗号在矩阵里用于区分行与列,这一小节,逗号新增一个功能,且不会与矩阵里的逗号混淆。普通索引用一层中括号,花式索引用两层中括号。
向量的花式索引
# 创建向量
arr1 = np.arange(0,90,10)
print(arr1)
[ 0 10 20 30 40 50 60 70 80]
# 花式索引
# 含义:我想同时取 arr1 中索引为 0 和 2 的元素。
# 结果:[0, 20]。
# 对比:如果是普通索引 arr1[0] 只能得到 0。
print( arr1[ [0,2] ] )
[ 0 20]
矩阵的花式索引
# 创建矩阵
arr2 = np.arange(1,17).reshape(4,4)
print(arr2)
[[ 1 2 3 4]
[ 5 6 7 8]
[ 9 10 11 12]
[13 14 15 16]]
# 花式索引
# 含义:这里传入了两个列表。第一个列表 [0, 1] 代表行索引,第二个列表 [0, 1] 代表列索引。
# 配对规则:NumPy 会将它们配对使用。
# 取第 0 行,第 0 列的元素 -> 1
# 取第 1 行,第 1 列的元素 -> 6
# 结果:[1, 6]
# 注意:这不是取左上角的 2x2 子矩阵(那是切片 arr2[0:2, 0:2] 做的事),而是取具体的两个坐标点 (0,0) 和 (1,1)。
print( arr2[ [0,1] , [0,1] ] )
print( arr2[ [0,1,2] , [2,1,0] ] )
[1 6]
[3 6 9]
# 修改数组元素
arr2[ [0,1,2,3] , [3,2,1,0] ] = 100
print(arr2)
[[ 1 2 3 100]
[ 5 6 100 8]
[ 9 100 11 12]
[100 14 15 16]]
根据以上实例,花式索引输出的仍然是一个向量。
访问数组切片
向量的切片
向量与列表切片的操作完全一致,因此本页内容在Python基础中均有涉及。
import numpy as np
arr1 = np.arange(10)
print( arr1 )
print( arr1[ 1 : 4 ] ) # 从索引[1]开始,切到索引[4]之前
print( arr1[ 1 : ] ) # 从索引[1]开始,切到结尾
print( arr1[ : 4 ] ) # 从数组开头开始,切到索引[4]之前
[0 1 2 3 4 5 6 7 8 9]
[1 2 3]
[1 2 3 4 5 6 7 8 9]
[0 1 2 3]
print( arr1 )
print( arr1[ 2 : -2 ] ) # 切除开头 2 个和结尾 2 个
print( arr1[ 2 : ] ) # 切除开头 2 个
print( arr1[ : -2 ] ) # 切除结尾 2 个
[0 1 2 3 4 5 6 7 8 9]
[2 3 4 5 6 7]
[2 3 4 5 6 7 8 9]
[0 1 2 3 4 5 6 7]
print( arr1 )
print( arr1[ : : 2 ] ) # 每 2 个元素采样一次
print( arr1[ : : 3 ] ) # 每 3 个元素采样一次
print( arr1[ 1 : -1 : 2 ] ) # 切除一头一尾后,每 2 个元素采样一次
[0 1 2 3 4 5 6 7 8 9]
[0 2 4 6 8]
[0 3 6 9]
[1 3 5 7]
矩阵的切片
arr2 = np.arange(1,21).reshape(4,5)
print( arr2 )
[[ 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 ] ) # 矩阵切片初体验
[[ 7 8 9]
[12 13 14]]
print( arr2[ ::3 , ::2 ] ) # 跳跃采样
[[ 1 3 5]
[16 18 20]]
提取矩阵的行
import numpy as np
arr3 = np.arange(1,21).reshape(4,5)
print( arr3 )
[[ 1 2 3 4 5]
[ 6 7 8 9 10]
[11 12 13 14 15]
[16 17 18 19 20]]
print( arr3[ 2 , : ] ) # 提取第 2 行
[11 12 13 14 15]
print( arr3[ 1:3 , : ] ) # 提取 1 至 2 行
[[ 6 7 8 9 10]
[11 12 13 14 15]]
# 考虑代码的简洁,当提取矩阵的某几行时可简写,但是提取列的时候不可以进行简写
print( arr3[ 2 , : ] ) # 规范的提取行
print( arr3[2] ) # 简便的提取行
[11 12 13 14 15]
[11 12 13 14 15]
提取矩阵的列
基于矩阵的切片功能,我们可以提取其部分列。
arr4 = np.arange(1,21).reshape(4,5)
print( arr4 )
[[ 1 2 3 4 5]
[ 6 7 8 9 10]
[11 12 13 14 15]
[16 17 18 19 20]]
print( arr4[ : , 2 ] ) # 提取第 2 列(注意,输出的是向量)
[ 3 8 13 18]
print( arr4[ : , 1:3 ] ) # 提取 1 至 2 列
[[ 2 3]
[ 7 8]
[12 13]
[17 18]]
值得注意的是,提取某一个单独的列时,出来的结果是一个向量。其实这么做只是为了省空间,我们知道,列矩阵必须用两层中括号来存储,而形状为 1000 的向量,自然比形状为(1000,1)的列矩阵更省空间(节约了 1000 对括号)。
有点离谱
如果你真的想要提取一个列矩阵出来,示例可以如下:
arr5 = np.arange(1,16).reshape(3,5)
print( arr5 )
[[ 1 2 3 4 5]
[ 6 7 8 9 10]
[11 12 13 14 15]]
cut = arr5[ : , 2 ] # 提取第 2 列为向量
print( cut )
[ 3 8 13]
cut = cut.reshape( (-1,1) ) # 升级为列矩阵
print(cut)
[[ 3]
[ 8]
[13]]
数组切片仅是视图
数组切片仅是视图
与 Python 列表和 Matlab 不同,NumPy 数组的切片仅仅是原数组的一个视图。换言之,NumPy 切片并不会创建新的变量,示例如下。
arr = np.arange(10) # 创建原数组 arr
print(arr)
[0 1 2 3 4 5 6 7 8 9]
cut = arr[ : 3 ] # 创建 arr 的切片 cut
print(cut)
[0 1 2]
cut[0] = 100 # 对切片的数值进行修改
print(cut)
[100 1 2]
print(arr) # 原数组也被修改
[100 1 2 3 4 5 6 7 8 9]
习惯 Matlab 的用户可能无法理解,但其实这正是 NumPy 的精妙之处。试想一下,一个几百万条数据的数组,每次切片时都创建一个新变量,势必造成大量的内存浪费。因此,NumPy 的切片被设计为原数组的视图是极好的。
深度学习中为节省内存,将多次使用 arr[:] = <表达式> 来替代 arr = <表达式>。
备份切片为新变量
如果真的需要为切片创建新变量(这种情况很稀少),使用 .copy( ) 方法。
arr = np.arange(10) # 创建一个 0 到 10 的向量 arr
print(arr)
[0 1 2 3 4 5 6 7 8 9]
copy = arr[ : 3 ] .copy() # 创建 arr 的拷贝切片
print(copy)
[0 1 2]
copy [0] = 100 # 对拷贝切片的数值进行修改
print(copy)
[100 1 2]
print(arr) # 原数组不为所动
[0 1 2 3 4 5 6 7 8 9]
数组赋值仅是绑定
数组赋值仅是绑定
与 NumPy 数组的切片一样,NumPy 数组完整的赋值给另一个数组,也只是绑定。换言之,NumPy 数组之间的赋值并不会创建新的变量,示例如下。
arr1 = np.arange(10) # 创建一个 0 到 10 的数组变量 arr
print(arr1)
[0 1 2 3 4 5 6 7 8 9]
arr2 = arr1 # 把数组 1 赋值给另一个数组 2
print(arr2)
[0 1 2 3 4 5 6 7 8 9]
arr2[0] = 100 # 修改数组 2
print(arr2)
[100 1 2 3 4 5 6 7 8 9]
print(arr1) # 原数组也被修改
[100 1 2 3 4 5 6 7 8 9]
此特性的出现仍然是为了节约空间,破局的方法仍然与前面相同。
复制数组为新变量
如果真的需要赋给一个新数组,使用 .copy( ) 方法。
arr1 = np.arange(10) # 创建一个 0 到 10 的数组变量 arr
print(arr1)
arr2 = arr1.copy() # 把数组 1 的拷贝赋值给另一个数组 2
print(arr2)
arr2[0] = 100 # 修改数组 2
print(arr2)
[0 1 2 3 4 5 6 7 8 9]
[0 1 2 3 4 5 6 7 8 9]
[100 1 2 3 4 5 6 7 8 9]
print(arr1) # 查看数组 1
[0 1 2 3 4 5 6 7 8 9]
数组的变形
数组的转置
数组的转置方法为 .T,其只对矩阵有效,因此遇到向量要先将其转化为矩阵。
向量的转置
arr1 = np.arange(1,4) # 创建向量
print(arr1)
[1 2 3]
arr2 = arr1.reshape( (1,-1) ) # 升级为矩阵
print(arr2)
# 可以看到这里与我先前的认知产生了冲突,矩阵一定是二维的,两个中括号
# 而向量可以看到是一维的
[[1 2 3]]
arr3 = arr2.T # 行矩阵的转置
print(arr3)
[[1]
[2]
[3]]
矩阵的转置
行矩阵的转置刚刚已经演示过了,列矩阵的转置如下面示例所示。
arr1 = np.arange(3).reshape(3,1) # 创建列矩阵
print(arr1)
[[0]
[1]
[2]]
arr2 = arr1.T # 列矩阵的转置
print(arr2) # 结果为行矩阵
[[0 1 2]]
普通矩阵的转置如示例所示。
arr1 = np.arange(4).reshape(2,2) # 创建矩阵
print(arr1)
[[0 1]
[2 3]]
arr2 = arr1.T # 矩阵的转置
print(arr2)
[[0 2]
[1 3]]
数组的翻转
数组的翻转方法有两个,一个是上下翻转的 np.flipud( ) ,表示 up-down;一个是左右翻转的 np.fliplr( ),表示 left-right。其中,向量只能使用 np.flipud( ),在数学中,向量并不是横着排的,而是竖着排的。
注意:
- np.flipud()(flip up-down)确实容易让人误解为“垂直方向”。
- 本质:flipud 实际上是 沿着 Axis 0 翻转。
- 在 2D 矩阵中:Axis 0 是“行索引”,翻转行索引 = 上下翻转。
- 在 1D 数组中:Axis 0 是唯一的轴。翻转它 = 反转数组顺序
向量的翻转
# 创建向量
arr1 = np.arange(10)
print( arr1 )
[0 1 2 3 4 5 6 7 8 9]
# 翻转向量
arr_ud = np.flipud(arr1)
print( arr_ud )
[9 8 7 6 5 4 3 2 1 0]
矩阵的翻转
# 创建矩阵
arr2 = np.arange(1,21).reshape(4,5)
print( arr2 )
[[ 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 )
[[ 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 )
[[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 来计算。
向量的变形
arr1 = np.arange(1,10) # 创建向量
print(arr1)
[1 2 3 4 5 6 7 8 9]
arr2 = arr1.reshape(3,3) # 变形为矩阵
print(arr2)
[[1 2 3]
[4 5 6]
[7 8 9]]
矩阵的变形
arr1 = np.array( [ [1,2,3],[4,5,6] ] ) # 创建矩阵
print(arr1)
[[1 2 3]
[4 5 6]]
arr2 = arr1.reshape(6) # 变形为向量
print(arr2)
[1 2 3 4 5 6]
arr3 = arr1.reshape(1,6) # 变形为矩阵
print(arr3)
[[1 2 3 4 5 6]]
数组的拼接
向量的拼接
两个向量拼接,将得到一个新的加长版向量。
import numpy as np
# 创建向量 1
arr1 = np.array( [1,2,3] )
print(arr1)
[1 2 3]
# 创建向量 2
arr2 = np.array( [4,5,6] )
print(arr2)
[4 5 6]
# 拼接
arr3 = np.concatenate( [arr1,arr2] )
print(arr3)
[1 2 3 4 5 6]
矩阵的拼接
两个矩阵可以按不同的维度进行拼接,但拼接时必须注意维度的吻合。
# 创建数组 1
arr1 = np.array( [[1,2,3],[ 4,5,6]] )
print(arr1)
[[1 2 3]
[4 5 6]]
# 创建数组 2
arr2 = np.array( [[7,8,9],[10,11,12]] )
print(arr2)
[[ 7 8 9]
[10 11 12]]
# 按第二个维度(列)拼接
arr4 = np.concatenate( [arr1,arr2] ,axis=1 )
print(arr4)
[[ 1 2 3 7 8 9]
[ 4 5 6 10 11 12]]
# 按第一个维度(行)拼接
arr3 = np.concatenate( [arr1,arr2] ) # 默认参数 axis=0
print(arr3)
[[ 1 2 3]
[ 4 5 6]
[ 7 8 9]
[10 11 12]]
数组的分裂
向量的分裂
向量分裂,将得到若干个更短的向量。
# 创建向量
arr = np.arange(10,100,10)
print(arr)
[10 20 30 40 50 60 70 80 90]
# 分裂数组
arr1,arr2,arr3 = np.split( arr , [2,8] )
print(arr1)
print(arr2)
print(arr3)
[10 20]
[30 40 50 60 70 80]
[90]
np.split( )函数中,给出的第二个参数[2,8]表示在索引[2]和索引[8]的位置截断。
矩阵的分裂
矩阵的分裂同样可以按不同的维度进行,分裂出来的均为矩阵。
# 创建矩阵
arr = np.arange(1,9).reshape(2,4)
print(arr)
[[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]]
# 按第一个维度(行)分裂
arr1,arr2 = np.split(arr,[1]) # 默认参数 axis=0
print(arr1 , '\n\n' , arr2) # 注意输出的是矩阵
[[1 2 3 4]]
[[5 6 7 8]]
数组的运算
数组与系数之间的运算
NumPy的运算符和Python基础的运算符作用相同。这里仅仅以矩阵为例,向量与系数的操作与之相同。
# 创建矩阵
arr = np.arange(1,9).reshape(2,4)
print( arr )
print( arr + 10 ) # 加法,每一项加上对应元素。
[[11 12 13 14]
[15 16 17 18]]
print( arr - 10 ) # 减法
[[-9 -8 -7 -6]
[-5 -4 -3 -2]]
print( arr * 10 ) # 乘法
[[10 20 30 40]
[50 60 70 80]]
print( arr / 10 ) # 除法
[[0.1 0.2 0.3 0.4]
[0.5 0.6 0.7 0.8]]
print( arr ** 2) # 平方
[[ 1 4 9 16]
[25 36 49 64]]
print( arr // 6) # 取整
[[0 0 0 0]
[0 1 1 1]]
print( arr % 6) # 取余
[[1 2 3 4]
[5 0 1 2]]
数组与数组之间的运算
同维度数组间的运算即对应元素之间的运算,这里仅以矩阵为例,向量与向量的操作与之相同。
# 创建矩阵
arr1 = np.arange(-1,-9,-1).reshape(2,4)
arr2 = -arr1
print (arr1, "\n\n", arr2)
[[-1 -2 -3 -4]
[-5 -6 -7 -8]]
[[1 2 3 4]
[5 6 7 8]]
print( arr1 + arr2 ) # 加法
[[0 0 0 0]
[0 0 0 0]]
print( arr1 - arr2 ) # 减法,每项对应相减
[[ -2 -4 -6 -8]
[-10 -12 -14 -16]]
print( arr1 * arr2 ) # 乘法,每项对应相乘,与我之前所学的矩阵乘法差别似乎挺大
[[ -1 -4 -9 -16]
[-25 -36 -49 -64]]
print( arr1 / arr2 ) # 除法,每项对应相除
[[-1. -1. -1. -1.]
[-1. -1. -1. -1.]]
print( arr1 ** arr2) # 幂方,每项对应进行幂方
[[ -1 4 -27 256]
[ -3125 46656 -823543 16777216]]
上述乘法是遵循对应元素相乘的,你可以称之为“逐元素乘积”。
那么如何实现线性代数中的“矩阵级乘法”呢?后面会介绍到相关函数。
广播
上文是同形状数组之间的逐元素运算,本节讲解不同形状的数组之间的运算。本课件仅讨论二维数组之内的情况,不同形状的数组之间的运算有以下规则:
- 如果是向量与矩阵之间做运算,向量自动升级为行矩阵;
- 如果某矩阵是行矩阵或列矩阵,则其被广播,以适配另一个矩阵的形状。
向量被广播
当一个形状为(x,y)的矩阵与一个向量做运算时,要求该向量的形状必须为 y,运算时向量会自动升级成形状为(1,y)的行矩阵,该形状为(1,y)的行矩阵再自动被广播为形状为(x,y)的矩阵,这样就与另一个矩阵的形状适配了。
如何理解? 当你在 NumPy 中写 矩阵 * 向量 时,底层逻辑确实是:
- 复制扩充:NumPy 会在内存中(虚拟地)把这个向量沿着缺失的维度“复制”多份,直到它的形状和矩阵一样。
- 对应相乘:然后两个形状相同的数组进行“对应位置元素相乘”。
# 向量
arr1 = np.array([-100,0,100])
print(arr1)
[-100 0 100]
# 矩阵
arr2 = np.random.random( (10,3) ) # 生成10行3列符合均匀分布的在0-1之间的随机数
print(arr2)
[[0.5424101 0.14755969 0.47123895]
[0.82583782 0.55121481 0.43939141]
[0.4983497 0.40566339 0.52970638]
[0.08229524 0.00968612 0.73838025]
[0.69708932 0.73879245 0.95158036]
[0.56185074 0.56189898 0.38051903]
[0.3856772 0.10558253 0.84706093]
[0.89688485 0.7225091 0.90903447]
[0.07965262 0.35894269 0.10446375]
[0.94437005 0.84989319 0.44591064]]
# 广播
print(arr1*arr2)
[[-54.24101008 0. 47.12389536]
[-82.5837816 0. 43.93914112]
[-49.83496989 0. 52.97063829]
[ -8.22952442 0. 73.83802537]
[-69.70893182 0. 95.1580363 ]
[-56.18507448 0. 38.05190302]
[-38.56772024 0. 84.70609337]
[-89.68848454 0. 90.90344683]
[ -7.96526175 0. 10.44637509]
[-94.43700487 0. 44.59106385]]
# 广播
print(arr1+arr2)
[[-9.94575899e+01 1.47559693e-01 1.00471239e+02]
[-9.91741622e+01 5.51214813e-01 1.00439391e+02]
[-9.95016503e+01 4.05663386e-01 1.00529706e+02]
[-9.99177048e+01 9.68612058e-03 1.00738380e+02]
[-9.93029107e+01 7.38792445e-01 1.00951580e+02]
[-9.94381493e+01 5.61898982e-01 1.00380519e+02]
[-9.96143228e+01 1.05582526e-01 1.00847061e+02]
[-9.91031152e+01 7.22509098e-01 1.00909034e+02]
[-9.99203474e+01 3.58942688e-01 1.00104464e+02]
[-9.90556300e+01 8.49893194e-01 1.00445911e+02]]
列矩阵被广播
当一个形状为(x,y)的矩阵与一个列矩阵做运算时,要求该列矩阵的形状必须为(x,1),该形状为(x,1)的列矩阵再自动被广播为形状为(x,y)的矩阵,这样就与另一个矩阵的形状适配了。
# 列矩阵
arr1 = np.arange(3).reshape(3,1)
print(arr1)
[[0]
[1]
[2]]
# 矩阵
arr2 = np.ones( (3,5) )
print(arr2)
[[1. 1. 1. 1. 1.]
[1. 1. 1. 1. 1.]
[1. 1. 1. 1. 1.]]
# 广播
print(arr1*arr2)
[[0. 0. 0. 0. 0.]
[1. 1. 1. 1. 1.]
[2. 2. 2. 2. 2.]]
行矩阵与列矩阵同时被广播
当一个形状为(1,y)的行矩阵与一个形状为(x,1) 的列矩阵做运算时,这俩矩阵都会被自动广播为形状为(x,y)的矩阵,这样就互相适配了。
# 向量
arr1 = np.arange(3)
print(arr1)
[0 1 2]
# 列矩阵
arr2 = np.arange(3).reshape(3,1)
print(arr2)
[[0]
[1]
[2]]
# 广播
print(arr1*arr2)
[[0 0 0]
[0 1 2]
[0 2 4]]
数组的函数
矩阵乘积
上一章中的乘法都是“逐元素相乘”,这里介绍线性代数中的矩阵乘积,本节只需要使用 np.dot( ) 函数。
当矩阵乘积中混有向量时,根据需求,其可充当行矩阵,也可充当列矩阵,但混有向量时输出结果必为向量。
向量与向量的乘积
设两个向量的形状按前后顺序分别是 5 以及 5 。从矩阵乘法的角度,有(1,5)*(5,1) = (1,1),因此输出的应该是形状为 1 的向量。
# 创建向量
arr1 = np.arange(5)
arr2 = np.arange(5)
print(arr1)
print(arr2)
[0 1 2 3 4]
[0 1 2 3 4]
# 矩阵乘积
print( np.dot(arr1,arr2) )
30
向量与矩阵的乘积
设向量的形状是 5,矩阵的形状是 (5,3) 。从矩阵乘法的角度,有(1,5)*(5,3) = (1,3),因此输出的应该是形状为 3 的向量。
# 创建数组
arr1 = np.arange(5)
arr2 = np.arange(15).reshape(5,3)
print(arr1, "\n")
print(arr2)
[0 1 2 3 4]
[[ 0 1 2]
[ 3 4 5]
[ 6 7 8]
[ 9 10 11]
[12 13 14]]
# 矩阵乘积
print( np.dot(arr1,arr2) )
[ 90 100 110]
# 矩阵乘积
arr3 = np.arange(15).reshape(3, 5)
print(arr1, "\n\n", arr3)
[0 1 2 3 4]
[[ 0 1 2 3 4]
[ 5 6 7 8 9]
[10 11 12 13 14]]
# 矩阵乘积
print( np.dot(arr3,arr1) )
[ 30 80 130]
可以看到,向量既可以作为行矩阵也可以作为列矩阵参与运算,但维度一定要行列匹配,同时最后向量和矩阵的乘积必定为向量。也就是下面要看的矩阵和向量的乘积,看来我还挺会发散的。
矩阵与向量的乘积
设矩阵的形状是 (3,5) ,向量的形状是 5。从矩阵乘法的角度,有(3,5)*(5,1) = (3,1),因此输出的应该是形状为 3 的向量。
# 创建数组
arr1 = np.arange(15).reshape(3,5)
arr2 = np.arange(5)
print(arr1)
print(arr2)
[[ 0 1 2 3 4]
[ 5 6 7 8 9]
[10 11 12 13 14]]
[0 1 2 3 4]
# 矩阵乘积
print( np.dot(arr1,arr2) )
[ 30 80 130]
矩阵与矩阵的乘积
设两个矩阵的形状按前后顺序分别为(5, 2)以及(2, 8)。从矩阵乘法的角度,有(5,2)*(2,8)=(5,8),因此输出的应该是形状为(5,8)的矩阵。
# 创建数组 1
arr1 = np.arange(10).reshape(5,2)
print(arr1)
[[0 1]
[2 3]
[4 5]
[6 7]
[8 9]]
# 创建数组 2
arr2 = np.arange(16).reshape(2,8)
print(arr2)
[[ 0 1 2 3 4 5 6 7]
[ 8 9 10 11 12 13 14 15]]
# 矩阵乘积
print( np.dot(arr1,arr2) )
[[ 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设计了很多数学函数,这里距离其中最重要、最常见的几个。
# 绝对值函数
arr_v = np.array([-10, 0, 10])
abs_v = np.abs(arr_v)
print('原数组是: ', arr_v)
print('绝对值是: ', abs_v)
原数组是: [-10 0 10]
绝对值是: [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 )
print( '正弦值是:' , sin_v )
print( '余弦值是:' , cos_v )
print( '正切值是:' , tan_v )
原数组是: [0. 1.57079633 3.14159265]
正弦值是: [0.0000000e+00 1.0000000e+00 1.2246468e-16]
余弦值是: [ 1.000000e+00 6.123234e-17 -1.000000e+00]
正切值是: [ 0.00000000e+00 1.63312394e+16 -1.22464680e-16]
# 指数函数
x = np.arange(1,4)
print( 'x =' , x )
print( 'e^x =' , np.exp(x) )
print( '2^x =' , 2**x )
print( '10^x = ' , 10**x )
x = [1 2 3]
e^x = [ 2.71828183 7.3890561 20.08553692]
2^x = [2 4 8]
10^x = [ 10 100 1000]
# 对数函数,甚至还用到了换底公式
x = np.array( [1,10,100,1000] )
print( 'x =' , x )
print( 'ln(x) =' , np.log(x) )
print( 'log2(x) =' , np.log(x) / np.log(2) )
print( 'log10(x) =' , np.log(x) / np.log(10) )
x = [ 1 10 100 1000]
ln(x) = [0. 2.30258509 4.60517019 6.90775528]
log2(x) = [0. 3.32192809 6.64385619 9.96578428]
log10(x) = [0. 1. 2. 3.]
聚合函数
聚合很有用,这里用矩阵进行演示,向量与之一致,但没有 axis 参数。以下在注释中介绍了6个最重要的聚合函数,其用法完全一致,但仅仅演示其中3个。
# 最大值函数 np.max( )与最小值函数 np.min( )
arr = np.random.random((2,3))
print( arr )
print( '按维度一求最大值:' , np.max(arr,axis=0) )
print( '按维度二求最大值:' , np.max(arr,axis=1) )
print( '整体求最大值:' , np.max(arr) )
# 之前axis=0情况不都是按照行合并或者裂变吗?怎么这里为0时算的是每一列的最大值?
# 核心定义:Axis 是“刀口”的方向
# axis=N 永远表示:沿着第 N 维的方向进行操作。
[[0.1896318 0.28361532 0.20871848]
[0.17066469 0.70321325 0.20348921]]
按维度一求最大值: [0.1896318 0.70321325 0.20871848]
按维度二求最大值: [0.28361532 0.70321325]
整体求最大值: 0.7032132487882445
# 求和函数 np.sum( )与求积函数 np.prod( )
arr = np.arange(10).reshape(2,5)
print( arr )
print( '按维度一求和:' , np.sum(arr,axis=0) )
print( '按维度二求和:' , np.sum(arr,axis=1) )
print( '整体求和:' , np.sum(arr) )
[[0 1 2 3 4]
[5 6 7 8 9]]
按维度一求和: [ 5 7 9 11 13]
按维度二求和: [10 35]
整体求和: 45
# 均值函数 np.mean( )与标准差函数 np.std( )
arr = np.arange(10).reshape(2,5)
print( arr )
print( '按维度一求平均:' , np.mean(arr,axis=0) )
print( '按维度二求平均:' , np.mean(arr,axis=1) )
print( '整体求平均:' , np.mean(arr) )
[[0 1 2 3 4]
[5 6 7 8 9]]
按维度一求平均: [2.5 3.5 4.5 5.5 6.5]
按维度二求平均: [2. 7.]
整体求平均: 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)
[[1 2 3]
[4 5 6]]
# 数组与数字作比较
print( arr >= 4 )
[[False False False]
[ True True True]]
同维数组比较
import numpy as np
# 创建同维数组
arr1 = np.arange(1,6)
arr2 = np.flipud(arr1)
print(arr1)
print(arr2)
[1 2 3 4 5]
[5 4 3 2 1]
# 同维度数组作比较
print( arr1 > arr2)
[False False False True True]
同时比较多个条件
Python基础里,同时检查多个条件使用的与、或、非是and、or、not
但Numpy中使用的是&、|、~
import numpy as np
# 创建数组
arr = np.arange(1,10)
print(arr)
[1 2 3 4 5 6 7 8 9]
# 多个条件
print( (arr < 4) | (arr > 6) )
[ True True True False False False True True True]
布尔型数组中True的数量
有三个关于True数量的有用函数,分别是np.sum()、np.any()、np.all()
np.sum()函数
统计布尔型数组里True的个数
# 创建一个形状为 10000 的标准正态分布数组
arr = np.random.normal( 0,1,10000 )
# 统计该分布中绝对值小于 1 的元素个数
num = np.sum( np.abs(arr) < 1 )
print( num )
6849
np.any()函数
只要布尔型数组里含有一个及其以上的True,就返回True
# 创建同维数组
arr1 = np.arange(1,10)
arr2 = np.flipud(arr1)
print(arr1)
print(arr2)
[1 2 3 4 5 6 7 8 9]
[9 8 7 6 5 4 3 2 1]
# 统计这两个数组里是否有共同元素,从结果来看,arr1 与 arr2 里含有共同元素,那就是 5。
print( np.any( arr1 == arr2 ) )
True
### np.all()函数
当布尔型数组里全是 True 时,才返回 True
# 模拟英语六级的成绩,创建 100000 个样本
arr = np.random.normal( 500,70,100000 )
# 判断是否所有考生的分数都高于 250
print( np.all( arr > 250 ) )
False
布尔型数组作为掩码
若一个普通数组和一个布尔型数组的维度相同,可以将布尔型数组作为普通数组的掩码,这样可以对普通数组中的元素作筛选。
筛选出数组中大于、等于或小于某个数字的元素
# 创建数组
arr = np.arange(1,13).reshape(3,4)
print(arr)
[[ 1 2 3 4]
[ 5 6 7 8]
[ 9 10 11 12]]
# 数组与数字作比较
print( arr > 4 )
[[False False False False]
[ True True True True]
[ True True True True]]
# 筛选出 arr > 4 的元素
print( arr[ arr > 4 ] )
[ 5 6 7 8 9 10 11 12]
注意,这个矩阵进行掩码操作后,退化为了向量。
筛选出数组逐元素比较的结果
# 创建同维数组
arr1 = np.arange(1,10)
arr2 = np.flipud(arr1)
print(arr1)
print(arr2)
[1 2 3 4 5 6 7 8 9]
[9 8 7 6 5 4 3 2 1]
# 同维度数组作比较
print( arr1 > arr2)
[False False False False False True True True True]
# 筛选出 arr1 > arr2 位置上的元素
print( arr1[ arr1 > arr2 ] )
print( arr2[ arr1 > arr2 ] )
[6 7 8 9]
[4 3 2 1]
满足条件的元素所在位置
假设一个很长的数组,我们想知道满足某个条件的元素们所在的索引位置,此时使用np.where()函数。
# 模拟英语六级成绩的随机数组,取 10000 个样本
arr = np.random.normal( 500,70,1000 )
# 找出六级成绩超过 650 的元素所在位置
print( np.where( arr > 650 ) )
(array([ 94, 271, 278, 363, 488, 502, 567, 570, 602, 709, 721, 954]),)
# 找出六级成绩最高分的元素所在位置
print( np.where( arr == np.max(arr) ) )
(array([567]),)
np.where( )函数的输出看起来比较怪异,它是输出了一个元组。元组第一个元素是“满足条件的元素所在位置”;第二个元素是数组类型,可忽略掉。
不过我这里的输出似乎没有dtype,第二个位置是空的。
从数组到张量
数组与张量
- 本次课属于《Python 深度学习》系列视频,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)。
语法不同点
