模型参数的访问、初始化和共享
最近在B站看沐神的动手学深度学习视频,记录一下学习过程
查看本文的jupyter notebook格式,更加清晰美观哦!
from mxnet import nd, init
from mxnet.gluon import nn
net = nn.Sequential()
net.add(nn.Dense(256, activation='relu'),
nn.Dense(10))
net.initialize() # 使用默认初始化方式
X = nd.random.uniform(shape=(2, 20))
Y = net(X) # 前向计算
对于使用Sequential类构造的神经网络,可以通过下标来访问网络中的任一层。通过Block类的params属性可以访问该层包含的所有参数,索引0表示Sequential实例最先添加的隐藏层。
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)
由上面的输出可以得知,它是一个由参数名映射到参数实例的字典(类型为ParameterDict类)。其中权重参数的名称为dense0_weight,它由net[0]的名称(dense0_)和自己的变量名(weight)组成。既可以使用名字来访问字典里面的元素,也可以直接使用它的变量名。通常后者的代码可读性更好。
net[0].params['dense0_weight'], net[0].weight
(Parameter dense0_weight (shape=(256, 20), dtype=float32),
Parameter dense0_weight (shape=(256, 20), dtype=float32))
Gluon中参数类型为Parameter类,它包含参数和梯度的数值,可以分别通过data函数和grad函数来访问。
net[0].weight.data()
[[ 0.06700657 -0.00369488 0.0418822 ... -0.05517294 -0.01194733
-0.00369594]
[-0.03296221 -0.04391347 0.03839272 ... 0.05636378 0.02545484
-0.007007 ]
[-0.0196689 0.01582889 -0.00881553 ... 0.01509629 -0.01908049
-0.02449339]
...
[ 0.00010955 0.0439323 -0.04911506 ... 0.06975312 0.0449558
-0.03283203]
[ 0.04106557 0.05671307 -0.00066976 ... 0.06387014 -0.01292654
0.00974177]
[ 0.00297424 -0.0281784 -0.06881659 ... -0.04047417 0.00457048
0.05696651]]
<NDArray 256x20 @cpu(0)>
net[0].weight.grad() # 由于没有进行反向传播计算,所以梯度的值全为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)>
net[1].bias.data() # 输出层的偏差值
[0. 0. 0. 0. 0. 0. 0. 0. 0. 0.]
<NDArray 10 @cpu(0)>
使用collect_params函数获取net变量所有嵌套的层所包含的所有参数。返回的是一个由参数名称到参数实例的字典。
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)
)
可以使用正则表达式匹配参数名,从而筛选出需要的参数
net.collect_params('.*weight')
sequential0_ (
Parameter dense0_weight (shape=(256, 20), dtype=float32)
Parameter dense1_weight (shape=(10, 256), dtype=float32)
)
初始化模型参数。
# 非首次初始化需要指定force_reinit为真
net.initialize(init=init.Normal(sigma=0.01), force_reinit=True)
net[0].weight.data()[0]
[ 0.00195949 -0.0173764 0.00047347 0.00145809 0.00326049 0.00457878
-0.00894258 0.00493839 -0.00904343 -0.01214079 0.02156406 0.01093822
0.01827143 -0.0104467 0.01006219 0.0051742 -0.00806932 0.01376901
0.00205885 0.00994352]
<NDArray 20 @cpu(0)>
使用常数来初始化权重参数
net.initialize(init=init.Constant(1), force_reinit=True)
net[0].weight.data()[0]
[1. 1. 1. 1. 1. 1. 1. 1. 1. 1. 1. 1. 1. 1. 1. 1. 1. 1. 1. 1.]
<NDArray 20 @cpu(0)>
如果只想对某个参数进行初始化,可以调用Parameter类的initialize函数,它与Block类的initialize函数用法一致。下面对隐藏层的权重参数使用Xavier随机初始化方法。
net[0].weight.initialize(init=init.Xavier(), force_reinit=True)
net[0].weight.data()[0]
[ 0.00512482 -0.06579044 -0.10849719 -0.09586414 0.06394844 0.06029618
-0.03065033 -0.01086642 0.01929168 0.1003869 -0.09339568 -0.08703034
-0.10472868 -0.09879824 -0.00352201 -0.11063069 -0.04257748 0.06548801
0.12987629 -0.13846186]
<NDArray 20 @cpu(0)>
自定义初始化方法
如果我们需要的初始化方法并没有在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(-10, 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)
[-5.3659673 7.5773945 8.986376 -0. 8.827555 0.
5.9840508 -0. 0. 0. 7.4857597 -0.
-0. 6.8910007 6.9788704 -6.1131554 0. 5.4665203
-9.735263 9.485172 ]
<NDArray 20 @cpu(0)>
此外,还可以通过Parameter类的set_data函数直接改写模型参数。例如,在下例中我们将隐藏层参数在现有的基础上加1。
net[0].weight.set_data(net[0].weight.data() + 1)
net[0].weight.data()[0]
[-4.3659673 8.5773945 9.986376 1. 9.827555 1.
6.9840508 1. 1. 1. 8.48576 1.
1. 7.8910007 7.9788704 -5.1131554 1. 6.4665203
-8.735263 10.485172 ]
<NDArray 20 @cpu(0)>
共享模型参数。在下面的例子中,第二隐藏层(shared变量)和第三隐藏层共享模型参数。因为模型参数里包含了梯度,所以在反向传播计算时,第二隐藏层和第三隐藏层的梯度都会被累加在shared.params.grad()里。
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]
[1. 1. 1. 1. 1. 1. 1. 1.]
<NDArray 8 @cpu(0)>