首页 > 随笔 > 【numpy笔记_5】数组的算数运算与广播机制

【numpy笔记_5】数组的算数运算与广播机制

简书 2023-02-26 00:26 3 阅读 查看原文

算数运算

与常规的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