深度学习(3):优化神经网络(1)

想提高一个深层神经网络的训练效率,需从各个方面入手,优化整个运算过程,同时预防其中可能发生的各种问题。

本文涉及优化深层神经网络中的数据划分,模型估计,预防过拟合,数据集标准化,权重初始化,梯度检验等内容。

数据划分

想要建立一个神经网络模型,首先,就是要设置好整个数据集中的训练集(Training Sets)开发集(Development Sets)测试集(Test Sets)

采用训练集进行训练时,通过改变几个超参数的值,将会得到几种不同的模型。开发集又称为交叉验证集(Hold-out Cross Validation Sets),它用来找出建立的几个不同模型中表现最好的模型。之后将这个模型运用到测试集上进行测试,对算法的好坏程度做无偏估计。通常,会直接省去最后测试集,将开发集当做“测试集”。一个需要注意的问题是,需要保证训练集和测试集的来源一致,否则会导致最后的结果存在较大的偏差。

模型估计

分类问题
如图中的左图,对图中的数据采用一个简单的模型,例如线性拟合,并不能很好地对这些数据进行分类,分类后存在较大的偏差(Bias),称这个分类模型欠拟合(Underfitting)。右图中,采用复杂的模型进行分类,例如深度神经网络模型,当模型复杂度过高时变容易出现过拟合(Overfitting),使得分类后存在较大的方差(Variance)。中间的图中,采用一个恰当的模型,才能对数据做出一个差不多的分类。

偏差和方差

通常采用开发集来诊断模型中是否存在偏差或者时方差:

  • 当训练出一个模型后,发现训练集的错误率较小,而开发集的错误率却较大,这个模型可能出现了过拟合,存在较大方差;
  • 发现训练集和开发集的错误率都都较大时,且两者相当,这个模型就可能出现了欠拟合,存在较大偏差;
  • 发现训练集的错误率较大时,且开发集的错误率远较训练集大时,这个模型就有些糟糕了,方差和偏差都较大。
  • 只有当训练集和开发集的错误率都较小,且两者的相差也较小,这个模型才会是一个好的模型,方差和偏差都较小。

模型存在较大偏差时,可采用增加神经网络的隐含层数、隐含层中的节点数,训练更长时间等方法来预防欠拟合。而存在较大方差时,则可以采用引入更多训练样本、对样本数据正则化(Regularization)等方法来预防过拟合。

预防过拟合

L2正则化

向Logistic回归的成本函数中加入L2正则化(也称“L2范数”)项:$${J(w,b) = \frac{1}{m} \sum_{i=1}^m \mathcal{L}({\hat y}^{(i)}, y^{(i)}) + \frac{\lambda}{2m}||w||_2^2}$$
其中:$$||w||_2^2 = \sum_{j=1}^n{w_j^2} = w^T w$$
L2正则化是最常用的正则化类型,也存在L1正则化项:$$\frac{\lambda}{m}||w||_1 = \frac{\lambda}{m}\sum_{j=1}^n |w_j|$$
由于L1正则化最后得到w向量中将存在大量的0,使模型变得稀疏化,所以一般都使用L2正则化。其中的参数$\lambda$称为正则化参数,这个参数通常通过开发集来设置。
向神经网络的成本函数加入正则化项:
$${J(w^{[1]},b^{[1]},…,w^{[L]},b^{[L]}) = \frac{1}{m} \sum_{i=1}^m \mathcal{L}({\hat y}^{(i)}, y^{(i)}) + \frac{\lambda}{2m} \sum\limits_{l=1}^L ||w^{[l]}}||^2_F$$

因为w是一个$n^{[l]} \times n^{[l-1]}$矩阵所以:$${||w^{[l]}||^2_F = \sum\limits_{i=1}^{n^{[l-1]}} \sum\limits_{j=1}^{n^{[l]}} (w_{ij}^{[l]})^2}$$
这被称为弗罗贝尼乌斯范数(Frobenius Norm),所以神经网络的中的正则化项被称为弗罗贝尼乌斯范数矩阵。

加入正则化项后,反向传播时:$$dw^{[l]} = \frac{\partial \mathcal{L}}{\partial w^{[l]}} + \frac{\lambda}{m} w^{[l]}$$
更新参数时:$$ w^{[l]} := w^{[l]} - \alpha \frac{\partial \mathcal{L}}{\partial w^{[l]}} - \alpha \frac{\lambda}{m} w^{[l]}$$
有了新定义的$dw^{[l]}$,参数$w^{[L]}$在更新时会多减去一项$\alpha \frac{\lambda}{m} w^{[l]}$,所以L2正则化项也被称为权值衰减(Weight Decay)

参数$\lambda$用来调整式中两项的相对重要程度,较小$\lambda$偏向于最后使原本的成本函数最小化,较大的$\lambda$偏向于最后使权值$w$最小化。当$\lambda$较大时,权值$w^{[L]}$便会趋近于$0$,相当于消除深度神经网络中隐藏单元的部分作用。另一方面,在权值$w^{[L]}$变小之下,输入样本$X$随机的变化不会对神经网络模造成过大的影响,神经网络受局部噪音的影响的可能性变小。这就是正则化能够降低模型方差的原因。

随机失活正则化

随机失活(DropOut)正则化,就是在一个神经网络中对每层的每个节点预先设置一个被消除的概率,之后在训练随机决定将其中的某些节点给消除掉,得到一个被缩减的神经网络,以此来到达降低方差的目的。DropOut正则化较多地被使用在计算机视觉(Computer Vision)领域。

使用Python编程时可以用反向随机失活(Inverted DropOut)来实现DropOut正则化:

对于一个神经网络第3层

1
2
3
4
5
keep.prob = 0.8
d3 = np.random.randn(a3.shape[0],a3.shape[1]) < keep.prob
a3 = np.multiply(a3,d3)
a3 /= keep.prob
z4 = np.dot(w4,a3) + b4

其中的d3是一个随机生成,与第3层大小相同的的布尔矩阵,矩阵中的值为0或1。而keep.prob ≤ 1,它可以随着各层节点数的变化而变化,决定着失去的节点的个数。

例如,将keep.prob设置为0.8时,矩阵d3中便会有20%的值会是0。而将矩阵a3和d3相乘后,便意味着这一层中有20%节点会被消除。需要再除以keep_prob的原因,是因为后一步求z4中将用到a3,而a3有20%的值被清零了,为了不影响下一层z4的最后的预期输出值,就需要这个步骤来修正损失的值,这一步就称为反向随机失活技术,它保证了a3的预期值不会因为节点的消除而收到影响,也保证了进行测试时采用的是DropOut前的神经网络。

与之前的L2正则化一样,利用DropOut,可以简化神经网络的部分结构,从而达到预防过拟合的作用。另外,当输入众多节点时,每个节点都存在被删除的可能,这样可以减少神经网络对某一个节点的依赖性,也就是对某一特征的依赖,扩散输入节点的权重,收缩权重的平方范数。

数据扩增法

数据扩增法

数据扩增(Data Augmentation)是在无法获取额外的训练样本下,对已有的数据做一些简单的变换。例如对一张图片进行翻转、放大扭曲,以此引入更多的训练样本。

早停止法

早停止法

早停止(Early Stopping)是分别将训练集和开发集进行梯度下降时成本变化曲线画在同一个坐标轴内,在箭头所指两者开始发生较大偏差时就及时进行纠正,停止训练。在中间箭头处,参数w将是一个不大不小的值,理想状态下就能避免过拟合的发生。然而这种方法一方面没有很好得降低成本函数,又想以此来避免过拟合,一个方法解决两个问题,哪个都不能很好解决。

标准化数据集

对训练及测试集进行标准化的过程为:$$ \bar{x} = \frac{1}{m} \sum_{i=1}^m x^{(i)} $$ $$ x^{(i)} := x^{(i)} - \bar{x} $$ $$ \sigma^2 = \frac{1}{m} \sum_{i=1}^m {x^{(i)}}^2$$ $$x^{(i)}:= \frac{x^{(i)}}{\sigma^2} $$
原始数据为:
原始数据

经过前两步,x减去它们的平均值后:
减去平均值

经过后两步,x除以它们的方差后:
除以方差

数据集未进行标准化时,成本函数的图像及梯度下降过程将是:
未进行标准化

而标准化后,将变为:
标准化后

初始化权重

在之前的建立神经网络的过程中,提到权重w不能为0,而将它初始化为一个随机的值。然而在一个深层神经网络中,当w的值被初始化过大时,进入深层时呈指数型增长,造成梯度爆炸;过小时又会呈指数级衰减,造成梯度消失

Python中将w进行随机初始化时,使用numpy库中的np.random.randn()方法,randn是从均值为0的单位标准正态分布(也称“高斯分布”)进行取样。随着对神经网络中的某一层输入的数据量n的增长,输出数据的分布中,方差也在增大。结果证明,可以除以输入数据量n的平方根来调整其数值范围,这样神经元输出的方差就归一化到1了,不会过大导致到指数级爆炸或过小而指数级衰减。也就是将权重初始化为:

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

这样保证了网络中所有神经元起始时有近似同样的输出分布。
当激活函数为ReLU函数时,权重最好初始化为:

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

以上结论的证明过程见参考资料。

梯度检验

梯度检验的实现原理,是根据导数的定义,对成本函数求导,有:$$ J’(\theta) = \frac{\partial J(\theta)}{\partial \theta}= \lim_{\epsilon\rightarrow 0}\frac{J(\theta+\epsilon)-J(\theta-\epsilon)}{2\epsilon}$$

则梯度检验公式:$$J’(\theta) = \frac{J(\theta+\epsilon)-J(\theta-\epsilon)}{2\epsilon}$$

其中当$\epsilon$越小时,结果越接近真实的导数也就是梯度值。可以使用这种方法,来判断反向传播进行梯度下降时,是否出现了错误。

梯度检验的过程,是对成本函数的每个参数$\theta_{[i]}$加入一个很小的$\epsilon$,求得一个梯度逼近值$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}$$

以解析方式求得$J’(\theta)$在$\theta$时的梯度值$d\theta$,进而再求得它们之间的欧几里得距离:
$$\frac{||d\theta_{approx[i]}-d\theta||_2}{||d \theta_{approx[i]}||_2+||dθ||_2}$$

其中$||x||_2$表示向量x的2范数(各种范数的定义见参考资料):
$$||x||_2 = \sum\limits_{i=1}^N |x_i|^2$$

当计算的距离结果与$\epsilon$的值相近时,即可认为这个梯度值计算正确,否则就需要返回去检查代码中是否存在bug。

需要注意的是,不要在训练模型时进行梯度检验,当成本函数中加入了正则项时,也需要带上正则项进行检验,且不要在使用随机失活后使用梯度检验。

Python实现

三种参数初始化

1.zeros\random\Xavier初始化

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
#参数w,b初始化为0
def initialize_parameters_zeros(layers_dims):

parameters = {}
L = len(layers_dims)

for l in range(1, L):
parameters['W' + str(l)] = np.zeros((layers_dims[l],layers_dims[l-1]))
parameters['b' + str(l)] = np.zeros((layers_dims[l],1))
return parameters

#参数w初始化为random
def initialize_parameters_random(layers_dims):

np.random.seed(3)
parameters = {}
L = len(layers_dims)

for l in range(1, L):
parameters['W' + str(l)] = np.random.randn(layers_dims[l],layers_dims[l-1]) * 10
parameters['b' + str(l)] = np.zeros((layers_dims[l],1))
return parameters

#参数w进行Xavier初始化
def initialize_parameters_he(layers_dims):

np.random.seed(3)
parameters = {}
L = len(layers_dims) - 1

for l in range(1, L + 1):
parameters['W' + str(l)] = np.random.randn(layers_dims[l],layers_dims[l-1]) * np.sqrt(1.0/(layers_dims[l-1]))
parameters['b' + str(l)] = np.zeros((layers_dims[l],1))
return parameters

2.效果比较

1
2
3
4
5
6
7
8
9
10
layers = [train_X.shape[0], 10, 5, 1] #设置神经网络层数及节点数

#参数初始化为0
parameters_init_zeros = model(train_X, train_Y, layers_dims = layers, initialization = "zeros")

#初始化random
parameters_init_random = model(train_X, train_Y, layers_dims = layers, initialization = "random")

#Xavier初始化
parameters_init_he = model(train_X, train_Y, layers_dims = layers, initialization = "he")

结果为:
zeros\random\Xavier初始化

无\L2\DropOut正则化

1
2
3
4
5
6
7
8
9
10
layers = [train_x.shape[0], 20, 3, 1] #设置神经网络层数及节点数

#不进行正则化
parameters_no_reg = model(train_x, train_y, layers_dims = layers, learning_rate = 0.3, num_iterations = 30000)

#采用L2正则化
parameters_L2_reg = model(train_x, train_y, layers_dims = layers, learning_rate = 0.3, num_iterations = 30000, lambd = 0.7)

#采用DropOut正则化
parameters_dropout_reg = model(train_x, train_y, layers_dims = layers, learning_rate = 0.3, num_iterations = 30000, keep_prob = 0.86)

结果为:
无\L2\DropOut正则化

参考资料

  1. 吴恩达-改善深层神经网络-网易云课堂
  2. Andrew Ng-Improving Deep Neural Networks-Coursera
  3. deeplearning.ai
  4. 神经网络权重初始化-知乎专栏
  5. 范数的定义-知乎回答
  6. 课程代码与资料-GitHub

注:本文涉及的图片及资料均整理翻译自Andrew Ng的Deep Learning系列课程,版权归其所有。翻译整理水平有限,如有不妥的地方欢迎指出。


更新历史:

  • 2017.10.02 完成初稿
  • 2018.02.13 调整部分内容
文章作者: Hugsy
文章链接: http://binweber.top/2017/09/28/deep_learning_3/
版权声明: 本博客所有文章除特别声明外,均采用 CC BY-NC-SA 4.0 许可协议。转载请注明来自 Weber
支付宝打赏~
微信打赏~