大家好这里是三岁,我们一起来看看paddle白话第四篇吧!!!
PaddlePaddle和其他框架一样,提供的一些API支持广播(broadcasting)机制,允许在一些运算时使用不同形状的张量。
广播(broadcasting)
解释:当两个数组的形状并不相同的时候,我们可以通过扩展数组的方法来实现相加、相减、相乘等操作
Paddle在广播中与Numpy的广播机制类似,但是又有革新,让我们一起来看看吧!
参考资料
Numpy官网-广播机制:https://numpy.org/doc/stable/user/basics.broadcasting.html#module-numpy.doc.broadcasting
CSDN地址
三岁白话系列CSDN地址:
https://blog.csdn.net/weixin_45623093/category_10616602.html
paddlepaddleCSDN系列文章:https://blog.csdn.net/PaddlePaddle
Ai Studio地址:https://aistudio.baidu.com/aistudio/projectdetail/1305898
# 导入第三方库 import paddle import numpy as np
同维度的操作
# 同维度的操作 x = paddle.to_tensor(np.ones((2, 3, 4), np.float32)) y = paddle.to_tensor(np.ones((2, 3, 4), np.float32)) print("x=", x) print("y=", y) print('x+y=', x+y) # 逐元素相加 print('x add y=', paddle.add(x, y)) # add API
x= Tensor(shape=[2, 3, 4], dtype=float32, place=CPUPlace, stop_gradient=True, [[[1., 1., 1., 1.], [1., 1., 1., 1.], [1., 1., 1., 1.]], [[1., 1., 1., 1.], [1., 1., 1., 1.], [1., 1., 1., 1.]]]) y= Tensor(shape=[2, 3, 4], dtype=float32, place=CPUPlace, stop_gradient=True, [[[1., 1., 1., 1.], [1., 1., 1., 1.], [1., 1., 1., 1.]], [[1., 1., 1., 1.], [1., 1., 1., 1.], [1., 1., 1., 1.]]]) x+y= Tensor(shape=[2, 3, 4], dtype=float32, place=CPUPlace, stop_gradient=True, [[[2., 2., 2., 2.], [2., 2., 2., 2.], [2., 2., 2., 2.]], [[2., 2., 2., 2.], [2., 2., 2., 2.], [2., 2., 2., 2.]]]) x add y= Tensor(shape=[2, 3, 4], dtype=float32, place=CPUPlace, stop_gradient=True, [[[2., 2., 2., 2.], [2., 2., 2., 2.], [2., 2., 2., 2.]], [[2., 2., 2., 2.], [2., 2., 2., 2.], [2., 2., 2., 2.]]])
广播规则
为了进行广播,操作中两个阵列的尾轴尺寸必须相同,或者其中之一必须相同。
解析:
如果两个数组的后缘维度(trailing dimension,即从末尾开始算起的维度)的轴长度相符,或其中的一方的长度为1,则认为它们是广播兼容的。广播会在缺失和(或)长度为1的维度上进行。
这句话乃是理解广播的核心。广播主要发生在两种情况,一种是两个数组的维数不相等,但是它们的后缘维度的轴长相符,另外一种是有一方的长度为1。
类型1:数组维度不同,后缘维度的轴长相符
多维和一维广播操作
多维和一维的操作要准守广播规则
x = paddle.to_tensor([1.0, 2.0, 3.0]) y = paddle.to_tensor(2.0) print('x+y=', x+y)
x+y= Tensor(shape=[3], dtype=float32, place=CPUPlace, stop_gradient=True, [3., 4., 5.])
原理
x = paddle.to_tensor([[0, 0, 0],[1, 1, 1],[2, 2, 2], [3, 3, 3]]) y = paddle.to_tensor([1, 2, 3]) print('x+y', x+y)
x+y Tensor(shape=[4, 3], dtype=int64, place=CPUPlace, stop_gradient=True, [[1, 2, 3], [2, 3, 4], [3, 4, 5], [4, 5, 6]])
原理:
错误示范:
x = paddle.to_tensor([[ 0.0, 0.0, 0.0], ... [10.0, 10.0, 10.0], ... [20.0, 20.0, 20.0], ... [30.0, 30.0, 30.0]]) # 大小:4*3 y = paddle.to_tensor([0, 1, 2, 3]) # 大小:4 print(x+y)
问题解析
报错情况:
EnforceNotMet: ---------------------- Error Message Summary: ---------------------- InvalidArgumentError: Broadcast dimension mismatch. Operands could not be broadcast together with the shape of X = [4, 3] and the shape of Y = [4]. Received [3] in X is not equal to [4] in Y at i:1. [Hint: Expected x_dims_array[i] == y_dims_array[i] || x_dims_array[i] <= 1 || y_dims_array[i] <= 1 == true, but received x_dims_array[i] == y_dims_array[i] || x_dims_array[i] <= 1 || y_dims_array[i] <= 1:0 != true:1.] (at /paddle/paddle/fluid/operators/elementwise/elementwise_op_function.h:160) [operator < elementwise_add > error]
问题
没有符合广播原则,最后的维度没有对齐
原理图:
多维度和多维度之间的广播
x = paddle.to_tensor([[[0, 1], [2, 3], [4, 5], [6,7]], [[0, 1], [2, 3], [4, 5], [6,7]], [[0, 1], [2, 3], [4, 5], [6,7]]]) print('x的形状', x.shape) y = paddle.to_tensor([[0, 1], [2, 3], [4, 5], [6,7]]) print('y的形状', y.shape) print('x+y', x+y)
x的形状 [3, 4, 2] y的形状 [4, 2] x+y Tensor(shape=[3, 4, 2], dtype=int64, place=CPUPlace, stop_gradient=True, [[[0, 2], [4, 6], [ 8, 10], [12, 14]], [[0, 2], [4, 6], [ 8, 10], [12, 14]], [[0, 2], [4, 6], [ 8, 10], [12, 14]]])
原理
类型二:数组维度相同,后缘维度的轴长不相同
x = paddle.to_tensor(([[0.0], [10.0], [20.0], [30.0]])) y = paddle.to_tensor([0.0, 1.0, 2.0]) print('x+y', x+y)
x+y Tensor(shape=[4, 3], dtype=float32, place=CPUPlace, stop_gradient=True, [[0., 1., 2.], [10., 11., 12.], [20., 21., 22.], [30., 31., 32.]])
原理:
特殊情况
x = paddle.to_tensor(np.ones((3, 5, 6), np.float32)) y = paddle.to_tensor(np.ones((1, 6), np.float32)) print(x+y)
Tensor(shape=[3, 5, 6], dtype=float32, place=CPUPlace, stop_gradient=True, [[[2., 2., 2., 2., 2., 2.], [2., 2., 2., 2., 2., 2.], [2., 2., 2., 2., 2., 2.], [2., 2., 2., 2., 2., 2.], [2., 2., 2., 2., 2., 2.]], [[2., 2., 2., 2., 2., 2.], [2., 2., 2., 2., 2., 2.], [2., 2., 2., 2., 2., 2.], [2., 2., 2., 2., 2., 2.], [2., 2., 2., 2., 2., 2.]], [[2., 2., 2., 2., 2., 2.], [2., 2., 2., 2., 2., 2.], [2., 2., 2., 2., 2., 2.], [2., 2., 2., 2., 2., 2.], [2., 2., 2., 2., 2., 2.]]])
解析:
这个地方虽然数组维度不相同,后缘维度的轴长也不相同
但是这里后缘维度不同的地方是1,也可以先把1进行处理
让我们举例说明
x = paddle.to_tensor([[[2., 3., 4., 5., 6., 7.], [2., 3., 4., 5., 6., 7.], [2., 3., 4., 5., 6., 7.], [2., 3., 4., 5., 6., 7.], [2., 3., 4., 5., 6., 7.]], [[2., 3., 4., 5., 6., 7.], [2., 3., 4., 5., 6., 7.], [2., 3., 4., 5., 6., 7.], [2., 3., 4., 5., 6., 7.], [2., 3., 4., 5., 6., 7.]], [[2., 3., 4., 5., 6., 7.], [2., 3., 4., 5., 6., 7.], [2., 3., 4., 5., 6., 7.], [2., 3., 4., 5., 6., 7.], [2., 3., 4., 5., 6., 7.]]]) y = paddle.to_tensor([[5, 4, 3, 2, 1, 10]]) y = paddle.to_tensor([[5, 4, 3, 2, 1, 10]]) print(x+y)
Tensor(shape=[3, 5, 6], dtype=float32, place=CPUPlace, stop_gradient=True, [[[ 7., 7., 7., 7., 7., 17.], [ 7., 7., 7., 7., 7., 17.], [ 7., 7., 7., 7., 7., 17.], [ 7., 7., 7., 7., 7., 17.], [ 7., 7., 7., 7., 7., 17.]], [[ 7., 7., 7., 7., 7., 17.], [ 7., 7., 7., 7., 7., 17.], [ 7., 7., 7., 7., 7., 17.], [ 7., 7., 7., 7., 7., 17.], [ 7., 7., 7., 7., 7., 17.]], [[ 7., 7., 7., 7., 7., 17.], [ 7., 7., 7., 7., 7., 17.], [ 7., 7., 7., 7., 7., 17.], [ 7., 7., 7., 7., 7., 17.], [ 7., 7., 7., 7., 7., 17.]]])
看到这里都明白了什么吧!不懂也不解释了!!!
Paddle不一样的广播机制——“elementwise”
飞桨的elementwise系列API针对广播机制增加了axis参数
添加了axis参数以后广播的位置就可以进行自定义了(自定义开始位置)
paddle.disable_static() x = paddle.to_tensor(np.ones((2, 1, 4), np.float32)) y = paddle.to_tensor(np.ones((3, 1), np.float32)) z = paddle.fluid.layers.elementwise_add(x, y, axis=1) # z的形状 [2, 3, 4] print(z) print("z的形状:", z.shape)
Tensor(shape=[2, 3, 4], dtype=float32, place=CPUPlace, stop_gradient=True, [[[2., 2., 2., 2.], [2., 2., 2., 2.], [2., 2., 2., 2.]], [[2., 2., 2., 2.], [2., 2., 2., 2.], [2., 2., 2., 2.]]]) z的形状: [2, 3, 4]
白话原理:
这里的广播虽然本来没有axis=1也可以正常运行,结果和这个是一样的
这里的原理和之前说的特殊情况是一个道理,就是只要维度相同,其中一位等于1该维度就可以广播
paddle.disable_static() x = paddle.to_tensor(np.ones((2, 3, 4, 5), np.float32)) y = paddle.to_tensor(np.ones((4, 5), np.float32)) z = paddle.fluid.layers.elementwise_add(x, y, axis=1) print(z.shape) # InvalidArgumentError: Broadcast dimension mismatch. # 因为指定了axis之后,计算广播的维度从axis开始从前向后比较
白话解析
报错内容:
EnforceNotMet: ---------------------- Error Message Summary: ---------------------- InvalidArgumentError: Broadcast dimension mismatch. Operands could not be broadcast together with the shape of X = [2, 3, 4, 5] and the shape of Y = [4, 5]. Received [3] in X is not equal to [4] in Y at i:1. [Hint: Expected x_dims_array[i] == y_dims_array[i] || x_dims_array[i] <= 1 || y_dims_array[i] <= 1 == true, but received x_dims_array[i] == y_dims_array[i] || x_dims_array[i] <= 1 || y_dims_array[i] <= 1:0 != true:1.] (at /paddle/paddle/fluid/operators/elementwise/elementwise_op_function.h:160) [operator < elementwise_add > error]
这里小的Tensor的维度4,5是和大的Tensor的后缘维度的轴长是相同的!!!
但是这里面的axis设置了是1也就是从倒数第二个维度开始计算小的Tensor的是(4, 5)大的Tensor是(3, 4)
维度不匹配,会产生以上的报错
paddle.disable_static() x = paddle.to_tensor(np.ones((2, 3, 4, 5), np.float32)) y = paddle.to_tensor(np.ones((3), np.float32)) z = paddle.fluid.layers.elementwise_add(x, y, axis=1) print("z的形状:", z.shape) print(z) # z的形状 [2, 3, 4, 5] # 因为此时是从axis=1的维度开始,从前向后比较维度进行广播
z的形状: [2, 3, 4, 5] Tensor(shape=[2, 3, 4, 5], dtype=float32, place=CPUPlace, stop_gradient=True, [[[[2., 2., 2., 2., 2.], [2., 2., 2., 2., 2.], [2., 2., 2., 2., 2.], [2., 2., 2., 2., 2.]], [[2., 2., 2., 2., 2.], [2., 2., 2., 2., 2.], [2., 2., 2., 2., 2.], [2., 2., 2., 2., 2.]], [[2., 2., 2., 2., 2.], [2., 2., 2., 2., 2.], [2., 2., 2., 2., 2.], [2., 2., 2., 2., 2.]]], [[[2., 2., 2., 2., 2.], [2., 2., 2., 2., 2.], [2., 2., 2., 2., 2.], [2., 2., 2., 2., 2.]], [[2., 2., 2., 2., 2.], [2., 2., 2., 2., 2.], [2., 2., 2., 2., 2.], [2., 2., 2., 2., 2.]], [[2., 2., 2., 2., 2.], [2., 2., 2., 2., 2.], [2., 2., 2., 2., 2.], [2., 2., 2., 2., 2.]]]])
白话解析
这里的内容和上面的所法一样的
就是虽然小Tensor的后缘维度的轴长和大的Tensor的后缘维度的轴长不同
但是设置过axis=1以后小的后缘维度对应的是大的后缘维度的第二位就是相同的可以进行广播操作
这里需要大家直行理解一下,白话的可能比较乱[尴尬]
特殊说明
广播提供了一种对数组操作进行矢量化的方法,从而使循环在C而不是Python中发生。这样做无需复制不必要的数据,通常可以实现高效的算法实现。在某些情况下,广播不是一个好主意,因为广播会导致内存使用效率低下,从而减慢计算速度。
看情况量力而行 啦!