概述
本文建立在前一篇文章的基础上进行讨论,这一系列文章的本意是记录本人的学习历程,避免“后面学,前面忘”的窘境。
前文讨论到超参数,本文继续讨论如何处理上文的问题。具体而言,主要涉及到:
- 对欠拟合、过拟合的处理
- 正则化方法(Regularization)
- 正则化
- Dropout正则化
- 标准化方法(Normalization)
- 梯度消失(Vanishing Gradient)
- 梯度爆炸(Exploding Gradient)
- 梯度检查(Gradient Checking)
神经网络训练数据
深度学习的火爆离不开现如今的大量数据以及Tensorflow之类算力的加成buff,一般把数据分为训练集、开发集、测试集(Train/Dev/Test sets)。
数据集
训练集就是训练神经网络的数据集,一般而言越多越好;开发集则一般用来调参,因此最终神经网络实际上是包含了训练集和开发集的信息,因此开发集上的测试结果并不是无偏的;测试集则用来测试训练好的神经网络,结果也是无偏的。
一般而言,当数据量较少时(),训练集、开发集、测试集的比例一般为。这三个数据集中测试集可以没有,这样的话开发集实际上充当了测试集的功能。但由于上面提到的原因,开发集并不完全等价于测试集。
当数据量很大时,比如,这时,并不一定要按照的比例来划分。由于开发集、测试集的功能,其并不需要很多的数据,因此给两者分配一定数量的数据可以完成目标就可以了。因此,可以按照的比例来分配数据。
数据量
当数据量很大时,神经网络将变得十分臃肿,进而拖慢训练速度。比如,当数据量为时,我们可以把整个数据集分为1000个小数据集,每个小数据集包含1000个数据。
当神经网络在上完成一次训练,便进行一次参数更新:即原本要跑完条数据才会更新参数,现在每跑完一小部分数据就更新一次参数。在上跑完一次,成为完成了一次epoch。
初始化参数
这里的参数指和,运用“He initialization”可以更快的使模型收敛。具体而言,只需要在常规初始化的参数后面乘上即可。用代码表示为:
parameters['W'+str(l)] = np.random.randn(layers_dims[l], layers_dims[l-1])*np.sqrt(2/layers_dims[l-1])parameters['b'+str(l)] = np.zeros((layers_dims[l], 1))
欠拟合与过拟合
前文提到,区分两者的关键是模型在训练集和测试集上的表现:
- 欠拟合:训练集误差大,测试集误差较小
- 过拟合:训练集误差小,测试集误差较大
- 同时发生:训练集、测试集上误差均较大
处理欠拟合的方法有:
- 增加神经网络深度
- 调整超参数
处理过拟合的方法有:
- 正则化
- 标准化
正则化(Regularization)
一般而言,避免过拟合的标准方法是正则化,它从改变成本函数开始。 正常的成本函数是:
改变为:
这里成为正则化常数,是一个需要我们不断测试来选择最佳值的超参数。
进而,反向传播的计算公式也有所改变:
Dropout
Dropout也是常用的一种正则化手段,其在每次迭代过程中随机关闭一些神经节点,看起来不可思议,问题是它居然真的有用。
代码中通过一个由0和1为元素构成的矩阵来实现神经节点的开关。
Dl = np.random.rand(Al.shape[0], Al.shape[1]) #随机产生0-1间的数字Dl = (Dl < keep_prob) #keep_prob是保持概率,小于这个数的将被设置为1Al *= DlAl /= keep_prob
规范化(Normalization)
也称为标准化,是对输入数据进行的处理:把输入数据的均值变为0,方差变为1。
经过这样的处理后,模型收敛速度更快。
梯度检测(Gradient Checking)
根据导数的定义,即
进而赋予一个很小的值来近似计算倒数来比较。
算法优化
数据量
这里是指前文提到的,当数据量很大时,把整体数据分成一些小数据集,在小数据集上训练数据。
动量法(Momentum)
动量法可以纠正梯度下降法的frustration。
初始化动量
动量的维度应该和参数相同,具体而言:
def initialize_velocity(parameters): """ 参数: parameters -- 包含参数W和b的python字典 Wl = parameters['W'+str(l)] bl = parameters['b'+str(l)]
返回值: v -- 包含计算好的动量的python字典 dWl的动量 = v['dW'+str(l)] dbl的动量 = v['db'+str(l)] """
L = len(parameters) // 2 v = {}
for l in range(L): v['dW'+str(l+1)] = np.zeros(parameters['W'+str(l+1)].shape) v['db'+str(l+1)] = np.zeros(parameters['b'+str(l+1)].shape)
return v
更新参数W,b
更新参数和的规则为:
对应的代码为:
def update(parameters, grads, v, beta, learning_rate): """ 参数: parameters -- Wl = parameters['W'+str(l)] bl = parameters['b'+str(l)] grads -- dWl = grads['dW'+str(l)] dbl = grads['db'+str(l)] beta -- 常量,动量法的一个超参数 learning_rate -- 常量,学习速率
返回值: parameters -- 同上,更新后的参数 v -- 保存计算好的动量值的字典 """
L = len(parameters) // 2
for l in range(L): v['dW'+str(l+1)] = beta * v['dW'+str(l+1)] + (1 - beta) * grads['dW'+str(l+1)] v['db'+str(l+1)] = beta * v['db'+str(l+1)] + (1 - beta) * grads['db'+str(l+1)]
parameters['W'+str(l+1)] -= learning_rate * v['dW'+str(l+1)] parameters['b'+str(l+1)] -= learning_rate * v['db'+str(l+1)]
return parameters, v
一般而言,的值一般选取0.8到0.999之间的某个数,是一个常用的数。
Adam优化法
Adam法结合了动量法和RMSProp,是效率较高的一种优化方法。 其数学表示如下:
这里:
- 表示Adam算法的运行次数
- 和是两个超参数
- 是一个非常小的常数,其主要目的是防止分母部分为零
代码如下:
def update_parameters(parameters, grads, v, s, t, learning_rate=0.01, beta1=0.9, beta2=0.999, epsilon=1e-8): """ 输入: parameters -- 包含W和b的字典 grads -- 包含梯度的字典 v -- Adam法的动量部分一 s -- Adam法的栋梁部分二 t -- Adam法的执行次数
返回值: parameters -- 调整后的参数字典 v, b -- 同上 """
L = len(parameters) // 2 v_corrected = {} s_corrected = {}
for l in range(L): v['dW'+str(l+1)] = beta1 * v['dW'+str(l+1)] + (1 - beta1) * grads['dW'+str(l+1)] v['db'+str(l+1)] = beta1 * v['db'+str(l+1)] + (1 - beta1) * grads['bd'+str(l+1)]
v_corrected['dW'+str(l+1)] = v['dW'+str(l+1)] / (1 - np.power(beta1, t)) v_corrected['db'+str(l+1)] = v['db'+str(l+1)] / (1 - np.power(beta1, t))
s['dW'+str(l+1)] = beta2 * s['dW'+str(l+1)] + (1 - beta2) * np.power(grads['dW'+str(l+1)], 2) s['db'+str(l+1)] = beta2 * s['db'+str(l+1)] + (1 - beta2) * np.power(grads['db'+str(l+1)], 2)
s_corrected['dW'+str(l+1)] = s['dW'+str(l+1)] / (1 - np.power(beta2, t)) s_corrected['db'+str(l+1)] = s['db'+str(l+1)] / (1 - np.power(beta2, t))
parameters['dW'+str(l+1)] -= learning_rate * v_corrected['dW'+str(l+1)] / (np.sqrt(s_corrected['dW'+str(l+1)]) + epsilon) parameters['db'+str(l+1)] -= learning_rate * v_corrected['db'+str(l+1)] / (np.sqrt(s_corrected['db'+str(l+1)]) + epsilon)
return parameters, v, s
超参数的调整
超参数
常见的超参数按重要性分类:
- 重要性No.1
- 学习速率
- 重要性No.2
- 动量法参数,常用
- 隐藏层的神经单元数量
- 每个小的数据集包含的数据量,多为2的倍数,如64,256
- 重要性No.3
- 神经网络的深度
- 学习速率递减参数
- 不那么重要
- Adam优化法参数,
调参的一般方法
随机选取参数进行训练并观察结果是一般方法,要注意的是不能简单的取随机数。
比如学习速率的范围一般取,但是,下面这样的做法是不可取的:
alpha = np.random.rand() * 0099 + 0.001
下面的方法是可取的:
beta = -1 - np.random.rand() * 2alpha = np.power(10, beta)
Batch Norm
BN是一种与之前的规范会类似的方法。前文说到的规范化是针对输入的,这里则是针对所有的做的处理。
多种类分类器(Multi-class Classification)
Softmax回归
与之前的二元分类器类似,当有个种类时,这里的输出是一个矩阵。具体而言:
Softmax是与Hardmax相对的,Hardmax是直接把中最大的输出直接赋值为1,其余赋值为0。因该方法简单直接,因而被赋予Hard之名,Softmax较为柔和。
Tensorflow
Tensorflow作为Google的开源机器学习框架,可以很方便的帮助我们部署机器学习的内容,尤其是其可以自动计算Backward Propagation部分。