转载自知乎:点击打开链接
本笔记中的所有取自课件的多媒体版权属于斯坦福大学。
训练神经网络的核心是一个优化问题,给定损失函数并定义网络权值,损失函数告诉我们这些权值在解决对应问题时的表现是好还是坏。假设在当前权值下,损失函数给出了等高线图,这里是一个二维的问题,X和Y轴表示两个权值,图上的颜色表示损失值,在这个问题中只优化 两个参数,目标是要找到红色最深的区域,这对应了损失值最小值的权值。之前已经使用过SGD来优化这个问题来解决这个问题,但是这个简单的优化算法会在实际使用中会产生很多的问题。
![](https://i-blog.csdnimg.cn/blog_migrate/36437bf38dec58f2b2cbef03ab8521b6.jpeg)
SGD算法的其中一个问题是,假设目标函数如下,有两个参数 ,当对其中一个进行改变时,当在水平方向改变值,损失函数的变化非常的慢。当在垂直方向上改变值时,损失函数的变化会较快。对于这样的情况,对损失值是不好的,它是Hessian矩阵中的最大奇异值和最小奇异值之比。但直观地看,损失值的等高线看起来像taco shell,它在一个方向上非常的sensitive,在另外的方向则并没那么sensitive。如果在这样的函数上运行SGD算法,会得到zigzagging的图形,原因是这类目标函数的梯度的方向并不是与最小值成一条直线,当计算梯度并沿着改方向前进时,这就可能一遍又一遍地跨过这些等高线,zigzag地前进或者后退。所以在水平维度上的前进速度会非常的慢,因为在这个方向上敏感度较低,但是在垂直维度上非常的敏感并做zigzag运动。事实上这个问题在高维度的空间上变得更加普遍,因为神经网络中会有非常多的参数,它会沿着上亿个方向进行运动,在这么多的不同的运动方向上,在最大值和最小值之间的方向上的比例可能会较高,这是SGD的表现就不会太好。
![](https://i-blog.csdnimg.cn/blog_migrate/48ccc27f053a114a91ed6538a7ae27a0.jpeg)
另一个SGD的问题是会陷入局部极小值。这里图中的X轴显示的是一个参数的值,Y轴显示的是损失值,现在目标函数曲线的中间有一段是凹进去了,这时SGD会卡在这个凹陷的地方,即卡在局部极小值。在那个位置梯度为0,因为那一段是平缓的,这时SGD将不会做任何的动作。
![](https://i-blog.csdnimg.cn/blog_migrate/7e4d3c20be76459253337d96fa0de684.jpeg)
相比于局部最小值,鞍点是往一个方向是向上,往另一个方向是向下,同时在当前的位置梯度为0。在这种情况下,SGD是会被卡在鞍点上,因为梯度是0。在一维问题中,局部极小值看起来是个大问题,鞍点看起来是个小问题,但实际上一旦涉及到高维的问题,这恰是相反的。考虑一下在一个有上亿参数的空间中,鞍点意味着在当前点上的某些方向损失会增加,某些方向的损失会减少,同时它也会出现得更加频繁,基本上会在任何点上出现。然而在局部极小值点上,在上亿个方向中的任何一个方向的前进损失都会变大,所以在高维空间时这种情况就会变得比较少。根据最近的研究,有时训练出现问题的地方不是在鞍点上,而是在鞍点附近,在下面的图中可以看到鞍点的梯度并不是0,但是该点的斜率是非常的小。这意味着如果向梯度方向前进而梯度却非常的小,任何时候当当前的参数值在目标等高线图上接近鞍点时,前进的速度都会非常的缓慢。
![](https://i-blog.csdnimg.cn/blog_migrate/4a76a2dabfad3ca8bef9e4cfa1ca2192.jpeg)
SGD还有随机性的问题,因为它是随机梯度下降,而损失函数是通过多次重复计算不同的实例的损失来定义的。例如在下面的例子中,假设N是整个训练集,可能N为100万。每次计算损失都会耗费很大的计算能力,我们一般都会通过小批量的实例对损失和梯度进行估计,这意味着实际上并不会每一步都去计算真是的梯度,而是在当前点对梯度进行noise的估计。在下面的有图中的每一个点的梯度上加入了random uniform noise来扰乱梯度,并在这样的情况下运行SGD,这是SGD可能要花很长的时间才能得到极小值。
这里有一个很简单的方法就能解决这个问题,其做法是在随机梯度下降中加入一个动量项。下图的左边是经典的SGD,而在右边有一个非常小的方差称为带动量的SGD,这里包含了两个等式和五行代码,其思想是保持一个不随时间变化的速度,并且将梯度估计添加到这个速度上,然后在这个速度的方向上前进而不是在梯度的方向上前进,这里也有和摩擦有关的超参数 。现在在每一步采用当前的速度,然后用摩擦系数
来对其进行衰减,
有时取值比较大,例如通常选择0.9,接着将其加到梯度上。
![](https://i-blog.csdnimg.cn/blog_migrate/e2add907d895958db9b37baf9da88f79.jpeg)
这个非常简单的做法实际上帮助我们解决了几乎所有刚才讨论过的问题。当去思考在局部极小值点鞍点发生了什么,想象在这个系统中的速度,可以理解为一个球往山下滚,它会下往下滚的过程中速度变快,一旦加上了速度,当通过局部极小值点的时候仍然会有速度,即便在这里没有梯度,这样就能够越过这个局部极小值点并继续往下滚,在鞍点也是类似过程。如果思考在不好的情况下有这种zigzagging的近似梯度,一旦使用动量这些zigzag就会很快相互抵消,这就可以有效地减少朝sensitive方向前进的数量,而在水平方向上的速度只会不断地增加,实际上还会加速在less sensitive的维度上下降。在这里添加动量实际上也会有助于处理高维度的问题。下图右侧展示了同样带有noise的梯度下降可视化过程,黑色的曲线是常规SGD,它会在zigzagging地经过所有地方,蓝色的曲线是带有动量的SGD,可以看到因为对SGD添加了动量而随着时间的变化得到了速度,而noise在估计梯度时被相应地抵消,所以最后能更加平稳地接近最小值点。
当使用带有动量的SGD时,例如在下图中的红色的点是当前的位置,在当前的位置有一些红色的向量表示梯度或者是对当前位置的梯度估计的方向,绿色向量是速度向量的方向,当进行动量更新的时候,实际上是在根据这两者的平均权值前进,这有助于克服梯度估计中的一些noise。这个动量也可以作一个轻微的变化,叫做Nesterov加速梯度。在普通的SGD动量中是估算当前位置的梯度,然后去速度和梯度的混合,在Nesterov加速梯度中,这里是从红色的点开始,然后在取得的速度的方向上前进,然后评估这个位置的梯度,随后回到初始位置将速度和梯度合起来。这是在把信息一点点混合在一起,如果速度的方向实际上有一点错误,那它可以让你在目标函数的等高线图中更大一点的部分中加入梯度信息。
![](https://i-blog.csdnimg.cn/blog_migrate/060eea93d220f6be9ada9ebe18cc9dc2.jpeg)
下面是Nesterov动量的公式,这是根据之前的速度向前进一步,然后计算此处的梯度。当前进到下一步时,实际上是在速度的方向上前进,这就是从多个点上合并信息。Nesterov公式会令人感到不便,因为当使用SGD来优化神经网络的时候,通常希望能同时计算损失函数和梯度,而Nesterov的动量优化形式会对此造成破坏。这个问题可以通过换元法解决,这里把 替换为
并化简公式成为另外一种形式,现在就可以同时计算损失函数和梯度。一旦进行了换元,公式
看起来就和一般的SGD动量优化方法中更新速度向量是一样的,可以得到当前位置的速度和计算梯度并用相减的方式将两者混合。在
中实际上是在更新参数向量,这里在用当前点的参数向量加上当前速度,在加上一个权值化的当前速度的与前一个速度的差值。所以Nesterov动量是包含了当前速度向量和前一个速度向量的误差修正。
![](https://i-blog.csdnimg.cn/blog_migrate/4dce4ddc7a97fabf1cdde30a12a0ddc9.jpeg)
再看看这三种优化方法的可视化比较,可以发现黑色的曲线是SGD,会逐渐被困在局部极小值点;SGD+Nesterov和SGD动量优化是绿色和蓝色曲线,它们会借助构建的速度来越过局部极小值点,因此这两种发方法能自我修正从而到达真正的最小值点。另一方面可以看到动量SGD和Nesterov动量的一个不同,由于Nesterov有校正因子的存在,所以与常规方法相比它不会那么剧烈地越过局部极小值点。
另一种常见的优化方法是称为AdaGrad,它的核心思想是在优化的过程中需要保持一个在训练过程中的每一个点的梯度的平方和。与速度向量不同的是现有有一个梯度的平方项,在训练的过程中会一直累加当前梯度的平方到这个梯度的平方项,当在更新参数向量时会除以这个梯度平方项。
![](https://i-blog.csdnimg.cn/blog_migrate/06f197b02fd95826e8de1c57fd4de30f.jpeg)
这样的放缩对于矩阵维度很大的情况下会有所改进,因为这个思想是如果有两个坐标轴,沿着其中一个坐标轴方向有很高的梯度而另一个坐标轴方向却有很小的梯度,那么随着累加小梯度的平方,会在最后更新参数向量时除以一个很小的数字,从而加速了在小梯度维度上的前进速度,然后再另一个维度方向上,由于梯度变得特别大,所以会除以一个非常大的数,这样就降低了这个维度方向上的前进速度。
![](https://i-blog.csdnimg.cn/blog_migrate/5c8babf7f23f50beb8d443b12df8b702.jpeg)
但这会存在一个问题,当t(时间)越来越大的时候,在训练过程中使用AdaGrad会让步长变得越来越小,因为一直在随着时间更新梯度平方的估计值,所以这个估计值在训练过程中一直随着时间单调递增,这会导致步长随着时间增大而变得越来越小。在学习目标是一个凸函数的情况下,有理论证明这个特征的效果很好,因为当接近极值点时候会逐渐的慢下来最后达到收敛。这点是AdaGrad在凸函数的情况下的一个很好的特性。但是在非凸函数的情况下事情就变得复杂,因为当到达一个局部极值点是,使用AdaGrad会让你困在这个局部极值点从而令训练过程无法继续下去。
![](https://i-blog.csdnimg.cn/blog_migrate/295b8b77d0241341addcaad5af33c118.jpeg)
因此AdaGrad有一个变体叫做RMSProp,在RMSProp中仍然计算梯度的平方,但并不是仅仅简单地在训练中累加梯度平方,而是让平方梯度按照一定比率下降,这看起来就和动量优化的方法很像,除了这里是给梯度的平方加上动量,而不是给梯度本身。现在有了RMSProp,在计算完梯度之后取出当前梯度的平方将其乘以一个decay rate,一般取decay rate为0.9或者0.99,然后用1减去decay rate乘以梯度平方,最后将这两部分相加。现在随着训练的进行,可以想象得到的是步长和AdaGrad一样在被除以平方梯度之后,步长会在梯度下降很慢的维度上训练速度会加快,而在梯度下降很快的方向上训练速度会减慢,与和AdaGrad不同的是,由于梯度平方估计被衰减了,这就可能让训练总是一直在变慢,这也不是我们想要的。
![](https://i-blog.csdnimg.cn/blog_migrate/d52d96324f14bc8a83081e9ac7cbefc8.jpeg)
下面是SGD、SGD+Momentum和RMSProp这三种优化方法的可视化比较,分别使用黑色、蓝色和红色表示。可以看到SGD+Momentum和RMSProp都要比SGD要好,但这两个方法的轨迹上有一点不同,SGD+Momentum会先绕过最小值然后又拐回来,但是使用RMSProp的话,它会一直调整自己的路线,这样就是在每个维度上做出了大致相同的优化。在图里可能看不出来,但是这个图也展现了使用相同的学习率下的AdaGrad算法(绿色曲线),但是因为不见减小的学习率,走着走着就卡住了。在实际应用中,AdaGrad可能不太会出现这种问题,这里的比较对AdaGrad不大fair,也许你需要在使用AdaGrad的时候提高学习率,然后它就可以表现得像RMSProp那样,但一般都倾向于在训练神经网络时不使用AdaGrad。
Adam算法结合速度动量和利用梯度平方两个方法,这里使用Adam更新第一动量和第二动量的估计值,在红框中让第一动量的估计值等于梯度的加权和,然后有一个第二动量的动态估计值类似AdaGrad和RMSProp一样是一个梯度平方的动态近似值。它的更新方式为使用第一动量,类似于速度动量,并除以第二动量的平方根,这样Adam最后看起来有点像RMSProp加上动量,或者是像动量加上第二个梯度平方,这就像是结合了两者的优点。但是这里也存在一点问题,在最初的第一步将第二动量初始化为0,接着第二动量经过一次更新后,因为beta2,也就是第二动量的decay rate,一般是0.9或者0.99,会导致更新的值仍然非常地接近于0。现在我们在这里通过除以第二动量(是一个非常小的数)来更新,就会得到一个非常大的步长,而这个非常大的步长却不是因为这一步的梯度太大,只是因为把第二动量初始化为0。
![](https://i-blog.csdnimg.cn/blog_migrate/eaaa04b544c9755bbbeb89f7351cf325.jpeg)
使用Adam在前几次的迭代更新中会得到很大的步长从而难以收敛,所以Adam算法也增加了偏置校正项来避免出现开始时得到很大的步长。可以看到在更新了第一动量和第二动量后,我们构造了第一动量和第二动量的无偏估计,通过使用当前时间步t,现在实际上是在使用无偏估计来做每一步的更新,而不是初始的第一动量和第二动量的估计值。Adam确实是一个非常好的最优化算法,并且对于不同的问题使用Adam算法都能得到不错的结果,因此它差不多一个用来解决任何新问题的默认算法。特别是如果将beta1设为0.9,beta设为0.999,学习率为1e-3或者5e-4,一般无论使用什么网络架构都会从这个设定开始。
![](https://i-blog.csdnimg.cn/blog_migrate/1c39ab0954951d4409145f0b9b0912a5.jpeg)
下面是加上Adam和上面的三种优化算法的可视化,Adam算法用紫色曲线表示,可以看到Adam像是结合了带动量的SGD和PMSProp,它也像带动量的SGD一样但不会绕过最小值点太多和也有类似RMSProp那样尝试在所有维度上都作出相同的改进。但在这个二维的可视化例子中Adam收敛起来和其他的算法相似,但是可以看到实质上它集合了SGD动量和RMSPropd的特征。
上面介绍的这些优化算法都有学习率这个超参数,在下图中可以看到当使用不同的学习率时,学习率太高会导致梯度爆炸(如黄色的曲线),学习率太低(如蓝色的曲线)会花很长的时间才收敛,选择合适学习率需要一些技巧。
![](https://i-blog.csdnimg.cn/blog_migrate/e8b0937689b56af3fe43a3604175a170.jpeg)
有一好的方法是不必在整个训练过程中都一直固定使用同一个学习率,有时候会把学习率沿着时间衰减,这就有点像是结合了左图中的不同曲线的效果和每个曲线的优点。比如在训练开始的时候使用较大的学习率,然后在训练过程中逐渐地衰减学习率。一个衰减的策略是step decay,例如在第10次迭代时用一个衰减因子降低学习率,接着继续训练。还有使用指数衰减,这是在训练过程中持续衰减,这时的学习率是连续衰减的。
![](https://i-blog.csdnimg.cn/blog_migrate/c578e5023f883be88e28df2bb497772c.jpeg)
在残差网络的论文中,会看到像右图这样的曲线,损失先是一直平缓下降,然后是突然骤降,接着继续平缓下降,再接着又突然骤降,这时因为使用了步长衰减的学习率,这些曲线中出现骤降的地方是因为在迭代时把学习率乘上一个因子。降低学习率的想法是假设模型已经接近一个比较不错的取值区域,但此时的梯度已经很小了,保持原有的学习率只能在最优点附近来回地徘徊,这时如果降低学习率,目标函数就能继续进一步地前进优化。值得注意的是带动量SGD的学习率衰减是很常见,但是类似Adam的优化算法就很好会用到;另外要注意的是,学习率衰减是一种二阶超参数,通常不要一开始就使用,当让神经网络开始训练时,想要挑选一个不带学习率衰减的不错的学习率来作为开始,尝试在交叉验证中同时调学习率衰减和初始学习率和其他事情,这会让你怀疑人生,因此设置学习率衰减的方法是先尝试不要衰减,看看会发生什么,然后仔细观察损失曲线决定在哪个地方开始衰减。
![](https://i-blog.csdnimg.cn/blog_migrate/7ab7b09bc352ec19bcb9a7cc54da7235.jpeg)
这些所有的优化算法都是一阶优化算法,在下面的这个一维的图中有一个目标函数曲线,当前的点是这个红色的点,我们在这个点求一个梯度,并使用梯度信息来计算这个函数的线性逼近,这相当于对这个函数进行的一阶泰勒逼近。现在假设一阶逼近就是实际的函数,然后想要迈出一步来逼近最小值,但是这个逼近在稍大的区间中并不成立,所以不能够向着那个方向一下子前进太多。
![](https://i-blog.csdnimg.cn/blog_migrate/c0d31146f4cf439e44b1e1a1b0094f2b.jpeg)
事实上,这里梯度的想法用到了函数的一阶偏导,实际上是有二阶逼近的,在这里可以同时考虑一阶和二阶偏导信息,现在对函数做一个二阶泰勒逼近,即用一个二次函数来局部逼近目标函数。因为这是二阶函数,所以可以直接跳到最小值点就皆大欢喜。
![](https://i-blog.csdnimg.cn/blog_migrate/27e72b5e38a91fcb7740fc791a7076d1.jpeg)
当把这个思想推广到多维度的情况,就会得到一个叫牛顿步长的东西。计算这个Hessian矩阵,即二阶偏导数矩阵,然后求这个Hessian矩阵的逆,为了能直接走到对目标函数用二次逼近后的最小值的地方。这种优化方法与前面的优化方向相比是不需要学习率,因为这里做了二阶逼近并直接走到这个二次函数的最小值点。至少在这个牛顿法的原始版本中,是需要学习率,每次只需要直接走到最小值的点即可。然而在实际中,可能会用上学习率,因为这个二次逼近也不是完美的,你可能只要想要沿着二次函数的最小值方向前进,而不是走到最小值的位置。
![](https://i-blog.csdnimg.cn/blog_migrate/76c63e4bd7571b448be95ddb657ff4a7.jpeg)
但这个算法对深度学习来说却有点不大实际,因为这个Hessian矩阵是N×N的维度,其中N表示网络中参数的数量,如果N是一亿,那么一亿的平方就非常的大,计算机的内存肯定是存储不下这么庞大的参数量,也肯定无法求解这个Hessian矩阵的逆。
![](https://i-blog.csdnimg.cn/blog_migrate/0d13c88305c61dc08e9eeae3e3e1a1af.jpeg)
事实上大家经常用这个拟牛顿法来代替牛顿法,不是直接地去求完整的Hessian矩阵的逆,而是去逼近这个矩阵的逆,常见的是使用低阶逼近。
![](https://i-blog.csdnimg.cn/blog_migrate/45ba92072b5e17440a0d479e7b921e40.jpeg)
L-BFGS就是一个二阶优化算法,它是二阶逼近,用Hessian矩阵来逼近。在实际中可能会看到在很多深度学习的问题并不适宜使用这个算法优化,因为这些二阶逼近的方法对随机的情况处理得不是很好,而且在非凸的问题上表现上不是非常好。
![](https://i-blog.csdnimg.cn/blog_migrate/3d2a7b8e69b5df85ed85f6106a476dbc.jpeg)
因此在实践中可以考虑的是对很多不同的神经网络问题,Adam已经是一个很好的选择,但是如果是在能够允许整个批次的更新的情况下,而且也知道改问题没有很多的随机性,那么L-BFGS会是一个很好的选择。
![](https://i-blog.csdnimg.cn/blog_migrate/91763de577447a329c30d2eeaf8182a8.jpeg)
目前讲到的优化算法都是在减少训练误差和最小化目标函数,但是我们并不特别在意训练误差,而是更在意在没见过的的数据上的表现,即模型的泛化能力。我们希望减少训练误差和测试误差之间的差距,现在的问题是如果已经很擅长去优化目标函数,要怎么做来减少训练与测试之间的误差差距,增加模型的泛化能力。
![](https://i-blog.csdnimg.cn/blog_migrate/7223d82febb657c686a8a7a3ff0c666c.jpeg)
一个简单粗暴又有效的方法是模型集成(模型集成是kaggle刷分的大杀器),其中的想法非常简单,比起使用一个模型,现在选择从不同的随机初始值上训练10个不同的模型。到了测试阶段就会在10个模型上运行测试数据,然后平均10个模型的预测结果,这么做也能够稍微地缓解过拟合并提高一些性能。
![](https://i-blog.csdnimg.cn/blog_migrate/c94be564ad5ad60a7026f820d469b0bb.jpeg)
有时候可以不独立地训练不同的模型,你可以在训练的过程中保留多个模型的sanpshots,然后用这些模型来做集成学习,接着在测试阶段仍然需要把这些多个快照的预测结果做平均,但是可以在训练的过程中收集这些快照。这是在ICLR上的一篇非常好的论文,讲的是上面的想法的一个高级一点版本。这里用了一个crazy的学习率计划,学习率刚开始的时候很慢,然后非常快,接着又很慢,在接着又特别快,这样的做法是说用这样的学习率会使得训练过程中模型会收敛到目标函数不同的区域,但是最后的结果仍然不错。如果对这些不同的snapshots进行集成,就能够大幅度提高最后的性能,即使只进行了一次的训练。
![](https://i-blog.csdnimg.cn/blog_migrate/2c20bd407a20e086703dfadc8326a340.jpeg)
另外一个可能会用的小技巧是在训练模型的时候,对不同时间的每个模型参数求指数衰减平均值,从而得到神经网络训练中一个比较平滑的集成模型,然后使用这些平滑衰减平均后的模型参数,而不是使用截至某一个时间的模型参数,这个方法叫做Polyak averaging,这有时候能有一点效果。
![](https://i-blog.csdnimg.cn/blog_migrate/ceca60807046cfa29fb540e975fcbd95.jpeg)
这里会有一个问题是如何能够提高单一模型的表现。使用集成学习时,测试阶段还是要跑比如10个模型,这感觉并不是太好。这时就想要找到一些方法可以提高单一模型的效果,正则化正是这样的一种方法。在模型中加入正则项来防止训练集上的过拟合,从而使测试集上的效果得到提升。
![](https://i-blog.csdnimg.cn/blog_migrate/8c911814d7cd77bdbc1f544169f74322.jpeg)
前面的lecture中已经讲述过几种正则化的方法。L2正则化在神经网络中的意义并不是很明确,有时候我们会在神经网络中选择其他方案。
![](https://i-blog.csdnimg.cn/blog_migrate/131453950cb4646f445f19c8b100aa71.jpeg)
一个在神经网络中非常常用的方法是dropout,dropout非常简单,每次在网络中前向传播时,在每一层随机将一部分神经元置零,每次被置零的神经元都不是完全相同的。每次经过神经网络中的一层,算出这一层的值并随机将其中一些置零,然后继续在神经网络中前进。如果把下图左边这个全连接网络和右边经过dropout的版本进行对比,可以看到dropout后的神经网络像是同样的网络变小了,因为只用到了其中一部分的神经元,其中每次前向传播用到的神经元都是不同的部分。
![](https://i-blog.csdnimg.cn/blog_migrate/308f833fefe3a1749ab4409ae18eff66.jpeg)
实际上只需要两行代码就能实现dropout,其中每行dropout一次,例如这里是一个三层的神经网络并加上了dropout。
![](https://i-blog.csdnimg.cn/blog_migrate/02e204cc25003293492de4de363419aa.jpeg)
dropout这个想法可取的一个比较勉强的解释是,人们觉得dropout避免了特征之间的相互适应。假设要分类判断是不是喵,可能在网络里一个神经元学到了有一只耳朵,有一个神经元学到了有尾巴,有一个神经元学到了输入图像有毛,然后将这些特征组合在一起来判断是否是猫,但现在加入dropout后,判断是否为喵时,网络就不能依赖这些特征组合给出的结果,这时就要通过不同的零散的特征来判断,这也许在某种程度上抑制了过拟合。
![](https://i-blog.csdnimg.cn/blog_migrate/14a9776a5e1f0c82d000306665afb9e3.jpeg)
另一种最近出现的关于dropout的解释是在单一模型中进行集成学习,如果观察左图,在应用dropout之后是在一个子网络中用所有的神经元的子集进行运算,每一种可能的dropout方式都可以产生一个不同的子网络,所以dropout像是同时对一组共享参数的网络进行集成学习。另外,因为dropout的可能性随着神经元个数呈现指数倍的增长,这就不可能穷举每种情况,这可以看作是一个超级庞大的网络集合在同时训练。
![](https://i-blog.csdnimg.cn/blog_migrate/c78f101443bca51c897b01891e60adf5.jpeg)
如果使用了dropout,就是把神经网络的基本运算都改变了,在之前神经网络中有权值为w的函数f,其中输入为x并函数输出为y。但是现在我们额外有了一个输入z,表示dropout中被置零的项,其中z的值是随机的,在测试阶段引入一些随机性可能会出现问题。这时就会想到平均这个随机性,如果把它写出来,可以考虑使用一些积分的方法来边缘化随机性,但在实践中这个积分是完全难以对其进行求解。你可能想做的一件事情是通过采样来逼近这个积分,例如对z进行多次采样,然后才测试阶段将其平均化,但这仍然会引入一些随机性,还是会存在问题。
![](https://i-blog.csdnimg.cn/blog_migrate/573a0d89e2e58cb77117495a9efbcc56.jpeg)
Thankfully,在dropout的情况下可以用一个简单的方式来局部逼近这个积分。如果考虑单个神经元输入是x、y和权值为 而且输出是a,然后在测试阶段得到的a的值是
。现在想象一下当使用dropout训练了这个网络,置零神经元的概率是0.5,在这个例子中训练期间的a的期望值可以通过如下方式计算得出为
。这里就出现了训练阶段a的期望值是测试阶段的一半,这时在测试阶段可以做一个简答的事情是在测试时没有任何的随机性,而是用dropout的概率乘以这个输出,这样训练和测试阶段a的期望值都是一样的。这就有点像简单地局部逼近这个复杂的积分,这就是对于dropout在实践中非常普遍的做法。
![](https://i-blog.csdnimg.cn/blog_migrate/18704575bddd5895c1b51b6312cf760d.jpeg)
在使用dropout时,对于预测函数用dropout的概率乘以输出层的输出。
![](https://i-blog.csdnimg.cn/blog_migrate/02cb0d28915c8c48ef2a73e5bd8d558d.jpeg)
总而言之,dropout在前向传播的过程的使用是非常的简单,按照指定的概率随机对神经元置零,然后在测试阶段的预测函数的输出里乘以这个指定的概率。
![](https://i-blog.csdnimg.cn/blog_migrate/4fc9ef322f7692b726d92e492107670b.jpeg)
值得注意的是有时候会看到一个常见的技巧,叫做inverted dropout。也许在测试阶段会更关心效率,所以想在测试的时候去除乘以概率p的这个额外的乘法操作,那么此时可以在测试阶段使用整个权值矩阵,但在训练阶段除以p。因为训练阶段一般会使用GPU,这时候的计算能力足够强大到让你不会在乎在这个阶段做一个额外的乘法运算操作。反向传播只在未被置零的节点上传递梯度,当用dropout训练的时候,一般要用更长的训练时间,因为在每一步只是更新神经网络的一部分,但是收敛之后模型的robust会更好。
![](https://i-blog.csdnimg.cn/blog_migrate/fb4c8c3fdae6762eee9c2647e3c35467.jpeg)
这里还有一个更通用的正则化方法,在训练阶段给神经网络添加一些随机性来防止它过拟合训练数据,然后在测试阶段要抵消掉所有的随机性,希望能够提高模型的泛化能力。dropout可能是最常见的使用这种方法的例子,但实际上batch normalization也符合这个想法,在训练阶段的batch normalization中,一个数据点可能和其他不同的数据点出现在不同的mini batch中,对于单个数据点来说,在训练过程中该点会被如何正则化具有一定的随机性。但是在测试阶段,通过使用一些基于全局估计的正则化来抵消掉这个随机性,而不是使用每一个mini batch来估算。实际上batch normalization倾向于具有和dropout类似的正则化效果,因为它们在训练阶段都随机地引入了某种随机性或者noise,但是又在测试阶段抵消掉。当使用batch normalization来训练神经网络的时候,有时会不使用dropout,因为batch normalization已经给神经网络增加了足够的正则化效果。但是dropout在某种程度上更好,因为可以通过调整参数p来调整正则化的强度,而在batch normalization中并没有这种控制的机制。
![](https://i-blog.csdnimg.cn/blog_migrate/efc0f24059ff4fcf0a2217e10b9907ec.jpeg)
数据增加也是一种正则化的方法,在训练的过程中以某种方式随机地转换图像,同时令标签保持不变,现在利用这些随机转换的图像进行训练而不是原始的图像。
![](https://i-blog.csdnimg.cn/blog_migrate/8903313b84b7f1db0b67ae876999e88c.jpeg)
数据增强的方式有随机的水平翻转,例如对一张喵的图像水平翻转,这仍然是一张喵的图像。
![](https://i-blog.csdnimg.cn/blog_migrate/a8980806b15673d5751d8f0ecf7f5d34.jpeg)
也可以从图像中随机抽取不同大小的裁剪图像,因为对喵的图像随机裁剪,这个图像仍然是一只喵。接着测试过程中,通过评估一些固定的裁剪图像来抵消这种随机性,一般是图像的四个角落、中间以及它们的翻转。
![](https://i-blog.csdnimg.cn/blog_migrate/3b83f4b5c706f62915435dfab3f537f8.jpeg)
同样在数据增强中有时会使用色彩抖动,即会在训练阶段随机改变图像的对比度和亮度,也可以通过色彩抖动来得到一些更复杂的结果。当试图在数据空间的主成因分析的方向上产生色彩抖动,或者以某种与数据相关的方式进行色彩抖动,但是这些方式不太常见。
![](https://i-blog.csdnimg.cn/blog_migrate/0e9984658b5799e244261ced99d84f9f.jpeg)
一般来说,数据增强是非常普遍的事情,你可以将其应用于任何问题,不管想要解决什么问题,都可以考虑在不更改标签的前提下对数据进行转换。现在在训练的时候,只需要将这些随机转换应用于输入数据,这种处理方式对神经网络有正则化的效果。一般的处理方式是在训练的时候又增加了某种随机性,然后再测试的时候将它们抵消掉。
![](https://i-blog.csdnimg.cn/blog_migrate/2f65a716d262b2b0db5fa8480dc3b522.jpeg)
这里还有一种与dropout相关的算法,叫dropconnect,这与dropout也是同样的想法,但不是在每次前向传播的过程中将激活函数置零,而是随机将权值矩阵的一些值置零,这与dropout也能产生同样的效果。
![](https://i-blog.csdnimg.cn/blog_migrate/4b3bbc3e85897c57b0a63b1ef80b8c58.jpeg)
还有一种不大常见的方法,叫做部分最大池化,一般当进行2×2最大池化时,会把固定的2×2的区域进行池化。但是现在通过部分最大池化,每次在池化层操作时,都将随机选择池化的区域。如下图所示,展示了3个不同的在训练时可能遇到的随机池化区域,在测试的时候有很多方法可以去抵消随机性,要么使用一些固定的池化区域,要么选取很多样本对其取平均。
![](https://i-blog.csdnimg.cn/blog_migrate/1882a61b6fb876c34c08523826a362e0.jpeg)
另一个漂亮的论文提出了随机深度的方法,如下图所示,这里有一个很深的神经网络,在训练时随机地从网络中丢弃部分层,在测试的时候使用全部网络层。这个想法是非常前沿的研究,在实际操作的时候并不常用。
![](https://i-blog.csdnimg.cn/blog_migrate/e0852864a7fe43dacc3b39d4294711aa.jpeg)
最后简答地介绍一下迁移学习,可以看到使用不同的正则化方法可以有助于减少训练误差和测试误差的差距。有时候出现过拟合是由于数据量不够,你希望得到一个强大的模型,但这样的模型在使用小数据集合是很容易会过拟合,正则化是防止过拟合的一种方法,另一种方法是使用迁移学习。使用迁移学习不需要大量的数据也能训练卷积神经网络。
![](https://i-blog.csdnimg.cn/blog_migrate/2067eefd090f74a30d8a86ff05818f66.jpeg)
迁移学习的思想非常简单,首先使用卷积神经网络,例如VGG架构,在例如ImageNet这样的大数据集上训练。现在尝试把从这个数据集中训练处的特征提取能力用到一个小数据集上,例如只有10个类别的数据集,接下来一般的做法是修改从最后一个层的特征到最后的分类输出之间的全连接层。这需要重新随机初始化这部分的权值矩阵,对于ImageNet是4096×1000维的权值矩阵,对于小数据集的分类,权值矩阵的大小就变为4096×10。重新随机初始化最后的矩阵,冻结前面层中权值,现在就只需要训练一个线性分类器,即最后的全连接层,让它在这个小数据集上收敛。如果当数据更大时,这时可以尝试精调整个网络,即在最后一层收敛后,试着更新整个网络的权值。一个通用的策略是当更新网络时,将学习率调低,因为最初的网络参数可能在ImageNet上收敛的,这时模型的泛化能力已经很强了,现在只是希望让它们有微小的调整来适应当前的数据集。
![](https://i-blog.csdnimg.cn/blog_migrate/f963d024710f987fb0da02632e7a5619.jpeg)
当使用迁移学习时,可以想象成是一个2×2的网格,在一侧可能会有很小的数据集或者很大的数据集,可能你的数据和一些大型数据集的图片很相似,例如ImageNet有很多动物、植物之类的图片。如果这时只是要分类类似的图片,但数据量很少,那就可以在ImageNet的预训练模型的基础上,只训练最后一层的线性分类器;如果有更多的数据,那就可以精调模型。但是如果数据集合ImageNet不相似并且数据量很少,那么这时就可能需要重新初始化大部分的网络,多做一些实验;当数据量大的时候,这时候就要调整大部分网络的参数。
![](https://i-blog.csdnimg.cn/blog_migrate/45b6928c2f7a2b063818d9c9622a02f5.jpeg)
迁移学习的思想是非常普遍的,如果你读计算机视觉的论文时,会经常看到类似的针对不同任务的系统图。下图左边是目标检测,右边是Imgae Caption,其中所有的模型有一个卷积神经网络处理图像。目前无论是计算机视觉的任何应用,大部分人都不会从头训练这些东西,大多数情况下,卷积神经网络都是在ImageNet上预训练,然后根据任务精调。同样的在Imgae Caption中,有时候可以预训练一些词向量,再根据具体的网络精调,但是这种一般在实际中一般并不常用。
![](https://i-blog.csdnimg.cn/blog_migrate/2c4f90f89bb659b8d26e07d76d790e6f.jpeg)
最后是今天所讲内容的一个小结。
![](https://i-blog.csdnimg.cn/blog_migrate/5b3aa7fadc279519b59ed26275efe5c5.jpeg)