40.3_支持广播

40.3 支持广播

在本步骤实现sum_to函数是为了支持NumPy的广播。广播是NumPy的一个功能,它使不同形状的多维数组之间的运算成为可能。下面是广播的示例代码。

$\mathbf{x0} = \mathbf{np}$  .array([1,2,3])   
x1  $=$  np.array([10])   
y  $=$  x0 + x1   
print(y)

运行结果

array([11, 12, 13])

在上面的代码中, ×0\times 0×1\times 1 的形状是不同的。在进行上面的计算时,元素会被复制,以使 ×1\times 1×0\times 0 的形状相匹配。这里比较重要的一点是 NumPy 的广播功能是在幕后进行的。DeZero 也会实现这样的广播。我们来看下面的代码。

$\begin{array}{l}\mathrm{x0} = \mathrm{Variable(np.array([1,2,3]))}\\ \mathrm{x1} = \mathrm{Variable(np.array([10]))}\\ \mathrm{y} = \mathrm{x0} + \mathrm{x1}\\ \mathrm{print(y)} \end{array}$

运行结果

variable([11, 12, 13])

上面的代码在正向传播时会进行广播,这是因为代码是基于ndarray实例实现的。当然,如果在正向传播中进行了广播,那么在反向传播时就必须进行广播的反向传播,但是目前的DeZero不会对广播的反向传播做任何处理。

NumPy的广播是在broadcast_to函数中进行的,broadcast_to函数的反向传播对应的是sum_to函数。考虑到这一点,我们将DeZero的Add类修改成下面这样。

dezero/core.py

class Add(Function): def forward(self, x0, x1): self.x0_shape, self.x1_shape = x0.shape, x1.shape y = x0 + x1 return y def backward(self, gy):  $\mathtt{gx0}$  ,  $\mathtt{gx1} = \mathtt{gy}$  ,gy if self.x0_shape != self.x1_shape:  $\mathtt{gx0} =$ dezero-functions.sum_to(gx0,self.x0_shape)  $\mathtt{gx1} =$ dezero-functions,sum_to(gx1,self.x1_shape) return gx0,gx1

如果在正向传播中进行了广播,就说明输入的 x0x0x1x1 在形状上是不同的。此时应进行广播的反向传播计算。为此需要求梯度 gx0gx0 的和,使 gx0gx0 变为 x0x0 的形状,还要求梯度 gx1gx1 的和,使 gx1gx1 变为 x1x1 的形状。

以上修改是针对dezero/core.py中的Add类进行的。Mul、Sub、Div等所有进行四则运算的类都要完成相同的修改。由此便可实现广播功能。经过以上修改,我们可以编写以下代码。

steps/step40.py

import numpy as np   
fromdezero import Variable   
 $\mathrm{x0} =$  Variable(np.array([1,2,3]))   
x1  $=$  Variable(np.array([10]))   
y  $=$  x0+x1   
print(y)   
y.backup()   
print(x1.grad)

运行结果

variable([11 12 13])  
variable([3])

上面的代码在 x0+x1x0 + x1 时进行了广播。不过,这次广播的反向传播在DeZero函数中被正确执行了。实际得到的 x1x1 的梯度是3,这是正确的结果。通过以上操作,DeZero实现了广播功能。

步骤41

矩阵的乘积

本步骤的主题是向量的内积和矩阵的乘积。这里会先介绍这两种计算方法,然后将它们实现为DeZero函数。完成本步骤后,我们就有了能够处理张量的最低限度的函数集,由此可以开始解决实际问题了。