第十二章 超参数设定和网络训练

12.2 训练技巧

12.2.3 批规范化操作

在PyTorch中,可按如下调用示例使用批规范化操作(Batch Normalization,BN)操作:

1# batch normalization (PyTorch)

2import torch

3import torch.nn as nn

4

5# Ln.6-Ln.8: Apply batch normalization.

6m = nn.BatchNorm2d(4)

7inp = torch.randn(1, 4, 3, 3)

8oup = m(inp)

9print(m.weight, m.bias)

在MindSpore中的实现为:

 1# batch normalization (MindSpore)

 2import numpy as np

 3import mindspore.nn as nn

 4import mindspore as ms

 5

 6x = ms.Tensor(np.array([[[[1, 2], [1, 2]], [[3, 4], [3, 4]]]]).astype(np.float32))

 7# Ln.8-Ln.9: Apply batch normalization.

 8bn = nn.BatchNorm2d(num_features=2, momentum=0.8)

 9output = bn(x)

10print(output)

BN更适用于mini-batch较大的场景,且需对训练数据做好充分的随机打乱从而获得更加接近整体分布的mini-batch统计量。此外,BN操作还需在运行过程中统计每个mini-batch的一阶和二阶统计量,这也限制了其在递归神经网络(recurrent neural networks)和动态神经网络(dynamic neural networks)中的应用。针对BN的不足,层规范化(layer normalization)操作被提出。该操作针对一整层的所有维度的输入计算对应均值与方差,之后,使用类似BN的规范化操作来转换各个维度的输入。层规范化操作基于PyTorch的具体实现细节可参考如下示例:

 1# implementations of layer normalization (PyTorch)

 2class LayerNorm(nn.Module):

 3

 4    def __init__(self, num_channels, eps=1e-05, affine=True):

 5        super().__init__()

 6        self.num_channels = num_channels

 7        self.eps = eps

 8        self.affine = affine

 9        if self.affine:

10            self.weight = nn.Parameter(torch.ones(num_channels))

11            self.bias = nn.Parameter(torch.zeros(num_channels))

12        else:

13            self.weight = None

14            self.bias = None

15        self.reset_parameters()

16

17    def reset_parameters(self):

18        if self.affine:

19            nn.init.ones_(self.weight)

20            nn.init.zeros_(self.bias)

21

22    def forward(self, x):

23        N, C, H, W = x.shape

24        assert C == self.num_channels

25        x = x.reshape(N, -1)

26

27        # Ln.28-Ln.35: Calculate mean and variance and apply batch normalization.

28        mean = x.mean(axis=1, keepdim=True)

29        var = (x ** 2).mean(axis=1, keepdim=True) - mean * mean

30

31        x = (x - mean) / (var + self.eps).sqrt()

32        x = x.reshape(N, C, H, W)

33        if self.affine:

34            x = self.weight.reshape(1, -1, 1, 1) * x + self.bias.reshape(1, -1, 1, 1)

35        return x

在MindSpore中的实现为:

 1# implementations of layer normalization (MindSpore)

 2class LayerNorm(Cell):

 3    """

 4    Applies Layer Normalization over a mini-batch of inputs.

 5

 6    Inputs:

 7        - **x** (Tensor) - The shape of `x` is :math:`(x_1, x_2, ..., x_R)`,

 8          and `input_shape[begin_norm_axis:]` is equal to `normalized_shape`.

 9

10    Outputs:

11        Tensor, the normalized and scaled offset tensor, has the same shape and data type as the `x`.

12    """

13

14    def __init__(self,

15                 normalized_shape,

16                 begin_norm_axis=-1,

17                 begin_params_axis=-1,

18                 gamma_init='ones',

19                 beta_init='zeros',

20                 epsilon=1e-7

21                 ):

22        """Initialize LayerNorm."""

23        super(LayerNorm, self).__init__()

24        if not isinstance(normalized_shape, (tuple, list)):

25            raise TypeError(f"For '{self.cls_name}', the type of 'normalized_shape' must be tuple[int] or list[int], "

26                            f"but got {normalized_shape} and the type is {type(normalized_shape)}.")

27        self.normalized_shape = normalized_shape

28        self.begin_norm_axis = begin_norm_axis

29        self.begin_params_axis = begin_params_axis

30        self.epsilon = epsilon

31        self.gamma = Parameter(initializer(

32            gamma_init, normalized_shape), name="gamma")

33        self.beta = Parameter(initializer(

34            beta_init, normalized_shape), name="beta")

35        # Ln.36-Ln.38: Initialize the LayerNorm funtion.

36        self.layer_norm = P.LayerNorm(begin_norm_axis=self.begin_norm_axis,

37                                      begin_params_axis=self.begin_params_axis,

38                                      epsilon=self.epsilon)

39

40    def construct(self, input_x):

41        # Ln.42: Apply the LayerNorm funtion.

42        y, _, _ = self.layer_norm(input_x, self.gamma.astype(input_x.dtype), self.beta.astype(input_x.dtype))

43        return y

44

45    def extend_repr(self):

46        return 'normalized_shape={}, begin_norm_axis={}, begin_params_axis={}, gamma{}, beta={}'.format(

47            self.normalized_shape, self.begin_norm_axis, self.begin_params_axis, self.gamma, self.beta)

不过,层规范化潜在假设了同一规范化操作应作用于整层神经元,在某种程度限制了神经元多样性,这也为后续的组规范化(group normalization)操作的提出埋下了伏笔。该操作对卷积通道进行了分组,既有效解决了mini-batch大小对于BN的影响,也在一定程度缓解了层规范化对于层内神经元多样性的局限。组规范化操作基于PyTorch的具体实现细节可参考如下示例:

 1# implementations of group normalization (PyTorch)

 2class GroupNorm(nn.Module):

 3

 4    def __init__(self, num_groups, num_channels, eps=1e-05, affine=True):

 5        super().__init__()

 6        assert num_channels % num_groups == 0

 7        self.num_groups = num_groups

 8        self.num_channels = num_channels

 9        self.eps = eps

10        self.affine = affine

11        if self.affine:

12            self.weight = nn.Parameter(torch.ones(num_channels))

13            self.bias = nn.Parameter(torch.zeros(num_channels))

14        else:

15            self.weight = None

16            self.bias = None

17        self.reset_parameters()

18

19    def reset_parameters(self):

20        if self.affine:

21            nn.init.ones_(self.weight)

22            nn.init.zeros_(self.bias)

23

24    def forward(self, x):

25        N, C, H, W = x.shape

26        assert C == self.num_channels

27        # Ln.28-Ln.30: Group the inputs and calculate the mean and variance.

28        x = x.reshape(N, self.num_groups, -1)

29        mean = x.mean(axis=2, keepdims=True)

30        var = (x ** 2).mean(axis=2, keepdim=True) - mean * mean

31

32        # Ln.33-Ln.36: Apply group normalization.

33        x = (x - mean) / (var + self.eps).sqrt()

34        x = x.reshape(N, C, H, W)

35        if self.affine:

36            x = self.weight.reshape(1, -1, 1, 1) * x + self.bias.reshape(1, -1, 1, 1)

37

38        return x

在MindSpore中的实现为:

 1# implementations of group normalization (MindSpore)

 2class GroupNorm(Cell):

 3    """

 4    Group Normalization over a mini-batch of inputs.

 5

 6    Inputs:

 7        - **x** (Tensor) - The input feature with shape (N, C, H, W) .

 8

 9    Outputs:

10        Tensor, the normalized and scaled offset tensor, has the same shape and data type as the `x`.

11

12    """

13

14    def __init__(self, num_groups, num_channels, eps=1e-05, affine=True, gamma_init='ones', beta_init='zeros'):

15        """Initialize GroupNorm."""

16        super(GroupNorm, self).__init__()

17        self.num_groups = validator.check_positive_int(num_groups, "num_groups", self.cls_name)

18        self.num_channels = validator.check_positive_int(num_channels, "num_channels", self.cls_name)

19        if num_channels % num_groups != 0:

20            raise ValueError(f"For '{self.cls_name}', the 'num_channels' must be divided by 'num_groups', "

21                             f"but got 'num_channels': {num_channels}, 'num_groups': {num_groups}.")

22        self.eps = validator.check_value_type('eps', eps, (float,), type(self).__name__)

23        self.affine = validator.check_bool(affine, arg_name="affine", prim_name=self.cls_name)

24

25        self.gamma = Parameter(initializer(

26            gamma_init, num_channels), name="gamma", requires_grad=affine)

27        self.beta = Parameter(initializer(

28            beta_init, num_channels), name="beta", requires_grad=affine)

29        self.shape = F.shape

30        self.reshape = F.reshape

31        self.reduce_mean = P.ReduceMean(keep_dims=True)

32        self.square = F.square

33        self.reduce_sum = P.ReduceSum(keep_dims=True)

34        self.sqrt = P.Sqrt()

35

36    def _cal_output(self, x):

37        """calculate groupnorm output"""

38        batch, channel, height, width = self.shape(x)

39        _channel_check(channel, self.num_channels, self.cls_name)

40

41        # Ln.42-Ln.45: Group the inputs and calculate the mean, variance and standard deviation.

42        x = self.reshape(x, (batch, self.num_groups, -1))

43        mean = self.reduce_mean(x, 2)

44        var = self.reduce_sum(self.square(x - mean), 2) / (channel * height * width / self.num_groups)

45        std = self.sqrt(var + self.eps)

46

47        # Ln.48-Ln.50: Apply group normalization.

48        x = (x - mean) / std

49        x = self.reshape(x, (batch, channel, height, width))

50        output = x * self.reshape(self.gamma, (-1, 1, 1)) + self.reshape(self.beta, (-1, 1, 1))

51        return output

52

53    def construct(self, x):

54        _shape_check(self.shape(x), self.cls_name)

55        _check_dtype(x.dtype, [mstype.float16, mstype.float32], "input", self.cls_name)

56        output = self._cal_output(x)

57        return output

58

59    def extend_repr(self):

60        return 'num_groups={}, num_channels={}'.format(self.num_groups, self.num_channels)

在规范化操作的研究中,研究者还提出了一种更为极致的规范化操作,即示例规范化(instance normalization,或contrast normalization),它针对每个样本的每个通道单独计算其统计信息。示例规范化操作基于PyTorch的具体实现细节可参考如下示例:

 1# implementations of instance normalization (PyTorch)

 2class InstanceNorm(nn.Module):

 3

 4    def __init__(self, num_channels, eps=1e-05, affine=True):

 5        super().__init__()

 6        self.num_channels = num_channels

 7        self.eps = eps

 8        self.affine = affine

 9        if self.affine:

10            self.weight = nn.Parameter(torch.ones(num_channels))

11            self.bias = nn.Parameter(torch.zeros(num_channels))

12        else:

13            self.weight = None

14            self.bias = None

15        self.reset_parameters()

16

17    def reset_parameters(self):

18        if self.affine:

19            nn.init.ones_(self.weight)

20            nn.init.zeros_(self.bias)

21

22    def forward(self, x):

23        N, C, H, W = x.shape

24        assert C == self.num_channels

25        # Ln.26-Ln.28: Calculate the mean and variance for a single sample of a single channel.

26        x = x.reshape(N, C, -1)

27        mean = x.mean(axis=2, keepdim=True)

28        var = (x ** 2).mean(axis=2, keepdim=True) - mean * mean

29

30        # Ln.31-Ln.34: Apply instance normalization.

31        x = (x - mean) / (var + self.eps).sqrt()

32        x = x.reshape(N, C, H, W)

33        if self.affine:

34            x = self.weight.reshape(1, -1, 1, 1) * x + self.bias.reshape(1, -1, 1, 1)

35

36        return x

在MindSpore中的实现为:

 1# implementations of instance normalization (MindSpore)

 2class _InstanceNorm(Cell):

 3    """Instance Normalization base class."""

 4

 5    @cell_attr_register

 6    def __init__(self,

 7                 num_features,

 8                 eps=1e-5,

 9                 momentum=0.1,

10                 affine=True,

11                 gamma_init='ones',

12                 beta_init='zeros',

13                 input_dims='2d'):

14        """Initialize Normalization base class."""

15        super(_InstanceNorm, self).__init__()

16        validator.check_value_type('num_features', num_features, [int], self.cls_name)

17        validator.check_value_type('eps', eps, [float], self.cls_name)

18        validator.check_value_type('momentum', momentum, [float], self.cls_name)

19        validator.check_value_type('affine', affine, [bool], self.cls_name)

20        args_input = {"gamma_init": gamma_init, "beta_init": beta_init}

21        self.check_types_valid(args_input, 'InstanceNorm2d')

22        if num_features < 1:

23            raise ValueError(f"For '{self.cls_name}', the 'num_features' must be at least 1, but got {num_features}.")

24

25        if momentum < 0 or momentum > 1:

26            raise ValueError(f"For '{self.cls_name}', the 'momentum' must be a number in range [0, 1], "

27                          f"but got {momentum}.")

28        self.num_features = num_features

29        self.eps = eps

30        self.input_dims = input_dims

31        self.moving_mean = Parameter(initializer('zeros', num_features), name="mean", requires_grad=False)

32        self.moving_variance = Parameter(initializer('ones', num_features), name="variance", requires_grad=False)

33        self.gamma = Parameter(initializer(

34            gamma_init, num_features), name="gamma", requires_grad=affine)

35        self.beta = Parameter(initializer(

36            beta_init, num_features), name="beta", requires_grad=affine)

37

38        self.shape = P.Shape()

39        self.momentum = momentum

40        # Ln.41: Initialize the InstanceNorm funtion.

41        self.instance_bn = P.InstanceNorm(epsilon=self.eps, momentum=self.momentum)

42

43    def construct(self, x):

44        _shape_check_in(self.shape(x), self.input_dims, self.cls_name)

45        # Ln.46-Ln.50: Apply instance normalization.

46        return self.instance_bn(x,

47                                self.gamma,

48                                self.beta,

49                                self.moving_mean,

50                                self.moving_variance)[0]

51

52    def extend_repr(self):

53        return 'num_features={}, eps={}, momentum={}, gamma={}, beta={}, moving_mean={}, moving_variance={}'.format(

54            self.num_features, self.eps, self.momentum, self.gamma, self.beta, self.moving_mean, self.moving_variance)

55

56    def check_types_valid(self, args_dict, name):

57        for key, _ in args_dict.items():

58            val = args_dict[key]

59            if not isinstance(val, (Tensor, numbers.Number, str, Initializer)):

60                raise TypeError(f"For '{self.cls_name}', the type of '{key}' must be in "

61                                f"[Tensor, numbers.Number, str, Initializer], but got type {type(val).__name__}.")

62            if isinstance(val, Tensor) and val.dtype != mstype.float32:

63                raise TypeError(f"For '{self.cls_name}', the type of '{key}' must be float32, "

64                                f"but got {val.dtype}.")

12.2.4 网络模型优化算法选择

12.2.4.1 随机梯度下降法

SGD基于PyTorch的实现可参考:

1# SGD (PyTorch)

2for param in params:

3    # Ln.4-Ln.5: Obtain the gradient and update it.

4    d_p = param.grad.data

5    param.add_(d_p, alpha=-lr)

基于MindSpore的实现为:

1# SGD (MindSpore)

2for param in params:

3    # Ln.4-Ln.5: Obtain the gradient and update it.

4    d_p = param.grad.asnumpy()

5    param = ops.AssignAdd(param, Tensor(-lr * d_p))

12.2.4.2 基于动量的随机梯度下降法

然而,SGD在优化神经网络时也面临一些挑战。它可能会因为更新步长较大或梯度波动导致的震荡而使收敛速度缓慢,并因此陷入到局部极小值。这种情况下,普通的SGD可能表现出不稳定性和收敛速度慢的问题。为了克服这些问题,动量被引入到优化算法中。基于动量的随机梯度下降法在PyTorch中的具体实现可参考如下示例:

 1# momentum SGD (PyTorch)

 2for i, param in enumerate(params):

 3    d_p = param.grad.data

 4

 5    # Ln.6-Ln.11: Obtain the momentum and update it.

 6    buf = momentum_buffer_list[i]

 7    if buf is None:

 8        buf = torch.clone(d_p).detach()

 9        momentum_buffer_list[i] = buf

10    else:

11        buf.mul_(momentum).add_(d_p, alpha=1 - dampening)

12

13    # Ln.14-Ln.15: Update the gradient and parameters.

14    d_p = buf

15    param.add_(d_p, alpha=-lr)

其基于MindSpore的实现为:

 1# momentum SGD (MindSpore)

 2for i, param in enumerate(params):

 3    d_p = param.grad.asnumpy()

 4

 5    # Ln.6-Ln.11: Obtain the momentum and update it.

 6    buf = momentum_buffer_list[i]

 7    if buf is None:

 8        buf = Tensor(d_p.copy())

 9        momentum_buffer_list[i] = buf

10    else:

11        buf = ops.Mul()(buf, momentum) + ops.Mul()(d_p, 1 - dampening)

12

13    # Ln.14-Ln.15: Update the gradient and parameters.

14    d_p = buf.asnumpy()

15    param = ops.AssignAdd()(param, Tensor(-lr * d_p))

12.2.4.3 Nesterov型动量随机梯度下降法

Nesterov动量在计算梯度之前,首先根据之前的动量更新来调整当前位置。这种预测下一个位置的方式有助于更准确地估计当前位置的梯度,从而减少了一些摆动和不必要的更新。在PyTorch中,Nesterov型动量随机下降法可实现为:

 1# Nesterov momentum SGD (PyTorch)

 2for i, param in enumerate(params):

 3    d_p = param.grad.data

 4

 5    # Ln.6-Ln.11: Obtain the momentum and update it.

 6    buf = momentum_buffer_list[i]

 7    if buf is None:

 8        buf = torch.clone(d_p).detach()

 9        momentum_buffer_list[i] = buf

10    else:

11        buf.mul_(momentum).add_(d_p, alpha=1 - dampening)

12

13    # Ln.14-Ln.15: Update the gradient by momentum and parameters.

14    d_p = d_p.add(buf, alpha=momentum)

15    param.add_(d_p, alpha=-lr)

其基于MindSpore的实现为:

 1# Nesterov momentum SGD (MindSpore)

 2for i, param in enumerate(params):

 3    d_p = param.grad.asnumpy()

 4

 5    # Ln.6-Ln.11: Obtain the momentum and update it.

 6    buf = momentum_buffer_list[i]

 7    if buf is None:

 8        buf = Tensor(d_p.copy())

 9        momentum_buffer_list[i] = buf

10    else:

11        buf = ops.Mul()(buf, momentum) + ops.Mul()(d_p, 1 - dampening)

12

13    # Ln.14-Ln.15: Update the gradient by momentum and parameters.

14    d_p = ops.TensorAdd()(d_p, ops.Mul()(buf, momentum)).asnumpy()

15    param = ops.AssignAdd()(param, Tensor(-lr * d_p))

12.2.4.4 Adagrad法

Adagrad是一种自适应学习率的优化算法,它针对每个参数应用不同的学习率。在PyTorch中,Adagrad法的实现可参考如下示例:

 1# Adagrad (PyTorch)

 2for (param, grad, state_sum, step) in zip(params, grads, state_sums, state_steps):

 3    if weight_decay != 0:

 4        if grad.is_sparse:

 5            raise RuntimeError("weight_decay option is not compatible with sparse gradients")

 6        grad = grad.add(param, alpha=weight_decay)

 7

 8    # Ln.9: Calculate the dynamic learning rate associated with the step.

 9    clr = lr / (1 + (step - 1) * lr_decay)

10

11    # Ln.12-Ln.25: Update the gradient and parameters by the dynamic learning rate.

12    if grad.is_sparse:

13        grad = grad.coalesce()  # the update is non-linear so indices must be unique

14        grad_indices = grad._indices()

15        grad_values = grad._values()

16        size = grad.size()

17

18        state_sum.add_(_make_sparse(grad, grad_indices, grad_values.pow(2)))

19        std = state_sum.sparse_mask(grad)

20        std_values = std._values().sqrt_().add_(eps)

21        param.add_(_make_sparse(grad, grad_indices, grad_values / std_values), alpha=-clr)

22    else:

23        state_sum.addcmul_(grad, grad, value=1)

24        std = state_sum.sqrt().add_(eps)

25        param.addcdiv_(grad, std, value=-clr)

其基于MindSpore的实现为:

 1# Adagrad (MindSpore)

 2from mindspore import Tensor

 3import mindspore.ops as ops

 4

 5for (param, grad, state_sum, step) in zip(params, grads, state_sums, state_steps):

 6    if weight_decay != 0:

 7        if grad.is_sparse():

 8            raise RuntimeError("weight_decay option is not compatible with sparse gradients")

 9        grad = grad + weight_decay * param

10

11    # Ln.9: Calculate the dynamic learning rate associated with the step.

12    clr = lr / (1 + (step - 1) * lr_decay)

13

14    # Ln.12-Ln.25: Update the gradient and parameters by the dynamic learning rate.

15    if grad.is_sparse():

16        grad = grad.coalesce()  # the update is non-linear so indices must be unique

17        grad_indices = grad.indices

18        grad_values = grad.values

19        size = grad.shape

20

21        state_sum = state_sum + ops.SparseApplyFtrl(grad_indices, grad_values**2)

22        std = ops.SparseApplyFtrl(grad_indices, state_sum).sqrt() + eps

23        param = param - clr * ops.SparseApplyFtrl(grad_indices, grad_values / std)

24    else:

25        state_sum = state_sum + ops.Mul()(grad, grad)

26        std = ops.Sqrt()(state_sum) + eps

27        param = param - clr * (grad / std)

12.2.4.5 Adadelta法

Adadelta是一种自适应学习率的优化算法,旨在解决 Adagrad算法中学习率急剧下降的问题。在PyTorch中,Adadelta法的实现可参考如下示例:

 1# Adadelta (PyTorch)

 2for (param, grad, square_avg, acc_delta) in zip(params, grads, square_avgs, acc_deltas):

 3    if weight_decay != 0:

 4        grad = grad.add(param, alpha=weight_decay)

 5

 6    # Ln.7-Ln.11: Calculate the running average of the gradient squares by rho, and update the gradient and parameters with it.

 7    square_avg.mul_(rho).addcmul_(grad, grad, value=1 - rho)

 8    std = square_avg.add(eps).sqrt_()

 9    delta = acc_delta.add(eps).sqrt_().div_(std).mul_(grad)

10    param.add_(delta, alpha=-lr)

11    acc_delta.mul_(rho).addcmul_(delta, delta, value=1 - rho)

其基于MindSpore的实现为:

 1# Adadelta (MindSpore)

 2for (param, grad, square_avg, acc_delta) in zip(params, grads, square_avgs, acc_deltas):

 3    if weight_decay != 0:

 4        grad = ops.TensorAdd()(grad, ops.Mul()(param, weight_decay))

 5

 6    # Ln.7-Ln.11: Calculate the running average of the gradient squares by rho, and update the gradient and parameters with it.

 7    square_avg = ops.TensorAdd()(ops.Mul()(square_avg, rho), ops.Mul()(ops.Sub()(1, rho), ops.Square()(grad)))

 8    std = ops.Sqrt()(ops.TensorAdd()(square_avg, eps))

 9    delta = ops.Mul()(ops.Div()(ops.Sqrt()(ops.TensorAdd()(acc_delta, eps)), std), grad)

10    param = ops.AssignAdd()(param, ops.Neg()(ops.Mul()(lr, delta)))

11    acc_delta = ops.TensorAdd()(ops.Mul()(acc_delta, rho), ops.Mul()(ops.Sub()(1, rho), ops.Square()(delta)))

12.2.4.6 RMSProp法

RMSProp(Root Mean Square Propagation)则在解决Adagrad算法中学习率急剧下降的问题的同时,并改善Adadelta算法在某些情况下学习率过于稳定的问题。在PyTorch中,RMSProp法的实现可参考如下示例:

 1# RMSProp (PyTorch)

 2for i, param in enumerate(params):

 3    grad = grads[i]

 4    square_avg = square_avgs[i]

 5

 6    if weight_decay != 0:

 7        grad = grad.add(param, alpha=weight_decay)

 8

 9    square_avg.mul_(alpha).addcmul_(grad, grad, value=1 - alpha)

10

11    if centered:

12        grad_avg = grad_avgs[i]

13        grad_avg.mul_(alpha).add_(grad, alpha=1 - alpha)

14        avg = square_avg.addcmul(grad_avg, grad_avg, value=-1).sqrt_().add_(eps)

15    else:

16        avg = square_avg.sqrt().add_(eps)

17

18    # Ln.19-Ln.24: Update parameters by gradient, global learning rate and the momentum `avg`.

19    if momentum > 0:

20        buf = momentum_buffer_list[i]

21        buf.mul_(momentum).addcdiv_(grad, avg)

22        param.add_(buf, alpha=-lr)

23    else:

24        param.addcdiv_(grad, avg, value=-lr)

其基于MindSpore的实现为:

 1# RMSProp (MindSpore)

 2for i, param in enumerate(params):

 3    grad = grads[i]

 4    square_avg = square_avgs[i]

 5

 6    if weight_decay != 0:

 7        grad = ops.TensorAdd()(grad, ops.Mul()(param, weight_decay))

 8

 9    square_avg = ops.TensorAdd()(ops.Mul()(square_avg, alpha), ops.Mul()(ops.Sub()(1, alpha), ops.Square()(grad)))

10

11    if centered:

12        grad_avg = grad_avgs[i]

13        grad_avg = ops.TensorAdd()(ops.Mul()(grad_avg, alpha), ops.Mul()(ops.Sub()(1, alpha), grad))

14        avg = ops.Sqrt()(ops.TensorAdd()(square_avg, ops.Neg()(ops.Square()(grad_avg)))).add_(eps)

15    else:

16        avg = ops.Sqrt()(square_avg).add_(eps)

17

18    # Ln.19-Ln.24: Update parameters by gradient, global learning rate and the momentum `avg`.

19    if momentum > 0:

20        buf = momentum_buffer_list[i]

21        buf = ops.TensorAdd()(ops.Mul()(buf, momentum), ops.Div()(grad, avg))

22        param = ops.AssignAdd()(param, ops.Neg()(ops.Mul()(lr, buf)))

23    else:

24        param = ops.AssignAdd()(param, ops.Neg()(ops.Mul()(lr, ops.Div()(grad, avg))))

12.2.4.7 Adam法

Adam(Adaptive Moment Estimation)则结合了动量和自适应学习率算法的优点。在PyTorch中,Adam法的实现可参考如下示例:

 1# Adam (PyTorch)

 2for i, param in enumerate(params):

 3    grad = grads[i]

 4    exp_avg = exp_avgs[i]

 5    exp_avg_sq = exp_avg_sqs[i]

 6    step = state_steps[i]

 7

 8    bias_correction1 = 1 - beta1 ** step

 9    bias_correction2 = 1 - beta2 ** step

10

11    if weight_decay != 0:

12        grad = grad.add(param, alpha=weight_decay)

13

14    # Ln.15-Ln.16: Decay the first and second moment running average coefficient

15    exp_avg.mul_(beta1).add_(grad, alpha=1 - beta1)

16    exp_avg_sq.mul_(beta2).addcmul_(grad, grad, value=1 - beta2)

17    if amsgrad:

18        # Ln.19: Maintains the maximum of all 2nd moment running avg. till now

19        torch.maximum(max_exp_avg_sqs[i], exp_avg_sq, out=max_exp_avg_sqs[i])

20        # Ln.21: Use the max. for normalizing running avg. of gradient

21        denom = (max_exp_avg_sqs[i].sqrt() / math.sqrt(bias_correction2)).add_(eps)

22    else:

23        denom = (exp_avg_sq.sqrt() / math.sqrt(bias_correction2)).add_(eps)

24

25    step_size = lr / bias_correction1

26

27    param.addcdiv_(exp_avg, denom, value=-step_size)

其基于MindSpore的实现为:

 1# Adam (MindSpore)

 2for i, param in enumerate(params):

 3    grad = grads[i]

 4    exp_avg = exp_avgs[i]

 5    exp_avg_sq = exp_avg_sqs[i]

 6    step = state_steps[i]

 7

 8    bias_correction1 = 1 - beta1 ** step

 9    bias_correction2 = 1 - beta2 ** step

10

11    if weight_decay != 0:

12        grad = ops.TensorAdd()(grad, ops.Mul()(param, weight_decay))

13

14    # Ln.15-Ln.16: Decay the first and second moment running average coefficient

15    exp_avg = ops.TensorAdd()(ops.Mul()(exp_avg, beta1), ops.Mul()(ops.Sub()(1, beta1), grad))

16    exp_avg_sq = ops.TensorAdd()(ops.Mul()(exp_avg_sq, beta2), ops.Mul()(ops.Sub()(1, beta2), ops.Square()(grad)))

17

18    if amsgrad:

19        # Ln.19: Maintains the maximum of all 2nd moment running avg. till now

20        max_exp_avg_sq = max_exp_avg_sqs[i]

21        max_exp_avg_sq = ops.Maximum()(max_exp_avg_sq, exp_avg_sq)

22        # Ln.21: Use the max. for normalizing running avg. of gradient

23        denom = ops.TensorAdd()(ops.Sqrt()(ops.Div()(max_exp_avg_sq, math.sqrt(bias_correction2))), eps)

24    else:

25        denom = ops.TensorAdd()(ops.Sqrt()(ops.Div()(exp_avg_sq, math.sqrt(bias_correction2))), eps)

26

27    step_size = lr / bias_correction1

28

29    param = ops.AssignAdd()(param, ops.Neg()(ops.Mul()(step_size, ops.Div()(exp_avg, denom))))

12.2.5 微调神经网络

关于多目标学习方法的实现,可参考基于PyTorch的实现示例:

 1# multi-task (PyTorch)

 2# Ln.3-Ln.4: Load pre-trained model.

 3model = resnet50(pretrained=True)

 4model.eval()

 5

 6# Ln.7-Ln.18: Find neiborhood.

 7orginal_shallow_features = {}

 8for i, image in enumerate(original_dataloader):

 9    features = model.extract_feature(image)

10    original_shallow_features[i] = features['res1']

11

12target_shallow_features = {}

13for i, image in enumerate(target_dataloader):

14    features = model.extract_feature(image)

15    target_shallow_features[i] = features['res1']

16

17original_subset = nearest_neighbor(orginal_shallow_features, target_shallow_features)

18original_subset_dataloader = make_subset(orginal_dataloader, original_subset)

19

20# Ln.21-Ln.39: Finetune.

21model.train()

22classifier = nn.Linear(2048, C)

23

24optimizer = optim.SGD([

25    {'params': model.parameters()},

26    {'params': classifier.parameters()}], lr=lr)

27criterion = nn.CrossEntropyLoss()

28

29for (origin_image, origin_label), (target_image, target_label) in zip(original_subset_dataloader, target_dataloader):

30    origin_pred = model(orgin_image)

31    origin_loss = criterion(origin_pred, origin_label)

32    target_vector = model.extract_features(target_image)['res5']

33    target_vector = torch.flatten(F.avg_pool2d(target_vector, 7), 1)

34    target_pred = classifier(target_vector)

35    target_loss = criterion(target_pred, target_label)

36    loss = origin_loss + target_loss

37    optimizer.zero_grad()

38    optimizer.backward(loss)

39    optimizer.step()

其基于MindSpore的实现为:

 1# multi-task (MindSpore)

 2# Ln.3-Ln.4: Load pre-trained model.

 3model = resnet50(pretrained=True)

 4model.set_train(False)

 5

 6# Ln.7-Ln.18: Find neiborhood.

 7original_shallow_features = {}

 8for i, image in enumerate(original_dataloader):

 9    features = model.extract_feature(Tensor(image))

10    original_shallow_features[i] = features['res1']

11

12target_shallow_features = {}

13for i, image in enumerate(target_dataloader):

14    features = model.extract_feature(Tensor(image))

15    target_shallow_features[i] = features['res1']

16

17original_subset = nearest_neighbor(original_shallow_features, target_shallow_features)

18original_subset_dataloader = make_subset(original_dataloader, original_subset)

19

20# Ln.21-Ln.39: Finetune.

21model.set_train(True)

22classifier = nn.Dense(2048, C)

23

24optimizer = nn.SGD([

25    {'params': model.trainable_params()},

26    {'params': classifier.trainable_params()}], lr=lr)

27criterion = SoftmaxCrossEntropyWithLogits(sparse=True, reduction='mean')

28

29for (origin_image, origin_label), (target_image, target_label) in zip(original_subset_dataloader, target_dataloader):

30    origin_pred = model(Tensor(origin_image))

31    origin_loss = criterion(origin_pred, Tensor(origin_label))

32    target_vector = model.extract_features(Tensor(target_image))['res5']

33    target_vector = ops.Reshape()(ops.ReduceMean()(target_vector, (2, 3)), (-1, 2048))

34    target_pred = classifier(target_vector)

35    target_loss = criterion(target_pred, Tensor(target_label))

36    loss = ops.Add()(origin_loss, target_loss)

37    optimizer.zero_grad()

38    loss.backward()

39    optimizer.step()