2.3 过拟合
为什么会有过拟合这样的情况呢?举一个极端的例子,这是训练集。假设根据这些训练 集,某一个很废的机器学习的方法找出了一个一无是处的函数。这个一无是处的函数,只要 输入 x 有出现在训练集里面,就把它对应的 y 当做输出。如果 x 没有出现在训练集里面,就 输出一个随机的值。这个函数啥事也没有干,其是一个一无是处的函数,但它在训练数据上 的损失是 0。把训练数据通通丢进这个函数里面, 它的输出跟训练集的标签是一模一样的,所 以在训练数据上面,这个函数的损失可是 0 呢,可是在测试数据上面,它的损失会变得很大, 因为它其实什么都没有预测,这是一个比较极端的例子,在一般的情况下,也有可能发生类似 的事情。
如图 2.6 所示,举例来说,假设输入的特征为 x,输出为 y,x 和 y 都是一维的。x 和 y 之间的关系是 2 次的曲线,曲线用虚线来表示,因为通常没有办法,直接观察到这条曲线。我 们真正可以观察到的是训练集,训练集可以想像成从这条曲线上面,随机采样出来的几个点。 模型的能力非常的强,其灵活性很大,只给它这 3 个点。在这 3 个点上面,要让损失低,所 以模型的这个曲线会通过这 3 个点,但是其它没有训练集做为限制的地方,因为它的灵活性 很大,它灵活性很大,所以模型可以变成各式各样的函数,没有给它数据做为训练,可以产生 各式各样奇怪的结果。
图 2.6 模型灵活导致的问题
如果再丢进测试数据,测试数据和训练数据,当然不会一模一样,它们可能是从同一个 分布采样出来的,测试数据是橙色的点,训练数据是蓝色的点。用蓝色的点, 找出一个函数以 后,测试在橘色的点上,不一定会好。如果模型它的自由度很大的话, 它可以产生非常奇怪的曲线,导致训练集上的结果好,但是测试集上的损失很大。 怎么解决过拟合的问题呢,有两个可能的方向:
第一个方向是往往是最有效的方向,即增加训练集。因此如果训练集,蓝色的点变多了, 虽然模型它的灵活性可能很大,但是因为点非常多,它就可以限制住,它看起来的形状还是会 很像,产生这些数据背后的 2 次曲线,如图 2.7所示。可以做数据增强(data augmentation, ) ,这个方法并不算是使用了额外的数据。
图 2.7 增加数据
数据增强就是根据问题的理解创造出新的数据。举个例子, 在做图像识别的时候,常做的 一个招式是,假设训练集里面有某一张图片,把它左右翻转,或者是把它其中一块截出来放大 等等。对图片进行左右翻转, 数据就变成两倍。但是数据增强不能够随便乱做。在图像识别里 面,很少看到有人把图像上下颠倒当作增强。因为这些图片都是合理的图片,左右翻转图片, 并不会影响到里面的内容。但把图像上下颠倒,可能不是一个训练集或真实世界里面会出现 的图像。如果给机器根据奇怪的图像学习, 它可能就会学到奇怪的东西。所以数据增强, 要根 据对数据的特性以及要处理的问题的理解,来选择合适的数据增强的方式。
另外一个解法是给模型一些限制,让模型不要有过大的灵活性。假设 x 跟 y 背后的关系 其实就是一条 2 次曲线,只是该 2 次曲线里面的参数是未知的。如图 2.8 所示,要用多限制 的模型才会好取决于对这个问题的理解。因为这种模型是自己设计的, 设计出不同的模型,结 果不同。假设模型是 2 次曲线,在选择函数的时候有很大的限制,因为 2 次曲线要就是这样 子,来来去去就是几个形状而已。所以当训练集有限的时候, 来来去去只能够选几个函数。所 以虽然说只给了 3 个点,但是因为能选择的函数有限,可能就会正好选到跟真正的分布比较 接近的函数,在测试集上得到比较好的结果。
解决过拟合的问题,要给模型一些限制,最好模型正好跟背后产生数据的过程,过程是一 样的就有机会得到好的结果。给模型制造限制可以有如下方法:
• 给模型比较少的参数。如果是深度学习的话,就给它比较少的神经元的数量,本来每层 一千个神经元,改成一百个神经元之类的,或者让模型共用参数,可以让一些参数有一 样的数值。全连接网络(fully-connected network)其实是一个比较有灵活性的架构, 而卷积神经网络(Convolutional Neural Network,CNN)是一个比较有限制的架 构。CNN 是一种比较没有灵活性的模型,其是针对图像的特性来限制模型的灵活性。所 以全连接神经网络,可以找出来的函数所形成的集合其实是比较大的,CNN 所找出来 的函数,它形成的集合其实是比较小的,其实包含在全连接网络里面的,但是就是因为 CNN 给了,比较大的限制,所以 CNN 在图像上,反而会做得比较好,这个之后都还会再提到,用比较少的特征,本来给 3 天的数据,改成用给两天的数据,其实结果就好了一些。
• 还有别的方法,比如早停(early stopping)、正则化(regularization)和丢弃法(dropout method)。
图 2.8 对模型增加限制
但也不要给太多的限制。假设模型是线性的模型,图 2.9 中有 3 个点,没有任何一条直 线可以同时通过这 3 个点。只能找到一条直线,这条直线跟这些点比起来,它们的距离是比 较近的。这个时候模型的限制就太大了, 在测试集上就不会得到好的结果。这种情况下的结果 不好,并不是因为过拟合了,而是因为给模型太大的限制,大到有了模型偏差的问题。
这边产生了一个矛盾的情况,模型的复杂程度,或这样让模型的灵活性越来越大。但复杂 的程度和灵活性都没有给明确的定义。比较复杂的模型包含的函数比较多,参数比较多。如 图 2.10 所示,随着模型越来越复杂,训练损失可以越来越低,但测试时,当模型越来越复杂 的时候,刚开始,测试损失会跟著下降,但是当复杂的程度,超过某一个程度以后,测试损失 就会突然暴增了。这就是因为当模型越来越复杂的时候, 复杂到某一个程度,过拟合的情况就 会出现,所以在训练损失上面可以得到比较好的结果。在测试损失上面,会得到比较大的损 失,可以选一个中庸的模型,不是太复杂的,也不是太简单的,刚刚好可以在训练集上损失最 低,测试损失最低。
假设 3 个模型的复杂的程度不太一样,不知道要选哪一个模型才会刚刚好,在测试集上得到最好的结果。因为选太复杂的就过拟合,选太简单的有模型偏差的问题。把这 3 个模型 的结果都跑出来,上传到 Kaggle 上面,损失最低的模型显然就是最好的模型,但是不建议这 么做。举个极端的例子, 假设有 1 到 1012 个模型,这些模型学习出来的函数都是一无是处的 函数。它们会做的事情就是,训练集里面有的数据就把它记下来,训练集没看过的,就直接 输出随机的结果。把这 1012 个模型的结果,通通上传到 Kaggle 上面,得到 1012 个分数,这 1012 的分数里面,结果最好的,模型也是最好的。
虽然每一个模型没看过测试数据,其输出的结果都是随机的,但不断随机,总是会找到一个好的结果。因此也许某个模型找出来的函数, 正好在测试数据上面的结果比较好,选这一个模型当作最后上传的结果,当作最后要用在私人测试集上的结果。该模型是随机的, 它恰好在 公开的测试数据上面得到一个好结果,但是它在私人测试集上可能仍然是随机的。测试集分 成公开的数据集跟私人的数据集,公开的分数可以看到,私人的分数要截止日期以后才知道。 如果根据公开数据集来选择模型,可能会出现这种情况:在公开的排行榜上面排前十,但是截 止日期一结束,可能掉到 300 名之外。因为计算分数的时候, 会同时考虑公开和私人的分数。
图 2.9 限制太大会导致模型偏差
Q:为什么要把测试集分成公开和私人?
A:假设所有的数据都是公开,就算是一个一无是处的模型,它也有可能在公开的数据 上面得到好的结果。如果只有公开的测试集, 没有私人测试集,写一个程序不断随机产 生输出就好,不断把随机的输出上传到 Kaggle,可以随机出一个好的结果。这个显然 没有意义。而且如果公开的测试数据是公开的, 公开的测试数据的结果是已知的,一个 很废的模型也可能得到非常好的结果。不要用公开的测试集调模型,因为可能会在私 人测试集上面得到很差的结果,不过因为在公开测试集上面的好的结果也有算分数。
2.4 交叉验证
比较合理选择模型的方法是把训练的数据分成两半,一部分称为训练集(training set), 一部分是验证集(validation set)。比如 90% 的数据作为训练集,有 10% 的数据作为验证 集。在训练集上训练出来的模型会使用验证集来衡量它们的分数,根据验证集上面的分数去 挑选结果,再把这个结果上传到 Kaggle 上面得到的公开分数。在挑分数的时候, 是用验证集 来挑模型,所以公开测试集分数就可以反映私人测试集的分数。但假设这个循环做太多次, 根 据公开测试集上的结果调整模型太多次,就又有可能在公开测试集上面过拟合,在私人测试 集上面得到差的结果。不过上传的次数有限制, 所以无法走太多次循环,可以避免在公开的测试集上面的结果过拟合。根据过去的经验, 就在公开排行榜上排前几名的,往往私人测试集很容易就不好。
其实最好的做法,就是用验证损失,最小的直接挑就好了,不要管公开测试集的结果。在 实现上,不太可能这么做,因为公开数据集的结果对模型的选择,可能还是会有些影响的。理 想上就用验证集挑就好,有过比较好的基线(baseline)算法以后,就不要再去动它了,就可 以避免在测试集上面过拟合。但是这边会有一个问题,如果随机分验证集,可能会分得不好, 分到很奇怪的验证集,会导致结果很差,如果有这个担心的话,可以用 k 折交叉验证(k-fold cross validation),如图 2.11 所示。k 折交叉验证就是先把训练集切成 k 等份。在这个例子, 训练集被切成 3 等份,切完以后,拿其中一份当作验证集,另外两份当训练集,这件事情要 重复 3 次。即第一份第 2 份当训练,第 3 份当验证;第一份第 3 份当训练,第 2 份当验证; 第一份当验证,第 2 份第 3 份当训练。
图 2.11 k 折交叉验证
接下来有 3 个模型,不知道哪一个是好的。把这 3 个模型,在这 3 个设置下,在这 3 个 训练跟验证的数据集上面,通通跑过一次,把这 3 个模型,在这 3 种情况的结果都平均起来,把每一个模型在这 3 种情况的结果,都平均起来,再看看谁的结果最好假设现在模型 1 的结 果最好,3 折交叉验证得出来的结果是,模型 1 最好。再把模型 1 用在全部的训练集上,训 练出来的模型再用在测试集上面。接下来也许我们要问的一个问题是,讲到预测 2 月 26 日, 也就是上周五的观看人数的结果如图 2.12 所示。所以把 3 层的网络,拿来测试一下是测试的结果。
图 2.12 3 层网络的结果最好
2.5 不匹配
图 2.13 中横轴就是从 2021 年的 1 月 1 号开始一直往下,红色的线是真实的数字,蓝色 的线是预测的结果。2 月 26 日是 2021 年观看人数最高的一天了,机器的预测差距非常的大, 差距有 2580,所以这一天是 2021 年观看人数最多的一天。跑了一层 2 层跟四层的看看,所有 的模型的结果都不好,两层跟 3 层的错误率都是 2 千多,其实四层跟一层比较好,都是 1800 左右,但是这四个模型不约而同的,觉得 2 月 26 日应该是个低点,但实际上 2 月 26 日是一 个峰值,模型其实会觉得它是一个低点,也不能怪它,因为根据过去的数据,周五晚上大家都 出去玩了。但是 2 月 26 日出现了反常的情况。这种情况应该算是另外一种错误的形式,这种 错误的形式称为不匹配(mismatch)。
图 2.13 另一种错误形式:不匹配
不匹配跟过拟合其实不同,一般的过拟合可以用搜集更多的数据来克服,但是不匹配是 指训练集跟测试集的分布不同,训练集再增加其实也没有帮助了。假设数据在分训练集跟测 试集的时候,使用 2020 年的数据作为训练集,使用 2021 年的数据作为测试集,不匹配的问 题可能就很严重。如果今天用 2020 年当训练集,2021 年当测试集,根本预测不准。因为 2020年的数据跟 2021 年的数据背后的分布不同。图 2.14 是图像分类中的不匹配问题。增加数据 也不能让模型做得更好,所以这种问题要怎么解决,匹不匹配要看对数据本身的理解了,我们 可能要对训练集跟测试集的产生方式有一些理解,才能判断它是不是遇到了不匹配的情况。
图 2.14 图像分类的不匹配问题
第 3 章 深度学习基础
本章介绍了深度学习常见的概念,理解这些概念能够帮助我们从不同角度来更好地优化 神经网络。要想更好地优化神经网络, 首先,要理解为什么优化会失败,收敛在局部极限值与 鞍点会导致优化失败。其次,可以对学习率进行调整,使用自适应学习率和学习率调度。最 后,批量归一化可以改变误差表面,这对优化也有帮助。
我们在做优化的时候经常会发现,随着参数不断更新,训练的损失不会再下降, 但是我们 对这个损失仍然不满意。把深层网络(deep network)、线性模型和浅层网络(shallow network) 做比较,可以发现深层网络没有做得更好——深层网络没有发挥出它完整的力量,所以优化 是有问题的。但有时候,模型一开始就训练不起来,不管我们怎么更新参数,损失都降不下
3.1.1 临界点及其种类
过去常见的一个猜想是我们优化到某个地方,这个地方参数对损失的微分为零,如图 3.1 所示。图 3.1 中的两条曲线对应两个神经网络训练的过程。当参数对损失微分为零的时候, 梯 度下降就不能再更新参数了,训练就停下来了,损失不再下降了。
图 3.1 梯度下降失效的情况
提到梯度为零的时候,大家最先想到的可能就是局部极小值(local minimum) ,如 图 3.2a 所示。所以经常有人说,做深度学习时使用梯度下降会收敛在局部极小值,梯度下降 不起作用。但其实损失不是只在局部极小值的梯度是零, 还有其他可能会让梯度是零的点,比 如鞍点(saddle point)。鞍点其实就是梯度是零且区别于局部极小值和局部极大值(local maximum)的点。图 3.2b 红色的点在 y 轴方向是比较高的,在 x 轴方向是比较低的,这就 是一个鞍点。鞍点的叫法是因为其形状像马鞍。鞍点的梯度为零, 但它不是局部极小值。我们 把梯度为零的点统称为临界点(critical point)。损失没有办法再下降, 也许是因为收敛在了 临界点,但不一定收敛在局部极小值,因为鞍点也是梯度为零的点。
但是如果一个点的梯度真的很接近零,我们走到临界点的时候,这个临界点到底是局部 极小值还是鞍点,是一个值得去探讨的问题。因为如果损失收敛在局部极小值, 我们所在的位 置已经是损失最低的点了,往四周走损失都会比较高,就没有路可以走了。但鞍点没有这个问 题,旁边还是有路可以让损失更低的。只要逃离鞍点,就有可能让损失更低。
图 3.2 局部极小值与鞍点
3.1.2 判断临界值种类的方法
判断一个临界点到底是局部极小值还是鞍点需要知道损失函数的形状。可是怎么知道损 失函数的形状?网络本身很复杂,用复杂网络算出来的损失函数显然也很复杂。虽然无法完整 知道整个损失函数的样子,但是如果给定某一组参数,比如 θ′ , 在 θ′ 附近的损失函数是有办 法写出来的——虽然 L(θ) 完整的样子写不出来。θ′ 附近的 L(θ) 可近似为
式 (3.1) 是泰勒级数近似(Tayler series appoximation)。其中,第一项 L(θ)′ 告诉我们, 当 θ 跟 θ′ 很近的时候,L(θ) 应该跟 L(θ′ ) 还蛮靠近的;第二项 (θ − θ′ )T g 中,g 代表梯度, 它是一个向量,可以弥补 L(θ′ ) 跟 L(θ) 之间的差距。有时候梯度 g 会写成 ▽L(θ′ )。gi 是向 量 g 的第 i 个元素,就是 L 关于 θ 的第 i 个元素的微分,即
(3.2)
光看 g 还是没有办法完整地描述 L(θ),还要看式 (3.1) 的第三项
′ )T H (θ − θ′ )。 第三项跟海森矩阵(Hessian matrix)H 有关。H 里面放的是 L 的二次微分,它第 i 行,
第 j 列的值 Hij 就是把 θ 的第 i 个元素对 L (θ′ ) 作微分,再把 θ 的第 j 个元素对
作微分后的结果,即Hij = (3.3)
总结一下,损失函数 L(θ) 在 θ′ 附近可近似为式 (3.1),式 (3.1) 跟梯度和海森矩阵有关,梯 度就是一次微分,海森矩阵里面有二次微分的项。
在临界点,梯度 g 为零,因此 (θ − θ′ )T g 为零。所以在临界点的附近, 损失函数可被近 似为
我们可以根据
′ )T H (θ − θ′ ) 来判断在 θ′ 附近的误差表面(error surface)到底长 什么样子。知道误差表面的“地貌”,我们就可以判断 L(θ′ ) 是局部极小值、局部极大值, 还是 鞍点。为了符号简洁,我们用向量 v 来表示 θ − θ′ , (θ − θ′ )T H (θ − θ′ ) 可改写为 vTHv, 有如下三种情况。
(1)如果对所有 v ,vTHv > 0. 这意味着对任意 θ , L(θ) > L(θ′ ). 只要 θ 在 θ′ 附近, L(θ) 都大于 L(θ′ ). 这代表 L(θ′ ) 是附近的一个最低点,所以它是局部极小值。
(2)如果对所有 v ,vTHv < 0. 这意味着对任意 θ , L(θ) < L(θ′ ) ,θ′ 是附近最高的一 个点,L(θ′ ) 是局部极大值。
(3)如果对于 v ,vTHv 有时候大于零,有时候小于零。这意味着在 θ′ 附近,有时候 L(θ) > L(θ′ ),有时候 L(θ) < L(θ′ ). 因此在 θ′ 附近,L(θ′ ) 既不是局部极大值,也不是局部 极小值,而是鞍点。
有一个问题,通过
′ )T H (θ − θ′ ) 判断临界点是局部极小值还是鞍点还是局部极 大值,需要代入所有的 θ . 但我们不可能把所有的 v 都拿来试试看,所以有一个更简便的方法 来判断 vTHv 的正负。算出一个海森矩阵后,不需要把它跟所有的 v 都乘乘看,只要看 H 的特征值。若 H 的所有特征值都是正的,H 为正定矩阵,则 vTHv > 0 ,临界点是局部极 小值。若 H 的所有特征值都是负的,H 为负定矩阵,则 vTHv < 0 ,临界点是局部极大值。 若 H 的特征值有正有负,临界点是鞍点。
如果 n 阶对称矩阵 A 对于任意非零的 n 维向量 x 都有 xTAx > 0,则称矩阵 A 为正 定矩阵。如果 n 阶对称矩阵 A 对于任意非零的 n 维向量 x 都有 xTAx < 0,则称矩 阵 A 为负定矩阵。
举个例子,我们有一个简单的神经网络,它只有两个神经元,而且这个神经元还没有激活 函数和偏置。输入 x,x 乘上 w1 以后输出,然后再乘上 w2 ,接着再输出,最终得到的数据就 是 y。
y = w1 w2 x. (3.5)
我们还有一个简单的训练数据集,这个数据集只有一组数据 (1,1),也就是 x = 1 的标签 是 1. 所以输入 1 进去,我们希望最终的输出跟 1 越接近越好,如图 3.3 所示。
图 3.3 简单的神经网络
可以直接画出这个神经网络的误差表面,如图 3.4 所示,可以取 [−2,2] 之间的 w1 跟 w2 的数值,算出这个范围内 w1 , w2 数值所带来的损失,四个角落的损失是高的。我们用黑色的 点来表示临界点,原点 (0,0) 是临界点,另外两排点是临界点。我们可以进一步地判断这些临 界点是鞍点还是局部极小值。原点是鞍点, 因为我们往某个方向走,损失可能会变大,也可能 会变小。而另外两排临界点都是局部极小值。这是我们取 [−2,2] 之间的参数得到的损失函数 以后,得到的损失的值后,画出误差表面后得到的结论。
除了尝试取所有可能的损失,我们还有其他的方法,比如把损失的函数写出来。对于图 3.3
所示的神经网络,损失函数 L是正确答案 y 减掉模型的输出 ˆ(y) = w1 w2 x 后取平方误差(square
error), 这里只有一组数据,因此不会对所有的训练数据进行加和。令 x = 1, y = 1,损失函 数为
L = (y − w1 w2 x)2 = (1 − w1 w2 )2 . (3.6)
图 3.4 误差表面
可以求出损失函数的梯度 g = [ ]:
(3.7)
什么时候梯度会为零(也就是到一个临界点)呢?比如,在原点时,w1 = 0, w2 = 0,此 时的梯度为零,原点就是一个临界点,但通过海森矩阵才能判断它是哪种临界点。刚才我们通 过取 [−2,2] 之间的 w1 和 w2 来判断出原点是一个鞍点,但是假设我们还没有取所有可能的 损失,我们要看看能不能够用海森矩阵来判断原点是什么临界点。
海森矩阵 H 收集了 L 的二次微分:
(3.8)
对于原点,只要把 w1 = 0, w2 = 0 代进去,有海森矩阵
(3.9)
通过海森矩阵来判断原点是局部极小值还是鞍点,要看它的特征值,这个矩阵有两个特征值:
2 和 −2,特征值有正有负,因此原点是鞍点。
如果我们当前处于鞍点,就不用那么害怕了。H 不只可以帮助我们判断是不是在一个鞍 点,还指出了参数可以更新的方向。之前我们参数更新的时候, 都是看梯度 g,但是我们走到 某个地方以后发现 g 变成 0 了,就不能再看 g 了,g 不见了。但如果临界点是一个鞍点,还 可以再看 H,怎么再看 H 呢,H 怎么告诉我们怎么更新参数呢?
设 λ 为 H 的一个特征值 λ , u 为其对应的特征向量。对于我们的优化问题,可令 u = θ − θ′ ,则
uTHu = uT (λu) = λⅡuⅡ2 . (3.10)
若 λ < 0,则 λⅡuⅡ2 < 0。所以
′ )T H (θ − θ′ ) < 0。此时,L(θ) < L(θ′ ),且θ = θ′ + u.
(3.11)
沿着 u 的方向更新 θ, 损失就会变小。因为根据式 (3.10) 和式 (3.11),只要 θ = θ′ + u,沿 着特征向量 u 的方向去更新参数,损失就会变小,所以虽然临界点的梯度为零,如果我们是 在一个鞍点,只要找出负的特征值,再找出这个特征值对应的特征向量。将其与 θ′ 相加,就 可以找到一个损失更低的点。
在前面的例子中,原点是一个临界点,此时的海森矩阵如式 (3.9) 所示,该海森矩阵有一 个负的特征值:−2,特征值 −2 对应的特征向量有无穷多个。不妨取 u = [1, 1]T ,作为 −2 对 应的特征向量。我们其实只要顺着 u 的方向去更新参数,就可以找到一个比鞍点的损失还要 更低的点。以这个例子来看, 原点是鞍点,其梯度为零,所以梯度不会告诉我们要怎么更新参 数。但海森矩阵的特征向量告诉我们只要往 [1, 1]T 的方向更新。损失就会变得更小,就可以 逃离鞍点。
所以从这个角度来看,鞍点似乎并没有那么可怕。但实际上, 我们几乎不会真的把海森矩 阵算出来,因为海森矩阵需要算二次微分,计算这个矩阵的运算量非常大,还要把它的特征值 跟特征向量找出来,所以几乎没有人用这个方法来逃离鞍点。还有一些其他逃离鞍点的方法 的运算量都比要算海森矩阵小很多。
3.1.3 逃离鞍点的方法
讲到这边会有一个问题:鞍点跟局部极小值谁比较常见?鞍点其实并没有很可怕,如果我 们经常遇到的是鞍点,比较少遇到局部极小值,那就太好了。科幻小说《三体 III:死神永生》 中有这样一个情节:东罗马帝国的国王君士坦丁十一世为对抗土耳其人,找来了具有神秘力 量的做狄奥伦娜。狄奥伦娜可以于万军丛中取上将首级, 但大家不相信她有这么厉害,想要狄 奥伦娜先展示下她的力量。于是狄奥伦娜拿出了一个圣杯,大家看到圣杯大吃一惊,因为这个 圣杯本来是放在圣索菲亚大教堂地下室的一个石棺里面,而且石棺是密封的,没有人可以打 开。狄奥伦娜不仅取得了圣杯,还自称在石棺中放了一串葡萄。于是君士坦丁十一世带人撬 开了石棺,发现圣杯真的被拿走了,而是棺中真的有一串新鲜的葡萄,为什么迪奥伦娜可以 做到这些事呢?是因为狄奥伦娜可以进入四维的空间。从三维的空间来看这个石棺是封闭的, 没有任何路可以进去,但从高维的空间来看,这个石棺并不是封闭的,是有路可以进去的。误 差表面会不会也一样呢。
如图 3.5(a) 所示的一维空间中的误差表面,有一个局部极小值。但是在二维空间(如 图 3.5(b) 所示),这个点就可能只是一个鞍点。常常会有人画类似图 3.5(c) 这样的图来告诉 我们深度学习的训练是非常复杂的。如果我们移动某两个参数,误差表面的变化非常的复杂, 有非常多局部极小值。低维度空间中的局部极小值点, 在更高维的空间中,实际是鞍点。同样 地,如果在二维的空间中没有路可以走,会不会在更高维的空间中,其实有路可以走?更高 的维度难以视化它,但我们在训练一个网络的时候,参数数量动辄达百万千万级,所以误差 表面其实有非常高的维度—— 参数的数量代表了误差表面的维度。既然维度这么高,会不会 其实就有非常多的路可以走呢?既然有非常多的路可以走,会不会其实局部极小值就很少呢? 而经验上,我们如果自己做一些实验,会发现实际情况也支持这个假说。图 3.6 是训练某不同 神经网络的结果,每个点对应一个神经网络。纵轴代表训练网络时, 损失收敛到临界点,损失 没法下降时的损失。我们常常会遇到两种情况:损失仍然很高,却遇到了临界点而不再下降; 或者损失降得很低,才遇到临界点。图 3.6 中,横轴代表最小值比例(minimum ratio),最小值比例定义为
最小值比例 = 总特征值数量(正特征值数量) . (3.12)
实际上,我们几乎找不到所有特征值都为正的临界点。在图 3.6所示的例子中, 最小值比例最 大也不过处于 0.5 ~ 0.6 的范围,代表只有约一半的特征值为正,另一半的特征值为负,代表 在所有的维度里面有约一半的路可以让损失上升,还有约一半的路可以让损失下降。虽然在 这个图上,越靠近右侧代表临界点“看起来越像”局部极小值,但是这些点都不是真正的局部极 小值。所以从经验上看起来, 局部极小值并没有那么常见。多数的时候, 我们训练到一个梯度 很小的地方,参数不再更新,往往只是遇到了鞍点。
图 3.5 误差表面
图 3.6 训练不同神经网络的结果
3.2 批量和动量
实际上在计算梯度的时候,并不是对所有数据的损失 L 计算梯度,而是把所有的数据分 成一个一个的批量(batch),如图 3.7所示。每个批量的大小是 B ,即带有 B 笔数据。每次在 更新参数的时候,会去取出 B 笔数据用来计算出损失和梯度更新参数。遍历所有批量的过程 称为一个回合(epoch)。事实上, 在把数据分为批量的时候,我们还会进行随机打乱(shuffle)。 随机打乱有很多不同的做法,一个常见的做法是在每一个回合开始之前重新划分批量,也就 是说,每个回合的批量的数据都不一样。
图 3.7 使用批量优化
3.2.1 批量大小对梯度下降法的影响
假设现在我们有 20 笔训练数据,先看下两个最极端的情况,如图 3.8 所示。
• 图 3.8 (a) 的情况是没有用批量,批量大小为训练数据的大小,这种使用全批量(full batch)的数据来更新参数的方法即批量梯度下降法(Batch Gradient Descent,BGD)。 此时模型必须把 20 笔训练数据都看完,才能够计算损失和梯度,参数才能够更新一次。
• 图 3.8 (b)中,批量大小等于 1,此时使用的方法即随机梯度下降法(Stochastic Gra- dient Descent,SGD),也称为增量梯度下降法。批量大小等于 1 意味着只要取出一 笔数据即可计算损失、更新一次参数。如果总共有 20 笔数据,那么在每一个回合里面, 参数会更新 20 次。用一笔数据算出来的损失相对带有更多噪声,因此其更新的方向如 图 3.8 所示,是曲曲折折的 。
实际上,批量梯度下降并没有“划分批量”:要把所有的数据都看过一遍,才能够更新一次 参数,因此其每次迭代的计算量大。但相比随机梯度下降, 批量梯度下降每次更新更稳定、更准确。
随机梯度下降的梯度上引入了随机噪声,因此在非凸优化问题中,其相比批量梯度下 降更容易逃离局部最小值。
实际上,考虑并行运算,批量梯度下降花费的时间不一定更长;对于比较大的批量,计算 损失和梯度花费的时间不一定比使用小批量的计算时间长 。使用 Tesla V100 GPU 在 MNIST 数据集得到的实验结果如图 3.9 所示。图 3.9 中横坐标表示批量大小,纵坐标表示给定批量 大小的批量,计算梯度并更新参数所耗费的时间。批量大小从 1 到 1000 ,需要耗费的时间几乎是一样的,因为在实际上 GPU 可以做并行运算,这 1000 笔数据是并行处理的,所以 1000 笔数据所花的时间并不是一笔数据的 1000 倍。当然 GPU 并行计算的能力还是存在极限的, 当批量大小很大的时候,时间还是会增加的。 当批量大小非常大的时候,GPU 在“跑”完一个批量,计算出梯度所花费的时间还是会随着批量大小的增加而逐渐增长 。当批量大小增加到 10000,甚至增加到 60000 的时候,GPU 计算梯度并更新参数所耗费的时间确实随着批量大 小的增加而逐渐增长。
图 3.8 批量梯度下降法与随机梯度下降法
MNIST 中的“NIST”是指国家标准和技术研究所(National Institute of Standards and Technology),其最初收集了这些数据。MNIST 中“M”是指修改的(Modified),数据 经过预处理以方便机器学习算法使用。MNIST 数据集收集了数万张手写数字(09̃) 的 28×28 像素的灰度图像及其标签。一般大家第一个会尝试的机器学习的任务,往往就是 用 MNIST 做手写数字识别, 这个简单的分类问题是深度学习研究中的“Hello World”。
图 3.9 批量大小与计算时间的关系
但是因为有并行计算的能力,因此实际上当批量大小小的时候,要“跑”完一个回合,花的 时间是比大的。假设训练数据只有 60000 笔,批量大小设 1,要 60000 个更新才能“跑”完一个 回合;如果批量大小等于 1000 ,60 个更新才能“跑”完一个回合,计算梯度的时间差不多。但60000 次更新跟 60 次更新比起来,其时间的差距量就非常大了。图 3.10(a) 是用一个批量计算 梯度并更新一次参数所需的时间。假设批量大小为 1,“跑”完一个回合,要更新 60000 次参数, 其时间是非常大的。但假设批量大小是 1000,更新 60 次参数就会“跑”完一个回合。图 3.10(b) 是“跑”完一个完整的回合需要花的时间。如果批量大小为 1000 或 60000 ,其时间比批量大小设 1 还要短 。图 3.10(a) 和图 3.10(b) 的趋势正好是相反的。因此实际上, 在有考虑并行计算 的时候,大的批量大小反而是较有效率的,一个回合大的批量花的时间反而是比较少的。
图 3.10 并行计算中批量大小与计算时间的关系
大的批量更新比较稳定,小的批量的梯度的方向是比较有噪声的(noisy)。但实际上有 噪声的的梯度反而可以帮助训练,如果拿不同的批量来训练模型来做图像识别问题,实验结 果如图 3.11 所示,横轴是批量大小,纵轴是正确率。图 3.11(a) 是 MNIST 数据集上的结果, 图 3.11(b) 是 CIFAR-10 数据集上的结果。批量大小越大, 验证集准确率越差。但这不是过拟 合,因为批量大小越大,训练准确率也是越低。因为用的是同一个模型,所以这不是模型偏见 的问题。 但大的批量大小往往在训练的时候,结果比较差。这个是优化的问题,大的批量大 小优化可能会有问题,小的批量大小优化的结果反而是比较好的。
图 3.11 不同的批量来训练模型来做图像识别问题的实验结果
一个可能的解释如图 3.12 所示,批量梯度下降在更新参数的时候,沿着一个损失函数来 更新参数,走到一个局部最小值或鞍点显然就停下来了。梯度是零, 如果不看海森矩阵,梯度下降就无法再更新参数了 。但小批量梯度下降法(mini-batch gradient descent)每次是挑一 个批量计算损失,所以每一次更新参数的时候所使用的损失函数是有差异的。选到第一个批 量的时候,用 L1 计算梯度;选到第二个批量的时候,用 L2 计算梯度。假设用 L1 算梯度的 时候,梯度是零,就会卡住。但 L2 的函数跟 L1 又不一样,L2 不一定会卡住,可以换下个批 量的损失 L2 计算梯度,模型还是可以训练,还是有办法让损失变小,所以这种有噪声的更新 方式反而对训练其实是有帮助的。
图 3.12 小批量梯度下降更好的原因
其实小的批量也对测试有帮助。假设有一些方法(比如调大的批量的学习率)可以把大的 批量跟小的批量训练得一样好。实验结果发现小的批量在测试的时候会是比较好的[1]。在论文 “On Large-Batch Training for Deep Learning: Generalization Gap and Sharp Minima”中,作 者在不同数据集上训练了六个网络(包括全连接网络、不同的卷积神经网络),在很多不同的 情况都观察到一样的结果。在小的批量,一个批量里面有 256 笔样本。在大的批量中,批量 大小等于数据集样本数乘 0.1。比如数据集有 60000 笔数据,则一个批量里面有 6000 笔数据 。大的批量跟小的批量的训练准确率(accuracy)差不多,但就算是在训练的时候结果差不 多,测试的时候,大的批量比小的批量差,代表过拟合。
这篇论文给出了一个解释,如图 3.13 所示,训练损失上面有多个局部最小值,这些局部 最小值的损失都很低,其损失可能都趋近于 0。但是局部最小值有好最小值跟坏最小值之分, 如果局部最小值在一个“峡谷”里面,它是坏的最小值;如果局部最小值在一个平原上,它是好 的最小值。训练的损失跟测试的损失函数是不一样的,这有两种可能。一种可能是本来训练跟 测试的分布就不一样;另一种可能是因为训练跟测试都是从采样的数据算出来的,训练跟测 试采样到的数据可能不一样,所以它们计算出的损失是有一点差距。 对在一个“盆地”里面的 最小值,其在训练跟测试上面的结果不会差太多,只差了一点点。但对在右边在“峡谷”里面的 最小值,一差就可以天差地远 。虽然它在训练集上的损失很低,但训练跟测试之间的损失函 数不一样,因此测试时,损失函数一变,计算出的损失就变得很大。
大的批量大小会让我们倾向于走到“峡谷”里面,而小的批量大小倾向于让我们走到“盆地” 里面。小的批量有很多的损失,其更新方向比较随机,其每次更新的方向都不太一样。即使 “峡谷”非常窄,它也可以跳出去,之后如果有一个非常宽的“盆地”,它才会停下来。
大的批量跟小的批量的对比结果如表 3.1 所示。在有并行计算的情况下,小的批量跟大 的批量运算的时间并没有太大的差距。除非大的批量非常大, 才会显示出差距。但是一个回合 需要的时间,小的批量比较长,大的批量反而是比较快的,所以从一个回合需要的时间来看, 大的批量是较有优势的。 而小的批量更新的方向比较有噪声的,大的批量更新的方向比较稳定。但是有噪声的更新方向反而在优化的时候有优势, 而且在测试的时候也会有优势。所以大 的批量跟小的批量各有优缺点,批量大小是需要去调整的超参数。
图 3.13 小批量优化容易跳出局部最小值的原因
其实用大的批量大小来做训练,用并行计算的能力来增加训练的效率,并且训练出的结 果很好是可以做到的[2-3]。比如 76 分钟训练 BERT[4] ,15 分钟训练 ResNet[5] ,一分钟训练 ImageNet[6] 等等。这些论文批量大小很大,比如论文“Large Batch Optimization for Deep Learning: Training BERT in 76 minutes ”中批量大小为三万。批量大小很大可以算得很快, 这些论文有一些特别的方法来解决批量大小可能会带来的劣势。
表 3.1 小批量梯度下降与批量梯度下降的比较
评价标准 | 小批量梯度下降 | 批量梯度下降 |
一次更新的速度(没有并行计算) | 相同 | 相同 |
一次更新的速度(有并行计算) | 相同 | 相同(批量大小不是很大) |
一个回合的时间 | 更慢 | 更快 |
梯度 | 有噪声 | 稳定 |
优化 | 更好 | 更坏 |
泛化 | 更好 | 更坏 |
3.2.2 动量法
动量法(momentum method)是另外一个可以对抗鞍点或局部最小值的方法。如图3.14
图 3.14 物理世界中的惯性
所示,假设误差表面就是真正的斜坡,参数是一个球,把球从斜坡上滚下来,如果使用梯度 下降,球走到局部最小值或鞍点就停住了。 但是在物理的世界里,一个球如果从高处滚下来, 就算滚到鞍点或鞍点,因为惯性的关系它还是会继续往前走。如果球的动量足够大, 其甚至翻 过小坡继续往前走。 因此在物理的世界里面,一个球从高处滚下来的时候,它并不一定会被 鞍点或局部最小值卡住,如果将其应用到梯度下降中,这就是动量。
一般的梯度下降(vanilla gradient descent)如图 3.15 所示。初始参数为 θ0 ,计算一下梯 度,计算完梯度后,往梯度的反方向去更新参数 θ 1 = θ0 − ηg0。有了新的参数 θ 1 后,再计 算一次梯度,再往梯度的反方向,再更新一次参数,到了新的位置以后再计算一次梯度,再往 梯度的反方向去更新参数。
图 3.15 一般梯度下降
引入动量后,每次在移动参数的时候,不是只往梯度的反方向来移动参数,而是根据梯度 的反方向加上前一步移动的方向决定移动方向。 图 3.16 中红色虚线方向是梯度的反方向,蓝 色虚线方向是前一次更新的方向,蓝色实线的方向是下一步要移动的方向。把前一步指示的 方向跟梯度指示的方向相加就是下一步的移动方向。如图 3.16 所示,初始的参数值为 θ0 = 0, 前一步的参数的更新量为 m0 = 0。接下来在 θ0 的地方,计算梯度的方向 g0。下一步的方向 是梯度的方向加上前一步的方向,不过因为前一步正好是 0,所以更新的方向跟原来的梯度下 降是相同的。但从第二步开始就不太一样了。从第二步开始,计算 g1 ,接下来更新的方向为 m2 = λm1 − ηg1 ,参数更新为 θ 2 ,接下来就反复进行同样的过程。
每一步的移动都用 m 来表示。m 其实可以写成之前所有计算的梯度的加权和,如式 (3.13) 所示。其中 η 是学习率,λ 是前一个方向的权重参数,也是需要调的。引入动量后, 可以从两 个角度来理解动量法。一个角度是动量是梯度的负反方向加上前一次移动的方向。另外一个 角度是当加上动量的时候,更新的方向不是只考虑现在的梯度,而是考虑过去所有梯度的总和。
m0 = 0
m 1 = −ηg0
m2 = −ληg0 − ηg1
(3.13)
图 3.16 动量法
动量的简单例子如图 3.17 所示。红色表示负梯度方向,蓝色虚线表示前一步的方向,蓝 色实线表示真实的移动量。一开始没有前一次更新的方向,完全按照梯度给指示往右移动参 数。负梯度方向跟前一步移动的方向加起来,得到往右走的方向。一般梯度下降走到一个局 部最小值或鞍点时,就被困住了。但有动量还是有办法继续走下去,因为动量不是只看梯度, 还看前一步的方向。即使梯度方向往左走, 但如果前一步的影响力比梯度要大,球还是有可能 继续往右走,甚至翻过一个小丘,也许可以走到更好的局部最小值,这就是动量有可能带来的好处 。
图 3.17 动量的好处
3.3 自适应学习率
临界点其实不一定是在训练一个网络的时候会遇到的最大的障碍。图 3.18 中的横坐标代 表参数更新的次数,竖坐标表示损失。一般在训练一个网络的时候,损失原来很大,随着参 数不断的更新,损失会越来越小,最后就卡住了,损失不再下降。当我们走到临界点的时候, 意味着梯度非常小,但损失不再下降的时候,梯度并没有真的变得很小,图 3.19 给出了示例。 图 3.19 中横轴是迭代次数,竖轴是梯度的范数(norm),即梯度这个向量的长度。随着迭代 次数增多,虽然损失不再下降,但是梯度的范数并没有真的变得很小。
图 3.20 是误差表面,梯度在山谷的两个谷壁间,不断地来回“震荡”,这个时候损失不会 再下降,它不是真的卡到了临界点,卡到了鞍点或局部最小值。但它的梯度仍然很大, 只是损 失不一定再减小了。所以训练一个网络,训练到后来发现损失不再下降的时候,有时候不是卡在局部最小值或鞍点,只是单纯的损失无法再下降。
图 3.18 训练网络时损失变化
图 3.19 训练网络时梯度范数变化
图 3.20 梯度来回“震荡”
我们现在训练一个网络,训练到现在参数在临界点附近,再根据特征值的正负号判断该 临界点是鞍点还是局部最小值。实际上在训练的时候, 要走到鞍点或局部最小值,是一件困难 的事情。一般的梯度下降,其实是做不到的。用一般的梯度下降训练,往往会在梯度还很大的 时候,损失就已经降了下去,这个是需要特别方法训练的。要走到一个临界点其实是比较困难 的,多数时候训练在还没有走到临界点的时候就已经停止了。
举个例子,我们有两个参数 w 和 b,这两个参数值不一样的时候,损失值也不一样,得 到了图 3.21 所示的误差表面,该误差表面的最低点在叉号处。事实上,该误差表面是凸的形 状。凸的误差表面的等高线是椭圆形的, 椭圆的长轴非常长,短轴相比之下比较短,其在横轴 的方向梯度非常小,坡度的变化非常小,非常平坦;其在纵轴的方向梯度变化非常大,误差表 面的坡度非常陡峭。现在我们要从黑点(初始点)来做梯度下降。
学习率 η = 10 −2 的结果如图 3.22(a) 所示。参数在峡谷的两端,参数在山壁的两端不断第“震荡”,损失降不下去,但是梯度仍然是很大的。我们可以试着把学习率设小一点,学习率 决定了更新参数的时候的步伐,学习率设太大,步伐太大就无法慢慢地滑到山谷里面。调学习 率从 10 −2 调到 10 −7 的结果如图 3.22(b) 所示,参数不再“震荡”了。参数会滑到山谷底后左 转,但是这个训练永远走不到终点,因为学习率已经太小了。AB 段的坡度很陡,梯度的值很 大,还能够前进一点。左拐以后, BC 段的坡度已经非常平坦了,这种小的学习率无法再让训 练前进。事实上在 BC 段有 10 万个点(10 万次更新),但都无法靠近局部最小值,所以显然 就算是一个凸的误差表面,梯度下降也很难训练。
图 3.21 凸误差表面
图 3.22 不同的学习率对训练的影响
最原始的梯度下降连简单的误差表面都做不好,因此需要更好的梯度下降的版本。在梯 度下降里面,所有的参数都是设同样的学习率,这显然是不够的,应该要为每一个参数定制 化学习率,即引入自适应学习率(adaptive learning rate)的方法,给每一个参数不同的学习 率。如图 3.23 所示,如果在某一个方向上,梯度的值很小,非常平坦,我们会希望学习率调 大一点;如果在某一个方向上非常陡峭,坡度很大,我们会希望学习率可以设得小一点。
3.3.1 AdaGrad
AdaGrad(Adaptive Gradient)是典型的自适应学习率方法,其能够根据梯度大小自 动调整学习率。AdaGrad 可以做到梯度比较大的时候,学习率就减小,梯度比较小的时候,学 习率就放大。
图 3.23 不同参数需要不同的学习率
梯度下降更新某个参数 θt(i) 的过程为
θt(i)+1 ← θt(i) − ηgt(i) (3.14)
θt(i) 在第 t 个迭代的值减掉在第 t 个迭代参数 i 算出来的梯度
θ =θt
(3.15)
gt(i) 代表在第 t 个迭代,即 θ = θt 时, 参数 θi 对损失 L 的微分,学习率是固定的。
现在要有一个随着参数定制化的学习率,即把原来学习率 η 变成
← θ
(3.16)
σt(i) 的上标为 i,这代表参数 σ 与 i 相关,不同的参数的 σ 不同。σt(i) 的下标为 t,这代表
参数 σ 与迭代相关,不同的迭代也会有不同的 σ。学习率从 η 改成
的时候,学习率就变得参数相关(parameter dependent)。
参数相关的一个常见的类型是算梯度的均方根(root mean square)。参数的更新过程为
(3.17)
其中 θ0(i) 是初始化参数。而 σ0(i) 的计算过程为
(3.18)
其中 g0(i) 是梯度。将 σ0(i) 的值代入更新的公式可知的值是 +1 或 −1。第一次在更新参数,从 θ0(i) 更新到 θ1(i) 的时候,要么是加上 η, 要么是减掉 η, 跟梯度的大小无关,这个是第一步的 情况。
第二次更新参数过程为
(3.19)
其中 σ1(i) 是过去所有计算出来的梯度的平方的平均再开根号,即均方根,如式 (3.20) 所示。
(3.20)
同样的操作反复继续下去,如式 (3.21) 所示。
(3.21)
第 t + 1 次更新参数的时候,即
θt(i)+1 ← θt(i) −
gt(i)
(3.22)
当作是新的学习率来更新参数。
图 3.24 中有两个参数:θ 1 和 θ 2。θ 1 坡度小,θ 2 坡度大。因为 θ 1 坡度小,根据式 (3.22),
θ1(i) 这个参数上面算出来的梯度值都比较小,因为梯度算出来的值比较小,所以算出来的 σt(i) 就小,σt(i) 小学习率就大。反过来, θ 1 坡度大,所以计算出的梯度都比较大,σt(i) 就比较大,在更新的时候,步伐(参数更新的量)就比较小。因此有了 σt(i) 这一项以后,就可以随着梯度的不同,每一个参数的梯度的不同,来自动调整学习率的大小。
图 3.24 自动调整学习率示例
3.3.2 RMSProp
同一个参数需要的学习率,也会随着时间而改变。在图 3.25 中的误差表面中,如果考虑 横轴方向,绿色箭头处坡度比较陡峭,需要较小的学习率,但是走到红色箭头处,坡度变得平 坦了起来,需要较大的学习率。因此同一个参数的同个方向,学习率也是需要动态调整的,于 是就有了一个新的方法———RMSprop(Root Mean Squared propagation)。
RMSprop 没有论文,Geoffrey Hinton 在 Coursera 上开过深度学习的课程,他在他的课 程里面讲了 RMSprop,如果要引用,需要引用对应视频的链接。
RMSprop 第一步跟 Adagrad 的方法是相同的,即
(3.23)
图 3.25 AdaGrad 的问题
第二步更新过程为
(3.24)
其中 0 < α < 1,其是一个可以调整的超参数。计算 θ1(i) 的方法跟 AdaGrad 算均方根不一样, 在算均方根的时候,每一个梯度都有同等的重要性,但在 RMSprop 里面,可以自己调整现在 的这个梯度的重要性。如果 α 设很小趋近于 0,代表 g1(i) 相较于之前算出来的梯度而言,比较 重要;如果 α 设很大趋近于 1,代表 g1(i) 比较不重要,之前算出来的梯度比较重要。
同样的过程就反复继续下去,如式 (3.25) 所示。
θ3(i) ← θ2(i) −
g2(i)
(3.25)
RMSProp 通过 α 可以决定,gt(i) 相较于之前存在 σt(i)−1 里面的 g1(i) , g2(i) , · · · · · · , gt(i)−1 的重要性有多大。如果使用 RMSprop,就可以动态调整 σt(i) 这一项。图 3.26 中黑线是误差表面,球就从 A 走到 B,AB 段的路很平坦,g 很小,更新参数的时候,我们会走比较大的步伐。走动 BC 段后梯度变大了,AdaGrad 反应比较慢,而 RMSprop 会把 α 设小一点,让新的、刚看到的梯度的影响比较大,很快地让 σt(i) 的值变大,很快地让步伐变小,RMSprop 可以很快地“踩刹车”。如果走到 CD 段,CD 段是平坦的地方,可以调整 α, 让其比较看重最近算出来的梯度,梯度一变小,σt(i) 的值就变小了,走的步伐就变大了。
3.3.3 Adam
最常用的优化的策略或者优化器(optimizer)是Adam(Adaptive moment estima-
tion)[7] 。Adam 可以看作 RMSprop 加上动量,其使用动量作为参数更新方向,并且能够自 适应调整学习率。PyTorch 里面已经写好了 Adam 优化器,这个优化器里面有一些超参数需 要人为决定,但是往往用 PyTorch 预设的参数就足够好了。
图 3.26 RMSprop 示例
3.4 学习率调度
如图 3.22 所示的简单的误差表面,我们都训练不起来,加上自适应学习率以后,使用 AdaGrad 方法优化的结果如图3.27 所示。一开始优化的时候很顺利,在左转的时候,有 Ada- Grad 以后,可以再继续走下去,走到非常接近终点的位置。走到 BC 段时,因为横轴方向的梯 度很小,所以学习率会自动变大,步伐就可以变大,从而不断前进。接下来的问题走到图 3.27
图 3.27 AdaGrad 优化的问题
中红圈的地方,快走到终点的时候突然“爆炸”了。σt(i) 是把过去所有的梯度拿来作平均。在 AB
段梯度很大,但在 BC 段,纵轴的方向梯度很小,因此纵轴方向累积了很小的 σt(i),累积到一定
程度以后,步伐就变很大,但有办法修正回来。因为步伐很大,其会走到梯度比较大的地方。
走到梯度比较大的地方后,σt(i) 会慢慢变大,更新的步伐大小会慢慢变小,从而回到原来的路
通过学习率调度(learning rate scheduling)可以解决这个问题。之前的学习率调整 方法中 η 是一个固定的值,而在学习率调度中 η 跟时间有关,如式 (3.26) 所示。学习率调度 中最常见的策略是学习率衰减(learning rate decay),也称为学习率退火(learning rate annealing)。随着参数的不断更新, 让 η 越来越小,如图 3.28 所示。图 3.22b 的情况,如果 加上学习率下降,可以很平顺地走到终点,如图 3.29 所示。在图 3.22b 红圈的地方,虽然步 伐很大,但 η 变得非常小,步伐乘上 η 就变小了,就可以慢慢地走到终点。
(3.26)
图 3.28 学习率衰减
除了学习率下降以外,还有另外一个经典的学习率调度的方式———预热。预热的方法 是让学习率先变大后变小,至于变到多大、变大的速度、变小的速度是超参数。 残差网络[8] 里 面是有预热的,在残差网络里面,学习率先设置成 0.01,再设置成 0.1,并且其论文还特别说 明,一开始用 0.1 反而训练不好。除了残差网络, BERT 和 Transformer 的训练也都使用了预热。
图 3.29 学习率衰减的优化效果
Q:为什么需要预热?
A:当我们使用 Adam、RMSprop 或 AdaGrad 时,需要计算 σ。而 σ 是一个统计的结 果。从 σ 可知某一个方向的陡峭程度。统计的结果需要足够多的数据才精准,一开始 统计结果 σ 是不精准的。一开始学习率比较小是用来探索收集一些有关误差表面的情 报,先收集有关 σ 的统计数据,等 σ 统计得比较精准以后,再让学习率慢慢爬升。如 果读者想要学更多有关预热的东西可参考 Adam 的进阶版———RAdam[9]。
3.5 优化总结
所以我们从最原始的梯度下降,进化到这一个版本,如式 (3.27) 所示。
(3.27)
其中 mt(i) 是动量。
这个版本里面有动量,其不是顺着某个时刻算出的梯度方向来更新参数,而是把过去所
有算出梯度的方向做一个加权总和当作更新的方向。接下来的步伐大小为 σ(m)t(i)t(i) 。最后通过 ηt来实现学习率调度。这个是目前优化的完整的版本,这种优化器除了 Adam 以外,还有各种
变形。但其实各种变形是使用不同的方式来计算 mt(i) 或 σt(i),或者是使用不同的学习率调度的
方式。
Q:动量 mt(i) 考虑了过去所有的梯度,均方根 σt(i) 考虑了过去所有的梯度,一个放在分
子,一个放在分母,并且它们都考虑过去所有的梯度,不就是正好抵消了吗?
A:mt(i) 和 σt(i) 在使用过去所有梯度的方式是不一样的,动量是直接把所有的梯度都加
起来,所以它有考虑方向,它有考虑梯度的正负。但是均方根不考虑梯度的方向, 只考
虑梯度的大小,计算 σt(i) 的时候,都要把梯度取一个平方项,把平方的结果加起来,所
以只考虑梯度的大小,不考虑它的方向,所以动量跟 σt(i) 计算出来的结果并不会互相抵
消。
3.6 分类
分类与回归是深度学习最常见的两种问题,第一章的观看次数预测属于回归问题,本节 将介绍分类问题。
回归是输入一个向量 x,输出 ˆ(y), 我们希望 ˆ(y) 跟某一个标签 y 越接近越好,y 是要学习
的目标。而分类可当作回归来看,输入 x 后,输出仍然是一个标量 ˆ(y), 要让它跟正确答案的
那个类越接近越好。ˆ(y) 是一个数字,我们可以把类也变成数字。如图 3.30 所示,类 1 是编号
1 ,类 2 是编号 2 ,类 3 是编号 3 ,ˆ(y) 跟类的编号越接近越好。但该方法在某些状况下会有问
题,假设类 1、2、3 有某种关系。比如根据一个人的身高跟体重,预测他的年级,一年级、二 年级还是三年级。一年级跟二年级关系比较近,一年级跟三年级关系比较远。用数字来表示类 会预设 1 和 2 有比较近的关系,1 和 3 有比较远的关系。但假设三个类本身没有特定的关系, 类 1 是 1 ,类 2 是 2 类 3 是 3。这种情况,需要引入独热向量来表示类。实际上,在做分类 的问题的时候,比较常见的做法也是用独热向量表示类。
图 3.30 用数字表示类的问题
如果有三个类,标签 y 就是一个三维的向量,比如类 1 是 [1, 0, 0]T ,类 2 是 [0, 1, 0]T ,类 3 是 [0, 0, 1]T。如果每个类都用一个独热向量来表示,就没有类 1 跟类 2 比较接近,类 1 跟 类 3 比较远的问题。如果用独热向量计算距离的话,类两两之间的距离都是一样的。
如果目标 y 是一个向量,比如 y 是有三个元素的向量,网络也要输出三个数字才行。如 图 3.31 所示,输出三个数值就是把本来输出一个数值的方法,重复三次。把 a1 、a2 和 a3 乘上三个不同的权重,加上偏置,得到 ˆ(y)1 ;再把 a1 、a2 和 a3 乘上另外三个权重,再加上另外一个偏置得到 ˆ(y)2 ;把 a1 、a2 和 a3 再乘上另外一组权重,再加上另外一个偏置得到 ˆ(y)3。输入一个特征向量,产生 ˆ(y)1 、ˆ(y)2 、ˆ(y)3 ,希望 ˆ(y)1 、ˆ(y)2 、ˆ(y)3 跟目标越接近越好。
图 3.31 网络多个输出示例
3.6.2 带有 softmax 的分类
按照上述的设定,分类实际过程是:输入 x,乘上 W ,加上 b,通过激活函数 σ, 乘上 W′ ,再加上 b′ 得到向量 ^(y)。但实际做分类的时候, 往往会把 ˆ(y) 通过 softmax 函数得到 y′ ,才 去计算 y′ 跟 ˆ(y) 之间的距离。
图 3.32 带有 softmax 的分类
Q:为什么分类过程中要加上 softmax 函数?
A:一个比较简单的解释是,y 是独热向量,所以其里面的值只有 0 跟 1,但是 ˆ(y) 里面 有任何值。既然目标只有 0 跟 1,但 ˆ(y) 有任何值,可以先把它归一化到 0 到 1 之间, 这样才能跟标签的计算相似度。
softmax 的计算如式 (3.28) 所示,先把所有的 y 取一个指数(负数取指数后也会变成正 的),再对其做归一化(除掉所有 y 的指数值的和)得到 y′。图 3.33 是 softmax 的块(block),
输入 y1 、y2 和 y3 ,产生 y1(′) 、y2(′) 和 y3(′)。比如 y1 = 3 ,y2 = 1 ,y3 = −3,
取完指数的时候,exp(3) = 20 、exp(1) = 2.7 和 exp(−3) = 0.05,做完归一化后,就变成 0.88、0.12 跟 0 。−3取完指数,再做归一化以后,会变成趋近于 0 的值。所以 softmax 除了归一化,让 y1(′) 、y2(′) 和 y3(′),变成 0 到 1 之间,和为 1 以外,它还会让大的值跟小的值的差距更大。
(3.28)
图 3.33 softmax 示例
图 3.33 考虑了三个类的状况,两个类也可以直接套 softmax 函数。但一般有两个类的时 候,我们不套 softmax,而是直接取 sigmoid。当只有两个类的时候,sigmoid 和 softmax 是 等价的。
3.6.3 分类损失
当我们把 x 输入到一个网络里面产生 ˆ(y) 后,通过 softmax 得到 y′ ,再去计算 y′ 跟 y 之
间的距离 e,如图 3.34 所示。
接下来从优化的角度来说明相较于均方误差,交叉熵是被更常用在分类上。如图 3.35 所示,有一个三类的分类,网络先输出 y1 、y2 和 y3 ,在通过 softmax 以后,产生 y1(′) 、y2(′) 和 y3(′)。
假设正确答案是 [1, 0, 0]T ,要计算 [1, 0, 0]T 跟 y1(′) 、y2(′) 和 y3(′) 之间的距离 e,e 可以是均方误差或交叉熵。假设 y1 的变化是从-10 到 10,y2 的变化也是从-10 到 10,y3 就固定设成-1000。因为 y3 的值很小,通过 softmax 以后,y3(′) 非常趋近于 0,它跟正确答案非常接近,且它对结果影响很少。总之,我们假设 y3 设一个定值,只看 y1 跟 y2 有变化的时候,对损失 e 的影响。
图 3.35 使用 softmax 的好处
图 3.36 是分别在 e 为均方误差和交叉熵时,y1 、y2 的变化对损失的影响,对误差表面的
影响,红色代表损失大,蓝色代表损失小。如果 y1 很大,y2 很小,代表 y1(′) 会很接近 1 ,y2(′)
会很接近 0。所以不管 e 取均方误差或交叉熵,如果 y1 大、y2 小,损失都是小的;如果 y1
小,y2 大,y1(′) 是 0 ,y2(′) 是 1,这个时候损失会比较大。
图 3.36 中左上角损失大,右下角损失小,所以期待最后在训练的时候,参数可以“走” 到右下角的地方。假设参数优化开始的时候,对应的损失都是左上角。如果选择交叉熵,如 图 3.36(a) 所示,左上角圆圈所在的点有斜率的,所以可以通过梯度,一路往右下的地方“走”; 如果选均方误差,如图 3.36(b)所示,左上角圆圈就卡住了,均方误差在这种损失很大的地方, 它是非常平坦的,其梯度是非常小趋近于 0 的。如果初始时在圆圈的位置, 离目标非常远,其 梯度又很小,无法用梯度下降顺利地“走”到右下角。
因此做分类时,选均方误差的时候,如果没有好的优化器,有非常大的可能性会训练不起 来。如果用 Adam,虽然图 3.36(b) 中圆圈的梯度很小,但 Adam 会自动调大学习率,还有机 会走到右下角,不过训练的过程比较困难。总之,改变损失函数可以改变优化的难度。
3.7 批量归一化
如果误差表面很崎岖,它比较难训练。能不能直接改误差表面的地貌,“把山铲平”,让它 变得比较好训练呢?批量归一化(Batch Normalization,BN)就是其中一个“把山铲平”的想法。不要小看优化这个问题, 有时候就算误差表面是凸(convex)的,它就是一个碗的形状, 都不一定很好训练。如图 3.37 所示,假设两个参数对损失的斜率差别非常大,在 w1 这个方 向上面,斜率变化很小,在 w2 这个方向上面斜率变化很大。
图 3.37 训练的问题
如果是固定的学习率,可能很难得到好的结果,所以我们才需要自适应的学习率、Adam 等比较进阶的优化的方法,才能够得到好的结果。从另外一个方向想,直接把难做的误差表 面把它改掉,看能不能够改得好做一点。在做这件事之前, 第一个要问的问题就是:w1 跟 w2 斜率差很多的这种状况,到底是从什么地方来的。
图 3.38 是一个非常简单的模型,其输入是 x1 跟 x2 ,对应的参数为 w1 跟 w2 ,它是一个线性的模型,没有激活函数。w1 乘 x1 ,w2 乘 x2 加上 b 以后就得到 ^(y),然后会计算 ^(y) 跟 y 之间的差距当做 e,把所有训练数据 e 加起来就是损失,然后去最小化损失。
图 3.38 简单的线性模型
什么样的状况会产生像上面这样子,比较不好训练的误差表面呢?对 w1 有一个小小的改 变,比如加上 △w1 的时候,L 也会有一个改变,那这个 w1 呢,是通过 w1 改变的时候,就 改变了 y ,y 改变的时候就改变了 e,接下来就改变了 L。
什么时候 w1 的改变会对 L 的影响很小呢,也就是它在误差表面上的斜率会很小呢一个可能性是当输入很小的时候,假设 x1 的值在不同的训练样本里面,它的值都很小,那因为 x1 是直接乘上 w1 ,如果 x1 的值都很小,w1 有一个变化的时候,它得到的,它对 y 的影响也是 小的,对 e 的影响也是小的,它对 L 的影响就会是小的反之,如图 3.39 所示,如果是 x2 的话,假设 x2 的值都很大,当 w2 有一个小小的变化 的时候,虽然 w2 这个变化可能很小,但是因为它乘上了 x2 ,x2 的值很大,那 y 的变化就很 大,e 的变化就很大,L 的变化就会很大,就会导致我们在 w 这个方向上,做变化的时候,我 们把 w 改变一点点,误差表面就会有很大的变化。所以既然在这个线性的的模型里面,当输 入的特征,每一个维度的值,它的范围差距很大的时候,我们就可能产生像这样子的误差表 面,就可能产生不同方向,斜率非常不同,坡度非常不同的误差表面所以怎么办呢,有没有可 能给特征里面不同的维度,让它有同样的数值的范围。如果我们可以给不同的维度, 同样的数 值范围的话,那我们可能就可以制造比较好的误差表面,让训练变得比较容易一点其实有很 多不同的方法,这些不同的方法往往就合起来统称为特征归一化(feature normalization)。
图 3.39 需要特征归一化的原因
以下所讲的方法只是特征归一化的一种可能性,即 Z 值归一化(Z-score normalization), 也称为标准化(standardization)。它并不是特征归一化的全部,假设 x1 到 xR ,是我们所有 的训练数据的特征向量。我们把所有训练数据的特征向量, 统统都集合起来。向量 x1 里面就x 1(1) 代表 x1 的第一个元素,x1(2) 代表 x2 的第一个元素,以此类推。我们把不同笔数据即不同特征向量,同一个维度里面的数值,把它取出来,对于每个维度 i,计算其平均值(mean) mi 和标准差(standard deviation) σi。接下来我们就可以做一种归一化。
(3.31)
我们就是把这边的某一个数值 x,减掉这一个维度算出来的平均值,再除掉这个维度,算 出来的标准差,得到新的数值
得到新的数值以后,再把新的数值把它塞回去。
归一化有个好处,做完归一化以后,这个维度上面的数值就会平均是 0,其方差是 1,所 以这一排数值的分布就都会在 0 上下;对每一个维度都做一样的归一化,所有特征不同维度都在 0 上下,可能就可以制造一个比较好的误差表面。所以像这样子的特征归一化方 式往往对训练有帮助,它可以让在做梯度下降的时候,损失收敛更快一点,训练更顺利一点。
图 3.40 Z 值归一化
3.7.1 考虑深度学习
x(˜) 代表归一化的特征,把它丢到深度网络里面,去做接下来的计算和训练。如图 3.41 所
示,x( ˜)1 通过第一层得到 z 1 ,有可能通过激活函数,不管是选 sigmoid 或者 ReLU 都可以,再
得到 a1 ,接着再通过下一层等等。对每个 x 都做类似的事情。
虽然 x(˜) 已经做归一化了,但是通过 W1 以后,没有做归一化。如果 x(˜) 通过 W1 得到 z 1 , 而 z 1 不同的维度间,它的数值的分布仍然有很大的差异,训练 W2 第二层的参数也会有困 难。对于 W2 ,a 或 z 其实也是一种特征,也应该要对这些特征做归一化。如果选择 sigmoid, 比较推荐对 z 做特征归一化,因为 sigmoid 是一个 s 的形状,其在 0 附近斜率比较大,如果 对 z 做特征归一化,把所有的值都挪到 0 附近,到时候算梯度的时候,算出来的值会比较大。 如果使用别的激活函数,可能对 a 归一化也会有好的结果。一般而言,特征归一化,要放在 激活函数之前,之后都是可以的,在实现上,没有太大的差别。
如何对 z 做特征归一化?z 可以看成另外一种特征。首先计算下 z 1 , z2 , z3 的平均值,即
图 3.41 深度学习的归一化
其中,除号代表逐元素的除,即分子分母两个向量对应元素相除。 归一化的过程如图 3.42 所示。
图 3.42 深度学习中间层的特征归一化
图 3.43 批量归一化可以理解为网络的一部分
在做批量归一化的时候,如图 3.44 所示,往往还会做如下操作:
其中,⊙ 代表逐元素的相乘。β , γ 可以想成是网络的参数,需要另外再被学习出来。
Q:为什么要加上 β 跟 γ 呢?
A:如果做归一化以后,
的平均值一定是 0,如果平均值是 0 的话,这会给网络一些 限制,这个限制可能会带来负面的影响,所以需要把 β , γ 加回去,让网络隐藏层的输出平均值不是 0。让网络学习 β , γ 来调整一下输出的分布,从而来调整 z(^) 的分布。
Q:批量归一化是为了要让每一个不同的维度的范围相同,如果把 γ 跟 β 加进去,这 样不同维度的分布,其范围不会又都不一样了吗?
A :有可能,但是实际上在训练的时候,γ 的初始值都设为 1,所以 γ 值都为 1 的向 量。β 是值全部都是 0 的向量,即零向量。所以让网络在一开始训练的时候,每一个 维度的分布,是比较接近的,也许训练到后来,已经训练够长的一段时间,已经找到一 个比较好的误差表面,走到一个比较好的地方以后,再把 γ , β 慢慢地加进去,所以加 了 γ , β 的批量归一化,往往对训练是有帮助的。
图 3.44 加 γ , β 的批量归一化
3.7.2 测试时的批量归一化
以上说的都是训练的部分,测试有时候又称为推断(inference)。批量归一化在测试的时 候,会有什么样的问题呢?在测试的时候,我们一次会得到所有的测试数据,确实也可以在测 试的数据上面,制造一个一个批量。但是假设系统上线, 做一个真正的线上的应用,比如批量 大小设 64,我一定要等 64 笔数据都进来,才做一次做运算,这显然是不行的。
但是在做批量归一化的时候,μ , σ 是用一个批量的数据算出来的。但如果在测试的时候, 根本就没有批量,如何算 μ , σ 呢?所以真正的实现上的解法是这个样子的。批量归一化在测 试的时候,并不需要做什么特别的处理,PyTorch 已经处理好了。在训练的时候, 如果有在做 批量归一化,每一个批量计算出来的 μ , σ, 都会拿出来算移动平均(moving average)。假 设现在有各个批量计算出来的 μ 1 , μ2 , μ3 , · · · · · · , μt ,则可以计算移动平均
μ(¯) ← pμ(¯) + (1 − p)μt
(3.36)
其中,μ(¯) 是 μ 的个平均值,p 是因子,这也是一个常数,这也是一个超参数,也是需要调的
那种。在 PyTorch 里面,p 设 0.1。计算滑动平均来更新 μ 的平均值。最后在测试的时候, 就 不用算批量里面的 μ 跟 σ 了。因为测试的时候, 在真正应用上也没有批量,就可以就直接拿
¯(μ) 跟 σ(¯) , 也就是 μ , σ 在训练的时候,得到的移动平均来取代原来的 μ 跟 σ,如图 3.45 所示,
图 3.45 测试时的批量归一化
图 3.46 是从批量归一化原始文献的实验结果,横轴代表的是训练的过程,纵轴代表的是 验证集上的准确率。黑色的虚线是没有做批量归一化的结果,它用的是 inception 的网络(一 种网络以 CNN 为基础的网络架构)。如果有做批量归一化,则是红色的这一条虚线。红色虚 线的训练速度显然比黑色的虚线还要快很多。虽然只要给模型足够的训练的时间,最后会收敛都差不多的准确率。但是红色虚线可以在比较短的时间内跑到一样的准确率。蓝色的菱形 代表说几个点的准确率是一样的。粉红色的线是 sigmoid 函数,一般的认知,但一般都会选择 ReLU,而不是用 sigmoid 函数,因为 sigmoid 函数的训练是比较困难的。但是这边想要强调 的点是,就算是 sigmoid 比较难搞的加批量归一化,还是可以训练的,这边没有 sigmoid,没 有做批量归一化的结果。因为在这个实验上, sigmoid 不加批量归一化,根本连训练都训练不 起来。蓝色的实线跟这个蓝色的虚线呢是把学习率设比较大一点, ×5 就是学习率变原来的 5 倍;×30 就是学习率变原来的 30 倍。因为如果做批量归一化,误差表面会比较平滑,比较容 易训练,所以就可以把学习率设大一点。这边有个不好解释的地方, 学习率设 30 倍的时候比 5 倍差,作者也没有解释。
图 3.46 批量归一化实验结果
3.7.3 内部协变量偏移
接下来的问题就是批量归一化为什么会有帮助呢?原始的批量归一化论文里面提出内部协变量偏移(internal covariate shift)概念。如图 3.47 所示,假设网络有很多层,-x 通 过第一层后得到 a ,a 通过第二层以后得到 b;计算出梯度以后,把 A 更新成 A′ ,把 B 这 一层的参数更新成 B ′。但是作者认为说,我们在计算 B 更新到 B ′ 的梯度的时候,这个时 候前一层的参数是 A,或者是前一层的输出是 a。那当前一层从 A 变成 A′ 的时候,其输出 就从 a 变成 a′ 。但是我们计算这个梯度的时候,是根据 a 算出来,所以这个更新的方向也 许它适合用在 a 上,但不适合用在 a′ 上面。因为我们每次都有做批量归一化,就会让 a 和 a′ 的分布比较接近,也许这样就会对训练有帮助。但是论文“How Does Batch Normalization Help Optimization?”[11] 认为内部协变量偏移有问题。这篇论文从不同的角度来说明内部协变 量偏移不一定是训练网络的时候的一个问题。批量归一化会比较好,可能不一定是因为它解 决了内部协变量偏移。这篇论文里面做了很多实验,比如其比较了训练的时候 a 的分布的变 化,发现不管有没有做批量归一化,其变化都不大。就算是变化很大, 对训练也没有太大的伤 害。不管是根据 a 算出来的梯度,还是根据 a′ 算出来的梯度,方向居然都差不多。内部协变 量偏移可能不是训练网络的时候,最主要的问题,它可能也不是批量归一化会好的一个的关 键。
协变量偏移(covariate shift),训练集和预测集样本分布不一致的问题就叫做协变量偏 移现象,这个词汇是原来就有的,内部协变量偏移是批量归一化的作者自己发明的。
图 3.47 内部协变量偏移示例
为什么批量归一化会比较好呢,那在这篇“How Does Batch Normalization Help Optimiza- tion?”这篇论文从实验和理论上,至少支持批量归一化可以改变误差表面,让误差表面比较不 崎岖这个观点。所以这个观点是有理论的支持,也有实验的佐证的。如果要让网络误差表面 变得比较不崎岖,其实不一定要做批量归一化,还有很多其他的方法都可以让误差表面变得 不崎岖,这篇论文就试了一些其他的方法,发现跟批量归一化表现也差不多,甚至还稍微好 一点,这篇论文的作者也觉得批量归一化是一种偶然的发现,但无论如何,其是一个有用的 方法。其实批量归一化不是唯一的归一化,还有很多归一化方法,比如批量重归一化(batch renormalization)[12]、层归一化(layer normalization)[13]、实例归一化(instance normalization) [14]、组归一化(group normalization)[15]、权重归一化(weight normalization)[16] 和谱归一 化(spectrum normalization)[17]。