上一个实验的损失函数是对称的圆碗,普通梯度下降就能走到底。这个实验换一个两个方向陡峭程度差别很大的函数,先观察普通梯度下降在什么情况下失效,再逐个引入动量、自适应步长和 Adam 三种改进方法,通过动手调参理解它们各自解决了什么问题。
本节要搞懂的问题:为什么不存在一个万能的学习率?做完这一节,你要能解释学习率太大和太小分别会出什么问题。
先读懂再动手。这个实验用的函数有一个特点:横穿山谷方向很陡,沿着山谷方向很平。对梯度下降来说,这意味着学习率太大时,每一步在陡的方向上位移太大,小球会在谷的两侧来回弹跳,最终飞出去(发散);学习率太小时,在平的方向上每一步几乎不动,走几百步也到不了最低点。两个方向的陡峭程度差别越大,能用的学习率范围就越窄。公式和实验一一样:新位置 = 旧位置 − 学习率 × 坡度,下面通过扫描不同的学习率来验证上面说的这些。
扫完之后你会发现,大部分学习率的结果都很差,不是慢得到不了最低点,就是发散。真正能用的只是一个很窄的区间。
用二分法找出这个区间的两个边界,记进实验报告:下界是再小就没法收敛的值,上界是再大就开始发散的值。滑块的精度够用,对半缩小范围十来次就能定位到小数点后五位。这个操作的意义在于:亲手确认在条件数大的地形上,可用的学习率范围确实很窄。
站在谷的左壁上,横向坡度很大,指向右下方。乘上学习率之后,这一步的横向位移很大,直接冲过谷底到右壁。到了右壁,横向坡度指向左下方,又冲回左壁。每一次弹跳只沿谷的方向前进了一点点,大部分位移浪费在了横向的来回运动上。
解决这个问题有两条思路。一是让来回弹的分量互相抵消,第 02 节的动量法走的是这条路。二是让陡的方向自动用小步子、平的方向自动用大步子,第 03 节的自适应步长走的是这条路。
| 学习率 | 最终 f | 结果 |
|---|
本节要搞懂的概念:动量系数。做完这一节,你要能解释动量为什么能减少震荡,以及动量系数太大会出什么问题。
先读懂再动手。上一节看到学习率的困境:调小走不动,调大来回弹。来回弹的根本原因是每一步只看当前坡度,完全不记得之前朝哪个方向在走。动量法的改进是:把前几步的运动方向记下来,和当前坡度一起决定下一步怎么走。这样做的效果是——横向来回弹的分量方向不断反转,累加之后正负相消,弹跳被压住;沿山谷前进的分量方向始终一致,累加之后越走越快,前进被加速。
动量系数控制"记忆力"的强弱:设为 0 就和普通梯度下降一样(完全不记忆),设为 0.9 表示每步保留九成历史速度(最常用的默认值)。系数越大惯性越强,但过大的话小球会冲过最低点。下面左图灰色是不带动量的,赭色是带动量的,两边学习率完全一样,通过调动量系数来观察轨迹变化。
速度 = 动量系数 × 上一步的速度 − 学习率 × 坡度横向的坡度一步向左、一步向右,方向不断反转。把这些相反的方向按比例累加起来,正负相消,横向的速度就被压住了。
沿着谷的方向坡度一直朝同一边,累加起来同号叠加,速度越走越快。所以动量的效果是双重的:既压住了无用的横向震荡,又加速了有用的纵向前进。
0.9 的意思是每走一步,之前累积的速度保留九成。往前推算,十步前那一步对现在的影响是 0.9 的十次方,大约三成五;三十步前只剩下百分之四。
所以动量法记住的是最近十几步的平均方向。系数越大,记得越久,惯性越强,也越容易冲过头。0.9 是工程上最常用的默认值,0.95 和 0.99 在训练很大的模型时也会用到。
| 动量系数 | 最终 f |
|---|
本节要搞懂的概念:自适应步长,以及 Adagrad 和 RMSprop 的区别。做完这一节,你要能解释 Adagrad 的步长为什么会"冻住"。
先读懂再动手。上一节的动量法解决了方向问题(弹跳抵消、前进加速),但还有一个问题没解决:同一个学习率管不了陡峭程度差很远的两个方向。陡的方向需要小学习率(不然弹出去),平的方向需要大学习率(不然走不动)——但学习率只有一个。
自适应步长的思路是:不再给所有方向共用一个学习率,而是让每个方向根据自己的历史坡度自动调。坡度一直很大的方向,步长自动缩小;坡度一直很小的方向,步长自动放大。具体怎么调?把历史坡度的平方累积起来,再开根号,用来除学习率(见下面的公式)。坡度越大,累积越大,除完之后步长越小。
问题出在"累积"这两个字上。Adagrad 把从第一步到当前的所有坡度平方全部加在一起,只涨不跌——如果开头几步坡度很大,累积量一下子冲到很高,后面几百步的小坡度根本加不动它,步长就被永久锁死在一个很小的值上。RMSprop 的改进是把"全部累加"换成"最近一段的加权平均",久远的大坡度会慢慢被遗忘,步长于是能随地形变化重新调整。下面通过三张图来验证这个差别。
新位置 = 旧位置 − 学习率 × 坡度 ÷ √(这个方向历史坡度的平方累积)意思是 Adagrad 的步长从那以后几乎不再变化,剩下的七百多步都用同一个步长在走。它名义上叫「自适应」,实际上只在开头那几十步做了一次适应。
两条赭线完全不同:它们一路往上爬,中途升到蓝线的四五倍,还会突然抖几下,然后回落。也就是说,RMSprop 的步长能随地形变化持续调整,Adagrad 的步长从头几十步之后就固定了。
Adagrad 把从第一步到现在的所有坡度平方都加进累积量。起点 (−1.5, 2) 那里 x 方向的坡度是 −155,平方之后是两万四。仅第一步就使累积量达到两万四。
接下来小球掉进谷底,坡度只剩零点几,平方之后是零点零几。往一个两万四的数上加零点零几,加八百次也才加了几十。所以那个累积量从第三步起就基本是个常数,步长跟着变成常数。
结论:Adagrad 的步长在开头几步就被起点附近的大梯度固定了。之后并没有发散,只是一直用同一个过小的步长在走,跟不上地形的变化。在实际训练中这个问题更突出,因为训练通常要跑几十万步。
RMSprop 只改了一件事:把「全部累加」换成「最近一段的加权平均」,久远的记录会按衰减系数慢慢被忘掉。开头那一下大坡度过几百步就失效了,步长于是能重新涨回来。把衰减系数往 0.99 拉,遗忘变慢,RMSprop 就越来越像 Adagrad,可以在图上验证这句话。
本节要搞懂的问题:Adam 是怎么把动量和自适应步长组合起来的?换一个起点后排名会不会变?
第 02 节的动量法解决了方向问题,第 03 节的 RMSprop 解决了步长问题。Adam 把这两个改进合并到一个算法里:既记住历史方向(动量),又根据各方向的梯度大小自动调步长(自适应)。下面让五个算法用各自的默认参数,从同一个起点出发做对比,看哪个走得最远、函数值降得最低。
| 算法 | 最终 f | 最终位置 | 头一次到 0.01 以下用了几步 |
|---|
Adam 是 Adaptive Moment Estimation 的缩写。它内部同时维护两样东西:一个是第 02 节那样的速度累积(用来消除来回弹),一个是第 03 节那样的梯度平方累积(用来自动调步长)。
课件第 2 章第 43 页那张表里的五行,就是这五个算法。
在这个实验里 Adam 一般名次很好,但这不代表它在所有任务上都最好。实际训练里带动量的普通下降在某些视觉任务上泛化得比 Adam 更好,这是长期有争议的一个话题。
更重要的一点是:换个起点,名次可能就变了。点图上不同的位置多试几次,你会发现排名并不稳定。所以选算法不能只看一次实验的名次。
本节要体会的过程:超参数调优。做完这一节,你要能说出"调参"和"随便试"的区别是什么。
上一节用的都是课本上的默认参数。默认参数是算法作者推荐的通用配置,不一定在当前任务上最优。本节的任务是手动调参:选一个算法,在固定步数预算内,调整学习率和附加参数,把函数值压到尽可能低。每次调参的结果都会记在榜上。通过有目的地反复试验,观察参数之间的配合关系。
| 算法 | 学习率 | 附加参数 | 最终 f |
|---|
动量法把学习率往大调,同时把动量往小调,两者在一定范围内可以互相换。
Adagrad 的学习率可以开得比别的算法大得多,因为它自己会往小缩。但也不是越大越好,往右拉到头看看。
Adam 的学习率往大调会怎样?它的默认 0.05 在这块地形上其实调得偏小。
Adam 的一阶动量默认 0.9,试试拉到 0.70。
能不能让普通梯度下降的成绩超过默认参数的 Adam?这件事是做得到的,方法在第 01 节。
前面各节的操作记录会自动汇总进报告。题目没有标准答案,写你自己看到的现象和想到的解释。
| 学习率 | 最终 f | 结果 |
|---|---|---|
| 未记录 | ||
| 算法 | 最终 f | 最终位置 | 到 0.01 用了几步 |
|---|---|---|---|
| 未比赛 | |||
| 算法 | 学习率 | 附加参数 | 最终 f |
|---|---|---|---|
| 未记录 | |||