[译]如何找到一个好的学习率(learning rate)

[译]如何找到一个好的学习率(learning rate)

转译自How Do You Find A Good Learning Rate 根据自己的阅读理解习惯,对行文逻辑进行了一定的整理。

在调参过程中,选择一个合适的学习率至关重要,就跟爬山一样,反向传播的过程可以类比于爬山的过程,而学习率可以类比为是步长,步子迈太小,可能永远也爬不到山顶,步子迈太大,可能山顶一下就被迈了过去,甚至极端情况,步子迈极其大的话,有可能一脚迈到天上去了(loss增大到nan)。

这里拿一个抛物线的例子来说明,我么的任务是找到抛物线的最低点,现在从 a_{0 } 开始, 由于步子太大(学习率过高),一步直接跨过最优点直接到达 a_{1} ,由于 y(a_{1}) > y(a_{0}) , 直接导致了loss的增大,所以最好的学习率要做到,让模型收敛地最快,但又恰好不会导致步子迈太大这种情况的发生。

图一 大lr的坏处

那怎么找一个最好的学习率呢?以中医理论为指导思想的调参师们的一般方法是不断的尝试,改个数,跑一遍,再改一个,再跑一遍,经验科学嘛,伪科学!在fastai框架中,本文作者提出了一种新的伪科学方法,那就是,在一次从头开始的训练过程的第一次epoch中,在每一个mini-batch的训练上从小到大不断增大学习率,记录随着学习率的变化而产生的loss值变化,最后绘制出一个变化曲线,如图二:

图二 随着学习率增长而产生的loss变化

随着训练的不断深入,我们看到loss逐渐下降,并在一段特定的学习率区间内产生了剧烈的下降,而到最后,由于学习率增长到实在太大了,所以loss开始增大了。

所以从这张图中,我们该选择哪个点作为最合适的学习率呢?图中的最低点吗?不是,因为这时候其实loss已经呈现出了loss爆炸的趋势了,所以 10^{-2} 要比 10^{-1} 更适合作为最佳学习率,因为我们需要一个既能保证学习的速度,又能防止产生loss爆炸情况的学习率,所以 10^{-2} 要更适合,可能有人要问为什么不选曲线下降梯度最大的地方作为学习率,要知道此处loss快速下降是模型因为经过不断训练快速拟合到数据上的结果,而不是说loss在这一学习率上下降最快,要了解这张图的意义,我们要找的最佳点是能保证最快速拟合,又不至于发生loss爆炸情况发生的点,所以还是需要一点伪科学的知识,去主观判断这样一个临界点,只不过这样一张图给了我们一定的参考,也就是不那么伪了。(能精确判断这种临界点的方法自然是最好的,大家可以研究一下!)

查找最佳学习率的fast.ai实现

在fast.ai中,要绘制出图二中的这种图很简单,可以看一下fastai的基础教程,将数据和模型封装成learner之后,只要调用两句:

learner.lr_find()
learner.sched.plot()

我们来看一下实现的细节。

首先是要确定x坐标轴,即lr的取值。fastai默认lr取在1e-8和10之间,即lr从1e-8到10逐渐增大。在实践中也可以发现,确定lr更重要的是确定量级,如1e-3和1e-2,由于同一量级的不同数值对结果影响并不大,所以加法增大意义不大,故增大的过程采用了指数级增大的方式:

lr_{i} = lr_{0} \times q^{i}

假设一个epoch中共有N个mini-batch, 那么就可以确定指数增长q的步长:

lr_{N-1}=lr_{0} \times q^{N-1} \Leftrightarrow q^{N-1} = \frac{lr_{N-1}}{lr_{0}} \Leftrightarrow q=(\frac{lr_{N-1}}{lr_{0}})^{\frac{1}{N-1}}

( lr_{0}默认取值1e^{-8}, lr_{N-1}默认10 )


其次作者对损失曲线图像进行了一定的平滑处理,因为如果不进行平滑处理的话,由于mini-batch的不稳定性,曲线图象一般是这样的:

图三 未平滑的曲线图像

所以从这种图像里,即使能大体把握住loss的变化趋势,但由于曲线震荡的原因,也很难对我们寻找最佳学习率做出很好的参考。所以作者采用了以下的方式对曲线上的每一个点进行了平滑处理:

avg\ loss_{i} = \beta * old\ avg\ loss_{i-1} + (1-\beta)*loss_{i}

smoothed\ loss_{i} = \frac{avg\ loss_{i}}{1-\beta^{i+1}}

对于每一个mini-batch上的 loss_{i} ,对它进行的平滑操作就是,首先使用一个取值在0-1之间的系数 \beta 进行加权平均计算,将其与之前的一个加权平均后的 old\ avg\ loss_{i-1} 做一个加权平均得到当前的加权平均值 avg\ loss_{i} ,而后除以一个平滑系数 1- \beta^{i+1} ,就得到了平滑后的 smoothed\ loss_{i} 。这样利用平滑后的loss进行曲线绘制,我们就能得到一个如图二的loss曲线图象,从这张图里,loss的变化趋势就显得十分清晰了,对我们寻找最佳学习率的点,就有一定的帮助了。

最后,作者为了避免造成不必要的资源浪费,选择了在loss爆炸到一个临界点的时候提前终止数据采集,这个临界点是:

current\ smoothed\ loss > 4 \times minimum\ smoothed\ loss

综上,体现在程序上就是:

def find_lr(init_value = 1e-8, final_value=10., beta = 0.98):
    num = len(trn_loader)-1
    mult = (final_value / init_value) ** (1/num)
    lr = init_value
    optimizer.param_groups[0]['lr'] = lr
    avg_loss = 0.
    best_loss = 0.
    batch_num = 0
    losses = []
    log_lrs = []
    for data in trn_loader:
        batch_num += 1
        #As before, get the loss for this mini-batch of inputs/outputs
        inputs,labels = data
        inputs, labels = Variable(inputs), Variable(labels)
        optimizer.zero_grad()
        outputs = net(inputs)
        loss = criterion(outputs, labels)
        #Compute the smoothed loss
        avg_loss = beta * avg_loss + (1-beta) *loss.data[0]
        smoothed_loss = avg_loss / (1 - beta**batch_num)
        #Stop if the loss is exploding
        if batch_num > 1 and smoothed_loss > 4 * best_loss:
            return log_lrs, losses
        #Record the best loss
        if smoothed_loss < best_loss or batch_num==1:
            best_loss = smoothed_loss
        #Store the values
        losses.append(smoothed_loss)
        log_lrs.append(math.log10(lr))
        #Do the SGD step
        loss.backward()
        optimizer.step()
        #Update the lr for the next step
        lr *= mult
        optimizer.param_groups[0]['lr'] = lr
    return log_lrs, losses

随后根据log_lrs, losses绘图就好了。

虽说也是伪科学确定最佳学习率,但好歹也有了一定的科学性,毕竟最好的学习率就是即能保证最快收敛,又能保证不会出现跨过最优点。能设计一个算法科学地找到最好的学习率当然是最好的,加油吧少年们!

附平滑系数的确定,很简单的一个无穷多项式加法,高中知识:

编辑于 2018-11-22

文章被以下专栏收录