(一)keras学习率余弦退火CosineAnnealing
https://blog.csdn.net/CarryLvan/article/details/104394960
keras学习率余弦退火CosineAnnealing 1.引言 2.余弦退火的原理 3.keras实现 1.引言 当我们使用梯度下降算法来优化目标函数的时候,当越来越接近Loss值的全局最小值时,学习率应该变得更小来使得模型不会超调且尽可能接近这一点,而余弦退火(Cosine annealing)可以通过余弦函数来降低学习率。余弦函数中随着x的增加余弦值首先缓慢下降,然后加速下降,再次缓慢下降。这种下降模式能和学习率配合,以一种十分有效的计算方式来产生很好的效果。
在论文Stochastic Gradient Descent with Warm Restarts中介绍主要介绍了带重启的随机梯度下降算法(SGDR),其中就引入了余弦退火的学习率下降方式,本文主要介绍余弦退火的原理以及实现。并且因为我们的目标优化函数可能是多峰的(如下图所示),除了全局最优解之外还有多个局部最优解,在训练时梯度下降算法可能陷入局部最小值,此时可以通过突然提高学习率,来“跳出”局部最小值并找到通向全局最小值的路径。这种方式称为带重启的随机梯度下降方法。
2.余弦退火的原理 论文介绍最简单的热重启的方法。当执行完T i T_iT? i ? ?个epoch之后就会开始热重启(warm restart),而下标i ii就是指的第几次restart,其中重启并不是重头开始,而是通过增加学习率来模拟,并且重启之后使用旧的x t x_tx? t ? ?作为初始解,这里的x t x_tx? t ? ?就是通过梯度下降求解loss函数的解,也就是神经网络中的权重,因为重启就是为了通过增大学习率来跳过局部最优,所以需要将x t x_tx? t ? ?置为旧值。
本文并不涉及重启部分的内容,所以只考虑在每一次run(包含重启就是restart)中,学习率是如何减小的。余弦退火( cosine annealing )的原理如下: η t = η m i n i + 1 2 ( η m a x i ? η m i n i ) ( 1 + c o s ( T c u r T i π ) ) \eta_t=\eta_{min}^{i}+\frac{1}{2}(\eta_{max}^{i}-\eta_{min}^{i})(1+cos(\frac{T_{cur}}{T_i}\pi)) η? t ? ?=η? min i ? ?+? 2 1 ? ?(η? max i ? ??η? min i ? ?)(1+cos(? T? i ? ? T? cur ? ? ? ?π))
表达式中的字符含义:
i ii就是第几次run(索引值); η m a x i \eta_{max}^{i}η? max i ? ?和η m i n i \eta_{min}^{i}η? min i ? ?分别表示学习率的最大值和最小值,定义了学习率的范围。论文中提到在每次restart之后,减少η m a x i \eta_{max}^{i}η? max i ? ?和η m i n i \eta_{min}^{i}η? min i ? ?的值会是有趣的,但是为了简单,论文中也保持η m a x i \eta_{max}^{i}η? max i ? ?和η m i n i \eta_{min}^{i}η? min i ? ?在每次restart之后仍然保持不变。 T c u r T_{cur}T? cur ? ?则表示当前执行了多少个epoch,但是T c u r T_{cur}T? cur ? ?是在每个batch运行之后就会更新,而此时一个epoch还没有执行完,所以T c u r T_{cur}T? cur ? ?的值可以为小数。例如总样本为80,每个batch的大小是16,那么在一个epoch中就会循环5次读入batch,那么在第一个epoch中执行完第一个batch后,T c u r T_{cur}T? cur ? ?的值就更新为1/5=0.2,以此类推。 T i T_iT? i ? ?表示第i次run中总的epoch数。当涉及到重启时,论文中提到为了提高性能表现,开始会初始化一个比较小的T i T_iT? i ? ?,在每次restart后,T i T_iT? i ? ?会以乘以一个T m u l t T_{mult}T? mult ? ?的方式增加,但是本文不涉及重启也就不需要考虑,即把T i T_iT? i ? ?固定为我们训练模型的epoch数。 3.keras实现 为了简单,这里稍微修改一下T c u r T_{cur}T? cur ? ?和T i T_{i}T? i ? ?的定义,原本表示的是epoch的数量,但是因为T c u r T_{cur}T? cur ? ?是在每个batch之后都会更新,所以将T i T_{i}T? i ? ?定义为总的batch需要执行的步数,而T c u r T_{cur}T? cur ? ?定义为当前对当前已经执行的batch的计数,即每执行一个batch,T c u r T_{cur}T? cur ? ?就加一。举个例子,样本总数为80,每个batch的大小为16,那么一共有5个batch,再令训练模型总的epoch为30,假设当前执行到第二个epoch的第二个batch结束,那么此时T c u r / T i = ( 1 ? 5 + 2 ) / ( 30 ? 5 ) T_{cur}/T_i=(1*5+2)/(30*5)T? cur ? ?/T? i ? ?=(1?5+2)/(30?5),按照之前的定义T c u r / T i = ( 1 + 2 / 5 ) / 30 T_{cur}/T_i=(1+2/5)/30T? cur ? ?/T? i ? ?=(1+2/5)/30,两者是等价的,但是因为之前的定义存在小数,如果1除以batch的总数除不尽,就会存在精度损失的情况。
这里除了实现余弦退火之外,还加入了warm up预热阶段,在warm up阶段学习率线性增长,当达到我们设置的学习率之后,再通过余弦退火的方式降低学习率。
为什么使用Warmup? 由于刚开始训练时,模型的权重(weights)是随机初始化的,此时若选择一个较大的学习率,可能带来模型的不稳定(振荡),选择Warmup预热学习率的方式,可以使得开始训练的几个epoches或者一些steps内学习率较小,在预热的小学习率下,模型可以慢慢趋于稳定,等模型相对稳定后再选择预先设置的学习率进行训练,使得模型收敛速度变得更快,模型效果更佳。
keras通过继承Callback实现余弦退火。通过继承Callback,当我们训练的时候传入我们的就函数,就可以在每个batch开始训练前以及结束后回调我们重写的on_batch_end和on_batch_begin函数。
完整代码(源自github):
import numpy as np from tensorflow import keras from keras import backend as K
def cosine_decay_with_warmup(global_step, ? ? ? ? ? ? ? ? ? ? ? ? ? ? ?learning_rate_base, ? ? ? ? ? ? ? ? ? ? ? ? ? ? ?total_steps, ? ? ? ? ? ? ? ? ? ? ? ? ? ? ?warmup_learning_rate=0.0, ? ? ? ? ? ? ? ? ? ? ? ? ? ? ?warmup_steps=0, ? ? ? ? ? ? ? ? ? ? ? ? ? ? ?hold_base_rate_steps=0): ? ?""" ? ?参数: ? ??? ??? ?global_step: 上面定义的Tcur,记录当前执行的步数。 ? ??? ??? ?learning_rate_base:预先设置的学习率,当warm_up阶段学习率增加到learning_rate_base,就开始学习率下降。 ? ??? ??? ?total_steps: 是总的训练的步数,等于epoch*sample_count/batch_size,(sample_count是样本总数,epoch是总的循环次数) ? ??? ??? ?warmup_learning_rate: 这是warm up阶段线性增长的初始值 ? ??? ??? ?warmup_steps: warm_up总的需要持续的步数 ? ??? ??? ?hold_base_rate_steps: 这是可选的参数,即当warm up阶段结束后保持学习率不变,知道hold_base_rate_steps结束后才开始学习率下降 ? ?""" ? ? if total_steps < warmup_steps: ? ? ? ? raise ValueError('total_steps must be larger or equal to ' ? ? ? ? ? ? ? ? ? ? ? ? ?'warmup_steps.') ? ? #这里实现了余弦退火的原理,设置学习率的最小值为0,所以简化了表达式 ? ? learning_rate = 0.5 * learning_rate_base * (1 + np.cos(np.pi * ? ? ? ? (global_step - warmup_steps - hold_base_rate_steps) / float(total_steps - warmup_steps - hold_base_rate_steps))) ? ? #如果hold_base_rate_steps大于0,表明在warm up结束后学习率在一定步数内保持不变 ? ? if hold_base_rate_steps > 0: ? ? ? ? learning_rate = np.where(global_step > warmup_steps + hold_base_rate_steps, ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ?learning_rate, learning_rate_base) ? ? if warmup_steps > 0: ? ? ? ? if learning_rate_base < warmup_learning_rate: ? ? ? ? ? ? raise ValueError('learning_rate_base must be larger or equal to ' ? ? ? ? ? ? ? ? ? ? ? ? ? ? ?'warmup_learning_rate.') ? ? ? ? #线性增长的实现 ? ? ? ? slope = (learning_rate_base - warmup_learning_rate) / warmup_steps ? ? ? ? warmup_rate = slope * global_step + warmup_learning_rate ? ? ? ? #只有当global_step 仍然处于warm up阶段才会使用线性增长的学习率warmup_rate,否则使用余弦退火的学习率learning_rate ? ? ? ? learning_rate = np.where(global_step < warmup_steps, warmup_rate, ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ?learning_rate) ? ? return np.where(global_step > total_steps, 0.0, learning_rate)
class WarmUpCosineDecayScheduler(keras.callbacks.Callback): ? ? """ ? ? 继承Callback,实现对学习率的调度 ? ? """ ? ? def __init__(self, ? ? ? ? ? ? ? ? ?learning_rate_base, ? ? ? ? ? ? ? ? ?total_steps, ? ? ? ? ? ? ? ? ?global_step_init=0, ? ? ? ? ? ? ? ? ?warmup_learning_rate=0.0, ? ? ? ? ? ? ? ? ?warmup_steps=0, ? ? ? ? ? ? ? ? ?hold_base_rate_steps=0, ? ? ? ? ? ? ? ? ?verbose=0): ? ? ? ? super(WarmUpCosineDecayScheduler, self).__init__() ? ? ? ? self.learning_rate_base = learning_rate_base ? ? ? ? self.total_steps = total_steps ? ? ? ? self.global_step = global_step_init ? ? ? ? self.warmup_learning_rate = warmup_learning_rate ? ? ? ? self.warmup_steps = warmup_steps ? ? ? ? self.hold_base_rate_steps = hold_base_rate_steps ? ? ? ? self.verbose = verbose ? ? ? ? #learning_rates用于记录每次更新后的学习率,方便图形化观察 ? ? ? ? self.learning_rates = [] ?? ?#更新global_step,并记录当前学习率 ? ? def on_batch_end(self, batch, logs=None): ? ? ? ? self.global_step = self.global_step + 1 ? ? ? ? lr = K.get_value(self.model.optimizer.lr) ? ? ? ? self.learning_rates.append(lr) ?? ?#更新学习率 ? ? def on_batch_begin(self, batch, logs=None): ? ? ? ? lr = cosine_decay_with_warmup(global_step=self.global_step, ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? learning_rate_base=self.learning_rate_base, ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? total_steps=self.total_steps, ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? warmup_learning_rate=self.warmup_learning_rate, ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? warmup_steps=self.warmup_steps, ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? hold_base_rate_steps=self.hold_base_rate_steps) ? ? ? ? K.set_value(self.model.optimizer.lr, lr) ? ? ? ? if self.verbose > 0: ? ? ? ? ? ? print('\nBatch %05d: setting learning ' ? ? ? ? ? ? ? ? ? 'rate to %s.' % (self.global_step + 1, lr))
下面的代码构建了一个简单的模型,并使用了warm up和余弦退火的方式来规划学习率。
from keras.models import Sequential from keras.layers import Dense # Create a model. model = Sequential() model.add(Dense(32, activation='relu', input_dim=100)) model.add(Dense(10, activation='softmax')) model.compile(optimizer='rmsprop', ? ? ? ? ? ? ? ? loss='categorical_crossentropy', ? ? ? ? ? ? ? ? metrics=['accuracy'])
#样本总数 sample_count = 12608 # Total epochs to train. epochs = 50 # Number of warmup epochs. warmup_epoch = 10 # Training batch size, set small value here for demonstration purpose. batch_size = 16 # Base learning rate after warmup. learning_rate_base = 0.0001
total_steps = int(epochs * sample_count / batch_size)
# Compute the number of warmup batches. warmup_steps = int(warmup_epoch * sample_count / batch_size)
# Generate dummy data. data = np.random.random((sample_count, 100)) labels = np.random.randint(10, size=(sample_count, 1))
# Convert labels to categorical one-hot encoding. one_hot_labels = keras.utils.to_categorical(labels, num_classes=10)
# Compute the number of warmup batches. warmup_batches = warmup_epoch * sample_count / batch_size
# Create the Learning rate scheduler. warm_up_lr = WarmUpCosineDecayScheduler(learning_rate_base=learning_rate_base, ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? total_steps=total_steps, ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? warmup_learning_rate=4e-06, ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? warmup_steps=warmup_steps, ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? hold_base_rate_steps=5, ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? )
# Train the model, iterating on the data in batches of 32 samples model.fit(data, one_hot_labels, epochs=epochs, batch_size=batch_size, ? ? ? ? ? ? verbose=0, callbacks=[warm_up_lr])
import matplotlib.pyplot as plt plt.plot(warm_up_lr.learning_rates) plt.xlabel('Step', fontsize=20) plt.ylabel('lr', fontsize=20) plt.axis([0, total_steps, 0, learning_rate_base*1.1]) plt.xticks(np.arange(0, epochs, 1)) plt.grid() plt.title('Cosine decay with warmup', fontsize=20) plt.show()
运行结果:
参考博客: 称霸Kaggle的十大深度学习技巧 学习率规划-余弦退火CosineAnnealing和WarmRestart原理及实现 Warmup预热学习率 参考论文: https://arxiv.org/abs/1608.03983 ———————————————— 版权声明:本文为CSDN博主「Donreen」的原创文章,遵循CC 4.0 BY-SA版权协议,转载请附上原文出处链接及本声明。 原文链接:https://blog.csdn.net/CarryLvan/article/details/104394960
(二)深度学习:学习率规划-余弦退火CosineAnnealing和WarmRestart原理及实现
https://blog.csdn.net/qq_38290475/article/details/103548680
摘要:文献【1】中除了权重衰减还利用了余弦退火(Cosine Annealing)以及Warm Restart,本文介绍这两种方法的原理及numpy和Keras的实现方法,其中Keras实现中继承回调函数Callbacks。
目录: 原理介绍 Numpy直观实现 Keras实现 主要参考文献: 【1】DECOUPLED WEIGHT DECAY REGULARIZATION
1. 原理介绍 论文中对学习率规划原理描述如下,公式(5)表明学习率随迭代次数的变化规律。
变量名称解释如下:
各上标、下标i ii为run的序号,意思是第i ii次的restart。
η m i n i \eta^i_{min}η? min i ? ?以及η m a x i \eta^i_{max}η? max i ? ?为学习率的范围。
T c u r T_{cur}T? cur ? ?虽然写着是计算epoch的,但后面一句又说是随着iteration变化的。
T i T_iT? i ? ?是当前run总共的epoch数目。
对T i T_iT? i ? ?和T c u r T_{cur}T? cur ? ?,文中显示T c u r T_{cur}T? cur ? ?可以是离散的小数,这里进行如下修改便于实现:
T c u r T i = T c u r × n b a t c h e s T i × n b a t c h e s = i t e r a t i o n T o t a l I t e r a t i o n s \frac{T_{cur}}{T_i}=\frac{T_{cur} \times n_{batches}}{T_i \times n_{batches}}=\frac{iteration}{TotalIterations} T? i ? ? T? cur ? ? ? ?=? T? i ? ?×n? batches ? ? T? cur ? ?×n? batches ? ? ? ?=? TotalIterations iteration ? ?
这样就变成了当前的iteration数目的计量。
具体实现时,在训练过程中,轮到相应的epoch时重新设置优化器的T o t a l I t e r a t i o n s TotalIterationsTotalIterations并初始化T c u r T_{cur}T? cur ? ?。
下面先用numpy简单可视化一下这样操作后学习率的变化。实际使用中一般继承Tensorflow(Keras)或者Pytorch等框架自带的学习率规划类。
2. Numpy直观实现 利用Numpy可视化余弦退火和Warm Restart之后的学习率变化情况。
import numpy as np import matplotlib.pyplot as plt
def compute_eta_t(eta_min, eta_max, T_cur, Ti): ? ? '''Equation (5). ? ? # Arguments ? ? ? ? eta_min,eta_max,T_cur,Ti are same as equation. ? ? # Returns ? ? ? ? eta_t ? ? ''' ? ? pi = np.pi ? ? eta_t = eta_min + 0.5 * (eta_max - eta_min) * (np.cos(pi * T_cur / Ti) + 1) ? ? return eta_t
# 每Ti个epoch进行一次restart。 Ti = [20, 40, 80, 160] n_batches = 200 eta_ts = [] for ti in Ti: ? ? T_cur = np.arange(0, ti, 1 / n_batches) ? ? for t_cur in T_cur: ? ? ? ? eta_ts.append(compute_eta_t(0, 1, t_cur, ti)) ? ? ? ?? n_iterations = sum(Ti) * n_batches epoch = np.arange(0, n_iterations) / n_batches
plt.plot(epoch, eta_ts)
结果如下,和【1】的附录中给出的学习率变化基本相同。
3. Keras实现 下面进行余弦退火和warm restart的框架实现。
使用Keras框架,继承Callback实现余弦退火,warm restart可以手动循环实现。。。
首先定义余弦退火类,在每个batch结束后计算eta。
from keras import * class CosineAnnealing(callbacks.Callback): ? ? """Cosine annealing according to DECOUPLED WEIGHT DECAY REGULARIZATION.
? ? # Arguments ? ? ? ? eta_max: float, eta_max in eq(5). ? ? ? ? eta_min: float, eta_min in eq(5). ? ? ? ? total_iteration: int, Ti in eq(5). ? ? ? ? iteration: int, T_cur in eq(5). ? ? ? ? verbose: 0 or 1. ? ? """
? ? def __init__(self, eta_max=1, eta_min=0, total_iteration=0, iteration=0, verbose=0, **kwargs): ? ? ? ?? ? ? ? ? super(CosineAnnealing, self).__init__()
? ? ? ? global lr_list ? ? ? ?? ? ? ? ? lr_list = [] ? ? ? ? self.eta_max = eta_max ? ? ? ? self.eta_min = eta_min ? ? ? ? self.verbose = verbose ? ? ? ? self.total_iteration = total_iteration ? ? ? ? self.iteration = iteration ? ?? ? ? def on_train_begin(self, logs=None): ? ? ? ? self.lr = K.get_value(self.model.optimizer.lr) ? ? ? ?? ? ? def on_train_end(self, logs=None): ? ? ? ? K.set_value(self.model.optimizer.lr, self.lr) ? ? ? ??
? ? def on_batch_end(self, epoch, logs=None): ? ? ? ? self.iteration += 1 ? ? ? ? logs = logs or {} ? ? ? ? logs['lr'] = K.get_value(self.model.optimizer.lr) ? ? ? ?? ? ? ? ? eta_t = self.eta_min + (self.eta_max - self.eta_min) * 0.5 * (1 + np.cos(np.pi * self.iteration / self.total_iteration)) ? ? ? ? new_lr = self.lr * eta_t ? ? ? ? K.set_value(self.model.optimizer.lr, new_lr) ? ? ? ? if self.verbose > 0: ? ? ? ? ? ? print('\nEpoch %05d: CosineAnnealing ' ? ? ? ? ? ? ? ? ? 'learning rate to %s.' % (epoch + 1, new_lr)) ? ? ? ? lr_list.append(logs['lr'])
下面是数据及模型的创建。
import keras from keras import layers import matplotlib.pyplot as plt import numpy as np import tensorflow as tf
import keras.backend as K
# 准备数据集 num_train, num_test = 2000, 100 num_features = 200
true_w, true_b = np.ones((num_features, 1)) * 0.01, 0.05
features = np.random.normal(0, 1, (num_train + num_test, num_features)) noises = np.random.normal(0, 1, (num_train + num_test, 1)) * 0.01 labels = np.dot(features, true_w) + true_b + noises
train_data, test_data = features[:num_train, :], features[num_train:, :] train_labels, test_labels = labels[:num_train], labels[num_train:]
# 选择模型 model = keras.models.Sequential([ ? ? layers.Dense(units=128, activation='relu', input_dim=200),? ? ? layers.Dense(128, activation='relu', kernel_regularizer=keras.regularizers.l2(0.00)), ? ? layers.Dense(1) ])
model.summary() model.compile(optimizer='adam', ? ? ? ? ? ? ? loss='mse', ? ? ? ? ? ? ? metrics=['mse'])
最后是训练过程,在这里加上前面定义的回调函数,并手动实现WarmRestart。
epochs = [2, 4, 8, 16, 32] lr_lists = [] for Ti in epochs: ? ? reduce_lr = CosineAnnealing(eta_max=1, eta_min=0, total_iteration=Ti * (2000 // 16), iteration=0, verbose=0)
? ? hist1 = model.fit(train_data, train_labels, batch_size=16, epochs=Ti, validation_data=[test_data, test_labels], callbacks=[reduce_lr]) ? ?? ? ? lr_lists += lr_list ? ?? plt.plot(lr_lists)
最后学习率在训练过程中的变化如图所示。
———————————————— 版权声明:本文为CSDN博主「Ten_yn」的原创文章,遵循CC 4.0 BY-SA版权协议,转载请附上原文出处链接及本声明。 原文链接:https://blog.csdn.net/qq_38290475/article/details/103548680
|