Practical Aspect of Deep learning
week1 深度学习的实用层面
1.1 训练/开发/测试集
在机器学习发展的小数据量时代,常见做法是将所有数据三七分,就是人们常说的70%验证集,30%测试集,如果没有明确设置验证集,也可以按照60%训练,20%验证和20%测试集来划分。这是前几年机器学习领域普遍认可的最好的实践方法。
如果只有100条,1000条或者1万条数据,那么上述比例划分是非常合理的。
但是在大数据时代,我们现在的数据量可能是百万级别,那么验证集和测试集占数据总量的比例会趋向于变得更小。因为验证集的目的就是验证不同的算法,检验哪种算法更有效,因此,验证集要足够大才能评估,比如2个甚至10个不同算法,并迅速判断出哪种算法更有效。我们可能不需要拿出20%的数据作为验证集。
在机器学习中,如果只有一个训练集和一个验证集,而没有独立的测试集,遇到这种情况,训练集还被人们称为训练集,而验证集则被称为测试集。搭建训练验证集和测试集能够加速神经网络的集成,也可以更有效地衡量算法地偏差和方差,从而帮助我们更高效地选择合适方法来优化算法。
1.2 偏差/方差
我们沿用猫咪图片分类这个例子,左边一张是猫咪图片,右边一张不是。理解偏差和方差的两个关键数据是训练集误差(Train set error)和验证集误差(Dev set error),为了方便论证,假设我们可以辨别图片中的小猫,我们用肉眼识别几乎是不会出错的。
假定训练集误差是1%,为了方便论证,假定验证集误差是11%,可以看出训练集设置得非常好,而验证集设置相对较差,我们可能过度拟合了训练集,在某种程度上,验证集并没有充分利用交叉验证集的作用,像这种情况,我们称之为“高方差”。
假设训练集误差是15%,我们把训练集误差写在首行,验证集误差是16%,假设该案例中人的错误率几乎为0%,人们浏览这些图片,分辨出是不是猫。算法并没有在训练集中得到很好训练,如果训练数据的拟合度不高,就是数据欠拟合,就可以说这种算法偏差比较高。相反,它对于验证集产生的结果却是合理的,验证集中的错误率只比训练集的多了1%,所以这种算法偏差高,因为它甚至不能拟合训练集,这与上一张幻灯片最左边的图片相似。
再举一个例子,训练集误差是15%,偏差相当高,但是,验证集的评估结果更糟糕,错误率达到30%,在这种情况下,我会认为这种算法偏差高,因为它在训练集上结果不理想,而且方差也很高,这是方差偏差都很糟糕的情况。
再看最后一个例子,训练集误差是0.5%,验证集误差是1%,用户看到这样的结果会很开心,猫咪分类器只有1%的错误率,偏差和方差都很低。
这些分析都是基于假设预测的,假设人眼辨别的错误率接近0%,一般来说,最优误差也被称为贝叶斯误差,所以,最优误差接近0%,我就不在这里细讲了,如果最优误差或贝叶斯误差非常高,比如15%。我们再看看这个分类器(训练误差15%,验证误差16%),15%的错误率对训练集来说也是非常合理的,偏差不高,方差也非常低。
重点是通过查看训练集误差,我们可以判断数据拟合情况,至少对于训练数据是这样,可以判断是否有偏差问题,然后查看错误率有多高。当完成训练集训练,开始使用验证集验证时,我们可以判断方差是否过高,从训练集到验证集的这个过程中,我们可以判断方差是否过高。
以上分析的前提都是假设基本误差很小,训练集和验证集数据来自相同分布,如果没有这些假设作为前提,分析过程更加复杂,我们将会在稍后课程里讨论。
1.3 机器学习基础
这是我在训练神经网络时用到的基本方法,初始模型训练完成后,我首先要知道算法的偏差高不高,如果偏差较高,试着评估训练集或训练数据的性能。如果偏差的确很高,甚至无法拟合训练集,那么你要做的就是选择一个新的网络,比如含有更多隐藏层或者隐藏单元的网络,或者花费更多时间来训练网络。
训练学习算法时,我会不断尝试这些方法,直到解决掉偏差问题,这是最低标准,反复尝试,直到可以拟合数据为止,至少能够拟合训练集。
一旦偏差降低到可以接受的数值,检查一下方差有没有问题,为了评估方差,我们要查看验证集性能,我们能从一个性能理想的训练集推断出验证集的性能是否也理想,如果方差高,最好的解决办法就是采用更多数据,如果你能做到,会有一定的帮助,但有时候,我们无法获得更多数据,我们也可以尝试通过正则化来减少过拟合
1.4 正则化
求成本函数 J 的最小值,它是我们定义的成本函数,参数包含一些训练数据和不同数据中个体预测的损失,w 和 b 是逻辑回归的两个参数,w 是一个多维度参数矢量,b 是一个实数。在逻辑回归函数中加入正则化,只需添加参数 λ ,也就是正则化参数
\(\frac{\lambda}{2m}\) 乘以 w 范数的平方,w 欧几里德范数的平方等于$ w_j$(j 值从1到 \(n_x\) )平方的和,也可表示为 \(w^Tw\),也就是向量参数 w 的欧几里德范数(2范数)的平方,此方法称为 L 2正则化。因为这里用了欧几里德法线,被称为向量参数 w 的 L 2范数。
为什么只正则化参数 w?为什么不再加上参数 b呢?你可以这么做,只是我习惯省略不写,因为 w通常是一个高维参数矢量,已经可以表达高偏差问题, w可能包含有很多参数,我们不可能拟合所有参数,而 b 只是单个数字,所以 w几乎涵盖所有参数,而不是 b ,如果加了参数 b ,其实也没太大影响,因为 b只是众多参数中的一个,所以我通常省略不计,如果你想加上这个参数,完全没问题。
L2 正则化是最常见的正则化类型,你们可能听说过L1 正则化, L1 正则化,加的不是 L 2范数,而是正则项\(\frac{\lambda}{m}\)乘以\(\sum_{j=1}^{n_x}|w|\), \(\sum_{j=1}^{n_x}|w|\)也被称为参数 w 向量的 L 1 范数,无论分母是 m还是 2m ,它都是一个比例常量。
如果用的是 L 1正则化, w最终会是稀疏的,也就是说 w 向量中有很多0,有人说这样有利于压缩模型,因为集合中参数均为0,存储模型所占用的内存更少。实际上,虽然 L 1 正则化使模型变得稀疏,却没有降低太多存储内存,所以我认为这并不是 L 1 正则化的目的,至少不是为了压缩模型,人们在训练网络时,越来越倾向于使用L2 正则化。
我们来看最后一个细节, λ 是正则化参数,我们通常使用验证集或交叉验证集来配置这个参数,尝试各种各样的数据,寻找最好的参数,我们要考虑训练集之间的权衡,把参数设置为较小值,这样可以避免过拟合,所以λ是另外一个需要调整的超级参数,顺便说一下,为了方便写代码,在Python编程语言中, λ 是一个保留字段,编写代码时,我们删掉 α ,写成 lambd,以免与Python中的保留字段冲突,这就是在逻辑回归函数中实现 L 2 正则化的过程,如何在神经网络中实现 L 2 正则化呢?
神经网络含有一个成本函数,该函数包含 \(w^{[1]}\),$b^{[1]} \(到\) w{[l]}$,$b\(所有参数,字母 L是神经网络所含的层数,因此成本函数等于 m 个训练样本损失函数的总和乘以\) \frac 1m\(,正则项为\)\frac {\lambda}{2m}$$\sum_1L|w|^2$,我们称为 \(||w^{[l]}||^2\)范数平方,这个矩阵范数 \(||w^{[l]}||^2\)(即平方范数),被定义为矩阵中所有元素的平方求和。
该矩阵范数被称作“弗罗贝尼乌斯范数”,用下标 F标注”,鉴于线性代数中一些神秘晦涩的原因,我们不称之为“矩阵 L 2范数”,而称它为“弗罗贝尼乌斯范数 (Frobenius norm)”,矩阵 L 2范数听起来更自然,但鉴于一些大家无须知道的特殊原因,按照惯例,我们称之为“弗罗贝尼乌斯范数”,它表示一个矩阵中所有元素的平方和。
该如何使用该范数实现梯度下降呢?
用backprop计算出 d w 的值,backprop会给出 J 对 w的偏导数,实际上是 \(w^{[l]}\) ,把 \(w^{[l]}\)替换为 \(w^{[l]}\)减去学习率乘以 dw 。
这就是之前我们额外增加的正则化项,既然已经增加了这个正则项,现在我们要做的就是给 d w加上这一项\(\frac {\lambda}{m} w^{[1]}\),然后计算这个更新项,使用新定义的\(dw^{[l]}\),它的定义含有相关参数代价函数导数和,以及最后添加的额外正则项,这也是 L 2正则化有时被称为“权重衰减”的原因。
该正则项说明,不论\(w^{[l]}\)是什么,我们都试图让它变得更小,实际上,相当于我们给矩阵 W 乘以$ (1-\alpha \frac \lambda m)$( 倍的权重,矩阵 w 减去 $ \alpha \frac \lambda m$ 倍的它,也就是用这个系数 \((1-\alpha \frac \lambda m)\)乘以矩阵 w,该系数小于1,因此 L 2 范数正则化也被称为“权重衰减”,因为它就像一般的梯度下降, w 被更新为少了 α 乘以backprop输出的最初梯度值,同时 w 也乘以了这个系数,这个系数小于1,因此 L 2正则化也被称为“权重衰减”。
1.5 为什么正则化有利于预防过拟合
为什么正则化有利于预防过拟合呢?为什么它可以减少方差问题?我们通过两个例子来直观体会一下。
想象这是一个过拟合的神经网络。这是我们的代价函数 J ,含有参数 w , b 。我们添加正则项,它可以避免数据权值矩阵过大,这就是弗罗贝尼乌斯范数,为什么压缩 L 2范数,或者弗罗贝尼乌斯范数或者参数可以减少过拟合?
直观上理解就是如果正则化 λ 设置得足够大,权重矩阵 w被设置为接近于0的值,直观理解就是把多隐藏单元的权重设为0,于是基本上消除了这些隐藏单元的许多影响。如果是这种情况,这个被大大简化了的神经网络会变成一个很小的网络,小到如同一个逻辑回归单元,可是深度却很大,它会使这个网络从过度拟合的状态更接近左图的高偏差状态。
但是 λ 会存在一个中间值,于是会有一个接近“Just Right”的中间状态。
直观理解就是 λ 增加到足够大, w会接近于0,实际上是不会发生这种情况的,我们尝试消除或至少减少许多隐藏单元的影响,最终这个网络会变得更简单,这个神经网络越来越接近逻辑回归,我们直觉上认为大量隐藏单元被完全消除了,其实不然,实际上是该神经网络的所有隐藏单元依然存在,但是它们的影响变得更小了。神经网络变得更简单了,貌似这样更不容易发生过拟合,因此我不确定这个直觉经验是否有用,不过在编程中执行正则化时,你实际看到一些方差减少的结果。
我们再来直观感受一下,正则化为什么可以预防过拟合,假设我们用的是这样的双曲线激活函数。
总结一下,如果正则化参数变得很大,参数 w很小, z 也会相对变小,此时忽略 b 的影响, z 会相对变小,实际上, z 的取值范围很小,这个激活函数,也就是曲线函数 tanh 会相对呈线性,整个神经网络会计算离线性函数近的值,这个线性函数非常简单,并不是一个极复杂的高度非线性函数,不会发生过拟合。
在代价函数J中增加了一项,目的是预防权重过大。如果用的是之前那个没有增加正则项的代价函数J 的话,可能看不到在所有区间内都单调递减现象。
这就是 L 2 正则化,它是我在训练深度学习模型时最常用的一种方法。在深度学习中,还有一种方法也用到了正则化,就是dropout正则化。
1.6 Dropout 正则化
除了 L 2 正则化,还有一个非常实用的正则化方法——“Dropout(随机失活)”,我们来看看它的工作原理。
假设你在训练上图这样的神经网络,它存在过拟合,这就是dropout所要处理的,我们复制这个神经网络,dropout会遍历网络的每一层,并设置消除神经网络中节点的概率。假设网络中的每一层,每个节点都以抛硬币的方式设置概率,每个节点得以保留和消除的概率都是0.5,设置完节点概率,我们会消除一些节点,然后删除掉从该节点进出的连线,最后得到一个节点更少,规模更小的网络,然后用backprop方法进行训练。
这是网络节点精简后的一个样本,对于其它样本,我们照旧以抛硬币的方式设置概率,保留一类节点集合,删除其它类型的节点集合。对于每个训练样本,我们都将采用一个精简后神经网络来训练它,这种方法似乎有点怪,单纯遍历节点,编码也是随机的,可它真的有效。不过可想而知,我们针对每个训练样本训练规模极小的网络,最后你可能会认识到为什么要正则化网络,因为我们在训练极小的网络。
如何实施dropout呢?方法有几种,接下来我要讲的是最常用的方法,即inverted dropout(反向随机失活),出于完整性考虑,我们用一个三层( l = 3 )网络来举例说明。编码中会有很多涉及到3的地方。我只举例说明如何在某一层中实施dropout。
首先要定义向量 \(d^{[3]}\)表示一个三层的dropout向量:
d3 = np.random.rand(a3.shape[0],a3.shape[1])
#生成一些boolean
然后看它是否小于某数,我们称之为keep-prob,keep-prob是一个具体数字,上个示例中它是0.5,而本例中它是0.8,它表示保留某个隐藏单元的概率,此处keep-prob等于0.8,它意味着消除任意一个隐藏单元的概率是0.2,它的作用就是生成随机矩阵,如果对 \(a^{[3]}\)进行因子分解,效果也是一样的。 \(d^{[3]}\)是一个矩阵,每个样本和每个隐藏单元,其中$ d^{[3]}$中的对应值为1的概率都是0.8,对应为0的概率是0.2,随机数字小于0.8。它等于1的概率是0.8,等于0的概率是0.2。
接下来要做的就是从第三层中获取激活函数,这里我们叫它$a^{[3]} \(,\)a^{[3]} $含有要计算的激活函数, $a^{[3]} \(等于上面的\)a^{[3]} \(乘以\)d^{[3]} $ ,a3 =np.multiply(a3,d3)
,这里是元素相乘,也可写为\(a^{[3]}*=d^{[3]}\),它的作用就是让$d^{[3]} $ 中所有等于0的元素(输出),而各个元素等于0的概率只有20%,乘法运算最终把 $d^{[3]} \(中相应元素输出,即让\)d^{[3]} $ 中0元素与$a^{[3]} $ 中相对元素归零。
如果用python实现该算法的话, $ d^{[3]}$则是一个布尔型数组,值为true和false,而不是1和0,乘法运算依然有效,python会把true和false翻译为1和0,大家可以用python尝试一下。
最后,我们向外扩展$ a^{[3]}$ ,用它除以0.8,或者除以keep-prob参数。
下面我解释一下为什么要这么做,为方便起见,我们假设第三隐藏层上有50个单元或50个神经元,在一维上$ a^{[3]}$是50,我们通过因子分解将它拆分成 50 ∗ m 维的,保留和删除它们的概率分别为80%和20%,这意味着最后被删除或归零的单元平均有10(50×20%=10)个,现在我们看下 \(z^{[4]}\) , \(z^{[4]}=w^{[4]}a^{[3]}+b^{[4]}\),我们的预期是, $ a^{[3]}$减少20%,也就是说 $ a^{[3]}$中有20%的元素被归零,为了不影响 \(z^{[4]}\)的期望值,我们需要用 $ w{[4]}a/0.8$ ,它将会修正或弥补我们所需的那20%, $ a^{[3]}$ 的期望值不会变,划线部分就是所谓的dropout方法。
它的功能是,不论keep-prop的值是多少0.8,0.9甚至是1,如果keep-prop设置为1,那么就不存在dropout,因为它会保留所有节点。反向随机失活(inverted dropout)方法通过除以keep-prob,确保 $ a^{[3]}$的期望值不变。
显然在测试阶段,我们并未使用dropout,自然也就不用抛硬币来决定失活概率,以及要消除哪些隐藏单元了,因为在测试阶段进行预测时,我们不期望输出结果是随机的,如果测试阶段应用dropout函数,预测会受到干扰。
1.7 理解 Dropout
Dropout可以随机删除网络中的神经单元,他为什么可以通过正则化发挥如此大的作用呢?
直观上理解:不要依赖于任何一个特征,因为该单元的输入可能随时被清除,因此该单元通过这种方式传播下去,并为单元的四个输入增加一点权重,通过传播所有权重,dropout将产生收缩权重的平方范数的效果,和之前讲的 L 2 正则化类似;实施dropout的结果实它会压缩权重,并完成一些预防过拟合的外层正则化; L 2 对不同权重的衰减是不同的,它取决于激活函数倍增的大小。
总结一下,dropout的功能类似于 L 2正则化,与 L 2 正则化不同的是应用方式不同会带来一点点小变化,甚至更适用于不同的输入范围。
第二个直观认识是,我们从单个神经元入手,如图,这个单元的工作就是输入并生成一些有意义的输出。通过dropout,该单元的输入几乎被消除,有时这两个单元会被删除,有时会删除其它单元,就是说,我用紫色圈起来的这个单元,它不能依靠任何特征,因为特征都有可能被随机清除,或者说该单元的输入也都可能被随机清除。我不愿意把所有赌注都放在一个节点上,不愿意给任何一个输入加上太多权重,因为它可能会被删除,因此该单元将通过这种方式积极地传播开,并为单元的四个输入增加一点权重,通过传播所有权重,dropout将产生收缩权重的平方范数的效果,和我们之前讲过的 L 2 正则化类似,实施dropout的结果是它会压缩权重,并完成一些预防过拟合的外层正则化。
事实证明,dropout被正式地作为一种正则化的替代形式, L2 对不同权重的衰减是不同的,它取决于倍增的激活函数的大小。
实施dropout的另一个细节是,这是一个拥有三个输入特征的网络,其中一个要选择的参数是keep-prob,它代表每一层上保留单元的概率。所以不同层的keep-prob也可以变化。第一层,矩阵$ w^{[1]}\(是7×3,第二个权重矩阵\) w{[2]}$是7×7,第三个权重矩阵$w\(是3×7,以此类推,\)w^{[2]} $是最大的权重矩阵,因为 \(w^{[2]}\)拥有最大参数集,即7×7,为了预防矩阵的过拟合,对于这一层,我认为这是第二层,它的keep-prob值应该相对较低,假设是0.5。对于其它层,过拟合的程度可能没那么严重,它们的keep-prob值可能高一些,可能是0.7,这里是0.7。如果在某一层,我们不必担心其过拟合的问题,那么keep-prob可以为1,为了表达清除,我用紫色线笔把它们圈出来,每层keep-prob的值可能不同。
注意keep-prob的值是1,意味着保留所有单元,并且不在这一层使用dropout,对于有可能出现过拟合,且含有诸多参数的层,我们可以把keep-prob设置成比较小的值,以便应用更强大的dropout,有点像在处理 L 2 正则化的正则化参数 λ ,我们尝试对某些层施行更多正则化,从技术上讲,我们也可以对输入层应用dropout,我们有机会删除一个或多个输入特征,虽然现实中我们通常不这么做,keep-prob的值为1,是非常常用的输入值,也可以用更大的值,或许是0.9。但是消除一半的输入特征是不太可能的,如果我们遵守这个准则,keep-prob会接近于1,即使你对输入层应用dropout。
结束前分享两个实施过程中的技巧,实施dropout,在计算机视觉领域有很多成功的第一次。计算视觉中的输入量非常大,输入太多像素,以至于没有足够的数据,所以dropout在计算机视觉中应用得比较频繁,有些计算机视觉研究人员非常喜欢用它,几乎成了默认的选择。dropout一大缺点就是代价函数 J 不再被明确定义,每次迭代,都会随机移除一些节点,如果再三检查梯度下降的性能,实际上是很难进行复查的。
1.8 其他正则化方法
一.数据扩增
假设你正在拟合猫咪图片分类器,如果你想通过扩增训练数据来解决过拟合,但扩增数据代价高,而且有时候我们无法扩增数据,但我们可以通过添加这类图片来增加训练集。例如,水平翻转图片,并把它添加到训练集。所以现在训练集中有原图,还有翻转后的这张图片,所以通过水平翻转图片,训练集则可以增大一倍,因为训练集有冗余,这虽然不如我们额外收集一组新图片那么好,但这样做节省了获取更多猫咪图片的花费。
除了水平翻转图片,你也可以随意裁剪图片,这张图是把原图旋转并随意放大后裁剪的,仍能辨别出图片中的猫咪。
通过随意翻转和裁剪图片,我们可以增大数据集,额外生成假训练数据。和全新的,独立的猫咪图片数据相比,这些额外的假的数据无法包含像全新数据那么多的信息,但我们这么做基本没有花费,代价几乎为零,除了一些对抗性代价。以这种方式扩增算法数据,进而正则化数据集,减少过拟合比较廉价。
对于光学字符识别,我们还可以通过添加数字,随意旋转或扭曲数字来扩增数据,把这些数字添加到训练集,它们仍然是数字。为了方便说明,我对字符做了强变形处理,所以数字4看起来是波形的,其实不用对数字4做这么夸张的扭曲,只要轻微的变形就好,我做成这样是为了让大家看的更清楚。实际操作的时候,我们通常对字符做更轻微的变形处理。因为这几个4看起来有点扭曲。所以,数据扩增可作为正则化方法使用,实际功能上也与正则化相似。
二.early stopping
还有另外一种常用的方法叫作early stopping,运行梯度下降时,我们可以绘制训练误差,或只绘制代价函数的优化过程,在训练集上用0-1记录分类误差次数。呈单调下降趋势,如图。
因为在训练过程中,我们希望训练误差,代价函数 J 都在下降,通过early stopping,我们不但可以绘制上面这些内容,还可以绘制验证集误差,它可以是验证集上的分类误差,或验证集上的代价函数,逻辑损失和对数损失等,你会发现,验证集误差通常会先呈下降趋势,然后在某个节点处开始上升,early stopping的作用是,你会说,神经网络已经在这个迭代过程中表现得很好了,我们在此停止训练吧,得到验证集误差,它是怎么发挥作用的?
当你还未在神经网络上运行太多迭代过程的时候,参数 w 接近0,因为随机初始化 w值时,它的值可能都是较小的随机值,所以在你长期训练神经网络之前 w 依然很小,在迭代过程和训练过程中 w的值会变得越来越大,比如在这儿,神经网络中参数 w 的值已经非常大了,所以early stopping要做就是在中间点停止迭代过程,我们得到一个 w 值中等大小的弗罗贝尼乌斯范数,与 L 2 正则化相似,选择参数 w 范数较小的神经网络,但愿你的神经网络过度拟合不严重。
术语early stopping代表提早停止训练神经网络,训练神经网络时,我有时会用到early stopping,但是它也有一个缺点,我们来了解一下。
我认为机器学习过程包括几个步骤,其中一步是选择一个算法来优化代价函数 J ,我们有很多种工具来解决这个问题,如梯度下降,后面我会介绍其它算法,例如Momentum,RMSprop和Adam等等,但是优化代价函数 J之后,我也不想发生过拟合,也有一些工具可以解决该问题,比如正则化,扩增数据等等。
在重点优化代价函数 J 时,你只需要留意 w 和 b ,J(w,b) 的值越小越好,你只需要想办法减小这个值,其它的不用关注。然后,预防过拟合还有其他任务,换句话说就是减少方差,这一步我们用“正交化”。思路就是在一个时间做一个任务。
但对我来说early stopping的主要缺点就是你不能独立地处理这两个问题,因为提早停止梯度下降,也就是停止了优化代价函数 J ,因为现在你不再尝试降低代价函数 J JJ ,所以代价函数J 的值可能不够小,同时你又希望不出现过拟合,你没有采取不同的方式来解决这两个问题,而是用一种方法同时解决两个问题,这样做的结果是我要考虑的东西变得更复杂。
如果不用early stopping,另一种方法就是 L 2 正则化,训练神经网络的时间就可能很长。我发现,这导致超级参数搜索空间更容易分解,也更容易搜索,但是缺点在于,你必须尝试很多正则化参数 λ 的值,这也导致搜索大量 λ 值的计算代价太高。
Early stopping的优点是,只运行一次梯度下降,你可以找出 w的较小值,中间值和较大值,而无需尝试 L 2 正则化超级参数 λ的很多值。
虽然 L 2 正则化有缺点,可还是有很多人愿意用它。吴恩达老师个人更倾向于使用 L 2正则化,尝试许多不同的 λ 值,假设你可以负担大量计算的代价。而使用early stopping也能得到相似结果,还不用尝试这么多 λ 值。
1.9 归一化输入
训练神经网络,其中一个加速训练的方法就是归一化输入。假设一个训练集有两个特征,输入特征为2维,归一化需要两个步骤:
-
零均值
\[u=\frac1m\sum_{i=1}^mx^{(i)} \]它是一个向量, x 等于每个训练数据 x 减去 μ ,意思是移动训练集,直到它完成零均值化。
-
归一化方差;注意特征 \(x_1\)的方差比特征 \(x_2\)的方差要大得多,我们要做的是给 σ 赋值
\[\sigma^2=\frac1m\sum_{i=1}^m(x^{(i)})^2 \]这是节点 y 的平方,\(\sigma^2\) 是一个向量,它的每个特征都有方差,注意,我们已经完成零值均化, $ (x{(i)})2 $元素 \(y^2\) 就是方差,我们把所有数据除以向量\(\sigma^2\) ,最后变成上图形式。
我们希望无论是训练集和测试集都是通过相同的$ \mu$和 $ \sigma^2 $定义的数据转换,这两个是由训练集得出来的。
\[J(w,b) = \frac{1}{m} \sum_{i=1}^m L(\hat{y}^{(i)},y^{(i)}) \]如果你使用非归一化的输入特征,代价函数会像这样:
当然,实际上 w 是一个高维向量,因此用二维绘制 w并不能正确地传达并直观理解,但总地直观理解是代价函数会更圆一些,而且更容易优化,前提是特征都在相似范围内,而不是从1到1000,0到1的范围,而是在-1到1范围内或相似偏差,这使得代价函数 J 优化起来更简单快速。
1.10 梯度消失与梯度爆炸
假设每个权重矩阵\(w[1] = \begin{bmatrix} 1.5&0 \\ 0&1.5 \end{bmatrix}\),技术上来说,最后一项有不同维度
\[y = w^{[1]}\begin{bmatrix} 1.5&0 \\ 0&1.5 \end{bmatrix}^{(L-1)}x \]最后计算的结果\(\hat{y} = 1.5^{(L-1)}x\),如果对于一个神经网络L值比较大,那么\(\hat{y}\)的值也会非常大,呈指数级增长,增长的比率是\(1.5^L\),那么神经网络y的值将会爆炸式增长。
相反,如果权重是0.5,函数的值将会指数级递减。
1.11 神经网络的权重初始化
我们来看看只有一个神经元的情况
\[z = w_1x_1+w_2x_2+...+w_nx_n,b=0 \]可以看到,n越大,希望\(w_i\)越小,因为z是\(w_ix_i\)的和,如果你把很多此类项相加,希望每项值更小,那么设置\(w_i\)的方差\(\frac{1}{n}\)
\[w^{[l]} = np.random.randn(shape)*np.sqrt(\frac{1}{n^{[l-1]}}) \]如果使用的是relu激活函数,那么方差设置为\(\frac{2}{n}\)会更好。
这里使用\(n^{[l-1]}\),是因为本例中,逻辑回归的特征是不变的,但在一般情况下l层的每个神经元都有\(n^{[l-1]}\)个输入。如果激活函数的输入特征被0均值和标准方差化,还是没有解决梯度消失和爆炸的问题,但是降低了这些问题,因为给了权重矩阵w设置了合理值。
对于tanh函数来说,用$\sqrt{\frac{2}{n{[l-1]}+n{[l]}}} $
1.12 梯度的数值逼近
双边误差公式的结果更准确
1.13 梯度检验
将所有的参数转换成一个巨大的向量数据,把矩阵w和b等转化为向量之后做连接运算,得到一个巨型向量\(\theta\),现在得到了一个有关于\(\theta\)的代价函数\(J(\theta)\),然后得到一个于w和b顺序相同的数据,将\(dw^{[1]},db^{[1]}...dw^{[l]},db^{[l]}\)转换成一个新的向量,用这个向量来初始化这个大向量\(d\theta\),这个向量与\(\theta\)维度相同。
注意\(dw^{[1]}\)和\(w^{[1]}\)维度相同,db也是类似。
实施梯度检验(grad check),可以将\(J(\theta_1,\theta_2,...)\),循环执行,对每个i也就是对每个\(\theta\)组成元素计算\(d\theta_{approx}[i]\)的值,这里使用双边误差:
\[d\theta_{approx}[i] = \frac{J(\theta_1,\theta_2,...,\theta_i+\epsilon,... )-J(\theta_1,\theta_2,...,\theta_i-\epsilon,... )}{2\epsilon} \]只对\(\theta_i\)增加\(\epsilon\)其他的项保持不变
这个值\(d\theta_{approx}[i]\)应该逼近\(d\theta[i] = \frac{\partial J}{\partial{\theta_i}}\),验证这两个向量是否接近
如果计算结果为:
-
\(10^{-7}\)或者更小,good。
-
\(10^{-5}\),要小心
-
\(10^{-3}\),可能有bug,因为一般会比这个小很多
1.14 梯度检验应用的注意事项
首先,不要在训练中使用梯度检验,它只用于调试。只要调试的时候,你才会计算它,来确认数值是否接近 d θ 。完成后,你会关闭梯度检验,梯度检验的每一个迭代过程都不执行它,因为它太慢了。
第二点,如果算法的梯度检验失败,要检查所有项,检查每一项,并试着找出bug,可以查找是哪个i导致相差这么多。这样可能帮助你定位具体是哪一层出现了问题。
第三点,在实施梯度检验时,如果使用正则化,请注意加上正则项。
第四点,梯度检验不能与dropout同时使用,因为每次迭代过程中,dropout会随机消除隐藏层单元的不同子集,难以计算dropout在梯度下降上的代价函数 J 。因此dropout可作为优化代价函数 J 的一种方法,但是代价函数J被定义为对所有指数极大的节点子集求和。而在任何迭代过程中,这些节点都有可能被消除,所以很难计算代价函数 J 。你只是对成本函数做抽样,用dropout,每次随机消除不同的子集,所以很难用梯度检验来双重检验dropout的计算,所以我一般不同时使用梯度检验和dropout。如果你想这样做,可以把dropout中的keepprob设置为1.0,然后打开dropout,并寄希望于dropout的实施是正确的,你还可以做点别的,比如修改节点丢失模式确定梯度检验是正确的。实际上,我一般不这么做,我建议关闭dropout,用梯度检验进行双重检查,在没有dropout的情况下,你的算法至少是正确的,然后打开dropout。
最后一点,也是比较微妙的一点,现实中几乎不会出现这种情况。当 w 和 b接近0时,梯度下降的实施是正确的,在随机初始化过程中……,但是在运行梯度下降时, w 和 b 变得更大。可能只有在 w 和 b 接近0时,backprop的实施才是正确的。但是当 w和 b 变大时,它会变得越来越不准确。你需要做一件事,我不经常这么做,就是在随机初始化过程中,运行梯度检验,然后再训练网络, w 和 b 会有一段时间远离0,如果随机初始化值比较小,反复训练网络之后,再重新运行梯度检验。
week 2 优化算法
2.1 Mini-batch 梯度下降
有m个样本,\(X = [x^{(1)}x^{(2)}...x^{(m)}]\),\(Y = [y^{(1)}y^{(2)}...y^{(m)}]\),这里X的维度是\((n_x,m)\),Y的维度是\((1,m)\)
可以把训练集分割为小一点的子集训练,这些子集被取名为mini-batch
接下来我要说一个新的符号,\(把x^{(1)}\) 到 \(x^{(1000)}\)称为 \(X^{(1)}\),\(x^{(1001)}\) 到 \(x^{(2000)}\)称为\(X^{(2)}\) ,如果你的训练样本一共有500万个,每个mini-batch都有1000个样本,也就是说,你有5000个mini-batch,因为5000乘以1000就是500万。
你共有5000个mini-batch,所以最后得到是\(X^{(5000)}\)
对 Y 也要进行相同处理,相应地拆分 Y 的训练集
mini-batch的数量 t 组成了 \(X^{(t)}\) 和 $ Y^{(t)}$,这就是1000个训练样本,包含相应的输入输出对。
之前我们使用了上角小括号 (i) 表示训练集里的值,所以$ x^{(i)}\(是第 i 个训练样本。我们用了上角中括号 [ l ] [l][l] 来表示神经网络的层数,\)z^{[l]}\(表示神经网络中第 l层的 z值,我们现在引入了大括号 t 来代表不同的**mini-batch**,所以我们有\)X^{(t)}$和 \(Y^{(t)}\)。
如果\(X^{\{1\}}\)是一个有1000样本的训练集,\(X^{\{1\}}\)维数应该是(\(n_x,1000\))。所有子集的维数都是(\(n_x,1000\)),\(Y^{\{t\}}\)的维数都是(1,1000)。
对于输入\(X^{\{t\}}\),执行前向传播,然后执行:
\[z^{[1]} = W^{[1]}X^{\{t\}}+b^{[1]} \]\[A^{[1]} = g^{[1]}(Z^{[1]}) \]\[... \]\[A^{[L]} = g^{[L]}(Z^{[L]}) \]这里使用一个向量化的执行命令,因为子集的规模是1000
\[J = \frac{1}{1000}\sum_{i=1}^{l}L(\hat{y}^{(i)},y^{(i)})+\frac{\lambda}{2*1000} \sum_l||w^l||_F^2 \]这里前面前向传播的l表示层数;后面这个求一个批次的样本损失的地方的l应该是表示这一个批次的样本数1000,对这1000个样本数的损失累计并/1000得到平均
你执行反向传播来计算 \(J^{\{t\}}\)的梯度,你只是使用 \(X^{\{t\}}\)和$Y^{{t}} \(,然后你更新加权值, W 实际上是\)W{[l]}$,更新为$W:=W^{[l]}-\alpha dW^{[l]}$,对 b 做相同处理, \(b^{[l]}:=b^{[l]}-\alpha db^{[l]}\) 。这是使用mini-batch梯度下降法训练样本的一步,我写下的代码也可被称为进行“一代”(1 epoch)的训练。只是一次遍历了训练集。
使用batch梯度下降法,一次遍历训练集只能让你做一个梯度下降,使用mini-batch梯度下降法,一次遍历训练集,能让你做5000个梯度下降。当然正常来说你想要多次遍历训练集,还需要为另一个while循环设置另一个for循环。所以你可以一直处理遍历训练集,直到最后你能收敛到一个合适的精度。
如果你有一个丢失的训练集,mini-batch梯度下降法比batch梯度下降法运行地更快,所以几乎每个研习深度学习的人在训练巨大的数据集时都会用到。
2.2 理解 Mini-batch 梯度下降
使用batch梯度下降法时,每次迭代你都需要历遍整个训练集,可以预期每次迭代成本都会下降,所以如果成本函数 J 是迭代次数的一个函数,它应该会随着每次迭代而减少,如果 J在某次迭代中增加了,那肯定出了问题,也许你的学习率太大。
使用mini-batch梯度下降法,如果你作出成本函数在整个过程中的图,则并不是每次迭代都是下降的,特别是在每次迭代中,你要处理的是$X^{{t}} $和 \(Y^{\{t\}}\),如果要作出成本函数$ J^{{t}}$的图,而 \(J^{\{t\}}\) 只和 \(X^{\{t\}}\) ,\(Y^{\{t\}}\)有关,也就是每次迭代下你都在训练不同的样本集或者说训练不同的mini-batch,如果你要作出成本函数 J的图,你很可能会看到这样的结果,走向朝下,但有更多的噪声,所以如果你作出$ J^{{t}}$的图,因为在训练mini-batch梯度下降法时,会经过多代,你可能会看到这样的曲线。没有每次迭代都下降是不要紧的,但走势应该向下,噪声产生的原因在于也许 $X^{{1}} $和 $Y^{{1}} \(是比较容易计算的**mini-batch**,因此成本会低一些。不过也许出于偶然,\) X^{{2}}$和 \(Y^{\{2\}}\)是比较难运算的mini-batch,或许你需要一些残缺的样本,这样一来,成本会更高一些,所以才会出现这些摆动,因为你是在运行mini-batch梯度下降法作出成本函数图。
你需要决定的变量之一是mini-batch的大小, m就是训练集的大小,极端情况下,如果mini-batch的大小等于 m,其实就是batch梯度下降法,在这种极端情况下,你就有了mini-batch \(X^{\{1\}}\)和 \(Y^{\{1\}}\),并且该mini-batch等于整个训练集,所以把mini-batch大小设为 m 可以得到batch梯度下降法。
另一个极端情况,假设mini-batch大小为1,就有了新的算法,叫做随机梯度下降法,每个样本都是独立的mini-batch,当你看第一个mini-batch,也就是\(X^{\{1\}}\)和$ Y^{{1}}$,如果mini-batch大小为1,它就是你的第一个训练样本,这就是你的第一个训练样本。接着再看第二个mini-batch,也就是第二个训练样本,采取梯度下降步骤,然后是第三个训练样本,以此类推,一次只处理一个。
看在两种极端下成本函数的优化情况,如果这是你想要最小化的成本函数的轮廓,最小值在那里,batch梯度下降法从某处开始,相对噪声低些,幅度也大一些,你可以继续找最小值。
相反,在随机梯度下降法中,从某一点开始,我们重新选取一个起始点,每次迭代,你只对一个样本进行梯度下降,大部分时候你向着全局最小值靠近,有时候你会远离最小值,因为那个样本恰好给你指的方向不对,因此随机梯度下降法是有很多噪声的,平均来看,它最终会靠近最小值,不过有时候也会方向错误,因为随机梯度下降法永远不会收敛,而是会一直在最小值附近波动,但它并不会在达到最小值并停留在此。
- batch:每次迭代时间太长(如果训练样本不大batch运行的也很好)
- stochastic(随机梯度下降法):通过减小学习率噪声会被改善或者有所减小,但随机梯度下降法的一大缺点是效率过于低下,所以实践中最好选择不大不小的mini-batch
- mini-batch:1.大量的向量化2.不需要等待整个训练集完成,就可以开始后续工作
用mini-batch梯度下降法,它不会总朝向最小值靠近,但它比随机梯度下降要更持续地靠近最小值的方向,它也不一定在很小的范围内收敛或者波动,如果出现这个问题,可以慢慢减少学习率,我们在下个视频会讲到学习率衰减,也就是如何减小学习率。
2.3 指数加权平均
\[v_0 = 0 \]\[v_1 = 0.9v_0+0.1\theta_1 \]\[v_2 = 0.9v_1+0.1\theta_2 \]\[... \]某天的v等于前一天的v值的0.9加上 当日温度的0.1
\[v_t = \beta v_{t-1} + (1-\beta)\theta_t \]\[\frac{1}{1-\beta} days' temperature \]比如如果 \(\beta = 0.9\),那么这里就是十天的平均值
这个高值 β 要注意几点,你得到的曲线要平坦一些,原因在于你多平均了几天的温度,所以这个曲线,波动更小,更加平坦,缺点是曲线进一步右移,因为现在平均的温度值更多,要平均更多的值,指数加权平均公式在温度变化时,适应地更缓慢一些,所以会出现一定延迟,因为当 β = 0.98 ,相当于给前一天的值加了太多权重,只有0.02的权重给了当日的值,所以温度变化时,温度上下起伏,当 β 较大时,指数加权平均值适应地更缓慢一些。
我们可以再换一个值试一试,如果 β 是另一个极端值,比如说0.5,根据右边的公式\(\frac1{(1-\beta)}\),这是平均了两天的温度。就是上图中的黄线。由于仅平均了两天的温度,平均的数据太少,所以得到的曲线有更多的噪声,有可能出现异常值,但是这个曲线能够更快适应温度变化。
通过调整这个参数( β ),往往中间有某个值效果最好, β 为中间值时得到的红色曲线,比起绿线和黄线更好地平均了温度。
2.4 理解指数加权平均
\[v_t = \beta v_{t-1}+(1-\beta)\theta_t \]β=0.9 的时候,得到的结果是红线,如果它更接近于1,比如0.98,结果就是绿线,如果 β小一点,如果是0.5,结果就是黄线。
所以这是一个加和并平均,100号数据,也就是当日温度。我们分析$ v_{100}$的组成,也就是在一年第100天计算的数据,但是这个是总和,包括100号数据,99号数据,97号数据等等。画图的一个办法是,假设我们有一些日期的温度,所以这是数据,这是 t ,所以100号数据有个数值,99号数据有个数值,98号数据等等, t为100,99,98等等,这就是数日的温度数值。
指数加权平均数公式的好处之一在于,它占用极少内存,电脑内存中只占用一行数字而已,然后把最新数据代入公式,不断覆盖就可以了,正因为这个原因,其效率,它基本上只占用一行代码,计算指数加权平均数也只占用单行数字的存储和内存,当然它并不是最好的,也不是最精准的计算平均数的方法。如果你要计算移动窗,你直接算出过去10天的总和,过去50天的总和,除以10和50就好,如此往往会得到更好的估测。但缺点是,如果保存所有最近的温度数据,和过去10天的总和,必须占用更多的内存,执行更加复杂,计算成本也更加高昂。
2.5 指数加权平均的偏差修正
特别是在估测初期,也就是不用\(v_t\),而是用 \(\frac{v_t}{1-\beta^t}\) , t 就是现在的天数。举个具体例子,当 t = 2 时, \(1-\beta^t=1-0.98^2=0.0396\) ,因此对第二天温度的估测变成了\(\frac{v_2}{0.0396}=\frac{0.0196\theta_1+0.02\theta_2}{0.0396}\),也就是$ \theta_1$和 $\theta_2 \(的加权平均数,并去除了偏差。你会发现随着 t增加,\)\beta^t$接近于0,所以当 t 很大的时候,偏差修正几乎没有作用,因此当 t 较大的时候,紫线基本和绿线重合了。不过在开始学习阶段,你才开始预测热身练习,偏差修正可以帮助你更好预测温度,偏差修正可以帮助你使结果从紫线变成绿线。
在机器学习中,在计算指数加权平均数的大部分时候,大家不在乎执行偏差修正,因为大部分人宁愿熬过初始时期,拿到具有偏差的估测,然后继续计算下去。如果你关心初始时期的偏差,在刚开始计算指数加权移动平均数的时候,偏差修正能帮助你在早期获取更好的估测。
2.6 动量梯度下降法
还有一种算法叫做Momentum,或者叫做动量梯度下降法,运行速度几乎总是快于标准的梯度下降算法,简而言之,基本的想法就是计算梯度的指数加权平均数,并利用该梯度更新你的权重。
慢慢摆动到最小值,这种上下波动减慢了梯度下降法的速度,你就无法使用更大的学习率,如果你要用较大的学习率(紫色箭头),结果可能会偏离函数的范围,为了避免摆动过大,你要用一个较小的学习率。
另一个看待问题的角度是,在纵轴上,你希望学习慢一点,因为你不想要这些摆动,但是在横轴上,你希望加快学习,你希望快速从左向右移,移向最小值,移向红点。
例如,在上几个导数中,你会发现这些纵轴上的摆动平均值接近于零,所以在纵轴方向,你希望放慢一点,平均过程中,正负数相互抵消,所以平均值接近于零。但在横轴方向,所有的微分都指向横轴方向,因此横轴方向的平均值仍然较大,因此用算法几次迭代后,你发现动量梯度下降法,最终纵轴方向的摆动变小了,横轴方向运动更快,因此你的算法走了一条更加直接的路径,在抵达最小值的路上减少了摆动。
这些微分项,想象它们为你从山上往下滚的一个球,提供了加速度,Momentum项相当于速度。
想象你有一个碗,你拿一个球,微分项给了这个球一个加速度,此时球正向山下滚,球因为加速度越滚越快,而因为 β 稍小于1,表现出一些摩擦力,所以球不会无限加速下去,所以不像梯度下降法,每一步都独立于之前的步骤,你的球可以向下滚,获得动量,可以从碗向下加速获得动量。
所以你有两个超参数,学习率 α以及参数 β , β 控制着指数加权平均数。 β 最常用的值是0.9,我们之前平均了过去十天的温度,所以现在平均了前十次迭代的梯度。实际上 β 为0.9时,效果不错,你可以尝试不同的值,可以做一些超参数的研究,不过0.9是很棒的鲁棒数。那么关于偏差修正,所以你要拿\(v_{dW}\)和 $ v_{db}$除以 \(1-\beta^t\) ,实际上人们不这么做,因为10次迭代之后,因为你的移动平均已经过了初始阶段。实际中,在使用梯度下降法或动量梯度下降法时,人们不会受到偏差修正的困扰。当然$ v_{dW}\(初始值是0,要注意到这是和 dW拥有相同维数的零矩阵,也就是跟 W 拥有相同的维数,\) v_{db} $的初始值也是向量零,所以和 d b 拥有相同的维数,也就是和 b是同一维数。
我更倾向于使用左边的公式,也就是有 1 − β 的这个公式,但是两个公式都将 β 设置为0.9,是超参数的常见选择。
2.7 RMSprop
还有一个叫做RMSprop的算法,全称是root mean square prop算法,它也可以加速梯度下降。
回忆一下我们之前的例子,如果你执行梯度下降,虽然横轴方向正在推进,但纵轴方向会有大幅度摆动。
这里为了分析这个例子,假设纵轴代表参数b,横轴代表参数w。
\[S_{dW} = \beta S_{dW}+(1-\beta)dW^2 \]这里平方的操作是针对这一整个符号的。
\[S_{db} = \beta S_{db}+(1-\beta)db^2 \]然后RMSprop会这样更新参数值:
\[W:=W-\alpha \frac{dW}{\sqrt {S_{dW}}} \]\[b:=b-\alpha \frac{db}{\sqrt {S_{db}}} \]在横轴方向W,我们希望学习速度快,并且在垂直方向b,我们希望减缓纵轴上的摆动,所以我们希望\(S_{dW}\)会相对较小,所以我们要除以一个较小的数,而又希望\(S_{db}\)比较大,所以这里要除以比较大的数字,就可以延缓纵轴变化
本质就是避免了在梯度过大的分量上过冲,又用平均避免了学习率不断衰减
均方差,将微分进行平方,然后使用平方根更新
2.8 Adam 优化算法
Adam优化算法基本上就是将Momentum和RMSprop结合在一起
使用Adam算法,首先你要初始化, \(v_{dW}=0,S_{dW}=0,v_{db}=0,S_{db}=0\)
在第t次的迭代中,你要计算微分,使用当前的mini-batch计算dW,db
接下来计算momentum指数加权平均数
\[v_{dW} = \beta_1 v_{dW}+(1-\beta_1)dW \]\[v_{db} = \beta_1v_{db} + (1-\beta_1)db \]接着用RMSprop来进行更新
\[S_{dW} = \beta S_{dW} + (1-\beta_2)(dW)^2 \]\[S_{db} = \beta S_{db} + (1-\beta_2)(db)^2 \]相当于Momentum更新了超参数\(\beta_1\),RMSprop更新了超参数\(\beta_2\).一般使用Adam算法的时候,要计算偏差修正\(v_{dW}^{correct}\)
\[v_{dW}^{correct} = \frac{v_{dW}}{1-\beta_1^t} \]\[v_{db}^{correct} = \frac{v_{db}}{1-\beta_1^t} \]\[S_{dW}^{correct} = \frac{S_{dW}}{1-\beta_2^t} \]\[S_{db}^{correct} = \frac{S_{db}}{1-\beta_2^t} \]最后更新权重:
\[W := W - \frac{\alpha v_{dW}^{correct}}{\sqrt{S_{dW}^{corrected}}+\epsilon} \](如果你只是用Momentum,使用$ v_{dW}\(或者修正后的\) v_{dW} $,但现在我们加入了RMSprop的部分,所以我们要除以修正后 \(S_{dW}\)的平方根加上 ϵ )。
\[b := b - \frac{\alpha v_{db}^{correct}}{\sqrt{S_{db}^{corrected}}+\epsilon} \]β1 常用的缺省值为0.9,这是 d W 的移动平均数,也就是 d W 的加权平均数,这是Momentum涉及的项。
至于超参数$ \beta_2$,Adam论文作者,也就是Adam算法的发明者,推荐使用0.999,这是在计算 $ (dW)^2\(以及\) (db)^2$的移动加权平均值
关于 ϵ 的选择其实没那么重要,Adam论文的作者建议 ϵ 为 \(10^{-8}\),但你并不需要设置它,因为它并不会影响算法表现。但是在使用Adam的时候,人们往往使用缺省值即可。
为什么这个算法叫做Adam?Adam代表的是Adaptive Moment Estimation
2.9 学习率衰减
加快学习算法的一个办法就是随时间慢慢减少学习率,我们将之称为学习率衰减。
假设你要使用mini-batch梯度下降法,mini-batch数量不大,大概64或者128个样本,在迭代过程中会有噪音(蓝色线),下降朝向这里的最小值,但是不会精确地收敛,所以你的算法最后在附近摆动,并不会真正收敛,因为你用的 α 是固定值,不同的mini-batch中有噪音。
但要慢慢减少学习率 α的话,在初期的时候, α 学习率还较大,你的学习还是相对较快,但随着 α 变小,你的步伐也会变慢变小,所以最后你的曲线(绿色线)会在最小值附近的一小块区域里摆动,而不是在训练过程中,大幅度在最小值附近摆动。
所以慢慢减少 α \alphaα 的本质在于,在学习初期,你能承受较大的步伐,但当开始收敛的时候,小一些的学习率能让你步伐小一些。
你可以这样做到学习率衰减,记得一代要遍历一次数据,如果你有以下这样的训练集,
你应该拆分成不同的mini-batch,第一次遍历训练集叫做第一代。第二次就是第二代,依此类推,你可以将学习率 α 设为
\[\alpha = \frac{1}{1+decay\ \ rate*epoch\_num} \alpha_0 \]decay-rate称为衰减率,epoch-num为代数,$ \alpha_0$ 为初始学习率
指数衰减,其中 $\alpha $相当于一个小于1的值,如 \(\alpha=0.95^{epoch\_num}\alpha_0\) ,所以你的学习率呈指数下降。
人们用到的其它公式有\(\alpha=\frac{k}{\sqrt{epoch\_num}}\alpha_0\)或者 \(\alpha=\frac{k}{\sqrt{t}}\alpha_0\)(t 为mini-batch的数字)。
有时人们也会用一个离散下降的学习率,也就是某个步骤有某个学习率,一会之后,学习率减少了一半,一会儿减少一半,一会儿又一半,这就是离散下降(discrete stair cease)的意思。
人们有时候还会做一件事,手动衰减。如果你一次只训练一个模型,如果你要花上数小时或数天来训练,有些人的确会这么做,看看自己的模型训练,耗上数日,然后他们觉得,学习速率变慢了,我把 α 调小一点。手动控制 α 当然有用,时复一时,日复一日地手动调整 α ,只有模型数量小的时候有用,但有时候人们也会这么做。
2.10 局部最优的问题
梯度下降法或者某个算法可能困在一个局部最优中,而不会抵达全局最优。如果你要作图计算一个数字,比如说这两个维度,就容易出现有多个不同局部最优的图,而这些低维的图曾经影响了我们的理解,但是这些理解并不正确。事实上,如果你要创建一个神经网络,通常梯度为零的点并不是这个图中的局部最优点,实际上成本函数的零梯度点,通常是鞍点。
如果局部最优不是问题,那么问题是什么?结果是平稳段会减缓学习,平稳段是一块区域,其中导数长时间接近于0,如果你在此处,梯度会从曲面从从上向下下降,因为梯度等于或接近0,曲面很平坦,你得花上很长时间慢慢抵达平稳段的这个点,我们可以沿着这段长坡走,直到这里,然后走出平稳段。
所以此次视频的要点是,首先,你不太可能困在极差的局部最优中,条件是你在训练较大的神经网络,存在大量参数,并且成本函数 J 被定义在较高的维度空间。
第二点,平稳段是一个问题,这样使得学习十分缓慢,这也是像Momentum或是RMSprop,Adam这样的算法,能够加速学习算法的地方。在这些情况下,更成熟的优化算法,如Adam算法,能够加快速度,让你尽早往下走出平稳段。
week3 超参数调试、Batch正则化和程序框架
3.1 调试处理
学习速率\(\alpha\)是需要调试的最重要的超参数
momentum参数\(\beta\),这里0.9就是一个很好的默认值。
还会去调试mini-batch的大小。
当应用Adam算法的时候,从不调试\(\beta_1\),\(\beta_2\)和\(\epsilon\),其他的总是选定0.9,0.999和\(10^{-8}\),如果你想的话也可以调试它们
\(\alpha\) 无疑是最重要的,接下来是我用橙色圈住的那些,然后是我用紫色圈住的那些,但这不是严格且快速的标准
常见的做法是在网格中取样点,你可以尝试这所有的25个点,然后选择哪个参数效果最好。当参数的数量相对较少时,这个方法很实用。
随机选择点,所以你可以选择同等数量的点,接着,用这些随机取的点试验超参数的效果。之所以这么做是因为,对于你要解决的问题而言,你很难提前知道哪个超参数最重要,正如你之前看到的,一些超参数的确要比其它的更重要。
举个例子,假设超参数1是 α (学习速率),取一个极端的例子,假设超参数2是Adam算法中,分母中的 ϵ 。在这种情况下, α 的取值很重要,而 ϵ 取值则无关紧要。如果你在网格中取点,接着,你试验了 α 的5个取值,那你会发现,无论 ϵ 取何值,结果基本上都是一样的。所以,你知道共有25种模型,但进行试验的 α 值只有5个,我认为这是很重要的。
对比而言,如果你随机取值,你会试验25个独立的 α ,似乎你更有可能发现效果更好的那个。
我已经解释了两个参数的情况,实践中,你搜索的超参数可能不止两个。假如,你有三个超参数,这时你搜索的不是一个方格,而是一个立方体,超参数3代表第三维,接着,在三维立方体中取值,你会试验大量的更多的值,三个超参数中每个都是。
当你给超参数取值时,另一个惯例是采用由粗糙到精细的策略。
比如在二维的那个例子中,你进行了取值,也许你会发现效果最好的某个点,也许这个点周围的其他一些点效果也很好,那在接下来要做的是放大这块小区域(小蓝色方框内),然后在其中更密集得取值或随机取值,聚集更多的资源,在这个蓝色的方格中搜索,如果你怀疑这些超参数在这个区域的最优结果,那在整个的方格中进行粗略搜索后,你会知道接下来应该聚焦到更小的方格中。在更小的方格中,你可以更密集得取点。所以这种从粗到细的搜索也经常使用。
3.2 为超参数选择合适的范围
你已经看到了在超参数范围中,随机取值可以提升你的搜索效率。但随机取值并不是在有效范围内的随机均匀取值,而是选择合适的标尺
假设你要选取隐藏单元的数量\(n^{[l]}\),假设,你选取的取值范围是从50到100中某点,这种情况下,看到这条从50-100的数轴,你可以随机在其取点,这是一个搜索特定超参数的很直观的方式。或者,如果你要选取神经网络的层数,我们称之为字母 L,你也许会选择层数为2到4中的某个值,接着顺着2,3,4随机均匀取样才比较合理,你还可以应用网格搜索,你会觉得2,3,4,这三个数值是合理的,这是在几个在你考虑范围内随机均匀取值的例子,这些取值还蛮合理的,但对某些超参数而言不适用。
看看这个例子,假设你在搜索超参数 α(学习速率),假设你怀疑其值最小是0.0001或最大是1。如果你画一条从0.0001到1的数轴,沿其随机均匀取值,那90%的数值将会落在0.1到1之间,结果就是,在0.1到1之间,应用了90%的资源,而在0.0001到0.1之间,只有10%的搜索资源,这看上去不太对。
反而,用对数标尺搜索超参数的方式会更合理,因此这里不使用线性轴,分别依次取0.0001,0.001,0.01,0.1,1,在对数轴上均匀随机取点,这样,在0.0001到0.001之间,就会有更多的搜索资源可用,还有在0.001到0.01之间等等。
所以在Python中,你可以这样做,使r=-4*np.random.rand()
,然后 a 随机取值, \(a=10^r\),所以,第一行可以得出$ r\in[-4,0] \(,那么\)a\in[10{-4},100]$ ,所以最左边的数字是 $ 10^{-4}$,最右边是\(10^0\)。
更常见的情况是,如果你在 \(10^a\)和$ 10^b$之间取值,在此例中,这是 \(10^a\)(0.0001),你可以通过0.0001算出 a 的值,即-4,在右边的值是 \(10^b\) ,你可以算出 b 的值 1 ,即0。你要做的就是在 [ a , b ]区间随机均匀地给 r 取值,这个例子中 \(r\in[-4,0]\),然后你可以设置 a 的值,基于随机取样的超参数 \(a=10^r\)
所以总结一下,在对数坐标下取值,取最小值的对数就得到 a的值,取最大值的对数就得到 b值,所以现在你在对数轴上的\(10^a\)到 \(10^b\)区间取值,在 a , b 间随意均匀的选取 r 值,将超参数设置为10^r$ ,这就是在对数轴上取值的过程。
最后,另一个棘手的例子是给 β 取值,用于计算指数的加权平均值。假设你认为 β 是0.9到0.999之间的某个值,也许这就是你想搜索的范围。记住这一点,当计算指数的加权平均值时,取0.9就像在10个值中计算平均值,有点类似于计算10天的温度平均值,而取0.999就是在1000个值中取平均。
我们要探究的是$1-\beta $,此值在0.1到0.001区间内,所以我们会给\(1-\beta\)取值,大概是从0.1到0.001,应用之前幻灯片中介绍的方法,这是 \(10^{-1}\) ,这是 \(10^{-3}\) ,值得注意的是,在之前的幻灯片里,我们把最小值写在左边,最大值写在右边,但在这里,我们颠倒了大小。这里,左边的是最大值,右边的是最小值。所以你要做的就是在 $ [-3,-1]$里随机均匀的给 r 取值。你设定了 $1-\beta=10^r \(,所以\) \beta=1-10^r$,然后这就变成了在特定的选择范围内超参数随机取值。希望用这种方式得到想要的结果,你在0.9到0.99区间探究的资源,和在0.99到0.999区间探究的一样多。
这是因为当 β 接近1时,所得结果的灵敏度会变化,即使 β 有微小的变化。所以 β 在0.9到0.9005之间取值,无关紧要,你的结果几乎不会变化。
但 β值如果在0.999到0.9995之间,这会对你的算法产生巨大影响,对吧?在这两种情况下,是根据大概10个值取平均。但这里,它是指数的加权平均值,基于1000个值,现在是2000个值,因为这个公式 $ \frac1{1-\beta}$ ,当 β接近1时, β 就会对细微的变化变得很敏感。所以整个取值过程中,你需要更加密集地取值,在 β 接近1的区间内,或者说,当 \(1-\beta\)接近于0时,这样,你就可以更加有效的分布取样点,更有效率的探究可能的结果。
3.3 超参数训练实战
深度学习领域中,发展很好的一点是,不同应用领域的人们会阅读越来越多其它研究领域的文章,跨领域去寻找灵感。
一种是你照看一个模型,通常是有庞大的数据组,但没有许多计算资源或足够的CPU和GPU的前提下,基本而言,你只可以一次负担起试验一个模型或一小批模型,在这种情况下,即使当它在试验时,你也可以逐渐改良。
每天花时间照看此模型,即使是它在许多天或许多星期的试验过程中。所以这是一个人们照料一个模型的方法,观察它的表现,耐心地调试学习率,但那通常是因为你没有足够的计算能力,不能在同一时间试验大量模型时才采取的办法。
另一种方法则是同时试验多种模型,你设置了一些超参数,尽管让它自己运行,或者是一天甚至多天,然后你会获得像这样的学习曲线,用这种方式你可以试验许多不同的参数设定,然后只是最后快速选择工作效果最好的那个。
所以这两种方式的选择,是由你拥有的计算资源决定的,如果你拥有足够的计算机去平行试验许多模型,那绝对采用鱼子酱方式。
3.4 归一化网络的激活函数
当训练一个模型,比如logistic回归时,你也许会记得,归一化输入特征可以加快学习过程。你计算了平均值,从训练集中减去平均值,计算了方差,接着根据方差归一化你的数据集,在之前的视频中我们看到,这是如何把学习问题的轮廓,从很长的东西,变成更圆的东西,更易于算法优化。所以这是有效的,对logistic回归和神经网络的归一化输入特征值而言。
对任何一个隐藏层而言,我们能否归一化 a 值,在此例中,比如说 $a^{[2]} \(的值,但可以是任何隐藏层的,以更快的速度训练,\) w{[3]},b\(因为\)a^{[2]}$是下一层的输入值,所以就会影响 \(w^{[3]},b^{[3]}\)的训练。简单来说,这就是Batch归一化的作用。
尽管严格来说,我们真正归一化的不是$ a^{[2]}$,而是 \(z^{[2]}\),深度学习文献中有一些争论,关于在激活函数之前是否应该将值\(z^{[2]}\)归一化,或是否应该在应用激活函数$ a^{[2]} \(后再规范值。**实践中,经常做的是归一化\) z^{[2]}$ ,所以这就是我介绍的版本,我推荐其为默认选择,那下面就是Batch**归一化的使用方法。
在神经网络中,已知一些中间值,假设你有一些隐藏单元值,从\(z^{(1)}\)到\(z^{(m)}\),这些来源于隐藏层,所以这样写会更准确,即 \(z^{[l](i)}\)为隐藏层, i 从1到 m ,但这样书写,我要省略 l及方括号,以便简化这一行的符号。所以已知这些值,如下,你要计算平均值,强调一下,所有这些都是针对 l 层,但我省略 l及方括号,然后用正如你常用的那个公式计算方差,接着,你会取每个\(z^{(i)}\)值,使其规范化,方法如下,减去均值再除以标准偏差,为了使数值稳定,通常将 ϵ 作为分母,以防止 \(\sigma=0\) 的情况。
所以现在我们已把这些 z 值标准化,化为含平均值0和标准单位方差,所以 z 的每一个分量都含有平均值0和方差1,但我们不想让隐藏单元总是含有平均值0和方差1,也许隐藏单元有了不同的分布会有意义,所以我们所要做的就是计算,我们称之为 \(\tilde{z}^{(i)}\), \(\tilde{z}^{(i)}=\gamma z^{(i)}_{norm}+\beta\),这里 γ 和 β 是你模型的学习参数,所以我们使用梯度下降或一些其它类似梯度下降的算法,比如Momentum或者Nesterov,Adam,你会更新 γ 和 β ,正如更新神经网络的权重一样。
请注意 γ 和 β的作用是,你可以随意设置\(\tilde{z}^{(i)}\)的平均值,事实上,如果 \(\gamma=\sqrt{\sigma^2+\epsilon}\) ,如果\(\gamma\) 等于这个分母项$ z{(i)}_{norm}=\frac{z-\mu}{\sqrt{\sigma^2+\epsilon}}$ 中的分母), β 等于 μ ,这里的这个值是 \(z^{(i)}_{norm}=\frac{z^{(i)}-\mu}{\sqrt{\sigma^2+\epsilon}}\)中的 μ ,那么 \(\gamma z^{(i)}_{norm}+\beta\) 的作用在于,它会精确转化这个方程,如果这些成立( \(\gamma=\sqrt{\sigma^2+\epsilon}\),\(\beta=\mu\) ),那么\(\tilde{z}^{(i)}=z^{(i)}\)。
通过对\(\gamma\) 和 β 合理设定,规范化过程,即这四个等式,从根本来说,只是计算恒等函数,通过赋予 \(\gamma\)和 β 其它值,可以使你构造含其它平均值和方差的隐藏单元值。
所以我希望你学到的是,归一化输入特征 X 是怎样有助于神经网络中的学习,Batch归一化的作用是它适用的归一化过程,不只是输入层,甚至同样适用于神经网络中的深度隐藏层。你应用Batch归一化了一些隐藏单元值中的平均值和方差,不过训练输入和这些隐藏单元值的一个区别是,你也许不想隐藏单元值必须是平均值0和方差1。
3.5 将 Batch 拟合进神经网络
但Batch归一化的做法是将值进行Batch归一化,简称BN,此过程将由 \(\beta^{[1]}\) 和\(\gamma^{[1]}\) 两参数控制,这一操作会给你一个新的规范化的$ z{[1]}$值$\tilde{z}\(,然后将其输入激活函数中得到\)a^{[1]} $,即
\[a^{[1]}=g^{[1]}(\tilde{z}^{[l]}) \]与你在第一层所做的类似,你会将 \(z^{[2]}\)进行Batch归一化,现在我们简称BN,这是由下一层的Batch归一化参数所管制的,即$ \beta^{[2]}$和 \(\gamma^{[2]}\) ,现在你得到 \(\tilde{z}^{[2]}\) ,再通过激活函数计算出 \(a^{[2]}\)等等。
Batch归一化是发生在计算 z 和 a 之间的
对于给定层,计算\(d\beta^{[1]}\),接着更新参数\(\beta\)
\[\beta^{[1]} = \beta^{[1]} - \alpha d \beta^{[1]} \]可以使用Adam或者RMSprop或者momentum 来更新参数\(\beta\),\(\gamma\)
Batch归一化在tensorflow中
tf.nn.batch_normalization
batch归一化通常和训练集的mini-batch一起使用。
用第一个mini-batch(\(X^{\{1\}}\)),计算\(z^{{[1]}}\),使用batch归一化减去均值,除以标准差,由\(\beta^{[1]}\)和\(\gamma^{[1]}\)重新缩放,这样得到了\(\tilde{z}^{[1]}\) 。然后计算\(\tilde{z}^{[2]}\)...
类似的工作,计算第二个mini-batch(\(X^{\{2\}}\))...
这里有一个细节:计算z的方式:
\[z^{[l]} = w^{[l]}a^{[l-1]}+b^{[l]} \]Batch归一化做的事,先将\(z^{[l]}\)归一化,结果为均值0和标准方差,再由\(\beta\)和\(\gamma\)重新缩放,这样意味着无论\(b^{[l]}\)的值是多少,都是要被减去的,因为在batch归一化的过程中,要计算\(z^{[l]}\)的均值再减去平均值,在这次的mini-batch中增加任何常数,数值都不会改变,因为加上的任何常数都会被均值减去所抵消。
在使用batch归一化的时候,你可以消除这个参数\(b^{[1]}\),或者也可以暂时将其设置为0,那么参数变成
\[z^{[l]} = w^{[l]}a^{[l-1]} \]然后计算归一化的
\[\tilde{z}^{[1]} = \gamma^{[l]}z^{[l} + \beta^{[l]} \]\(z^{[l]}\),\(b^{[l]}\) \(\beta^{[1]}\),\(\gamma^{[1]}\) 的维数:(\(n^{[l]}\),1)
运行t = 1 到batch数量的for循环,在mini-batch(\(X^{\{1\}}\))上用正向prop,每个隐藏层都应用正想prop,然后用batch归一化代替\(z^{[l]}\)为\(\tilde{z}^{[l]}\)。然后反向prop计算\(dw^{[l]}\)和\(db^{[l]}\),\(d\beta^{[l]}\)和\(d\gamma^{[l]}\)。然后更新参数:
\[w^{[l]} = w^{[l]} - \alpha dw^{[l]} \]\[\beta^{[l]} = \beta^{[l]} - \alpha d\beta^{[l]} \]\[\gamma^{[l]} = \gamma^{[l]} - \alpha d\gamma^{[l]} \]3.6 BatchNorm 为什么起作用
一个原因是,通过归一化所有的输入特征值 x ,以获得类似范围的值,可以加速学习。
第二个原因是,它可以使权重比你的网络更滞后或更深层
“Covariate shift”,想法是这样的,如果你已经学习了 x 到 y 的映射,如果 x的分布改变了,那么你可能需要重新训练你的学习算法。
Batch归一化减少了输入值改变的问题,它的确使这些值变得更稳定,神经网络的之后层就会有更坚实的基础。即使使输入分布改变了一些,它会改变得更少。它做的是当前层保持学习,当改变时,迫使后层适应的程度减小了,你可以这样想,它减弱了前层参数的作用与后层参数的作用之间的联系,它使得网络每层都可以自己学习,稍稍独立于其它层,这有助于加速整个网络的学习。
重点是Batch归一化的意思是,尤其从神经网络后层之一的角度而言,前层不会左右移动的那么多,因为它们被同样的均值和方差所限制,所以,这会使得后层的学习工作变得更容易些。
Batch归一化还有一个作用,它有轻微的正则化效果,Batch归一化中非直观的一件事是,每个mini-batch,我会说mini-batch\(X^{\{t\}}\)的值为 \(z^{[t]},z^{[l]}\) ,在mini-batch计算中,由均值和方差缩放的,因为在mini-batch上计算的均值和方差,而不是在整个数据集上,均值和方差有一些小的噪声,因为它只在你的mini-batch上计算,比如64或128或256或更大的训练例子。因为均值和方差有一点小噪音,因为它只是由一小部分数据估计得出的。缩放过程从$ z^{[l]}$到 $ \tilde{z}^{[l]}$,过程也有一些噪音,因为它是用有些噪音的均值和方差计算得出的。
也许另一个轻微非直观的效果是,如果你应用了较大的mini-batch,对,比如说,你用了512而不是64,通过应用较大的min-batch,你减少了噪音,因此减少了正则化效果,这是dropout的一个奇怪的性质,就是应用较大的mini-batch可以减少正则化效果。
3.7 测试时的 BatchNorm
回想一下,在训练时,这些就是用来执行Batch归一化的等式。
注意用于调节计算的$ \mu$ 和\(\sigma^2\) 是在整个mini-batch上进行计算,但是在测试时,你可能不能将一个mini-batch中的6428或2056个样本同时处理,因此你需要用其它方式来得到$ \mu$ 和 \(\sigma^2\)
指数加权平均就成了你对这一隐藏层的 z 均值、方差的估值
3.8 Softmax 回归
如果我们有多种可能的类型的话呢?叫做Softmax回归,能让你在试图识别某一分类时做出预测,或者说是多种分类中的一个。
C 来表示你的输入会被分入的类别总个数,在这个例子中,我们有4种可能的类别,包括“其它”或“以上均不符合”这一类。当有4个分类时,指示类别的数字,就是从0到 C − 1,换句话说就是0、1、2、3。
在神经网络的最后一层,你将会像往常一样计算各层的线性部分,$z^{[l]} $这是最后一层的 z 变量
\[z^{[l]} = W^{[l]}a^{[L-1]}+b^{[l]} \]这里算出了z之后,使用softmax激活函数
这个函数,首先需要计算一个临时变量t = \(e^{z^{[l]}}\),对所有元素求幂,输出的\(a^{[l]} = \frac{e^{z^{[l]}}}{\sum_{j=1}^4 t_i}\)
\[a^{[l]} = \frac{t_i}{\sum_{j=1}^4 t_i} \]3.9 训练一个 Softmax 分类器
上一个视频中我们学习了Softmax层和Softmax激活函数,在这个视频中,你将更深入地了解Softmax分类,并学习如何训练一个使用了Softmax层的模型。
Softmax这个名称的来源是与所谓hardmax对比,hardmax会把向量 z 变成这个向量 \(\begin{bmatrix} 1 \\ 0 \\ 0 \\ 0 \end{bmatrix}\),hardmax函数会观察 z的元素,然后在 z 中最大元素的位置放上1,其它位置放上0,所这是一个hard max,也就是最大的元素的输出为1,其它的输出都为0。与之相反,Softmax所做的从 z 到这些概率的映射更为温和,我不知道这是不是一个好名字,但至少这就是softmax这一名称背后所包含的想法,与hardmax正好相反。
有一点我没有细讲,Softmax回归将logistic回归推广到了两种分类以上。
输出层会计算$ z^{[l]}$ ,它是 C ∗ 1 维的,在这个例子中是4×1,然后你用Softmax激活函数来得到$ a^{[l]}$ 或者说y ,然后又能由此计算出损失。
其实初始化反向传播所需要的关键步骤或者说关键方程是这个表达式 \(dz^{[l]}=\hat{y}-y\),你可以用 \(\hat{y}\)这个4×1向量减去 y 这个4×1向量,你可以看到这些都会是4×1向量,当你有4个分类时,在一般情况下就是 C ∗ 1 ,这符合我们对 d z 的一般定义,这是对$ z{[l]}$损失函数的偏导数$dz=\frac{\partial J}{\partial z^{[l]}}$,如果你精通微积分就可以自己推导,或者说如果你精通微积分,可以试着自己推导,但如果你需要从零开始使用这个公式,它也一样有用。
有了这个,你就可以计算\(dz^{[l]}\) ,然后开始反向传播的过程,计算整个神经网络中所需要的所有导数。
3.10 深度学习框架
3.11 TensorFlow
import numpy as np
import tensorflow as tf
tf.compat.v1.disable_eager_execution()
coefficients = np.array([[1.],[-20.],[100.]])
# 定义变量 一旦被定义,这里的平方乘法和加减运算都重载了
w = tf.Variable(0,dtype=tf.float32)
x = tf.compat.v1.placeholder(tf.float32,[3,1]) #placeholder说明一会再给x赋值
# 定义损失函数
# 只需实现前向传播,这里的乘除有内置的函数去求导,自动完成后向传播
# cost = tf.add(tf.add(w**2,tf.multiply(-10.,w)),25)
#cost = w**2 - 10*w + 25
cost = x[0][0]*w**2+x[1][0]*w + x[2][0]
train = tf.compat.v1.train.GradientDescentOptimizer(0.01).minimize(cost)
init = tf.compat.v1.global_variables_initializer()
session = tf.compat.v1.Session()
session.run(init)
print(session.run(w))
# 运行一步梯度下降法
session.run(train,feed_dict={x:coefficients})
print(session.run(w))
for i in range(1000):
session.run(train,feed_dict={x:coefficients})
print(session.run(w))
标签:beta,第二课,训练,梯度,batch,Practical,正则,参数,learning
From: https://www.cnblogs.com/MyBlogForRecord/p/18293507