五、深度学习的实践方面

1、训练集、验证集、测试集(Train/Dev/Test sets)

  在配置训练(train)、验证(devlopment)和测试(test)数据集的过程中做出正确决策在很大程度上会帮助大家创建高效的神经网络。训练神经网络时我们需要做很多决策,如神经网络的层数、每个隐藏层包含的隐藏单元、学习因子(学习速率)、各层激活函数的选择等。

  一开始我们并不知道这些参数,实际上,应用型机器学习是一个高度迭代的过程。通常在项目启动时,先有一个初步想法(idea),比如构建一个含有特定层数、隐藏单元数量或者数据集个数等的神经网络,然后编码(code),并尝试运行这些代码,通过运行和测试得到该神经网络,或这些配置信息的运行结果;你可能根据结果重新完善自己的想法,改变策略,不断试验(experiment),或者找到更好的神经网络不断更新迭代自己的方案。

  最佳决策往往取决于你拥有的数据量,计算机配置中输入特征的数量,用GPU还是CPU训练,GPU和CPU的具体配置以及其他诸多因素。即使是最有经验的深度学习专家也很难第一次就找到最合适的参数。应用深度学习是一个反复迭代的过程,需要通过反复多次的循环训练得到最优化参数。因此,循环该过程的效率是决定项目进展速度的一个关键因素。而创建高质量的训练数据集、验证集和测试集也有助于提高循环效率。

  通常将所有的样本数据分成三个部分:Train/Dev/Test sets。训练集(Train sets)用来执行训练算法;验证集(Dev sets)或简单交叉验证集选择最好的模型,经过充分验证不同算法的表现情况,选定了最终模型;测试集(Test sets)进行评估,用来测试最好算法的实际表现,作为该算法的无偏估计。

  为了无偏评估算法的运行状况,小数据量时(100、1000或者10000)机器学习一般三七分,即70%训练集30%测试集。如果有验证集,则设置比例依次为60%、20%、20%对于百万级数据样本,Train/Dev/Test sets的比例通常可以设置为98%/1%/1%,对于数量过百万的应用,比例可以为99.5%/0.25%/0.25%,或者99.5%/0.4%/0.1%。因此,样本数据量越大,相应的Dev/Test sets的比例可以设置的越低一些。

  根据经验,建议大家要确保验证集和测试集的数据来自同一分布。因为你们要用验证集来评估不同的模型,尽可能地优化性能。如果验证集和测试集来自同一个分布就会很好。最后,数据没有测试集也没关系,测试集的目的是对所选定的神经网络系统做出无偏评估,如果不需要无偏评估,也可以不设置测试集,在验证集(test sets)上选择合适的模型。

2、偏差和方差(Bias/Variance)

  假设这是数据集,如果给这个数据集(图左)拟合一条直线,可能得到一个逻辑回归拟合,但它并不能很好地拟合该数据集,这是偏差高(high bias)的情况,称为欠拟合(underfit the data);相反地,如果我们拟合一个非常复杂的分类器,比如深度神经网络或者含有隐藏单元的神经网络,像这个数据集(图右),分类器方差较高(high variance),数据过度拟合(overfit the data);这两者之间,可能还有像(图中)一样的情况,复杂程度适中,数据拟合适度的分类器,这个看上起更为合理,称适度拟合(just right),这是在二维的情况下,将偏差和方差可视化。

  多维通过绘制数据和可视化分割边界无法实现,但我们可以通过几个指标来研究偏差和方差。一般来说,Train set error体现了是否出现bias,Dev set error体现了是否出现variance(正确地说,应该是Dev set error与Train set error的相对差值)。以上分析前提都是假设基本误差(base error)很小(假设人眼辨别的错误率接近0%),训练集和验证集数据来自相同分布。

3、机器学习基础

  在训练神经网络时,首先,如果碰到高偏差(欠拟合),通常是增加神经网络的隐藏层层数、神经元个数,训练时间延长,选择其它更复杂的NN模型等。这是最低标准。在基础误差不高的情况下,一般都能通过这些方式有效降低和避免高偏差,至少在训练集上表现良好。其次,减少高方差(过拟合)的方法通常是增加训练样本数据,但有时无法获得更多的数据,进行正则化(Regularization)来减少过拟合,有时候我们不得不不断尝试,但有时能找到更合适的神经网络框架,可能会一箭双雕——同时减少方差和偏差。

  注意:

    1. 处理高方差和高偏差的方法不一样,首先要通过验证集找到存在的问题,再对症下药,明确这一点有助于选出最有效的方法。
    2. 但是在当前的深度学习和大数据时代,只需要持续训练一个更大的网络,只要准备了更多的数据,同时减少方差和偏差不是没有可能。现在有工具可以做到降低一方而不增加另一方,不必过于关注如何平衡偏差和方差。

4、正则化(Regularization)

  如果怀疑神经网络过度拟合了数据,即出现了高方差(high variance),那么最先想到的可能是正则化(regularization),另一个方法是准备更多的数据(more data)。

  下面用逻辑回归来实现这些设想。求成本函数(cost function)J的最小值。它是我们定义的成本函数,参数包含一些训练数据和不同数据中个体预测的损失,w和b逻辑回归的两个参数,w是一个多维度参数矢量,b是一个实数。实现正则化只需要添加参数λ(正则化参数),这种方法称为L2 regularization。先给出两种常见的正则化表达式:

L2(训练模型时用的更多) regularization的表达式为:

L1 regularization的表达式为:

  λ是正则化参数(超参数的一种),通过验证集或者交叉验证来配置这个参数,尝试各种各样的数据,选择最好的λ。要考虑训练集中的权衡,把参数设置为较小值,这样可以避免过拟合。在python中,lambda是保留字,所以编码时,使用lambd来表示lambda,以避免冲突。

  在深度学习中如何实现正则化呢?(Frobenius范数——“弗罗贝尼乌斯范数”,用下标F表示)

  首先写出成本函数J的表达式,这里加了惩罚项(如上图)。“弗罗贝尼乌斯范数”,它表示一个矩阵中所有元素的平方和。如何使用该范数实现梯度下降呢?用backprop计算dW的值,backprop会给出J对W的偏导数,实际上是W^[l],因为我们额外增加的正则化项,所以给dW加上(λ/m)*W^[l]这一项,然后定义这个更新项,使用新定义的dW^[l],它的定义含有相关参数代价函数导数和,以及最后添加的额外正则项,这也是L2正则化有时被称为“权重衰减”(weight decay)的原因。展开来写得到下面的表达式:

5、Dropout正则化

  除了L2 regularization之外,还有另外一种非常实用的正则化方法:Dropout(随机失活)

  假设你在训练左图这样的神经网络,存在过拟合,我们复制这个神经网络,dropout会遍历网络的每一层,并设置消除神经网络中节点的概率。假设网络中的每一层,每个节点都以抛硬币的方式设置概率,即每个节点得以保留和消除的概率都是0.5,这样我们会随机消除一些节点,然后删除掉从该节点进出的连线,最后得到一个节点更少,规模更小的网络,然后用backprop方法进行训练。(如下图右侧)

  这是网络节点精简后的一个样本,对于其它样本,我们照旧设置概率为0.5。对于每个训练样本,我们都将采用一个精简后神经网络来训练它(如上图右边)。

  如何实施dropout呢?方法有几种,接下来讲的是最常用的方法,即inverted dropout(反向随机失活),出于完整性考虑,我们用一个三层网络来举例说明。

  显然在测试阶段,我们并未使用dropout,因为在测试阶段进行预测时,我们不期望输出结果是随机的,所有神经元都在工作,如果测试阶段应用dropout函数,预测会受到干扰。Inverted dropout函数在除以keep-prob时可以记住上一步的操作,目的是确保即使在测试阶段不执行dropout来调整数值范围,激活函数的预期结果也不会发生变化,所以没必要在测试阶段额外添加尺度参数,这与训练阶段不同。  

  dropout一大缺点就是成本函数J不再被明确定义,每次迭代,都会随机移除一些节点,如果再三检查梯度下降的性能,实际上是很难进行复查的。定义明确的成本函数J每次迭代后都会下降,因为我们所优化的代价函数J实际上并没有明确定义,或者说在某种程度上很难计算,所以我们失去了调试工具来绘制这样的图片。通常会关闭dropout函数,将keep-prob的值设为1,运行代码,确保函数J单调递减。然后打开dropout函数,希望在dropout过程中,代码并未引入bug。

6、其他正则化方法

  一种方法是数据扩增(data augmentation)。假设你正在拟合猫咪图片分类器,(下图)如果你想通过扩增训练数据来解决过拟合,但扩增数据代价高,而且有时候我们无法扩增数据,但我们可以通过添加这类图片来增加训练集。例如,水平翻转图片、随意裁剪图片,并把它添加到训练集。所以现在训练集中有原图,还有翻转后的这张图片,所以通过水平翻转图片,训练集则可以增大一倍,因为训练集有冗余,这虽然不如我们额外收集一组新图片那么好,但这样做节省了获取更多猫咪图片的花费。

  另外一种常用的方法叫作early stopping,运行梯度下降时,我们可以绘制训练误差,或只绘制成本函数J的优化过程,在训练集上用0-1记录分类误差次数。呈单调下降趋势,如下图。还可以绘制验证集误差,它可以是验证集上的分类误差,或验证集上的代价函数,逻辑损失和对数损失等,你会发现,验证集误差通常会先呈下降趋势,然后在某个节点处开始上升。

  当你还未在神经网络上运行太多迭代过程的时候,参数w接近0,因为随机初始化w值时,它的值可能都是较小的随机值,所以在你长期训练神经网络之前w依然很小,在迭代过程和训练过程中w的值会变得越来越大,所以early stopping要做就是在中间点停止迭代过程,我们得到一个w值中等大小的弗罗贝尼乌斯范数,与L2正则化相似,选择参数w范数较小的神经网络,但愿你的神经网络过度拟合不严重。

  early stopping的主要缺点就是你不能独立地处理这两个问题(优化J和预防过拟合)。Early stopping的优点是,只运行一次梯度下降,你可以找出w的较小值,中间值和较大值,而无需尝试L2正则化超级参数的很多值。但往往更倾向于使用L2正则化。

如果不用early stopping,另一种方法就是L2正则化,训练神经网络的时间就可能很长。

  机器学习的其中一步是选择一个算法来优化代价函数J,常用工具有梯度下降,Momentum,RMSprop和Adam等。同时,也不想出现过拟合,解决工具如正则化,扩增数据等

7、正则化输入(Normalizing inputs)

  训练神经网络,其中一个加速训练的方法就是归一化输入(normalizing inputs)。假设我们有个训练集,它有两个输入特征,所以输入特征x是二维的,这是数据集的散点图。归一化输入需要两个步骤,第一步是零均值化

是一个向量,X等于每个训练数据x减去u,意思是移动训练集,直到它完成零均值化。

第二步是归一化方差,注意特征x1的方差比x2大得多。

  这是节点y的平方,是一个向量,它的每个特征都有方差。由于已经完成了零均值化,把所有数据除以σ平方。这样x1和x2的方差都等于1。提示一下,如果你用它来调整训练数据,那么用相同的均值和方差来归一化测试集不论是训练数据还是测试数据,都是通过相同μ和σ^2定义的相同数据转换,其中μ和σ^2是由训练集数据计算得来的。

8、梯度小时和梯度爆炸(Vanishing and Exploding gradients)

  训练神经网络,尤其是深度神经所面临的一个问题就是梯度消失或梯度爆炸(vanishing and exploding gradients),也就是你训练神经网络的时候,导数或坡度有时会变得非常大,或者非常小,甚至于以指数方式变小,这加大了训练的难度。

  针对该问题,我们想出了一个不完整的解决方案,通过例子来理解。先看只有一个神经元的情况,单个神经元可能有4个输入特征,从x1到x4,如下图:

经过a=g(z)处理,最终得到y帽。

 因为b=0,先忽略b,为了预防z值过大或者过小,你可以看到n越大,你希望w_i越小,最合理的方法是设置为w_i = 1/n,n表示神经元的输入特征数量。设置第l层权重矩阵为:

其中,n^[l-1]是第l-1层神经元的数量。

相应的python伪代码为:

w[l] = np.random.randn(n[l],n[l-1])*np.sqrt(1/n[l-1])
如果你是用的是Relu激活函数,方差设置为2/n,效果会更好。

w[l] = np.random.randn(n[l],n[l-1])*np.sqrt(2/n[l-1])

  这里,用的是n^[l-1],因为本例中,逻辑回归的特征是不变的。但一般情况下第l层上的每个神经元都有个n^[l-1]输入。如果激活函数的输入特征被零均值和标准方差化,方差是1,也会调整到相似范围,这就没解决问题(梯度消失和爆炸问题)。但它确实降低了梯度消失和爆炸问题,因为它给权重矩阵W设置了合理值,你也知道,它不能比1大很多,也不能比1小很多,所以梯度没有爆炸或消失过快。

  对于几个其它变体函数,如tanh激活函数,它是1/n^[l-1],被称为Xavier初始化。还有另一种方法,可能在一些论文中看到过,它们使用的是公式:

代码如下:

w[l] = np.random.randn(n[l],n[l-1])*np.sqrt(2/n[l-1]*n[l])

  总之,如果激活函数是tanh,一般选择上面的初始化方法,方差为1/n。如果激活函数是ReLU,权重w的初始化一般令其方差为2/n。

  这些公式只是给你一个起点,它们给出初始化权重矩阵的方差的默认值,至于选择哪种初始化方法因人而异。如果你想添加方差,方差参数则是另一个你需要调整的超级参数,可以给公式np.sqrt(2/n[l-1]) 添加一个乘数参数,调优作为超级参数激增(hyperparameter surge)一份子的乘子参数(multiplier)。有时调优该超级参数效果一般,优先级较低。

9、梯度检验(Gradient checking)

  在实施backprop时,有一个测试叫做梯度检验(gradient checking),它的作用是确保backprop正确实施。可以帮我们节省了很多时间,能发现backprop实施过程中的bug,接下来,我们看看如何利用它来调试或检验backprop的实施是否正确。

  假设你的网络中有下列参数,梯度检验首先要做的是W^[1],b^[1],...,W^[L],b^[L]这些矩阵构造成一维向量,然后将这些一维向量组合起来构成一个更大的一维向量θ。这样cost function J(W^[1],b^[1],...,W^[L],b^[L])就可以表示成J(θ)。然后将反向传播过程通过梯度下降算法得到的dW^[1],db^[1],...,dW^[L],db^[L] 按照一样的顺序构造成一个一维向量 dθ。 dθ的维度与 θ一致。

  为了实施梯度检验,你要做的就是循环执行,从而对每个i也就是对每个θ组成元素计算的值,这里使用双边误差,也就是:

  这个值应该逼近dθ[i]=偏J/偏θ_i(成本函数J的偏导数),你需要对i的每个值都执行这个运算,得到两个向量,验证这些向量是否彼此接近,即计算两个向量的欧式距离

  分母只是用于预防这些向量太小或太大,分母使得这个方程式变成比率,我们执行这个方程式,ε可能为10^-7,如果你发现上面方程式得到的值为或更小,这就很好,这就意味着导数逼近很有可能是正确的,它的值非常小。如果这个值在10^-5范围内,就要小心了,表明梯度计算可能有问题,需要再次检查这个向量的所有项,确保没有一项误差过大,可能这里有bug。如果这个值为10^-3,或者更大,这时应该仔细检查所有θ项,经过一些调试,最终结果会是非常小的值。

10、关于梯度检验实现的标记

  注意:

    1. 首先,不要在训练中使用梯度检验,它只用于调试。为了实施梯度下降,你必须使用W和b反向传播来计算dθ,只有调试的时候才会计算它。
    2. 第二点,如果算法的梯度检验失败,要检查所有项,检查每一项,并试着找出bug。注意θ的各项与b和w的各项都是一一对应的。
    3. 第三点,在实施梯度检验时,如果使用正则化,请注意正则项。
    4. 第四点,梯度检验不能与dropout同时使用,因为每次迭代过程中,dropout会随机消除隐藏层单元的不同子集,难以计算dropout在梯度下降上的代价函数J。
    5. 最后一点,也是比较微妙的一点,现实中几乎不会出现这种情况,随机初始化时运行梯度检查。
posted @   鹤比纷恆红  阅读(24)  评论(0编辑  收藏  举报
相关博文:
阅读排行:
· 分享一个免费、快速、无限量使用的满血 DeepSeek R1 模型,支持深度思考和联网搜索!
· 使用C#创建一个MCP客户端
· ollama系列1:轻松3步本地部署deepseek,普通电脑可用
· 基于 Docker 搭建 FRP 内网穿透开源项目(很简单哒)
· 按钮权限的设计及实现
点击右上角即可分享
微信分享提示