参数管理 ======== 在选择了架构并设置了超参数后,我们就进入了训练阶段。 此时,我们的目标是找到使损失函数最小化的模型参数值。 经过训练后,我们将需要使用这些参数来做出未来的预测。 此外,有时我们希望提取参数,以便在其他环境中复用它们, 将模型保存下来,以便它可以在其他软件中执行, 或者为了获得科学的理解而进行检查。 之前的介绍中,我们只依靠深度学习框架来完成训练的工作, 而忽略了操作参数的具体细节。 本节,我们将介绍以下内容: - 访问参数,用于调试、诊断和可视化; - 参数初始化; - 在不同模型组件间共享参数。 我们首先看一下具有单隐藏层的多层感知机。 .. raw:: html
mxnetpytorchtensorflowpaddle
.. raw:: html
.. raw:: latex \diilbookstyleinputcell .. code:: python from mxnet import init, np, npx from mxnet.gluon import nn npx.set_np() net = nn.Sequential() net.add(nn.Dense(8, activation='relu')) net.add(nn.Dense(1)) net.initialize() # 使用默认初始化方法 X = np.random.uniform(size=(2, 4)) net(X) # 正向传播 .. raw:: latex \diilbookstyleoutputcell .. parsed-literal:: :class: output [07:11:54] ../src/storage/storage.cc:196: Using Pooled (Naive) StorageManager for CPU .. raw:: latex \diilbookstyleoutputcell .. parsed-literal:: :class: output array([[0.0054572 ], [0.00488594]]) .. raw:: html
.. raw:: html
.. raw:: latex \diilbookstyleinputcell .. code:: python import torch from torch import nn net = nn.Sequential(nn.Linear(4, 8), nn.ReLU(), nn.Linear(8, 1)) X = torch.rand(size=(2, 4)) net(X) .. raw:: latex \diilbookstyleoutputcell .. parsed-literal:: :class: output tensor([[-0.0970], [-0.0827]], grad_fn=) .. raw:: html
.. raw:: html
.. raw:: latex \diilbookstyleinputcell .. code:: python import tensorflow as tf net = tf.keras.models.Sequential([ tf.keras.layers.Flatten(), tf.keras.layers.Dense(4, activation=tf.nn.relu), tf.keras.layers.Dense(1), ]) X = tf.random.uniform((2, 4)) net(X) .. raw:: latex \diilbookstyleoutputcell .. parsed-literal:: :class: output .. raw:: html
.. raw:: html
.. raw:: latex \diilbookstyleinputcell .. code:: python import warnings warnings.filterwarnings(action='ignore') import paddle from paddle import nn net = nn.Sequential(nn.Linear(4, 8), nn.ReLU(), nn.Linear(8, 1)) X = paddle.rand([2, 4]) net(X) .. raw:: latex \diilbookstyleoutputcell .. parsed-literal:: :class: output Tensor(shape=[2, 1], dtype=float32, place=Place(cpu), stop_gradient=False, [[0.48685262], [0.34589919]]) .. raw:: html
.. raw:: html
参数访问 -------- 我们从已有模型中访问参数。 当通过\ ``Sequential``\ 类定义模型时, 我们可以通过索引来访问模型的任意层。 这就像模型是一个列表一样,每层的参数都在其属性中。 如下所示,我们可以检查第二个全连接层的参数。 .. raw:: html
mxnetpytorchtensorflowpaddle
.. raw:: html
.. raw:: latex \diilbookstyleinputcell .. code:: python print(net[1].params) .. raw:: latex \diilbookstyleoutputcell .. parsed-literal:: :class: output dense1_ ( Parameter dense1_weight (shape=(1, 8), dtype=float32) Parameter dense1_bias (shape=(1,), dtype=float32) ) .. raw:: html
.. raw:: html
.. raw:: latex \diilbookstyleinputcell .. code:: python print(net[2].state_dict()) .. raw:: latex \diilbookstyleoutputcell .. parsed-literal:: :class: output OrderedDict([('weight', tensor([[-0.0427, -0.2939, -0.1894, 0.0220, -0.1709, -0.1522, -0.0334, -0.2263]])), ('bias', tensor([0.0887]))]) .. raw:: html
.. raw:: html
.. raw:: latex \diilbookstyleinputcell .. code:: python print(net.layers[2].weights) .. raw:: latex \diilbookstyleoutputcell .. parsed-literal:: :class: output [, ] .. raw:: html
.. raw:: html
.. raw:: latex \diilbookstyleinputcell .. code:: python print(net[2].state_dict()) .. raw:: latex \diilbookstyleoutputcell .. parsed-literal:: :class: output OrderedDict([('weight', Parameter containing: Tensor(shape=[8, 1], dtype=float32, place=Place(cpu), stop_gradient=False, [[ 0.35208166], [-0.81014472], [ 0.77976549], [-0.21038508], [ 0.06733459], [-0.73830664], [ 0.65213728], [-0.33782813]])), ('bias', Parameter containing: Tensor(shape=[1], dtype=float32, place=Place(cpu), stop_gradient=False, [0.]))]) .. raw:: html
.. raw:: html
输出的结果告诉我们一些重要的事情: 首先,这个全连接层包含两个参数,分别是该层的权重和偏置。 两者都存储为单精度浮点数(float32)。 注意,参数名称允许唯一标识每个参数,即使在包含数百个层的网络中也是如此。 目标参数 ~~~~~~~~ 注意,每个参数都表示为参数类的一个实例。 要对参数执行任何操作,首先我们需要访问底层的数值。 有几种方法可以做到这一点。有些比较简单,而另一些则比较通用。 下面的代码从第二个全连接层(即第三个神经网络层)提取偏置, 提取后返回的是一个参数类实例,并进一步访问该参数的值。 .. raw:: html
mxnetpytorchtensorflowpaddle
.. raw:: html
.. raw:: latex \diilbookstyleinputcell .. code:: python print(type(net[1].bias)) print(net[1].bias) print(net[1].bias.data()) .. raw:: latex \diilbookstyleoutputcell .. parsed-literal:: :class: output Parameter dense1_bias (shape=(1,), dtype=float32) [0.] 参数是复合的对象,包含值、梯度和额外信息。 这就是我们需要显式参数值的原因。 除了值之外,我们还可以访问每个参数的梯度。 在上面这个网络中,由于我们还没有调用反向传播,所以参数的梯度处于初始状态。 .. raw:: latex \diilbookstyleinputcell .. code:: python net[1].weight.grad() .. raw:: latex \diilbookstyleoutputcell .. parsed-literal:: :class: output array([[0., 0., 0., 0., 0., 0., 0., 0.]]) .. raw:: html
.. raw:: html
.. raw:: latex \diilbookstyleinputcell .. code:: python print(type(net[2].bias)) print(net[2].bias) print(net[2].bias.data) .. raw:: latex \diilbookstyleoutputcell .. parsed-literal:: :class: output Parameter containing: tensor([0.0887], requires_grad=True) tensor([0.0887]) 参数是复合的对象,包含值、梯度和额外信息。 这就是我们需要显式参数值的原因。 除了值之外,我们还可以访问每个参数的梯度。 在上面这个网络中,由于我们还没有调用反向传播,所以参数的梯度处于初始状态。 .. raw:: latex \diilbookstyleinputcell .. code:: python net[2].weight.grad == None .. raw:: latex \diilbookstyleoutputcell .. parsed-literal:: :class: output True .. raw:: html
.. raw:: html
.. raw:: latex \diilbookstyleinputcell .. code:: python print(type(net.layers[2].weights[1])) print(net.layers[2].weights[1]) print(tf.convert_to_tensor(net.layers[2].weights[1])) .. raw:: latex \diilbookstyleoutputcell .. parsed-literal:: :class: output tf.Tensor([0.], shape=(1,), dtype=float32) .. raw:: html
.. raw:: html
.. raw:: latex \diilbookstyleinputcell .. code:: python print(type(net[2].bias)) print(net[2].bias) print(net[2].bias.value) .. raw:: latex \diilbookstyleoutputcell .. parsed-literal:: :class: output Parameter containing: Tensor(shape=[1], dtype=float32, place=Place(cpu), stop_gradient=False, [0.]) 参数是复合的对象,包含值、梯度和额外信息。 这就是我们需要显式参数值的原因。 除了值之外,我们还可以访问每个参数的梯度。 在上面这个网络中,由于我们还没有调用反向传播,所以参数的梯度处于初始状态。 .. raw:: latex \diilbookstyleinputcell .. code:: python net[2].weight.grad == None .. raw:: latex \diilbookstyleoutputcell .. parsed-literal:: :class: output True .. raw:: html
.. raw:: html
一次性访问所有参数 ~~~~~~~~~~~~~~~~~~ 当我们需要对所有参数执行操作时,逐个访问它们可能会很麻烦。 当我们处理更复杂的块(例如,嵌套块)时,情况可能会变得特别复杂, 因为我们需要递归整个树来提取每个子块的参数。 下面,我们将通过演示来比较访问第一个全连接层的参数和访问所有层。 .. raw:: html
mxnetpytorchtensorflowpaddle
.. raw:: html
.. raw:: latex \diilbookstyleinputcell .. code:: python print(net[0].collect_params()) print(net.collect_params()) .. raw:: latex \diilbookstyleoutputcell .. parsed-literal:: :class: output dense0_ ( Parameter dense0_weight (shape=(8, 4), dtype=float32) Parameter dense0_bias (shape=(8,), dtype=float32) ) sequential0_ ( Parameter dense0_weight (shape=(8, 4), dtype=float32) Parameter dense0_bias (shape=(8,), dtype=float32) Parameter dense1_weight (shape=(1, 8), dtype=float32) Parameter dense1_bias (shape=(1,), dtype=float32) ) .. raw:: html
.. raw:: html
.. raw:: latex \diilbookstyleinputcell .. code:: python print(*[(name, param.shape) for name, param in net[0].named_parameters()]) print(*[(name, param.shape) for name, param in net.named_parameters()]) .. raw:: latex \diilbookstyleoutputcell .. parsed-literal:: :class: output ('weight', torch.Size([8, 4])) ('bias', torch.Size([8])) ('0.weight', torch.Size([8, 4])) ('0.bias', torch.Size([8])) ('2.weight', torch.Size([1, 8])) ('2.bias', torch.Size([1])) .. raw:: html
.. raw:: html
.. raw:: latex \diilbookstyleinputcell .. code:: python print(net.layers[1].weights) print(net.get_weights()) .. raw:: latex \diilbookstyleoutputcell .. parsed-literal:: :class: output [, ] [array([[ 0.07396382, -0.6543436 , -0.7244056 , -0.6157465 ], [ 0.40404958, 0.7228444 , 0.4572547 , 0.7116396 ], [ 0.5283937 , 0.25636894, -0.49113625, 0.6337872 ], [ 0.5183577 , 0.19943613, 0.5296057 , 0.6009421 ]], dtype=float32), array([0., 0., 0., 0.], dtype=float32), array([[-1.0469127 ], [ 0.31355536], [ 0.5405549 ], [ 0.7610214 ]], dtype=float32), array([0.], dtype=float32)] .. raw:: html
.. raw:: html
.. raw:: latex \diilbookstyleinputcell .. code:: python print(*[(name, param.shape) for name, param in net[0].named_parameters()]) print(*[(name, param.shape) for name, param in net.named_parameters()]) .. raw:: latex \diilbookstyleoutputcell .. parsed-literal:: :class: output ('weight', [4, 8]) ('bias', [8]) ('0.weight', [4, 8]) ('0.bias', [8]) ('2.weight', [8, 1]) ('2.bias', [1]) .. raw:: html
.. raw:: html
这为我们提供了另一种访问网络参数的方式,如下所示。 .. raw:: html
mxnetpytorchtensorflowpaddle
.. raw:: html
.. raw:: latex \diilbookstyleinputcell .. code:: python net.collect_params()['dense1_bias'].data() .. raw:: latex \diilbookstyleoutputcell .. parsed-literal:: :class: output array([0.]) .. raw:: html
.. raw:: html
.. raw:: latex \diilbookstyleinputcell .. code:: python net.state_dict()['2.bias'].data .. raw:: latex \diilbookstyleoutputcell .. parsed-literal:: :class: output tensor([0.0887]) .. raw:: html
.. raw:: html
.. raw:: latex \diilbookstyleinputcell .. code:: python net.get_weights()[1] .. raw:: latex \diilbookstyleoutputcell .. parsed-literal:: :class: output array([0., 0., 0., 0.], dtype=float32) .. raw:: html
.. raw:: html
.. raw:: latex \diilbookstyleinputcell .. code:: python net.state_dict()['2.bias'] .. raw:: latex \diilbookstyleoutputcell .. parsed-literal:: :class: output Parameter containing: Tensor(shape=[1], dtype=float32, place=Place(cpu), stop_gradient=False, [0.]) .. raw:: html
.. raw:: html
从嵌套块收集参数 ~~~~~~~~~~~~~~~~ 让我们看看,如果我们将多个块相互嵌套,参数命名约定是如何工作的。 我们首先定义一个生成块的函数(可以说是“块工厂”),然后将这些块组合到更大的块中。 .. raw:: html
mxnetpytorchtensorflowpaddle
.. raw:: html
.. raw:: latex \diilbookstyleinputcell .. code:: python def block1(): net = nn.Sequential() net.add(nn.Dense(32, activation='relu')) net.add(nn.Dense(16, activation='relu')) return net def block2(): net = nn.Sequential() for _ in range(4): # 在这里嵌套 net.add(block1()) return net rgnet = nn.Sequential() rgnet.add(block2()) rgnet.add(nn.Dense(10)) rgnet.initialize() rgnet(X) .. raw:: latex \diilbookstyleoutputcell .. parsed-literal:: :class: output array([[-6.3465841e-09, -1.1096747e-09, 6.4161774e-09, 6.6354131e-09, -1.1265502e-09, 1.3285141e-10, 9.3619361e-09, 3.2229091e-09, 5.9429857e-09, 8.8181418e-09], [-8.6219423e-09, -7.5150813e-10, 8.3133243e-09, 8.9321119e-09, -1.6739999e-09, 3.2406069e-10, 1.2115976e-08, 4.4926454e-09, 8.0741742e-09, 1.2075874e-08]]) .. raw:: html
.. raw:: html
.. raw:: latex \diilbookstyleinputcell .. code:: python def block1(): return nn.Sequential(nn.Linear(4, 8), nn.ReLU(), nn.Linear(8, 4), nn.ReLU()) def block2(): net = nn.Sequential() for i in range(4): # 在这里嵌套 net.add_module(f'block {i}', block1()) return net rgnet = nn.Sequential(block2(), nn.Linear(4, 1)) rgnet(X) .. raw:: latex \diilbookstyleoutputcell .. parsed-literal:: :class: output tensor([[0.2596], [0.2596]], grad_fn=) .. raw:: html
.. raw:: html
.. raw:: latex \diilbookstyleinputcell .. code:: python def block1(name): return tf.keras.Sequential([ tf.keras.layers.Flatten(), tf.keras.layers.Dense(4, activation=tf.nn.relu)], name=name) def block2(): net = tf.keras.Sequential() for i in range(4): # 在这里嵌套 net.add(block1(name=f'block-{i}')) return net rgnet = tf.keras.Sequential() rgnet.add(block2()) rgnet.add(tf.keras.layers.Dense(1)) rgnet(X) .. raw:: latex \diilbookstyleoutputcell .. parsed-literal:: :class: output .. raw:: html
.. raw:: html
.. raw:: latex \diilbookstyleinputcell .. code:: python def block1(): return nn.Sequential(nn.Linear(4, 8), nn.ReLU(), nn.Linear(8, 4), nn.ReLU()) def block2(): net = nn.Sequential() for i in range(4): # 在这里嵌套 net.add_sublayer(f'block {i}', block1()) return net rgnet = nn.Sequential(block2(), nn.Linear(4, 1)) rgnet(X) .. raw:: latex \diilbookstyleoutputcell .. parsed-literal:: :class: output Tensor(shape=[2, 1], dtype=float32, place=Place(cpu), stop_gradient=False, [[0.], [0.]]) .. raw:: html
.. raw:: html
设计了网络后,我们看看它是如何工作的。 .. raw:: html
mxnetpytorchtensorflowpaddle
.. raw:: html
.. raw:: latex \diilbookstyleinputcell .. code:: python print(rgnet.collect_params) print(rgnet.collect_params()) .. raw:: latex \diilbookstyleoutputcell .. parsed-literal:: :class: output 32, Activation(relu)) (1): Dense(32 -> 16, Activation(relu)) ) (1): Sequential( (0): Dense(16 -> 32, Activation(relu)) (1): Dense(32 -> 16, Activation(relu)) ) (2): Sequential( (0): Dense(16 -> 32, Activation(relu)) (1): Dense(32 -> 16, Activation(relu)) ) (3): Sequential( (0): Dense(16 -> 32, Activation(relu)) (1): Dense(32 -> 16, Activation(relu)) ) ) (1): Dense(16 -> 10, linear) )> sequential1_ ( Parameter dense2_weight (shape=(32, 4), dtype=float32) Parameter dense2_bias (shape=(32,), dtype=float32) Parameter dense3_weight (shape=(16, 32), dtype=float32) Parameter dense3_bias (shape=(16,), dtype=float32) Parameter dense4_weight (shape=(32, 16), dtype=float32) Parameter dense4_bias (shape=(32,), dtype=float32) Parameter dense5_weight (shape=(16, 32), dtype=float32) Parameter dense5_bias (shape=(16,), dtype=float32) Parameter dense6_weight (shape=(32, 16), dtype=float32) Parameter dense6_bias (shape=(32,), dtype=float32) Parameter dense7_weight (shape=(16, 32), dtype=float32) Parameter dense7_bias (shape=(16,), dtype=float32) Parameter dense8_weight (shape=(32, 16), dtype=float32) Parameter dense8_bias (shape=(32,), dtype=float32) Parameter dense9_weight (shape=(16, 32), dtype=float32) Parameter dense9_bias (shape=(16,), dtype=float32) Parameter dense10_weight (shape=(10, 16), dtype=float32) Parameter dense10_bias (shape=(10,), dtype=float32) ) .. raw:: html
.. raw:: html
.. raw:: latex \diilbookstyleinputcell .. code:: python print(rgnet) .. raw:: latex \diilbookstyleoutputcell .. parsed-literal:: :class: output Sequential( (0): Sequential( (block 0): Sequential( (0): Linear(in_features=4, out_features=8, bias=True) (1): ReLU() (2): Linear(in_features=8, out_features=4, bias=True) (3): ReLU() ) (block 1): Sequential( (0): Linear(in_features=4, out_features=8, bias=True) (1): ReLU() (2): Linear(in_features=8, out_features=4, bias=True) (3): ReLU() ) (block 2): Sequential( (0): Linear(in_features=4, out_features=8, bias=True) (1): ReLU() (2): Linear(in_features=8, out_features=4, bias=True) (3): ReLU() ) (block 3): Sequential( (0): Linear(in_features=4, out_features=8, bias=True) (1): ReLU() (2): Linear(in_features=8, out_features=4, bias=True) (3): ReLU() ) ) (1): Linear(in_features=4, out_features=1, bias=True) ) .. raw:: html
.. raw:: html
.. raw:: latex \diilbookstyleinputcell .. code:: python print(rgnet.summary()) .. raw:: latex \diilbookstyleoutputcell .. parsed-literal:: :class: output Model: "sequential_1" _________________________________________________________________ Layer (type) Output Shape Param # ================================================================= sequential_2 (Sequential) (2, 4) 80 dense_6 (Dense) (2, 1) 5 ================================================================= Total params: 85 Trainable params: 85 Non-trainable params: 0 _________________________________________________________________ None .. raw:: html
.. raw:: html
.. raw:: latex \diilbookstyleinputcell .. code:: python print(rgnet) .. raw:: latex \diilbookstyleoutputcell .. parsed-literal:: :class: output Sequential( (0): Sequential( (block 0): Sequential( (0): Linear(in_features=4, out_features=8, dtype=float32) (1): ReLU() (2): Linear(in_features=8, out_features=4, dtype=float32) (3): ReLU() ) (block 1): Sequential( (0): Linear(in_features=4, out_features=8, dtype=float32) (1): ReLU() (2): Linear(in_features=8, out_features=4, dtype=float32) (3): ReLU() ) (block 2): Sequential( (0): Linear(in_features=4, out_features=8, dtype=float32) (1): ReLU() (2): Linear(in_features=8, out_features=4, dtype=float32) (3): ReLU() ) (block 3): Sequential( (0): Linear(in_features=4, out_features=8, dtype=float32) (1): ReLU() (2): Linear(in_features=8, out_features=4, dtype=float32) (3): ReLU() ) ) (1): Linear(in_features=4, out_features=1, dtype=float32) ) .. raw:: html
.. raw:: html
因为层是分层嵌套的,所以我们也可以像通过嵌套列表索引一样访问它们。 下面,我们访问第一个主要的块中、第二个子块的第一层的偏置项。 .. raw:: html
mxnetpytorchtensorflowpaddle
.. raw:: html
.. raw:: latex \diilbookstyleinputcell .. code:: python rgnet[0][1][0].bias.data() .. raw:: latex \diilbookstyleoutputcell .. parsed-literal:: :class: output array([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.]) .. raw:: html
.. raw:: html
.. raw:: latex \diilbookstyleinputcell .. code:: python rgnet[0][1][0].bias.data .. raw:: latex \diilbookstyleoutputcell .. parsed-literal:: :class: output tensor([ 0.1999, -0.4073, -0.1200, -0.2033, -0.1573, 0.3546, -0.2141, -0.2483]) .. raw:: html
.. raw:: html
.. raw:: latex \diilbookstyleinputcell .. code:: python rgnet.layers[0].layers[1].layers[1].weights[1] .. raw:: latex \diilbookstyleoutputcell .. parsed-literal:: :class: output .. raw:: html
.. raw:: html
.. raw:: latex \diilbookstyleinputcell .. code:: python print(rgnet[0].state_dict()['block 0.0.bias']) .. raw:: latex \diilbookstyleoutputcell .. parsed-literal:: :class: output Parameter containing: Tensor(shape=[8], dtype=float32, place=Place(cpu), stop_gradient=False, [0., 0., 0., 0., 0., 0., 0., 0.]) .. raw:: html
.. raw:: html
参数初始化 ---------- 知道了如何访问参数后,现在我们看看如何正确地初始化参数。 我们在 :numref:`sec_numerical_stability`\ 中讨论了良好初始化的必要性。 深度学习框架提供默认随机初始化, 也允许我们创建自定义初始化方法, 满足我们通过其他规则实现初始化权重。 .. raw:: html
mxnetpytorchtensorflowpaddle
.. raw:: html
默认情况下,MXNet通过初始化权重参数的方法是 从均匀分布\ :math:`U(-0.07, 0.07)`\ 中随机采样权重,并将偏置参数设置为0。 MXNet的\ ``init``\ 模块提供了多种预置初始化方法。 .. raw:: html
.. raw:: html
默认情况下,PyTorch会根据一个范围均匀地初始化权重和偏置矩阵, 这个范围是根据输入和输出维度计算出的。 PyTorch的\ ``nn.init``\ 模块提供了多种预置初始化方法。 .. raw:: html
.. raw:: html
默认情况下,Keras会根据一个范围均匀地初始化权重矩阵, 这个范围是根据输入和输出维度计算出的。 偏置参数设置为0。 TensorFlow在根模块和\ ``keras.initializers``\ 模块中提供了各种初始化方法。 .. raw:: html
.. raw:: html
默认情况下,PaddlePaddle会使用Xavier初始化权重矩阵, 偏置参数设置为0。 PaddlePaddle的\ ``nn.initializer``\ 模块提供了多种预置初始化方法。 .. raw:: html
.. raw:: html
内置初始化 ~~~~~~~~~~ 让我们首先调用内置的初始化器。 下面的代码将所有权重参数初始化为标准差为0.01的高斯随机变量, 且将偏置参数设置为0。 .. raw:: html
mxnetpytorchtensorflowpaddle
.. raw:: html
.. raw:: latex \diilbookstyleinputcell .. code:: python # 这里的force_reinit确保参数会被重新初始化,不论之前是否已经被初始化 net.initialize(init=init.Normal(sigma=0.01), force_reinit=True) net[0].weight.data()[0] .. raw:: latex \diilbookstyleoutputcell .. parsed-literal:: :class: output array([-0.00324057, -0.00895028, -0.00698632, 0.01030831]) .. raw:: html
.. raw:: html
.. raw:: latex \diilbookstyleinputcell .. code:: python def init_normal(m): if type(m) == nn.Linear: nn.init.normal_(m.weight, mean=0, std=0.01) nn.init.zeros_(m.bias) net.apply(init_normal) net[0].weight.data[0], net[0].bias.data[0] .. raw:: latex \diilbookstyleoutputcell .. parsed-literal:: :class: output (tensor([-0.0214, -0.0015, -0.0100, -0.0058]), tensor(0.)) .. raw:: html
.. raw:: html
.. raw:: latex \diilbookstyleinputcell .. code:: python net = tf.keras.models.Sequential([ tf.keras.layers.Flatten(), tf.keras.layers.Dense( 4, activation=tf.nn.relu, kernel_initializer=tf.random_normal_initializer(mean=0, stddev=0.01), bias_initializer=tf.zeros_initializer()), tf.keras.layers.Dense(1)]) net(X) net.weights[0], net.weights[1] .. raw:: latex \diilbookstyleoutputcell .. parsed-literal:: :class: output (, ) .. raw:: html
.. raw:: html
.. raw:: latex \diilbookstyleinputcell .. code:: python def init_normal(m): if type(m) == nn.Linear: paddle.nn.initializer.Normal(mean=0.0, std=0.01) paddle.zeros(m.bias) net.apply(init_normal) net[0].weight[0],net[0].state_dict()['bias'] .. raw:: latex \diilbookstyleoutputcell .. parsed-literal:: :class: output (Tensor(shape=[8], dtype=float32, place=Place(cpu), stop_gradient=False, [-0.45191017, 0.32920808, -0.20261598, 0.45921832, 0.20553625, -0.44192296, 0.23093420, 0.31619781]), Parameter containing: Tensor(shape=[8], dtype=float32, place=Place(cpu), stop_gradient=False, [0., 0., 0., 0., 0., 0., 0., 0.])) .. raw:: html
.. raw:: html
我们还可以将所有参数初始化为给定的常数,比如初始化为1。 .. raw:: html
mxnetpytorchtensorflowpaddle
.. raw:: html
.. raw:: latex \diilbookstyleinputcell .. code:: python net.initialize(init=init.Constant(1), force_reinit=True) net[0].weight.data()[0] .. raw:: latex \diilbookstyleoutputcell .. parsed-literal:: :class: output array([1., 1., 1., 1.]) .. raw:: html
.. raw:: html
.. raw:: latex \diilbookstyleinputcell .. code:: python def init_constant(m): if type(m) == nn.Linear: nn.init.constant_(m.weight, 1) nn.init.zeros_(m.bias) net.apply(init_constant) net[0].weight.data[0], net[0].bias.data[0] .. raw:: latex \diilbookstyleoutputcell .. parsed-literal:: :class: output (tensor([1., 1., 1., 1.]), tensor(0.)) .. raw:: html
.. raw:: html
.. raw:: latex \diilbookstyleinputcell .. code:: python net = tf.keras.models.Sequential([ tf.keras.layers.Flatten(), tf.keras.layers.Dense( 4, activation=tf.nn.relu, kernel_initializer=tf.keras.initializers.Constant(1), bias_initializer=tf.zeros_initializer()), tf.keras.layers.Dense(1), ]) net(X) net.weights[0], net.weights[1] .. raw:: latex \diilbookstyleoutputcell .. parsed-literal:: :class: output (, ) .. raw:: html
.. raw:: html
.. raw:: latex \diilbookstyleinputcell .. code:: python def init_constant(m): if type(m) == nn.Linear: paddle.nn.initializer.Constant(value = 1) paddle.zeros(m.bias) net.apply(init_constant) net[0].weight[0],net[0].state_dict()['bias'] .. raw:: latex \diilbookstyleoutputcell .. parsed-literal:: :class: output (Tensor(shape=[8], dtype=float32, place=Place(cpu), stop_gradient=False, [-0.45191017, 0.32920808, -0.20261598, 0.45921832, 0.20553625, -0.44192296, 0.23093420, 0.31619781]), Parameter containing: Tensor(shape=[8], dtype=float32, place=Place(cpu), stop_gradient=False, [0., 0., 0., 0., 0., 0., 0., 0.])) .. raw:: html
.. raw:: html
我们还可以对某些块应用不同的初始化方法。 例如,下面我们使用Xavier初始化方法初始化第一个神经网络层, 然后将第三个神经网络层初始化为常量值42。 .. raw:: html
mxnetpytorchtensorflowpaddle
.. raw:: html
.. raw:: latex \diilbookstyleinputcell .. code:: python net[0].weight.initialize(init=init.Xavier(), force_reinit=True) net[1].initialize(init=init.Constant(42), force_reinit=True) print(net[0].weight.data()[0]) print(net[1].weight.data()) .. raw:: latex \diilbookstyleoutputcell .. parsed-literal:: :class: output [-0.17594433 0.02314097 -0.1992535 0.09509248] [[42. 42. 42. 42. 42. 42. 42. 42.]] .. raw:: html
.. raw:: html
.. raw:: latex \diilbookstyleinputcell .. code:: python def init_xavier(m): if type(m) == nn.Linear: nn.init.xavier_uniform_(m.weight) def init_42(m): if type(m) == nn.Linear: nn.init.constant_(m.weight, 42) net[0].apply(init_xavier) net[2].apply(init_42) print(net[0].weight.data[0]) print(net[2].weight.data) .. raw:: latex \diilbookstyleoutputcell .. parsed-literal:: :class: output tensor([ 0.5236, 0.0516, -0.3236, 0.3794]) tensor([[42., 42., 42., 42., 42., 42., 42., 42.]]) .. raw:: html
.. raw:: html
.. raw:: latex \diilbookstyleinputcell .. code:: python net = tf.keras.models.Sequential([ tf.keras.layers.Flatten(), tf.keras.layers.Dense( 4, activation=tf.nn.relu, kernel_initializer=tf.keras.initializers.GlorotUniform()), tf.keras.layers.Dense( 1, kernel_initializer=tf.keras.initializers.Constant(1)), ]) net(X) print(net.layers[1].weights[0]) print(net.layers[2].weights[0]) .. raw:: latex \diilbookstyleoutputcell .. parsed-literal:: :class: output .. raw:: html
.. raw:: html
.. raw:: latex \diilbookstyleinputcell .. code:: python def xavier(m): if type(m) == nn.Linear: paddle.nn.initializer.XavierUniform(m.weight) def init_42(m): if type(m) == nn.Linear: paddle.nn.initializer.Constant(42) net[0].apply(xavier) net[2].apply(init_42) print(net[0].weight[0]) print(net[2].weight) .. raw:: latex \diilbookstyleoutputcell .. parsed-literal:: :class: output Tensor(shape=[8], dtype=float32, place=Place(cpu), stop_gradient=False, [-0.45191017, 0.32920808, -0.20261598, 0.45921832, 0.20553625, -0.44192296, 0.23093420, 0.31619781]) Parameter containing: Tensor(shape=[8, 1], dtype=float32, place=Place(cpu), stop_gradient=False, [[ 0.35208166], [-0.81014472], [ 0.77976549], [-0.21038508], [ 0.06733459], [-0.73830664], [ 0.65213728], [-0.33782813]]) .. raw:: html
.. raw:: html
自定义初始化 ~~~~~~~~~~~~ 有时,深度学习框架没有提供我们需要的初始化方法。 在下面的例子中,我们使用以下的分布为任意权重参数\ :math:`w`\ 定义初始化方法: .. math:: \begin{aligned} w \sim \begin{cases} U(5, 10) & \text{ 可能性 } \frac{1}{4} \\ 0 & \text{ 可能性 } \frac{1}{2} \\ U(-10, -5) & \text{ 可能性 } \frac{1}{4} \end{cases} \end{aligned} .. raw:: html
mxnetpytorchtensorflowpaddle
.. raw:: html
在这里,我们定义了\ ``Initializer``\ 类的子类。 通常,我们只需要实现\ ``_init_weight``\ 函数, 该函数接受张量参数(\ ``data``\ )并为其分配所需的初始化值。 .. raw:: latex \diilbookstyleinputcell .. code:: python class MyInit(init.Initializer): def _init_weight(self, name, data): print('Init', name, data.shape) data[:] = np.random.uniform(-10, 10, data.shape) data *= np.abs(data) >= 5 net.initialize(MyInit(), force_reinit=True) net[0].weight.data()[:2] .. raw:: latex \diilbookstyleoutputcell .. parsed-literal:: :class: output Init dense0_weight (8, 4) Init dense1_weight (1, 8) .. raw:: latex \diilbookstyleoutputcell .. parsed-literal:: :class: output array([[ 0. , -0. , -0. , 8.522827 ], [ 0. , -8.828651 , -0. , -5.6012006]]) .. raw:: html
.. raw:: html
同样,我们实现了一个\ ``my_init``\ 函数来应用到\ ``net``\ 。 .. raw:: latex \diilbookstyleinputcell .. code:: python def my_init(m): if type(m) == nn.Linear: print("Init", *[(name, param.shape) for name, param in m.named_parameters()][0]) nn.init.uniform_(m.weight, -10, 10) m.weight.data *= m.weight.data.abs() >= 5 net.apply(my_init) net[0].weight[:2] .. raw:: latex \diilbookstyleoutputcell .. parsed-literal:: :class: output Init weight torch.Size([8, 4]) Init weight torch.Size([1, 8]) .. raw:: latex \diilbookstyleoutputcell .. parsed-literal:: :class: output tensor([[5.4079, 9.3334, 5.0616, 8.3095], [0.0000, 7.2788, -0.0000, -0.0000]], grad_fn=) .. raw:: html
.. raw:: html
在这里,我们定义了一个\ ``Initializer``\ 的子类, 并实现了\ ``__call__``\ 函数。 该函数返回给定形状和数据类型的所需张量。 .. raw:: latex \diilbookstyleinputcell .. code:: python class MyInit(tf.keras.initializers.Initializer): def __call__(self, shape, dtype=None): data=tf.random.uniform(shape, -10, 10, dtype=dtype) factor=(tf.abs(data) >= 5) factor=tf.cast(factor, tf.float32) return data * factor net = tf.keras.models.Sequential([ tf.keras.layers.Flatten(), tf.keras.layers.Dense( 4, activation=tf.nn.relu, kernel_initializer=MyInit()), tf.keras.layers.Dense(1), ]) net(X) print(net.layers[1].weights[0]) .. raw:: latex \diilbookstyleoutputcell .. parsed-literal:: :class: output .. raw:: html
.. raw:: html
同样,我们实现了一个\ ``my_init``\ 函数来应用到\ ``net``\ 。 .. raw:: latex \diilbookstyleinputcell .. code:: python def my_init(m): if type(m) == nn.Linear: print("Init", *[(name, param.shape) for name, param in m.named_parameters()][0]) paddle.nn.initializer.XavierUniform(m.weight, -10, 10) h = paddle.abs(m.weight) >= 5 h = paddle.to_tensor(h) m = paddle.to_tensor(m.weight) m *= h net.apply(my_init) net[0].weight[:2] .. raw:: latex \diilbookstyleoutputcell .. parsed-literal:: :class: output Init weight [4, 8] Init weight [8, 1] .. raw:: latex \diilbookstyleoutputcell .. parsed-literal:: :class: output Tensor(shape=[2, 8], dtype=float32, place=Place(cpu), stop_gradient=False, [[-0.45191017, 0.32920808, -0.20261598, 0.45921832, 0.20553625, -0.44192296, 0.23093420, 0.31619781], [-0.34684724, -0.05426282, -0.12417591, -0.11773884, 0.51981491, 0.52964920, 0.54408771, -0.02475590]]) .. raw:: html
.. raw:: html
注意,我们始终可以直接设置参数。 .. raw:: html
mxnetpytorchtensorflowpaddle
.. raw:: html
.. raw:: latex \diilbookstyleinputcell .. code:: python net[0].weight.data()[:] += 1 net[0].weight.data()[0, 0] = 42 net[0].weight.data()[0] .. raw:: latex \diilbookstyleoutputcell .. parsed-literal:: :class: output array([42. , 1. , 1. , 9.522827]) 高级用户请注意:如果要在\ ``autograd``\ 范围内调整参数, 则需要使用\ ``set_data``\ ,以避免误导自动微分机制。 .. raw:: html
.. raw:: html
.. raw:: latex \diilbookstyleinputcell .. code:: python net[0].weight.data[:] += 1 net[0].weight.data[0, 0] = 42 net[0].weight.data[0] .. raw:: latex \diilbookstyleoutputcell .. parsed-literal:: :class: output tensor([42.0000, 10.3334, 6.0616, 9.3095]) .. raw:: html
.. raw:: html
.. raw:: latex \diilbookstyleinputcell .. code:: python net.layers[1].weights[0][:].assign(net.layers[1].weights[0] + 1) net.layers[1].weights[0][0, 0].assign(42) net.layers[1].weights[0] .. raw:: latex \diilbookstyleoutputcell .. parsed-literal:: :class: output .. raw:: html
.. raw:: html
.. raw:: latex \diilbookstyleinputcell .. code:: python net[0].weight.set_value(net[0].weight.numpy() + 1) val = net[0].weight.numpy() val[0, 0] = 42 net[0].weight.set_value(val) net[0].weight[0] .. raw:: latex \diilbookstyleoutputcell .. parsed-literal:: :class: output Tensor(shape=[8], dtype=float32, place=Place(cpu), stop_gradient=False, [42. , 1.32920814, 0.79738402, 1.45921826, 1.20553625, 0.55807704, 1.23093414, 1.31619787]) .. raw:: html
.. raw:: html
参数绑定 -------- 有时我们希望在多个层间共享参数: 我们可以定义一个稠密层,然后使用它的参数来设置另一个层的参数。 .. raw:: html
mxnetpytorchtensorflowpaddle
.. raw:: html
.. raw:: latex \diilbookstyleinputcell .. code:: python 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 = np.random.uniform(size=(2, 20)) net(X) # 检查参数是否相同 print(net[1].weight.data()[0] == net[2].weight.data()[0]) net[1].weight.data()[0, 0] = 100 # 确保它们实际上是同一个对象,而不只是有相同的值 print(net[1].weight.data()[0] == net[2].weight.data()[0]) .. raw:: latex \diilbookstyleoutputcell .. parsed-literal:: :class: output [ True True True True True True True True] [ True True True True True True True True] 这个例子表明第二层和第三层的参数是绑定的。 它们不仅值相等,而且由相同的张量表示。 因此,如果我们改变其中一个参数,另一个参数也会改变。 这里有一个问题:当参数绑定时,梯度会发生什么情况? 答案是由于模型参数包含梯度, 因此在反向传播期间第二个隐藏层和第三个隐藏层的梯度会加在一起。 .. raw:: html
.. raw:: html
.. raw:: latex \diilbookstyleinputcell .. code:: python # 我们需要给共享层一个名称,以便可以引用它的参数 shared = nn.Linear(8, 8) net = nn.Sequential(nn.Linear(4, 8), nn.ReLU(), shared, nn.ReLU(), shared, nn.ReLU(), nn.Linear(8, 1)) net(X) # 检查参数是否相同 print(net[2].weight.data[0] == net[4].weight.data[0]) net[2].weight.data[0, 0] = 100 # 确保它们实际上是同一个对象,而不只是有相同的值 print(net[2].weight.data[0] == net[4].weight.data[0]) .. raw:: latex \diilbookstyleoutputcell .. parsed-literal:: :class: output tensor([True, True, True, True, True, True, True, True]) tensor([True, True, True, True, True, True, True, True]) 这个例子表明第三个和第五个神经网络层的参数是绑定的。 它们不仅值相等,而且由相同的张量表示。 因此,如果我们改变其中一个参数,另一个参数也会改变。 这里有一个问题:当参数绑定时,梯度会发生什么情况? 答案是由于模型参数包含梯度,因此在反向传播期间第二个隐藏层 (即第三个神经网络层)和第三个隐藏层(即第五个神经网络层)的梯度会加在一起。 .. raw:: html
.. raw:: html
.. raw:: latex \diilbookstyleinputcell .. code:: python # tf.keras的表现有点不同。它会自动删除重复层 shared = tf.keras.layers.Dense(4, activation=tf.nn.relu) net = tf.keras.models.Sequential([ tf.keras.layers.Flatten(), shared, shared, tf.keras.layers.Dense(1), ]) net(X) # 检查参数是否不同 print(len(net.layers) == 3) .. raw:: latex \diilbookstyleoutputcell .. parsed-literal:: :class: output True .. raw:: html
.. raw:: html
.. raw:: latex \diilbookstyleinputcell .. code:: python # 我们需要给共享层一个名称,以便可以引用它的参数。 shared = nn.Linear(8, 8) net = nn.Sequential(nn.Linear(4, 8), nn.ReLU(), shared, nn.ReLU(), shared, nn.ReLU(), nn.Linear(8, 1)) net(X) # 检查参数是否相同 print(net[2].weight[0] == net[4].weight[0]) .. raw:: latex \diilbookstyleoutputcell .. parsed-literal:: :class: output Tensor(shape=[8], dtype=bool, place=Place(cpu), stop_gradient=False, [True, True, True, True, True, True, True, True]) .. raw:: html
.. raw:: html
小结 ---- - 我们有几种方法可以访问、初始化和绑定模型参数。 - 我们可以使用自定义初始化方法。 练习 ---- 1. 使用 :numref:`sec_model_construction` 中定义的\ ``FancyMLP``\ 模型,访问各个层的参数。 2. 查看初始化模块文档以了解不同的初始化方法。 3. 构建包含共享参数层的多层感知机并对其进行训练。在训练过程中,观察模型各层的参数和梯度。 4. 为什么共享参数是个好主意? .. raw:: html
mxnetpytorchtensorflowpaddle
.. raw:: html
`Discussions `__ .. raw:: html
.. raw:: html
`Discussions `__ .. raw:: html
.. raw:: html
`Discussions `__ .. raw:: html
.. raw:: html
`Discussions `__ .. raw:: html
.. raw:: html