《动手学深度学习》第十天---模型构造

(一)继承Block类来构造模型

Block类是nn模块里提供的一个模型构造类,我们可以继承它来定义我们想要的模型。下面继承Block类构造本节开头提到的多层感知机。这里定义的MLP类重载了Block类的__init__函数和forward函数。它们分别用于创建模型参数和定义前向计算。前向计算也即正向传播。

   from mxnet import nd
   from mxnet.gluon import nn

class MLP(nn.Block):     //class 类名[(父类名)]:[成员函数及成员变量]
    def __init__(self, **kwargs):  // __init__构造用于在类的对象被创建时,马上运行。该方法用于对对象初始化。
    类的各属性(或成员变量)均可以在构造函数中定义,定义时加上对象指针即可。
        super(MLP, self).__init__(**kwargs)//调用super函数
        self.hidden = nn.Dense(256,activation='relu')//创建成员变量并赋予初值,隐藏层
        self.output = nn.Dense(10)//创建成员变量并赋予初值,输出层
   
    def forward(self,x)://子类重载函数forward
        return self.output(self.hidden(x))       
 //super函数:第一个参数是当前子类的类的名字,如MLP。第二个参数是self。
然后是点号,点号后面是要调用的父类的方法。
可以解决经过init后子类需要从父类继承的属性被初始化的问题。

《动手学深度学习》第十天---模型构造_第1张图片net(X)会调用MLP继承自Block类的__call__函数,这个函数将调用MLP类定义的forward函数来完成前向计算。

(二)Sequential类继承自Block类

class MySequential(nn.Block)://MySequential类继承自Block类
def __init__(self, **kwargs)://重载构造函数,初始化对象的各属性,第一个参数是self
        super(MySequential, self).__init__(**kwargs)//继承父类的参数
def add(self, block):
    # block是一个Block子类实例,假设它有一个独一无二的名字。我们将它保存在Block类的
    # 成员变量_children里,其类型是OrderedDict。当MySequential实例调用
    # initialize函数时,系统会自动对_children里所有成员初始化
    self._children[block.name] = block

def forward(self, x):
    # OrderedDict保证会按照成员添加时的顺序遍历成员
    for block in self._children.values():
        x = block(x)
    return x

这里MySequential类的使用跟“多层感知机的简洁实现”一节中Sequential类的使用没什么区别。
这个例子看的其实不是很清楚,我依然不知道Sequential的工作机制。
查阅了一些资料后,提炼一些关键句:
Sequential可以被当成一个有序的容器,神经网络模块按添加顺(也就是add函数的功能)序进行执行(这就可以理解forward里面的循环了)。
我们知道每一个block都是Block的子类,所以在forward中都会实现__call__方法,而__call__中调用了forward函数,而forward函数实现的是:inputweight + bias
block(x)实际上就是调用__call__(input),然后__call__(x)调用forward()函数,最后返回计算结果为:x
block_weight+block_bias

但需要注意的是计算时weight是经过转置的。在定义里面weight是[out_features,in_features]。
这样就好理解啦!

(三)构造复杂的模型

from mxnet import nd
from mxnet.gluon import nn
class FancyMLP(nn.Block):
    def __init__(self,**kwargs):
        super(FancyMLP,self).__init__(**kwargs)
        self.rand_weight = self.params.get_constant(
        'rand_weight',nd.random.uniform(shape=(20,20)))#get_constant()检索名为self.prefix+name的常数,该常数不会随迭代过程而改变
        self.dense = nn.Dense(20,activation='relu')
    def forward(self,x):
        x = self.dense(x)
        x = nd.relu(nd.dot(x,self.rand_weight.data())+1) # 使用创建的常数参数,以及NDArray的relu函数和dot函数
        x = self.dense(x) # 复用全连接层。等价于两个全连接层共享参数
        while x.norm().asscalar() > 1:#计算范数。默认为L2范数
            x/=2
        if x.norm().asscalar() < 0.8:
            x*=10
        return x.sum()

class NestMLP(nn.Block):
    def __init__(self, **kwargs):
        super(NestMLP, self).__init__(**kwargs)
        self.net = nn.Sequential()
        self.net.add(nn.Dense(64, activation='relu'),
                     nn.Dense(32, activation='relu'))
        self.dense = nn.Dense(16, activation='relu')

    def forward(self, x):
        return self.dense(self.net(x))

net = nn.Sequential()
net.add(NestMLP(), nn.Dense(20), FancyMLP())

net.initialize()
net(X)

你可能感兴趣的:(《动手学深度学习》)