吴恩达深度学习专项课程共分为五个部分,本篇博客将介绍第二部分改善深层神经网络专项的第三周课程:超参数调试、Batch Normalization和深度学习框架。
目录
1.调试处理
在之前的课程中,我们知道训练神经网络需要很多不同的超参数设置 ,那么如何选者一组合适的超参数呢?本小节将介绍如何系统的进行超参数调试的技巧。
- 超参数
训练神经网络的一大难点就是超参数的调试,下面列举了一些常见的超参数以及重要性:
超参数的调试过程,一般按照重要性进行:
学习率毫无疑问是最重要的超参数。
Momentum超参数、隐层的单元数以及mini-batch的大小属于第二梯队的超参数。
隐层的数量、学习率衰减率(如果使用学习率衰减策略的话)属于第三梯队的超参数。
如果使用Adam优化算法的话,它的超参数一般采用默认设置(0.9,0.999,
),不进行调试。
- 使用随机值:不要使用网格
在传统的机器学习问题中,一般使用网格来搜索超参数,如下图所示:
在上图中尝试的是5*5的网格,所以有25组超参数选择,尝试25种模型来找到效果最好的那一个,当然网格的规格是可以调整的。这种方法在超参数比较少的时候效果不错,而且它还有一个缺点:它会把所有超参数的重要程度看作是相同的,比如举一个极端的例子,假设超参数1是学习率,超参数2是Adam分母中的,我们知道一般来说学习率是最重要的超参数,而
根本不用调,几乎对模型起不到任何效果。而按照网格搜索的话,虽然尝试了25种不同的模型,而重要的超参数学习率只取了5个,所以效果不是很好。
在深度学习问题中,超参数数量比较多,而且对于一个特定的模型来说,超参数的重要程度不好说(之前我们列举的只是一般的粗略的超参数重要程度,对于某个特定模型来说重要程度可能是不确定的)。所以我们一般采用随机取值的方法,如下图所示:
与之前一样尝试25组不同的超参数(即,25种不同的模型),如果随机取值的话,假设超参数1是学习率,超参数2是Adam分母中的,此时我们将会尝试25种不同的学习率,相较之前的5种有了大幅度提升。而且随机取值,有利于发掘很多潜在的重要超参数取值。我们知道深度学习的超参数比较多,远不止上图中的3种,如果有n个超参数,那么搜索空间将会是n维空间。
- 从粗糙到精细的搜索过程
假设目前有2个超参数,即在2维空间中随机搜索超参数取值。如果你发现有几组超参数的取值对于你所优化的目标来说表现特别好,那么就可以在那几组超参数附近,划定一个较小的范围,进行更密集的随机取值,如上图所示。对于有多个超参数的情况也是一样的,先大范围粗略的搜索,然后在划定一个小的范围,进行更精细的搜索。
2.为超参数选择合适的范围
随机取值可以提高超参数搜索的效率,但随机取值并不是在有效范围内随机均匀取值,而是选择合适的标尺,这对于超参数的搜索非常重要。
- 随机均匀取值
对于某些超参数来说随机均匀取值(整数值)可能是适用的,如隐层单元数和隐层数量#layers:、
对于来说假设它的有效范围是50-100,那么就可以在这个区间中进行随机均匀取值;对于#layers来说,假设它的有效范围是2-4,那么也可以在这个区间中进行随机均匀取值或采用网格进行取值。
- 为超参数设置一个合适的标尺
对于一些超参数如学习率来说,简单的随机均匀取值可能不再适用。
假设可以尝试的学习率最小值是0.0001,最大值为1,如果在该区间采用随机均匀(0.1)取值的话,就会出现下图中情况:
此时会把90%的搜索资源放在0.1-1这个区间中,0.0001-0.1这个区间只占10%的搜索资源。
此时一般采用对数坐标,对学习率的最小值取对数得到区间左端点a,对学习的最大值取对数得到区间右端点b,对区间[a,b]进行随机均匀取值(浮点数值)得到变量r,学习率的取值.此时,0.0001-0.001,0.001-0.01,0.01-0.1以及0.1-1这四个小区间将占用相同的搜索资源,如下图所示:
- 指数加权平均超参数
假设指数加权平均超参数的合理取值范围是0.9-0.999,和之前一样,在这个区间中进行随机均匀取值并不是一个好办法。此时1-
的范围是0.1-0.001,通过对原始区间端点取对数得到新区间的端点,在新区间中进行随机均匀取值(浮点数值)得到r,然后
,
.那么0.9-0.99和0.99-0.999这两个区间所占的搜索资源是相同的,如下图所示:
为什么不在原始区间中随机均匀取值呢?我们知道=0.9时,指数加权平均可以大致看作一个大小为10的滑动窗口,即平均了10个值,近似的滑动窗口大小
.也就是说当
越近于与1时,窗口的大小变化越敏感。比如
在区间0.9-0.9005取值时,大致都是平均了10个值,而当
在区间0.999-0.995取值时,大致平均了1000~2000个值之间。而采用对数坐标取值后,0.9-0.99和0.99-0.999占用的搜索资源相同,也就是说越接近于1时,越接近敏感地带时,则尝试更多的取值。这样比较合理。
如果你没有为超参数的选择确定一个合适的标尺,也不要担心,如果在原始区间中采用随机均匀取值,取得数值比较多时,也可以得到一个不错的效果,尤其是结合采用了从粗糙到精细的搜索策略。
3.超参数训练的实践 Pandas vs. Caviar
- 不时地重新测试超参数
机器学习模型的迭代过程:
首先有一个初始的idea(如一组初始超参数),然后编程实现,最后做实验查看模型效果,进一步更新idea(尝试不同的超参数选择)以此循环迭代,直到找到一组最好的超参数,得到一个最优的模型。
如今,深度学习在多个领域有广泛的应用,而且某一个领域采用的深度模型超参数配置通常可以应用于其他领域,某一领域的研究者现在越来越多的阅读其他领域的研究论文,以找寻灵感。
当你为模型找到一组最优的超参数配置时,要不定时(几个月)的进行重新测试,因为可能一些条件的变化,该组超参数或许不再是最好的,所以需要定期重新测试,保持模型效果的稳定性。
- 搜索超参数的两种重要的思想流派、
Babysitting one model:
通常发生在数据非常庞大且没有足够的计算资源(GPU等),可就是说你只可以一次负担起试验一个模型或一小批模型。这种情况下,即使在试验时,也可以不断改良,如下图所示:
比如在第1天,随机初始化模型参数/超参数,逐渐观察自己的学习曲线(如代价函数曲线,误差曲线等),比如在这一天末时你发现他学习的不错;那么在第2天你可以尝试增加一点学习率,继续观察效果,两天后,他依旧不错;第三天可以尝试增加或减少一下,每天你都观察他不断的调整你的超参数;也许有一天你发现你的学习率大了,则可以回归之前那一天的模型,减小一下学习率或进行一些其他尝试。也就是说每天都花时间照看这个模型,保存每一天训练得到的模型参数(便于接着上一天的训练效果,进行其他的超参数调试,或某一天效果不好,回溯到前一天)观察它的表现,耐心调试超参数。
注意每次调试超参数的周期不一定是上例中的1天,可能是几天/几周或一个月等。
这种方式被称为Pandas方式,熊猫的孩子一般很少,它会每一天都对孩子进行悉心照料,一个熊猫宝宝就相当于一个模型。
Training many models in parallel:
当你的计算资源非常丰富时,可以同时并行尝试不同的模型,每个模型有不同的超参数设置。绘制不同模型的学习曲线如代价函数曲线,误差曲线等,以找出最好的那个模型或超参数选择。
这种方式被称为Caviar鱼子酱方式,鱼类每次会产下很多卵,不必对其中的某一个进行过多照料,最后期望他们中的某一个或某几个表现好就行了。
在计算机视觉或在线广告等领域,由于数据量非常大,而且需要采用的模型非常多(超参数选择非常多),如果采用鱼子酱方式对计算资源的要求非常高,所以采用Pandas方式更多一些。
4.正则化网络的激活函数
Batch归一化会使超参数搜索问题变得容易,使神经网络对超参数的选择更加稳定,超参数的范围会更庞大,工作效果也很好,使你可以很容易的训练深层网络。
- 正则化输入加速训练
之前在第一周的课程中,我们曾经学习过,当正则化输入特征时,会加速神经网络的训练,对于逻辑回归和神经网络来说都是适用的。如对于下图的逻辑回归来说,我们可以正则化输入特征,来加速模型参数w,b的训练:
对输入特征正则化的过程如上图所示。对加速原理的直观理解,如果不对输入进行正则化,那么代价函数的轮廓可能是扁长的,需要很长时间很多次迭代才能达到全局最小值,而正则化输入后,代价函数的轮廓曲线会变得更正和圆,可以通过更少的迭代直接达到最小值。对于高维空间,高维的参数原理也是类似的。
那么对于深层网络来说,是不是可以既对输入特征进行标准化,而且也对每一个隐层的激活输出进行标准化呢?即对进行标准化是不是可以加速
的训练呢?这就是Batch正则化的原理。实际上我们是对
进行batch 正则化,到底是对
还是
进行batch norm存在很大的争议,不过我们推荐对
进行batch正则化,这也是本节课介绍的版本:
- 实现Batch Norm
对于m个输入样本(m为mini-batch的大小):
是某一个隐层的中间结果,它是(
的矩阵
。它的每一列
代表一个样本产生的中间结果。
某一个隐层的batch norm过程如下:
和之前对输入进行标准化类似,对隐层中的每个单元针对m个样本求均值和方差,再进行标准化,分母加避免除以0,此时得到的
的每一行服从均值为0,方差为1的分布。batch Norm与输入标准化不同的是,不希望每个隐层标准化后的结果
的每一行都是同一个分布(均值0,方差1),所以引入了两个参数
对
的每一行进行调整,得到
,最后用
代替
,如下所示:
注意是模型的参数,是
的向量(其中的每个分量是对每个隐层单元在m个样本上的均值和标准差的调整),和之前的
一样,需要计算梯度,用梯度下降法或高级优化方法进行更新。
可以使归一化后的
的每一行服从多种不同的分布,不再全都是均值为0,方差为1的分布。当然当
的分量全为1,
的分量全为0时归一化后的
的每一行将服从均值为0方差为1的分布;当
,
时相当于不进行标准化,
=
,总之,
取不同的值,归一化后的
的每一行将服从不同的分布。
引入的好处是可以让标准化后的
的每一行服从不同的分布,一个直观的理解是,如果你使用的是sigmoid激活函数,他不会让你的输入仅仅局限在一小部分,不能充分体现sigmoid非线性函数的作用:
batch norm 的作用是使隐层单元(对m个样本)的均值和方差标准化,使的每一行有固定的均值和方差,具体的均值和方差取值由
决定,学习算法可以把
更新为任意值。
5.将Batch Norm拟合进网络
上一小节介绍了某一个隐层的batch norm,本小节介绍如何把batch norm拟合进神经网络(输出层也可以有Batch Norm)。
- 将Batch Norm拟合进网络
反向传播时,需要同样要计算(
),然后:
实际中实现batch-norm可以使用深度学习框架,仅用一行代码就能搞定,一般不需要自己手写,但是原理一定要懂。
- 和mini-batch一块工作
m是mini-batch的大小
实际上参数是没有意义的,因为之后要对
进行标准化,需要减去均值,所以计算
时加上
没有任何用处:
所以模型参数只有.
- 实现梯度下降
当然也可以使用高级优化算法,如Adam,Momentum,RMSprop,注意是模型参数,是一个向量;
是高级优化算法的超参数,是一个标量。
6.Batch Norm为什么奏效
- Batch Norm奏效的第一个原因
之前我们曾将输入特征进行归一化(每个特征的取值范围差距很大),使每个特征的取值范围相近,起到了加速神经网络训练的效果。Batch Norm也采用了类似的原理,对每个隐层也做了类似的归一化,同理也能起到加速的效果。不过这只是Batch Norm作用的冰山一角,本小节将探讨一些更深层的原理。
- Batch Norm奏效的第二个原因
它会使深层(第十层)的权重比浅层(第一层)的权重更经受的住变化。接下来进行解释:假设你在用一个逻辑回归或浅层/深层神经网络做一个猫图片分类的任务:
假设你在左图的数据集上进行训练,其中y=1的样本全是黑猫;如果用训练好的模型对右边的数据集进行分类,此时y=1的样本不只是黑猫还有其他颜色的猫,你之前训练好的模型可能不再有效。
假设左图数据集和右图数据集的正负样本分布如下:
此时把在左边数据集训练好的模型,拿到右边数据集上可能不再适用,效果可能会不好。即使存在上图中绿色的决策边界,不论在哪个数据集上都能很好的完成分类,但也许你不会期望你的模型仅在左边的数据集上训练就能得到这样的决策边界。
数据分布改变也叫“Covariance shift”,如果你已经学习到了一个从X->y的映射,如果X的分布改变了, 你可能需要重新训练你的模型,即使数据分布改变了,X->y的映射关系(真实函数)并没有改变,也需要重新训练模型;如果由于数据分布改变,X->y的映射关系变了,那么情况就更糟糕了,重新训练模型的需求将会更加迫切。
为什么神经网络有“Covariance shift”问题:
试想有上图所示的深层神经网络,比如我们从第三个隐层的角度来看,此网络已经学了,它从面前面几层获取输入,希望使输出值
接近真实值y。
接下来我们把第三个隐层之前的部分遮住,想象一下把之前的结构截断,那么从第三个隐层的角度来看,是它的输入特征,可以看作
,他的工作是找到从
到
的一个映射,也许学习的参数
使网络从
到
映射的不错。
现在我们把左边揭开,网络还有参数,由于参数
是变化的,所以
也是不断变化的,那么对于第三个隐层来说,他就有了“Covariance shift”问题(和之前猫分类的例子一样)。
Batch Norm做的是减少了这些隐层单元的偏移量。也就是无论随着之前参数的更新怎么变化,但是通过batch norm后它的均值和方差是不变的,由
决定。当然神经网络可以选择强制使经过batch norm后的z的均值为0,方差为1,或其他任何均值和方差。Batch Norm限制了前层参数的更新对后层数值分布的影响程度,减少了输入值改变的问题,他确实使这些值变得稳定,神经网络之后的层就会有更坚实的基础,即使输入分布改变一些,它会改变的更少。即使当前面的层保持学习,参数不断变化,它迫使后面的层适应前面层的改变的程度减少了,可以理解为降低了前面层参数的作用与后面层参数的作用之间的联系,使网络每一层都可以自己学习,稍稍独立于其他层,有助于加速整个网络的学习。对于后层来说,前层的输出(后层的输入)不会左右移动很多,因为它会被相同的均值和方差限制,使后层的学习变得更容易些。
- Batch Norm 奏效的第三个原因
Batch Norm有轻微的正则化效果。但是不要把Batch Norm作为一个正则化方法(它是一种将你隐层单元激活值归一化的方法并加速学习)这不是它的目的,只是一个额外的非期望的效果。
当使用mini-batch梯度下降时,对每一个mini-batch进行前行传播时,我们会在当前的mini-batch上对进行Batch Norm得到
,由于mini-batch一般比较小如64,128,而不是整个训练集,所以在计算均值和方差时会有一些噪声。所以得到的
会有噪声,和dropout类似,它也会给每个隐层的激活输出
添加噪声,dropout添加噪声的方式是把每个隐层单元以一定的概率乘以0或1,所以dropout会有几重噪音,batch norm也含有几重噪音,因为均值和方差的估计都是有噪声的。所以和dropout类似,batch norm有轻微的正则化效果,因为他给隐层单元添加了噪声,迫使后层的隐层单元不过分依赖于前层的任何一个隐层单元,使权重参数更分散,更小一些,不至于过大(使网络更简单)。因为添加的噪声相比于dropout小很多,所以只起到轻微的正则化作用。可以将dropout和batch norm结合使用,会有更强大的正则化效果。当mini-batch大小比较大时,如512,对于dropout和batch norm来说会减少其正则化的效果,因为减少了噪声。
7.测试时的Batch Norm
Batch Norm将你的训练数据以mini-batch的形式逐一处理,但在测试阶段,你可能需要对每个样本逐一处理。
- 训练时对每一层进行BN的公式 (下式的
就是之前的
):
- 测试时的Batch Norm
测试时,可能需要对每个样本逐一处理,而对单个样本求均值和方差是没有意义的,所以可以对训练时每个mini-batch在第l层上计算出的均值和方差
利用指数加权平均进行跟踪,估算出第l层的
,然后就可以对单个样本进行Batch Norm了:
8.Softmax回归
之前我们学习的分类都是2分类,本小节我们将学习一种新的逻辑回归形式---softmax回归,他可以进行多分类(>=2),不止是2分类。
- 识别猫,狗,小鸡和其他
这是一个4分类的例子,定义猫是类别1,狗类别2,小鸡类别3,其他类别0.我们一般用C代表分类的类别数,本例中C=#classes=4.
如果用神经网络进行多分类(>=2)的话,输出层的单元数(C>=2,当然2分类的话输出层也可以只有一个单元),本例中是4.其中输出层的每个单元的输出应该是一个概率值,比如第一个单元的输出值应该是在给定输入x的情况下属于类别0的概率,其他单元以此类推,并且输出层所有单元的输出概率值之和要等于1,如下图所示:
如果输入是一个样本的特征向量x(nx,1),输出的是(C,1).
如果输入是m个样本的特征矩阵X(nx,m),输出(C,m).每一列代表一个样本的输入或输出。
- softmax层
之前我们做2分类时,在输出层先得到一个线性组合中间结果,然后再经过sigmoid激活函数得到激活输出。
多分类时,可以把softmax看作是一个激活函数,具体在输出层的计算过程如下:
举一个具体的例子,四分类,C=4:
- softmax例子
当softmax层之前没有隐层时,即直接从输入层到输出层(softmax层),可以把它看作是一个逻辑回归的一般形式,不止是可以进行2分类,还可以进行多分类。比如进行3分类,如下图所示:
输入特征向量的维数是2维,即只有两个输入特征x1,x2,此时进行3分类(C=3),可视化训练集和决策边界如下:
当进行更多类别的分类时,如C>=4,可视化训练集和决策边界如下:
你会发现当softmax之前没有隐层时,在进行多分类时,任意两个类别的决策边界是线性的。
当softmax之前有很多隐层时,就会产生复杂的非线性决策边界,形式如下:
9.训练一个Softmax分类器
- 理解sofmax
还是之前4分类的例子,即C=4:
进行多分类时,神经网络输出层使用softmax激活函数,它的具体运算过程如上,输出层的每个单元都将输出一个概率,且所有单元的概率和为1.
softmax名称的由来,是对应于hardmax,hardmax是把输出层概率最大的单元置为1,其余为0.
实际上,softmax回归或softmax激活函数实际上是把逻辑回归推广到C分类(C>=2)的形式,当C=2,可以证明softmax回归其实就是逻辑回归。因为,当C=2,输出层的激活输出会包含两个值,且这两个值的和为1,所以只需要计算其中一个就可以了,另一个用1减。所以此时输出层有一个单元就足够了,softmax回归就变成了逻辑回归。
- 损失函数
单个输入样本:
y是样本的真实标签(one-hot),和一样是一个(C,1)的向量。损失函数的定义如下:
比如,以四分类为例,C=4,样本的真实标签y和模型输出如下:
此时代入损失函数,会得到下式:
也就是让真实类别所对应的概率尽可能大(其他类别不关心)。
m个输入样本:
Y是m个样本的真实标签(C,m),是模型针对m个样本的输出(C,m).每一列代表一个样本:
m个样本的代价函数定义如下:
- 带有softmax的梯度下降
对于单个样本:
(C,1)
对于m个样本:
(C,m)
上述过程可以自行推导,之后的反向传播过程与之前讲解的2分类形式一致。
10.深度学习框架
现在有很多深度学习框架,可以让我们编写深度模型的过程变得很简单。之前我们学习了很多正则化(dropout,L2等)和加速模型训练(Adam,Batch Norm,Momentum,mini-batch等)的技术,这些技术首先要掌握它的原理,然后可以纯手写实现这些技术进一步加深理解就够了。至于把这些技术整合在一起实现一个复杂的深度模型时,推荐使用深度学习框架,可以极大的提高开发效率。所以掌握一种或几种主流的深度学习框架非常重要。
而且使用框架,只需要定义好模型结构,保证前向传播过程正确实施,定义好代价函数,然后选择一个优化算法最小化代价函数更新参数就好了,反向传播计算梯度的过程,可以自动完成。
下图列举了几种主流的深度学习框架:
可以从编程的方便、简易程度,开发环境的配置;框架运行速度以及是否真正开源等方面选择一款合适的深度学习框架,进行深入学习,比较推荐使用谷歌开发的Tensorflow,之后我们的大多数实验或项目都是使用这个框架。
11.TensorFlow
本小节演示一下如何使用框架最小化代价函数J,求解模型最优参数,演示使用框架训练神经网络的一般程序结构。
tf的强大之处在于:只需要定义好模型结构,保证前向传播过程正确实施,定义好代价函数,然后选择一个优化算法最小化代价函数更新参数就好了,反向传播计算梯度的过程,可以自动完成。
以一个简单的代价函数 为例,最优值
.接下来使用tf进行求解:
import tensorflow as tf
import numpy as np
coefficients = np.array([[1],[-10],[25]]) #相当于训练集
w = tf.Variable([0],dtype = tf.float32) #初始化模型参数
x = tf.placeholder(tf.float32,[3,1]) #placeholder创建占位符 ,实际计算时在传入数据
cost = x[0][0]*w**2+x[1][0]*w+x[2][0] #定义代价函数
train = tf.train.GradientDescentOptimizer(0.01).minimize(cost) #选择一个优化算法最小化代价函数 定义了一次迭代更新参数的过程
with tf.Session() as sess: #with语句固定写法
init = tf.global_variables_initializer()
sess.run(init)
for i in range(1000): #进行1000次梯度下降迭代
sess.run(train,feed_dict={x:coefficients}) #把实际数据喂给x
print(sess.run(w)) #打印1000次迭代后的w值
实际上在真正传入数据之前,只是定义了一个计算图,并没有真正的计算,把数据通过feed_dict传入后,才进行计算。
在实际神经网络训练中,也是先定义一个计算图,没有真正运算。我们会在每次迭代中,传入相应的mini-batch中的数据,进行计算。