44.2_Layer类的实现

44.2 Layer类的实现

接下来实现Layer类,它与DeZero的Function类相似,都是变换变量的类,不过二者在持有参数这一点上不同。Layer类是持有参数并使用这些参数进行变换的类。

Layer类是作为基类实现的,而具体的变换是在继承了Layer类的类中实现的。例如,对于线性变换,我们将在继承了Layer类的Linear类中实现它。

下面看一下Layer类的实现。首先是初始化操作和__setattr__这一特殊方法。

dezero/layers.py

fromdezero.core import Parameter   
class Layer: def__init__(self): self._params  $\equiv$  set() def__setattr__(self,name, value): ifisinstance(value,Parameter): self._params.add(name) super().__setattr__(name, value)

Layer类持有一个名为.params的实例变量。这个.params保存了Layer实例所拥有的参数。

.params实例变量的类型是集合。集合与列表不同,它的元素是没有顺序的。另外,集合不会持有ID相同的对象。

setattr__方法是在设置实例变量时被调用的特殊方法。如果定义了__setattr(self, name, value),那么实例变量的名字会作为name参数、实例变量的值会作为value参数传给该函数。通过重写这个方法,我们就可以在添加实例变量时添加一些特殊处理。

这里添加只有当value是Parameter实例时才向self._params增加name的处理①。这样我们就可以把Layer类的参数汇总到实例变量.params中。代码如下所示。

layer  $=$  Layer()   
layer.p1  $=$  Parameter(np.array(1))   
layer.p2  $=$  Parameter(np.array(2))   
layer.p3  $=$  Variable(np.array(3))   
layer.p4  $=$  'test'   
print(layer._params)   
print('--------')   
for name in layer._params: print(name, layer._dict_[name])

运行结果

{'p2','p1'} p2 variable(2) p1 variable(1)

如上面的代码所示,设置layer的实例变量后,只有引用了Parameter实例的变量名被添加到了layer_.params中。此外,由于所有的实例变量都以字典的形式存储在实例变量__dict__中,所以我们通过__dict__就可以单独取出Parameter实例。

接下来向Layer类添加以下4个方法。

dezero/layers.py

importweakref   
class Layer: def__call__(self,\*inputs): outputs  $=$  self.forward(\*inputs) if notisinstanceoutputs,tuple): outputs  $=$  (outputs,) self.Inputs  $=$  [weakref.ref(x)for  $\mathbf{x}$  in inputs] self.Outputs  $=$  [weakref.ref(y) for y in outputs] return outputs if len(output)  $>1$  else outputs[0] defforward(self,inputs): raiseNotImplementedError() defparams(self): fornameinself._params: yield self._dict_[name] defcleargrids(self): forparamin self.params(): param.cleargrad()

__call__方法接收输入并调用forward方法。forward方法由继承的类实现。如果输出只有一个值,那么__call__方法将不返回元组,而是直接返回该值(这个做法与Function类的实现相同)。另外,考虑到将来的需求,__call__方法通过弱引用持有输入变量和输出变量。

params方法取出Layer实例所持有的Parameter实例。另外,cleargrads方法重置所有参数的梯度。这个方法的名称是复数形式,即在cleargrad的后面加上了s。这么做是为了显式地表明该函数会对Layer拥有的所有参数调用cleargrad(单数形式)。

params方法使用yield返回值。yield的使用方法与return相同。区别是return会结束处理并返回值,而yield是暂停处理并返回值。因此,再次使用yield会恢复处理。以上面的代码为例,每次调用params方法时,暂停的处理都会重新运行。组合使用yield和for语句,即可按顺序取出参数。

以上就是Layer类的实现。下面继承这个Layer类,实现线性变换等具体的处理。