动手学深度学习 4 Deep-learning-computation
Posted haricotvert
tags:
篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了动手学深度学习 4 Deep-learning-computation相关的知识,希望对你有一定的参考价值。
4. deep-learning-computaion
4.1 模型构造
4.1.1 继承Block
来构造模型
Block
类是nn
模块里提供的一个模型构造类,我们可以继承它来定义我们想要的模型也可以继承它来构造层。
事实上,Sequential
类继承自Block
类。当模型的前向计算为简单串联各个层的计算时,可以通过更加简单的方式定义模型。这正是Sequential
类的目的:它提供add
函数来逐一添加串联的Block
子类实例,而模型的前向计算就是将这些实例按添加的顺序逐一计算,且无需定义forward
函数。
from mxnet import init, nd
from mxnet.gluon import nn
net = nn.Sequential()
net.add(nn.Dense(256, activation='relu'))
net.add(nn.Dense(10))
net.initialize() # 使用默认初始化方式
X = nd.random.uniform(shape=(2, 20))
Y = net(X) # 前向计算
4.1.2 构造复杂的模型
在构造复杂的网络时,需要直接继承Block
类来拓展模型构造的灵活性。
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()
net = FancyMLP()
net.initialize()
print(net) # 打印网络结构
net(X) # 计算最后一个隐藏层
4.2 模型参数的访问、初始化和共享
4.2.1 访问模型参数
对于使用Sequential
类构造的神经网络,我们可以通过方括号[]
来访问网络的任一层。对于Sequential
实例中含模型参数的层,我们可以通过Block
类的params
属性来访问该层包含的所有参数。
from mxnet import init, nd
from mxnet.gluon import nn
net = nn.Sequential()
net.add(nn.Dense(256, activation='relu'))
net.add(nn.Dense(10))
net.initialize() # 使用默认初始化方式
X = nd.random.uniform(shape=(2, 20))
Y = net(X)
net[0].params, type(net[0].params)
输出
(dense0_ (
Parameter dense0_weight (shape=(256, 20), dtype=float32)
Parameter dense0_bias (shape=(256,), dtype=float32)
),
mxnet.gluon.parameter.ParameterDict)
访问模型参数
net[0].weight # 权重的情况
net[0].weight.data() # 权重数据
net[0].weight.grad() # 梯度数据
可以使用collect_params
函数来获取net
变量所有嵌套(例如通过add
函数嵌套)的层所包含的所有参数。返回的是一个由参数名称到参数实例的字典。这个函数也可以通过正则表达式来匹配参数名,从而筛选需要的参数。
net.collect_params()
输出
sequential0_ (
Parameter dense0_weight (shape=(256, 20), dtype=float32)
Parameter dense0_bias (shape=(256,), dtype=float32)
Parameter dense1_weight (shape=(10, 256), dtype=float32)
Parameter dense1_bias (shape=(10,), dtype=float32)
)
4.2.2 访问模型参数
MXNet的init
模块里提供了多种预设的初始化方法。
# 非首次对模型初始化需要指定force_reinit为真
net.initialize(init=init.Normal(sigma=0.01), force_reinit=True) # 初始化成均值为0,标准差为0.01的正态分布随机数,并将偏差参数清零
net.initialize(init=init.Constant(1), force_reinit=True) # 常数初始化
net[0].weight.initialize(init=init.Xavier(), force_reinit=True) # Xavier随机初始化
4.2.3 自定义初始化方法
有时候需要的初始化方法并没有在init
模块中提供,可以实现一个Initializer
类的子类。通常,我们只需要实现_init_weight
这个函数,并将其传入的NDArray
修改成初始化的结果。在下面的例子里,我们令权重有一半概率初始化为0,有另一半概率初始化为([-10,-5])和([5,10])两个区间里均匀分布的随机数。
class MyInit(init.Initializer):
def _init_weight(self, name, data):
print('Init', name, data.shape)
data[:] = nd.random.uniform(low=-10, high=10, shape=data.shape)
data *= data.abs() >= 5
net.initialize(MyInit(), force_reinit=True)
net[0].weight.data()[0]
输出
Init dense0_weight (256, 20)
Init dense1_weight (10, 256)
[ 0. -0. 9.97694 6.8228035 -7.011034 8.092955
7.36252 -9.248813 -6.750141 0. 0. -6.6630497
-7.5236006 5.5810204 6.960165 7.298666 6.1463795 -0.
0. -7.200548 ]
<NDArray 20 @cpu(0)>
此外,我们还可以通过Parameter
类的set_data
函数来直接改写模型参数。例如,在下例中我们将隐藏层参数在现有的基础上加1。
net[0].weight.set_data(net[0].weight.data() + 1)
net[0].weight.data()[0]
4.2.4 共享模型参数
有些情况下,我们希望在多个层之间共享模型参数。如果不同层使用同一份参数,那么它们在前向计算和反向传播时都会共享相同的参数。下面在构造层的时候使用特定的参数使得参数共享。
net = nn.Sequential()
shared = nn.Dense(8, activation='relu')
net.add(nn.Dense(8, activation='relu'),
shared,
nn.Dense(8, activation='relu', params=shared.params),
nn.Dense(10))
net.initialize()
X = nd.random.uniform(shape=(2, 20))
net(X)
我们在构造第三隐藏层时通过params
来指定它使用第二隐藏层的参数。因为模型参数里包含了梯度,所以在反向传播计算时,第二隐藏层和第三隐藏层的梯度都会被累加在shared.params.grad()
里。
4.3 模型参数的延后初始化
系统根据输入的形状自动推断出所有层的权重参数的形状。系统在创建这些参数之后,调用初始化函数函数对它们进行初始化,然后再进行前向计算。初始化只会在第一次前向计算时被调用。之后再运行net(X)
时则不会重新初始化。
系统将真正的参数初始化延后到获得足够信息时才执行的行为叫作延后初始化(deferred initialization)。它可以让模型的创建更加简单:只需要定义每个层的输出大小,而不用人工推测它们的输入个数。这对于之后将介绍的定义多达数十甚至数百层的网络来说尤其方便。
然而,任何事物都有两面性。正如本节开头提到的那样,延后初始化也可能会带来一定的困惑。在第一次前向计算之前,我们无法直接操作模型参数,例如无法使用data
函数和set_data
函数来获取和修改参数。因此,我们经常会额外做一次前向计算来迫使参数被真正地初始化。
4.3.1 避免延后初始化
如果系统在调用initialize
函数能够知道所有参数的形状,那么延后初始化就不会发生。
第一种情况是我们要对已初始化的模型重新初始化时。因为参数形状不会发生变化,所以系统能够立即进行重新初始化。
第二种情况是在创建层的时候指定了它的输入个数。可以通过in_units
来指定每个全连接层的输入个数。
net = nn.Sequential()
net.add(nn.Dense(256, in_units=20, activation='relu'))
net.add(nn.Dense(10, in_units=256))
net.initialize()
4.4 自定义层
深度学习的一个魅力在于神经网络中各式各样的层,例如全连接层和后面章节中将要介绍的卷积层、池化层与循环层。虽然Gluon提供了大量常用的层,但有时候我们依然希望自定义层。
4.4.1 不含模型参数的自定义层
from mxnet import gluon, nd
from mxnet.gluon import nn
class CenteredLayer(nn.Block):
def __init__(self, **kwargs):
super(CenteredLayer, self).__init__(**kwargs)
def forward(self, x):
return x - x.mean()
4.4.2 含模型参数的自定义层
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)
4.5 读取和存储
在训练模型时,需要将内存中训练好的模型参数存储在硬盘上供后序读取使用。
4.5.1 读写NDArray
我们可以直接使用save
函数和load
函数分别存储和读取NDArray
。下面的例子创建了NDArray
变量x
,并将其存在文件名同为x
的文件里。这是一个二进制文件,也可以将数据从存储的文件读回内存。
from mxnet import nd
from mxnet.gluon import nn
x = nd.ones(3)
nd.save('x', x)
x2 = nd.load('x')
4.5.2 读写Gluon模型的参数
除NDArray以外,我们还可以读写Gluon模型的参数。Gluon的Block类提供了save_parameters
函数和load_parameters
函数来读写模型参数。
4.6 GPU计算
由于电脑没有 NVIDIA GPU,该节暂时不看
以上是关于动手学深度学习 4 Deep-learning-computation的主要内容,如果未能解决你的问题,请参考以下文章