关于机器学习的三个阶段
623
2022-05-30
华为ModelArts-Lab拓展实验记录(一)
在2019年7月至2019年12月期间,参加了华为云ModelArt训练营活动,在ModelArt平台上,做了一些AI实验,现在整理一下资料,把我主要做的几个拓展实验内容做了记录,方便以后查阅,且分享给那些对使用华为云ModelArt开发有兴趣的朋友。第一个拓展实验就是训练营第三期内容的拓展,关于衰减率和优化器的拓展实验。
1、概述
本次是华为ModelArts的拓展题目,主要是有两个内容:
1、自己定义学习率衰减规律。使用LearningRateScheduler方法,自己定义学习率衰减。
2、不同的优化器都有不一样的表现。可以尝试用更多的轮数对比不同的优化器,重新审视各个模型在训练各个阶段的表现。
所以,本次试验分为了两个部分组成,分别在华为ModelArts平台上分别进行,从最基础的概念做以下做介绍。
1.1 基础方法的概念简介
学习衰减率:在训练模型的时候,通常会遇到这种情况:我们平衡模型的训练速度和损失(loss)后选择了相对合适的学习率(learning rate),但是训练集的损失下降到一定的程度后就不在下降了。学习率衰减(Learning rate decay)的想法来自于,若以一个固定的学习率进行训练,很有可能在最优点附近跳动,所以,需要设计一个函数,让学习率随着训练次数(N of epoch)的增加而衰减,收敛梯度下降的学习步长。
早停法:为了防止深度学习神经网络在学习过程中的过拟合,当网络在训练集上表现越来越好,错误率越来越低的时候,实际上在某一刻,它在测试集的表现已经开始变差。我们设计了一种方法,当验证误差没有进一步改善时,算法就提前终止。这种策略被称作早停(early stopping)。
梯度下降优化算法:梯度下降算法(Gradient Descent Optimization)是神经网络模型训练最常用的优化算法,其原理是利用导数反映的是函数 f(x) 在 x轴上某一点处沿着 x轴正方向的变化率/变化趋势,利用偏导数推导,使得负梯度方向不断下降,逐步降低函数损失值,以此达到最优点。主要有批量梯度下降(Batch Gradient Descent,BGD)、随机梯度下降法(Stochastic Gradient Descent,SGD)和小批量梯度下降法(Mini-batch Gradient Descent,MBGD)。
模型检查点:保存模型并不限于在训练之后,在训练之中也需要保存,因为TensorFlow训练模型时难免会出现中断的情况。我们自然希望能够将辛苦得到的中间参数保留下来,否则下次又要重新开始。这种在训练中保存模型,习惯上称之为保存检查点(checkpoint)。
1.2 试验环境
本次试验主要是在ModelArts上使用Notebook上使用python 语言进行代码编辑和执行,主要使用了Keras库和框架。Keras是一个由Python编写的开源人工神经网络库,可以作为Tensorflow、Microsoft-CNTK和Theano的高阶应用程序接口,进行深度学习模型的设计、调试、评估、应用和可视化。在实验中,我参考了在线文档:https://keras-cn.readthedocs.io/en/latest/。
2、学习率衰减实验
实验在不同条件和环境下,各种方式下,设计不同的学习衰减率,模型的表现。
首先介绍 回调函数Callbacks,这个是keras 里面用来训练时候,回调函数是一组在训练的特定阶段被调用的函数集,通过传递回调函数列表到模型的.fit()中,即可在给定的训练阶段调用该函数集中的函数。Keras的回调函数是一个类,keras.callbacks.CallbackList(callbacks=[], queue_length=10)。keras.callbacks.Callback(),这个用法,我们用时候定义好 callbacks 即可。
重点是它的参数params:是字典,传进去的参数都是字典方式,所以无论是学习率、早停还是模型检查点,所以传进去的参数位置可以任意,不会有问题。这个地方是Python 语言特有的代码特色,要理解!!!
2.1 LearningRateScheduler 和ReduceLROnPlateau
这是两个容易混淆的函数,都是Keras里面的学习率的控制函数。
1、ReduceLROnPlateau 是一个动态监测的过程的学习率衰减函数,当当评价指标不在提升时,减少学习率,可以设置一大堆参数,进行控制。文档位置:https://keras-cn.readthedocs.io/en/latest/legacy/other/callbacks/#reducelronplateau。
2、LearningRateScheduler 是一个该回调函数是学习率调度器,它的参数是 schedule,它是个函数,该函数以epoch号为参数(从0算起的整数),返回一个新学习率(浮点数)。意思就是需要写一个函数,参数是epoch(学习轮数),返回值是一个学习率。文档要看https://m.w3cschool.cn/tensorflow_python/tf_keras_callbacks_LearningRateScheduler.html。keras中文档内的描述少了一个verbose:int;当其为0时,保持安静;当其为1时,表示更新消息。我们当然要设置为1。
两者区别么。。。ReduceLROnPlateau是被动执行,根据你参数监控的内容,如果达到要求,就执行你对学习率而设置衰减率。LearningRateScheduler是主动执行,每一轮的学习率都是你设置的返回值 。ReduceLROnPlateau是被动触发器,LearningRateScheduler是主动执行函数。本次实验主要采用LearningRateScheduler函数。
2.2 LearningRateScheduler的 使用方式
这种方式,主要是针对梯度下降的优化算法,梯度下降算法有两个重要的控制因子:一个是步长,由学习率控制;一个是方向,由梯度指定。 影响因素:
1)学习率设置太小,需要花费过多的时间来收敛
2)学习率设置较大,在最小值附近震荡却无法收敛到最小值
3)进入局部极值点就收敛,没有真正找到的最优解
4)停在鞍点处,不能够在另一维度继续下降
LearningRateScheduler的 使用让我们可以动态调整学习率,在不同的优化阶段能够动态改变学习率,以得到更好的结果。制定不同策略,编写各种函数,动态改变学习率。
LearningRateScheduler 函数返回的也是个字典类型数据,可以直接在callbacks里使用。
2.3 主要实验过程代码
es = EarlyStopping(monitor='val_acc', min_delta=0.001, patience=5, verbose=1, mode='auto') cp = ModelCheckpoint(filepath="./model/ckp_vgg16_dog_and_cat.h5", monitor="val_acc", verbose=1, save_best_only=True, mode="auto", period=1)
上面两行代码定义了 es(早停) 和 cp(检查点),参数可以查手册
这里的模式选择的是VGG16,VGG是一种卷积神经网络模型,可分为A,A-LRN,B,C,D,E共6个配置(ConvNet Configuration),其中以D,E两种配置较为常用,分别称为VGG16和VGG19。
epochs = 10 #总的循环次数 def step_decay(epoch): base_lrate = 0.1 #初始学习率 lrate = 0.0001 if mode is 'power_decay': decay_rate = 0.8 #衰减率 lrate = base_lrate * ((1 - math.float(epoch) / epochs) ** decay_rate) if mode is 'linear_decay': #线性衰减 decay_rate = 0.98 #衰减率 lrate = 1/(1 + decay_rate * epoch) * base_lrate if mode is 'exponent_decay': #指数衰减 decay_rate = 0.2 #衰减率 lrate = base_lrate * math.pow(decay_rate, (epoch + 1)/epochs) if mode is 'cos_decay': #余弦衰减 min_lrate = 0.001 lrate = 0.5 * (base_lrate - min_lrate) * (1 + math.cos( epoch/epochs * 3.14 )) return lratemode = 'linear_decay' lr = LearningRateScheduler(step_decay, 1)
lr 学习率,采用了LearningRateScheduler函数,设置参数。这里我实验了4种不同的衰减方式,并且写了衰减函数的代码。
以上是主要核心代码,全部代码链接地址。
2.4 LearningRateScheduler函数在不同策略下的表现
初始参数所得目标值与要求的最小值距离比较远,随着迭代次数增加,会越来越靠近最小值。学习率衰减的基本思想是学习率随着训练的进行逐渐衰减,即在开始的时候使用较大的学习率,加快靠近最小值的速度,在后来些时候用较小的学习率,提高稳定性,避免因学习率太大跳过最小值,保证能够收敛到最小值。
本次实验,训练轮数为10轮,目的就是为了验证LearningRateScheduler函数。
线性衰减:最常用的方式,学习率随着在每迭代 stepsize 次后减少 一定的量,随着不断的训练,不断重复该过程,学习率衰减的规律呈现线性分布。
最后10轮后,准确率0.7左右,损失率0.5左右,整个过程很线性啊!
指数衰减: 一种更加灵活的学习率设置方法,学习率呈现指数衰减,可以先用一个较大的学习率来快速得到一个比较优的解,然后随着迭代的继续逐步减少学习率,使模型在训练后期更加稳定。最后10轮后,准确率0.75左右,损失率0.52左右,整个过程中间的拟合状态不是很好,有较大的锯齿波形。
余弦衰减:余弦退火可以当做是学习率衰减的一种方式,在采用小批量随机梯度下降(MBGD/SGD)算法时,神经网络应该越来越接近 Loss 值的全局最小值。当它逐渐接近这个最小值时,学习率应该变得更小来使得模型不会超调且尽可能接近这一点。余弦退火利用余弦函数来降低学习率,进而解决这个问题,我们也同样训练了10轮,最后结果,准确率 0.61 左右,损失率0.6 左右,并不太好。。。其实还是训练轮数不够的原因。
3、不同优化器,在各个模型在训练各个阶段的表现
为了更加直观反映问题,模型统一采用 VGG16,没有早停和模型检查点。
3.1 SGD优化器
SGD指stochastic gradient descent,即随机梯度下降。是梯度下降的batch版本。文档见:https://keras-cn.readthedocs.io/en/latest/legacy/other/optimizers/#sgd。
SGD就是每一次迭代计算mini-batch的梯度,然后对参数进行更新,是最常见的优化方法了。
1、原始SGD优化器,这个就是最基础的,什么优化策略都没有设置,裸奔。原始模式 36 轮训练。结果准确率0.75左右,损失率0.5左右。主要代码如下:
epochs = 36opt = keras.optimizers.SGD(lr=0.01, momentum=0.0, decay=0.0, nesterov=False) model.compile(loss='categorical_crossentropy',optimizer=opt,metrics=['accuracy'])
2、带优化的SGD优化器,在以上基础上做了优化:增加了动量 momentum项,能够在相关方向加速SGD,抑制振荡,从而加快收敛。增加了nesterov项,能在梯度更新时做一个校正,避免前进太快,同时提高灵敏度。设置了decay,每次更新后的学习率衰减值。也是36轮,这样下来结果好很多了,准确率0.93左右,损失率0.2左右。主要代码如下:
epochs = 36opt = keras.optimizers.SGD(lr=0.01, momentum=0.0, decay=0.0, nesterov=False) model.compile(loss='categorical_crossentropy',optimizer=opt,metrics=['accuracy'])
3、调小学习率的带优化的SGD,在以上基础上,调小了学习率。也是36轮,这样下来结果就很惨淡,准确率0.61左右,损失率0.6左右,SGD对学习率的初始设置还是非常重要的。主要代码如下:
epochs = 36opt = keras.optimizers.SGD(lr=0.0001, decay=1e-6, momentum=0.9, nesterov=True) model.compile(loss='categorical_crossentropy',optimizer=opt,metrics=['accuracy'])
3.2 优化器 RMSprop
RMSProp 算法旨在抑制梯度的锯齿下降,但与动量相比, RMSProp 不需要手动配置学习率超参数,由算法自动完成。 更重要的是,RMSProp 可以为每个参数选择不同的学习率。这个算法可以对低频的参数做较大的更新,对高频的参数做较小的更新,一种自适应的算法,解决了梯度急速下降的问题。Adadelta 、Adagrad和RMSprop都算同一类的。这里用RMSprop做代表,做了36轮训练。结果不错,准确率0.93左右,损失率0.2左右。主要代码如下:
epochs = 36opt = keras.optimizers.RMSprop(lr=0.0001, rho=0.9, epsilon=1e-06, decay=1e-6) model.compile(loss='categorical_crossentropy',optimizer=opt,metrics=['accuracy'])
3.3 优化器 Adam
Adam(Adaptive Moment Estimation)本质上是带有动量项的RMSprop,它利用梯度的一阶矩估计和二阶矩估计动态调整每个参数的学习率。Adam的优点主要在于经过偏置校正后,每一次迭代学习率都有个确定范围,使得参数比较平稳。Adam 是一种可以替代传统随机梯度下降过程的一阶优化算法,它能基于训练数据迭代地更新神经网络权重,被广泛使用。。。还有个3e-4的流程,它就是最流行的,也是个自动的算法。这里也同样进行了36轮的算法。结果满意,准确率0.94左右,损失率0.2左右。主要代码如下:
epochs = 36opt = keras.optimizers.Adam(lr=0.0001, decay=1e-6) model.compile(loss='categorical_crossentropy',optimizer=opt,metrics=['accuracy'])
3.4 优化器SGD的主动策略
以上,自适应的Adam和RMSProp都表现不错,尽管这些自适应算法有助于我们在复杂的损失函数上找到极小值点,但这还不够。为了更好的达到目标,往往采取原始的SGD 加上人工策略的做法,这里都用到了LearningRateScheduler函数。
SGDR:性能良好的旧版热重启 SGD,在训练时,梯度下降算法可能陷入局部最小值,而不是全局最小值。梯度下降算法可以通过突然提高学习率,来 “跳出” 局部最小值并找到通向全局最小值的路径。这种方法将余弦退火与热重启相结合,使用余弦函数作为周期函数,并在每个周期最大值时重新开始学习速率。“热重启” 是因为学习率重新开始时并不是从头开始的,而是由模型在最后一步收敛的参数决定的。可以看一下这张示意图(来源于网上):
这里,写了个函数,40轮,每10轮我重启一次,开始一次新的余弦函数,计算学习率。准确率0.76左右,损失率0.5左右,原因很简单,我的训练轮数不够了,一般都要150轮到200轮。不过,可以在代码中看到,图的趋势一直都是不断改进,不断提升准确率和降低损失率。网上的资料,论文显示在150轮,这样的方式可以达到和Adam一样的水平。代码如下:
epochs = 40loop = 10def step_decay(epoch): lrate = 0.001 base_lrate = 0.01 #初始学习率 min_lrate = 0.0001 lv = epoch // loop epoch = epoch - loop * lv lrate = 0.5 * (base_lrate - min_lrate) * (1 + math.cos( epoch/loop * 3.14 )) return lrateopt = keras.optimizers.SGD(lr=0.0001, momentum=0.0, decay=0.0, nesterov=False)model.compile(loss='categorical_crossentropy',optimizer=opt,metrics=['accuracy'])lr = LearningRateScheduler(step_decay, 1) callbacks = [lr]
重点来了,我想效果好,又想训练少,怎么办,找了这个办法。首先,有一种名为 LR Range Test 的技术。这个方法很简单,你只需将模型和数据迭代几次,把学习率初始值设置得比较小,然后在每次迭代后增加。你需要记录学习率的每次损失并将它画出。这里我写了个学习率的函数,跑了36轮,从最小学习率 1e-7 到最大学习率1,做了覆盖和测试,代码如下:
#sdg 最大学习率测试loop = 5epochs = 36def step_test(epoch): lrate = 0.001 start_lrate = 1e-7 #初始学习率 lv = epoch // loop epoch = epoch%loop lrate = math.pow(10, lv) * start_lrate + math.pow(10, lv) * start_lrate * 1.6 * epoch return lrateopt = keras.optimizers.SGD(lr=0.0001, momentum=0.0, decay=0.0, nesterov=False)model.compile(loss='categorical_crossentropy',optimizer=opt,metrics=['accuracy'])lr = LearningRateScheduler(step_test, 1) callbacks = [lr]
结果如上图。
我在自己训练结果划分了三个区域,针对学习率分别是过小,适合和过大。在适合区域中,选取了损失率最大值在31轮,31轮的学习率是0.1,就是我为了下面训练选取的最大学习率。原理如下图(来源于网上):
一周期策略:循环学习率策略仅包含一个周期,因此称作「一周期」策略。在一周期策略中,最大学习率被设置为 LR Range test 中可以找到的最高值,最小学习率比最大学习率小几个数量级。最大值已经找到就是0.1 ,最小值选取它的1/10,也在适合区域内。这里我也写了个自定义的学习率函数,一个40轮,18轮是从低到高,18轮是从高至低,剩下4轮设置为略小于训练周期的总数,这样循环结束后有残余时间降低学习率,从而帮助模型稳定下来。这里我略作修改,下降的18轮没有走线性,而是余弦方式,最后4轮是指数下降。最后的代码如下:
#sdg 优化器 手动速率epochs = 40loop = 18def step_decay(epoch): lrate = 0.01 max_lrate = 0.1 #初始学习率 min_lrate = 0.01 lv = epoch // loop if lv is 0: epoch = epoch + 1 lrate = max_lrate / loop * epoch if lv is 1: epoch = epoch - loop * lv lrate = 0.5 * (max_lrate - min_lrate / 2 ) * ( 1.2 + math.cos( epoch/loop * 3.14 )) if lv is 2: drop = 0.5 epoch = epoch - loop * lv lrate = min_lrate * math.pow(drop, (1 + epoch)/4) return lrateopt = keras.optimizers.SGD(lr=0.0001, momentum=0.0, decay=0.0, nesterov=False)model.compile(loss='categorical_crossentropy',optimizer=opt,metrics=['accuracy'])lr = LearningRateScheduler(step_decay, 1) callbacks = [lr]
最后结果:准确率0.93左右,损失率0.2左右。其实,还可以进一步优化,作为实验,结果已经可以接受。
3.5 结论
一般情况下,使用「自适应方法」的Adam已经普遍适用了。RMSProp和Adam这种自适应算法已经很好了,但是以SGD和人工学习率策略结合的做法依然很重要,比自适应算法更有潜力。梯度下降是机器学习和深度学习中非常重要的优化算法,而学习率是用好梯度下降法的关键。除了一些其他客观的原因,学习率的设定是影响模型性能好坏的非常重要的因素,所以应该给予足够的重视。
人工智能
版权声明:本文内容由网络用户投稿,版权归原作者所有,本站不拥有其著作权,亦不承担相应法律责任。如果您发现本站中有涉嫌抄袭或描述失实的内容,请联系我们jiasou666@gmail.com 处理,核实后本网站将在24小时内删除侵权内容。