【numpy笔记_5】数组的算数运算与广播机制
算数运算
与常规的list对象不同,numpy支持把整个数组带入算数运算。
之前提过,array对象往往要求所有元素保持统一的数据类型,因此numpy的运算能以数组为单位,而不用将元素提出来。
这也是numpy能够胜任高效运算的原因之一。
来看几个例子:
import numpy as np
arr = np.arange(1,10).reshape(3, 3)
array_1 = arr > 7 # 条件比较,返回bool
array_2 = arr * 0.3 # 加减乘除运算
arrs = np.arange(11,20).reshape(3,3)
array_3 = arrs / arr # 数组间的运算
print(array_1)
print('* '*20)
print(array_2)
print('* '*20)
print(array_3)
# 运行结果:
[[False False False]
[False False False]
[False True True]]
* * * * * * * * * * * * * * * * * * * *
[[0.3 0.6 0.9]
[1.2 1.5 1.8]
[2.1 2.4 2.7]]
* * * * * * * * * * * * * * * * * * * *
[[11. 6. 4.33333333]
[ 3.5 3. 2.66666667]
[ 2.42857143 2.25 2.11111111]]
可以看到,numpy甚至支持两个数组对象的算数运算。我们因此得到结论:
在numpy中,参与算术运算的单位可以是数组(“可以”不是“只能”)。
数组和数字的计算没什么好说的。
而数组与数组的运算,我们发现上面的例子中,两个运算的数组结构相同。这很好理解,两组的元素呈一一对应关系,这是映射。
那如果一对结构不同的数组呢?这就是我们要讲的广播机制。
广播机制
“广播”,顾名思义有传递、向外扩散的意思。
numpy的广播机制也是如此,指两个不同shape的数组,在它们的shape满足一定条件时,可进行广播运算。这时,两个数组的元素按照一定规则扩散开来运算,并得到一个新的数组。
借用经典的描述就是,numpy中的广播机制旨在解决不同形状数组之间的算数运算问题。
我们来看数组运算的几个例子,观察广播机制是如何运作的:
import numpy as np
arr_2d_3x3 = np.arange(1,10).reshape(3, 3) # 3*3
arr_1d_3 = np.arange(1,4) # 3个元素的一维数组
arr_1d_4 = np.arange(1,5) # 4个元素
array_jia = arr_2d_3x3 + arr_1d_3 # 3*3 与 3个元素 运算(拿拼音代替了..)
array_jian = arr_2d_3x3 - arr_1d_4 # 3*3 与 4个元素 运算
print(arr_2d_3x3)
print('- '*5)
print(arr_1d_3)
print(arr_1d_4)
print('* '*20)
print(array_jia)
print('* '*20)
print(array_jian)
# 运行结果:
[[1 2 3]
[4 5 6]
[7 8 9]] # arr_2d_3x3
- - - - -
[1 2 3] # arr_1d_3
[1 2 3 4] # arr_1d_4
* * * * * * * * * * * * * * * * * * * *
[[ 2 4 6]
[ 5 7 9]
[ 8 10 12]] # array_jia,3*3 与 3个元素 运算
* * * * * * * * * * * * * * * * * * * *
ValueError: operands could not be broadcast together with shapes (3,3) (4,) # array_jian,3*3 与 4个元素 运算
可以看到,两个数组在列数(个数)相同、行数(长短)不同时,“较短”的数组对“较长”的数组向下做了传播式的运算,这便是"广播机制"叫法的由来。而列数不同的数组就报错了,便不能广播。
到这还没完,再看一组例子:
import numpy as np
arr_2d_3x3 = np.arange(1,10).reshape(3, 3) # 3*3
arr_2d_1x3 = np.arange(1,4).reshape(1, 3) # 1*3
arr_2d_3x1 = np.arange(1,4).reshape(3, 1) # 3*1
array_jia = arr_2d_3x3 + arr_2d_1x3 # 3*3 与 1*3 运算
array_jian = arr_2d_3x3 - arr_2d_3x1 # 3*3 与 3*1 运算
print(arr_2d_3x3)
print('- '*5)
print(arr_2d_1x3)
print('- '*5)
print(arr_2d_3x1)
print('* '*20)
print(array_jia)
print('* '*20)
print(array_jian)
# 运行结果:
[[1 2 3]
[4 5 6]
[7 8 9]] # 3*3
- - - - -
[[1 2 3]] # 1*3
- - - - -
[[1]
[2]
[3]] # 3*1
* * * * * * * * * * * * * * * * * * * *
[[ 2 4 6]
[ 5 7 9]
[ 8 10 12]] # 3*3 与 1*3 运算
* * * * * * * * * * * * * * * * * * * *
[[0 1 2]
[2 3 4]
[4 5 6]] # 3*3 与 3*1 运算
arr_2d_3x3 = np.arange(1,10).reshape(3, 3) # 3*3
arr_2d_1x3 = np.arange(1,7).reshape(2, 3) # 2*3
array_jia = arr_2d_3x3 + arr_2d_1x3 # 3*3 与 2*3 运算
print(array_jia)
# 运行结果:
ValueError: operands could not be broadcast together with shapes (3,3) (2,3)
通过上面第一个例子,很明显numpy在对两个方向不一致的数组进行运算时,基于“较短”数组的shape,选择运算规则是向行扩散还是列扩散。
用大白话说就是,相对于大数组,小数组行、列的饱和度。即:
- 列饱和、行缺失则广播行补充;
- 行饱和、列缺失则广播列补充。
无论如何,只要行/列有一个饱和的情况下,就将不饱和的地方广播至饱和。
而第二个例子可能你也感觉到了,3x3 和 2x3 这种形状好像根本没法广播,因为小数组拿来广播的元素是不唯一的。
是的,就是这么好理解。
可能你想问,那3d及以上的数组怎么广播呢?
其实,我们已经知道高维数组(体)由多个2维数组(面)组成。
所以你可能知道了,无非就是对多个“面”逐一广播而已。
就像这样:
import numpy as np
arr_2d_3x3 = np.arange(1,13).reshape(2, 3, 2) # 3*3
arr_2d_3x1 = np.arange(1,4).reshape(3,1)
array_jia = arr_2d_3x3 + arr_2d_3x1 # 3*3 与 3*1 运算
print(arr_2d_3x3)
print('- '*5)
print(arr_2d_3x1)
print('* '*20)
print(array_jia)
# 运行结果:
[[[ 1 2]
[ 3 4]
[ 5 6]]
[[ 7 8]
[ 9 10]
[11 12]]]
- - - - -
[[1]
[2]
[3]] # 这是多行一列,一行多列就不举例了
* * * * * * * * * * * * * * * * * * * *
[[[ 2 3]
[ 5 6]
[ 8 9]] # 先广播第一块
[[ 8 9]
[11 12]
[14 15]]] # 再广播第二块
最后,我们总结一下广播机制:
- 广播机制,指两个数组间的推广运算;
- 广播对shape有一定要求。相对于大数组,小数组的行/列必须有一处饱和,且另一处唯一。即shape(1, n)或shape(n,1);
- array对象和数字直接运算(object + 1),就是numpy广播机制的体现。
- 用于广播的小数组,必须是单个数字或二维(行、列)。
-
广播的本质是一种算数运算,适用于加减乘除及其他所有的运算方式。
广播机制.png