【完】Numpy学习笔记

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

2023-02-25  本文已影响0人  fakeProgramer

算数运算

与常规的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]]]   # 再广播第二块

最后,我们总结一下广播机制:

上一篇下一篇

猜你喜欢

热点阅读