39.3_axis和keepdims

39.3 axis和keepdims

NumPy的np.sum函数的功能更加强大。首先,它能指定求和时的轴。代码示例如下所示。

$\mathbf{x} = \mathbf{np}$  .array([1,2,3],[4,5,6])   
y  $=$  np-sum(x,axis=0)   
print(y)   
print(x.shape,'->',y.shape)

运行结果

[579] (2,3) ->(3,)

代码中x的形状是(2,3),输出y的形状是(3,)。上面的代码在np.sum(x, axis=0)中指定了axis=0。这里的axis表示轴,也就是多维数组排列的方向。请看图39-4。


图39-4 ndarray实例的axis(轴的索引)

图39-4是一个二维数组的例子。轴的索引如图39-4所示。我们可以在np.sum函数中指定轴,沿着该轴的方向求和(图39-5)。


图39-5 对每个axis进行x.sum()计算的结果

axis参数可以指定为int、None或元组。如果将axis指定为None,函数会计算所有元素的总和,然后输出一个值(标量)(默认参数是axis=None)。如果将axis指定为元组,如(0,2),函数就会沿着该元组指定的轴求和(在(0,2)的情况下,函数会对两个轴进行计算)。

np.sum函数中还有keepdims参数,它是用于指定输入和输出是否应具有相同维度(轴的数量)的标志位。下面是keepdims的使用示例。

$\mathbf{x} = \mathbf{np}$  .array([1,2,3],[4,5,6])   
y  $=$  np-sum(x,keepdims=True)   
print(y)   
print(y.shape)

运行结果

[[21]]

(1, 1)

在上面的代码中,y的形状是(1, 1)。如果keepdims=False,那么y的形状为()(标量)。从中可以看出,指定keepdims=True可以保留轴的数量。

前面介绍的axis和keepdims两个参数在实践中经常用到。因此,我们来修改DeZero的sum函数,使其支持这两个参数。尽管axis和keepdims会使和的计算变得复杂一些,但sum函数的反向传播的理论不变,即只复制梯度的元素,使其与输入变量在形状上一致。修改后的Sum类和sum函数如下所示。

dezero/functions.py

fromdezero importutils   
class Sum(Function): def__init__(self, axis,keepdots): self.axes  $=$  axis self_keepdots  $\equiv$  keepdots   
def forward(self,x): self.x_shape  $=$  x.shape y  $=$  x.sum(axis= self.axes,keepdots  $\equiv$  self_keepdots) returny   
def backward(self,gy): gy  $=$  utils.reshape_sum_backward(gy,self.x_shape,self.axes,) self_keepdots) gx  $=$  broadcast_to(gy,self.x_shape) returngx   
def sum(x, axis=None,keepdots=False): return Sum( axis,keepdots)(x)

Sum类在初始化阶段接收axis和keepdims,将它们设置为属性,然后在

正向传播中使用这些属性计算总和。反向传播的实现则和之前一样,使用broadcast_to函数。由此复制梯度的元素,使它的形状与输入变量的形状相同。

在反向传播的实现中,我们在broadcast_to函数之前使用了utilsreshape_sum_backward函数。这个函数会对gy的形状稍加调整(因为使用axis和keepdims求和时会出现改变梯度形状的情况)。这是与NumPy相关的问题,不是核心内容,所以这里就不详细解释了。

这样就完成了DeZero的sum函数。我们再对sum函数进行改造,使其也可以作为Variable的方法使用。为此,我们需要向Variable类中添加以下代码。

dezero/core.py

class Variable: def sum(self, axis=None, keepdims=False): returndezero-functions-sum(self, axis,keepdims)

下面是DeZero的sum函数的使用示例。

steps/step39.py

$\begin{array}{rl} & {\mathrm{x} = \mathrm{Variable}(\mathrm{np.array}([1,2,3],[4,5,6])})}\\ & {\mathrm{y} = \mathrm{F-sum}(\mathrm{x},\mathrm{axis} = 0)}\\ & {\mathrm{ybackward()}}\\ & {\mathrm{print}(\mathrm{y})}\\ & {\mathrm{print}(\mathrm{x}.grad)}\\ & {\mathrm{x} = \mathrm{Variable}(\mathrm{np.random.randint}(2,3,4,5))}\\ & {\mathrm{y} = \mathrm{x}.sum(\mathrm{keepdims} = \mathrm{True})}\\ & {\mathrm{print}(\mathrm{y}.shape)} \end{array}$

运行结果

variable([579])   
variable([[111] [111])   
(1,1,1,1)

以上就是本步骤的内容。

步骤40

进行广播的函数

上一个步骤实现了DeZero的sum函数。这个sum函数的反向传播中预先使用了broadcast_to函数,本步骤将实现这个broadcast_to函数。另外,为了在DeZero中实现与NumPy同样的广播功能,我们将对DeZero的一些函数进行修改。

NumPy具备广播功能,NumPy的广播有时发生在DeZero的正向传播中。不过当前的DeZero无法在广播发生时正确地进行反向传播。为了能正确地处理广播,我们需要修改DeZero。

下面先使用NumPy的函数进行说明,然后实现DeZero函数。