精华内容
下载资源
问答
  • 学习率下降策略
    千次阅读
    2018-07-26 22:32:41

    tensorflow 里面提供了几种学习率下降的策略,有指数下降法、分段固定值下降法、多项式下降法、自然指数下降法、随时间/步数下降法、余弦下降法、余弦重启式下降法、线性余弦下降法、带噪声的线性余弦下降法,下面各个介绍:

    1.指数下降法自然指数下降法:

    它们的定义的参数形式都是:

    def exponential_decay(learning_rate,
                          global_step,
                          decay_steps, #decay_steps: How often to apply decay.,比如多少步下降一次
                          decay_rate,
                          staircase=False, # 是否应用于离散情况。
                          name=None):

    不同的地方在于衰减后的学习率decayed_rate 的计算方法不一样。

    指数下降法的计算方式是:

    decayed_learning_rate = learning_rate *
                              decay_rate ^ (global_step / decay_steps)

    自然下降法的计算方式是:

    decayed_learning_rate = learning_rate * exp(-decay_rate * global_step)

    2.分段固定值下降法

    这种方法类似于分段函数式下降,比如在前5000步数内学习率为0.1,5000到10000步内是0.05,之后任意多步都是0.01,参数定义如下:

    def piecewise_constant(x, boundaries, values, name=None):
      """Piecewise constant from boundaries and interval values.
      Example: use a learning rate that's 1.0 for the first 100001 steps, 0.5
        for the next 10000 steps, and 0.1 for any additional steps.
      ```python
      global_step = tf.Variable(0, trainable=False)
      boundaries = [100000, 110000]
      values = [1.0, 0.5, 0.1]
      learning_rate = tf.train.piecewise_constant(global_step, boundaries, values)
      # Later, whenever we perform an optimization step, we increment global_step.
      ```

    3.多项式下降法:

    def polynomial_decay(learning_rate,
                         global_step,
                         decay_steps,
                         end_learning_rate=0.0001,
                         power=1.0,
                         cycle=False,
                         name=None):
    
      ```python
      global_step = min(global_step, decay_steps)
      decayed_learning_rate = (learning_rate - end_learning_rate) *
                              (1 - global_step / decay_steps) ^ (power) +
                              end_learning_rate
      ```

    4.随时间步下降法:

    def inverse_time_decay(learning_rate,
                           global_step,
                           decay_steps,
                           decay_rate,
                           staircase=False,
                           name=None):
    
      ```python
      decayed_learning_rate = learning_rate / (1 + decay_rate * global_step /
      decay_step)
      ```

    5.余弦下降:

    def cosine_decay(learning_rate, global_step, decay_steps, alpha=0.0, name=None):
    ```python
      global_step = min(global_step, decay_steps)
      cosine_decay = 0.5 * (1 + cos(pi * global_step / decay_steps))
      decayed = (1 - alpha) * cosine_decay + alpha
      decayed_learning_rate = learning_rate * decayed
      ```
    '''
    alpha: A scalar `float32` or `float64` Tensor or a Python number.
          Minimum learning rate value as a fraction of learning_rate.
    '''

     

    更多相关内容
  • pytorch学习率下降策略

    千次阅读 2021-01-23 00:25:30
    阶段离散下降调整策略 连续下降调整策略 周期性调整策略 自适应调整策略 自定义调整策略

    阶段离散下降调整策略:

    首先“阶段离散”下降调整这个词不是个专有名词,它只是一个形容。
    符合这种调整策略的方法,一般是step,step学习率下降策略是最为常用的一种,表现为,在初始学习率的基础上,每到一个阶段学习率将以gamma的指数倍下降,通常情况下gamma为0.1。显然随着训练迭代学习率会变的越来越小,但是不管怎么变,这个数都在趋近于0,永远不会到0.
    效果类似于:

    # lr = 0.05     if epoch < 30        
    # lr = 0.005    if 30 <= epoch < 60        
    # lr = 0.0005   if 60 <= epoch < 90
    

    pytorch中定义了两种方法做这件事,分别是等间隔调整学习率(Step),按需调整学习率(MultiStep),实际上它们的效果是一致的

    等间隔下降调整策略

    等间隔的调整是在定义间隔是什么,即step_size,当训练的epoch满足step_size时,学习率就调整一次,last_epoch对step_size取整。

    class StepLR(_LRScheduler):
        def __init__(self, optimizer, step_size, gamma=0.1, last_epoch=-1, verbose=False):
            self.step_size = step_size
            self.gamma = gamma
            super(StepLR, self).__init__(optimizer, last_epoch, verbose)
    
        def get_lr(self):
            if not self._get_lr_called_within_step:
                warnings.warn("To get the last learning rate computed by the scheduler, "
                              "please use `get_last_lr()`.", UserWarning)
    
            if (self.last_epoch == 0) or (self.last_epoch % self.step_size != 0):
                return [group['lr'] for group in self.optimizer.param_groups]
            return [group['lr'] * self.gamma
                    for group in self.optimizer.param_groups]
    
        def _get_closed_form_lr(self):
            return [base_lr * self.gamma ** (self.last_epoch // self.step_size)
                    for base_lr in self.base_lrs]
    

    按需下降调整策略

    按需调整学习率是在直接定义目标是什么,训练中,当前的epoch达到目标的时候,学习率调整,milestones就是定义的一系列目标,当last_epoch不在milestones中时,学习率保持不变,相反的,则gamma的指数倍调整,当然gamma是个小数,所以学习率越来越小。

    class MultiStepLR(_LRScheduler):
        def __init__(self, optimizer, milestones, gamma=0.1, last_epoch=-1, verbose=False):
            self.milestones = Counter(milestones)
            self.gamma = gamma
            super(MultiStepLR, self).__init__(optimizer, last_epoch, verbose)
    
        def get_lr(self):
            if not self._get_lr_called_within_step:
                warnings.warn("To get the last learning rate computed by the scheduler, "
                              "please use `get_last_lr()`.", UserWarning)
    
            if self.last_epoch not in self.milestones:
                return [group['lr'] for group in self.optimizer.param_groups]
            return [group['lr'] * self.gamma ** self.milestones[self.last_epoch]
                    for group in self.optimizer.param_groups]
    
        def _get_closed_form_lr(self):
            milestones = list(sorted(self.milestones.elements()))
            return [base_lr * self.gamma ** bisect_right(milestones, self.last_epoch)
                    for base_lr in self.base_lrs]
    

    连续下降调整策略:

    “连续”下降这个词不是个专有名词,它只是一个形容。
    符合这种下降策略的方法有,线性下降策略,cos下降策略,指数衰减下降策略,前两种在Pytorch没有实现。它们的表现是,随着训练迭代,学习率的变化时逐步减少的,不会出现step方式的阶段下降,并且到迭代结束时,学习率为0,而不是一个非常小的值。

    线性下降调整策略

    线性下降策略非常好理解,就是学习率与迭代周期是线性关系,初始学习率和周期确定了,下降的斜率也就确定了。

     lr = args.lr * (1 - (current_iter - warmup_iter) / (max_iter - warmup_iter))
    

    cos下降调整策略

    cos下降策略到最后一步迭代的最后时,系数刚好为cos(pi/2),即为0,开始迭代时系数为为cos(0),即为1,中间遵循余弦曲线的方式下降。

    lr = args.lr * (1 + cos(pi * (current_iter - warmup_iter) / (max_iter - warmup_iter))) / 2
    

    指数衰减调整策略

    指数衰减调整策略的计算方式和step是一样的,都是当前的epoch作为指数, gamma作为底数,即gamma**epoch。不同之处在于,指数衰减下降策略是每个epoch都会做的,可以看做在epoch间连续,其次,更为重要的是,选择指数衰减下降策略时gamma不能选择为0.1,否则几个epoch过去,学习率就非常趋近于0了,所以一般是0.9。

    class ExponentialLR(_LRScheduler):
        def __init__(self, optimizer, gamma, last_epoch=-1, verbose=False):
            self.gamma = gamma
            super(ExponentialLR, self).__init__(optimizer, last_epoch, verbose)
    
        def get_lr(self):
            if not self._get_lr_called_within_step:
                warnings.warn("To get the last learning rate computed by the scheduler, "
                              "please use `get_last_lr()`.", UserWarning)
    
            if self.last_epoch == 0:
                return self.base_lrs
            return [group['lr'] * self.gamma
                    for group in self.optimizer.param_groups]
    
        def _get_closed_form_lr(self):
            return [base_lr * self.gamma ** self.last_epoch
                    for base_lr in self.base_lrs]
    
    

    周期性调整策略:

    周期性调整的特点是不再从始至终单调的下降,而是会出现上升的情况。

    余弦退火调整策略

    以余弦函数为周期,并在每个周期最大值时重新设置学习率。以初始学习率为最大学习率,以 2∗Tmax 为周期,在一个周期内先下降,后上升。

    class CosineAnnealingLR(_LRScheduler):
    	def __init__(self, optimizer, T_max, eta_min=0, last_epoch=-1, verbose=False):
            self.T_max = T_max
            self.eta_min = eta_min
            super(CosineAnnealingLR, self).__init__(optimizer, last_epoch, verbose)
    
        def get_lr(self):
            if not self._get_lr_called_within_step:
                warnings.warn("To get the last learning rate computed by the scheduler, "
                              "please use `get_last_lr()`.", UserWarning)
    
            if self.last_epoch == 0:
                return self.base_lrs
            elif (self.last_epoch - 1 - self.T_max) % (2 * self.T_max) == 0:
                return [group['lr'] + (base_lr - self.eta_min) *
                        (1 - math.cos(math.pi / self.T_max)) / 2
                        for base_lr, group in
                        zip(self.base_lrs, self.optimizer.param_groups)]
            return [(1 + math.cos(math.pi * self.last_epoch / self.T_max)) /
                    (1 + math.cos(math.pi * (self.last_epoch - 1) / self.T_max)) *
                    (group['lr'] - self.eta_min) + self.eta_min
                    for group in self.optimizer.param_groups]
    
        def _get_closed_form_lr(self):
            return [self.eta_min + (base_lr - self.eta_min) *
                    (1 + math.cos(math.pi * self.last_epoch / self.T_max)) / 2
                    for base_lr in self.base_lrs]
    

    循环调整策略

    在这里插入图片描述
    循环调整顾名思义,就是以一个周期和一个上下界反复调整学习率,这个方法出自《Cyclical Learning Rates for Training Neural Networks》,这么做的理由是要避免模型进入局部最优的状态,也就是鞍点(saddle points)。而循环学习率方法使得一个范围(base_lr ~ max_lr)里的学习率在训练中都能得到运用,也就是说,在下边界和上边界中,那个最佳的学习率将会在训练中有机会运用到训练中。

    torch.optim.lr_scheduler.CyclicLR(optimizer, base_lr, max_lr, 
    							step_size_up=2000,
    							step_size_down=None, mode='triangular', gamma=1.0, scale_fn=None, 
    							scale_mode='cycle', cycle_momentum=True, base_momentum=0.8,
    							max_momentum=0.9, last_epoch=-1)
    
    

    自适应调整策略:

    顾名思义,之前的调整策略都是在出初始学习率和训练周期固定好之后就确定下来的规则,不会根据训练状态的变化而变化,自适应的调整则不同,依训练状况伺机调整,该法通过监测某一指标的变化情况,当该指标不再怎么变化的时候,就是调整学习率的时机。

    ReduceLROnPlateau

    ReduceLROnPlateau的名字很直观,就是在持续平稳的状态时下降学习率,当某指标不再变化(下降或升高),则调整学习率,这是非常实用的学习率调整策略。例如,当验证集的 loss 不再下降时,进行学习率调整;或者监测验证集的 accuracy,当accuracy 不再上升时,则调整学习率。

    class ReduceLROnPlateau(object):
        def __init__(self, optimizer, mode='min', factor=0.1, patience=10,
                     threshold=1e-4, threshold_mode='rel', cooldown=0,
                     min_lr=0, eps=1e-8, verbose=False):
    
            if factor >= 1.0:
                raise ValueError('Factor should be < 1.0.')
            self.factor = factor
    
            # Attach optimizer
            if not isinstance(optimizer, Optimizer):
                raise TypeError('{} is not an Optimizer'.format(
                    type(optimizer).__name__))
            self.optimizer = optimizer
    
            if isinstance(min_lr, list) or isinstance(min_lr, tuple):
                if len(min_lr) != len(optimizer.param_groups):
                    raise ValueError("expected {} min_lrs, got {}".format(
                        len(optimizer.param_groups), len(min_lr)))
                self.min_lrs = list(min_lr)
            else:
                self.min_lrs = [min_lr] * len(optimizer.param_groups)
    
            self.patience = patience
            self.verbose = verbose
            self.cooldown = cooldown
            self.cooldown_counter = 0
            self.mode = mode
            self.threshold = threshold
            self.threshold_mode = threshold_mode
            self.best = None
            self.num_bad_epochs = None
            self.mode_worse = None  # the worse value for the chosen mode
            self.eps = eps
            self.last_epoch = 0
            self._init_is_better(mode=mode, threshold=threshold,
                                 threshold_mode=threshold_mode)
            self._reset()
    
        def _reset(self):
            """Resets num_bad_epochs counter and cooldown counter."""
            self.best = self.mode_worse
            self.cooldown_counter = 0
            self.num_bad_epochs = 0
    
        def step(self, metrics, epoch=None):
            # convert `metrics` to float, in case it's a zero-dim Tensor
            current = float(metrics)
            if epoch is None:
                epoch = self.last_epoch + 1
            else:
                warnings.warn(EPOCH_DEPRECATION_WARNING, UserWarning)
            self.last_epoch = epoch
    
            if self.is_better(current, self.best):
                self.best = current
                self.num_bad_epochs = 0
            else:
                self.num_bad_epochs += 1
    
            if self.in_cooldown:
                self.cooldown_counter -= 1
                self.num_bad_epochs = 0  # ignore any bad epochs in cooldown
    
            if self.num_bad_epochs > self.patience:
                self._reduce_lr(epoch)
                self.cooldown_counter = self.cooldown
                self.num_bad_epochs = 0
    
            self._last_lr = [group['lr'] for group in self.optimizer.param_groups]
    
        def _reduce_lr(self, epoch):
            for i, param_group in enumerate(self.optimizer.param_groups):
                old_lr = float(param_group['lr'])
                new_lr = max(old_lr * self.factor, self.min_lrs[i])
                if old_lr - new_lr > self.eps:
                    param_group['lr'] = new_lr
                    if self.verbose:
                        print('Epoch {:5d}: reducing learning rate'
                              ' of group {} to {:.4e}.'.format(epoch, i, new_lr))
    
        @property
        def in_cooldown(self):
            return self.cooldown_counter > 0
    
        def is_better(self, a, best):
            if self.mode == 'min' and self.threshold_mode == 'rel':
                rel_epsilon = 1. - self.threshold
                return a < best * rel_epsilon
    
            elif self.mode == 'min' and self.threshold_mode == 'abs':
                return a < best - self.threshold
    
            elif self.mode == 'max' and self.threshold_mode == 'rel':
                rel_epsilon = self.threshold + 1.
                return a > best * rel_epsilon
    
            else:  # mode == 'max' and epsilon_mode == 'abs':
                return a > best + self.threshold
    
        def _init_is_better(self, mode, threshold, threshold_mode):
            if mode not in {'min', 'max'}:
                raise ValueError('mode ' + mode + ' is unknown!')
            if threshold_mode not in {'rel', 'abs'}:
                raise ValueError('threshold mode ' + threshold_mode + ' is unknown!')
    
            if mode == 'min':
                self.mode_worse = inf
            else:  # mode == 'max':
                self.mode_worse = -inf
    
            self.mode = mode
            self.threshold = threshold
            self.threshold_mode = threshold_mode
    
        def state_dict(self):
            return {key: value for key, value in self.__dict__.items() if key != 'optimizer'}
    
        def load_state_dict(self, state_dict):
            self.__dict__.update(state_dict)
            self._init_is_better(mode=self.mode, threshold=self.threshold, threshold_mode=self.threshold_mode)
    

    自定义调整策略:

    LambdaLR

    LambdaLR可以为不同参数组设定不同学习率调整策略。

    torch.optim.lr_scheduler.LambdaLR(optimizer, lr_lambda, last_epoch=-1)
    

    与其他调整规则的区别在于,optimizer和lr_lambda可以是list,对应之后,相应的参数就会根据对应规则调整

    展开全文
  • 学习率】梯度下降学习率的设定策略

    千次阅读 多人点赞 2020-07-01 16:46:15
    转载 卢明冬 ...深度学习论文 - Cyclical Learning Rates for Training Neural Networks (pytorch-CLR实现)https://blog.csdn.net/weixin_41993767/article/details/87934941 (NN训练 trick -..

    转载 卢明冬

    参考 

    学习率和batchsize如何影响模型的性能? - yumoye - 博客园

    学会使用顶级算法的秘诀是什么?如何找到合适的学习率?-电子发烧友网

    深度学习论文 - Cyclical Learning Rates for Training Neural Networks

    (pytorch-CLR实现)

    各种方法的实现https://github.com/anandsaha/pytorch.cyclic.learning.rate

    (实现了各种方法-只不过调用还需要自己额外运行)https://github.com/thomasjpfan/pytorch/blob/401ec389db2c9d2978917a6e4d1101b20340d7e7/torch/optim/lr_scheduler.py
    

    可以参考 (Keras实现)神经网络学习速率设置指南(CLR Callback,LRFinder,SGDR等最新的学习率设置方案)附完整代码解析_初识-CV的博客-CSDN博客_神经网络学习速率

    以下是其他参考资料

    CLR实现DeepLearning论文阅读笔记(一):Cyclical Learning Rates for Training Neural Networks(CLR)_MLlotus的博客-CSDN博客

    (NN训练 trick - lr 设置)百度安全验证

    手把手教你估算深度神经网络的最优学习率(附代码 & 教程)手把手教你估算深度神经网络的最优学习率(附代码&教程)

    fast.ai 库实现手把手教你估算深度神经网络的最优学习率(附代码&教程)_52AI人工智能的博客-CSDN博客

    bckenstler/CLR keras 实现) https://github.com/bckenstler/CLR

    Keras实现探索学习率设置技巧以提高Keras中模型性能 | 炼丹技巧 - 简书

    目录

    1.学习率的重要性

    2.学习率的设定类型

    1)固定学习率

    2)不同的参数使用不同的学习率

    3)动态调整学习率

    4)自适应学习率

    3.学习率的设定策略

    3.1.固定学习率(Fixed Learning Rate)

    3.2.学习率衰减(Learning Rate Decay)

    3.3.找到合适的学习率

    3.4.基于Armijo准则的线性回溯搜索算法

    1)二分线性搜索(Bisection Line Search)

    2)回溯线性搜索 (Backing Line Search)

    3)二次插值法

    示例代码

    3.5.循环学习率(Cyclical Learning Rate)

    3.6.余弦退火(Cosine annealing)

    3.7.热重启随机梯度下降(SGDR)

    3.8.不同网络层使用不同学习率(Differential Learning Rates)

    3.9.快照集成和随机加权平均(Snapshot Ensembling And Stochastic Weight Averaging)

    从传统的集成学习一路走来

    权重空间内的解

    窄极值和宽极值

    快照集成(Snapshot Ensembling)

    快速几何集成(Fast Geometric Ensembling,FGE)

    随机加权平均(Stochastic Weight Averaging,SWA)

    方法实现

    4.小结

    5.参考资料


    1.学习率的重要性

    如果把梯度下降算法比作机器学习中的一把 “神兵利器”,那么学习率就是梯度下降算法这把武器对应的 “内功心法”,只有调好学习率这个超参数,才能让梯度下降算法更好地运作,让模型产生更好的效果。

    在《梯度下降算法总结》一文中,我们已经谈到过在实际应用中梯度下降学习算法可能会遇到局部极小值和鞍点两大挑战。那么,什么样的梯度下降才算是 “合格” 的,简单总结一下其实就两个字,“快” 和 “准”。“快”,即收敛速度要尽量快,“准”,即能够准确找到最优解。

    也就是说,好的梯度下降是尽量在快的时间找到最优的解。我们结合 《梯度下降算法总结》中的内容,看看可能的影响因素有哪些:

        1)学习率设置太小,需要花费过多的时间来收敛

        2)学习率设置较大,在最小值附近震荡却无法收敛到最小值

        3)进入局部极值点就收敛,没有真正找到的最优解

        4)停在鞍点处,不能够在另一维度继续下降

    那么除了一些客观的因素外,可能还会有一些主观因素,如业务需求对时间和准确率的侧重点不一样,或者有的场景可能为了减少过拟合,还会适当降低对训练数据准确性来提高模型的泛化能力,比如深度学习中早停止策略,通过合理地提前结束迭代避免过拟合。

    梯度下降算法有两个重要的控制因子:一个是步长,由学习率控制;一个是方向,由梯度指定。

    因此,要想对梯度下降的 “快” 和 “准” 实现调控,就可以通过调整它的两个控制因子来实现。因梯度方向已经被证明是变化最快的方向,很多时候都会使用梯度方向,而另外一个控制因子学习率则是解决上述影响的关键所在,换句话说,学习率是最影响优化性能的超参数之一。

    2.学习率的设定类型

    1)固定学习率

    介绍梯度下降时,我们讲到的学习率都是固定不变的,每次迭代每个参数都使用同样的学习率。找到一个比较好的固定学习率非常关键,否则会导致收敛太慢或者不收敛。

    2)不同的参数使用不同的学习率

    如果数据是稀疏的且特征分布不均,似乎我们更应该给予较少出现的特征一个大的更新。这时可能需要对不同特征对应的参数设定不同的学习率。深度学习的梯度下降算法中 Adagrad 和 Adam 方法都针对每个参数设置了相应的学习率,这部分内容详见《梯度下降算法总结》,本篇不作讨论。

    3)动态调整学习率

    动态调整就是我们根据应用场景,在不同的优化阶段能够动态改变学习率,以得到更好的结果。动态调整学习率是本篇的重点内容,为了解决梯度学习在一些复杂问题时出现的挑战,数据科学家们在动态调整学习率的策略上做了很多研究和尝试。

    4)自适应学习率

    自适应学习率从某种程度上讲也算是动态调整学习率的范畴,不过更偏向于通过某种算法来根据实时情况计算出最优学习率,而不是人为固定一个简单策略让梯度下降按部就班地实行。

    3.学习率的设定策略

    3.1.固定学习率(Fixed Learning Rate)

    固定学习率适用于那些目标函数是凸函数的模型,通常为了保证收敛会选一个稍微小的数值,如 0.01、0.001。固定学习率的选择对梯度下降影响非常大,下图展示了不同学习率对梯度下降的影响。

    3.2.学习率衰减(Learning Rate Decay)

    一般情况下,初始参数所得目标值与要求的最小值距离比较远,随着迭代次数增加,会越来越靠近最小值。学习率衰减的基本思想是学习率随着训练的进行逐渐衰减,即在开始的时候使用较大的学习率,加快靠近最小值的速度,在后来些时候用较小的学习率,提高稳定性,避免因学习率太大跳过最小值,保证能够收敛到最小值。

    衰减方式既可以是线性衰减也可以是指数衰减,可参考以下几种方式 [1]:

    逐步下降的方法适用于目标函数不太复杂的情况,相比固定学习率方法主要体现在速度提升上,但在神经网络和深度学习场景中,对于跳出众多鞍点和局部极小值的帮助并不大。

    3.3.找到合适的学习率

    Leslie N. Smith 在一篇 《Cyclical Learning Rates for Training Neural Networks》论文中提出一个非常简单的寻找最佳学习率的方法。这种方法可以用来确定最优的初始学习率,也可以界定适合的学习率的取值范围 [2]。

    在这种方法中,我们尝试使用较低学习率来训练神经网络,但是在每个批次中以指数形式增加(或线性增加)。

    目前,该方法在 Fast.ai 包中已经作为一个函数可直接进行使用。Fast.ai 包是由 Jeremy Howard 开发的一种高级 pytorch 包(就像 Keras 之于 Tensorflow)。

    相应代码如下:

    1

    2

    3

    4

    # run on learn object where learning rate is increased exponentially

    learn.lr_find()

    # plot graph of learning rate against iterations

    learn.sched.plot_lr()

    每次迭代后学习率以指数形式增长:

    同时,记录每个学习率对应的 Loss 值,然后画出学习率和 Loss 值的关系图:

    1

    2

    # plots the loss against the learning rate

    learn.sched.plot()

    通过找出学习率最高且 Loss 值仍在下降的值来确定最佳学习率。在上述情况中,该值将为 0.01。

    3.4.基于Armijo准则的线性回溯搜索算法

    基于 Armijo 准则的线性回溯搜索算法主要目的是通过计算的方式得到合适的学习率 [5]。

    学习率的计算标准

    1)二分线性搜索(Bisection Line Search)

    2)回溯线性搜索 (Backing Line Search)

    3)二次插值法

    二次插值法是回溯线性搜索的继续优化,利用了多项式插值 (Interpolation) 方法。多项式插值的思想是通过多项式插值法拟合简单函数,然后根据该简单函数估计原函数的极值点,这里我们使用二次函数来拟合。例如在上面的算法中,我们需要通过使得 h′(α)=0 来求极值,而使用二次插值法是找到同样过 α 点的二次函数求极值,结果近似 h(α) 求极值。

    先来看看怎样构造这个二次函数。

    如果知道 3 个点,那就可以确定一个二次曲线经过这三个已知点,换句话说为了确定一个二次曲线就需要 3 个类型的信息,因此我们就可以这样想:如果题目给定了在一个点 x1 处的函数值 y1=f(x1)、在该点处的切线值即 x1 处的导数值 f′(x1)、x2 点处的函数值 y2=f(x2),那么也是能唯一的确定一个二次函数,看下图:

    示例代码

    1

    2

    3

    4

    5

    6

    7

    8

    9

    10

    11

    12

    13

    14

    15

    16

    17

    18

    19

    20

    21

    22

    23

    24

    25

    26

    27

    28

    29

    30

    31

    32

    33

    34

    35

    36

    37

    38

    39

    40

    41

    42

    43

    44

    45

    46

    47

    48

    49

    50

    51

    52

    53

    54

    55

    56

    57

    58

    59

    60

    61

    62

    63

    64

    65

    66

    67

    68

    69

    70

    71

    72

    73

    74

    75

    76

    77

    78

    79

    80

    81

    82

    83

    84

    85

    86

    87

    88

    89

    90

    91

    92

    93

    94

    95

    96

    97

    98

    99

    100

    101

    102

    103

    104

    105

    106

    107

    108

    109

    110

    111

    112

    113

    114

    115

    116

    117

    118

    119

    120

    121

    122

    123

    124

    125

    126

    127

    128

    129

    130

    131

    132

    133

    134

    135

    136

    137

    138

    139

    140

    141

    142

    143

    144

    145

    146

    147

    148

    149

    150

    151

    152

    153

    154

    155

    156

    157

    158

    159

    160

    161

    162

    163

    164

    165

    166

    167

    168

    169

    170

    171

    172

    173

    174

    175

    176

    177

    178

    179

    180

    181

    182

    183

    184

    185

    186

    187

    188

    189

    190

    191

    192

    193

    194

    195

    196

    197

    198

    199

    200

    201

    202

    203

    204

    205

    206

    207

    208

    209

    210

    211

    212

    213

    214

    215

    216

    217

    218

    219

    220

    221

    222

    223

    224

    225

    226

    227

    228

    229

    230

    231

    232

    233

    #! /usr/bin/env python

    # -*- coding: UTF-8 -*-

    """

    @Author: LuMingdong.cn

    @Project: ML

    @File: learning_rate.py

    @Create Date: 2018/8/28 0028 9:49

    @Version: 1.0

    @Description: Now is better than never.  ——The Zen of Python

    """

    import numpy as np

    def Bisection(dfun, dir, x, alpha):

        """

        :param dfun: 梯度函数

        :param dir: 梯度方向,-1 为负方向, 1位正方向

        :param x: 当前点,向量

        :param alpha: 初始学习速率

        :return: 返回找到的学习速率

        """

        # d: 当前点x处的导数,因为要寻找的是当前点处的最佳学习速率alpha,当前点的梯度是固定的,是个值,向量

        d = dir * dfun(x)

        v_ha = np.dot(dfun(x + alpha * d), d)

        eps = 1e-6  # 设置返回阈值

        if abs(v_ha) < eps:

            # 对于部分函数,似乎不需要迭代很多次就得到很小的值了,所以加了判断,符合条件就不再更新alpha了

            # (不知道代码有没有错误,对于二分搜索的性能表现不太清楚,不清楚这种表现正不正常。)

            return alpha

        a1 = alpha

        a2 = alpha

        v_ha1 = v_ha

        v_ha2 = v_ha

        """找到另外一个学习率,确定一个区间"""

        if v_ha > 0:

            while v_ha1 > 0:

                a1 /= 10

                v_ha1 = np.dot(dfun(x + a1 * d), d)

        elif v_ha < 0:

            while v_ha2 < 0:

                a2 *= 10

                v_ha2 = np.dot(dfun(x + a2 * d), d)

        else:

            return alpha

        """二分线性搜索"""

        iter_num=1

        maxiter = 1000

        while iter_num < maxiter:

            mid = (a1 + a2) / 2

            v_mid = np.dot(dfun(x + mid * d), d)

            if abs(v_mid) < eps or abs(a2-a1)<eps:

                return mid

            elif v_mid < 0:

                a1 = mid

            else:

                a2 = mid

            iter_num += 1

        return mid

    def ArmijoBacktrack(fun, dfun, dir, x, alpha, c=0.3):

        """

        基于Armijo的回溯线性搜索

        :param fun: 目标函数,是个函数

        :param dfun: 梯度函数

        :param dir: 梯度方向,-1 为负方向,1位正方向

        :param x: 当前点,向量

        :param alpha: 初始学习速率

        :param c: 参数c, 一般小于0.5

        :return: 返回找到的学习速率

        """

        d = dir * dfun(x)

        now = fun(x)

        nextv = fun(x + alpha * d)

        count = 50

        while nextv < now and count > 0:

            """ 寻找最大的alpha """

            alpha = alpha * 2

            nextv = fun(x + alpha * d)

            count -= 1

        iterstep = 50

        slope = np.dot(dfun(x), d)

        while nextv > now + slope * c * alpha and iterstep > 0:

            """ 折半搜索 """

            alpha = alpha / 2

            nextv = fun(x + alpha * d)

            iterstep -= 1

        return alpha

    def ArmijoQuad(fun, dfun, dir, x, alpha, c=0.3):

        """

        基于Armijo的二次插值线性搜索

        :param fun: 目标函数,是个函数

        :param dfun: 梯度函数

        :param dir: 梯度方向,-1 为负方向,1位正方向

        :param x: 当前点,向量

        :param alpha: 初始学习速率

        :param c: 参数c, 一般小于0.5

        :return: 返回找到的学习速率

        """

        d = dir * dfun(x)

        now = fun(x)

        nextv = fun(x + alpha * d)

        count = 50

        while nextv < now and count > 0:

            """ 寻找最大的alpha """

            alpha = alpha * 2

            nextv = fun(x + alpha * d)

            count -= 1

        iterstep = 50

        slope = np.dot(dfun(x), d)

        while nextv > now + slope * c * alpha and iterstep > 0:

            """ 二次插值 """

            # h'(0) = slope

            # h(0) = now

            # h(alpha) = nextv

            a1 = (slope * alpha * alpha) / (2 * (slope * alpha + now - nextv))

            if a1 < 0:

                # 不满足a > 0,按原来的折半

                alpha = alpha / 2

            else:

                alpha = a1

            nextv = fun(x + alpha * d)

            iterstep -= 1

        return alpha

    def GradientDescent(k, fun, dfun, dir, x, alpha, itersteps):

        """

        梯度下降

        :param k: 搜索alpha的算法类型

        :param fun: 目标函数,是个函数

        :param dfun: 梯度函数

        :param dir: 梯度方向,-1 为负方向,1位正方向

        :param x: 当前点,向量

        :param alpha: 初始学习速率

        :param c: 参数c, 一般小于0.5

        :return: 返回学习率和函数值的过程数据

        """

        alpha_list = []

        x_list = []

        fx_list =[]

        for i in range(itersteps):

            if k == 0:

                # 固定学习率

                alpha = alpha

            elif k == 1:

                # 二分线性搜索

                alpha = Bisection(dfun, dir, x, alpha)

            elif k == 2:

                # 回溯搜索

                alpha = ArmijoBacktrack(fun, dfun, dir, x, alpha, c=0.3)

            elif k == 3:

                # 二次插值

                alpha = ArmijoQuad(fun, dfun, dir, x, alpha, c=0.3)

            else:

                raise Exception("k must be one of [0, 1, 2, 3]")

            d = dir * dfun(x)

            x = x + alpha * d

            # 保存过程数据

            alpha_list.append(alpha)

            x_list.append(x)

            fx_list.append(fun(x))

        return  alpha_list, x_list, fx_list

    def fun(args):

        """

        x^2+y^4+z^6

        :param args: 参数

        :return: 函数值

        """

        return args[0] ** 2 + args[1] ** 4 + args[2] ** 6

        # return args[0] ** 4

    def dfun(args):

        """

        x^2+y^4+z^6

        :param args: 参数

        :return: 各参数梯度,向量

        """

        return np.array([2 * args[0], 4 * args[1] ** 3, 6 * args[2] ** 5])

        # return 4*args[0]**3

    if __name__ == '__main__':

        # 基础参数

        args = np.array([4,3,2], dtype=float) # x

        k = 3 # 0:固定学习率  1:二分搜索  2:回溯搜索  3:二次查找值

        dir = -1

        alpha = 0.01

        itersteps = 100

        # 梯度下降

        a, theta, fx=GradientDescent(k, fun, dfun, dir, args, alpha, itersteps)

        # 画图

        import matplotlib.pyplot as plt

        x = range(itersteps)

        plt.plot(x, fx)

        # 设置坐标轴刻度

        # my_x_ticks = np.arange(0, itersteps, 5)

        # my_y_ticks = np.arange(0,  10, 0.05)

        # plt.xticks(my_x_ticks)

        # plt.yticks(my_y_ticks)

        plt.show()

    3.5.循环学习率(Cyclical Learning Rate)

    使用较快的学习率也有助于我们在训练中更早地跳过一些局部极小值。

    人们也把早停和学习率衰减结合起来,在迭代 10 次后损失函数没有改善的情况下学习率开始衰减,最终在学习率低于某个确定的阈值时停止。

    近年来,循环学习率变得流行起来,在循环学习率中,学习率是缓慢增加的,然后缓慢减小,以一种循环的形式持续着 [3]。

    上图是 Leslie N. Smith 提出的 Triangular 和 Triangular2 循环学习率方法。左侧的最大学习率和最小学习率保持不变。右侧的区别在于每个周期之后学习率减半。

    3.6.余弦退火(Cosine annealing)

    余弦退火可以当做是学习率衰减的一种方式,早些时候都是使用指数衰减或者线性衰减,现在余弦衰减也被普遍使用 [2]。

    在采用小批量随机梯度下降(MBGD/SGD)算法时,神经网络应该越来越接近 Loss 值的全局最小值。当它逐渐接近这个最小值时,学习率应该变得更小来使得模型不会超调且尽可能接近这一点。

    余弦退火利用余弦函数来降低学习率,进而解决这个问题,如下图所示:

    从上图可以看出,随着 x 的增加,余弦值首先缓慢下降,然后加速下降,再次缓慢下降。这种下降模式能和学习率配合,以一种十分有效的计算方式来产生很好的效果。

    我们可以用 Fast.ai 库中的 learn.fit() 函数,来快速实现这个算法,在整个周期中不断降低学习率。

    1

    2

    # Calling learn fit automatically takes advantage of cosine annealing

    learn.fit(0.1, 1)

    效果如下图所示,在一个需要 200 次迭代的周期中学习率不断降低,:

    同时,在这种方法基础上,我们可以进一步引入重启机制。

    3.7.热重启随机梯度下降(SGDR)

    在训练时,梯度下降算法可能陷入局部最小值,而不是全局最小值。下图展示了陷入局部最小值的梯度下降算法 [2]。

    梯度下降算法可以通过突然提高学习率,来 “跳出” 局部最小值并找到通向全局最小值的路径。Loshchilov 和 Hutter 在《SGDR: Stochastic Gradient Descent with Warm Restarts》论文中提出了热重启随机梯度下降(Stochastic Gradient Descent with Warm Restarts, SGDR)方法,这种方法将余弦退火与热重启相结合,使用余弦函数作为周期函数,并在每个周期最大值时重新开始学习速率。“热重启” 是因为学习率重新开始时并不是从头开始的,而是由模型在最后一步收敛的参数决定的。

    用 Fast.ai 库可以快速导入 SGDR 算法。当调用 learn.fit(learning_rate, epochs) 函数时,学习率在每个周期开始时重置为参数输入时的初始值,然后像上面余弦退火部分描述的那样,逐渐减小。

    上图中每 100 次迭代,学习率下降到最小点,我们称为一个循环。

    循环的迭代次数也可以是不一样的,下面设定了每个循环所包含的周期都是上一个循环的 2 倍。

    1

    2

    3

    4

    5

    6

    7

    8

    9

    10

    11

    12

    # decide how many epochs it takes for the learning rate to fall to

    # its minimum point. In this case, 1 epoch

    cycle_len = 1

    # at the end of each cycle, multiply the cycle_len value by 2

    cycle_mult=2

    # in this case there will be three restarts. The first time with

    # cycle_len of 1, so it will take 1 epoch to complete the cycle.

    # cycle_mult=2 so the next cycle with have a length of two epochs, 

    # and the next four.

    learn.fit(0.1, 3, cycle_len=2, cycle_mult=2)

    结果如下图表示:

    3.8.不同网络层使用不同学习率(Differential Learning Rates)

    一般情况下,在训练时通过优化网络层会比提高网络深度要更重要,在网络中使用有差别的学习率(Differential Learning rates),可以很好的提高模型性能 [2]。

    在计算机视觉深度学习中,通过已有模型来训练深度学习网络,是一种已经被验证过非常可靠高效的方法。目前大部分网络(如 Resnet、VGG 和 Inception 等)都是在 ImageNet 数据集训练的,因此我们要根据所用数据集与 ImageNet 图像的相似性,来适当改变网络权重。

    在修改这些权重时,我们通常要对模型的最后几层进行修改,因为这些层被用于检测基本特征(如边缘和轮廓),不同数据集有着不同基本特征。

    首先,要使用 Fast.ai 库来获得预训练的模型,代码如下:

    1

    2

    3

    4

    5

    6

    7

    8

    9

    10

    11

    12

    13

    14

    # import library for creating learning object for convolutional #networks

    from fastai.conv_learner import *

    # assign model to resnet, vgg, or even your own custom model

    model = VVG16()

    # create fast ai data object, in this method we use from_paths where 

    # inside PATH each image class is separated into different folders

    PATH = './folder_containing_images' 

    data = ImageClassifierData.from_paths(PATH)

    # create a learn object to quickly utilise state of the art

    # techniques from the fast ai library

    learn = ConvLearner.pretrained(model, data, precompute=True)

    创建学习对象之后(learn object),通过快速冻结前面网络层并微调后面网络层来解决问题:

    1

    2

    3

    4

    5

    6

    # freeze layers up to the last one, so weights will not be updated.

    learn.freeze()

    # train only the last layer for a few epochs

    learning_rate = 0.1

    learn.fit(learning_rate, epochs=3)

    当后面网络层产生了良好效果,我们会应用 “有差别学习率” 的方法来改变前面网络层。在实际中,一般将学习率的缩小倍数设置为 10 倍:

    1

    2

    3

    4

    5

    6

    7

    8

    9

    # set requires_grads to be True for all layers, so they can be updated

    learn.unfreeze()

    # learning rate is set so that deepest third of layers have a rate of 0.001.

    # middle layers have a rate of 0.01, and final layers 0.1.

    learning_rate = [0.001, 0.01, 0.1]

    # train model for three epoch with using differential learning rates

    learn.fit(learning_rate, epochs=3)

    3.9.快照集成和随机加权平均(Snapshot Ensembling And Stochastic Weight Averaging)

    最后将要提到的策略可以说是多个优化方法综合应用的策略,可能已经超出了 “学习率的设定” 主题的范围了,不过,我觉得下面的方法是最近一段时间研究出来的一些非常好的优化方法,因此也包括了进来,权当做是学习率优化的综合应用了。

    本小节主要涉及三个优化策略:快照集成(Snapshot Ensembling)、快速几何集成(Fast Geometric Ensembling,FGE)、随机加权平均(Stochastic Weight Averaging,SWA)

    相关内容可参考以下论文:

    《Snapshot Ensembles: Train 1, get M for free》by Gao Huang et. al

    《Loss Surfaces, Mode Connectivity, and Fast Ensembling of DNNs》by Garipov et. al

    《Averaging Weights Leads to Wider Optima and Better Generalization》by Izmailov et. al

    下面我们详细了解这些优化策略。

    从传统的集成学习一路走来

    在经典机器学习中,集成学习(Ensemble learning)是非常重要的思想,很多情况下都能带来非常好的性能,因此几乎是机器学习比赛中必用的 “神兵利器”。

    集成学习算法本身不算一种单独的机器学习算法,而是通过构建并结合多个机器学习器来完成学习任务。可以说是 “集百家之所长”,完美的诠释了 “三个臭皮匠赛过诸葛亮”。集成学习在机器学习算法中拥有较高的准确率,不足之处就是模型的训练过程可能比较复杂,效率不是很高。

    强力的集成学习算法主要有 2 种:基于 Bagging 的算法和基于 Boosting 的算法,基于 Bagging 的代表算法有随机森林,而基于 Boosting 的代表算法则有 Adaboost、GBDT、XGBOOST 等,这部分内容我们后面会单独讲到。

    集成学习的思路就是组合若干不同的模型,让它们基于相同的输入做出预测,接着通过某种平均化方法决定集成模型的最终预测。这个决定过程可能是通过简单的投票或取均值,也可能是通过另一个模型,该模型能够基于集成学习中众多模型的预测结果,学习并预测出更加准确的最终结果。岭回归是一种可以组合若干个不同预测的结果的方法,Kaggle 上卫星数据识别热带雨林竞赛的冠军就使用过这一方法 [4]。  

    集成学习的思想同样适用于深度学习,集成应用于深度学习时,组合若干网络的预测以得到一个最终的预测。通常,使用多个不同架构的神经网络得到的性能会更好,因为不同架构的网络一般会在不同的训练样本上犯错,因而集成学习带来的收益会更大。

    当然,你也可以集成同一架构的模型,也许效果会出乎意料的好。就好比本小节将要提到的快照集成方法,在训练同一个网络的过程中保存了不同的权值快照,然后在训练之后创建了同一架构、不同权值的集成网络。这么做可以提升测试的表现,同时也超省事,因为你只需要训练一个模型、训练一次就好,只要记得随时保存权值就行。

    快照集成应用了我们刚才提到的热重启随机梯度下降(Stochastic Gradient Descent with Warm Restarts, SGDR),这种循环学习率几乎为快照集成量身打造,利用热重启随机梯度下降法的特点,每次收敛到局部极值点的时候就可以缓存一个权重快照,缓存那么几个就可以做集成学习了。

    无论是经典机器学习中的集成学习,还是深度学习里面的集成学习,抑或是改良过的快照集成方法,都是模型空间内的集成,它们均是组合若干模型,接着使用这些模型的预测以得到最终的预测结果。而一些数据科学家还提出了一种全新的权值空间内的集成,这就是随机加权平均法,该方法通过组合同一网络在训练的不同阶段的权值得到一个集成,接着使用组合后的权值做出预测。这种方法有两个好处:

      • 组合权重后,我们最终仍然得到一个模型,这有利于加速预测。
      • 事实证明,这种方法胜过当前最先进的快照集成。

    在了解其实现原理之前,我们首先需要理解损失平面(loss surface)和泛化解(generalizable solution)。

    权重空间内的解

    第一个不得不提到的是,经过训练的网络是高维权值空间中的一个点。对给定的架构而言,每个不同的网络权值组合都代表了一个不同的模型。任何给定架构都有无穷的权重组合,因而有无穷多的解。训练神经网络的目标是找到一个特定的解(权值空间中的点),使得训练数据集和测试数据集上的损失函数的值都比较低。

    在训练期间,训练算法通过改变权值来改变网络并在权值空间中漫游。梯度下降算法在一个损失平面上漫游,该平面的海拔为损失函数的值。

    窄极值和宽极值

    坦白的讲,可视化并理解高维权值空间的几何特性非常困难,但我们又不得不去了解它。因为随机梯度下降的本质是,在训练时穿过这一高维空间中的损失平面,试图找到一个良好的解——损失平面上的一个损失值较低的 “点”。不过后来我们发现,这一平面有很多局部极值。但这些局部极值并不都有一样好的性质。

    一般极值点会有宽的极值和窄的极值,如下图所示:

    数据科学家研究试验后发现:宽的局部极小值在训练和测试过程中产生类似的损失;但对于窄的局部极小值而言,训练和测试中产生的损失就会有很大区别。这意味着,宽的极值比窄的极值有更好的泛化性。

    平坦度可以用来衡量一个解的优劣。其中的原理是,训练数据集和测试数据集会产生相似但不尽相同的损失平面。你可以将其想象为测试平面相对训练平面而言平移了一点。对窄的解来说,一个在测试的时候损失较低的点可能因为这一平移产生变为损失较高的点。这意味着窄的(尖锐的)解的泛化性不好——训练损失低,测试损失高。另一方面,对于宽的(平坦的)解而言,这一平移造成的训练损失和测试损失间的差异较小。

    之所以提到窄极值和宽极值,是因为随机加权平均(SWA)就能带来讨人喜欢的、宽的(平坦的)解

    快照集成(Snapshot Ensembling)

    快照集成应用了应用了热重启随机梯度下降(SGDR),最初,SGD 会在权值空间中跳出一大步。接着,由于余弦退火,学习率会逐渐降低,SGD 逐步收敛到局部极小值,缓存权重作为一个模型的 “快照”,把它加入集成模型。然后将学习率恢复到更高的值,这种更高的学习率将算法从局部极小值推到损失面中的随机点,然后使算法再次收敛到另一个局部极小值。重复几次,最后,他们对所有缓存权重集的预测进行平均,以产生最终预测。

    上图对比了使用固定学习率的单个模型与使用循环学习率的快照集成的收敛过程,快照集成是在每次学习率周期末尾保存模型,然后在预测时使用。 

    快照集成的周期长度为 20 到 40 个 epoch。较长的学习率周期是为了在权值空间中找到足够具有差异化的模型,以发挥集成的优势。如果模型太相似,那么集成模型中不同网络的预测将会过于接近,以至于集成并不会带来多大益处了。

    快照集成表现优异,提升了模型的表现,但快速几何集成效果更好。

    快速几何集成(Fast Geometric Ensembling,FGE)

    《Loss Surfaces, Mode Connectivity, and Fast Ensembling of DNNs》中提出的快速几何集成 FGE 和快照集成非常像,但是也有一些独特的特点。它们的不同主要有两点。第一,快速几何集成使用线性分段周期学习率规划,而不是余弦变化。第二,FGE 的周期长度要短得多——2 到 4 个 epoch。乍一看大家肯定直觉上觉得这么短的周期是不对的,因为每个周期结束的时候的得到的模型互相之间离得太近了,这样得到的集成模型没有什么优势。然而作者们发现,在足够不同的模型之间,存在着损失较低的连通路径。我们有机会沿着这些路径用较小的步长行进,同时这些模型也能够有足够大的差异,足够发挥集成的优势。因此,相比快照集成, FGE 表现更好,搜寻模型的步长更小(这也使其训练更快)。

    左图:根据传统的直觉,良好的局部极小值被高损失区域分隔开来(虚线)

    中/右图:局部极小值之间存在着路径,这些路径上的损失都很低(实线)。

    FGE 沿着这些路径保存快照,从而创建快照的集成。

    要从快照集成或 FGE 中受益,需要存储多个模型,接着让每个模型做出预测,之后加以平均以得到最终预测。因此,我们为集成的额外表现支付了更高的算力代价。所以天下没有免费的午餐。真的没有吗?这就是随机加权平均的用武之地了。

    随机加权平均(Stochastic Weight Averaging,SWA)

    随机加权平均只需快速几何集成的一小部分算力,就可以接近其表现。SWA 可以用在任意架构和数据集上,都会有不错的表现。根据论文中的实验,SWA 可以得到我之前提到过的更宽的极小值。在经典认知下,SWA 不算集成,因为在训练的最终阶段你只得到一个模型,但它的表现超过了快照集成,接近 FGE。

    左图:W1、W2、W3 分别代表 3 个独立训练的网络,WSWA 为其平均值。

    中图:WSWA 在测试集上的表现超越了 SGD。

    右图:WSWA 在训练时的损失比 SGD 要高。

    结合 WSWA 在测试集上优于 SGD 的表现,这意味着尽管 WSWA 训练时的损失较高,它的泛化性更好。

    SWA 的直觉来自以下由经验得到的观察:每个学习率周期得到的局部极小值倾向于堆积在损失平面的低损失值区域的边缘(上图左侧的图形中,褐色区域误差较低,点 W1、W2、W3 分别表示 3 个独立训练的网络,位于褐色区域的边缘)。对这些点取平均值,可能得到一个宽阔的泛化解,其损失更低(上图左侧图形中的 WSWA)。

    下面是 SWA 的工作原理。它只保存两个模型,而不是许多模型的集成:

      • 第一个模型保存模型权值的平均值(WSWA)。在训练结束后,它将是用于预测的最终模型。
      • 第二个模型(W)将穿过权值空间,基于周期性学习率规划探索权重空间。

    SWA 权重更新公式

    WSWA←WSWA⋅nmodels+Wnmodels+1

    在每个学习率周期的末尾,第二个模型的当前权重将用来更新第一个模型的权重。因此,在训练阶段,只需训练一个模型,并在内存中储存两个模型。预测时只需要平均模型,基于其进行预测将比之前描述的集成快很多,因为在那种集成中,你需要使用多个模型进行预测,最后再进行平均。

    方法实现

    论文的作者自己提供了一份 PyTorch 的实现https://github.com/timgaripov/swa

    此外,基于 fast.ai 库的 SWA 可见https://github.com/fastai/fastai/pull/276/commits

    4.小结

    本文主要介绍了几种梯度下降学习率的设定策略,其中 “固定学习率”、“学习率衰减” 适用于简单不太复杂的应用场景,“基于 Armijo 准则的线性回溯搜索算法” 可以当做一种自适应学习率调整,不过由于计算复杂且无法有效解决陷入局部极小值点和鞍点处的问题,使用的人并不多。在 “找到合适的学习率” 一小节中,我们介绍了一种简单有效的方法,可以快速找到一个适合的学习率,同时这种方法也可以界定学习率设定的合理范围,推荐使用。“热重启随机梯度下降” 是 “循环学习率” 和 “余弦退火” 的结合,可以非常有效的解决梯度下降容易陷入局部极值点和鞍点等问题,它正在成为当前效果最好的、也是最标准的做法,它简单易上手,计算量很轻,可以说事半功倍,尤其在深度学习中表现非常好,推荐使用。最后介绍了 “分层学习率”、“快照集成”,“随机加权平均”,是近段时间比较好的研究成果,也是不错的综合优化方法。

    梯度下降是机器学习和深度学习中非常重要的优化算法,而学习率是用好梯度下降法的关键。除了一些其他客观的原因,学习率的设定是影响模型性能好坏的非常重要的因素,所以应该给予足够的重视。最后,还记得上面提到过的 “梯度下降算法有两个重要的控制因子:一个是步长,由学习率控制;一个是方向,由梯度指定。” 吗?我们已经在学习率上有了深入的探索和研究,那在方向上是否还有可以优化的方法?如果搜索方向不是严格的梯度方向是否也可行?这里就涉及使用二阶导数的牛顿法和拟牛顿法了。不过个人觉得它们远不及梯度下降重要,如果有时间再更新这方面的内容吧。

    5.参考资料

    [1] 小胖蹄儿. learning rate 四种改变方式. CSDN

    [2] Samuel Lynn-Evans. Ten Techniques Learned From fast.ai. FloydHub Blog

    [3] Hafidz Zulkifli. Understanding Learning Rates and How It Improves Performance in Deep Learning. Towards Data Science

    [4] Max Pechyonkin. Stochastic Weight Averaging — a New Way to Get State of the Art Results in Deep Learning. Towards Data Science

    [5] 邹博. 机器学习

    展开全文
  • 图解6种pytorch学习率调整策略

    千次阅读 2020-03-16 17:09:11
    文章目录学习率调整策略基础知识pytorch中的6种学习率调整策略StepLR数学原理代码实例MultiStepLR数学原理代码实例ExponentialLR数学原理代码实例CosineAnnealingLR数学原理代码...梯度下降算法中的学习率的作用是:...


    该篇笔记整理自余庭嵩的讲解。

    学习率调整策略

    基础知识

    梯度下降算法中的学习率的作用是:
    w i + 1 = w i − L R ∗ g ( w i ) \boldsymbol{w}_{\boldsymbol{i}+\boldsymbol{1}}=\boldsymbol{w}_{\boldsymbol{i}}-\mathrm{L} R * \boldsymbol{g}\left(\boldsymbol{w}_{\boldsymbol{i}}\right) wi+1=wiLRg(wi)
    可见学习率LR控制了更新的步伐,训练开始时学习率会大,后面学习率会减小。因为一开始的学习率大是为了快速接近最优值,但如果后面还是不减小的话,就很有可能更新超过最优值点或者在最优点附近震荡,因此调整学习率是使得算法在保证精确度的前提下更快的途径。
    pytorch中给出了学习率如何进行调整的方法,所有的学习率调整策略都会继承_LRScheduler这个类,这个父类的构造函数内容如下。
    主要属性

    • optimizer:关联的优化器。优化器存放学习率,而_LRScheduler改动学习率
    • last_epoch:记录epoch数
    • base_lrs:记录初始学习率,列表形式

    上面的base_lrs是一个列表的形式,当优化器中有多个参数组的时候,base_lrs里面就会存放多个学习率,具体通过构造函数里面的三句话即可实现:

    for group in optimizer.param_groups:
    	group.setdefault('initial_lr', group['lr'])
    
    self.base_lrs = list(map(lambda group: group['initial_lr'], optimizer.param_groups))
    

    主要方法

    • step():更新下一个epoch的学习率
    • get_lr():虚函数,计算下一个epoch的学习率。这是要通过子类重写的。

    这里step函数的内容如下:

    def step(self, epoch=None):
        if epoch is None:
            epoch = self.last_epoch + 1
        self.last_epoch = epoch
        for param_group, lr in zip(self.optimizer.param_groups, self.get_lr()):
            param_group['lr'] = lr
    

    这里注意:执行step的步骤千万不要放在iteration的for循环当中,也就是说不可以和optimizer的step在同一个循环内,而是要放在epoch的那个循环中。否则学习率将会快速降低。
    step里面最关键的部分就是上面代码中那个for循环,这部分将会对学习率做出更改。主要是通过get_lr函数实现lr的计算,再把计算好的lr放到参数组中。

    pytorch中的6种学习率调整策略

    下面的6种学习率调整策略都会有代码实例来直观感受其作用,为避免重复,在开头我们先统一构造一个输入数据以供后续使用。数据构造如下:

    LR = 0.1
    iteration = 10
    max_epoch = 200
    
    weights = torch.randn((1), requires_grad=True)
    target = torch.zeros((1))
    
    optimizer = optim.SGD([weights], lr=LR, momentum=0.9)
    

    StepLR

    数学原理

    具体调用是通过torch.optim.lr_scheduler.StepLR(),其功能是等间隔调整学习率。
    主要参数:

    • step_size:调整间隔数
    • gamma:调整系数,常用的是0.1或者0.5,就是缩小10倍或者2倍

    也就是说每隔step_size个epoch调整一次学习率,调整方式为
    l r = l r ∗ g a m m a lr = lr * gamma lr=lrgamma

    代码实例

    通过如下代码观察学习率随epoch的变化情况:

    scheduler_lr = optim.lr_scheduler.StepLR(optimizer, step_size=50, gamma=0.1)  # 设置学习率下降策略
    lr_list, epoch_list = list(), list()
    for epoch in range(max_epoch):
    
        lr_list.append(scheduler_lr.get_lr())
        epoch_list.append(epoch)
    
        for i in range(iteration):
    
            loss = torch.pow((weights - target), 2)
            loss.backward()
    
            optimizer.step()
            optimizer.zero_grad()
    
        scheduler_lr.step()
    
    plt.plot(epoch_list, lr_list, label="Step LR Scheduler")
    plt.xlabel("Epoch")
    plt.ylabel("Learning rate")
    plt.legend()
    plt.show()
    

    结果示意图为:
    在这里插入图片描述

    MultiStepLR

    数学原理

    具体调用是通过torch.optim.lr_scheduler.MultiStepLR(),其功能是按给定间隔调整学习率。
    主要参数:

    • milestones:设定调整时刻数。是一个list的形式。
    • gamma:调整系数

    调整方式还是和StepLR一样:
    l r = l r ∗ g a m m a lr = lr * gamma lr=lrgamma

    代码实例

    通过如下代码观察学习率随epoch的变化情况:

    milestones = [50, 125, 160]
    scheduler_lr = optim.lr_scheduler.MultiStepLR(optimizer, milestones=milestones, gamma=0.1)
    
    lr_list, epoch_list = list(), list()
    for epoch in range(max_epoch):
    
        lr_list.append(scheduler_lr.get_lr())
        epoch_list.append(epoch)
    
        for i in range(iteration):
    
            loss = torch.pow((weights - target), 2)
            loss.backward()
    
            optimizer.step()
            optimizer.zero_grad()
    
        scheduler_lr.step()
    
    plt.plot(epoch_list, lr_list, label="Multi Step LR Scheduler\nmilestones:{}".format(milestones))
    plt.xlabel("Epoch")
    plt.ylabel("Learning rate")
    plt.legend()
    plt.show()
    

    结果示意图为:
    在这里插入图片描述

    ExponentialLR

    数学原理

    具体调用是通过torch.optim.lr_scheduler.ExponentialLR(),其功能是按指数衰减调整学习率。
    主要参数

    • gamma:指数的底,通常设置成一个接近于1的数,例如0.95

    调整方式为
    l r = l r ∗ g a m m a e p o c h lr = lr*{gamma}^{epoch} lr=lrgammaepoch

    代码实例

    通过如下代码观察学习率随epoch的变化情况:

    gamma = 0.95
    scheduler_lr = optim.lr_scheduler.ExponentialLR(optimizer, gamma=gamma)
    
    lr_list, epoch_list = list(), list()
    for epoch in range(max_epoch):
    
        lr_list.append(scheduler_lr.get_lr())
        epoch_list.append(epoch)
    
        for i in range(iteration):
    
            loss = torch.pow((weights - target), 2)
            loss.backward()
    
            optimizer.step()
            optimizer.zero_grad()
    
        scheduler_lr.step()
    
    plt.plot(epoch_list, lr_list, label="Exponential LR Scheduler\ngamma:{}".format(gamma))
    plt.xlabel("Epoch")
    plt.ylabel("Learning rate")
    plt.legend()
    plt.show()
    

    结果示意图为:
    在这里插入图片描述

    CosineAnnealingLR

    数学原理

    具体调用是通过torch.optim.lr_scheduler.CosineAnnealingLR(),其功能是按余弦周期调整学习率。
    主要参数

    • T_max:下降周期,也就是经过T_max个epoch,学习率从最大下降至最低
    • eta_min:学习率下限

    调整方式为
    η t = η min ⁡ + 1 2 ( η max ⁡ − η min ⁡ ) ( 1 + cos ⁡ ( T cur ⁡ T max ⁡ π ) ) \eta_{t}=\eta_{\min }+\frac{1}{2}\left(\eta_{\max }-\eta_{\min }\right)\left(1+\cos \left(\frac{T_{\operatorname{cur}}}{T_{\max }} \pi\right)\right) ηt=ηmin+21(ηmaxηmin)(1+cos(TmaxTcurπ))

    代码实例

    通过如下代码观察学习率随epoch的变化情况:

    t_max = 50
    scheduler_lr = optim.lr_scheduler.CosineAnnealingLR(optimizer, T_max=t_max, eta_min=0.)
    
    lr_list, epoch_list = list(), list()
    for epoch in range(max_epoch):
    
        lr_list.append(scheduler_lr.get_lr())
        epoch_list.append(epoch)
    
        for i in range(iteration):
    
            loss = torch.pow((weights - target), 2)
            loss.backward()
    
            optimizer.step()
            optimizer.zero_grad()
    
        scheduler_lr.step()
    
    plt.plot(epoch_list, lr_list, label="CosineAnnealingLR Scheduler\nT_max:{}".format(t_max))
    plt.xlabel("Epoch")
    plt.ylabel("Learning rate")
    plt.legend()
    plt.show()
    

    结果示意图为:
    在这里插入图片描述

    ReduceLRonPlateau

    数学原理

    具体调用是通过torch.optim.lr_scheduler.ReduceLRonPlateau(),其功能为监控指标,当指标不再变化则进行调整。例如当loss值不再下降的时候,就调整学习率。
    主要参数

    • mode:min/max 两种模式,在min模式下,如果监控的指标不下降,那就进行调整学习率操作,在max模式下,如果监控的指标不上升,那就进行调整学习率操作
    • factor:调整系数,类似上面部分的gamma
    • patience:监控的这个指标可以接受连续多少次不发生变化,默认值是10
    • cooldown:冷却时间,调整完之后停止监控一段时间。之后再继续监控
    • verbose:控制是否打印日志,如果是True,那么就在学习率变更的时候打印
    • min_lr:设置学习率下限
    • eps:学习率衰减最小值

    代码实例

    通过如下代码观察学习率随epoch的变化情况,注意在scheduler_lr.step()的时候要把监控的变量输入进去:

    loss_value = 0.5
    accuray = 0.9
    
    factor = 0.1
    mode = "min"
    patience = 10
    cooldown = 10
    min_lr = 1e-4
    verbose = True
    
    scheduler_lr = optim.lr_scheduler.ReduceLROnPlateau(optimizer, factor=factor, mode=mode, patience=patience,
                                                            cooldown=cooldown, min_lr=min_lr, verbose=verbose)
    
    for epoch in range(max_epoch):
        for i in range(iteration):
    
            optimizer.step()
            optimizer.zero_grad()
    
        if epoch == 5:
            loss_value = 0.4
    
        scheduler_lr.step(loss_value)
    

    输出结果为:

    Epoch    16: reducing learning rate of group 0 to 1.0000e-02.
    Epoch    37: reducing learning rate of group 0 to 1.0000e-03.
    Epoch    58: reducing learning rate of group 0 to 1.0000e-04.
    

    LambdaLR

    数学原理

    具体调用是通过torch.optim.lr_scheduler.LambdaLR(),其功能为自定义调整策略。
    主要参数

    • lr_lambda:这个变量可以是函数或者列表,是列表的情况下,列表中的每一个元素也必须是一个函数

    此外,LambdaLR还可以根据不同的参数组设置不同的学习率调整策略。

    代码实例

    通过如下代码观察学习率随epoch的变化情况:

    lr_init = 0.1
    
    weights_1 = torch.randn((6, 3, 5, 5))
    weights_2 = torch.ones((5, 5))
    
    optimizer = optim.SGD([
        {'params': [weights_1]},
        {'params': [weights_2]}], lr=lr_init)
    
    lambda1 = lambda epoch: 0.1 ** (epoch // 20)  # 每到20次乘0.1
    lambda2 = lambda epoch: 0.95 ** epoch  # 每次乘0.95
    
    scheduler = torch.optim.lr_scheduler.LambdaLR(optimizer, lr_lambda=[lambda1, lambda2])
    
    lr_list, epoch_list = list(), list()
    for epoch in range(max_epoch):
        for i in range(iteration):
    
            optimizer.step()
            optimizer.zero_grad()
    
        scheduler.step()
    
        lr_list.append(scheduler.get_lr())
        epoch_list.append(epoch)
    
        print('epoch:{:5d}, lr:{}'.format(epoch, scheduler.get_lr()))
    
    plt.plot(epoch_list, [i[0] for i in lr_list], label="lambda 1")
    plt.plot(epoch_list, [i[1] for i in lr_list], label="lambda 2")
    plt.xlabel("Epoch")
    plt.ylabel("Learning Rate")
    plt.title("LambdaLR")
    plt.legend()
    plt.show()
    

    这里需要注意的一点是,在LambdaLR的get_lr()函数中是这样写的:

    return [base_lr * lmbda(self.last_epoch)
            for lmbda, base_lr in zip(self.lr_lambdas, self.base_lrs)]
    

    这里就会调用到上面定义的函数,而函数的唯一输入值就是上一个epoch的值,所以学习率调整策略的本质就是学习率是关于epoch的一元函数。
    结果示意图为:
    在这里插入图片描述

    总结

    学习率初始值一般设置较小的数,例如0.01、0.001、0.0001等等,当模型finetune的时候一般设置最小的情况,例如0.0001,当随机初始化直接训练的时候一般设置较大的数,例如0.01。
    最大的学习率要怎么找呢?有关如何搜索最大学习率的问题,这篇文章给出了解答《Cyclical Learning Rates for Training Neural Networks》

    展开全文
  • Environment OS: macOS Mojave Python version: 3.7 PyTorch version: 1.4.0 IDE: PyCharm ...PyTorch 在 torch.optim.lr_scheduler 中提供了十种调整学习率的类,它们的基类为 torch.optim.lr_scheduler._LRSc
  • Pytorch学习率调整策略

    千次阅读 2022-01-17 11:22:20
    PyTorch学习率调整策略通过torch.optim.lr_scheduler接口实现。该模块提供了一些根据epoch训练次数来调整学习率的方法。一般情况下我们会设置随着epoch的增大而逐渐减小学习率从而达到更好的训练效果。
  • pytroch中常见的学习率调整策略
  • 大家好,今天和大家分享一下如何使用 TensorFlow 自定义 指数学习率下降、阶梯学习率下降、余弦学习率下降 方法,并使用 Mnist数据集验证自定义的学习率下降策略。 创建的自定义学习率类方法,需要继承 tf.keras....
  • 深度学习中的固定学习率衰减策略总结 深层神经网络的参数学习主要是通过梯度下降方法来寻找一组可以最小化结构风险的参数。 在梯度下降学习率的取值非常关键,如果过大可能不会收敛,过小则收敛速度太慢。 通常的...
  • 【深度学习】学习率策略合集

    千次阅读 2022-03-15 21:21:12
    深度学习中有各种学习率策略,本文统计了pytorch和paddlepaddle共16种学习率策略,给出示例代码及对应的学习率变化图,相信一定对你有所帮助。
  • 学习率会在刚开始的时候上升,加快模型的收敛速度,寻找最优点位置,到达一定step后,学习率下降,此时我们可以认为这是一个模型在微调的过程。上升采用线性上升,下降采用cos函数下降。 step = (训练样本数 * 训练...
  • pytorch的4种学习率衰减策略

    千次阅读 2021-05-08 10:09:49
    作者丨机器学习入坑者@知乎(已授权) ... 编辑丨极市平台 极市导读 本文介绍了四种衰减类型:指数衰减、...梯度下降算法需要我们指定一个学习率作为权重更新步幅的控制因子,常用的学习率有0.01、0.001以及0.0001..
  • 首先,我们以YOLOX的学习率调整策略为例进行分析:该策略是带有Warmup(热身)的余弦调度策略。同时为了配合数据增强。在最后15个epoch里采用固定的最小学习率。 训练预热 - Warmup 训练初始.
  • 史上最全学习率调整策略lr_scheduler

    千次阅读 2022-07-06 20:48:16
    学习率是深度学习训练中至关重要的参数,很多时候一个合适的学习率才能发挥出模型的较大潜力。所以学习率调整策略同样至关重要,这篇博客介绍一下Pytorch中常见的学习率调整方法。
  • 梯度下降学习率的设定策略

    万次阅读 多人点赞 2019-04-23 14:39:38
    本文转载自卢明冬的博客-梯度下降学习率的设定策略 1.学习率的重要性 1)学习率设置太小,需要花费过多的时间来收敛 2)学习率设置较大,在最小值附近震荡却无法收敛到最小值 3)进入局部极值点就收敛,没有真正找到...
  • 学习率是 神经网络 优化时的重要超参数。学习率α的取值非常关键,学习率越大则权重更新的越快。在梯度下降方法中,如果过大就不会收敛,如果过小则收敛速度太慢。学习率越大,输出误差对参数的影响就越大,参数更新...
  • Pytorch深度学习—学习率调整策略为什么要调整学习率? 前面的课程学习了优化器的概念,优化器中有很多超参数如学习率lr,momentum动量、weight_decay系数,这些超参数中最重要的就是学习率学习率可以直接控制模型...
  • 梯度下降算法需要我们指定一个学习率作为权重更新步幅的控制因子,常用的学习率有0.01、0.001以及0.0001等,学习率越大则权重更新。一般来说,我们希望在训练初期学习率大一些,使得网络收敛迅速,在训练后期学习率...
  • 几种学习率衰减策略

    千次阅读 2019-12-04 14:19:36
    在梯度下降方法中,学习率α的取值非常关键,如果过大就不会收敛,如果过小则收敛速度太慢。常用的学习率调整方法包括学习率衰减率、学习率预热、周期学习率以及一些自适应地调整学习率的方法,比如AdaGrad、RMSprop...
  • 7.3 学习率调整策略

    千次阅读 2020-09-25 20:31:33
    二、pytorch的六种学习率调整策略 前两节课学习了优化器的概念,优化器中有很多超参数,在这些参数中最重要的参数就是学习率。 我们知道学习率直接控制了参数更新的大小。在整个训练过程中,...
  • 学习率 α\alphaα 控制着梯度更新的步长(step),α\alphaα 越大,意味着下降的越快,到达最优点的速度也越快,如果为000,则网络就会停止更新 学习率过大,在算法优化的前期会加速学习,使得模型更容易接近局部或
  • 1.Yolov5学习率调整策略

    千次阅读 多人点赞 2021-01-29 14:11:46
    1.Yolov5学习率调整策略:lr_scheduler.LambdaLR 本代码模拟yolov5的学习率调整,深度解析其中torch.optim.lr_scheduler在yolov5的使用方法,有助于提高我们对该代码的理解。 ​ 为了简单实现模拟yolov5的学习率调整...
  • YOLOv5-优化器和学习率调整策略

    千次阅读 2022-01-05 17:29:09
    优化器和学习率调整策略 pytorch-优化器和学习率调整 关于优化器和学习率的一些基础记得很细,还有相关实现代码
  • 如有错误,恳请指出。...配合辅助绘制函数,这里可以将两种学习率调整策略学习率随epochs变化绘制出来,这里我重新写了一个函数比较方便调用lf。参考代码: 下面利用以上函数分别查看线性学习率与One C
  • 大家好,今天和各位分享一下如何使用 TensorFlow 构建多项式学习率衰减策略、单周期余弦退火学习率衰减策略、多周期余弦退火学习率衰减策略,并使用Mnist数据集来验证构建的方法是否可行。 在上一篇中和大家分享了...
  • 系统学习Pytorch笔记七:优化器和学习率调整策略

    千次阅读 多人点赞 2020-04-26 10:05:29
    Pytorch官方英文文档:...1. 写在前面 疫情在家的这段时间,想系统的学习一遍Pytorch基础知识,因为我发现虽然直接Pytorch实战上手比较快,但是关于一些内部的原理知识其实并不是太懂,这样...
  • 学习率将逐渐上升,然后下降重量衰减:要应用的重量衰减。 这将遵循与学习率相同的时间表周期长度:完成“一个周期”策略的步骤数。 在“ cycle_length”之后,学习率将呈指数递减的趋近于零。经过测试: Python ...
  • Keras中那些学习率衰减策略

    千次阅读 多人点赞 2020-07-26 13:30:44
    神经网路的训练绕不开的调参,调参中学习率至为重要。下面聊聊Keras中提供的学习率策略。 四种学习率衰减策略 指数衰减

空空如也

空空如也

1 2 3 4 5 ... 20
收藏数 52,701
精华内容 21,080
关键字:

学习率下降策略