(一)继承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后子类需要从父类继承的属性被初始化的问题。
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()函数,最后返回计算结果为:xblock_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)