模型参数的延后初始化
最近在B站看沐神的动手学深度学习视频,记录一下学习过程
查看本文的jupyter notebook格式,更加清晰美观哦!
系统将真正的参数初始化延后到获得足够多的信息之后才执行的行为叫延后初始化(deferred initialization)。它可以使得模型的创建变得更加简单:只需定义每层输出,不需要人工推测输入个数。这对于定义多大数十甚至数百层的网络来说尤其方便。但是延后初始化也可能带来一些困扰。在第一次前向计算之前,我们无法直接操作模型参数,例如无法使用data函数和set_data函数来获取和修改参数。因此,我们经常会做一次额外的前向计算来迫使模型被真正地初始化。
from mxnet import nd, init
from mxnet.gluon import nn
class MyInit(init.Initializer):
def _init_weight(self, name, data):
print('Init', name, data.shape)
# 实际的初始化逻辑在此省略了
net = nn.Sequential()
net.add(nn.Dense(256, activation='relu'),
nn.Dense(10))
net.initialize(init=MyInit())
由上面的运行可知,调用initialize函数时,并没有真正初始化模型参数。下面定义输入并执行一次前向计算。
X = nd.random.uniform(shape=(2, 20))
Y = net(X)
Init dense7_weight (256, 20)
Init dense8_weight (10, 256)
在根据输入做前向计算时,系统能够根据输入的形状自动推断出所有层的权重参数的形状。系统在创建这些参数之后,调用MyInit实例对它们进行初始化,然后才进行前向计算。当然,这种初始化只会在第一次前向计算时被调用,之后再进行前向计算时,不会重新初始化。
Y = net(X)
避免延后初始化。如果系统在调用initialize函数时能够知道所以参数的形状,那么延后初始化就不会发生。有两种情况不会出现延后初始化。
第一种情况是我们要对已初始化的模型重新进行初始化时。这时,由于模型参数的形状不会发生改变,所以系统能够立即进行重新初始化。
net.initialize(init=MyInit(), force_reinit=True)
Init dense7_weight (256, 20)
Init dense8_weight (10, 256)
第二种情况是我们在创建层的时候指定输入的个数,使系统不需要额外的信息来推测参数的形状。下例中我们通过in_units来指定每个全连接层的输入个数,使初始化能够在initialize函数调用时立即发生。
net = nn.Sequential()
net.add(nn.Dense(256, in_units=20, activation='relu'),
nn.Dense(10, in_units=256))
net.initialize(init=MyInit())
Init dense9_weight (256, 20)
Init dense10_weight (10, 256)
自定义层
不含模型参数的自定义层。下面这个CenteredLayer类通过继承Block类定义了一个将输入减去均值之后输出的层,并将层的计算定义在了forward函数里。这个层里不含模型参数。
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()
实例化,并进行前向计算
layer = CenteredLayer()
layer(nd.array([1, 2, 3, 4, 5]))
[-2. -1. 0. 1. 2.]
<NDArray 5 @cpu(0)>
也可以构造更加复杂的模型
net = nn.Sequential()
net.add(nn.Dense(128),
CenteredLayer())
net.initialize()
打印自定义层各个输出的均值。因为均值是浮点数,所以它是一个很接近0的数。
y = net(nd.random.uniform(shape=(4, 8)))
y.mean().asscalar()
4.7293724e-11
含模型参数的自定义层。在自定义含模型参数的层时,可以利用Block类自带的ParameterDict类型的成员变量params。它是一个由字符串类型的参数名字映射到Parameter类型的模型参数的字典。我们可以通过get函数从ParameterDict创建Parameter实例。
params = gluon.ParameterDict()
params.get("param2", shape=(2, 3))
params
(
Parameter param2 (shape=(2, 3), dtype=<class 'numpy.float32'>)
)
尝试实现一个含权重参数和偏差参数的全连接层。它使用ReLU函数作为激活函数,其中in_units和units分别代表输入个数和输出个数。
class MyDense(nn.Block):
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)
实例化MyDense类,并访问它的模型参数。
dense = MyDense(units=3, in_units=5)
dense.params
mydense1_ (
Parameter mydense1_weight (shape=(5, 3), dtype=<class 'numpy.float32'>)
Parameter mydense1_bias (shape=(3,), dtype=<class 'numpy.float32'>)
)
使用自定义层做前向计算
dense.initialize()
dense(nd.random.uniform(shape=(2, 5)))
[[0. 0. 0.06959771]
[0.01722693 0. 0.02132036]]
<NDArray 2x3 @cpu(0)>
使用自定义层构造模型,它和Gluon的其它层在使用上类似。
net = nn.Sequential()
net.add(nn.Dense(8, in_units=64),
nn.Dense(1, in_units=8))
net.initialize()
net(nd.random.uniform(shape=(2, 64)))
[[ 0.0092427 ]
[-0.00526328]]
<NDArray 2x1 @cpu(0)>
读取和存储
有时候需要把训练好的模型部署到很多不同的设备。在这种情况下,可以把内存中训练好的模型参数存储在硬盘上供后续读取使用。
读写NDArray。可以直接使用save函数和load函数分别存储和读取NDArray。
from mxnet import nd
from mxnet.gluon import nn
x = nd.ones(3)
nd.save('x', x)
将数据从存储的文件读回内存
x2 = nd.load('x')
x2
[
[1. 1. 1.]
<NDArray 3 @cpu(0)>]
存储一列NDArray并读回内存
y = nd.zeros(4)
nd.save('xy', [x, y])
x2, y2 = nd.load('xy')
(x2, y2)
(
[1. 1. 1.]
<NDArray 3 @cpu(0)>,
[0. 0. 0. 0.]
<NDArray 4 @cpu(0)>)
存储并读取一个从字符串映射到NDArray的字典
mydict = {'x':x, 'y':y}
nd.save('mydict', mydict)
mydict2 = nd.load('mydict')
mydict2
{'x':
[1. 1. 1.]
<NDArray 3 @cpu(0)>,
'y':
[0. 0. 0. 0.]
<NDArray 4 @cpu(0)>}
读写Gluon模型的参数。Gluon的Block类提供了save_parameters函数和load_parameters函数来读写模型参数。
class MLP(nn.Block):
def __init__(self, **kwargs):
super(MLP, self).__init__(**kwargs)
self.hidden = nn.Dense(256, activation='relu')
self.output = nn.Dense(10)
def forward(self, x):
return self.output(self.hidden(x))
net = MLP()
net.initialize()
X = nd.random.uniform(shape=(2, 20))
Y = net(X)
filename = 'mlp.params'
net.save_parameters(filename)
net2 = MLP()
net2.load_parameters(filename)
Y2 = net2(X)
Y2 == Y
[[1. 1. 1. 1. 1. 1. 1. 1. 1. 1.]
[1. 1. 1. 1. 1. 1. 1. 1. 1. 1.]]
<NDArray 2x10 @cpu(0)>
Y.context
cpu(0)

这篇博客介绍了深度学习模型参数的延后初始化概念,以及如何在MXNet中实现。通过实例展示了如何在初始化时指定输入个数避免延后初始化,以及如何创建和使用自定义层,包括不含模型参数和含模型参数的自定义层。同时,讲解了如何保存和加载模型参数,以及NDArray和Gluon模型的读写操作。

被折叠的 条评论
为什么被折叠?



