雷锋网(公众号:雷锋网)按:本文为大牛讲堂算法工程师入门课程第一篇。地平线资深算法工程师罗恒、穆黎森、黄李超将分别带来深度学习、增强学习、物体检测的相关课程。本期地平线资深算法研究员罗恒将为大家带来深度学习简介,包括神经网络历史回顾和神经网络训练等内容。
▼
整个神经网络,从历史上就可以把很多东西搞清楚,比如它是怎么变过来的。我认为神经网络其实是变得越来越简单,越来越好用的。现在神经网络常用的东西已经很固定了,你不用再加任何东西就能用。但有些时候你觉得这个东西应该work,但它不work,这种情况该怎么办。所以尽管现在很多东西已经纯标准化,但了解这些对于你找问题找错误,还是很重要。所以这次主要讲两个东西,优化和正则化。绝大多数情况,正则化不是问题,我们只关心优化问题,往往你发现需要做正则化的时候多半是数据问题。
神经网络经历了三次研究热潮。第一次是60年代,感知器出来后很多人认为离人工智能已经很近了。但马上有人写了本书叫《感知器》,说它不能解决异或问题,这个时候大家一下子又不感兴趣了。到80年代,BP算法出来,又开始变得火热起来。但当时人们认为多层神经网络优化困难,又对它失去了信心。直到很多年后,2006年出来了Deep Belief Nets。学术界又开始慢慢对神经网络有兴趣,等到神经网络在语音识别和物体识别上取得突破后,神经网络的第三次热潮又开始了,一直持续到现在。
感知器已经有了现代神经网络的原型,输入特征与参数w连接,加权累加之后进入神经元,通过激活函数输出。与现在的网络的区别主要有两点,第一是数据要经过人工编码形成特征;第二是网络的输出是离散的二值。前者导致了相当一段时间,人们认为什么是好的特征应该由专家来设计,有了好的特征之后再解决分类问题,从这个角度上看,SVM也只是个特殊的感知器。
关于第二点,输出的离散的,这个带来一个问题,就是从输入到输出不是一个连续的光滑的,这个其实是限制使用梯度优化网络的最大障碍。
很快,Minsky和Papert的《感知器》里面证明了上面的感知器不能解决异或等一大类问题,这本书因此也造成了神经网络研究的第一次低潮。有趣的是,其实也就是在《感知器》里面,作者指出如果在网络中能够增加一些额外的类似感知器的单元,那么就能解决那些不能解决的问题。
这类神经元后来被Hinton称之为隐藏单元(Hidden Units),在传统的感知器中,输出神经元是可见的,根据训练数据,我们知道输出单元应当是什么样的状态,而隐藏单元的状态则不可知。就像之前提到的,如果隐藏单元的输出是离散的,那么根据输入特征,得到对应的输出就需要考虑隐藏单元的各种可能状态,那么就是一个组合优化问题,求解困难。
80年代,BP算法的成功的主要原因,就是改变激活函数为光滑连续的函数(可导),这样一来,对于一个含有隐藏单元的神经网络,从输入x到输入y也是一个关于所有连接参数w的光滑连续的函数。那么使用链式法则求导,我们就可以得到网络所有参数的梯度,也就是说我们现在知道按梯度方向调整参数w就可以使得给定输入数据x改变网络的输出大小,不断地修正,就可以使得网络的行为去拟合训练数据。之后,BP算法大获成功,神经网络迎来了第二次的研究热潮。
80年代到90年代,大量的研究人员使用BP算法训练神经网络,但是大家发现优化十分困难,最常见的失败就是无论怎么调整参数,训练的loss就是不降。由于神经网络是优化一个非线性函数,那么意味着理论上存在局部极小点,因此,每当loss不降,大家都是自然而然地认为这是遇到了局部极小点,同时理论上层数越多的神经网络非线性越强会有更多的局部极小,现实的观察也发现更深的网络往往结果更差,从而大量的研究者对神经网络失去信心,神经网络进入第二次低潮。90年代末开始的SVM相关研究以及后来对凸优化的痴迷,也都是在这个大背景下发生的。
进入2000年之后,Hinton发现使用一些非监督方法对多层神经网络做初始化之后再使用BP算法,能够成功地训练并得到更好的结果。再后来,发现直接使用BP算法进行监督训练就能在很多很多问题上得到非常好的结果。那么为什么90年代的时候没有成功呢?一个是当时训练数据相对很少,而且当时的机器训练起来很慢;另外一个是当时训练优化方法不太先进,所以有时在训练的时候梯度会出一些问题。
上面的图是一个四隐藏层的神经网络训练在MNIST上面(激活函数是tanh),大家可以看到训练一开始,最顶层的神经元的激活值迅速减小,那么意味着从这一层往后传递的梯度也会迅速减小,同时也可以观察到loss会基本保持不变(最后一层的激活函数接近0,那么无论什么样的输入到这一层之后都差不多,自然也就无法正确分类降低loss)。
但是随着训练的进行,顶层的激活又从0开始慢慢变大,loss最后又开始降。也就是在优化的时候,本来以为是掉到某一个坑里(局部极小),无论把w怎么改变,你的loss都没办法降下来。但实际情况是你在一个很平的高原上,你沿各个方向走,这个loss都差不多。现在我们知道使用适当的参数初始化、使用没有饱和的激活函数(如ReLU)、使用Batch Normalization都可以、使用一些自适应学习率方法(如Adam,RMSProp)都可以缓解上面这种情况,当然更重要的一点是使用GPU。
历史讲完了,接下来讲讲算法工程师日常工作面临的问题。我有一堆数据,质量差不多,我要拿到这些数据去训练一个模型,让这个模型能够投入使用。至于训练模型,我们现在工具已经很完备了,数据整理好之后梳理好命令,机器去跑,拿到一个结果,就最好了。但通常呢,会发现结果不好,结果不好有两种情况,一种是我刚才说的,模型在训练集上表现不好,也就是对应我们要讲的第一个问题,优化问题。也就是你拿到一个数据,你第一件事情你不要考虑测试集,你就考虑训练集,我要让我这个模型在训练上能做的足够好,所以这个实际上是一个优化问题。通常我们绝大多数困难都在这个问题。
当这个问题解决之后,可能又开始第二个问题。就是我在训练集上做的很好,但是在验证集上做的不好,这个就是overfitting,overfitting有很多种情况,从学术上讲比如说训练数据中包含一些噪声。我们日常中当你发现你的loss很低,但是在你的验证集结果不好,通常是你的训练集和你的测试集不一样。这个不一样可能有很多种原因,比如是不同的时间采集的,采集数据的策略发生了变化等等。这时首先需要做的是通过可视化对数据的分布有直觉上的认识,解决数据本身的问题。最简单的一种可视化办法,比如说你是一个二分类,你现在数据你就把它全部过一遍,二分类只有一个输出,每个训练的数据给它分一个数,你把这个数从大到小排一遍,验证集也可以得到一个数,也从大到小排一遍。
然后你就从大到小随机抽一些采样,就会发现你数据的问题在哪。不断地做这个过程,不断地改进新的数据集,这个将会是你的最大的收益。也就是改你的数据扩大你的数据集,使你的数据覆盖的种类更全,收益可能是几十个百分点的,而调整你的优化策略可能只是几个百分点的收益,正则化方法可能只是千分位上的收益。
所以,理解神经网络背后的优化过程,理解你的数据才是最重要的。具体的可视化、数据采样的方法往往需要结合问题本身发挥创造力。当训练、验证数据大致满足需求之后,接下来要做的就是训练网络,不断减小训练集上的loss。如今有大量的开源工具,对于一些主流的任务,通常可以方便的找到适合的网络结构以及相应的超参数。
大家只要结合自己的计算资源上的限制对这些网络做些适当的剪裁就可以,这里就不展开了。下面介绍一些训练过程中常见的情况以及如何调整。训练中常见的困难是loss不降,常见的情况是输入数据从下往上传输的时候,某一层的表示完全相同(比如某层的激活函数为0),这样学习自然就无法进行了。
因此在训练的过程中查看激活函数的相关统计信息是个好习惯。常见的情况,比如到softmax的连接矩阵w迅速变小,这有可能是由于数据类别分布非常不均衡导致的,这时候做些采样以及适当改变mini-batch的大小可能会有缓解。除了考虑数据样本的均衡之外,也可以适当地改变这层连接矩阵的参数初始化。当网络中间某层的激活函数总是输出0,这种时候loss的下降也会停止。
这时候可以考虑几个方面,首先在这个激活函数之前是否有Batch Normalization,如果没有最好加上试试(对于现在的前向网络,最好保证除softmax之外的激活函数之前都有BN),如果有BN也可以进一步检查eps是否设的过大,可以适当调小试试;其次也可以检查一下是否ReLU造成的,这时候也可以试着改变这一层的bias和BN的beta的初始化(初始化成某个正数,比如+1);第三,如果改变了原始的网络结构,那么也最好避免表示的瓶颈(这一层的隐单元个数不要比之前层的隐单元个数少得过多)。
如果是从输入开始的第一层就激活为0,那么就要检查数据的预处理和相应参数初始化。一种很简便的方式,则是让输入数据先通过一个Batch Normalization层,然后再连接后面的w。训练集上的loss能够正常的下降之后,那么接下来就需要看验证集上的表现了。如果我们是不计代价地获得验证集的表现,那么就像Yann LeCun给的建议一样,首先应当通过不断地增加网络的大小使得出现overfitting(训练集的loss越来越低,而验证集的loss降低到某个程度之后不变甚至开始变高)。
说到这里,插一句。神经网络是非常灵活强大的模型,也就是说只要模型的大小足够,那么应当可以完美地拟合训练数据。如果当你发现没有办法完美拟合训练集,假如优化过程没有问题,那么很大的可能性是训练数据中存在自相矛盾的数据,比如同样一张图在训练集出现多次,并且每次的label又各自不同。语言模型往往很难完美拟合训练集也是类似的原因,就是不同的词却有相同的(或极相似的的)上下文。提升验证集的效果最直接效果最好的办法就是在训练集中增加更多的各式各样的与验证集类的数据。
这个听上去像废话,这里强调一下是希望大家时刻记住条件允许的时候,这总是应该最先考虑的努力方向;其次,依据关注的问题不同,各式各样的数据增强往往对结果也会带来很大的提升。除去上面两种,那么接下来可以考虑各种正则化方法。先讲两种比较容易被大家忽视的正则化。
首先,尽可能在每轮的训练中彻底地shuffle训练数据,这会带来一定的正则化效果(特别是在到处有Batch Normalization的网络,充分的shuffle可以避免某些数据总是出现在同一个mini-batch),其次,在训练效率和问题本身允许的情况下,尽量尝试更小的mini-batch。小的mini-batch可以使得sgd的过程中产生很多对模型推广有益的噪声。此外,dropout、weight decay都应该尝试,也可以适当的调整他们的参数。这些的调整,对于验证集的结果提升往往比较有限。
最后,对于那些离散的长尾输入数据(比如一些语言相关的输入),也可以考虑在输入层的参数上加入L1正则化。
-End-
雷锋网特约稿件,未经授权禁止转载。详情见。
原创文章,作者:ItWorker,如若转载,请注明出处:https://blog.ytso.com/126798.html