4-深度学习使用层面

深度学习使用层面

训练/开发/测试数据集

创建高质量的训练数据集、验证集和测试集有助于更好地训练神经网络。
一般来说,会将所有的训练数据划分为以下三个部分:

  • 训练集(training set):用来对算法进行训练
  • 验证集(development,也可称为开发集):用训练好的多个模型对验证集进行验证,来选择最好的模型(参数组合)
  • 测试集(test set):选定了最终模型后,使用测试集进行评估,这是为了无偏评估算法的运行状况。

在机器学习发展的小数据量时代,常见做法是将数据三七粉,也就是70%验证集,30%测试集。如果没有明确设置验证集,也可以按照60%训练,20%验证和20%测试集来划分,这是前几年机器学习领域普遍认可的最好实践方法。

如果有100条,1000条或者1万条数据,上述比例的划分是很合理的。


但是在大数据时代,数据量可能是百万甚至更好级别,那么验证集和测试集占数据总量的比例会趋向于变得更小。

因为验证集的目的就是验证不同的算法,检验哪种算法更有效,

测试机的主要目的是正确评估分类器的性能。

因此如果有百万量级的数据,比例可以是:训练集98%,验证集和测试集各1%。

对于数据量过百万的应用,训练集可以占到99.5%,验证和测试集各占0.25%。或者验证集0.4%,测试集0.1%。


总结一下,在其及学习中,通常将数据样本分成训练集、验证集和测试集三部分。数据集规模相对较小的话,使用传统的划分策略;数据集规模较大的,验证集和测试集要小于数据总量的20%或10%。

现代深度学习的另一个趋势是很多时候在训练和测试集分布不匹配的情况下进行训练。

例如训练一个判断是猫图片的算法。训练集可能是分辨率很高,比较专业的图片,而测试集是像素较低,模糊的图片。

针对这种情况,根据经验,要尽量确保验证集和测试集的数据来自同一分布。


最后一点,如果没有测试集也不要紧,测试集的目的是对最终选定的神经网络作出无偏估计,如果不需要无偏估计,也可以不设置测试集,只设置验证集。

偏差,方差(Bias/Variance)

深度学习中,偏差指的是预测值和真实值之间的误差(可以看成算法的拟合能力),方差可以理解为训练数据集精度和测试数据集精度的差异,或在不同数据集上的是预测值和所有数据集上的平均预测值之间的关系。(可以想象成算法的稳定性)。

低偏差低方差是我们期待的结果。

1234
训练集误差10%10%1%1%
测试集误差20%10.5%20%1.5%
结果高偏差高方差高偏差低方差低偏差高方差低偏差低方差

通常来说,简单的模型偏差较大,方差较小。

复杂的模型偏差较小,方差较大。

高偏差代表欠拟合,即模型无法很好的拟合训练样本。

而高方差代表过拟合,即模型对于不同的训练数据集预测结果差异很大。

image-20220911110509868

这是一张常见的靶心图,红色靶心为实际值,蓝色点集为预测值。在模型的不断训练迭代过程中,能碰到四种情况:

  • 低偏差,低方差:这是训练的理想模型,蓝色点集基本落在靶心内,且数据的离散程度小;
  • 低偏差,高方差:这是深度学习面临的最大问题,过拟合了,也就是模型太贴合训练数据,导致泛华能力差,如果使用其他数据集进行测试,则准确度会下降的很厉害;
  • 高偏差,低方差:这往往是训练的初始阶段,欠拟合;
  • 高偏差,高方差:这是训练最糟糕的情况,准确度差,数据的离散程度也差。

一般来说,在初始模型训练完成后,首先要看算法的偏差高不高,如果偏差较高,就试试评估训练集的性能,如果偏差的确很高,那么就要选择一个新的网络,例如含有更多隐藏层或者隐藏单元的网络,或者尝试更好的优化算法。

不断尝试这些方法,直到解决偏差问题,也就是至少使模型可以正确的拟合训练集。

一旦偏差降到一个可以接受的数值,那么就检查方差有没有问题,为了评估方差,需要查看验证集性能,如果方差高,最好的解决办法就是采用更多数据。但是很多时候,无法获得更多数据,那么也可以使用正则化来减少过拟合。

正则化(Regularization)

机器学习中有一个奥克姆剃刀原则:有两个解释,那么最可能正确的解释是更简单的哪一个。

这个原则也适用于神经网络的模型:简单的模型比复杂模型泛华能力好。


正则化,就是在成本函数中加入一个正则化项(惩罚项),惩罚模型的复杂度,防止神经网络过拟合。

逻辑回归的L1和L2正则化

在逻辑回归的损失函数中增加L2正则化:

J(w,b)=1mi=1mL(y^,y)+λ2mw22J(w,b) = \frac{1}{m}\sum_{i=1}^{m}L(\widehat{y}, y) + \frac{\lambda}{2m}||w||^{2}_{2}

参数ww是一个多维度参数向量,bb是一个实数。

后面加上的式子称为L2范数。

其中L2范数表达式为:

λ2mw22=λ2mj=1nxwj2=λ2mwTw\frac{\lambda}{2m}||w||^{2}_{2} = \frac{\lambda}{2m}\sum_{j=1}^{n_x}w^{2}_{j} = \frac{\lambda}{2m}w^Tw

w22w^{2}_{2},上面2表示平方,下面的2表示L2。

wTww^Tw就是参数ww的平方和结果,也可以理解为向量ww的各分量平方和,是一个实数。

λ\lambda称为正则化参数,是一个超参数。2m2m则是为了求导后计算方便。

此方法称为L2正则化。

为什么只正则化参数ww,而不加上参数bb。可以这么做,但是习惯省略不写,因为ww是一个高维参数矢量,已经可以表达高方差问题。而bb是单个数字,没有太大影响。


也有L1正则化,表达式为:

J(w,b)=1mi=1mL(y^,y)+λ2mw1J(w,b) = \frac{1}{m}\sum_{i=1}^{m}L(\widehat{y}, y) + \frac{\lambda}{2m}||w||_{1}

L1范数表达式为:

λ2mw1=λ2mj=1nxwj\frac{\lambda}{2m}||w||_{1} = \frac{\lambda}{2m}\sum_{j=1}^{n_x}|w_j|

也就是向量ww各分量之和。

由于L1正则化得到的ww向量中将存在大量的0,使WW变得稀疏,因此L2正则化更常用。


在Python中,lambda是一个保留字,因此使用lambd表示λ\lambda参数。

上述就是在逻辑回归中实现L2正则化的过程,那么如何在神经网络中实现L2正则化呢。

神经网络中成本函数包括所有参数,从W[1],b[1]W^{[1]},b^{[1]}一直到W[L],b[L]W^{[L]}, b^{[L]}。字母L是神经网络层数。

正则项为:

λ2m1LW[l]2\frac{\lambda}{2m}\sum_{1}^{L}|W^{[l]}|^{2}

W[l]2|W^{[l]}|^{2}为范数平方,这个矩阵范数W[l]2|W^{[l]}|^{2}被定义为矩阵中所有元素的平方再求和。

来看一下求和公式具体参数,W[l]W^{[l]}的维度是(n[l],n[l1])(n^{[l]}, n^{[l-1]})

W[l]2=i=1n[l1]j=1nl(wij[l])2|W^{[l]}|^{2} = \sum_{i=1}^{n^{[l-1]}}\sum_{j=1}^{n^{l}}(w^{[l]}_{ij})^{2}

可以理解为先对$$w^{[l]}$$的每个列向量求平方和,变成一个维度为(1,n[l1])(1, n^{[l-1]})的行向量,再对行向量求和。

该矩阵范数被称为弗罗贝尼乌斯范数,用下标FF标注,也称为矩阵L2范数

使用该范数实现梯度下降

在之前的反向传播中,会计算出dWdW的值,然后使用公式:

W[l]=W[l]αdW[l]W^{[l]} = W^{[l]} - \alpha dW^{[l]}

更新参数。

那么要做的就是在上面这个公式中加上正则化项。

W[l]=W[l]α(dW[l]+λmW[l])=(1αλm)W[l]αdW[l]\begin{aligned} W^{[l]} & = W^{[l]} - \alpha(dW^{[l]} + \frac{\lambda}{m}W^{[l]}) \\ & = (1-\alpha\frac{\lambda}{m})W^{[l]} - \alpha dW^{[l]} \end{aligned}

可以看到,相当于给矩阵W[l]W^{[l]}乘以(1αλm)(1-\alpha\frac{\lambda}{m})倍的权重,该系数(1αλm)(1-\alpha\frac{\lambda}{m})小于1,因此L2范数正则化也被称为权重衰减

正则化为什么能防止过拟合

image-20220911121133326

上面三幅图中,左图是高偏差,欠拟合;右图是高方差,过拟合。

中间图正好。

现在想想一个过拟合的深度神经网络,在添加正则化项弗罗贝尼乌斯范数后,可以避免权重矩阵过大。

那么为什么弗罗贝尼乌斯范数可以减小过拟合。

直观上理解就是,如果正则化参数λ\lambda设置的足够大,那么权重矩阵WW会被设置为接近0的值,也就是把多隐藏单元的权重设为0,于是基本上消除了这些隐藏单元的影响,如果是这种情况,那么这个被大大简化的神经网络会变成一个很小的网络,如同一个逻辑回归单元,它会使这个网络,从过度拟合的右图变成欠拟合的左图。

但是λ\lambda会存在一个中间值,使模型接近Just Right的状态。


再来直观感受一下,正则化为什么可以较小过拟合,建设使用的激活函数是tanh

神经网络基础-反向传播-激活函数| PLM's Notes | 好好学习,天天笔记

g(z)g(z)表示tanh(z)tanh(z),那么可以发现,tanh函数中间有一段接近线性状态的图像。

如果正则化参数λ\lambda很大,那么代价函数的参数会变大,导致参数WW很小,从而导致ZZ变小(接近0),从而进入线性的图像区域。

之前说过,如果每层都是线性的,那么整个网络就是一个线性网络。因此进入线性的图像区域,会简化整个网络,从而减小过拟合。

Dropout正则化

除了L2正则化,还有一个很实用的正则化方法-Dropout(随机失活)

即:随机地对神经网络中的每一层进行丢弃部分神经元操作。

image-20220911123126736

假设在训练上图这样的神经网络,它存在过拟合。

那么dropout会遍历网络中的每一层,并设置消除神经网络中节点的概率,假设网络中的每一层,每个节点,都以抛硬币的方式设置概率,例如每个节点保留和消除的概率都是0.5,那么会消除一些节点,从而得到一个节点更少,规模更小的网络。

image-20220911123323034

对于每个训练样本,都采用一个精简后的神经网络来训练。


如何实现dropout呢,有一种最常用的方法:inverted dropout(反向随机失活)

使用一个三层的神经网络举例说明,只具体说明如何在某一层中实施dropout

对于网络中的每一层设置保留概率keep_prob,假设keep_prob为0.8,也就是说在该层中所有神经元有20%的概率失效,也就是权重设为0。

首先定义向量dd,d[3]d^{[3]}表示三层的dropout向量。

d3 = np.random.rand(a3.shape[0], a3.shape[1]) < keep_prob

np.random.rand会随机生成[0, 1)的一个数,接受两个参数,表示矩阵的维度。

使用keep_prob与矩阵中每个元素比较,小于keep_prob则设置True,即保留对应位置的神经元,否则为False,丢弃对应位置的神经元。d3是一个布尔矩阵,但是python在运算时,会把它转换成0和1.

然后从第三层中获取激活函数,叫他a[3]a^{[3]}a[3]=a[3]d[3]a^{[3]} = a^{[3]}*d^{[3]},这里是点乘。即a3=np.multiply(a3,d3)

它的作用就是让d[3]d^{[3]}中的0元素将a[3]a^{[3]}中对应位置的值归零。

然后向外拓展a[3]a^{[3]},用它除以keep_prob参数。

a3 /= keep_prob

这是为了不影响期望值。


假设第三隐藏层中有50个单元,保留它们的概率为80%,这意味着最后被归零的单元平均有50x20%=10个。再看下z[4]z^{[4]}z[4]=w[4]a[3]+b[4]z^{[4]} = w^{[4]}a^{[3]} + b^{[4]},也就是说a[3]a^{[3]}中有20%的元素被归零,为了不影响z[4]z^{[4]}的期望值,需要用w[4]a[3]/0.8w^{[4]}a^{[3]}/0.8,这样就会修正所需的那20%,a[3]a^{[3]}的期望值不会变。


不同的训练样本,清除的隐藏单元也不同。

但是在测试阶段,不使用dropout。因为在测试阶段,不希望输出结果是随机的,如果在测试阶段使用dropout,预测会受到干扰。


如果设置keep_drop=1,代表保留该层的所有单元。

理解dropout

Dropout为什么可以发挥作用呢?

直观上理解,Dropout使神经网络不会依赖于任何一个特征,因为该单元的输入很可能随时被清除。

因此该单元通过这种方式传播下去,通过传播所有权重,dropout会产生收缩权重的平房范数效果。

  • 对于不同的层,设置的keep_prob大小也不同,神经元较少的层,会设为1.0,而神经元较多的层会设置较小的keep_prob
  • dropout正则化方法通常被使用在计算机视觉领域,图像拥有很多特征,容易过拟合。

dropout的一个缺点是,代价函数无法被明确定义,因为神经元会被随机删除。

一般来说,在使用dropout时,先讲keep_prob全部设为1,也就是先不使用dropout方法,确定神经网络没有问题,然后再打开dropout方法。

其他正则化方法

数据扩增

假设正在拟合猫的图片分类器,如果想通过扩增训练数据来解决过拟合,但是扩增数据代价高,并且有时候无法扩增数据。

那么可以通过,添加这类图片来增加训练集,例如:水平翻转图片,并添加到原数据集,或截取图片的某一部分,并放大,添加到原图片集。这虽然不如额外收集一组新图片那么好,但是节省了获取更多图片的花销。

image-20220911135633304

或者训练一个手写数字识别算法,可以将数据集的图片扭曲,旋转来扩增数据。

image-20220911135736127

early stopping

通常在不断训练的过程中,损失越来越小,但是到了一定程度以后,模型学到的过于复杂,造成在测试集上,损失开始较小,很来又变大,模型的ww参数会越来越大,那么可以在测试集上的损失减小到一定程度之后停止训练。

image-20220911135956847

机器学习过程包括几个步骤,其中一步是选择算法来优化代价函数J,有很多工具可以解决,例如梯度下降,后面还会介绍Momentum等。

但是优化代价函数后,有可能会出现过拟合的情况,那么再使用例如正则化、扩增数据来解决。

这是两个问题。

因此early stopping的主要缺点就是无法独立地处理这两个问题。因为提前停止梯度下降,也就停止了优化代价函数。

所以early stopping是使用一种方法同时解决两个问题,而不是使用不同方法分别解决。

这会使要考虑的问题变得复杂。

如果不使用early stopping,另一种方法就是L2正则化,那么训练神经网络的时间可能就会很长,因为必须要尝试很多正则化参数λ\lambda的值。

early stopping的优点是,只运行一次梯度下降,就可以找到ww的较小值,中间值和较大值。

归一化输入(Normalizing inputs)

训练神经网络,其中一个加速训练的方法就是 归一化输入。

例如一个训练集有两个特征,即输入特征是2维的,那么归一化有两个步骤:

  1. 零均值;
  2. 归一化方差。

第一步是零均值化。

μ=1mi=1mx(i)\mu = \frac{1}{m}\sum_{i=1}^{m}x^{(i)}

这是一个向量,xx等于每个训练数据减去μ\mu,意思是移动训练集,完成零均值化。

x=xμx = x - \mu

image-20220911141242158

上图是原始数据集,零均值化后,会变成:

image-20220911141312056

第二步是归一化方差,注意上图特征x1x_1的方差要比x2x_2的方差大得多,

σ2=1mi=1m(x(i))2\sigma^2 = \frac{1}{m}\sum_{i=1}^{m}(x^{(i)})^2

σ2\sigma^2是一个向量,它的每个特征都有方差。(也就是求每个训练样本的同一个特征平方和),最后将不同特征平方和写成向量σ2\sigma^2

然后数据除以σ2\sigma^2

x=x/σ2x = x / \sigma^2

数据集会变成:

image-20220911142306142

注意:如果归一化训练数据,那么也要使用相同的μ\muσ2\sigma^2来归一化测试集。

为什么归一化输入

如果使用非归一化的输入特征,特征x1x_1取值是[1, 1000],而特征x2x_2取值是[0,1],那么参数w1w_1w2w_2的比率会大。

代价函数可能会像这样:

image-20220911142508977

这是一个细长狭窄的代价函数,最小值在中心位置。

那么优化过程会很慢,迭代次数很多。

而如果归一化特征,使代价函数看起来更圆,更对称,那么使用梯度下降能够更快地找到最小值。

image-20220911142655165

因此归一化的目的就是使不同的特征取值都处于相似的范围内,这样就可以更快地找到最小值,帮助学习算法运行的更快。

梯度消失/梯度爆炸(Vanishing/Exploding gradients)

训练神经网络时,尤其是深层神经网络会面临的一个问题是梯度消失或者梯度爆炸。

下面来说明梯度消失和梯度爆炸的含义。

假设在训练一个极深的神经网络,每层有两个神经元,即层数L很大。

这个神经网络会有参数W[1],W[2],...W[L]W^{[1]}, W^{[2]}, ...W^{[L]},为了简化问题,假设b[l]=0b^{[l]}=0,激活函数g(z)=zg(z) = z

那么这样的话,输出y^\widehat{y}有:

y^=W[L]W[L1]W[2]W[1]x\widehat{y} = W^{[L]}W^{[L-1]}\cdots W^{[2]}W^{[1]}x

因为b[1]=0,g(z)=zb^{[1]}=0,g(z) = z, 因此a[1]=z[1]=W[1]xa^{[1]} =z^{[1]} =W^{[1]}x

同理,W[2]W[1]x=a[2]W^{[2]}W^{[1]}x = a^{[2]},以此类推,可得到上面输出yy表达式。

然后假设每个权重矩阵:

W[l]=[1.5001.5]W^{[l]} = \begin{bmatrix} 1.5 & 0 \\ 0 & 1.5 \end{bmatrix}

当然,最后一项W[L]W^{[L]}可能有不同的维度,那么就把其他的权重矩阵全看做上面的矩阵值。

则:

y=W[L][1.5001.5](L1)xy = W^{[L]} { \begin{bmatrix} 1.5 & 0 \\ 0 & 1.5 \end{bmatrix} }^{(L-1)}x

它是1.5倍的单位矩阵,最后的结果y^\widehat{y}就是1.5L1x{1.5}^{L-1}x

如果对于一个深层神经网络,L值很大,那么y^\widehat{y}的值是呈指数级增长的。

相反,如果权重矩阵:

W[l]=[0.5000.5]W^{[l]} = \begin{bmatrix} 0.5 & 0 \\ 0 & 0.5 \end{bmatrix}

那么同理,y^\widehat{y}会呈指数级递减。

所以,如果权重只比1略大一点,那么深度神经网络的激活函数将指数级增长;如果比1略小一点,那么激活函数将指数级递减。

这同样适用于与层数L相关的导数或梯度函数。

如果出现了梯度消失或梯度爆炸,这将导致训练难度增加,梯度下降算法将花费很长时间学习。

神经网络的权重初始化

针对梯度消失和梯度爆炸问题,有一个不完整的解决方案,虽然不能彻底解决问题,但是可以减轻。

为了更好理解,首先举一个神经元的例子,然后再演变到整个深度网络。

image-20220911150035733

单个神经元有4个输入特征,经过a=g(z)a=g(z)处理,得到y^\widehat{y}

z=w1x1+w2x2++wnxnz = w_1x_1+w_2x_2 + \cdots + w_nx_n,暂时忽略bb,设为b=0b=0。如果预防zz值过大或过小,那么wiw_i的值就要小,那么最合理的方法就是设置wi=1nw_i = \frac{1}{n}。n表示神经元的输入特征数量。

因此推广到深度神经网络,设置某层的权重矩阵为:

w[l]=np.randon.randn(n[l],n[n1])np.sqrt(1n[l1])w^{[l]} = np.randon.randn(n^{[l]}, n^{[n-1]}) * np.sqrt(\frac{1}{n^{[l-1]}})

n[l1]n^{[l-1]}就是前一层的神经元个数。

这种方法被称为Xavier初始化

这种方法对于激活函数sigmoidtanh效果很好。

但是对于Relu激活函数,效果并不好。

如果使用Relu,那么可将方差1n\frac{1}{n}改成2n\frac{2}{n}


实际上,上面的方差参数是一个需要调整的超参数。

但是通常调优该参数的效果一般,因此会把这个超参数的优先级放得比较低。

梯度检验(Gradient checking)

梯度检验可以帮助发现反向传播中是否有问题。

双边误差和单边误差

在微积分中,导数的概念是:

f(θ)=limε0f(θ+ε)f(θε)2εf(\theta)^{'} = \lim_{\varepsilon \to 0} \frac{f(\theta + \varepsilon) - f(\theta - \varepsilon)}{2\varepsilon}

也可以是:

f(θ)=limε0f(θ+ε)f(θ)εf(\theta)^{'} = \lim_{\varepsilon \to 0}\frac{f(\theta + \varepsilon) - f(\theta)}{\varepsilon}

对应的,分别是双边误差和单边误差。

双边误差比单边误差更准确。

因此在梯度检验中,使用双边误差。


假设网络中有下列参数W[1],b[1],,W[l],b[l]W^{[1]}, b^{[1]}, \cdots, W^{[l]}, b^{[l]}

那么要执行梯度检验,首先要做的就是把所有参数转换成一个巨型向量。该向量表示为参数θ\theta。那么现在,得到了一个关于θ\theta的代价函数J(θ)J(\theta)。同样的,把dW[1],db[1],,dW[l],db[l]dW^{[1]}, db^{[1]},\cdots,dW^{[l]}, db^{[l]}也转换成一个新的向量dθd\theta,与θ\theta具有相同的维度。

θ\theta是一个超参数。


首先要明确,代价函数J(θ)J(\theta)是关于θ\theta的一个函数,也可以将θ\theta展开为J(θ1,θ2,)J(\theta_1,\theta_2,\cdots)。无论超级参数向量θ\theta的维度是多少。

要执行梯度检验,就是循环执行,使用双边误差检验方法检验每一个参数。

使用dθapprox[i]d\theta_{approx}[i]表示双边误差方法计算的估计值。

dθapprox[i]=J(θ1,θ2,,θi+ε,)J(θ1,θ2,,θiε,)2εd\theta_{approx}[i] = \frac{J(\theta_1,\theta_2,\cdots, \theta_i + \varepsilon, \cdots) - J(\theta_1,\theta_2,\cdots, \theta_i - \varepsilon, \cdots)}{2\varepsilon}

即 只对θi\theta_i增加ε\varepsilon,其余项保持不变。循环对θ\theta的每一个分量执行上述公式,得到dθapproxd\theta_{approx}

接下来,就是验证dθd\thetadθapproxd\theta_{approx}是否彼此接近。

那么,如何定义两个向量彼此接近呢?

计算这两个向量的距离,即dθapprox[i]dθ[i]d\theta_{approx}[i] - d\theta[i]的欧几里得范数。

欧几里得范数(L2范数)定义为:一个向量各个元素的平方和,再开平方。

例如向量a=[a1,a2,,an]a = [a_1, a_2, \cdots, a_n],则欧几里得范数:$||a|| = \sqrt


4-深度学习使用层面
https://zhaoquaner.github.io/2022/09/19/DeepLearning/CourseNote/4-深度学习实用层面/
更新于
2022年9月19日
许可协议