【mxnet】【gluon.nn】模型的构建(Sequential,block)、模型参数(Parameter)、初始化(延迟、立即)
来源与参考资料:
https://blog.csdn.net/bea_tree/article/details/80152223
https://blog.csdn.net/tsq292978891/article/details/104261080
https://blog.csdn.net/bea_tree/article/details/80152223
https://www.cnblogs.com/hellcat/p/9046655.html
什么是Gluon ?
Gluon是MXNet的动态图接口;Gluon学习了Keras,Chainer,和Pytorch的优点,并加以改进。接口更简单,且支持动态图(Imperative)编程。相比TF,Caffe2等静态图(Symbolic)框架更加灵活易用。同时Gluon还继承了MXNet速度快,省显存,并行效率高的优点,并支持静、动态图混用,比Pytorch更快。
前言:gluon.nn中的内容
['Activation', 'AvgPool1D', 'AvgPool2D', 'AvgPool3D', 'BatchNorm',
‘Block’, 'Conv1D' 'Conv1DTranspose', 'Conv2D', 'Conv2DTranspose', 'Conv3D', 'Conv3DTranspose',
‘Dense’,
'Dropout', 'ELU', 'Embedding', 'Flatten', 'GELU', 'GlobalAvgPool1D', 'GlobalAvgPool2D', 'GlobalAvgPool3D', 'GlobalMaxPool1D', 'GlobalMaxPool2D', 'GlobalMaxPool3D', 'GroupNorm',
’HybridBlock’, 'HybridLambda',
‘HybridSequential’, 'InstanceNorm', 'Lambda', 'LayerNorm', 'LeakyReLU', 'MaxPool1D', 'MaxPool2D', 'MaxPool3D', 'PReLU', 'ReflectionPad2D', 'SELU',
’Sequential’, 'Swish',
‘SymbolBlock’,
'__builtins__', '__cached__', '__doc__', '__file__', '__loader__', '__name__', '__package__', '__path__', '__spec__',
'activations', 'basic_layers', 'conv_layers']
黑色加粗体模块代表的是经常用到的模块。
模块关系
chap1 模型的构造方法
一. gluon.nn.Block
作用:Gluon中主要使用Block来构造模型,Block是Gluon里的一个类,位于incubator-mxnet/python/mxnet/gluon/block.py
.
1.1使用Block简单定义一个模型(类)
定义
使用Block无需定义求导或反传函数,MXNet会使用autograd对forward自动生成相应的backward函数
class MLP(nn.block):
def __init__(self,**kwargs):
super(MLP,self).__init__(**kwargs)
with self.name_scope():
self.hidden =nn.Dense(256,active='relu')
self.output =nn.Dense(10)
def forward(self,x)
return self.output(self.hidden(x))
模型解释
- 定义一个继承
nn.Block
的类MLP
- 网络模型的含义是:多层感知机
- 隐藏层:256个神经元的全连接层,激活函数为’relu’
- 输出层:10个神经元(输出10个分类)
查看模型
通过模型实例化的方法查看模型的构造。
net2=MLP() #实例化
net2
out:
MLP(
(dense0): Dense(None -> 256, linear)
(dense1): Dense(None -> 10, linear)
)
模型构造
- 可以看到nn.Block的使用是通过创建一个它子类的类,至少包含了两个函数。
__init__
:创建参数。上面的栗子我们使用了包含了参数的dense层forward()
:定义网络的计算 我们所创建的类的使用跟前面的net没有太多不一样
函数解析
super(MLP, self).__init__(**kwargs)
:这句话调用nn.Block的__init__
函数,它提供了prefix
(指定名字)和params
(指定给模型参数)两个参数。self.name_scope()
:调用nn.Block
提供的name_scope()
函数。nn.Dense
的定义放在这个scope
里面。它的作用是给里面的所有层和参数的名字加上 前缀(prefix) 使得他们在系统里面独一无二。默认自动会自动生成前缀,我们也可以在创建的时候手动指定。- 调用
with xxx.name_scope
时候,实际调用的是Block
的name_scope
函数,而name_scope
返回的是self._scope
,Block在构造的时候,会创建self._scope
属性,而self._scope=_BlockScope(self)
,所以with语句最终调用的是_BlockScope
。
!!再强调一遍,name_scope函数是Block中所提供的,在继承Block类的时候,回自动继承该函数。
初始化并计算
定义模型的方式是命令式编程,即动态图。可以直接初始化(initialize),然后带入变量进行计算。
构造数据集
# 构造一个ndarry 数据集
from mxnet import ndarray as nd
x=nd.random.uniform(shape=(4,20))
初始化 并计算
net2.initialize() # 初始化
y=net2(x)
y
out:
[[ 0.03101085 0.02168356 0.0295247 -0.04508945 0.1063665 -0.01549807
-0.01931991 -0.0174359 0.08324829 -0.02836939]
[ 0.02866081 0.0270328 0.04569577 0.00374631 0.05676577 -0.03816601
-0.02232856 0.00256437 0.03291165 -0.01463041]
[ 0.01507497 -0.0098699 0.02329455 0.00512386 0.05632419 -0.04279631
0.00370565 -0.04851181 0.06240019 -0.0090477 ]
[ 0.01429253 -0.01069933 0.0241727 0.01729557 0.05941372 -0.04356314
0.02740986 -0.03211073 0.04626467 -0.0012433 ]]
<NDArray 4x10 @cpu(0)>
查看前缀名字
print('defulit prefix:' ,net2.dense0.name) #查看默认的前缀名字
net3=MLP(prefix='another_mlp_') #手动设置前缀名字
print('customized prefix:',net3.dense0.name)
out:
defulit prefix: mlp1_dense0
customized prefix: another_mlp_dense0
注意
大家会发现这里并没有定义如何求导,或者是backward()
函数。事实上,系统会使用autograd
对forward()
自动生成对应的backward()
函数
1.3 使用Block灵活定义一个模型
手动创建参数
class FancyMLP(nn.Block):
def __init__(self, **kwargs):
super(FancyMLP, self).__init__(**kwargs)
with self.name_scope():
self.dense=nn.Dense(256)
self.weight=nd.random_uniform(shape=(256,20)) #手动创建weight
def forward(self,x):
x=nd.relu(self.dense(x))
print('layer1:',x)
x=nd.relu(nd.dot(x, self.weight)+1)
print('layer2:',x)
x=nd.relu(self.dense(x))
return x
实例化并计算
fancy_mlp=FancyMLP()
fancy_mlp.initialize()
y=fancy_mlp(x)
y
手动创建常量参数
在前向计算中使用了NDArray函数和Python的控制流:
- forward函数内部是自由发挥的舞台
- 多次调用同一层
class FancyMLP2(nn.Block):
def __init__(self, **kwargs):
super(FancyMLP2, 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()
实例化并计算
fancy_mlp=FancyMLP2()
fancy_mlp.initialize()
y=fancy_mlp(x)
y
1.3 nn.Block 的本质
在gluon
里,nn,Block
是一个一般化的东西。整个神经网络可以是一个nn.Block
,单个层也可以是一个nn.Block
。我们可以(近似)无限嵌套nn.Block
来构建新的nn.Block
.
nn.Block
主要提供这个东西
- 存储参数
- 描述
forward
如何执行- 自动求导
二、 gluon.nn.Sequential
- 类
Sequential
是Block
的一个子类,位于incubator-mxnet/python/mxnet/gluon/nn/basic_layers.py。 - sequential:含义为顺序的
nn.Sequential
也可以看作是一个nn.Block
容器,它通过add来
添加nn.Block
。它自动生成forward*()
函数,其就是把加进来的nn.Block
逐一运行。
2.1 简单构造一个Sequential
定义
class Sequential(nn.Block):
def __init__(self, **kwargs):
super(Sequential, self).__init__(**kwargs)
def add(self, block):
self._children[block.name] = block
def forward(self, x):
# OrderedDict保证会按照成员添加时的顺序遍历成员
for block in self._children.values():
x = block(x)
return x
函数解释
add
函数中block
是⼀个Block⼦类实例
,假设它有⼀个独⼀⽆⼆的名字。我们将它保存在Block类的 成员变量_children⾥,其类型是OrderedDict。当Sequential实例
调⽤initialize函数
时,系统会⾃动对_children
⾥所有成员初始化.OrderedDict保证会按照成员添加时的顺序遍历成员
模型初始化并计算
net4=Sequential() # 实例化
with net4.name_scope():
net4.add(nn.Dense(256,activation='relu'))
net4.add(nn.Dense(10))
net4.initialize()
y=net4(x)
y
out: # x是上文的shape(4,20)
[[-0.01733222 0.0176371 0.01002938 -0.00697294 -0.01776312 -0.01870181
0.00586433 0.01294655 -0.01341713 0.05589377]
[ 0.01121028 0.01951449 -0.0053646 -0.05023907 -0.04137681 0.022567
-0.01802346 -0.00340049 -0.02069135 0.06066972]
[-0.03452056 0.0267077 0.02450367 -0.00812524 0.00079923 0.03207999
-0.01413449 0.03577802 0.01179684 0.09542421]
[ 0.00043382 0.01306088 0.04191748 -0.00215333 0.00136736 0.02795672
-0.01280743 0.03023343 0.00559268 0.05043183]]
<NDArray 4x10 @cpu(0)>
函数解析
wiht net4.name_scope()
: 是调用这个模型的scope空间.并且在这个空间内,依照添加顺序放入容器Sequential中。
三、Block和Sequential的嵌套使用
class RecMLP(nn.Block):
def __init__(self, **kwargs):
super(RecMLP, self).__init__(**kwargs)
self.net=nn.Sequential()
with self.name_scope():
self.net.add(nn.Dense(256,activation='relu'))
self.net.add(nn.Dense(128,activation='relu'))
self.dense=nn.Dense(64)
def forward(self, x):
return nd.relu(self.dense(self.net(x)))
rec_mlp=nn.Sequential()
rec_mlp.add(RecMLP())
rec_mlp.add(nn.Dense(10))
print(rec_mlp)
out:
Sequential(
(0): RecMLP(
(net): Sequential(
(0): Dense(None -> 256, Activation(relu))
(1): Dense(None -> 128, Activation(relu))
)
(dense): Dense(None -> 64, linear)
)
(1): Dense(None -> 10, linear)
)
解释
Q:嵌套体现在哪里?
class RecMLP
中定义了一个Sequential容器,并在这个容器中添加了3个全连接层(nn.Dense)- 首先对
nn.Sequential()
实例化的对象为rec_mlp
- 在
rec_mlp
这个顺序容器中添加模块RecMLP()
&nn.Dense()
chap 2 模型参数及初始化
前言
简单的模型并初始化运算
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)
y # 返回一个shape为(2,10)的ndarry
参数关系
类class Parameter
在Gluon 中模型参数的类型是Parameter
class Parameter(builtins.object)
| Parameter(name, grad_req='write', shape=None, dtype=<class 'numpy.float32'>, lr_mult=1.0, wd_mult=1.0, init=None, allow_deferred_init=False, differentiable=True, stype='default', grad_stype='default')
一个容器,其中包含块的参数(权重)。
参数:
- name : str parameter的名称
- grad_req : {‘write’, ‘add’, ‘null’}, default ‘write’ 更新梯度的方式
'write'
每次将梯度写入NDArray'add'
每次将梯度加入NDArray,使用此选项时,您需要在每次迭代之前手动调用zero_grad()清除梯度缓冲区。- ‘null’ 参数没有梯度,梯度数组的空间将不再分配
- shape : int or tuple of int, default None 默认情况下未指定形状。 形状未知的参数可用于
Symbol API
,但是使用NDArray API
时,init将引发错误。- dtype : numpy.dtype or str, default ‘float32’ 此参数的数据类型。 例如
numpy.float32''或
’float32’’’。- lr_mult : float, default 1.0 .学习率乘数。 学习率将乘以lr_mult, 使用优化器更新此参数时。
- wd_mult : float, default 1.0 权重衰减乘数(L2正则化系数)。 与lr_mult类似。
- init : Initializer, default None 此参数的初始化程序。 默认情况下将使用全局初始化器。
- stype: {‘default’, ‘row_sparse’, ‘csr’}, defaults to ‘default’. 参数的存储类型。
- grad_stype: {‘default’, ‘row_sparse’, ‘csr’}, defaults to ‘default’. 参数的梯度的存储类型。
属性:
grad_req : {'write', 'add', 'null'}
可以在初始化之前或之后进行设置。 在不需要梯度w.r.t x的情况下,使用x.grad_req ='null'
将grad_req设置为``null’'可以节省内存和计算量。lr_mult
: float.学习率乘数,可以使用param.lr_mult = 2.0
wd_mult
: float,参数的局部权重衰减系数
方法:
__init__(self, name, grad_req='write', shape=None, dtype=<class 'numpy.float32'>, lr_mult=1.0, wd_mult=1.0, init=None, allow_deferred_init=False, differentiable=True, stype='default', grad_stype='default')
初始化__repr__(self)
返回repr(self)。cast(self, dtype)
将此参数的数据和渐变转换为新的数据类型。data(self, ctx=None)
在一个上下文中返回此参数的副本。 必须在此上下文上初始化过。grad(self, ctx=None)
** 在一个上下文中返回此参数的梯度缓冲区**。initialize(self, init=None, ctx=None, default_init=<mxnet.initializer.Uniform object at 0x000001C6F7E22608>, force_reinit=False)
初始化参数和渐变数组。 仅用于NDArray API。list_ctx(self)
返回初始化此参数的上下文列表。list_data(self)
以与创建相同的顺序在所有上下文中返回此参数的副本。list_grad(self)
以相同顺序返回所有上下文上的梯度缓冲区list_row_sparse_data(self, row_id)
以与创建相同的顺序返回所有上下文中“ row_sparse”参数的副本。 该副本仅保留其ID出现在提供的行ID中的行。该参数必须已初始化。reset_ctx(self, ctx)
将参数重新分配给其他上下文。row_sparse_data(self, row_id)
在与row_id相同的上下文中返回’row_sparse’参数的副本。 该副本仅保留其ID出现在提供的行ID中的行。 必须先在此上下文上初始化参数。set_data(self, data)
在所有上下文中设置此参数的值。var(self)
返回表示此参数的符号。zero_grad(self)
将所有上下文上的渐变缓冲区设置为0。如果参数未初始化或不需要渐变,则不执行任何操作。
类class ParameterDict
class ParameterDict(builtins.object)
| ParameterDict(prefix='', shared=None)
一组参数的字典管理
参数:
- prefix : str, default,该dict创建的所有Parameters名称之前的前缀。
- shared : ParameterDict or None | 如果不是“ None”,则当此字典的:py:meth:
get
方法创建新参数时,将首先尝试从“共享”字典中检索它。 通常用于与另一个块共享参数。
常用的方法
- ·
__getitem__(self, key)
- ·
__init__(self, prefix='', shared=None)
初始化 - ·
__iter__(self)
__repr__(self)
Return repr(self).get(self, name, **kwargs)
先在“共享字典”中检索,如果没有,则创建get_constant(self, name, value=None)
同理对于含关键字的常量参数,如上initialize(self, init=xxx , ctx=None, verbose=False, force_reinit=False)
load(self, filename, ctx=None, allow_missing=False, ignore_extra=False, restore_prefix='', cast_dtype=False, dtype_source='current')
从文件中加载参数- ·
save(self, filename, strip_prefix='')
保存参数到文件 setattr(self, name, value)
将所有参数的属性设置为新值。update(self, other)
将``other’'中的所有参数复制到自身。zero_grad(self)
将所有参数的渐变缓冲区设置为0。values(self)
一、访问模型参数
collect_params
来访问Block ⾥的所有参数。- 通过
[]
来访问Sequential类构造出来的网络的特定层。 - 对于带有模型参数的层,可以通过Block类的
params
属性来得到它包含的所有参数。
1.1查看特定层的参数
查看隐藏层的参数
print(net[0].params)
print(net[0].collect_params())
out:
dense5_ (
Parameter dense5_weight (shape=(256, 20), dtype=float32)
Parameter dense5_bias (shape=(256,), dtype=float32)
)
dense5_ (
Parameter dense5_weight (shape=(256, 20), dtype=float32)
Parameter dense5_bias (shape=(256,), dtype=float32)
)
1.2 查看模型的参数
print(net.params)
print(net.collect_params())
out:
sequential7_ (
)
sequential7_ (
Parameter dense5_weight (shape=(256, 20), dtype=float32)
Parameter dense5_bias (shape=(256,), dtype=float32)
Parameter dense6_weight (shape=(10, 256), dtype=float32)
Parameter dense6_bias (shape=(10,), dtype=float32)
)
1.3 访问特定参数
- 可以通过名字来访问字典里的元素
- 也可以直接使用它的变量名
下面两种方法是等价的,但通常后者的代码可读性更好。
(net[0].params['dense5_weight'], net[0].weight)
out:
(Parameter dense5_weight (shape=(256, 20), dtype=float32),
Parameter dense5_weight (shape=(256, 20), dtype=float32))
1.4 访问Parameter类
Parameter类,是指mxnet.gluon.Parameter
具有方法:.data()
/ .grad()
/ .set_data()
/ .initialize()
……
参数包括其值和梯度,可以分别通过data和grad函数来访问:
(net[0].weight.data(), net[0].weight.grad())
out:
(
[[ 0.00209901 0.05904067 -0.06864434 ... -0.04006305 -0.01206786
0.03797156]
[-0.01189543 -0.06829604 -0.06551221 ... -0.03798948 0.01231539
0.02479976]
[ 0.05571384 0.01300324 0.05883805 ... -0.03858209 -0.05259956
0.0635146 ]
...
[ 0.03575765 -0.05932823 0.01155707 ... 0.01381769 0.06261503
-0.0523302 ]
[ 0.06805447 0.06285384 -0.0445598 ... -0.03939923 -0.0129104
-0.02384796]
[-0.05508799 0.05976071 0.05924045 ... 0.04503661 0.04228228
-0.06546508]]
<NDArray 256x20 @cpu(0)>,
[[0. 0. 0. ... 0. 0. 0.]
[0. 0. 0. ... 0. 0. 0.]
[0. 0. 0. ... 0. 0. 0.]
...
[0. 0. 0. ... 0. 0. 0.]
[0. 0. 0. ... 0. 0. 0.]
[0. 0. 0. ... 0. 0. 0.]]
<NDArray 256x20 @cpu(0)>)
1.5 声明Parameter类
Parameter(‘name’,shape=())
声明需要名字和尺寸:
my_param = gluon.Parameter('exciting_parameter_yay', shape=(3, 3))
my_param.initialize()
my_param.data() # 访问
out:
[[-0.01207382 -0.03414407 0.02478623]
[ 0.04532047 -0.00666415 -0.03741182]
[-0.04191073 -0.02651191 0.06012652]]
<NDArray 3x3 @cpu(0)>
params.get(‘name’,shape=())
我们还可以使用Block自带的ParameterDict
类的成员变量params。顾名思义,这是一个由字符串类型的参数名字映射到Parameter类型的模型参数的字典。
我们可以通过get
函数从ParameterDict
创建Parameter
,同样的声明需要名字和尺寸:
params = gluon.ParameterDict()
params.get("param2", shape=(2, 3))
params
out:
(
Parameter param2 (shape=(2, 3), dtype=<class 'numpy.float32'>)
)
形如net.params返回的就是一个ParameterDict类,自己的层class书写时,就是用这个方式创建参数并被层class感知(收录进层隶属的ParameterDict类中)。
get(self, name, **kwargs)
- 含义:在类
Parmeter
中检索self.prefix + name
,如果没找到,首先在“共享”字典中检索它,如果仍然找不到,则创建一个带有关键字参数的新的Parmeter
,并将其插入到self中。- name : str 所需参数的名称。 它将以该词典的前缀开头。
- **kwargs : dict 所创建的`Parameter的其余关键字参数。
二、初始化模型参数
2.1 默认模型初始化
当使用默认的模型初始化,Gluon会将权重参数元素初始化为[-0.07, 0.07]之间均匀分布的随机数,偏差参数则全为0.
但经常我们需要使用其他的方法来初始话权重,MXNet的init模块里提供了多种预设的初始化方法。例如下面例子我们将权重参数初始化成均值为0,标准差为0.01的正态分布随机数。
# 非首次对模型初始化需要指定 force_reinit
net.initialize(init=init.Normal(sigma=0.01), force_reinit=True)
- mxnet.init模块
- force_reinit为了防止用户失误将参数全部取消
2.2 其他模型初始化
- 模型的其他初始化方法还有:均匀分布uniform、正太分布normal、初始化为常数constant、xavier_init()
具体请参考我的文章《【理论】【代码】参数随机初始化方法》
2.3 自定义初始化函数
有时候我们需要的初始化方法并没有在init模块中提供,这时我们有两种方法来自定义参数初始化。
方法一
一种是实现一个Initializer类的子类
使得我们可以跟前面使用init.Normal
那样使用它。在这个方法里,我们只需要实现_init_weight
这个函数,将其传入的NDArray
修改成需要的内容。下面例子里我们把权重初始化成[-10,-5]和[5,10]两个区间里均匀分布的随机数.
注:教程只讲了weight初始化,后面查看源码还有bias的,以及一些其他的子方法,理论上用时再研究,不过方法二明显更简单易用……
默认初始化的方法
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].weight.data()[0]
out: 初始化为[-0.07, 0.07]之间均匀分布的随机数,偏差参数则全为0.
[ 0.03714553 0.0345792 0.0348129 -0.05703654 0.05652077 -0.03955693
-0.05832086 0.03489561 0.00730694 0.03243712 0.01182665 -0.06361395
0.06467109 -0.04071802 -0.02909935 -0.02983189 -0.03628397 0.02481686
-0.05595885 -0.06117464]
<NDArray 20 @cpu(0)>
自定义初始化子类
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]
out: 数据采用[-10 ,10]之间的均匀分布初始化
Init dense6_weight (256, 20)
Init dense7_weight (10, 256)
[-0. 5.0426903 0. 6.592045 7.2110233 -0.
0. -8.069008 -0. -0. -7.370344 0.
-8.892513 -0. -0. -0. -0. -0.
-0. 0. ]
<NDArray 20 @cpu(0)>
方法二
通过Parameter类的set_data函数
可以直接改写模型参数。例如下例中我们将隐藏层参数在现有的基础上加1..请跟上面的结果对比。
net[0].weight.set_data(net[0].weight.data()+1)
net[0].weight.data()[0]
out: 上面结果+1
[ 1. 6.0426903 1. 7.592045 8.211023 1.
1. -7.069008 1. 1. -6.370344 1.
-7.8925133 1. 1. 1. 1. 1.
1. 1. ]
<NDArray 20 @cpu(0)>
三、共享模型参数
在有些情况下,我们希望在多个层之间共享模型参数。我们在“模型构造”的(1.3节)这一节看到了如何在Block类里forward函数里多次调用同一个类来完成。这里将介绍另外一个方法,它在构造层的时候指定使用特定的参数。如果不同层使用同一份参数,那么它们不管是在前向计算还是反向传播时都会共享共同的参数。
原理:层函数params API
接收其他层函数的params
属性即可
方法一
在下面例子里,我们让模型的第二隐藏层和第三隐藏层共享模型参数:
from mxnet import nd
from mxnet.gluon import nn
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)
net[1].weight.data()[0] == net[2].weight.data()[0]
out:
[1. 1. 1. 1. 1. 1. 1. 1.]
<NDArray 8 @cpu(0)>
我们在构造第三隐藏层时通过·params·来指定它使用第二隐藏层的参数。由于模型参数里包含了梯度,所以在反向传播计算时,第二隐藏层和第三隐藏层的梯度都会被累加在shared.params.grad()
里。
方法二
隐藏层的第一层和第二层共享参数
net=nn.Sequential()
with net.name_scope():
net.add(nn.Dense(4, in_units=4, activation='relu'))
net.add(nn.Dense(4, in_units=4, activation='relu',params=net[-1].params) )
net.add(nn.Dense(2, in_units=4))
net.initialize(init=init.Normal(sigma=0.02),force_reinit=True)
net[1].weight.data()[0] == net[2].weight.data()[0]
out:
[0. 0. 0. 0.]
<NDArray 4 @cpu(0)>
四、模型延后初始化
从下面jupyter可以看到模型参数实际初始化的时机:既不是调用initialize时,也不是没次运行时,仅仅第一次送入数据运行时会调用函数进行参数初始化,所以MXNet不需要定义指定输入数据尺寸的关键也在这里。
4.1 自动推测参数的大小
在之后做forward的时候会自动推测参数的大小。具体看怎么工作的。 新创建一个网络,然后打印参数。你会发现两个全连接层的权重形状里都有0,这是因为在不知道输入数据的形况下,我们无法判断它们的形状。
def get_net():
net=nn.Sequential()
with net.name_scope():
net.add(nn.Dense(4, activation='relu'))
net.add(nn.Dense(2))
return net
net=get_net()
print(net.collect_params())
out: # 权重形状里面有0,不清楚输入数据
sequential11_ (
Parameter sequential11_dense0_weight (shape=(4, 0), dtype=float32)
Parameter sequential11_dense0_bias (shape=(4,), dtype=float32)
Parameter sequential11_dense1_weight (shape=(2, 0), dtype=float32)
Parameter sequential11_dense1_bias (shape=(2,), dtype=float32)
)
4.2 延后初始化
from mxnet import init,nd
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'))
net.add(nn.Dense(10))
net.initialize(init=MyInit())
注意到,MyInit在调用时会打印信息,但当前我们并没有看到相应的日志。下面我们执行前向计算。
x=nd.random.uniform(shape=(2,20))
y=net(x)
out:
Init dense12_weight (256, 20)
Init dense13_weight (10, 256)
这时候,有关模型参数的信息被打印出来。在根据输⼊X做前向计算时,系统能够根据输⼊的形
状⾃动推断出所有层的权重参数的形状。系统在创建这些参数之后,调⽤MyInit实例对它们进⾏初始化,然后才进⾏前向计算。
当然,这个初始化只会在第⼀次前向计算时被调⽤。之后我们再运⾏前向计算net(X)时则不会重新初始化,因此不会再次产⽣MyInit实例的输出。
y=net(x)
out: 无结果产生,因为不会重新初始化
4.3立即初始化参数
如果系统在调⽤initialize函数时能够知道所有参数的形状,那么延后初始化就不会发⽣。
我们在这⾥分别介绍两种这样的情况。
第⼀种情况
是我们要对已初始化的模型重新初始化时。因为参数形状不会发⽣变化,所以系统能够⽴即进⾏重新初始化。
net.initialize(init=MyInit(), force_reinit=True)
out: # 有打印,迫使重新初始化了
Init dense12_weight (256, 20)
Init dense13_weight (10, 256)
第⼆种情况
是我们在创建层的时候指定了它的输⼊个数,使系统不需要额外的信息来推测参数形状。下例中我们通过in_units来指定每个全连接层的输⼊个数,使初始化能够在initialize函数被调⽤时⽴即发⽣。
net = nn.Sequential()
net.add(nn.Dense(256, in_units=20, activation='relu'))
net.add(nn.Dense(10, in_units=256))
net.initialize(init=MyInit())
out: # 有打印,则初始化了
Init dense14_weight (256, 20)
Init dense15_weight (10, 256)
数据形状改变时网络行为
如果在下一次net(x)前改变x形状,包括批量大小和特征大小,会发生什么?
批量大小改变不影响,特征大小改变会报错.