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函数。