Source code for mindspore.nn.optim.optimizer

# Copyright 2020-2022 Huawei Technologies Co., Ltd
#
# Licensed under the Apache License, Version 2.0 (the "License");
# you may not use this file except in compliance with the License.
# You may obtain a copy of the License at
#
# http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS,
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
# See the License for the specific language governing permissions and
# limitations under the License.
# ============================================================================
"""optimizer"""
from __future__ import absolute_import

import inspect
from typing import Iterable
import numpy as np

import mindspore
from mindspore.ops import functional as F, composite as C, operations as P
from mindspore.ops.operations import _inner_ops as inner
from mindspore.nn.cell import Cell
from mindspore.nn.layer.container import CellList
from mindspore.common.parameter import Parameter, ParameterTuple
from mindspore.common.initializer import initializer
from mindspore.common import Tensor
from mindspore.common.sparse_tensor import RowTensorInner
import mindspore.common.dtype as mstype
from mindspore import _checkparam as validator
from mindspore import log as logger
from mindspore.parallel._utils import _get_global_rank, _get_device_num, _get_parallel_mode
from mindspore.parallel._ps_context import _is_ps_mode
from mindspore.context import ParallelMode
from mindspore import context
from mindspore.nn.learning_rate_schedule import LearningRateSchedule
from mindspore.nn.optim._dist_optimizer_registry import generate_dist_optimizer_list

__all__ = ['Optimizer', 'opt_init_args_register']


def opt_init_args_register(fn):
    """Register optimizer init args."""

    import functools

    @functools.wraps(fn)
    def deco(self, *args, **kwargs):
        bound_args = inspect.signature(fn).bind(self, *args, **kwargs)
        bound_args.apply_defaults()
        arguments = bound_args.arguments
        arguments.pop('self')
        if 'params' in arguments.keys():
            setattr(self, 'init_params', dict({"params": arguments['params']}))
            arguments.pop('params')
        if 'optimizer' in arguments.keys():
            setattr(self, 'init_params', dict({"params": arguments['optimizer'].init_params["params"]}))
            arguments.pop('optimizer')
        if 'learning_rate' in arguments.keys():
            if isinstance(arguments['learning_rate'], Tensor):
                arguments['learning_rate'] = arguments['learning_rate'].asnumpy().tolist()
            if isinstance(arguments['learning_rate'], Cell):
                setattr(self, 'init_learning_rate', None)
            else:
                setattr(self, 'init_learning_rate', arguments['learning_rate'])
            arguments.pop('learning_rate')
        setattr(self, 'init_args', arguments)
        fn(self, *args, **kwargs)

    return deco


[docs]class Optimizer(Cell): """ Base class for updating parameters. Never use this class directly, but instantiate one of its subclasses instead. Grouping parameters is supported. If parameters are grouped, different strategy of `learning_rate`, `weight_decay` and `grad_centralization` can be applied to each group. Note: If parameters are not grouped, the `weight_decay` in optimizer will be applied on the network parameters without 'beta' or 'gamma' in their names. Users can group parameters to change the strategy of decaying weight. When parameters are grouped, each group can set `weight_decay`. If not, the `weight_decay` in optimizer will be applied. Args: learning_rate (Union[float, int, Tensor, Iterable, LearningRateSchedule]): - float: The fixed learning rate value. Must be equal to or greater than 0. - int: The fixed learning rate value. Must be equal to or greater than 0. It will be converted to float. - Tensor: Its value should be a scalar or a 1-D vector. For scalar, fixed learning rate will be applied. For vector, learning rate is dynamic, then the i-th step will take the i-th value as the learning rate. - Iterable: Learning rate is dynamic. The i-th step will take the i-th value as the learning rate. - LearningRateSchedule: Learning rate is dynamic. During training, the optimizer calls the instance of `LearningRateSchedule <https://www.mindspore.cn/docs/en/master/api_python/mindspore.nn.html#learningrateschedule-class>`_ with step as the input to get the learning rate of current step. parameters (Union[list[Parameter], list[dict]]): Must be list of `Parameter` or list of `dict`. When the `parameters` is a list of `dict`, the string "params", "lr", "weight_decay", "grad_centralization" and "order_params" are the keys can be parsed. - params: Required. Parameters in current group. The value must be a list of `Parameter`. - lr: Optional. If "lr" in the keys, the value of corresponding learning rate will be used. If not, the `learning_rate` in optimizer will be used. Fixed and dynamic learning rate are supported. - weight_decay: Optional. If "weight_decay" in the keys, the value of corresponding weight decay will be used. If not, the `weight_decay` in the optimizer will be used. - grad_centralization: Optional. Must be Boolean. If "grad_centralization" is in the keys, the set value will be used. If not, the `grad_centralization` is False by default. This configuration only works on the convolution layer. - order_params: Optional. When parameters is grouped, this usually is used to maintain the order of parameters that appeared in the network to improve performance. The value should be parameters whose order will be followed in optimizer. If `order_params` in the keys, other keys will be ignored and the element of 'order_params' must be in one group of `params`. weight_decay (Union[float, int]): An int or a floating point value for the weight decay. It must be equal to or greater than 0. If the type of `weight_decay` input is int, it will be converted to float. Default: ``0.0`` . loss_scale (float): A floating point value for the loss scale. It must be greater than 0. If the type of `loss_scale` input is int, it will be converted to float. In general, use the default value. Only when `FixedLossScaleManager` is used for training and the `drop_overflow_update` in `FixedLossScaleManager` is set to ``False`` , this value needs to be the same as the `loss_scale` in `FixedLossScaleManager`. Refer to class :class:`mindspore.amp.FixedLossScaleManager` for more details. Default: ``1.0`` . Raises: TypeError: If `learning_rate` is not one of int, float, Tensor, Iterable, LearningRateSchedule. TypeError: If element of `parameters` is neither Parameter nor dict. TypeError: If `loss_scale` is not a float. TypeError: If `weight_decay` is neither float nor int. ValueError: If `loss_scale` is less than or equal to 0. ValueError: If `weight_decay` is less than 0. ValueError: If `learning_rate` is a Tensor, but the dimension of tensor is greater than 1. Supported Platforms: ``Ascend`` ``GPU`` ``CPU`` Examples: >>> import mindspore as ms >>> from mindspore import nn >>> import numpy as np >>> import mindspore >>> from mindspore import nn, ops, Tensor >>> >>> class MyMomentum(nn.Optimizer): ... def __init__(self, params, learning_rate, momentum=0.9): ... super(MyMomentum, self).__init__(learning_rate, params) ... self.moments = self.parameters.clone(prefix="moments", init="zeros") ... self.momentum = momentum ... self.opt = ops.ApplyMomentum() ... ... def construct(self, gradients): ... params = self.parameters ... lr = self.get_lr() ... gradients = self.flatten_gradients(gradients) ... gradients = self.decay_weight(gradients) ... gradients = self.gradients_centralization(gradients) ... gradients = self.scale_grad(gradients) ... ... success = None ... for param, mom, grad in zip(params, self.moments, gradients): ... success = self.opt(param, mom, lr, grad, self.momentum) ... return success >>> >>> net = nn.Dense(2, 3) >>> loss_fn = nn.MAELoss() >>> opt = MyMomentum(net.trainable_params(), 0.01) >>> >>> device_target = opt.target >>> opt_unique = opt.unique >>> weight_decay_value = opt.get_weight_decay() >>> >>> def forward_fn(data, label): ... logits = net(data) ... loss = loss_fn(logits, label) ... return loss, logits >>> >>> grad_fn = mindspore.value_and_grad(forward_fn, None, opt.parameters, has_aux=True) >>> >>> def train_step(data, label): ... (loss, _), grads = grad_fn(data, label) ... opt(grads) ... return loss >>> >>> data = Tensor(np.random.rand(4, 10, 2), mindspore.dtype.float32) >>> label = Tensor(np.random.rand(4, 10, 3), mindspore.dtype.float32) >>> train_step(data, label) """ _support_parallel_optimizer = False def __init__(self, learning_rate, parameters, weight_decay=0.0, loss_scale=1.0): super(Optimizer, self).__init__(auto_prefix=False) parameters = self._parameters_base_check(parameters, "parameters") self.param_rank = None self.optim_filter = None if not isinstance(parameters, list): raise TypeError(f"For 'Optimizer' argument 'parameters' must be 'list', but got {type(parameters)}.") if not all(isinstance(x, Parameter) for x in parameters) and not all(isinstance(x, dict) for x in parameters): raise TypeError("For 'Optimizer', all elements of the argument 'parameters' must be 'Parameter' or 'dict'," " please check the 'parameters'.") if isinstance(loss_scale, int): loss_scale = float(loss_scale) validator.check_value_type("loss_scale", loss_scale, [float], self.cls_name) validator.check_positive_float(loss_scale, "loss_scale", self.cls_name) self.loss_scale = loss_scale self.dynamic_weight_decay = False self.grad_centralization = False self._unique = True self._target = context.get_context("device_target") self._use_flattened_params = False self._grad_fusion_size = 0 self.dynamic_lr = False self.assignadd = P.AssignAdd() self.global_step = Parameter(initializer(0, [1], mindspore.int32), name='global_step') self.is_group = False self.is_group_lr = False self.is_group_params_ordered = False self.use_parallel = False learning_rate = self._preprocess_single_lr(learning_rate) if isinstance(parameters[0], dict): self.is_group = True self.group_params = [] self.group_lr = [] self.group_weight_decay = [] self.group_grad_centralization = [] self._init_group_params(parameters, learning_rate, weight_decay, self.grad_centralization) self._init_opt_attrs(learning_rate, parameters, weight_decay) self.add_flags(skip_auto_parallel_compile=True) def _init_opt_attrs(self, learning_rate, parameters, weight_decay): """initialize optimizer attributions""" weight_decay = self._preprocess_weight_decay(weight_decay) if self.is_group_lr: if self.dynamic_lr: self.learning_rate = CellList(self.group_lr, auto_prefix=False) else: self.learning_rate = ParameterTuple(self.group_lr) else: self.learning_rate = self._build_single_lr(learning_rate, 'learning_rate') if self.is_group: self.parameters = ParameterTuple(self.group_params) self._parameters = self.parameters decay_filter = lambda x: isinstance(x, Cell) or x > 0 dynamic_decay_filter = lambda x: isinstance(x, Cell) self.decay_flags = tuple(decay_filter(x) for x in self.group_weight_decay) self.dynamic_decay_flags = tuple(dynamic_decay_filter(x) for x in self.group_weight_decay) self.weight_decay = tuple(x if flag else Tensor(x, mstype.float32) for x, flag in zip(self.group_weight_decay, self.dynamic_decay_flags)) self.exec_weight_decay = any(self.decay_flags) self.grad_centralization_flags = tuple(self.group_grad_centralization) else: self.parameters = ParameterTuple(parameters) flat_params = self._get_flattened_params(parameters) if self._use_flattened_params: self._parameters = ParameterTuple(flat_params) else: self._parameters = self.parameters decay_filter = lambda x: 'beta' not in x.name and 'gamma' not in x.name self.decay_flags = tuple(decay_filter(x) for x in self._parameters) self.dynamic_decay_flags = isinstance(weight_decay, Cell) self.exec_weight_decay = isinstance(weight_decay, Cell) or weight_decay > 0 self.weight_decay = Tensor(weight_decay, mstype.float32) if not self.dynamic_decay_flags else weight_decay # when a parameter has been unique, there is no need do another unique in optimizer. for param in self._parameters: if param.unique: self._unique = False break # set user's parameters as local parameters for param in self._parameters: self._user_parameters.append(param.name) ps_filter = lambda x: x.is_param_ps self.ps_parameters = tuple(ps_filter(x) for x in self._parameters) cache_filter = lambda x: x.cache_enable self.cache_enable = tuple(cache_filter(x) for x in self._parameters) self.reciprocal_scale = Tensor(1.0 / self.loss_scale, mstype.float32) self.need_scale = self.loss_scale != 1.0 self.global_step_increase_tensor = Tensor([1], mstype.int32) self.param_length = len(self._parameters) self.map_ = C.Map() self.map_reverse = C.Map(None, True) self.hyper_map = C.HyperMap() self.hyper_map_reverse = C.HyperMap(None, True) self._use_parallel_optimizer() self.enable_tuple_broaden = True def _get_flattened_params(self, parameters): """Get parameters for each contiguous memory chunks used by input parameters if they are flattened.""" if self.is_group: # We don't use flattened parameters when parameters are grouped. return parameters # Check whether parameters are flattened. flattened = Tensor._is_flattened(parameters) # pylint: disable=W0212 if not flattened: # Parameters are not flattened. return parameters # Try to get chunk tensors from flattened parameters. chunk_tensors = Tensor._get_flattened_tensors(parameters) # pylint: disable=W0212 if not chunk_tensors: # Failed to get chunk tensors. logger.warning("Parameters are not properly flattened, fallback to not flattened parameters.") return parameters # Convert chunk tensors to parameters. self._use_flattened_params = True self._grad_fusion_size = Tensor._get_fusion_size(chunk_tensors) # pylint: disable=W0212 return [Parameter._from_tensor(t, name='_chunk_param' + str(i) + '_' + str(t.dtype)) # pylint: disable=W0212 for i, t in enumerate(chunk_tensors)] def _use_parallel_optimizer(self): """Indicates whether to use automatic parallelism.""" if context.get_auto_parallel_context("enable_parallel_optimizer"): if _get_parallel_mode() == ParallelMode.DATA_PARALLEL and context.get_context("device_target") == "Ascend": self.use_parallel = True elif _get_parallel_mode() == ParallelMode.DATA_PARALLEL \ and context.get_context("device_target") != "Ascend": raise RuntimeError(f'For "Optimizer", parallel optimizer only supports "Ascend" in data parallel mode, ' f'but got {context.get_context("device_target")}.') elif _get_parallel_mode() in (ParallelMode.STAND_ALONE, ParallelMode.HYBRID_PARALLEL): raise RuntimeError("For 'Optimizer', parallel optimizer is not supported in {}, you should set " "parallel mode to 'data_parallel', 'semi_auto_parallel' or 'auto_parallel'." .format(_get_parallel_mode())) if self.use_parallel: if not self._support_parallel_optimizer: raise RuntimeError("For 'Optimizer', parallel optimizer only support optimizer 'Lamb' and " "'AdamWeightDecay' and 'AdaFactor', but got {}.".format(self.cls_name)) self.dev_num = _get_device_num() if self.dev_num > self.param_length: raise RuntimeError("Parallel optimizer can not be applied when the number of parameters {} is" " less than the number of devices {}".format(self.param_length, self.dev_num)) self.param_rank = self._get_parameter_group_id() self.optim_filter = tuple(map(lambda x: x == _get_global_rank(), self.param_rank)) self.param_names = [] for param in self._parameters: self.param_names.append(param.name) else: self.optim_filter = (True,) * self.param_length @property def unique(self): """ Whether to make the gradients unique in optimizer. Generally, it is used in sparse networks. Set to True if the gradients of the optimizer are sparse, while set to False if the forward network has made the parameters unique, that is, the gradients of the optimizer is no longer sparse. The default value is True when it is not set. """ return self._unique @unique.setter def unique(self, value): """Set the `unique` attribute.""" if not isinstance(value, bool): raise TypeError("For 'Optimizer', the property 'unique' must be bool, " "but got {}".format(type(value))) self._unique = value @property def target(self): """ The property is used to determine whether the parameter is updated on host or device. The input type is str and can only be 'CPU', 'Ascend' or 'GPU'. """ return self._target @target.setter def target(self, value): """ If the input value is set to "CPU", the parameters will be updated on the host using the Fused optimizer operation. """ raise NotImplementedError @staticmethod def _preprocess_grad_centralization(grad_centralization): if not isinstance(grad_centralization, bool): raise TypeError("For 'Optimizer', the 'gradients_centralization' must be bool type, " "but got {}.".format(type(grad_centralization))) return grad_centralization @staticmethod def _parameters_base_check(parameters, param_info): """Parameters base check.""" if parameters is None: raise ValueError(f"For 'Optimizer', the argument {param_info} can not be None.") if not isinstance(parameters, Iterable): raise TypeError(f"For 'Optimizer', the argument {param_info} must be Iterable type, " f"but got {type(parameters)}.") parameters = list(parameters) if not parameters: raise ValueError(f"For 'Optimizer', the argument {param_info} must not be empty.") return parameters @staticmethod def _use_distibuted_optimizer(): """ Whether use distributed optimizers. """ return _is_ps_mode()
[docs] def flatten_gradients(self, gradients): """ Flatten gradients into several chunk tensors grouped by data type if network parameters are flattened. A method to enable performance improvement by using contiguous memory for parameters and gradients. User-defined optimizers based on :class:`mindspore.nn.Optimizer` should call this interface to support contiguous memory for network parameters. Args: gradients (tuple[Tensor]): The gradients of network parameters. Returns: tuple[Tensor], The gradients after flattened, or the original gradients if parameters are not flattened. """ if self._use_flattened_params: flatten_concat = inner.FlattenConcat(fusion_size=self._grad_fusion_size) return flatten_concat(gradients) return gradients
[docs] def decay_weight(self, gradients): """ Weight decay. An approach to reduce the overfitting of a deep learning neural network model. User-defined optimizers based on :class:`mindspore.nn.Optimizer` can also call this interface to apply weight decay. Args: gradients (tuple[Tensor]):The gradients of network parameters, and have the same shape as the parameters. Returns: tuple[Tensor], The gradients after weight decay. """ if self.exec_weight_decay: params = self._parameters weight_decay = self.get_weight_decay() if self.is_group: gradients = self.map_(F.partial(_apply_decay), weight_decay, self.decay_flags, params, gradients) else: gradients = self.map_(F.partial(_apply_decay, weight_decay), self.decay_flags, params, gradients) return gradients
[docs] def gradients_centralization(self, gradients): """ Gradients centralization. A method for optimizing convolutional layer parameters to improve the training speed of a deep learning neural network model. User-defined optimizers based on :class:`mindspore.nn.Optimizer` can also call this interface to centralize gradients. Args: gradients (tuple[Tensor]): The gradients of network parameters, and have the same shape as the parameters. Returns: tuple[Tensor], The gradients after gradients centralization. """ if self.is_group: gradients = self.map_(F.partial(_apply_grad_centralization), self.grad_centralization_flags, gradients) return gradients
[docs] def scale_grad(self, gradients): """ Restore gradients for mixed precision. User-defined optimizers based on :class:`mindspore.nn.Optimizer` can also call this interface to restore gradients. Args: gradients (tuple[Tensor]): The gradients of network parameters, and have the same shape as the parameters. Returns: tuple[Tensor], The gradients after loss scale. """ if self.need_scale: gradients = self.map_(F.partial(_grad_scale, self.reciprocal_scale), gradients) return gradients
def _set_base_target(self, value): """ If the input value is set to "CPU", the parameters will be updated on the host using the Fused optimizer operation. """ if not isinstance(value, str): raise TypeError("For 'Optimizer', the property 'target' must be string, but got {}".format(type(value))) if value not in ('CPU', 'Ascend', 'GPU'): raise ValueError("For 'Optimizer', the property 'target' must be one of ['CPU', 'Ascend' ,'GPU'], " "but got {}".format(value)) if self._target == "CPU" and value in ('Ascend', 'GPU'): raise ValueError("For 'Optimizer', the property 'target' cannot be set to 'GPU' or 'Ascend' " "in the 'CPU' environment.") if self._target == "Ascend" and value == 'GPU': raise ValueError("For 'Optimizer', the property 'target' cannot be set to 'GPU' " "in the 'Ascend' environment.") if self._target == "GPU" and value == 'Ascend': raise ValueError("For 'Optimizer', the property 'target' cannot be set to 'Ascend' " "in the 'GPU' environment.") self._is_device = (value != 'CPU') self._target = value def _grad_sparse_indices_deduplicate(self, gradients): """ In the case of using big operators, deduplicate the 'indexes' in gradients.""" if self._target != 'CPU' and self._unique: gradients = self.map_(F.partial(_indices_deduplicate), gradients) return gradients def _preprocess_weight_decay(self, weight_decay): """preprocess weight decay""" if isinstance(weight_decay, (float, int)): weight_decay = float(weight_decay) validator.check_non_negative_float(weight_decay, "weight_decay", self.cls_name) weight_decay = weight_decay * self.loss_scale elif isinstance(weight_decay, Cell): self.dynamic_weight_decay = True weight_decay = _WrappedWeightDecay(weight_decay, self.loss_scale) else: raise TypeError("For 'Optimizer', the argument 'Weight_decay' must be int, " "float or Cell.but got {}".format(type(weight_decay))) return weight_decay def _preprocess_single_lr(self, learning_rate): """Check lr value, and convert lr to a float, a Tensor or a LearningRateSchedule.""" if isinstance(learning_rate, (float, int)): learning_rate = float(learning_rate) validator.check_non_negative_float(learning_rate, "learning rate", self.cls_name) return learning_rate if isinstance(learning_rate, Tensor) and learning_rate.ndim == 0: learning_rate = Tensor(learning_rate.asnumpy(), dtype=mstype.float32) return learning_rate self.dynamic_lr = True if isinstance(learning_rate, Iterable): return Tensor(np.array(list(learning_rate)).astype(np.float32)) if isinstance(learning_rate, Tensor): if learning_rate.ndim > 1: raise ValueError(f"For 'Optimizer', if 'learning_rate' is Tensor type, then the dimension of it should " f"be 0 or 1, but got {learning_rate.ndim}.") if learning_rate.ndim == 1 and learning_rate.size < 2: logger.warning("For 'Optimizer', if use 'Tensor' type dynamic learning rate, " "please make sure that the number " "of elements in the tensor is greater than 1, " "but got {}.".format(learning_rate.size)) learning_rate = Tensor(learning_rate.asnumpy(), dtype=mstype.float32) return learning_rate if isinstance(learning_rate, LearningRateSchedule): return learning_rate raise TypeError("For 'Optimizer', the argument 'learning_rate' must be int, float, Tensor, Iterable or " "LearningRateSchedule, but got {}.".format(type(learning_rate))) def _build_single_lr(self, learning_rate, name): """Build learning rate value, convert learning rate to a Parameter or a LearningRateSchedule.""" if isinstance(learning_rate, float): learning_rate = Parameter(Tensor(learning_rate, mstype.float32), name) if self.is_group_lr and self.dynamic_lr: learning_rate = _ConvertToCell(learning_rate) return learning_rate if isinstance(learning_rate, Tensor) and learning_rate.ndim == 0: learning_rate = Parameter(learning_rate, name) if self.is_group_lr and self.dynamic_lr: learning_rate = _ConvertToCell(learning_rate) return learning_rate if isinstance(learning_rate, Tensor) and learning_rate.ndim == 1: return _IteratorLearningRate(learning_rate, name) return learning_rate def _check_group_params(self, parameters): """Check group params.""" parse_keys = ['params', 'lr', 'weight_decay', 'order_params', 'grad_centralization'] for group_param in parameters: invalid_key = list(filter(lambda x: x not in parse_keys, group_param.keys())) if invalid_key: raise KeyError(f"For 'Optimizer', the key in group params must be one of in {parse_keys}, " f"but got {invalid_key}.") if 'order_params' in group_param.keys(): if len(group_param.keys()) > 1: raise ValueError(f"For 'Optimizer', the order params dict in group parameters should only " f"include the 'order_params' key, but got {group_param.keys()}.") if not isinstance(group_param['order_params'], Iterable): raise TypeError("For 'Optimizer', the value of 'order_params' in group parameters should " "be Iterable type, but got {}.".format(type(group_param['order_params']))) continue parameters = self._parameters_base_check(group_param['params'], "group `params`") for index, param in enumerate(parameters): if not isinstance(param, Parameter): raise TypeError(f"For 'Optimizer', the element in group parameters must be Parameter type, " f"but got {type(param)} at index {index}.") def _parse_group_params(self, parameters, learning_rate): """Parse group params.""" self._check_group_params(parameters) if isinstance(learning_rate, Tensor) and learning_rate.ndim == 1: tensor_lr_length = learning_rate.size else: tensor_lr_length = 0 for group_param in parameters: if 'order_params' in group_param.keys(): if len(group_param.keys()) > 1: raise ValueError(f"For 'Optimizer', the order params dict in group parameters should only include " f"the 'order_params' key, but got {group_param.keys()}.") if not isinstance(group_param['order_params'], Iterable): raise TypeError("For 'Optimizer', the value of 'order_params' in group parameters must be " "Iterable type, but got {}.".format(type(group_param['order_params']))) self.is_group_params_ordered = True continue if 'lr' in group_param.keys(): self.is_group_lr = True group_lr = self._preprocess_single_lr(group_param['lr']) if isinstance(group_lr, Tensor) and group_lr.ndim == 1: group_lr_length = group_lr.size if tensor_lr_length == 0: tensor_lr_length = group_lr_length elif group_lr_length != tensor_lr_length: raise ValueError("For 'Optimizer', the Tensor type dynamic learning rate in group must be " "the same size as the argument 'learning_rate'.") def _init_group_params(self, parameters, learning_rate, weight_decay, grad_centralization): """Initialize learning rate, weight decay or grad centralization in group params.""" self._parse_group_params(parameters, learning_rate) default_lr = self._build_single_lr(learning_rate, 'learning_rate') params_store = [] for group_num, group_param in enumerate(parameters): if 'order_params' in group_param.keys(): ordered_parameters = group_param['order_params'] continue self.group_params += group_param['params'] if 'lr' in group_param.keys(): lr_param_name = 'learning_rate_group_' + str(group_num) lr = self._preprocess_single_lr(group_param['lr']) lr = self._build_single_lr(lr, lr_param_name) else: lr = default_lr if 'weight_decay' in group_param.keys(): weight_decay_ = self._preprocess_weight_decay(group_param['weight_decay']) else: weight_decay_ = self._preprocess_weight_decay(weight_decay) if 'grad_centralization' in group_param.keys(): self.grad_centralization = self._preprocess_grad_centralization(group_param['grad_centralization']) for param in group_param['params']: validator.check_value_type("parameter", param, [Parameter], self.cls_name) grad_centralization_ = self.grad_centralization else: grad_centralization_ = grad_centralization for key in group_param.keys(): if key not in ('params', 'lr', 'weight_decay', 'grad_centralization'): logger.warning(f"The optimizer cannot parse '{key}' when setting parameter groups, " f"the key should in ['params', 'lr', 'weight_decay', 'grad_centralization']") for param in group_param['params']: validator.check_value_type("parameter", param, [Parameter], self.cls_name) if param.name in params_store: raise RuntimeError(f"For 'Optimizer', the {param.name} parameter already exists, it does not " f"support repeated setting. Please check whether the optimizer parameter " f"has been set multiple times.") params_store.append(param.name) self.group_lr.append(lr) self.group_weight_decay.append(weight_decay_) self.group_grad_centralization.append(grad_centralization_) if self.is_group_params_ordered: self._order_and_adjust_group_params(ordered_parameters) def _order_and_adjust_group_params(self, ordered_parameters): """ Order group parameter, learning rate, weight decay and grad centralization in group params. """ params_length = len(self.group_params) if len(ordered_parameters) != len(self.group_params): raise ValueError(f"For 'Optimizer'," f"the length of order parameters must be the same as the length of group parameters, " f"but got order parameters' length {len(ordered_parameters)}, " f"group parameters' length {len(self.group_params)}.") ordered_params = [None] * params_length ordered_learning_rate = [None] * params_length ordered_weight_decay = [None] * params_length ordered_grad_centralization = [None] * params_length params_name = [param.name for param in ordered_parameters] for param, lr, wd, gc in zip(self.group_params, self.group_lr, self.group_weight_decay, self.group_grad_centralization): index = params_name.index(param.name) ordered_params[index] = param ordered_learning_rate[index] = lr ordered_weight_decay[index] = wd ordered_grad_centralization[index] = gc self.group_params = ordered_params self.group_lr = ordered_learning_rate self.group_weight_decay = ordered_weight_decay self.group_grad_centralization = ordered_grad_centralization
[docs] def get_weight_decay(self): """ The optimizer calls this interface to get the weight decay value for the current step. User-defined optimizers based on :class:`mindspore.nn.Optimizer` can also call this interface before updating the parameters. Returns: float, the weight decay value of current step. """ if self.dynamic_weight_decay: if self.is_group: weight_decay = () for weight_decay_, flag_ in zip(self.weight_decay, self.dynamic_decay_flags): current_weight_decay = weight_decay_(self.global_step) if flag_ else weight_decay_ weight_decay += (current_weight_decay,) return weight_decay return self.weight_decay(self.global_step) return self.weight_decay
[docs] def get_lr(self): """ The optimizer calls this interface to get the learning rate for the current step. User-defined optimizers based on :class:`mindspore.nn.Optimizer` can also call this interface before updating the parameters. Returns: float, the learning rate of current step. """ lr = self.learning_rate if self.dynamic_lr: if self.is_group_lr: lr = () for learning_rate in self.learning_rate: current_dynamic_lr = learning_rate(self.global_step).reshape(()) lr += (current_dynamic_lr,) else: lr = self.learning_rate(self.global_step).reshape(()) return lr
[docs] def get_lr_parameter(self, param): """ When parameters is grouped and learning rate is different for each group, get the learning rate of the specified `param`. Args: param (Union[Parameter, list[Parameter]]): The `Parameter` or list of `Parameter`. Returns: A single `Parameter` or `list[Parameter]` according to the input type. If learning rate is dynamic, `LearningRateSchedule` or `list[LearningRateSchedule]` that used to calculate the learning rate will be returned. Examples: >>> from mindspore import nn >>> # Define the network structure of LeNet5. Refer to >>> # https://gitee.com/mindspore/docs/blob/master/docs/mindspore/code/lenet.py >>> net = LeNet5() >>> conv_params = list(filter(lambda x: 'conv' in x.name, net.trainable_params())) >>> no_conv_params = list(filter(lambda x: 'conv' not in x.name, net.trainable_params())) >>> group_params = [{'params': conv_params, 'lr': 0.05}, ... {'params': no_conv_params, 'lr': 0.01}] >>> optim = nn.Momentum(group_params, learning_rate=0.1, momentum=0.9, weight_decay=0.0) >>> conv_lr = optim.get_lr_parameter(conv_params) >>> print(conv_lr[0].asnumpy()) 0.05 """ def get_lr_value(learning_rate): if isinstance(learning_rate, (_ConvertToCell, _IteratorLearningRate)): return learning_rate.learning_rate return learning_rate if isinstance(param, Parameter): param_list = [param] elif isinstance(param, list): param_list = param else: raise TypeError(f"For 'get_lr_parameter', the 'param' must be 'Parameter' or 'list' type, " f"but got {type(param)}.") lr = [] ids = [id(p) for p in self._parameters] for p in param_list: validator.check_value_type("parameter", p, [Parameter], self.cls_name) if id(p) not in ids: raise ValueError(f"For 'get_lr_parameter', the parameter {p.name} is not in optimizer, please check " f"whether the argument 'param' is correct.") if self.is_group_lr: index = ids.index(id(p)) lr.append(get_lr_value(self.learning_rate[index])) else: lr.append(get_lr_value(self.learning_rate)) return lr if isinstance(param, list) else lr[0]
def _is_dynamic_lr_or_weight_decay(self): """ Determine whether the learning rate or weight decay is dynamic. Returns: bool, represents the learning rate or weight decay is dynamic or not. """ return self.dynamic_lr or self.dynamic_weight_decay def _get_parameter_group_id(self): """ Get the parameter partition group id, which is less than the number of devices. Returns: tuple, the group id tuple of parameters. """ rank_list = () count = 0 for _ in range(self.param_length): rank_list = rank_list + (count,) count = count + 1 if count == self.dev_num: count = 0 return rank_list
[docs] def broadcast_params(self, optim_result): """ Apply Broadcast operations in the sequential order of parameter groups. Args: optim_result(bool): The results of updating parameters. This input is used to ensure that the parameters are updated before they are broadcast. Returns: The broadcast parameters. """ # If rank_id is 0, 1, 2, 3, there are param0 ~ param7, # then the value is[(param0, param4), (param1, param5), (param2, param6), (param3, param7)] param_group = [] for _ in range(self.dev_num): param_group.append(F.make_tuple()) for i in range(self.param_length): param_group[self.param_rank[i]] = param_group[self.param_rank[i]] + (self._parameters[i],) new_param_group = [] for root in range(self.dev_num): if root > 0: depend = F.depend(param_group[root], new_param_group[root - 1]) else: depend = F.depend(param_group[root], optim_result) next_params = P.Broadcast(root)(depend) new_param_group.append(next_params) for i in range(F.tuple_len(next_params)): F.assign(param_group[root][i], next_params[i]) return new_param_group
def _get_distributed_optimizer_list(self, optimizer_type, *args, **kwargs): """ Get the distributed optimizers list in distributed training mode. """ return generate_dist_optimizer_list(optimizer_type, self._parameters, *args, **kwargs) def construct(self, *hyper_params): raise NotImplementedError
op_add = P.AddN() op_gather = P.Gather() op_mul = P.Mul() op_gc = inner.Centralization() _apply_decay = C.MultitypeFuncGraph("apply_decay") _apply_grad_centralization = C.MultitypeFuncGraph("apply_grad_centralization") @_apply_decay.register("Tensor", "Bool", "Tensor", "RowTensor") def _tensor_apply_decay_with_sparse(weight_decay, if_apply, weight, gradient): """Get grad with weight_decay.""" if if_apply: indices = gradient.indices values = op_add((op_gather(weight, indices, 0) * F.cast(weight_decay, F.dtype(weight)), gradient.values)) shape = gradient.dense_shape return RowTensorInner(indices, values, shape) return gradient @_apply_decay.register("Tensor", "Bool", "Tensor", "Tensor") def _tensor_apply_decay(weight_decay, if_apply, weight, gradient): """Get grad with weight_decay.""" if if_apply: return op_add((op_mul(weight, F.cast(weight_decay, F.dtype(weight))), gradient)) return gradient @_apply_grad_centralization.register("Bool", "RowTensor") def _tensor_apply_grad_centralization_with_sparse(if_apply, gradient): """Get grad with grad_centralization.""" if if_apply: indices = gradient.indices shape = gradient.dense_shape grad_shape = F.shape(gradient) axis = [] for i in range(1, len(grad_shape)): axis.append(i) if len(axis) >= 1: if grad_shape[1] % 16 != 0: return gradient values = op_gc(gradient.values, axis) return RowTensorInner(indices, values, shape) return gradient @_apply_grad_centralization.register("Bool", "Tensor") def _tensor_apply_grad_centralization(if_apply, gradient): """Get grad with grad_centralization.""" if if_apply: axis = [] grad_shape = F.shape(gradient) for i in range(1, len(grad_shape)): axis.append(i) if len(axis) >= 1: if grad_shape[1] % 16 != 0: return gradient return op_gc(gradient, axis) return gradient _grad_scale = C.MultitypeFuncGraph("grad_scale") _indices_deduplicate = C.MultitypeFuncGraph("indices_deduplicate") @_grad_scale.register("Number", "Tensor") def tensor_grad_scale(scale, grad): """Get grad with scale.""" if scale == 1.0: return grad return op_mul(grad, F.cast(scale, F.dtype(grad))) @_grad_scale.register("Tensor", "Tensor") def tensor_grad_scale_with_tensor(scale, grad): """Get grad with scale.""" return op_mul(grad, F.cast(scale, F.dtype(grad))) @_grad_scale.register("Tensor", "RowTensor") def tensor_grad_scale_with_sparse(scale, grad): """Get grad with scale.""" return RowTensorInner(grad.indices, grad.values * F.cast(scale, F.dtype(grad.values)), grad.dense_shape) @_grad_scale.register("Tensor", "MapTensor") def tensor_grad_scale_with_map_tensor(scale, grad): """Get grad with scale.""" return grad @_indices_deduplicate.register("RowTensor") def rowtensor_deduplicate_indices_slices(grad): """Unique the indices and sums the 'values' corresponding to the duplicate indices.""" indices = grad.indices values = grad.values unique_indices, index_position = P.Unique()(indices) summed_values = P.UnsortedSegmentSum()(values, index_position, P.Shape()(unique_indices)[0]) return RowTensorInner(unique_indices, summed_values, grad.dense_shape) @_indices_deduplicate.register("Tensor") def tensor_deduplicate_indice_slices(grad): """Return the input gradient directly in the dense sences.""" return grad class _ConvertToCell(LearningRateSchedule): """Inner api, convert learning rate of scalar to LearningRateSchedule.""" def __init__(self, learning_rate): super(_ConvertToCell, self).__init__() if not isinstance(learning_rate, Parameter): raise TypeError("For 'Optimizer', the argument 'learning_rate' must be Parameter, " "but got {}.".format(type(learning_rate))) self.learning_rate = learning_rate def construct(self, global_step): return self.learning_rate + 1.0 - 1.0 class _IteratorLearningRate(LearningRateSchedule): """Inner api, convert learning rate of Tensor(list) to LearningRateSchedule.""" def __init__(self, learning_rate, name): super(_IteratorLearningRate, self).__init__() if isinstance(learning_rate, Tensor): if learning_rate.ndim != 1: raise ValueError(f"For 'Optimizer', the dimension of the argument 'learning_rate' should " f"be 1, but got {learning_rate.ndim}.") else: raise TypeError("For 'Optimizer', the argument 'learning_rate' must be Tensor, " "but got {}.".format(type(learning_rate))) self.learning_rate = Parameter(learning_rate, name) self.gather = P.Gather() def construct(self, global_step): return self.gather(self.learning_rate, global_step, 0) class _WrappedWeightDecay(Cell): """Inner api, a combination of dynamic or non-dynamic weight decay""" def __init__(self, weight_decay, loss_scale=1.0): super(_WrappedWeightDecay, self).__init__() self.weight_decay = weight_decay self.loss_scale = Tensor(loss_scale, mstype.float32) def construct(self, global_step): return self.weight_decay(global_step) * self.loss_scale