第一周:深度学习的实用层面(Practical aspects of Deep Learning)

1.1 训练,验证,测试集(Train / Dev / Test sets)

在配置训练、验证和测试数据集的过程中做出正确决策会在很大程度上帮助创建高效的神经网络。训练神经网络时,需要做出很多决策,例如:

  1. 神经网络分多少层

  2. 每层含有多少个隐藏单元

  3. 学习速率是多少

  4. 各层采用哪些激活函数

循环迭代的过程是这样的:

  1. 先有个想法Idea,先选择初始的参数值,构建神经网络模型结构

  2. 然后通过代码Code的形式,实现这个神经网络;

  3. 通过实验Experiment验证这些参数对应的神经网络的表现性能。

  4. 根据验证结果,对参数进行适当的调整优化,再进行下一次的Idea->Code->Experiment循环。通过很多次的循环,不断调整参数,选定最佳的参数值,从而让神经网络性能最优化

在大数据时代,数据量可能是百万级别,验证集和测试集占数据总量的比例会趋向于变得更小。因为验证集的目的就是验证不同的算法,检验哪种算法更有效,因此,验证集要足够大才能评估,比如2个甚至10个不同算法,并迅速判断出哪种算法更有效。可能不需要拿出20%的数据作为验证集

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

搭建训练验证集和测试集能够加速神经网络的集成,也可以更有效地衡量算法的偏差和方差,从而更高效地选择合适方法来优化算法

1.2 偏差,方差(Bias /Variance)

在一个只有x1x_1x2x_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 机器学习基础(Basic Recipe for Machine Learning)

初始模型训练完成后,首先要知道算法的偏差高不高,如果偏差较高,试着评估训练集或训练数据的性能。如果偏差的确很高,甚至无法拟合训练集,要做的就是增加神经网络的隐藏层个数、神经元个数,训练时间延长,选择其它更复杂的NN模型等

如果网络足够大,通常可以很好的拟合训练集,一旦偏差降低到可以接受的数值,检查一下方差有没有问题,为了评估方差,要查看验证集性能,从一个性能理想的训练集推断出验证集的性能是否也理想,如果方差高,最好的解决办法就是增加训练样本数据,进行正则化Regularization,选择其他更复杂的NN模型

两点需要注意:

第一点,高偏差和高方差是两种不同的情况,通常用训练验证集来诊断算法是否存在偏差或方差问题,然后根据结果选择尝试部分方法。如果算法存在高偏差问题,准备更多训练数据没什么用处

第二点,在当前的深度学习和大数据时代,只要持续训练一个更大的网络,只要正则适度,通常构建一个更大的网络便可以在不影响方差的同时减少偏差,而采用更多数据通常可以在不过多影响偏差的同时减少方差。

这两步实际要做的工作是:使用更复杂的神经网络和海量的训练样本,一般能够同时有效减小Bias和Variance

1.4 正则化(Regularization)

深度学习可能存在过拟合问题——高方差,有两个解决方法,一个是正则化,另一个是准备更多的数据

λ2m\frac{\lambda}{2m}乘以ww范数的平方,欧几里德范数的平方等于wjw_jjj值从1到nxn_x)平方的和,也可表示为wwTww^T,也就是向量参数ww的欧几里德范数(2范数)的平方,此方法称为L2L2正则化。因为这里用了欧几里德法线,被称为向量参数wwL2L2范数。

J(w,b)=1mi=1mL(y^(i),y(i))+λ2mw22J(w,b)=\frac1m\sum_{i=1}^mL(\hat y^{(i)},y^{(i)})+\frac{\lambda}{2m}||w||_2^2
w22=j=1nxwj2=wTw||w||_2^2=\sum_{j=1}^{n_x}w_j^2=w^Tw

为什么不再加上参数bb呢?因为通常ww是一个高维参数矢量,几乎涵盖所有参数,已经可以表达高偏差问题,所以参数很大程度上由ww决定,而bb只是众多参数中的一个,改变bb值对整体模型影响较小,所以通常省略不计,如果加了参数bb,也没太大影响

L2L2正则化是最常见的正则化类型,L1L1正则化是正则项λm\frac{\lambda}{m}乘以j=1nxw\sum_{j=1}^{n^x}|w|j=1nxw\sum_{j=1}^{n^x}|w|也被称为参数向量wwL1L1范数无论分母是,mm还是2m2m,它都是一个比例常量

J(w,b)=1mi=1mL(y^(i),y(i))+λ2mw1J(w,b)=\frac1m\sum_{i=1}^mL(\hat y^{(i)},y^{(i)})+\frac{\lambda}{2m}||w||_1
w1=j=1nxwj||w||_1=\sum_{j=1}^{n_x}|w_j|

如果用的是L1L1正则化,ww最终会是稀疏的,也就是说ww向量中有很多0,虽然L1L1正则化使模型变得稀疏,却没有降低太多存储内存,实际上L1 regularization在解决high variance方面比L2 regularization并不更具优势。而且,L1的在微分求导方面比较复杂

λ\lambda是正则化参数,可以设置λ\lambda为不同的值,在Dev set中进行验证,选择最佳的λ\lambda,通常使用验证集或交叉验证集来配置这个参数

在深度学习模型中,L2 regularization的表达式为:

J(w[1],b[1],,w[L],b[L])=1mi=1mL(y^(i),y(i))+λ2ml=1Lw[l]2J(w^{[1]},b^{[1]},\cdots,w^{[L]},b^{[L]})=\frac1m\sum_{i=1}^mL(\hat y^{(i)},y^{(i)})+\frac{\lambda}{2m}\sum_{l=1}^L||w^{[l]}||^2
w[l]2=i=1n[l]j=1n[l1](wij[l])2||w^{[l]}||^2=\sum_{i=1}^{n^{[l]}}\sum_{j=1}^{n^{[l-1]}}(w_{ij}^{[l]})^2

w[l]2||w^{[l]}||^2称为Frobenius范数,记为w[l]F2||w^{[l]}||_F^2。一个矩阵的Frobenius范数就是计算所有元素平方和再开方,如下所示:

AF=i=1mj=1naij2||A||_F=\sqrt {\sum_{i=1}^m\sum_{j=1}^n|a_{ij}|^2}

由于加入了正则化项,梯度下降算法中的dw[l]dw^{[l]}计算表达式需要做如下修改:

dw[l]=dwbefore[l]+λmw[l]dw^{[l]}=dw^{[l]}_{before}+\frac{\lambda}{m}w^{[l]}
w[l]:=w[l]αdw[l]w^{[l]}:=w^{[l]}-\alpha\cdot dw^{[l]}

L2 regularization也被称做weight decay。这是因为,由于加上了正则项,dw[l]dw^{[l]}有个增量,在更新w[l]w^{[l]}的时候,会多减去这个增量,使得w[l]w^{[l]}比没有正则项的值要小一些。不断迭代更新,不断地减小

w[l]:=w[l]αdw[l]=w[l]α(dwbefore[l]+λmw[l])=(1αλm)w[l]αdwbefore[l]\begin{aligned}w^{[l]} &:=w^{[l]}-\alpha\cdot dw^{[l]}\\ &=w^{[l]}-\alpha\cdot(dw^{[l]}_{before}+\frac{\lambda}{m}w^{[l]})\\ &=(1-\alpha\frac{\lambda}{m})w^{[l]}-\alpha\cdot dw^{[l]}_{before} \end{aligned}

其中,(1αλm)<1(1-\alpha\frac{\lambda}{m})<1

1.5 为什么正则化有利于预防过拟合呢?(Why regularization reduces overfitting?)

如果正则化λ\lambda设置得足够大,权重矩阵WW被设置为接近于0的值,直观理解就是把多隐藏单元的权重设为0,于是基本上消除了这些隐藏单元的许多影响。如果是这种情况,这个被大大简化了的神经网络会变成一个很小的网络,小到如同一个逻辑回归单元,可是深度却很大,它会使这个网络从过度拟合的状态更接近左图的高偏差状态。但是λ\lambda会存在一个中间值,于是会有一个接近“Just Right”的中间状态。

正则化为什么可以预防过拟合:

假设激活函数是tanhtanh函数。tanhtanh函数的特点是在zz接近零的区域,函数近似是线性的,而当z|z|很大的时候,函数非线性且变化缓慢。当使用正则化,λ\lambda较大,即对权重w[l]w^{[l]}的惩罚较大,w[l]w^{[l]}减小。因为z[l]=w[l]a[l]+b[l]z^{[l]}=w^{[l]}a^{[l]}+b^{[l]}。当w[l]w^{[l]}减小的时候,z[l]z^{[l]}也会减小。则此时的z[l]z^{[l]}分布在tanhtanh函数的近似线性区域。那么这个神经元起的作用就相当于是linear regression。如果每个神经元对应的权重w[l]w^{[l]}都比较小,那么整个神经网络模型相当于是多个linear regression的组合,即可看成一个linear network。得到的分类超平面就会比较简单,不会出现过拟合现象

1.6 dropout 正则化(Dropout Regularization)

Dropout是指在深度学习网络的训练过程中,对于每层的神经元,按照一定的概率将其暂时从网络中丢弃。即每次训练时,每一层都有部分神经元不工作,起到简化复杂网络模型的效果,从而避免发生过拟合

Inverted dropout(反向随机失活)

假设对于第ll层神经元,设定保留神经元比例概率keep_prob=0.8,即该层有20%的神经元停止工作。dldl为dropout向量,设置dldl为随机vector,其中80%的元素为1,20%的元素为0。

生成dropout vector:

dl = np.random.rand(al.shape[0],al.shape[1])<keep_prob

ll层经过dropout,随机删减20%的神经元,只保留80%的神经元,其输出为:

al = np.multiply(al,dl)

最后,对alal进行scale up处理,即:

al /= keep_prob

alal进行scale up是为了保证在经过dropout后,alal作为下一层神经元的输入值尽量保持不变,尽可能保持alal的期望值相比之前没有大的变化

Inverted dropout的另外一个好处就是在对该dropout后的神经网络进行测试时能够减少scaling问题。因为在训练时,使用scale up保证alal的期望值没有大的变化,测试时就不需要再对样本数据进行类似的尺度伸缩操作

对于mm个样本,单次迭代训练时,随机删除掉隐藏层一定数量的神经元;然后,在删除后的剩下的神经元上正向和反向更新权重ww和常数项bb;接着,下一次迭代中,再恢复之前删除的神经元,重新随机删除一定数量的神经元,进行正向和反向更新wwbb。不断重复上述过程,直至迭代训练完成

使用dropout训练结束后,在测试和实际应用模型时,不需要进行dropout和随机删减神经元,所有的神经元都在工作

1.7 理解 dropout(Understanding Dropout)

Dropout通过每次迭代训练时,随机选择不同的神经元,相当于每次都在不同的神经网络上进行训练,能够防止过拟合

对于某个神经元来说,某次训练时,它的某些输入在dropout的作用下被过滤了。而在下一次训练时,又有不同的某些输入被过滤。经过多次训练后,某些输入被过滤,某些输入被保留。这样,该神经元就不会受某个输入非常大的影响而被均匀化了。即对应的权重w不会很大。从效果上来说与L2 regularization是类似的,都是对权重w进行“惩罚”,减小了w的值。

对于同一组训练数据,利用不同的神经网络训练之后,求其输出的平均值可以减少overfitting

Dropout就是利用这个原理,每次丢掉一定数量的隐藏层神经元,相当于在不同的神经网络上进行训练,这样就减少了神经元之间的依赖性,即每个神经元不能依赖于某几个其他的神经元(指层与层之间相连接的神经元),使神经网络更加能学习到与其他神经元之间的更加健壮的特征

在使用dropout的时候,有几点需要注意。:

  • 不同隐藏层的dropout系数keep_prob可以不同。

  • 一般来说,神经元越多的隐藏层,keep_out可以设置得小一些.,例如0.5;神经元越少的隐藏层,keep_out可以设置的大一些,例如0.8

  • 实际应用中,不建议对输入层进行dropout,如果输入层维度很大,例如图片,那么可以设置dropout,但keep_out应设置的大一些,例如0.8,0.9。

  • 越容易出现overfitting的隐藏层,其keep_prob就设置的相对小一些

  • 如果担心某些层比其它层更容易发生过拟合,可以把某些层的keep-prob值设置得比其它层更低,缺点是为了使用交叉验证,要搜索更多的超级参数

使用dropout的时候,先将所有层的keep_prob全设置为1,再绘制cost function,即涵盖所有神经元,看J是否单调下降。下一次迭代训练时,再将keep_prob设置为其它值

1.8 其他正则化方法(Other regularization methods)

除了L2 regularization和dropout regularization之外,其它减少过拟合的方法:

  • 增加训练样本数量。但是通常成本较高,难以获得额外的训练样本。但是可以对已有的训练样本进行一些处理来“制造”出更多的样本,称为data augmentation(数据扩增)。例如图片识别问题中,可以对已有的图片进行水平翻转、垂直翻转、任意角度旋转、缩放或扩大等

在数字识别中,也可以将原有的数字图片进行任意旋转或者扭曲,或者增加一些noise,如下图所示:

  • early stopping。一个神经网络模型随着迭代训练次数增加,train set error一般是单调减小的,而dev set error 先减小,之后又增大。即训练次数过多时,模型会对训练样本拟合的越来越好,但是对验证集拟合效果逐渐变差,发生了过拟合。可以通过train set error和dev set error随着迭代次数的变化趋势,选择合适的迭代次数,即early stopping。

当还未在神经网络上运行太多迭代过程的时候,参数ww接近0,因为随机初始化ww值时,它的值可能都是较小的随机值,但在迭代过程和训练过程中ww的值会变得越来越大,所以early stopping要做就是在中间点停止迭代过程

机器学习训练模型有两个目标:一是优化cost function,尽量减小J;二是防止过拟合。这两个目标彼此对立的,即减小J的同时可能会造成过拟合,反之亦然。二者之间的关系称为正交化orthogonalization

  • Early stopping的做法通过减少迭代训练次数来防止过拟合,这样J就不会足够小。即early stopping将上述两个目标融合在一起,同时优化,但可能没有“分而治之”的效果好。

  • 与early stopping相比,L2 regularization可以实现“分而治之”的效果:迭代训练足够多,减小J,而且也能有效防止过拟合。而L2 regularization的缺点之一是最优的正则化参数λ\lambda的选择比较复杂。而early stopping比较简单。

  • 总的来说,L2 regularization更加常用一些

1.9 归一化输入(Normalizing inputs)

在训练神经网络时,标准化输入可以提高训练的速度

μ=1mi=1mX(i)\mu=\frac1m\sum_{i=1}^mX^{(i)}
σ2=1mi=1m(X(i))2\sigma^2=\frac1m\sum_{i=1}^m(X^{(i)})^2
X:=Xμσ2X:=\frac{X-\mu}{\sigma^2}

由于训练集进行了标准化处理,测试集或在实际应用时,应该使用同样的μ\muσ2\sigma^2对其进行标准化处理。保证训练集和测试集的标准化操作一致

对输入进行标准化操作,是为了让所有输入归一化在同样的尺度上,方便进行梯度下降算法时能够更快更准确地找到全局最优解。假如输入特征是二维的,且x1x_1的范围是[1,1000],x2x_2的范围是[0,1]。如果不进行标准化处理,x1x_1x2x_2之间分布极不平衡,训练得到的w1w_1w2w_2也会在数量级上差别很大。这样导致的结果是costfunction与wwbb的关系可能是一个非常细长的椭圆形碗。对其进行梯度下降算法时,由于w1w_1w2w_2数值差异很大,只能选择很小的学习因子α\alpha,来避免J发生振荡。一旦α\alpha较大,必然发生振荡,JJ不再单调下降。如果进行了标准化操作,x1x_1x2x_2分布均匀,w1w_1w2w_2数值差别不大,得到的cost function与wwbb的关系是类似圆形碗。对其进行梯度下降算法时,α\alpha可以选择相对大一些,且JJ一般不会发生振荡,保证了JJ是单调下降

如果输入特征之间的范围比较接近,不进行标准化操作没有太大影响

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

当训练一个层数非常多的神经网络时,计算得到的梯度可能非常小或非常大,甚至是指数级别的减小或增大

Y^=W[L]W[L1]W[L2]W[3]W[2]W[1]X\hat Y=W^{[L]}W^{[L-1]}W^{[L-2]}\cdots W^{[3]}W^{[2]}W^{[1]}X

如果各层权重W[l]W[l]的元素都稍大于1,例如1.5,则预测输出Y^\hat Y将正比于1.5L1.5^L。L越大,Y^\hat Y越大,且呈指数型增长。称之为梯度爆炸

如果各层权重W[l]W[l]的元素都稍小于1,例如0.5,则预测输出Y^\hat Y将正比于0.5L0.5^L。网络层数L越多,Y^\hat Y呈指数型减小。称之为梯度消失

1.11 神经网络的权重初始化(Weight Initialization for Deep Networks)

深度神经网络模型中,以单个神经元为例,该层(ll)的输入个数为nn,其输出为:

z=w1x1+w2x2++wnxnz=w_1x_1+w_2x_2+\cdots+w_nx_n
a=g(z)a=g(z)

忽略了常数项b

为了让zz不会过大或者过小,ww应该越小才好。方法是在初始化ww时,令其方差为1n\frac{1}{n}

激活函数是tanhtanh相应的python伪代码为:

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

如果激活函数是ReLUReLU,权重ww的初始化一般令其方差为2n\frac{2}{n}

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

另外一种初始化ww的方法,令其方差为2n[l1]+n[l]\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]))

1.12 梯度的数值逼近(Numerical approximation of gradients)

Back Propagation神经网络有一项重要的测试是梯度检验(gradient checking)。其目的是检查验证反向传播过程中梯度下降算法是否正确。

对于一个非零的ε\varepsilon,它的逼近误差可以写成O(ε2)O(\varepsilon^2)ε\varepsilon值非常小,大写符号OO的含义是指逼近误差

函数ff在点θ\theta处的梯度可以表示成:

g(θ)=f(θ+ε)f(θε)2εg(\theta)=\frac{f(\theta+\varepsilon)-f(\theta-\varepsilon)}{2\varepsilon}

ε>0\varepsilon>0,且足够小

1.13 梯度检验(Gradient checking)

  • 梯度检查要做的是将W[1],b[1],,W[L],b[L]W^{[1]},b^{[1]},\cdots,W^{[L]},b^{[L]}这些矩阵构造成一维向量,然后将这些一维向量组合起来构成一个更大的一维向量θ\theta。这样的cost functionJ(W[1],b[1],,W[L],b[L])J(W^{[1]},b^{[1]},\cdots,W^{[L]},b^{[L]})可以表示成J(θ)J(\theta)

  • 然后将反向传播过程通过梯度下降算法得到的$dW^{[1]},db^{[1]},\cdots,dW^{[L]},db^{[L]}$按照一样的顺序构造成一个一维向量dθd\thetadθd\theta的维度与θ\theta一致

  • 接着利用J(θ)J(\theta)对每个θi\theta_i算近似梯度,其值与反向传播算法得到的dθid\theta_i相比较,检查是否一致。例如,对于第ii个元素,近似梯度为:

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的近似梯度后,可以计算dθapproxd\theta_{approx}dθd\theta的欧氏(Euclidean)距离来比较二者的相似度。公式如下:

dθapproxdθ2dθapprox2+dθ2\frac{||d\theta_{approx}-d\theta||_2}{||d\theta_{approx}||_2+||d\theta||_2}
  • 如果欧氏距离越小,例如10710^{-7},甚至更小,则表明dθapproxd\theta_{approx}dθd\theta越接近,即反向梯度计算是正确的,没有bugs。如果欧氏距离较大,例如10510^{-5},则表明梯度计算可能出现问题,需要再次检查是否有bugs存在。如果欧氏距离很大,例如10310^{-3},甚至更大,则表明dθapproxd\theta_{approx}dθd\theta差别很大,梯度下降计算过程有bugs,需要仔细检查

1.14 梯度检验应用的注意事项(Gradient Checking Implementation Notes)

在进行梯度检验的过程中有几点需要注意的地方:

  • 不要在训练中使用梯度检验而仅仅在调试时使用。计算所有ii值的dθapprox[i]d\theta_{approx}[i]是一个非常漫长的计算过程,为了实施梯度下降,必须使用WWbb backprop来计算dθd\theta并使用backprop来计算导数,所以只有调试的时候才会计算它,来确认数值是否接近dθd\theta。完成后要关闭梯度检验。别在每一次进行梯度下降迭代的时候都运行梯度检验,因为太慢了

  • 如果dθapproxd\theta_{approx}dθd\theta差距很大, 应检查不同的ii值,看看哪些dθapproxd\theta_{approx}的值与dθd\theta的值差距最大

  • 进行梯度检验时,如果使用了正则化,注意不要忽略正则化项,计算近似梯度的时候要包括进去

  • 梯度检验不能与随机失活(dropout) 一起使用,因为在每一次的迭代中,随机失活(dropout)将随机消除隐藏层单元的不同子集,在使用随机失活(dropout) 进行梯度下降的过程中并不存在一个容易计算的代价函数JJ,随机失活(dropout)可以被视为对代价函数的优化, 但是这个代价函数的定义是在每一次迭代中对所有非常大的可消除节点集进行求和,所以这个代价函数是很难计算的,只需要对代价函数进行抽样,在那些使用随机失活(dropout)的集合中每次消除不同的随机集合,所以使用梯度检验来检查包含了随机失活(dropout)的运算是很困难的,可以把keep-prob和dropout设为1.0,然后打开dropout。梯度检查时关闭dropout,检查完毕后再打开dropout。关掉随机失活(dropout) 使用梯度检验来检查算法,在没有dropout的情况下至少是正确的 然后再打开dropout

  • 在随机初始化的时候运行梯度检验,然后训练网络一段时间,wwbb 将会在0附近摇摆一段时间,即很小的随机初始值,在进行几次训练的迭代后再运行梯度检验。(不常用)

Last updated