【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时候,实际调用的是Blockname_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()函数。事实上,系统会使用autogradforward()自动生成对应的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主要提供这个东西

  1. 存储参数
  2. 描述forward如何执行
  3. 自动求导

二、 gluon.nn.Sequential

  • SequentialBlock的一个子类,位于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’. 参数的梯度的存储类型。

属性

  1. grad_req : {'write', 'add', 'null'}可以在初始化之前或之后进行设置。 在不需要梯度w.r.t x的情况下,使用x.grad_req ='null'将grad_req设置为``null’'可以节省内存和计算量。
  2. lr_mult : float.学习率乘数,可以使用param.lr_mult = 2.0
  3. wd_mult : float,参数的局部权重衰减系数

方法

  1. __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')初始化
  2. __repr__(self)返回repr(self)。
  3. cast(self, dtype)将此参数的数据和渐变转换为新的数据类型。
  4. data(self, ctx=None) 在一个上下文中返回此参数的副本。 必须在此上下文上初始化过
  5. grad(self, ctx=None)** 在一个上下文中返回此参数的梯度缓冲区**。
  6. initialize(self, init=None, ctx=None, default_init=<mxnet.initializer.Uniform object at 0x000001C6F7E22608>, force_reinit=False)初始化参数和渐变数组。 仅用于NDArray API。
  7. list_ctx(self) 返回初始化此参数的上下文列表。
  8. list_data(self)以与创建相同的顺序在所有上下文中返回此参数的副本。
  9. list_grad(self)以相同顺序返回所有上下文上的梯度缓冲区
  10. list_row_sparse_data(self, row_id)以与创建相同的顺序返回所有上下文中“ row_sparse”参数的副本。 该副本仅保留其ID出现在提供的行ID中的行。该参数必须已初始化。
  11. reset_ctx(self, ctx)将参数重新分配给其他上下文。
  12. row_sparse_data(self, row_id)在与row_id相同的上下文中返回’row_sparse’参数的副本。 该副本仅保留其ID出现在提供的行ID中的行。 必须先在此上下文上初始化参数。
  13. set_data(self, data)在所有上下文中设置此参数的值。
  14. var(self)返回表示此参数的符号。
  15. 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方法创建新参数时,将首先尝试从“共享”字典中检索它。 通常用于与另一个块共享参数。

常用的方法

  1. ·__getitem__(self, key)
  2. ·__init__(self, prefix='', shared=None)初始化
  3. · __iter__(self)
  4. __repr__(self) Return repr(self).
  5. get(self, name, **kwargs)先在“共享字典”中检索,如果没有,则创建
  6. get_constant(self, name, value=None)同理对于含关键字的常量参数,如上
  7. initialize(self, init=xxx , ctx=None, verbose=False, force_reinit=False)
  8. load(self, filename, ctx=None, allow_missing=False, ignore_extra=False, restore_prefix='', cast_dtype=False, dtype_source='current')从文件中加载参数
  9. ·save(self, filename, strip_prefix='')保存参数到文件
  10. setattr(self, name, value) 将所有参数的属性设置为新值。
  11. update(self, other) 将``other’'中的所有参数复制到自身。
  12. zero_grad(self)将所有参数的渐变缓冲区设置为0。
  13. 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 其他模型初始化

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形状,包括批量大小和特征大小,会发生什么?
批量大小改变不影响,特征大小改变会报错.

  • 9
    点赞
  • 23
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值