[软件工程应用与实践]lingvo学习笔记

本文介绍了深度学习中的梯度裁剪技术,用于解决梯度爆炸问题,通过设定梯度阈值并缩放超出范围的梯度。同时,讨论了学习率调度的重要性,包括线性缩放、warmup阶段和衰减策略。学习率调度如LinearScale、Warmup和Decay结合使用,可以更有效地优化模型训练。此外,还概述了Learner类在lingvo.core.learner.py中的实现,包括损失计算、优化器和学习率调度器的使用。
摘要由CSDN通过智能技术生成

2021SC@SDUSC

lingvo.core.learner.py

learner根据损失来优化变量的子集。
它包括一个learning rate schedule,一个优化器,和gradient clipping机制。一个BaseTask可以有多个learner,每个learner优化 变量的一个子集(通常是不连接的)。

关于 gradient clipping
1.梯度爆炸的影响
在一个只有一个隐藏节点的网络中,损失函数和权值w偏置b构成error surface,其中有一堵墙,如下所示在这里插入图片描述
2.解决梯度爆炸问题的方法
通常会使用一种叫”clip gradients “的方法. 它能有效地权重控制在一定范围之内.
算法步骤如下。

  • 首先设置一个梯度阈值:clip_gradient
  • 在后向传播中求出各参数的梯度,这里我们不直接使用梯度进去参数更新,我们求这些梯度的l2范数
  • 然后比较梯度的l2范数||g||与clip_gradient的大小
  • 如果前者大,求缩放因子clip_gradient/||g||, 由缩放因子可以看出梯度越大,则缩放因子越小,这样便很好地控制了梯度的范围
  • 最后将梯度乘上缩放因子便得到最后所需的梯度

在这里插入图片描述

参考文章

关于 Learning Rate Scheduler 自适应学习率

学习率(Learning Rate,LR)是深度学习训练中非常重要的超参数。同样的模型和数据下,不同的LR将直接影响模型何时能够收敛到预期的准确率。

随机梯度下降SGD算法中,每次从训练数据中随机选择一批样本,样本数为Batch Size。很多实验都证明了,在LR不变的情况下,Batch Size越大,模型收敛效果越差。
AlexNet ImageNet在不同Batch Size下的TOP1准确率
Linear Scale
随着Batch Size增大,一个Batch Size内样本的方差变小;也就是说越大的Batch Size,意味着这批样本的随机噪声越小。那么我们可以更加相信这批样本所产生的梯度,可以增大LR,在负梯度方向更快下降。有人提出了根据Batch Size的大小,线性地调整LR,在ResNet50上实验有效果。例如,ResNet50论文中使用的Batch Size为256,LR为0.1,那么对于更大的Batch Size bs
在这里插入图片描述
Warmup
在训练最开始,模型中绝大多数参数都是随机初始化的,与最终模型很远。一开始就使用一个很大的LR,会增加不确定性。所以在训练最开始,先使用一个较小的LR,训练几轮Epoch后,再使用较大的LR

Decay
一直使用较大的LR也有问题,在训练中后期,过大的LR可能导致模型在最优解附近震荡,无法快速收敛。所以,在中后期,需要将LR进行一些衰减(Decay)。ResNet论文中最初使用的Step Decay:每训练30个Epoch,LR衰减为刚才的0.1倍。还有Cosine衰减[^3]:最初的LR为 [公式] ,整个训练的Step数目为[公式](steps_per_epoch * total_epochs),当前Step为[公式],当前的[公式]由下面公式计算得到。
在这里插入图片描述
Step Decay和Cosine Decay的LR随着训练Epoch变化如下
Step Decay与Cosine Decay,初始LR为0.1
Linear + Warmup + Decay
将以上三种LR策略组合起来,可以形成一个完整的LR策略:根据Batch Size大小,线性地缩放LR基准值,前几个Epoch使用较小的LR先进行Warmup,之后从LR基准值开始,逐渐对LR进行衰减。比如,ResNet50 ImageNet下Batch Size为1024,LR基准值为0.4 = 0.1 * (1024 / 256),整个训练过程中的LR策略如下图所示:
Batch Size为1024,LR基准值为0.4,Warmup + Decay
参考文章——Learning Rate Schedule:CNN学习率调整策略

读代码

class Learner(base_layer.BaseLayer)

training program层。该层以损失张量作为输入,并输出一个trainer op。

类初始化

def __init__(self, params):
    super().__init__(params)
    p = self.params

    self._var_grads = None
    self._eval_metrics = {}

不能在推理中创建多余的变量。

    is_training = not (self.do_eval or p.is_inference)
    if p.grad_norm_tracker and is_training:
      self.CreateChild('grad_norm_tracker', p.grad_norm_tracker)

不能在推理中创建优化器和lr_schedule。

    self.CreateChild('optimizer', p.optimizer)
    self.CreateChild('lr_schedule', p.lr_schedule)
    if isinstance(p.loss_name, (list, tuple)):
      assert p.gradient_combiner
      self.CreateChild('gradient_combiner', p.gradient_combiner)
    else:
      assert p.gradient_combiner is None

方法def _CreateChildrenVariables(self)

在传统模式下,multi learner由于ValueError不能使用。
向后兼容:在tf.variable_scope(p.name)之外手动调用child.InstantiateVariables()。

def _CreateChildrenVariables(self):
    p = self.params
    if not p.learner_use_variable_scope:
      if 'grad_norm_tracker' in self.children:
        self.grad_norm_tracker.InstantiateVariables()
      self.lr_schedule.InstantiateVariables()
      self.optimizer.InstantiateVariables()
    super()._CreateChildrenVariables()

方法def Apply(self, metrics, vmap, gradient_mask=None, gradient_adjuster=None)

方法作用:计算“vmap”上的更新以优化损失

参数

  • metrics: 字典[str, (value, weight)],可以根据p.loss_name计算损失。
  • vmap: .NestedMap 对象,包含要优化的变量。
  • gradient_mask: 如果not None,则字典映射变量名到0/1标量。
  • gradient_adjuster: 如果not None,则为一个函数,该函数会改变给定的var_gradient_梯度。

返回值

(losses, op, eval_metrics),其中

  • losses:标量张量的列表;
  • op:tf.Operation要更新变量;
  • eval_metrics:是一个Dict[str, (value, weight)],其中每个value/weight是一个标量张量。

源码:

在name_scope之外应用梯度以保持self.optimizer.Apply()创建的变量的向后兼容性。

def Apply(self, metrics, vmap, gradient_mask=None, gradient_adjuster=None):

    losses, var_grads, eval_metrics = self._ComputeLossesAndGradients(
        metrics, vmap)
    if 'tpu_embedding_var_grads' in var_grads:
      tpu_embedding_var_grads = var_grads.tpu_embedding_var_grads
      del var_grads.tpu_embedding_var_grads

      tpu_embedding_collection = py_utils.GetTpuEmbeddingGraphCollection()[0]
      assert tpu_embedding_collection
      tpu_emb_update_op, stats = tpu_embedding_collection.ApplyGradients(
          py_utils.GetTaskCallScope(),
          tpu_embedding_var_grads.Transform(lambda var_grad: var_grad.grad))
      eval_metrics.update(stats)
    else:
      tpu_emb_update_op = tf.no_op()

    assert py_utils.GetGlobalStep() is not None
    lr = self.LearningRate()

    var_grads, stats = self.AdjustGradients(
        var_grads,
        gradient_mask=gradient_mask,
        gradient_adjuster=gradient_adjuster)
    eval_metrics.update(stats)
    self._var_grads = var_grads

    eval_metrics['learning_rate'] = (tf.convert_to_tensor(lr),
                                     tf.convert_to_tensor(1.))

    var_update_op = tf.group(
        [tpu_emb_update_op,
         self.optimizer.Apply(lr, var_grads)])
    return losses, var_update_op, eval_metrics

计算有效梯度方法ComputeActivationGradients

源码

def ComputeActivationGradients(self, activations, activations_grad, vmap):
    p = self.params
    vmap = self.GetTrainableVariables(vmap)

    for v in vmap.Flatten():
      tf.logging.info('%s: bprop variable: %s', p.name, v.name)
    return self.optimizer.ComputeGradients(
        activations,
        vmap,
        p.grad_aggregation_method,
        p.colocate_gradients_with_ops,
        p.gate_gradients,
        compute_gradients_fn=self._CustomComputeGradientsFn(),
        skip_zero_gradients=p.skip_zero_gradients,
        skip_none_gradients=False,
        activations_grad=activations_grad,
        is_activations=True)

计算损失方法ComputeLosses

源码

def ComputeLosses(self, metrics):
    p = self.params

    def _Loss(metric_name):
      metric = metrics.get(metric_name, None)
      if metric is None:
        raise ValueError('Loss %s not found in metrics %s' %
                         (metric_name, list(metrics.keys())))
      return metric

    loss_name = p.loss_name or p.name
    losses = []
    if isinstance(loss_name, (list, tuple)):
      for metric_name in loss_name:
        loss_metric = _Loss(metric_name)
        losses.append(loss_metric[0])
    else:
      loss_metric = _Loss(loss_name)
      losses.append(loss_metric[0])

    return losses
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值