- 作者:韩信子@ShowMeAI
- 教程地址:http://www.showmeai.tech/tutorials/35
- 本文地址:http://www.showmeai.tech/article-detail/218
- 声明:版权所有,转载请联系平台与作者并注明出处
收藏ShowMeAI查看更多精彩内容
本系列为吴恩达老师《深度学习专项课程(Deep Learning Specialization)》学习与总结整理所得,对应的课程视频可以在这里查看。
引言
在ShowMeAI前一篇文章 神经网络优化算法 中我们对以下内容进行了介绍:
- batch/mini-batch/ Stochastic gradient descent
- 指数加权平均(Exponentially weighted averages)和偏移校正(bias correction)
- 动量梯度下降、RMSprop和Adam算法
- 学习率衰减法
- 局部最优的概念及结论
本篇我们将重点展开介绍超参数调试、BN(Batch Normalization批归一化)和深度学习编程框架三个部分的内容。
1.超参数调试处理
深度神经网络需要调试的超参数(Hyperparameters)较多,我们来看看如何对其调试。
1.1 重要程度排序
吴恩达老师前面讲到过的超参数中,重要程度依次是(仅供参考):
最重要:
- 学习率 \( \alpha\)
其次重要:
- \( \beta\) :动量衰减参数,常设置为 0.9
- \( n^{[l]}\) :各隐藏层神经元个数(#hidden units)
- Mini-Batch 的大小
再次重要:
- \( \beta_1\) ,\( \beta_2\) ,\( \varepsilon\) :Adam 优化算法的超参数,常设为 0.9、0.999、\( 10^{-8}\)
- \( L\) :神经网络层数(#layers)
- decay_rate:学习衰减率
1.2 调参技巧
我们下面来看看神经网络的超参数选择与调试方法。在传统的机器学习中,我们对每个参数等距离选取任意个数的点,然后,分别使用不同点对应的参数组合进行训练,最后根据验证集上的表现好坏,来选定最佳的参数。
例如有两个待调试的参数Hyperparameter 1和Hyperparameter 2,在每个参数上分别均匀间隔选取\( 5\) 个点,这样构成了\( 5 \times 5=25\) 种参数组合,如图所示。这种做法在参数比较少的时候效果较好。
但是在深度神经网络模型中,我们一般不采用这种均匀间隔取点的方法,比较好的做法是使用随机选择。也就是说,对于上面这个例子,我们随机选择25个点,作为待调试的超参数,如下图所示:
随机化选择参数的目的是为了尽可能地得到更多种参数组合。还是上面的例子,如果使用均匀采样的话,每个参数只有5种情况;而使用随机采样的话,每个参数有25种可能的情况,因此更有可能得到最佳的参数组合。
这种做法带来的另外一个好处就是对重要性不同的参数之间的选择效果更好。假设hyperparameter1为\( \alpha\) ,hyperparameter2为\( \varepsilon\) ,显然二者的重要性是不一样的。
- 如果使用第一种均匀采样的方法,\( \varepsilon\) 的影响很小,相当于只选择了5个\( \alpha\) 值。
- 如果使用第二种随机采样的方法,\( \varepsilon\) 和\( \alpha\) 都有可能选择25种不同值。这大大增加了\( \alpha\) 调试的个数,更有可能选择到最优值。
其实,在实际应用中完全不知道哪个参数更加重要的情况下,随机采样的方式能有效解决这一问题,但是均匀采样做不到这点。
在经过随机采样之后,我们可能得到某些区域模型的表现较好。然而,为了得到更精确的最佳参数,我们应该继续对选定的区域进行由粗到细的采样(coarse to fine sampling scheme)。也就是放大表现较好的区域,再对此区域做更密集的随机采样。例如,对图中右下角的方形区域再做25点的随机采样,以获得最佳参数。
综上,超参调试过程的技巧总结如下:
- ① 随机选择点(而非均匀选取),用这些点实验超参数的效果。这样做的原因是我们提前很难知道超参数的重要程度,可以通过选择更多值来进行更多实验;
- ② 由粗糙到精细:聚焦效果不错的点组成的小区域,在其中更密集地取值,以此类推;
1.3 选择合适的范围
上一段讲到使用随机采样调试超参数,对于某些超参数是可以进行尺度均匀采样的,但是某些超参数需要选择不同的合适尺度进行随机采样。举例来说:
① 对于超参数(#layers)和(#hidden units),都是正整数,是可以进行均匀随机采样的,即超参数每次变化的尺度都是一致的(如每次变化为1,犹如一个刻度尺一样,刻度是均匀的)。
② 对于超参数\( \alpha\) ,待调范围是\( [0.0001, 1]\) 。
- 如果使用均匀随机采样,那么有90%的采样点分布在\( [0.1, 1]\) 之间,只有10%分布在\( [0.0001, 0.1]\) 之间。这在实际应用中是不太好的,因为最佳的\( \alpha\) 值可能主要分布在\( [0.0001, 0.1]\) 之间,而\( [0.1, 1]\) 范围内\( \alpha\) 值效果并不好。
- 我们更关注的是区间\( [0.0001, 0.1]\) ,应该在这个区间内细分更多刻度。
对于非均匀采样,一种常用的做法是将linear scale转换为log scale,将均匀尺度转化为非均匀尺度,然后再在log scale下进行均匀采样。这样,\( [0.0001, 0.001]\) 、\( [0.001, 0.01]\) 、\( [0.01, 0.1]\) 、\( [0.1, 1]\) 各个区间内随机采样的超参数个数基本一致,也就扩大了之前\( [0.0001, 0.1]\) 区间内采样值个数。
我们以重要参数学习率和动量衰减参数为例:
- 对于学习率 \( \alpha\) ,用对数标尺而非线性轴更加合理:0.0001、0.001、0.01、0.1 等,然后在这些刻度之间再随机均匀取值;
- 对于动量衰减参数\( \beta\) ,取 0.9 就相当于在 10 个值中计算平均值,而取 0.999 就相当于在 1000 个值中计算平均值。可以考虑给 \( 1-\beta\) 取值,这样就和取学习率类似了。
上述操作的原因是当 \( \beta\) 接近 1 时,即使 \( \beta\) 只有微小的改变,所得结果的灵敏度会有较大的变化。例如,\( \beta\) 从 0.9 增加到 0.9005 对结果\( (1/(1-\beta\) ))几乎没有影响,而 \( \beta\) 从 0.999 到 0.9995 对结果的影响巨大(从 1000 个值中计算平均值变为 2000 个值中计算平均值)。
1.4 一些建议
(1) 深度学习如今已经应用到许多不同的领域。不同的应用出现相互交融的现象,某个应用领域的超参数设定有可能通用于另一领域。不同应用领域的人也应该更多地阅读其他研究领域的 paper,跨领域地寻找灵感。
(2) 考虑到数据的变化或者服务器的变更等因素,建议每隔几个月至少一次,重新测试或评估超参数,来获得实时的最佳模型;
(3) 根据你所拥有的计算资源来决定你训练模型的方式:
- Panda(熊猫方式):在在线广告设置或者在计算机视觉应用领域有大量的数据,但受计算能力所限,同时试验大量模型比较困难。可以采用这种方式:试验一个或一小批模型,初始化,试着让其工作运转,观察它的表现,不断调整参数;
- Caviar(鱼子酱方式):拥有足够的计算机去平行试验很多模型,尝试很多不同的超参数,选取效果最好的模型;
2.Batch Normalization
Sergey Ioffe和Christian Szegedy两位学者在paper中提出了批标准化(Batch Normalization,经常简称为 BN)方法。Batch Normalization不仅可以让调试超参数更加简单,而且可以让神经网络模型更加「健壮」。也就是说较好模型可接受的超参数范围更大一些,包容性更强,使得更容易去训练一个深度神经网络。
接下来,我们就来介绍什么是Batch Normalization,以及它是如何工作的。
之前,我们对输入特征 \( X\) 使用了标准化处理。我们也可以用同样的思路处理隐藏层的激活值\( a^{[l]}\) ,以加速 \( W^{[l+1]}\) 和 \( b^{[l+1]}\) 的训练。在实践中,经常选择标准化隐藏层输入 \( Z^{[l]}\) ,这里我们对第\( l\) 层隐层做如下处理:
其中,\( m\) 是单个 Mini-Batch 所包含的样本个数,\( \varepsilon\) 是为了防止分母为零,通常取 \( 10^{-8}\) 。
我们来从「样本」和「通道」维度对上述BN环节做一个可视化展开详解,对于某一层\( N \times D\) 的输入,我们上述操作的计算过程如下图所示。(注意其中\( i\) 为样本维度,\( j\) 为通道维度)
在经过上述处理前后的数据分布如下图所示,我们可以看到这个处理对数据的「Normalization」影响。
这样,我们使得所有的输入 \( z^{(i)}\) 均值为 0,方差为 1。但我们不想粗暴地让处理后的隐藏层单元直接变为均值 0 方差 1,这样原本学习到的数据分布就被直接抹掉了。因此,我们引入可学习参数\( \gamma\) 和 \( \beta\) ,对\( z_{norm}^{(i)}\) 进行线性变换,如下:
其中,\( \gamma\) 和 \( \beta\) 都是模型的学习参数,所以可以用各种梯度下降算法来更新 \( \gamma\) 和 \( \beta\) 的值,如同更新神经网络的权重一样。
通过对 \( \gamma\) 和 \( \beta\) 的合理设置,可以让 \( \tilde z^{(i)}\) 的均值和方差为任意值。这样,我们对隐藏层的 \( z^{(i)}\) 进行标准化处理,用得到的 \( \tilde z^{(i)}\) 替代 \( z^{(i)}\) 。
公式中设置可学习参数 \( \gamma\) 和 \( \beta\) ,原因是如果各隐藏层的输入均值在靠近 0 的区域,即处于激活函数的线性区域,不利于训练非线性神经网络,从而得到效果较差的模型。因此,需要用 \( \gamma\) 和 \( \beta\) 对标准化后的结果做进一步处理。
2.1 将 BN 应用于神经网络
上面讲解了对某单一隐层的所有神经元进行BN批归一化的方法,总结示意图如下:
接下来我们研究一下如何把Bath Norm应用到整个神经网络中,对于 L 层神经网络,经过 Batch Normalization 的作用,整体流程如下:
实际上,Batch Normalization 经常使用在 Mini-Batch 上,这也是其名称的由来。
使用 Batch Normalization 时,因为标准化处理中包含减去均值的一步,因此 \( b\) 实际上没有起到作用,其数值效果交由 \( \beta\) 来实现。因此,在 Batch Normalization 中,可以省略 \( b\) 或者暂时设置为 0。
在使用梯度下降算法时,分别对 \( W^{[l]}\) ,\( \beta^{[l]}\) 和 \( \gamma ^{[l]}\) 进行迭代更新。
除了传统的梯度下降算法之外,也同样可以使用ShowMeAI上一篇文章 神经网络优化算法 提到的动量梯度下降、RMSProp 或者 Adam 等优化算法。
2.2 BN 有效的原因
Batch Normalization 效果很好的原因有以下两点:
- 通过对隐藏层各神经元的输入做类似的标准化处理,提高神经网络训练速度;
- 可以使前面层的权重变化对后面层造成的影响减小,整体网络更加健壮。
关于第二点,如果实际应用样本和训练样本的数据分布不同(如下图中的黑猫图片和橘猫图片),我们称发生了「Covariate Shift」。这种情况下,一般要对模型进行重新训练。Batch Normalization 的作用就是减小 Covariate Shift 所带来的影响,让模型变得更加健壮,鲁棒性(Robustness)更强。
即使输入的值改变了,由于 Batch Normalization 的作用,使得均值和方差保持不变(由 \( \gamma\) 和 \( \beta\) 决定),限制了在前层的参数更新对数值分布的影响程度,因此后层的学习变得更容易一些。Batch Normalization 减少了各层 \( W\) 和 \( b\) 之间的耦合性,让各层更加独立,实现自我训练学习的效果。
另外,Batch Normalization 也起到微弱的正则化(regularization)效果。因为在每个 Mini-Batch 而非整个数据集上计算均值和方差,只由这一小部分数据估计得出的均值和方差会有一些噪声,因此最终计算出的 \( \tilde z^{(i)}\) 也有一定噪声。类似于 Dropout,这种噪声会使得神经元不会再特别依赖于任何一个输入特征。
因为 Batch Normalization 只有微弱的正则化效果,因此可以和 Dropout 一起使用,以获得更强大的正则化效果。通过应用更大的 Mini-Batch 大小,可以减少噪声,从而减少这种正则化效果。
吴恩达老师也提醒大家,不要将 Batch Normalization 作为正则化的手段,而是当作加速学习的方式。正则化只是一种非期望的副作用,Batch Normalization 解决的还是反向传播过程中的梯度问题(梯度消失和爆炸)。
2.3 测试阶段的 Batch Normalization
Batch Normalization 将数据以 Mini-Batch 的形式逐一处理,但在测试时,可能需要对每一个样本逐一处理,这样无法得到 \( \mu\) 和 \( \sigma^2\) 。
理论上,我们可以将所有训练集放入最终的神经网络模型中,然后将每个隐藏层计算得到的 \( \mu^{[l]}\) 和 \( \sigma^{2[l]}\) 直接作为测试过程的 \( \mu\) 和 \( \sigma\) 来使用。但是,实际应用中一般不使用这种方法,而是使用之前学习过的指数加权平均的方法来预测测试过程单个样本的 \( \mu\) 和 \( \sigma^2\) 。
对于第 \( l\) 层隐藏层,考虑所有 Mini-Batch 在该隐藏层下的 \( \mu^{[l]}\) 和 \( \sigma^{2[l]}\) ,然后用指数加权平均的方式来预测得到当前单个样本的 \( \mu^{[l]}\) 和 \( \sigma^{2[l]}\) 。这样就实现了对测试过程单个样本的均值和方差估计。
3.Softmax 回归
目前为止,介绍的分类例子都是二分类问题:神经网络输出层只有一个神经元,表示预测输出 \( \hat{y}\) 是正类的概率 \( P(y = 1 \mid x)\) ,\( \hat{y}> 0.5\) 则判断为正类,反之判断为负类。
对于多分类问题,用 \( C\) 表示种类个数,则神经网络输出层,也就是第\( L\) 层的单元数量 \( n^{[L]} = C\) 。每个神经元的输出依次对应属于该类的概率,即 \( P(y=c \mid x),c=0,1,...,C-1\) 。有一种 Logistic 回归的一般形式,叫做 Softmax 回归,可以处理多分类问题。
对于 Softmax 回归模型的输出层,即第 \( L\) 层,有:\( Z^{[L]} = W^{[L]}a^{[L-1]} + b^{[L]}\)
for i in range(L)
,有:\( a^{[L]}_i = \frac{e^{Z^{[L]}_i}}{\sum^C_{i=1}e^{Z^{[L]}_i}}\)
为输出层每个神经元的输出,对应属于该类的概率,满足:\( \sum^C_{i=1}a^{[L]}_i = 1\)
一个直观的计算例子如下:
下图为一些softmax线性分类器得到的分类边界
3.1 损失函数和成本函数
我们来看看softmax分类器的损失函数,这里定义其损失函数为:\( L(\hat y, y) = -\sum^C_{j=1}y_jlog\hat y_j\)
当 \( i\) 为样本真实类别,则有:\( y_j = 0, j \ne i\)
因此,损失函数可以简化为:\( L(\hat y, y) = -y_ilog\hat y_i = log \hat y_i\)
所有 \( m\) 个样本的成本函数为:\( J = \frac{1}{m}\sum^m_{i=1}L(\hat y, y)\)
3.2 梯度下降法
多分类的 Softmax 回归模型与二分类的 Logistic 回归模型只有输出层上有一点区别。经过不太一样的推导过程,仍有
$$ dZ^{[L]} = A^{[L]} - Y $$
反向传播过程的其他步骤也和 Logistic 回归的一致。
参考资料:Softmax回归 - Ufldl
4.深度学习框架
4.1 比较著名的框架
- Caffe / Caffe2
- CNTK
- DL4J
- Keras
- Lasagne
- mxnet
- PaddlePaddle
- TensorFlow
- pytorch
4.2 选择框架的标准
- 便于编程:包括神经网络的开发和迭代、配置产品;
- 运行速度:特别是训练大型数据集时;
- 是否真正开放:不仅需要开源,而且需要良好的管理,能够持续开放所有功能。
5.Tensorflow
目前最火的深度学习框架之一是来自google的Tensorflow 。下面简单做一个介绍。(更多的TensorFlow实战方法,请参考)
Tensorflow 框架内可以直接调用梯度下降算法,极大地降低了编程人员的工作量。例如以下代码:
import numpy as np
import tensorflow as tf
cofficients = np.array([[1.],[-10.],[25.]])
w = tf.Variable(0,dtype=tf.float32)
x = tf.placeholder(tf.float32,[3,1])
# Tensorflow 重载了加减乘除符号
cost = x[0][0]*w**2 + x[1][0]*w + x[2][0]
# 改变下面这行代码,可以换用更好的优化算法
train = tf.train.GradientDescentOptimizer(0.01).minimize(cost)
init = tf.global_variables_initializer()
session = tf.Session()
session.run(init)
for i in range(1000):
session.run(train, feed_dict=(x:coefficients))
print(session.run(w))
打印为 4.99999,基本可以认为是我们需要的结果。更改 cofficients 的值可以得到不同的结果 w。
上述代码中:
session = tf.Session()
session.run(init)
print(session.run(w))
也可以写作:
with tf.Session() as session:
session.run(init)
print(session.run(w))
with 语句适用于对资源进行访问的场合,确保不管使用过程中是否发生异常都会执行必要的「清理」操作,释放资源,比如文件使用后自动关闭、线程中锁的自动获取和释放等。
想了解更多 Tensorflow 有关知识,请参考 官方文档。
参考资料:
ShowMeAI系列教程推荐
- 图解Python编程:从入门到精通系列教程
- 图解数据分析:从入门到精通系列教程
- 图解AI数学基础:从入门到精通系列教程
- 图解大数据技术:从入门到精通系列教程
- 图解机器学习算法:从入门到精通系列教程
- 机器学习实战:手把手教你玩转机器学习系列
- 深度学习教程 | 吴恩达专项课程 · 全套笔记解读
推荐文章
- 深度学习教程 | 深度学习概论
- 深度学习教程 | 神经网络基础
- 深度学习教程 | 浅层神经网络
- 深度学习教程 | 深层神经网络
- 深度学习教程 | 深度学习的实用层面
- 深度学习教程 | 神经网络优化算法
- 深度学习教程 | 网络优化:超参数调优、正则化、批归一化和程序框架
- 深度学习教程 | AI应用实践策略(上)
- 深度学习教程 | AI应用实践策略(下)
- 深度学习教程 | 卷积神经网络解读
- 深度学习教程 | 经典CNN网络实例详解
- 深度学习教程 | CNN应用:目标检测
- 深度学习教程 | CNN应用:人脸识别和神经风格转换
- 深度学习教程 | 序列模型与RNN网络
- 深度学习教程 | 自然语言处理与词嵌入
- 深度学习教程 | Seq2seq序列模型和注意力机制