模型构造
上一篇中,模型构造是首先需构造Sequential实例,然后添加各层。MXNet还可以通过继承Block类来构造模型。
下面一个例子中,init函数声明带有模型参数的层,函数使用get_constant方法创建训练中不被迭代的参数,即常数参数。forward函数定义模型的前向计算,通过输入x最终返回输出内容。
class FancyMLP(nn.Block):
def __init__(self, **kwargs):
super(FancyMLP, self).__init__(**kwargs)
# 使用get_constant创建的随机权重参数不会在训练中被迭代(即常数参数)
self.rand_weight = self.params.get_constant(
'rand_weight', nd.random.uniform(shape=(20, 20)))
self.dense = nn.Dense(20, activation='relu')
def forward(self, x):
x = self.dense(x)
# 使用创建的常数参数,以及NDArray的relu函数和dot函数
x = nd.relu(nd.dot(x, self.rand_weight.data()) + 1)
# 复用全连接层。等价于两个全连接层共享参数
x = self.dense(x)
# 控制流,这里我们需要调用asscalar函数来返回标量进行比较
while x.norm().asscalar() > 1:
x /= 2
if x.norm().asscalar() < 0.8:
x *= 10
return x.sum()
模型参数访问
使用Sequential类构造的神经网络,可以通过方括号[]来访问网络的任一层。既可以通过名字来访问字典里的元素,也可以直接使用它的变量名。以下两种方法是等价的,可以显示参数的shape和类型。具体参数和梯度的数值可以分别通过data函数和grad函数来访问。
# 获取参数的名称、shape和类型
net[0].weight
net[0].params['dense0_weight']
# 获取具体数值
net[0].weight.data()
net[0].weight.grad()
最后,我们可以使用collect_params函数来获取net变量所有嵌套的层所包含的所有参数。此函数还可以通过正则表达式来匹配参数名,从而筛选需要的参数。
net.collect_params()
# 筛选以weight结尾的参数
net.collect_params('.*weight')
初始化
MXNet提供了多种初始化方法,下面将权重参数初始化成均值为0、标准差为0.01的正态分布随机数。
# 非首次对模型初始化需要指定force_reinit为真
net.initialize(init=init.Normal(sigma=0.01), force_reinit=True)
此外,MXNet使用延后初始化,让模型获得足够信息后才进行初始化。这样我们无须人工推测每个层的输入个数,让模型构造更加简单。
自定义层
我们还可以通过Block类自定义神经网络中的层。在定义含模型参数的层时,利用Block类自带的ParameterDict类型的成员变量params,通过get函数创建参数实例。以下实现了一个含权重参数和偏差参数的全连接层。in_units和units分别代表输入和输出个数。
class MyDense(nn.Block):
# units为该层的输出个数,in_units为该层的输入个数
def __init__(self, units, in_units, **kwargs):
super(MyDense, self).__init__(**kwargs)
self.weight = self.params.get('weight', shape=(in_units, units))
self.bias = self.params.get('bias', shape=(units,))
def forward(self, x):
linear = nd.dot(x, self.weight.data()) + self.bias.data()
return nd.relu(linear)
读取和存储
使用save函数和load函数分别存储和读取NDArray。
x = nd.ones(3)
nd.save('x', x)
x2 = nd.load('x')
# 存储一列NDArray并读回内存
nd.save('xy', [x, y])
x2, y2 = nd.load('xy')
# 存储并读取从字符串映射到NDArray的字典
mydict = {'x': x, 'y': y}
nd.save('mydict', mydict)
mydict2 = nd.load('mydict')
GPU计算
通过copyto函数和as_in_context函数在设备之间传输数据。两者的区别是,copyto函数总是为目标变量开新的内存或显存,而如果源变量和目标变量的context(CPU或GPU)一致,as_in_context函数使目标变量和源变量共享源变量的内存或显存。
# 将内存上的NDArray变量x复制到GPU上
y = x.copyto(mx.gpu())
z = x.as_in_context(mx.gpu())