优化器optimizer及实例化代码

什么是优化器?

在深度学习中,优化器(Optimizer)是一个核心概念,它负责调整神经网络的权重和偏置,以便最小化损失函数,从而提高模型的准确性和性能。

常见的优化器,包括梯度下降系列(批量梯度下降BGD、随机梯度下降SGD、小批量梯度下降MBGD)、动量法、NAG、Adagrad、RMSprop以及Adam等,它们的核心目标是通过调整学习率、利用梯度信息等手段,高效地最小化损失函数,从而优化和提升神经网络模型的性能。

为什么需要优化器?

由于目标函数拥有众多参数且结构复杂,直接寻找最优参数变得十分困难。因此,我们需要借助优化器,它能够逐步调整参数,确保每次优化都朝着最快降低损失的方向前进。

优化器调参

即根据模型实际情况,调整学习率、动量因子、权重衰减等超参数,以优化训练效果和性能。需通过经验和实验找最佳组合,实现快速收敛、减少摆动、防止过拟合。

  • 学习率:过大的学习率可能导致模型无法收敛,而过小的学习率则会使训练过程变得缓慢。因此,需要根据实际情况选择合适的学习率。
  • 动量因子:对于使用动量的优化器,动量因子的选择也很重要。动量因子决定了过去梯度对当前梯度的影响程度。合适的动量因子可以加速收敛,减少摆动。
  • 权重衰减:权重衰减是一种正则化方法,用于防止模型过拟合。在优化器中,可以通过添加权重衰减项来减少模型的复杂度。

优化器使用

每个优化器都是一个类,一定要进行实例化才能使用,比如以下这段代码描述了随机梯度下降(SGD)优化器(带有动量)的一个 Python 类。

class SGD(optimizer_v2.OptimizerV2):

  _HAS_AGGREGATE_GRAD = True

  def __init__(self,
               learning_rate=0.01,
               momentum=0.0,
               nesterov=False,
               name="SGD",
               **kwargs):
    super(SGD, self).__init__(name, **kwargs)
    self._set_hyper("learning_rate", kwargs.get("lr", learning_rate))
    self._set_hyper("decay", self._initial_decay)

    self._momentum = False
    if isinstance(momentum, ops.Tensor) or callable(momentum) or momentum > 0:
      self._momentum = True
    if isinstance(momentum, (int, float)) and (momentum < 0 or momentum > 1):
      raise ValueError("`momentum` must be between [0, 1].")
    self._set_hyper("momentum", momentum)

    self.nesterov = nesterov

  def _create_slots(self, var_list):
    if self._momentum:
      for var in var_list:
        self.add_slot(var, "momentum")

  def _prepare_local(self, var_device, var_dtype, apply_state):
    super(SGD, self)._prepare_local(var_device, var_dtype, apply_state)
    apply_state[(var_device, var_dtype)]["momentum"] = array_ops.identity(
        self._get_hyper("momentum", var_dtype))

  def _resource_apply_dense(self, grad, var, apply_state=None):
    var_device, var_dtype = var.device, var.dtype.base_dtype
    coefficients = ((apply_state or {}).get((var_device, var_dtype))
                    or self._fallback_apply_state(var_device, var_dtype))

    if self._momentum:
      momentum_var = self.get_slot(var, "momentum")
      return gen_training_ops.ResourceApplyKerasMomentum(
          var=var.handle,
          accum=momentum_var.handle,
          lr=coefficients["lr_t"],
          grad=grad,
          momentum=coefficients["momentum"],
          use_locking=self._use_locking,
          use_nesterov=self.nesterov)
    else:
      return gen_training_ops.ResourceApplyGradientDescent(
          var=var.handle,
          alpha=coefficients["lr_t"],
          delta=grad,
          use_locking=self._use_locking)

  def _resource_apply_sparse_duplicate_indices(self, grad, var, indices,
                                               **kwargs):
    if self._momentum:
      return super(SGD, self)._resource_apply_sparse_duplicate_indices(
          grad, var, indices, **kwargs)
    else:
      var_device, var_dtype = var.device, var.dtype.base_dtype
      coefficients = (kwargs.get("apply_state", {}).get((var_device, var_dtype))
                      or self._fallback_apply_state(var_device, var_dtype))

      return gen_resource_variable_ops.ResourceScatterAdd(
          resource=var.handle,
          indices=indices,
          updates=-grad * coefficients["lr_t"])

  def _resource_apply_sparse(self, grad, var, indices, apply_state=None):
    # This method is only needed for momentum optimization.
    var_device, var_dtype = var.device, var.dtype.base_dtype
    coefficients = ((apply_state or {}).get((var_device, var_dtype))
                    or self._fallback_apply_state(var_device, var_dtype))

    momentum_var = self.get_slot(var, "momentum")
    return gen_training_ops.ResourceSparseApplyKerasMomentum(
        var=var.handle,
        accum=momentum_var.handle,
        lr=coefficients["lr_t"],
        grad=grad,
        indices=indices,
        momentum=coefficients["momentum"],
        use_locking=self._use_locking,
        use_nesterov=self.nesterov)

  def get_config(self):
    config = super(SGD, self).get_config()
    config.update({
        "learning_rate": self._serialize_hyperparameter("learning_rate"),
        "decay": self._serialize_hyperparameter("decay"),
        "momentum": self._serialize_hyperparameter("momentum"),
        "nesterov": self.nesterov,
    })
    return config

当动量为0时,参数w的更新规则(梯度为g):

w = w - 学习率 * g

当动量大于0时的更新规则:

  速度 = 动量 * 速度 - 学习率 * g
  w = w + 速度

当`nesterov=True`时(即采用Nesterov动量),此规则变为:

  速度 = 动量 * 速度 - 学习率 * g
  w = w + 动量 * 速度 - 学习率 * g

参数:

  • 学习率
  • 动量:浮点超参数 >=0,加速梯度下降在相关方向并抑制振荡。
  • nesterov:布尔值,是否应用Nesterov动量
  • name:可选名称前缀,用于应用梯度时创建的操作。默认为`"SGD"。
  • kwargs:关键字参数。允许是 `"clipnorm"或`"clipvalue"之一。 `"clipnorm"(浮点)按范数裁剪梯度;`"clipvalue"(浮点)按值裁剪梯度。

用法:

这段代码展示了如何使用 TensorFlow 中的 SGD(随机梯度下降)优化器进行基本的优化步骤。这里通过一个简单的二次函数 loss = (var ** 2) / 2.0 来演示优化过程,其中 var 是一个 TensorFlow 变量,初始值为 1.0。梯度下降的目标是通过调整 var 的值来最小化损失函数

  >>> opt = tf.keras.optimizers.SGD(learning_rate=0.1) # 创建一个 SGD 优化器实例,学习率设置为 0.1
  >>> var = tf.Variable(1.0) # 定义一个 TensorFlow 变量 var,初始值为 1.0
  >>> loss = lambda: (var ** 2)/2.0  # 损失函数的梯度(导数)为 d(loss)/d(var) = var
  >>> step_count = opt.minimize(loss, [var]).numpy()   # 步长是 :- 学习率 * 梯度
# 使用 SGD 优化器的 minimize 方法来执行一次优化步骤。
# 这一步计算损失函数关于 var 的梯度,并根据梯度和学习率更新 var 的值。  
  >>> var.numpy()
  0.9

打印更新后的 var 值,期望看到 var 值减少,因为损失函数关于 var 是正相关的。 

带动量的SGD:

>>> opt = tf.keras.optimizers.SGD(learning_rate=0.1, momentum=0.9) #带动量的 SGD 优化器实例
  >>> var = tf.Variable(1.0)
  >>> val0 = var.value()
  >>> loss = lambda: (var ** 2)/2.0         # d(loss)/d(var1) = var1
  >>> # First step is `- learning_rate * grad`
  >>> step_count = opt.minimize(loss, [var]).numpy() #执行优化步骤
  >>> val1 = var.value()
  >>> (val0 - val1).numpy()
  0.1

更新公式为: new_var=var−learning_rate×var,所以 new_var=1.0−0.1×1.0=0.9。比较带动量优化前后 var 的值,由于动量的影响,期望看到更新步长增加。1.0−0.9=0.1

 后续步骤中步长的增加:

  >>> # On later steps, step-size increases because of momentum
  >>> step_count = opt.minimize(loss, [var]).numpy()
  >>> val2 = var.value()
  >>> (val1 - val2).numpy()
  0.18

再次执行优化步骤,并比较连续两次更新的步长,由于动量的累积效应,步长应该进一步增加。

什么是Nesterov动量?

Nesterov动量由Yurii Nesterov在1983年提出,它是一种动量优化方法,通过结合前几步的梯度信息来调整当前的更新。Nesterov动量的主要思想是,利用历史梯度信息来预测当前梯度的方向,从而更有效地进行参数更新。

Nesterov动量的更新规则

在带动量的梯度下降中,如果momentum(动量)大于0,更新规则如下:

  • 计算动量更新: velocity=momentum×velocity−learning_rate×g 其中,velocity是动量,momentum是动量系数,learning_rate是学习率,g是当前梯度。

  • 更新参数: w=w+velocity 其中,w是模型参数。

nesterov=True时,更新规则变为:

  • 计算动量更新: velocity=momentum×velocity−learning_rate×g

  • 更新参数: w=w+momentum×velocity−learning_rate×g 这种更新方式考虑了当前梯度和前一步动量,使得更新更加平滑,有助于避免陷入局部最小值。

优点

  • 加速收敛:Nesterov动量可以帮助模型更快地收敛到最优解。

  • 减少震荡:通过平滑更新,Nesterov动量可以减少优化过程中的震荡,提高优化的稳定性。

reference:

https://blog.csdn.net/2401_85390073/article/details/143932362

For `nesterov=True`, See [Sutskever et al., 2013](http://jmlr.org/proceedings/papers/v28/sutskever13.pdf).

评论
成就一亿技术人!
拼手气红包6.0元
还能输入1000个字符
 
红包 添加红包
表情包 插入表情
 条评论被折叠 查看
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值