第八章 网络参数初始化
8.1 全零初始化
全零初始化在PyTorch中的调用函数为:
1# zero initialization (PyTorch)
2import torch.nn as nn
3
4def reset_parameters(m) -> None:
5 # Ln.6-Ln.8: Zero initialization weight and bias
6 nn.init.zeros_(m.weight)
7 if m.bias is not None:
8 nn.init.zeros_(m.bias)
在MindSpore中的调用函数为:
1# zero initialization (MindSpore)
2import numpy as np
3import mindspore.nn as nn
4import mindspore as ms
5from mindspore.common.initializer import Zero
6
7input_data = ms.Tensor(np.ones([1, 3, 16, 50], dtype=np.float32))
8
9# Ln.10: Apply zero initialization.
10net = nn.Conv2d(3, 64, 3, weight_init=Zero())
11output = net(input_data)
PyTorch中全零初始化的具体实现为:
1# implementations of zero initialization (PyTorch)
2def zeros_(tensor: Tensor) -> Tensor:
3 """
4 Fills the input Tensor with the scalar value `0`.
5
6 Args:
7 tensor: an n-dimensional `torch.Tensor`
8 """
9 # Ln. 10: Apply zero initialization.
10 return _no_grad_zero_(tensor)
在MindSpore中的实现为:
1# implementations of zero initialization (MindSpore)
2@_register('zeros')
3class Zero(Initializer):
4 """
5 Generates an array with constant value of zero in order to initialize a tensor.
6 """
7 def _initialize(self, arr):
8 # Ln. 9: Apply zero initialization.
9 arr.fill(0)
8.2 随机初始化
在PyTorch中,对参数进行服从高斯分布的参数随机初始化的调用函数为:
1# Gaussian initialization (PyTorch)
2import torch.nn as nn
3
4def reset_parameters(m) -> None:
5 # Ln.6: Apply Gaussian initialization weight.
6 nn.init.normal_(m.weight, mean=0, std=1)
7 if m.bias is not None:
8 nn.init.zeros_(m.bias)
MindSpore中的调用函数为:
1# Gaussian initialization (MindSpore)
2import numpy as np
3import mindspore.nn as nn
4import mindspore as ms
5from mindspore.common.initializer import Normal
6
7input_data = ms.Tensor(np.ones([1, 3, 16, 50], dtype=np.float32))
8
9# Ln.10: Apply Gaussian initialization.
10net = nn.Conv2d(3, 64, 3, weight_init=Normal(sigma=1.0, mean=0.0))
11output = net(input_data)
PyTorch中服从高斯分布的参数随机初始化的具体实现为:
1# implementations of Gaussian initialization (PyTorch)
2def normal_(tensor: Tensor, mean: float = 0., std: float = 1.) -> Tensor:
3 """
4 Fills the input Tensor with values drawn from the normal distribution.
5
6 Args:
7 tensor: an n-dimensional `torch.Tensor`
8 mean: the mean of the normal distribution
9 std: the standard deviation of the normal distribution
10 """
11 # Ln.12-Ln.14: Apply Gaussian initialization.
12 if torch.overrides.has_torch_function_variadic(tensor):
13 return torch.overrides.handle_torch_function(normal_, (tensor,), tensor=tensor, mean=mean, std=std)
14 return _no_grad_normal_(tensor, mean, std)
在MindSpore中的具体实现为:
1# implementations of Gaussian initialization (MindSpore)
2@_register()
3class Normal(Initializer):
4 """
5 Generates an array with values sampled from Normal distribution in order to initialize a tensor.
6
7 Args:
8 sigma (float): The standard deviation of Normal distribution. Default: 0.01.
9 mean (float): The mean of Normal distribution. Default: 0.0.
10 """
11 def __init__(self, sigma=0.01, mean=0.0):
12 super(Normal, self).__init__(sigma=sigma, mean=mean)
13 self.sigma = sigma
14 self.mean = mean
15
16 def _initialize(self, arr):
17 # Ln.18-Ln.19: Apply Gaussian initialization.
18 data = _init_random_normal(self.mean, self.sigma, arr.shape)
19 _assignment(arr, data)
但是,上述做法仍会带来一个问题,即网络输出数据分布的方差会随着输入神经元个数而改变。为解决这一问题,一般会在初始化的同时加上对方差大小的规范化。Xavier初始化就可以根据每一层输入与输出的连接数目来确定权重的初始化范围,以保持信号在前向传播过程中的稳定性,其PyTorch实现如下所示:
1# Xavier initialization (PyTorch)
2def xavier_normal_(tensor: Tensor, gain: float = 1.) -> Tensor:
3 """
4 Fills the input `Tensor` with values according to the method
5 described in `Understanding the difficulty of training deep feedforward
6 neural networks` - Glorot, X. & Bengio, Y. (2010), using a normal
7 distribution.
8
9 Also known as Glorot initialization.
10
11 Args:
12 tensor: an n-dimensional `torch.Tensor`
13 gain: an optional scaling factor
14 """
15 # Ln.16-Ln.17: Calculate the normalized standard deviation.
16 fan_in, fan_out = _calculate_fan_in_and_fan_out(tensor)
17 std = gain * math.sqrt(2.0 / float(fan_in + fan_out))
18
19 return _no_grad_normal_(tensor, 0., std)
20
21def _calculate_fan_in_and_fan_out(tensor):
22 dimensions = tensor.dim()
23 if dimensions < 2:
24 raise ValueError("Fan in and fan out can not be computed for tensor with fewer than 2 dimensions")
25
26 num_input_fmaps = tensor.size(1)
27 num_output_fmaps = tensor.size(0)
28 receptive_field_size = 1
29 if tensor.dim() > 2:
30 for s in tensor.shape[2:]:
31 receptive_field_size *= s
32 fan_in = num_input_fmaps * receptive_field_size
33 fan_out = num_output_fmaps * receptive_field_size
34
35 return fan_in, fan_out
在MindSpore中的实现为:
1# Xavier initialization (MindSpore)
2@_register('xavier_normal')
3class XavierNormal(Initializer):
4 """
5 Generates an array with values sampled from Xavier normal distribution in order to initialize a tensor.
6
7 Args:
8 gain (float): An optional scaling factor. Default: 1.
9 """
10 def __init__(self, gain=1):
11 super().__init__(gain=gain)
12 self.gain = gain
13
14 def _initialize(self, arr):
15 # Ln.16-Ln.27: Calculate the normalized standard deviation.
16 fan_in, fan_out = _calculate_fan_in_and_fan_out(arr.shape)
17 std = self.gain * math.sqrt(2.0 / float(fan_in + fan_out))
18 data = _init_random_normal(0, std, arr.shape)
19
20 _assignment(arr, data)
不过,细心的读者应能发现Xavier方法仍有不甚完美之处,即该方法并未考虑非线性映射函数对输入s的影响。2015年He等人对此提出改进——将非线性映射造成的影响考虑进参数初始化中,他们提出原本Xavier方法中方差规范化的分母应为sqrt(n/2)而不是sqrt(n)。He方法在PyTorch中的调用方式为kaiming_normal_()函数:
1# He initialization (PyTorch)
2def kaiming_normal_(
3 tensor: Tensor, a: float = 0, mode: str = 'fan_in', nonlinearity: str = 'leaky_relu'
4):
5 """
6 Fills the input `Tensor` with values according to the method
7 described in `Delving deep into rectifiers: Surpassing human-level
8 performance on ImageNet classification` - He, K. et al. (2015), using a
9 normal distribution.
10
11 Also known as He initialization.
12
13 Args:
14 tensor: an n-dimensional `torch.Tensor`
15 a: the negative slope of the rectifier used after this layer (only
16 used with ``'leaky_relu'``)
17 mode: either ``'fan_in'`` (default) or ``'fan_out'``. Choosing ``'fan_in'``
18 preserves the magnitude of the variance of the weights in the
19 forward pass. Choosing ``'fan_out'`` preserves the magnitudes in the
20 backwards pass.
21 nonlinearity: the non-linear function (`nn.functional` name),
22 recommended to use only with ``'relu'`` or ``'leaky_relu'`` (default).
23 """
24 if 0 in tensor.shape:
25 warnings.warn("Initializing zero-element tensors is a no-op")
26 return tensor
27 # Ln.28-Ln.32: Apply He normalization according to the `mode` and the `nonlinearity` function.
28 fan = _calculate_correct_fan(tensor, mode)
29 gain = calculate_gain(nonlinearity, a)
30 std = gain / math.sqrt(fan)
31 with torch.no_grad():
32 return tensor.normal_(0, std)
33
34 def calculate_gain(nonlinearity, param=None):
35 """
36 Return the recommended gain value for the given nonlinearity function.
37
38 Args:
39 nonlinearity: the non-linear function (`nn.functional` name)
40 param: optional parameter for the non-linear function
41 """
42 linear_fns = ['linear', 'conv1d', 'conv2d', 'conv3d', 'conv_transpose1d', 'conv_transpose2d', 'conv_transpose3d']
43 if nonlinearity in linear_fns or nonlinearity == 'sigmoid':
44 return 1
45 elif nonlinearity == 'tanh':
46 return 5.0 / 3
47 elif nonlinearity == 'relu':
48 return math.sqrt(2.0)
49 elif nonlinearity == 'leaky_relu':
50 if param is None:
51 negative_slope = 0.01
52 elif not isinstance(param, bool) and isinstance(param, int) or isinstance(param, float):
53 negative_slope = param
54 else:
55 raise ValueError("negative_slope {} not a valid number".format(param))
56 return math.sqrt(2.0 / (1 + negative_slope ** 2))
57 elif nonlinearity == 'selu':
58 return 3.0 / 4 # Value found empirically (https://github.com/pytorch/pytorch/pull/50664)
59 else:
60 raise ValueError("Unsupported nonlinearity {}".format(nonlinearity))
61
62 def _calculate_correct_fan(tensor, mode):
63 mode = mode.lower()
64 valid_modes = ['fan_in', 'fan_out']
65 if mode not in valid_modes:
66 raise ValueError("Mode {} not supported, please use one of {}".format(mode, valid_modes))
67
68 fan_in, fan_out = _calculate_fan_in_and_fan_out(tensor)
69 return fan_in if mode == 'fan_in' else fan_out
在MindSpore中的实现为:
1# He initialization (MindSpore)
2@_register('he_normal')
3class HeNormal(Initializer):
4 """
5 Generates an array with values sampled from HeKaiming Normal distribution
6 in order to initialize a tensor.
7
8 For details of HeNormal algorithm, please check `<https://arxiv.org/abs/1502.01852>`_.
9
10 Args:
11 negative_slope (int, float): The negative slope of the rectifier used after this layer
12 (only used when `nonlinearity` is 'leaky_relu'). Default: 0.
13 mode (str): Either 'fan_in' or 'fan_out'. Choosing 'fan_in' preserves the magnitude of the
14 variance of the weights in the forward pass. Choosing 'fan_out' preserves the magnitudes
15 in the backwards pass. Default: 'fan_in'.
16 nonlinearity (str): The non-linear function, recommended to use only with 'relu' or 'leaky_relu'.
17 Default: 'leaky_relu'.
18 """
19 def __init__(self, negative_slope=0, mode='fan_in', nonlinearity='leaky_relu'):
20 super(HeNormal, self).__init__(negative_slope=negative_slope, mode=mode, nonlinearity=nonlinearity)
21 self.negative_slope = negative_slope
22 self.mode = mode
23 self.nonlinearity = nonlinearity
24
25 def _initialize(self, arr):
26 # Ln.27-Ln.31: Apply He normalization according to the `mode` and the `nonlinearity` function.
27 fan = _calculate_correct_fan(arr.shape, self.mode)
28 gain = _calculate_gain(self.nonlinearity, self.negative_slope)
29 std = gain / math.sqrt(fan)
30 data = _init_random_normal(0, std, arr.shape)
31 _assignment(arr, data)
以上是参数初始化分布服从高斯分布的情形。 均匀分布也是一种很好的初始化分布,当参数初始化分布服从均匀分布(uniform distribution)时,由于分布性质的不同,对于均匀分布需指定其取值区间,则Xavier初始化方法和He初始化方法在PyTorch中分别修改为:
1# Xavier initialization (uniform distribution, PyTorch)
2def xavier_uniform_(tensor: Tensor, gain: float = 1.) -> Tensor:
3 """
4 Fills the input `Tensor` with values according to the method
5 described in `Understanding the difficulty of training deep feedforward
6 neural networks` - Glorot, X. & Bengio, Y. (2010), using a uniform
7 distribution.
8
9 Also known as Glorot initialization.
10
11 Args:
12 tensor: an n-dimensional `torch.Tensor`
13 gain: an optional scaling factor
14 """
15 # Ln.16-Ln.18: Calculate uniform bounds from standard deviation.
16 fan_in, fan_out = _calculate_fan_in_and_fan_out(tensor)
17 std = gain * math.sqrt(2.0 / float(fan_in + fan_out))
18 a = math.sqrt(3.0) * std
19
20 return _no_grad_uniform_(tensor, -a, a)
1# He initialization (uniform distribution, PyTorch)
2def kaiming_uniform_(
3 tensor: Tensor, a: float = 0, mode: str = 'fan_in', nonlinearity: str = 'leaky_relu'
4):
5 """
6 Fills the input `Tensor` with values according to the method
7 described in `Delving deep into rectifiers: Surpassing human-level
8 performance on ImageNet classification` - He, K. et al. (2015), using a
9 uniform distribution.
10
11 Also known as He initialization.
12
13 Args:
14 tensor: an n-dimensional `torch.Tensor`
15 a: the negative slope of the rectifier used after this layer (only
16 used with ``'leaky_relu'``)
17 mode: either ``'fan_in'`` (default) or ``'fan_out'``. Choosing ``'fan_in'``
18 preserves the magnitude of the variance of the weights in the
19 forward pass. Choosing ``'fan_out'`` preserves the magnitudes in the
20 backwards pass.
21 nonlinearity: the non-linear function (`nn.functional` name),
22 recommended to use only with ``'relu'`` or ``'leaky_relu'`` (default).
23 """
24 if torch.overrides.has_torch_function_variadic(tensor):
25 return torch.overrides.handle_torch_function(
26 kaiming_uniform_,
27 (tensor,),
28 tensor=tensor,
29 a=a,
30 mode=mode,
31 nonlinearity=nonlinearity)
32
33 if 0 in tensor.shape:
34 warnings.warn("Initializing zero-element tensors is a no-op")
35 return tensor
36 # Ln.37-Ln.40: Calculate uniform bounds from standard deviation.
37 fan = _calculate_correct_fan(tensor, mode)
38 gain = calculate_gain(nonlinearity, a)
39 std = gain / math.sqrt(fan)
40 bound = math.sqrt(3.0) * std
41 with torch.no_grad():
42 return tensor.uniform_(-bound, bound)
在MindSpore中的实现为:
1# Xavier initialization (uniform distribution, MindSpore)
2@_register('xavier_uniform')
3class XavierUniform(Initializer):
4 """
5 Generates an array with values sampled from Xavier uniform distribution
6 in order to initialize a tensor.
7
8 For details of XavierUniform algorithm, please check
9 `<http://proceedings.mlr.press/v9/glorot10a.html>`_.
10
11 Args:
12 gain (float): An optional scaling factor. Default: 1.
13 """
14 def __init__(self, gain=1):
15 super(XavierUniform, self).__init__(gain=gain)
16 self.gain = gain
17
18 def _initialize(self, arr):
19 # Ln.20-Ln.21: Calculate uniform bounds from standard deviation.
20 n_in, n_out = _calculate_fan_in_and_fan_out(arr.shape)
21 boundary = self.gain * math.sqrt(6.0 / (n_in + n_out))
22 data = _init_random_uniform(-boundary, boundary, arr.shape)
23 _assignment(arr, data)
1# He initialization (uniform distribution, MindSpore)
2@_register('he_uniform')
3class HeUniform(Initializer):
4 """
5 Generates an array with values sampled from HeKaiming Uniform distribution
6 in order to initialize a tensor.
7
8 For details of HeUniform algorithm, please check
9 `<https://arxiv.org/abs/1502.01852>`_.
10
11 Args:
12 negative_slope (int, float, bool): The negative slope of the rectifier used after this layer
13 (only used when `nonlinearity` is 'leaky_relu'). Default: 0.
14 mode (str): Either 'fan_in' or 'fan_out'. Choosing 'fan_in' preserves the magnitude of the
15 variance of the weights in the forward pass. Choosing 'fan_out' preserves the magnitudes
16 in the backwards pass. Default: 'fan_in'.
17 nonlinearity (str): The non-linear function, recommended to use only with 'relu' or 'leaky_relu'.
18 Default: 'leaky_relu'.
19 """
20 def __init__(self, negative_slope=0, mode='fan_in', nonlinearity='leaky_relu'):
21 super(HeUniform, self).__init__(negative_slope=negative_slope, mode=mode, nonlinearity=nonlinearity)
22 self.negative_slope = negative_slope
23 self.mode = mode
24 self.nonlinearity = nonlinearity
25
26 def _initialize(self, arr):
27 # Ln.28-Ln.31: Calculate uniform bounds from standard deviation.
28 fan = _calculate_correct_fan(arr.shape, self.mode)
29 gain = _calculate_gain(self.nonlinearity, self.negative_slope)
30 std = gain / math.sqrt(fan)
31 boundary = math.sqrt(3.0) * std
32 data = _init_random_uniform(-boundary, boundary, arr.shape)
33 _assignment(arr, data)