深度学习训练策略-学习率预热Warmup

本文深入探讨了Warmup学习率预热方法,解释了其在深度学习训练中的作用及必要性,对比了constantwarmup与gradualwarmup的优劣,并提供了实际应用的代码示例。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

一、什么是Warmup?

Warmup是在ResNet论文中提到的一种学习率预热的方法,它在训练开始的时候先选择使用一个较小的学习率,训练了一些steps(15000steps,见代码1)或者epoches(5epoches,见代码2),再修改为预先设置的学习来进行训练。例如:

二、为什么使用Warmup?

由于刚开始训练时,模型的权重(weights)是随机初始化的,此时若选择一个较大的学习率,可能带来模型的不稳定(振荡),选择Warmup预热学习率的方式,可以使得开始训练的几个epoches或者一些steps内学习率较小,在预热的小学习率下,模型可以慢慢趋于稳定,等模型相对稳定后再选择预先设置的学习率进行训练,使得模型收敛速度变得更快,模型效果更佳。

ExampleExampleExample:Resnet论文中使用一个110层的ResNet在cifar10上训练时,先用0.01的学习率训练直到训练误差低于80%(大概训练了400个steps),然后使用0.1的学习率进行训练。

三、Warmup的改进

二中所述的Warmup是constant warmup,它的不足之处在于从一个很小的学习率一下变为比较大的学习率可能会导致训练误差突然增大。于是18年Facebook提出了gradual warmup来解决这个问题,即从最初的小学习率开始,每个step增大一点点,直到达到最初设置的比较大的学习率时,采用最初设置的学习率进行训练。

四、总结

使用Warmup预热学习率的方式,即先用最初的小学习率训练,然后每个step增大一点点,直到达到最初设置的比较大的学习率时(注:此时预热学习率完成),采用最初设置的学习率进行训练(注:预热学习率完成后的训练过程,学习率是衰减的),有助于使模型收敛速度变快,效果更佳。

gradual warmup示例代码1:15000 steps 

"""
Implements gradual warmup, if train_steps < warmup_steps, the
learning rate will be `train_steps/warmup_steps * init_lr`.
Args:
    warmup_steps:warmup步长阈值,即train_steps<warmup_steps,使用预热学习率,否则使用预设值学习率
    train_steps:训练了的步长数
    init_lr:预设置学习率
"""
import numpy as np
warmup_steps = 2500
init_lr = 0.1  
# 模拟训练15000步
max_steps = 15000
for train_steps in range(max_steps):
    if warmup_steps and train_steps < warmup_steps:
        warmup_percent_done = train_steps / warmup_steps
        warmup_learning_rate = init_lr * warmup_percent_done  #gradual warmup_lr
        learning_rate = warmup_learning_rate
    else:
        #learning_rate = np.sin(learning_rate)  #预热学习率结束后,学习率呈sin衰减
        learning_rate = learning_rate**1.0001 #预热学习率结束后,学习率呈指数衰减(近似模拟指数衰减)
    if (train_steps+1) % 100 == 0:
             print("train_steps:%.3f--warmup_steps:%.3f--learning_rate:%.3f" % (
                 train_steps+1,warmup_steps,learning_rate))

2.上述代码实现的Warmup预热学习率以及学习率预热完成后衰减(sin or exp decay)的曲线图如下:

学习率warmup先升至初始学习率,后衰减

gradual warmup示例代码2: 5 epochs

import tensorflow as tf
import numpy as np

callbacks = tf.keras.callbacks
backend = tf.keras.backend


class LearningRateScheduler(callbacks.Callback):
    def __init__(self,
                 schedule,
                 learning_rate=None,
                 warmup=False,
                 steps_per_epoch=None,
                 verbose=0):
        super(LearningRateScheduler, self).__init__()
        self.learning_rate = learning_rate
        self.schedule = schedule
        self.verbose = verbose
        self.warmup_epochs = 5 if warmup else 0
        self.warmup_steps = int(steps_per_epoch) * self.warmup_epochs if warmup else 0
        self.global_batch = 0

        if warmup and learning_rate is None:
            raise ValueError('learning_rate cannot be None if warmup is used.')
        if warmup and steps_per_epoch is None:
            raise ValueError('steps_per_epoch cannot be None if warmup is used.')

    def on_train_batch_begin(self, batch, logs=None):
        self.global_batch += 1
        if self.global_batch < self.warmup_steps:
            if not hasattr(self.model.optimizer, 'lr'):
                raise ValueError('Optimizer must have a "lr" attribute.')
            lr = self.learning_rate * self.global_batch / self.warmup_steps
            backend.set_value(self.model.optimizer.lr, lr)
            if self.verbose > 0:
                print('\nBatch %05d: LearningRateScheduler warming up learning '
                      'rate to %s.' % (self.global_batch, lr))

    def on_epoch_begin(self, epoch, logs=None):
        if not hasattr(self.model.optimizer, 'lr'):
            raise ValueError('Optimizer must have a "lr" attribute.')
        lr = float(backend.get_value(self.model.optimizer.lr))

        if epoch >= self.warmup_epochs:
            try:  # new API
                lr = self.schedule(epoch - self.warmup_epochs, lr)
            except TypeError:  # Support for old API for backward compatibility
                lr = self.schedule(epoch - self.warmup_epochs)
            if not isinstance(lr, (float, np.float32, np.float64)):
                raise ValueError('The output of the "schedule" function '
                                 'should be float.')
            backend.set_value(self.model.optimizer.lr, lr)

            if self.verbose > 0:
                print('\nEpoch %05d: LearningRateScheduler reducing learning '
                      'rate to %s.' % (epoch + 1, lr))

    def on_epoch_end(self, epoch, logs=None):
        logs = logs or {}
        logs['lr'] = backend.get_value(self.model.optimizer.lr)


def step_decay(lr=3e-4, max_epochs=100, warmup=False):
    """
    step decay.
    :param lr: initial lr
    :param max_epochs: max epochs
    :param warmup: warm up or not
    :return: current lr
    """
    drop = 0.1
    max_epochs = max_epochs - 5 if warmup else max_epochs

    def decay(epoch):
        lrate = lr * np.power(drop, np.floor((1 + epoch) / max_epochs))
        return lrate

    return decay

args.learning_rate = 0.01
args.num_epochs = 1000
args.lr_warmup = True
steps_per_epoch = 100  # update for use 

lr_decay = step_decay(args.learning_rate, args.num_epochs - 5 if args.lr_warmup else args.num_epochs, warmup=args.lr_warmup)
learning_rate_scheduler = LearningRateScheduler(lr_decay, args.learning_rate, args.lr_warmup, steps_per_epoch, verbose=1)

本文整理自:

1、Warmup预热学习率

2、https://github.com/luyanger1799/amazing-semantic-segmentation

### Warm Up 训练的概念与方法 Warm Up 是一种用于提升模型训练稳定性和效果的技术,尤其适用于大规模深度学习模型的训练。其核心思想是在训练初期使用较低的学习率,随着训练进程逐步增加至目标学习率。这种方法可以有效缓解因参数随机初始化而导致的梯度过大问题,从而提高训练稳定性。 #### 温暖启动的核心原理 在 mini-batch 梯度下降过程中,较大的 batch size 往往需要较高的学习率来加速收敛。然而,在训练初期,由于模型参数尚未经过充分调整,过高的学习率可能导致训练过程不稳定甚至发散[^4]。因此,通过引入 Warm Up 技术,在最初的若干步或 epoch 中采用较小的学习率,并逐渐将其增大到预定值,能够显著改善这一情况。 #### 实现方式 以下是两种常见的 Warm Up 实现策略: 1. **固定时间间隔后的切换** 此方法规定了一个固定的训练阶段(如前 10 epochs 或 10,000 steps),在此期间保持低学习率;随后切换回正常设定的学习率继续完成剩余部分的训练流程[^2]。例如 ResNet 的实验表明,在 cifar-10 数据集上的应用中,先以 0.01 的学习率运行约 400 iterations 直至达到一定条件后再转而运用更高的 0.1 学习率进行后续操作[^3]。 2. **渐进式升温 (Gradual Warmup)** 不同于简单的两段划分法,“gradual warmup”则采取更为平滑的方式过渡:从极小初始值开始按线性或其他函数形式连续上调直至标准水平为止。这种方式不仅有助于进一步增强系统的鲁棒性,而且对于超大网络结构尤为适用因为它们可能更加敏感于早期更新幅度的变化。 #### Python代码示例 下面展示如何基于 PyTorch 构建一个带有 Gradual Warmup 功能的学习率调度器: ```python import torch.optim.lr_scheduler as lr_scheduler class GradualWarmupScheduler(lr_scheduler._LRScheduler): """ Gradually warm-up(increasing) learning rate in optimizer. Proposed in 'Accurate, Large Minibatch SGD: Training ImageNet in 1 Hour'. """ def __init__(self, optimizer, multiplier, total_epoch, after_scheduler=None): self.multiplier = multiplier if self.multiplier < 1.: raise ValueError('multiplier should be greater thant or equal to 1.') self.total_epoch = total_epoch self.after_scheduler = after_scheduler super().__init__(optimizer) def get_lr(self): if self.last_epoch > self.total_epoch: if self.after_scheduler: if not self.finished: self.after_scheduler.base_lrs = [base_lr * self.multiplier for base_lr in self.base_lrs] self.finished = True return self.after_scheduler.get_last_lr() return [base_lr * self.multiplier for base_lr in self.base_lrs] return [base_lr * ((self.multiplier - 1.) / self.total_epoch * self.last_epoch + 1.) for base_lr in self.base_lrs] def step_ReduceLROnPlateau(self, metrics, epoch=None): if epoch is None: epoch = self.last_epoch + 1 self.last_epoch = epoch if epoch != 0 else 1 if self.last_epoch <= self.total_epoch: warmup_factor = (self.multiplier - 1.) / self.total_epoch * self.last_epoch + 1. for param_group, lr in zip(self.optimizer.param_groups ,self.base_lrs): param_group['lr'] = lr * warmup_factor else: if epoch == self.total_epoch + 1 and self.after_scheduler: self.after_scheduler.step(metrics, None) else: if self.after_scheduler: self.after_scheduler.step(metrics, None) else: pass def main(): model = ... # your model here optimizer = torch.optim.SGD(model.parameters(), lr=0.01, momentum=0.9, weight_decay=1e-4) scheduler_steplr = lr_scheduler.StepLR(optimizer, step_size=30, gamma=0.1) scheduler_warmup = GradualWarmupScheduler(optimizer, multiplier=8, total_epoch=5, after_scheduler=scheduler_steplr) for epoch in range(100): # 假设总共有100个epochs train(...) validate(...) scheduler_warmup.step() if __name__ == '__main__': main() ``` 上述脚本定义了一类新的调度程序 `GradualWarmupScheduler` ,它可以在指定数量的 Epochs 内执行温和上升的操作,之后可链接其他类型的 LR 调整机制共同作用。 ---
评论 4
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值