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

来源:互联网 发布:java实现三级联动 编辑:程序博客网 时间:2024/06/05 13:36

机器学习的应用是一个不断重复的过程。有了想法,就用利用代码去实现这个想法,获得一定经验后,又反过来改进想法。想要提高一个深层神经网络的训练效率,必须从各个方面入手,优化整个运算过程,同时预防其中可能发生的各种问题。

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

数据划分

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

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

模型估计

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

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

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

预防过拟合

L2正则化

向Logistic回归的成本函数中加入L2正则化(也称“L2范数”)项:

J(w,b)=1mi=1mL(y^(i),y(i))+λ2m||w||22(function () {

其中:
(this).text().split(\n).length;var

L2正则化是最常用的正则化类型,也存在L1正则化项:
λm||w||1=λmj=1n|wj|(this).addClass(hasnumbering).parent().append(

由于L1正则化最后得到w向量中将存在大量的0,使模型变得稀疏化,所以一般都使用L2正则化。其中的参数λ称为正则化参数,这个参数通常通过开发集来设置。
向神经网络的成本函数加入正则化项:
J(w[1],b[1],...,w[L],b[L])=1mi=1mL(y^(i),y(i))+λ2ml=1L||w[l]||2F

因为w是一个n[l]n[l1]
这被称为弗罗贝尼乌斯范数(Frobenius Norm),所以神经网络的中的正则化项被称为弗罗贝尼乌斯范数矩阵。

加入正则化项后,反向传播时:

dw[l]=Lw[l]+λmw[l]

更新参数时:
w[l]:=w[l]αLw[l]αλmw[l]

有了新定义的dw[l],所以L2正则化项也被称为权值衰减(Weight Decay)

参数λ变小之下,输入样本X随机的变化不会对神经网络模造成过大的影响,神经网络受局部噪音的影响的可能性变小。这就是正则化能够降低模型方差的原因。

随机失活正则化

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

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

对于一个神经网络第3层

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

其中的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将是一个不大不小的值,理想状态下就能避免过拟合的发生。然而这种方法一方面没有很好得降低成本函数,又想以此来避免过拟合,一个方法解决两个问题,哪个都不能很好解决。

标准化数据集

对训练及测试集进行标准化的过程为:

x¯=1mi=1mx(i)

原始数据为:
原始数据

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

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

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

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

初始化权重

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

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

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

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

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

(tip:以上结论具体的证明推导过程见参考资料)

梯度检验

根据导数的定义,对成本函数求导,有:

J(θ)=J(θ)θ=limϵ0J(θ+ϵ)J(θϵ)2ϵ

则梯度检验公式:
J(θ)=J(θ+ϵ)J(θϵ)2ϵ

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

如图,加入一个很小的ϵ
其中||x||2
(tip:关于各种范数的定义见参考资料)

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

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

Python实现

三种参数初始化

1.zeros\random\Xavier初始化

#参数w,b初始化为0def 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初始化为randomdef 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
  • 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

2.效果比较

layers = [train_X.shape[0], 10, 5, 1] #设置神经网络层数及节点数#参数初始化为0parameters_init_zeros = model(train_X, train_Y, layers_dims = layers, initialization = "zeros")#初始化randomparameters_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")
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10

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

无\L2\DropOut正则化

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)
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10

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

梯度检验

#梯度检验def gradient_check_n(parameters, gradients, X, Y, epsilon = 1e-7):    parameters_values, _ = dictionary_to_vector(parameters)    grad = gradients_to_vector(gradients)    num_parameters = parameters_values.shape[0]    J_plus = np.zeros((num_parameters, 1))    J_minus = np.zeros((num_parameters, 1))    gradapprox = np.zeros((num_parameters, 1))    for i in range(num_parameters):        thetaplus = np.copy(parameters_values)                                              thetaplus[i][0] = thetaplus[i][0] + epsilon                                         J_plus[i], _ =  forward_propagation_n(X, Y, vector_to_dictionary(thetaplus))                                                                             thetaminus = np.copy(parameters_values)                                           thetaminus[i][0] = thetaminus[i][0] - epsilon                                             J_minus[i], _ = forward_propagation_n(X, Y, vector_to_dictionary(thetaminus))                                       gradapprox[i] = (J_plus[i] - J_minus[i])/(2*epsilon)    numerator = np.linalg.norm(grad - gradapprox)                                denominator = np.linalg.norm(grad) + np.linalg.norm(gradapprox)                                 difference = numerator/denominator                                              if difference > 1e-7:        print ("\033[93m" + "There is a mistake in the backward propagation! difference = " + str(difference) + "\033[0m")    else:        print ("\033[92m" + "Your backward propagation works perfectly fine! difference = " + str(difference) + "\033[0m")    return difference
  • 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

参考资料

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

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

更新历史:
* 2017.10.2 完成初稿

原文链接

('pre.prettyprint code').each(function () { var lines = numbering = $('
    ').addClass('pre-numbering').hide(); numbering); for (i = 1; i
阅读全文
0 0