📺 深度学习的实用层面 Practical aspects
🔈 在前几节我们已经学习了如何建立一个神经网络,或者浅层的,或者深度的。接下来我们将着重讨论和研究如何优化神经网络模型,例如调整超参数,提高算法运行速度等等。
一、训练,验证,测试集 Train / Dev / Test sets
选择最佳的**训练集(Training sets)、验证集(Development sets)、测试集(Test sets)**对神经网络的性能影响非常重要。
除此之外,在构建一个神经网络的时候,我们需要设置许多参数,例如神经网络的层数、每个隐藏层包含的神经元个数、学习因子(学习速率)、激活函数的选择等等。
实际上很难在第一次设置的时候就选择到这些最佳的参数,而是需要通过不断地迭代更新来获得。这个循环迭代的过程是这样的:
我们先有个想法 Idea,先选择初始的参数值,构建神经网络模型结构;
然后通过代码Code的形式,实现这个神经网络;
最后,通过实验 Experiment 验证这些参数对应的神经网络的表现性能。根据验证结果,我们对参数进行适当的调整优化,再进行下一次的 Idea -> Code -> Experiment 循环。
通过很多次的循环,不断调整参数,选定最佳的参数值,从而让神经网络性能最优化。
深度学习已经应用于许多领域中,比如 NLP,CV,Speech Recognition 等等。通常来说,最适合某个领域的深度学习网络往往不能直接应用在其它问题上。解决不同问题的最佳选择是根据样本数量、输入特征数量和电脑配置信息(GPU或者CPU)等,来选择最合适的模型。即使是最有经验的深度学习专家也很难第一次就找到最合适的参数。因此,应用深度学习是一个反复迭代的过程,需要通过反复多次的循环训练得到最优化参数。决定整个训练过程快慢的关键在于单次循环所花费的时间,单次循环越快,训练过程越快。而设置合适的Train/Dev/Test sets数量,能有效提高训练效率。一般地,我们将所有的样本数据分成三个部分:Train/Dev/Test sets。Train sets 用来训练你的算法模型;Dev sets 用来验证不同算法的表现情况,从中选择最好的算法模型;Test sets 用来测试最好算法的实际表现,作为该算法的无偏估计。之前人们通常设置Train sets和Test sets的数量比例为70%和30%。如果有Dev sets,则设置比例为60%、20%、20%,分别对应Train/Dev/Test sets。这种比例分配在样本数量不是很大的情况下,例如100,1000,10000,是比较科学的。但是如果数据量很大的时候,例如100万,这种比例分配就不太合适了。科学的做法是要将Dev sets和Test sets的比例设置得很低。因为Dev sets的目标是用来比较验证不同算法的优劣,从而选择更好的算法模型就行了。因此,通常不需要所有样本的20%这么多的数据来进行验证。对于100万的样本,往往只需要10000个样本来做验证就够了。Test sets也是一样,目标是测试已选算法的实际表现,无偏估计。对于100万的样本,往往也只需要10000个样本就够了。
因此,对于大数据样本,Train/Dev/Test sets的比例通常可以设置为98%/1%/1%,或者99%/0.5%/0.5%。样本数据量越大,相应的Dev/Test sets的比例可以设置的越低一些。
现代深度学习还有个重要的问题就是训练样本和测试样本分布上不匹配,意思是训练样本和测试样本来自于不同的分布。举个例子,假设你开发一个手机app,可以让用户上传图片,然后app识别出猫的图片。在app识别算法中,你的训练样本可能来自网络下载,而你的验证和测试样本可能来自不同用户的上传。从网络下载的图片一般像素较高而且比较正规,而用户上传的图片往往像素不稳定,且图片质量不一。因此,训练样本和验证/测试样本可能来自不同的分布。解决这一问题的比较科学的办法是尽量保证Dev sets和Test sets来自于同一分布。值得一提的是,训练样本非常重要,通常我们可以将现有的训练样本做一些处理,例如图片的翻转、假如随机噪声等,来扩大训练样本的数量,从而让该模型更加强大。即使Train sets和Dev/Test sets不来自同一分布,使用这些技巧也能提高模型性能。
最后提一点的是如果没有Test sets也是没有问题的。Test sets的目标主要是进行无偏估计。我们可以通过Train sets训练不同的算法模型,然后分别在Dev sets上进行验证,根据结果选择最好的算法模型。这样也是可以的,不需要再进行无偏估计了。如果只有Train sets和Dev sets,通常也有人把这里的Dev sets称为Test sets,我们要注意加以区别。
二、偏差,方差 Bias / Variance
偏差(Bias)和方差(Variance)是机器学习领域非常重要的两个概念和需要解决的问题。在传统的机器学习算法中,Bias 和 Variance 是对立的,分别对应着欠拟合和过拟合,我们常常需要在Bias和Variance之间进行权衡。而在深度学习中,我们可以同时减小Bias和Variance,构建最佳神经网络模型。如下图所示,显示了二维平面上,high bias,适度拟合 just right,high variance 的例子。可见,high bias 对应着欠拟合 underfitting,而 high variance 对应着过拟合 overfitting。
上图这个例子中输入特征是二维的,高偏差 high bias 和 高方差 high variance 可以直接从图中分类线看出来。而对于输入特征是高维的情况,如何来判断是否出现了 high bias 或者 high variance呢?
例如猫识别问题,输入是一幅图像,其特征维度很大。这种情况下,我们可以通过两个数值 训练集误差 Train set error 和 验证集误差 Dev set error 来理解 bias 和 variance:
🔸 High Variance 过拟合:假设Train set error为1%,而Dev set error为11%,即该算法模型对训练样本的识别很好,但是对验证集的识别却不太好。这说明了该模型对训练样本可能存在过拟合,模型泛化能力不强,导致验证集识别率低。这恰恰是high variance的表现。
🔸 High Bias 欠拟合:假设Train set error为15%,而Dev set error为16%,虽然二者error接近,即该算法模型对训练样本和验证集的识别都不是太好。这说明了该模型对训练样本存在欠拟合。这恰恰是high bias的表现。
🔸 High Variance And High Bias:假设Train set error为15%,而Dev set error为30%,说明了该模型既存在 high bias 也存在 high variance(深度学习中最坏的情况)。
模型既存在 high bias 也存在 high variance,可以理解成某段区域是欠拟合的,某段区域是过拟合的。
🔸 Low Variance And Low Bias:假设Train set error为0.5%,而Dev set error为1%,即 low bias 和 low variance,是最好的情况。
值得一提的是,以上的这些假设都是建立在 base error 是 0 的基础上,即人类都能正确识别所有猫类图片。base error 不同,相应的Train set error和Dev set error会有所变化,但没有相对变化。
一般来说,Train set error 体现了是否出现bias,Dev set error 体现了是否出现 variance(正确地说,应该是Dev set error 与 Train set error的相对差值)。
三、传统机器学习中解决高方差或高偏差的方法
回顾一下传统机器学习中解决高方差或高偏差的方法:
解决高方差(过拟合):
获得更多的训练样本
尝试减少特征的数量
尝试增加正则化程度 λ
解决高偏差(欠拟合):
尝试获得更多的特征
尝试增加多项式特征
尝试减少正则化程度 λ
显然,传统机器学习算法中,Bias 和 Variance 通常是对立的,减小 Bias 会增加 Variance,减小 Variance 会增加Bias。而在现在的深度学习中,通过使用更复杂的神经网络和海量的训练样本,一般能够同时有效减小 Bias 和Variance。这也是深度学习之所以如此强大的原因之一。
四、正则化 Regularization
虽然扩大训练样本数量也是减小 high variance 的一种方法,但是通常获得更多训练样本的成本太高,比较困难。所以,更可行有效的办法就是使用 regularization。
① 逻辑回归的正则化
回顾一下逻辑回归的代价函数表达式:
$J(w,b)=\frac1m\sum_{i=1}^mL(\hat y^{(i)},y^{(i)})=-\frac1m\sum_{i=1}^m[y^{(i)}log\ \hat y^{(i)}+(1-y^{(i)})log\ (1-\hat y^{(i)})]$
w 和 b 是逻辑回归的两个参数,w 是一个多维度参数矢量,b 是一个实数。在逻辑回归函数中加入参数 λ 实现正则化:
解释一下:
$\frac{\lambda}{2m}$ 乘以$w$范数的平方,其中 w 欧几里德范数的平方等于$w_{j}$($j$ 值从1到$n_{x}$)平方的和,也可表示为$w^{T}w$,也就是向量参数$w$ 的欧几里德范数(2范数)的平方,此方法称为 L2 正则化 ,因为这里用了欧几里德范数,被称为向量参数$w$的 L2 范数。
❓ 这里有个问题:为什么只对w进行正则化而不对b进行正则化呢?其实也可以对 b 进行正则化。但是一般 w 的维度很大,而b只是一个常数。相比较来说,参数很大程度上由w决定,改变b值对整体模型影响较小。所以,一般为了简便,就忽略对b的正则化了。
除了 L2 regularization 之外,还有另外一只正则化方法:L1 正则化。其表达式为:
与L2 regularization相比,L1 regularization得到的w更加稀疏,即很多w为零值。其优点是节约存储空间,因为大部分w为0。然而,实际上 L1 regularization 在解决 high variance 方面比 L2 regularization 并不更具优势。而且,L1的在微分求导方面比较复杂。所以,一般 L2 regularization 更加常用。
L1、L2 regularization中的 λ 就是正则化参数(超参数的一种)。可以设置 λ 为不同的值,在 Dev set 中进行验证,选择最佳的 λ。顺便提一下,在 Python 中,由于 lambda
是保留字,所以为了避免冲突,我们使用 lambd
来表示 λ。
② 神经网络的正则化
在深度学习模型(神经网络)中,L2 regularization 的表达式为:
通常,我们把 $||w^{[l]}||^2$ 称为 Frobenius 范数(弗罗贝尼乌斯范数),记为 $||w^{[l]}||_F^2$。一个矩阵的 Frobenius 范数就是计算所有元素平方和再开方:
值得注意的是,由于加入了正则化项,梯度下降算法中的 $dw^{[l]}$ 计算表达式需要做如下修改:
L2 regularization 也被称做 权重衰减 weight decay。这是因为,由于加上了正则项,$dw^{[l]}$ 有个增量,在更新 $w^{[l]}$ 的时候,会多减去这个增量,使得 $w^{[l]}$ 比没有正则项的值要小一些。不断迭代更新,不断地减小。
其中,$(1-\alpha\frac{\lambda}{m}) < 1$
五、为什么正则化有利于预防过拟合
为什么正则化能够有效避免 high variance,防止过拟合呢?下面我们通过几个例子说明。
还是之前那张图,从左到右,分别表示了欠拟合,刚好拟合,过拟合三种情况。
假如我们选择了非常复杂的神经网络模型,如上图左上角所示。在未使用正则化的情况下,我们得到的分类超平面可能是类似上图右侧的过拟合。但是,如果使用 L2 regularization,当 λ很大时,$w^{[l]}\approx0$。$w^{[l]}$ 近似为零,意味着该神经网络模型中的某些神经元实际的作用很小,可以忽略。从效果上来看,其实是将某些神经元给忽略掉了。这样原本过于复杂的神经网络模型就变得不那么复杂了,而变得非常简单化了。
如下图所示,整个简化的神经网络模型经过正则化变成了一个逻辑回归模型。问题就从 high variance 变成了 high bias 了:
六、Dropout 正则化
除了 L2 regularization 之外,还有另外一种防止过拟合的有效方法:Dropout 随机失活。
Dropout 是指在深度学习网络的训练过程中,对于每层的神经元,按照一定的概率将其暂时从网络中丢弃。也就是说,每次训练时,每一层都有部分神经元不工作,起到简化复杂网络模型的效果,从而避免发生过拟合。
Dropout 有不同的实现方法,接下来介绍一种常用的方法:反向随机失活 Inverted dropout。假设对于第 l 层神经元,设定保留神经元比例概率 keep_prob=0.8
,即该层有 20% 的神经元停止工作。dl 为 dropout 向量,设置 dl 为随机 vector,其中 80% 的元素为 1,20% 的元素为 0。在 python 中可以使用如下语句生成 dropout vector:
dl = np.random.rand(al.shape[0], al.shape[1]) < keep_prob
然后,第 l 层经过 dropout,随机删减 20% 的神经元,只保留 80% 的神经元,其输出为:
al = np.multiply(al,dl)
最后,还要对 al 进行 scale up 处理,即:
al /= keep_prob
以上就是 Inverted dropout 的方法。之所以要对 al 进行 scale up 是为了保证在经过 dropout 后,al 作为下一层神经元的输入值尽量保持不变。假设第 l 层有50个神经元,经过 dropout 后,有10个神经元停止工作,这样只有 40 个神经元有作用。那么得到的 al 只相当于原来的 80%。scale up 后,能够尽可能保持 al 的期望值相比之前没有大的变化。
Inverted dropout 的另外一个好处就是在对该 dropout 后的神经网络进行测试时能够减少 scaling 问题。因为在训练时,使用 scale up 保证 al 的期望值没有大的变化,测试时就不需要再对样本数据进行类似的尺度伸缩操作了。
⭐ 对于m个样本,单次迭代训练时,随机删除掉隐藏层一定数量的神经元;然后,在删除后的剩下的神经元上正向和反向更新权重w和常数项b;接着,下一次迭代中,先恢复之前删除的神经元,再重新随机删除一定数量的神经元,进行正向和反向更新w和b。不断重复上述过程,直至迭代训练完成。
值得注意的是,使用dropout训练结束后,在测试和实际应用模型时,不需要进行dropout和随机删减神经元,所有的神经元都在工作。
在使用 dropout 的时候,有几点需要注意:
首先,不同隐藏层的 dropout 系数
keep_prob
可以不同。一般来说,神经元越多的隐藏层,keep_prob
可以设置得小一些,例如 0.5;神经元越少的隐藏层,keep_prob
可以设置的大一些,例如0.8。keep_prob
的值是 1,意味着保留所有单元另外,实际应用中,不建议对输入层进行 dropout,如果输入层维度很大,例如图片,那么可以设置 dropout,但
keep_prob
应设置的大一些,例如0.8,0.9。
Dropout在电脑视觉CV领域应用比较广泛,因为输入层维度较大,而且没有足够多的样本数量。值得注意的是dropout 是一种 regularization 技巧,用来防止过拟合的,最好只在需要regularization的时候使用dropout。
八、深度学习中其他减少过拟合的方法
除了L2 regularization和dropout regularization之外,还有其它减少过拟合的方法。
一种方法是增加训练样本数量。但是通常成本较高,难以获得额外的训练样本。但是,我们可以对已有的训练样本进行一些处理来“制造”出更多的样本,称为 data augmentation。例如图片识别问题中,可以对已有的图片进行水平翻转、垂直翻转、任意角度旋转、缩放或扩大等等。如下图所示,这些处理都能“制造”出新的训练样本。虽然这些是基于原有样本的,但是对增大训练样本数量还是有很有帮助的,不需要增加额外成本,却能起到防止过拟合的效果。
在数字识别中,也可以将原有的数字图片进行任意旋转或者扭曲,或者增加一些noise,如下图所示:
还有另外一种防止过拟合的方法:early stopping。一个神经网络模型随着迭代训练次数增加,train set error一般是单调减小的,而dev set error 先减小,之后又增大。也就是说训练次数过多时,模型会对训练样本拟合的越来越好,但是对验证集拟合效果逐渐变差,即发生了过拟合。因此,迭代训练次数不是越多越好,可以通过train set error和dev set error随着迭代次数的变化趋势,选择合适的迭代次数,即early stopping。
然而,Early stopping有其自身缺点。通常来说,机器学习训练模型有两个目标:一是优化cost function,尽量减小J;二是防止过拟合。这两个目标彼此对立的,即减小 J 的同时可能会造成过拟合,反之亦然。我们把这二者之间的关系称为正交化 orthogonalization。
在深度学习中,我们可以同时减小 Bias 和 Variance,构建最佳神经网络模型。但是,Early stopping 的做法通过减少训练次数来防止过拟合,这样 J 就不会足够小。也就是说,early stopping 将上述两个目标融合在一起,同时优化,但可能没有“分而治之”的效果好。
与 early stopping 相比,L2 regularization 可以实现“分而治之”的效果:迭代训练足够多,减小J,而且也能有效防止过拟合。而L2 regularization的缺点之一是最优的正则化参数 λ 的选择比较复杂。对这一点来说,early stopping比较简单。总的来说,L2 regularization更加常用一些。
九、归一化输入 Normalizing inputs
在训练神经网络时,标准化输入可以提高训练的速度。标准化输入就是对训练数据集进行归一化的操作,即将原始数据减去其均值 μ 后,再除以其方差 $\sigma^2$:
以二维平面为例,下图展示了其归一化过程:
值得注意的是,由于训练集进行了标准化处理,那么对于测试集或在实际应用时,应该使用同样的 μ 和 $\sigma^2$ 对其进行标准化处理。这样保证了训练集合测试集的标准化操作一致。
之所以要对输入进行标准化操作,主要是为了让所有输入归一化同样的尺度上,方便进行梯度下降算法时能够更快更准确地找到全局最优解。假如输入特征是二维的,且 x1的范围是 [1,1000],x2 的范围是 [0,1]。如果不进行标准化处理,x1 与 x2 之间分布极不平衡,训练得到的 w1 和 w2 也会在数量级上差别很大。这样导致的结果是 cost function 与 w 和 b 的关系可能是一个非常细长的椭圆形碗。对其进行梯度下降算法时,由于w1和w2数值差异很大,只能选择很小的学习因子 α,来避免 J 发生振荡。一旦 α 较大,必然发生振荡,J 不再单调下降。如下左图所示。
然而,如果进行了标准化操作,x1与x2分布均匀,w1和w2数值差别不大,得到的cost function与w和b的关系是类似圆形碗。对其进行梯度下降算法时,α 可以选择相对大一些,且 J 一般不会发生振荡,保证了 J 是单调下降的。如下右图所示。
如果输入特征之间的范围本来就比较接近,那么不进行标准化操作也是没有太大影响的。但是,标准化处理在大多数场合下还是值得推荐的。
十、梯度消失 / 梯度爆炸 Vanishing / Exploding gradients
在神经网络尤其是深度神经网络中存在可能存在这样一个问题:梯度消失和梯度爆炸。意思是当训练一个层数非常多的神经网络时,计算得到的梯度可能非常小或非常大,甚至是指数级别的减小或增大。这样会让训练过程变得非常困难。
举个例子来说明,假设一个多层的每层只包含两个神经元的深度神经网络模型,如下图所示:
为了简化复杂度,便于分析,我们令各层的激活函数为线性函数,即 g(Z)=Z。且忽略各层常数项 b 的影响,令 b 全部为零。那么,该网络的预测输出$\hat Y$为:
如果各层权重 $W^{[l]}$ 的元素都稍大于 1,例如1.5,则预测输出 $\hat Y$ 将正比于$1.5^L$。L越大,$\hat Y$越大,且呈指数型增长。我们称之为数值爆炸。
相反,如果各层权重$W^{[l]}$ 的元素都稍小于1,例如0.5,则预测输出$\hat Y$将正比于$0.5^L$。网络层数L越多,$\hat Y$呈指数型减小。我们称之为数值消失。
也就是说,如果各层权重$W^{[l]}$都大于 1 或者都小于 1,那么各层激活函数的输出将随着层数 $l$ 的增加,呈指数型增大或减小。当层数很大时,出现数值爆炸或消失。同样,这种情况也会引起梯度呈现同样的指数型增大或减小的变化。L 非常大时,例如 L=150,则梯度会非常大或非常小,引起每次更新的步进长度过大或者过小,这让训练过程十分困难。
十一、神经网络的权重初始化 Weight Initialization
下面介绍如何改善 Vanishing and Exploding gradients 这类问题,方法是对权重 w 进行一些初始化处理。
深度神经网络模型中,以单个神经元为例,该层(l)的输入个数为 n,其输出为(忽略常数项 b,b = 0):
为了让 z 不会过大或者过小,思路是让 w 与 n 有关,且 n 越大,w 应该越小才好。这样能够保证 z 不会过大。
🔸 如果激活函数是 tanh,一般是在初始化 w 时,令其方差为 $\frac1n$。相应的 Python 伪代码为:
# w[l] 即第 l-1 层喂给第 l 层神经元的数量
w[l] = np.random.randn(n[l],n[l-1]) * np.sqrt(1/n[l-1])
🔸 如果激活函数是 ReLU,权重 w 的初始化一般令其方差为 $\frac2n$:
w[l] = np.random.randn(n[l],n[l-1]) * np.sqrt(2/n[l-1])
🔸 除此之外,Yoshua Bengio 提出了另外一种初始化 w 的方法,令其方差为 $\frac{2}{n^{[l-1]}n^{[l]}}$:
w[l] = np.random.randn(n[l],n[l-1]) * np.sqrt(2/n[l-1]*n[l])
至于选择哪种初始化方法因人而异,可以根据不同的激活函数选择不同方法。另外,我们可以对这些初始化方法中设置某些参数,作为超参数,通过验证集进行验证,得到最优参数,来优化神经网络。
十二、梯度的数值逼近 Numerical approximation of gradients
Back Propagation 神经网络有一项重要的测试是梯度检查(gradient checking)。其目的是检查验证反向传播过程中梯度下降算法是否正确。该小节将先介绍如何近似求出梯度值。
十三、梯度检验 Gradient checking
介绍完如何近似求出梯度值后,我们将介绍如何进行梯度检查,来验证训练过程中是否出现bugs。
梯度检查首先要做的是分别将 $W^{[1]},b^{[1]},\cdots,W^{[L]},b^{[L]}$ 这些矩阵构造成一维向量,然后将这些一维向量组合起来构成一个更大的一维向量 θ。这样cost function $J(W^{[1]},b^{[1]},\cdots,W^{[L]},b^{[L]})$ 就可以表示成 $J(\theta)$。
然后将反向传播过程通过梯度下降算法得到的 $dW^{[1]},db^{[1]},\cdots,dW^{[L]},db^{[L]}$ 按照一样的顺序构造成一个一维向量 $d\theta$。$d\theta$ 的维度与 $\theta$一致。
接着利用 $J(\theta)$ 对每个 $\theta_i$ 计算近似梯度,其值与反向传播算法得到的$d\theta_i$ 相比较,检查是否一致。例如,对于第 i 个元素,近似梯度为:
计算完所有 $\theta_i$ 的近似梯度后,可以计算 $d\theta_{approx}$ 与 $d\theta$ 的欧氏(Euclidean)距离来比较二者的相似度。公式如下:
一般来说,如果欧氏距离越小,例如 $10^{-7}$,甚至更小,则表明 $d\theta_{approx}$ 与 $d\theta$ 越接近,即反向梯度计算是正确的,没有bugs。如果欧氏距离较大,例如$10^{-5}$,则表明梯度计算可能出现问题,需要再次检查是否有bugs存在。如果欧氏距离很大,例如$10^{-3}$甚至更大,则表明 $d\theta_{approx}$ 与 $d\theta$ 差别很大,梯度下降计算过程有bugs,需要仔细检查。
十四、梯度检验应用的注意事项
在进行梯度检查的过程中有几点需要注意的地方:
不要在整个训练过程中都进行梯度检查,仅仅作为debug使用。
如果梯度检查出现错误,找到对应出错的梯度,检查其推导是否出现错误。
注意不要忽略正则化项,计算近似梯度的时候要包括进去。
梯度检查时关闭 dropout,检查完毕后再打开 dropout。
随机初始化时运行梯度检查,经过一些训练后再进行梯度检查(不常用)。