《Improving Deep Neural Networks:Hyperparameter tuning, Regularization and Optimization》课堂笔记

Lesson 2 Improving Deep Neural Networks:Hyperparameter tuning, Regularization and Optimization

这篇文章其实是 Coursera 上吴恩达老师的深度学习专业课程的第二门课程的课程笔记。

参考了其他人的笔记继续归纳的。

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

在机器学习发展的小数据量时代,常见做法是将所有数据三七分,就是人们常说的 70% 训练集,30% 测试集。如果明确设置了验证集,也可以按照 60% 训练集,20% 验证集和 20% 测试集来划分。这是前几年机器学习领域普遍认可的最好的实践方法。

如果只有 100 条,1000 条或者 1 万条数据,那么上述比例划分是非常合理的。

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

比如我们有 100 万条数据,那么取 1 万条数据便足以进行评估,找出其中表现最好的 1-2 种算法。同样地,根据最终选择的分类器,测试集的主要目的是正确评估分类器的性能,所以,如果拥有百万数据,我们只需要 1000 条数据,便足以评估单个分类器,并且准确评估该分类器的性能。假设我们有 100 万条数据,其中 1 万条作为验证集,1 万条作为测试集,100 万里取 1 万,比例是 1%,即:训练集占 98%,验证集和测试集各占 1%。对于数据量过百万的应用,训练集可以占到 99.5%,验证和测试集各占 0.25%,或者验证集占 0.4%,测试集占 0.1%。

因为我们要用验证集来评估不同的模型,尽可能地优化性能,所以我们尽量要使验证集和测试集来自同一分布。

偏差,方差 (Bias/Variance)

假设这就是数据集,如果给这个数据集拟合一条直线,可能得到一个逻辑回归拟合,但它并不能很好地拟合该数据,这是高偏差(high bias)的情况,我们称为“欠拟合”(underfitting)。

相反的如果我们拟合一个非常复杂的分类器,比如深度神经网络或含有隐藏单元的神经网络,可能就非常适用于这个数据集,但是这看起来也不是一种很好的拟合方式分类器方差较高(high variance),数据过度拟合(overfitting)。

在两者之间,可能还有一些像图中这样的,复杂程度适中,数据拟合适度的分类器,这个数据拟合看起来更加合理,我们称之为“适度拟合”(just right)是介于过度拟合和欠拟合中间的一类。

在这样一个只有 \(x_1\)\(x_2\) 两个特征的二维数据集中,我们可以绘制数据,将偏差和方差可视化。在多维空间数据中,绘制数据和可视化分割边界无法实现,但我们可以通过几个指标,来研究偏差和方差。

以下就是几种情况,知道模型有什么样的表现,我们就能对应的采取什么样的策略去调试。

吴恩达老师在训练神经网络时用到的基本方法如下。

初始模型训练完成后,首先得知道算法的偏差高不高,如果偏差较高,那么试着评估训练集或训练数据的性能。如果偏差的确很高,甚至无法拟合训练集,那么我们要做的就是选择一个新的网络,比如说含有更多隐藏层或者隐藏单元的网络,或者花费更多时间来训练网络,或者尝试更先进的优化算法。

在训练学习算法时,我们需要不断尝试这些方法,直到解决偏差问题,这是最低标准。直到可以拟合数据为止,至少能够拟合训练集。

旦偏差降低到可以接受的数值,检查一下方差有没有问题,为了评估方差,我们要查看验证集性能,我们能从一个性能理想的训练集推断出验证集的性能是否也理想,如果方差高,最好的解决办法就是采用更多数据,但有时候,我们无法获得更多数据,所以可以尝试通过正则化来减少过拟合。系统地说出做法很难,总之就是不断重复尝试,直到找到一个低偏差、低方差的框架。

正则化 (Regularization)

正则化是解决深度学习存在高方差问题的方法。

\(L2\) 正则化

L2 正则化的作用原理

以逻辑回归为例。,求代价函数 \(J\) 的最小值。在逻辑回归函数中加入正则化,只需添加参数 \(\lambda\),也就是正则化参数。

\(\frac{\lambda}{2m}\) 乘以参数 \(w\) 范数的平方,即 \(\left\| w \right\|_2^2\)\(w\) 的欧几里得范数的平方,等于 \(w_j\)\(j\) 值从 1 到 \(n_x\))平方的和,也可表示为 \(w^Tw\),也就是向量参数 \(w\) 的欧几里得范数(2 范数)的平方。这个方法称为 \(L2\) 正则化,因为这里用了欧几里得范数,被称为向量参数 \(w\)\(L2\) 范数。
\[ J(w,b)=\frac{1}{m} \sum^m_{i=1}{L(\hat{y}^{(i)},y^{(i)})}+\frac{\lambda}{2m}\left\|w\right\|^2_2 \]
至于为什么只正则化参数 \(w\),而不再加上参数 \(b\) 呢?可以,但没必要。因为 \(w\) 通常是一个高纬度参数矢量,已经可以表达高偏差问题,\(w\) 可能包含有很多参数,而我们不可能拟合所有参数,\(b\) 只是单个数字,所以 \(w\) 几乎涵盖所有参数。如果加了参数 \(b\),其实也没太大影响。

\(L2\) 正则化是最常见的正则化类型,其实也有 \(L1\) 正则化。它加的不是 \(L2\) 范数,而是正则项 \(\frac{\lambda}{m}\) 乘以 \(\sum^{n_x}_{j=1}\left|w\right|\)。其中,\(\sum^{n_x}_{j=1}\left|w\right|\) 也被称为参数 \(w\) 向量的 \(L1\) 范数,无论分母是 \(m\) 还是 \(2m\),它都是一个比例常量。
\[ J(w,b)=\frac{1}{m} \sum^m_{i=1}{L(\hat{y}^{(i)},y^{(i)})}+\frac{\lambda}{m}\sum^{n_x}_{j=1}\left|w\right| \]
如果用的是 \(L1\) 正则化,\(w\) 最终会是稀疏的,也就是说 \(w\) 向量中有很多 0。有人说这样有利于压缩模型,因为集合中的参数均为 0,存储模型所占用的内存更少。实际上,虽然 \(L1\) 正则化使模型变得稀疏,却没有降低太多存储内存,所以吴恩达老师认为这并不是 \(L1\) 正则化的目的,至少不是为了压缩模型。所以人们在训练网络时,越来越倾向于使用 \(L2\) 正则化。

在神经网络中实现 \(L2\) 正则化

神经网络含有一个代价函数,该函数包含 \(W^{[1]},b^{[1]}\)\(W^{[L]},b^{[L]}\) 所有参数,其中 \(l\) 表示的是神经网络的层数,\(L\) 为神经网络的总层数。那么,代价函数等于 \(m\) 个训练样本损失函数的均值,正则项为 \(\frac{\lambda}{2m}\sum^L_{l=1}{\left\|W^{[l]}\right\|^2}\)。这个矩阵范数 \(\left\|W^{[l]}\right\|^2\)(即平方范数),被定义为矩阵中所有元素的平方求和,我们称之为“弗罗贝尼乌斯范数 (Frobenius norm)”。
\[ J(W^{[1]},b^{[1]},\dots,W^{[L]},b^{[L]})=\frac{1}{m} \sum^m_{i=1}{L(\hat{y}^{(i)},y^{(i)})}+\frac{\lambda}{2m}\sum^L_{l=1}\left\|W^{[l]}\right\|^2_F\\ \left\|W^{[l]}\right\|^2_F=\sum^{n^{[l-1]}}_{i=1}\sum^{n^{[l]}}_{j=1}(W^{[l]}_{ij})^2 \]
使用弗罗贝尼乌斯范数实现梯度下降

在没有使用弗罗贝尼乌斯范数时,我们是用反向传播计算出 \(dW\) 的值,然后使用它来更新参数 \(W\)
\[ dW^{[l]}=(from \ backprop)\\ W^{[l]}:=W^{[l]}-\alpha dW^{[l]} \]
既然已经增加了正则项,那么我们需要给 \(dW\) 加上一项 \(\frac{\lambda}{m}W^{[l]}\)。然后更新参数。
\[ dW^{[l]}=(from \ backprop)+\frac{\lambda}{m}W^{[l]}\\ W^{[l]}:=W^{[l]}-\alpha dW^{[l]} \]
我们对上面的公式进行一下数学变换。
\[ W^{[l]}:=W^{[l]}-\alpha ((from \ backprop)+\frac{\lambda}{m}W^{[l]})\\ =W^{[l]}-\frac{\alpha \lambda}{m}W^{[l]}-\alpha(from \ backprop)\\ =(1-\frac{\alpha \lambda}{m})W^{[l]}-\alpha(from \ backprop) \]
我们发现 \((1-\frac{\alpha \lambda}{m})\) 这个系数时小于 1 的,所以不论 \(W^{[l]}\) 是什么,我们都试图让它变得更小。所以,\(L2\) 范数正则化也被称为“权重衰减”。

为什么正则化有利于预防过拟合?

直观理解就是正则化参数 \(\lambda\) 增加到足够大,参数 \(W\) 会接近于 0(实际上是不会发生这种情况的)。我们尝试消除或至少减少许多隐藏单元的影响,最终这个网络会变得更简单,这个神经网络越来越接近逻辑回归,也就是从过拟合状态不断接近高偏差状态。但是,\(\lambda\) 会存在一个中间值,于是会有一个接近“Just Right”的中间状态。

而从图形上来看的话,假设我们用的是 tanh 双曲线激活函数。用 \(g(z)\) 表示 \(tanh(z)\)。如果正则化参数 \(\lambda\) 很大,参数 \(W\) 会相对较小,而由于 \(Z^{[l]}=W^{[l]}a^{[l-1]}+b^{[l]}\),所以 \(z\) 也会很小。特别地,如果 \(z\) 的值最终在红色区域这个范围内,都是相对较小的值,\(g(z)\) 大致呈线性。而我们之前说过,如果每层都是线性的,那么整个网络就是一个线性网络,即使是一个非常深的深层网络,因具有线性激活函数的特征,最终我们只能计算线性函数,因此,它不适用于非常复杂的决策,以及过度拟合数据集的非线性决策边界。

Dropout 正则化

除了 \(L2\) 正则化,还有一个非常实用的正则化方法——“Dropout(随机失活)”。

Dropout 正则化的作用原理

假设我们在训练下图这样的神经网络,它存在过拟合。

我们复制这个神经网络,dropout 会遍历网络的每一层,并设置消除神经网络中节点的概率(假设为 0.5)。于是一些节点会被消除,最后我们得到一个节点更少,规模更小的网络,然后用反向传播方法进行训练。

在神经网络中实现 Dropout 正则化

实现 dropout 的最常用的方法是 inverted dropout(反向随机失活)

我们用神经网络的第三层来举例说明。

首先要定义向量 \(d\)\(d^{[3]}\) 表示网络第三层 的 dropout 向量:

d3 = np.random.rand(a3.shape[0],a3.shape[1])

然后看它是否小于某数,我们称之为 keep-prob,它是一个具体的数字。在上面的图里面,它被设为 0.5,现在我们把它设为 0.8。它表示保留某个隐藏单元的概率,即消除任意一个隐藏单元的概率是 0.2。

接下来要做的就是从第三层中获取激活函数,这里我们叫它 \(a^{[3]}\)\(a^{[3]}\) 含有要计算的激活函数。这个 \(a^{[3]}\) 就等于没有 dropout 的 \(a^{[3]}\) 乘以 \(d^{[3]}\)a3 = np.multiply(a3,d3),这里是元素相乘。它的作用就是让 \(d^{[3]}\) 中 0 元素与 \(a^{[3]}\) 中相对元素归零。

顺便一提,如果用 python 实现这个算法的话,我们的 \(d^{[3]}\) 其实是一个布尔型数组,值为 true 和 false,而不是 1 和 0。

由于 \(z^{[4]}=w^{[4]}a^{[3]}+b^{[4]}\),而 \(a^{[3]}\) 被随机减少了 20%。为了不影响 \(z^{[4]}\) 的期望值,我们需要用 \(\frac{w^{[4]}a^{[3]}}{0.8}\) 来修正被随机减少的那 20%。我们在测试阶段是不使用 dropout 函数的,它会使我们的输出结果随机。

当然,我们可以在网络的不同层使用不同的 keep-prob 值进行不同程度的 dropout。如下图所示。注意 keep-prob 的值为 1 表示的是保留所有单元,不在这一层使用 dropout。

dropout 一大缺点就是代价函数 \(J\) 不再被明确定义,每次迭代,都会随机移除一些节点,如果再三检查梯度下降的性能,实际上是很难进行复查的。定义明确的代价函数 \(J\) 每次迭代后都会下降,因为我们所优化的代价函数 \(J\) 实际上并没有明确定义,或者说在某种程度上很难计算,所以我们失去了调试工具来绘制迭代曲线。
吴恩达老师的做法是,关闭 dropout 函数,将 keep-prob 设为 1,运行代码,确保代价函数单调递减。然后再打开 dropout 函数。

理解 Dropout

Dropout 随机删除网络中的神经单元,看起来很奇怪的操作却能实现正则化。

直观上理解,dropout 使得网络不依赖于任何一个特征,因为该单元的输入可能随时被清除,因此该单元通过这种方式传播下去,并为单元的四个输入增加一点权重,通过传播所有权重,dropout 将产生收缩权重的平方范数的效果。

"Can't rely on any one feature, so have to spread out weights."

其他正则化方法

数据扩增 (Data augmentation)

我们可以通过扩增训练数据来解决过拟合,但扩增数据代价高,而且有时候是无法扩增数据的。那么可以通过水平翻转图片、随意裁剪图片等方法来扩增数据,这虽然不如额外收集一组新图片那么好,但这样节省了很高的数据收集成本。

early stopping

运行梯度下降时,我们可以绘制训练误差,或只绘制代价函数 \(J\) 的优化过程,在训练集上用 0-1 记录分类误差次数。

因为在训练过程中,我们希望训练误差,代价函数 \(J\) 都在下降,通过 early stopping,我们不但可以绘制上面这些内容,还可以绘制验证集误差,它可以是验证集上的分类误差,或验证集上的代价函数,逻辑损失和对数损失等。我们发现,验证集误差通常会先呈下降趋势,然后在某个节点处开始上升。而 early stopping 的作用是,在神经网络已经在迭代过程中表现得很好的时候停止训练。

当我们还未在神经网络上运行太多迭代过程的时候,参数 \(w\) 接近 0,因为随机初始化 \(w\) 值时,它的值可能都是较小的随机值,所以在长期训练神经网络之前 \(w\) 依然很小,在迭代过程和训练过程中 \(w\) 的值会变得越来越大,所以 early stopping 要做就是在中间点停止迭代过程,我们得到一个 \(w\) 值中等大小的弗罗贝尼乌斯范数,与\(L2\)正则化相似,选择参数 \(w\) 范数较小的神经网络。

early stopping 的主要缺点就是我们不能独立地处理这两个问题,因为提早停止梯度下降,也就是停止了优化代价函数 \(J\),因为现在不再尝试降低代价函数 \(J\),所以代价函数 \(J\) 的值可能不够小,同时我们又希望不出现过拟合,没有采取不同的方式来解决这两个问题,而是用一种方法同时解决两个问题,这样做的结果是我们要考虑的东西变得更复杂。

early stopping 的优点是,只运行一次梯度下降,就可以找出 \(w\) 的较小值,中间值和较大值,而无需尝试 \(L2\) 正则化超参数 \(\lambda\) 的很多值。

归一化输入 (Normalizing inputs)

训练神经网络,其中一个加速训练的方法就是归一化输入。

假设一个训练集有两个特征,输入特征为 2 维,归一化需要两个步骤:(1)零均值化;(2)归一化方差。

(1)零均值化

\(\mu = \frac{1}{m}\sum_{i =1}^{m}x^{(i)}\),它是一个向量,\(x\) 等于每个训练数据 \(x\) 减去 \(\mu\),意思是移动训练集,直到它完成零均值化。

(2)归一化方差

注意特征 \(x_{1}\) 的方差比特征 \(x_{2}\) 的方差要大得多,我们要做的是给 \(\sigma\) 赋值,\(\sigma^{2}= \frac{1}{m}\sum_{i =1}^{m}{({x^{(i)})}^{2}}\),这是节点 \(y\) 的平方,\(\sigma^{2}\) 是一个向量,它的每个特征都有方差,注意,我们已经完成零值均化,\(({x^{(i)})}^{2}\) 元素 \(y^{2}\) 就是方差,我们把所有数据除以向量 \(\sigma^{2}\),最后变成上图形式。

如果你用它来调整训练数据,那么用相同的 \(μ\)\(\sigma^{2}\) 来归一化测试集。

如果我们使用非归一化的输入特征,代价函数会非常细长狭窄,这样我们必须使用一个非常小的学习率,进行多次迭代,来找到最小值。而归一化特征之后,代价函数会更对称,是一个更圆的球形轮廓。那么不论从哪个位置开始,可以使用较大步长,梯度下降法都能够更直接地找到最小值。

所以如果输入特征处于不同范围内,可能有些特征值从 0 到 1,有些从 1 到 1000,那么归一化特征值就非常重要了。如果特征值处于相似范围内,那么归一化就不是很重要了。

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

训练神经网络,尤其是深度神经所面临的一个问题就是梯度消失或梯度爆炸,也就是训练神经网络的时候,导数或坡度有时会变得非常大,或者非常小,甚至于以指数方式变小,这加大了训练的难度。

对于下图这个很深的神经网络(为了简便,我们只画出两个隐藏单元,实际可以有很多)。

为了简单起见,假设我们使用激活函数 \(g(z)=z\),也就是线性激活函数,我们忽略 \(b\),假设 \(b^{[l]}\)=0,如果那样的话,输出 \(y=W^{[L]}W^{[L -1]}W^{[L - 2]}\ldots W^{[3]}W^{[2]}W^{[1]}x\)。其实我们得到的输出结果是 \(\hat{y}\) 而不是 \(y\)

接着,假设每个权重矩阵 \(W^{[l]}=\begin{bmatrix} 1.5 & 0 \\0 & 1.5 \\\end{bmatrix}\),最后一项有不同的维度,也就是 \(y= W^{[L]}\begin{bmatrix} 1.5 & 0 \\ 0 & 1.5 \\\end{bmatrix}^{(L -1)}x\)。但是我们假设所有的矩阵都等于 1.5 倍的单位矩阵,那么最后的计算结果 \(\hat{y}=1.5^Lx\)。所以,对于一个深度神经网络来说 \(L\) 值较大,那么 \(\hat{y}\) 的值也会非常大,实际上它是呈指数级增长的。

而如果权重是 0.5 倍的单位矩阵,即 \(W^{[l]} = \begin{bmatrix} 0.5& 0 \\ 0 & 0.5 \\ \end{bmatrix}\),那么 \(\hat{y}=0.5^Lx\)。激活函数的值将以指数级下降。

权重初始化

先以单个神经元的情况举例。

单个神经元可能有 4 个输入特征,从 \(x_1\)\(x_4\),经过 \(a=g(z)\) 处理,最终得到 \(\hat{y}\)。这些输入表示为 \(a^{[l]}\),暂时我们用 \(x\) 表示。

我们把 \(b\) 设为 0,那么 \(z=w_1x_1+w_2x_2+\dots+w_nx_n\)。可以发现,\(n\) 越大,即输入特征数越多,\(z\) 的值就越大。为了预防 \(z\) 值过大或过小,我们希望每项值更小,合理的方法是设置 \(w_i=\frac{1}{n}\)

实际上,我们设置每层权重矩阵如下
\[ w^{[l]}=np.random.randn(shape)*np.sqrt(\frac{1}{n^{[l-1]}}) \]
如果用的是 Relu 激活函数,方差设置为 \(\frac{2}{n}\),效果会更好。

如果激活函数的输入特征被零均值和标准方差化,方差是 1,\(z\) 也会调整到相似范围,这就没有解决梯度消失和梯度爆炸问题。但它却是降低了这些问题,因为它给权重矩阵 \(w\) 设置了合理值,它不会比 1 大很多,也不会比 1 小很多,所以梯度没有爆炸或消失过快。

一篇由 Herd 等人撰写的论文曾介绍过。对于几个其它变体函数,如 tanh 激活函数,有篇论文提到,常量 1 比常量 2 的效率更高,对于 tanh 函数来说,它是\(\sqrt{\frac{1}{n^{[l-1]}}}\),它适用于 tanh 激活函数,被称为Xavier初始化。Yoshua Bengio 和他的同事还提出另一种方法,他们使用的是公式\(\sqrt{\frac{2}{n^{[l-1]} + n^{\left[l\right]}}}\)

优化算法 (Optimization algorithms)

机器学习的应用是一个高度依赖经验的过程,伴随着大量的迭代的过程,我们需要训练诸多模型。而优化算法能够帮助我们快速训练模型。

Mini-batch 梯度下降 (Mini-batch gradient descent)

我们之前使用向量化能够让我们有效地对所有 \(m\) 个样本进行计算,只需把所有的训练样本放大巨大的矩阵 \(X\) 中去,\(X=[x^{(1)} x^{(2)} \dots x^{(m)}]\)。同理,\(Y\) 也是这样。但是如果 \(m\) 很大的话,处理速度仍然会很慢。我们必须处理整个训练集,才能迭代一次梯度下降。

那么我们可以把训练集分割为小一点的子集训练,这些子集被取名为 mini-batch。假设每个子集中只有 1000 个样本,那么把其中的 \(x^{(1)}\)\(x^{(1000)}\) 取出来,将其称为第一个子训练集。然后接着取 \(x^{(1001)}\)\(x^{(2000)}\),以此类推。我们把它们记为 \(X^{\{1\}}, X^{\{2\}},\dots\)。如果训练样本一共有 500 万个,每个 mini-batch 都有 1000 个样本,那么我们就有 5000 个 mini-batch,最后得到的是 \(X^{\{5000\}}\)。同样,\(Y\) 也进行这样的处理。从 \(Y^{\{1\}}\)\(Y^{\{5000\}}\)

Mini-batch 梯度下降与我们之前用的梯度下降算法不一样的是,我们每次处理的只是单个 mini-batch \(X^{\{t\}}\)\(Y^{\{t\}}\),而不是同时处理全部的 \(X\)\(Y\)。伪代码如下图。

需要注意的是,上图的代码只是进行了“一代 (1 epoch)”的训练,也就是只是一次遍历了训练集。

理解 mini-batch 梯度下降法

使用 batch 梯度下降法,每次迭代都需要遍历整个训练集,可以预期每次迭代成本都会下降,所以如果代价函数 \(J\) 在某次迭代中增加了,那肯定出 bug 了,可能是由于学习率太大了。

而使用 mini-batch 梯度下降法,我们会发现代价函数 \(J\) 并不是每次迭代都是下降的。那是因为代价函数 \(J^{\{t\}}\) 只和 \(X^{\{t\}},Y^{\{t\}}\) 有关,也就是说每次迭代我们都在训练不同的样本集(不同的 mini-batch)。所以 \(J^{\{t\}}\) 的图像是总体趋势朝下,但是有很多噪声。

对于 mini-batch 梯度下降法来说,我们需要决定的变量之一就是 mini-batch 的大小。

极端情况下,它可以等于训练集的大小,其实就是 batch 梯度下降法。但是它的弊端在于,样本数量巨大的时候,单次迭代耗时太长。如果训练样本不大, batch 梯度下降法运行地很好。

另一种极端情况下,它可以等于 1,也叫作随机梯度下降法。每个样本都是独立的 mini-batch,每次迭代我们都只处理一个样本。随机梯度下降法是有很多噪声的,平均来看,它最终会靠近最小值,不过有时候也会方向错误,因为随机梯度下降法永远不会收敛,而是会一直在最小值附近波动,但它并不会在达到最小值并停留在此。随机梯度下降法的一大缺点是,我们会失去所有向量化带给我们的加速,效率会过于低下。

所以 mini-batch 的大小应该取 1 到 \(m\) 之间的值。如果训练集较小(< 2000),直接使用 batch 梯度下降法,否则使用 mini-batch。一般 mini-batch 大小为 64 到 512,考虑到电脑内存设置和使用的方式,如果 mini-batch 大小是 2 的 n 次方,代码会运行地快一些。

指数加权平均 (Exponentially weighted averages)

在统计中也叫指数加权移动平均。以伦敦的气温为例。

散点看起来有些杂乱,如果要计算趋势的话,也就是温度的局部平均值,或者说移动平均值。我们要做的是,首先使 \(v_0=0\),每天使用 0.9 倍的之前的加权数加上当日温度的 0.1 倍。即 \(v_1=0.9v_0+0.1\theta_1\),得到第一天的温度值。以此类推。

这样,我们得到图中红色的曲线。

把 0.9 这个常数泛化为变量 \(\beta\),那么之前的 0.1 则为 \((1-\beta)\),即 \(v_t=\beta v_{t-1}+(1-\beta)\theta_t\)。在计算时,\(v_t\) 可以看作是 \(\frac{1}{1-\beta}\) 的每日温度。如果 \(\beta\) 是 0.9,这是十天的平均值。而如果 \(\beta\) 为 0.98,\(\frac{1}{1-0.98}=50\),那么可以看作是过去 50 天的温度,这时,我们就可以得到下图中绿色的曲线。

指数加权平均公式在温度变化时,适应地更缓慢一些,所以会出现一定延迟,因为当 \(\beta=0.98\),相当于给前一天的值加了太多权重,只有 0.02 的权重给了当日的值,所以温度变化时,温度上下起伏,当 \(\beta\) 较大时,指数加权平均值适应地更缓慢一些。

我们可以再换一个值试一试,如果 \(\beta\) 是另一个极端值,比如说 0.5,\(\frac{1}{1-0.5}=2\),这是平均了两天的温度。作图运行后得到黄色的曲线。

由于仅平均了两天的温度,平均的数据太少,所以得到的曲线有更多的噪声,有可能出现异常值,但是这个曲线能够更快适应温度变化。

理解指数加权平均

其实核心公式就是
\[ v_t=\beta v_{t-1}+(1-\beta)\theta_t \]
现在我们使 \(\beta\) 等于 0.9,把每个公式具体写出来,倒着写。
\[ v_{100}=0.9v_{99}+0.1\theta_{100}\\ v_{99}=0.9v_{98}+0.1\theta_{99}\\ v_{98}=0.9v_{97}+0.1\theta_{98}\\ \dots \dots \]
那么 \(v_{100}\) 其实可以写作下面这个形式
\[ v_{100} = 0.1\theta_{100} + 0.1 \times 0.9 \theta_{99} + 0.1 \times {(0.9)}^{2}\theta_{98} + 0.1 \times {(0.9)}^{3}\theta_{97} + 0.1 \times {(0.9)}^{4}\theta_{96} + \dots \]
假设我们有一些日期的温度,所以这是数据,\(t\) 为 100,99,98 等等,这就是数日的温度数值。

然后我们构建一个指数衰减函数,从 0.1 开始,到 \(0.1 \times 0.9\),到 \(0.1 \times {(0.9)}^{2}\),以此类推,所以就有了这个指数衰减函数。

计算 \(v_{100}\) 是通过把每日温度与指数衰减函数相乘,然后求和。

所有的这些系数(\((0.1 \times 0.1) \times (0.9 \times 0.1) \times ({(0.9)}^{2} \times 0.1)) \times ({(0.9)}^{3}\times 0.1) \times \dots\)),相加起来为 1 或者逼近 1,我们称之为偏差修正

我们如果实际运行 \(\beta=0.98\),得到的其实不是下图中的绿色曲线而是紫色曲线。而紫色曲线起点较低。

计算移动平均数的时候,初始化 \(v_{0} = 0\)\(v_{1} = 0.98v_{0} +0.02\theta_{1}\),但是 \(v_{0} =0\),所以这部分没有了(\(0.98v_{0}\)),所以 \(v_{1} =0.02\theta_{1}\),所以如果一天温度是 40 华氏度,那么 \(v_{1} = 0.02\theta_{1} =0.02 \times 40 = 8\),因此得到的值会小很多,所以第一天温度的估测不准。同理,第二天也会出现估测不准的情况。

有个办法可以修改这一估测,让估测变得更好,更准确,特别是在估测初期,也就是不用 \(v_{t}\),而是用 \(\frac{v_{t}}{1- \beta^{t}}\)

举个具体例子,当 \(t=2\) 时,\(1 - \beta^{t} = 1 - {0.98}^{2} = 0.0396\),因此对第二天温度的估测变成了 \(\frac{v_{2}}{0.0396} =\frac{0.0196\theta_{1} + 0.02\theta_{2}}{0.0396}\),也就是 \(\theta_{1}\)\(\theta_{2}\) 的加权平均数,并去除了偏差。随着 \(t\) 增加,\(\beta^{t}\) 接近于 0,所以当 \(t\) 很大的时候,偏差修正几乎没有作用,因此当 \(t\) 较大的时候,紫线基本和绿线重合了。

动量梯度下降法 (Gradient descent with Momentum)

动量梯度下降法运行速度几乎总是快于标准的梯度下降算法,简而言之,基本的想法就是计算梯度的指数加权平均数,并利用该梯度更新权重。

假如我们要优化下图所示的代价函数,红点表示最小值的位置。而我们从蓝色点开始梯度下降,无论是 batch 还是 mini-batch,一步一步迭代才会慢慢摆动到最小值。这种上下波动减慢了梯度下降法的速度,而如果使用较大的学习率(紫色箭头),结果可能会偏离函数的范围,为了避免摆动过大,我们要用一个较小的学习率。

使用动量梯度下降法,我们可以在纵轴上,学习慢一点,因为我们不想要这些摆动;而在横轴上,加快学习,快速移向最小值。

我们需要做的是,在每次迭代即第 \(t\) 次迭代的过程中,计算微分 \(dW,db\) 的移动平均数。也就是
\[ v_{dW}=\beta v_{dW}+(1-\beta)dW\\ v_{db}=\beta v_{db}+(1-\beta)db \]
然后重新赋值权重
\[ W:=W-\alpha v_{dW}\\ b:=b-\alpha v_{db} \]
这样平均过程中,纵轴上正负数相互抵消,平均值接近于 0。而横轴上,所有的微分都指向横轴方向,因此横轴方向的平均值仍然较大。

我们一般 \(\beta\) 为 0.9 可以达到不错的效果,当然也可以尝试不同的值。实际中,在使用梯度下降法或动量梯度下降法时,人们不会受到偏差修正的困扰。

RMSprop

RMSprop 算法的全称为 root mean square prop 算法,它也可以加速梯度下降。

还是上面那个例子。我们假设纵轴代表参数 \(b\),横轴代表参数 \(W\),可能有 \(W_1,W_2\) 或者其他重要的参数,为了便于理解,被称为 \(b\)\(W\)

在第 \(t\) 次迭代中,该算法会照常计算当下 mini-batch 的微分 \(dW,db\),保留指数加权平均数,但是用新符号 \(S_{dW}\) 而不是 \(v_{dW}\),因此 \(S_{dW}=\beta S_{dW}+(1-\beta)dW^2\),其中平方是对于整个符号进行平方的。同样地,\(S_{db}=\beta S_{db}+(1-\beta)db^2\)

接着,仍然是更新参数
\[ W:=W-\alpha \frac{dW}{\sqrt{S_{dW}}}\\ b:=b-\alpha \frac{db}{\sqrt{S_{db}}} \]
我们来理解一下其原理。记得在横轴方向或者在例子中的 \(W\) 方向,我们希望学习速度快,而在垂直方向,也就是例子中的 \(b\) 方向,我们希望减缓纵轴上的摆动,所以有了 \(S_{dW}\)\(S_{db}\),我们希望 \(S_{dW}\) 会相对较小,所以我们要除以一个较小的数,而希望 \(S_{db}\) 又较大,所以这里我们要除以较大的数字,这样就可以减缓纵轴上的变化。你看这些微分,垂直方向的要比水平方向的大得多,所以斜率在 \(b\) 方向特别大,所以这些微分中,\(db\) 较大,\(dW\) 较小,因为函数的倾斜程度,在纵轴上,也就是 b 方向上要大于在横轴上,也就是 \(W\) 方向上。\(db\) 的平方较大,所以 \(S_{db}\) 也会较大,而相比之下,\(dW\) 会小一些,亦或 \(dW\) 平方会小一些,因此 \(S_{dW}\) 会小一些,结果就是纵轴上的更新要被一个较大的数相除,就能消除摆动,而水平方向的更新则被较小的数相除。

Adam 优化算法

Adam 优化算法基本上就是将 Momentum 和 RMSprop 结合在一起。Adam代表的是Adaptive Moment Estimation

使用 Adam 算法,首先我们初始化各项参数 ,\(v_{dW} = 0\)\(S_{dW} =0\)\(v_{db} = 0\)\(S_{db} =0\),在第 \(t\) 次迭代中,计算微分 \(dW,db\),一般我们会用 mini-batch 梯度下降法。

接下来计算 Momentum 指数加权平均数,这里我们为了不混淆两个算法中的参数,Momentum 中使用 \(\beta_1\),RMSprop 中使用 \(\beta_2\)。 即
\[ v_{dW}= \beta_{1}v_{dW} + ( 1 - \beta_{1})dW\\ v_{db}= \beta_{1}v_{db} + ( 1 - \beta_{1})db \]
然后,使用 RMSprop 进行更新,即
\[ S_{dW}=\beta_2 S_{dW}+(1-\beta_2)(dW)^2\\ S_{db}=\beta_2 S_{db}+(1-\beta_2)(db)^2 \]
一般使用 Adam 算法的时候,要计算偏差修正,即
\[ v_{dW}^{corrected}=\frac{v_{dW}}{1-\beta_1^t}\\ v_{db}^{corrected}=\frac{v_{db}}{1-\beta_1^t}\\ S_{dW}^{corrected}=\frac{S_{dW}}{1-\beta_1^t}\\ S_{db}^{corrected}=\frac{S_{db}}{1-\beta_1^t} \]
最后,更新权重。由于我们要确保我们的算法不会除以 0,而如果 \(S_dW\) 的平方根趋近于 0 的话,就会导致我们的结果非常大。为了确保数值稳定,在实际操作中,我们要在分母上加上一个很小很小的 \(\epsilon\),一般我们设为 \(10^{-8}\)。即
\[ W:=W-\frac{\alpha v_{dW}^{corrected}}{\sqrt{S_{dW}^{corrected}}+\epsilon}\\ b:=b-\frac{\alpha v_{db}^{corrected}}{\sqrt{S_{db}^{corrected}}+\epsilon} \]
所以 Adam 算法结合了两个算法,是一种极其常用的学习算法,被证明能有效适用于不同神经网络,适用于广泛的结构。

关于这个算法,它其实有很多超参数。

学习率 \(\alpha\) 很重要,经常需要调试。\(\beta_1\) 常用的缺省值为 0.9。\(\beta_2\) 的值,Adam 的作者推荐使用 0.999;而 \(\epsilon\) 一般也建议使用 \(10^{-8}\)

学习率衰减 (Learning rate decay)

加快学习算法的一个办法就是随时间慢慢减少学习率,我们将之称为学习率衰减。

假设我们使用 mini-batch 梯度下降法,mini-batch 的数量不大,在迭代过程中会有噪音(下图中蓝色的线),下降朝向最小值。但是不会精确地收敛,所以算法最后在附近摆动,因为我们用的 \(\alpha\) 是固定值,不同的 mini-batch 中有噪音。

但是如果我们慢慢减小学习率 \(\alpha\) 的话,在初期的时候,学习率还较大,学习还是相对较快;随着学习率变小,学习的步伐也会变慢变小,最后曲线会在最小值附近的一小块区域里摆动(上图中绿色的线),而不是在训练过程中,大幅度在最小值附近摆动。

具体做法可以这样实现。我们将学习率 \(\alpha\) 设为 \(\alpha=\frac{1}{1+decay\_rate*epoch\_num}\alpha_0\),其中 decay_rate 为衰减率,epoch_num 为迭代代数,\(\alpha_0\) 为初始学习率。衰减率 decay_rate 其实是一个我们需要调整的超参数。

当然,还有其他人们会用的公式。如下。
\[ \alpha=0.95^{epoch\_num}\alpha_0\\ \alpha=\frac{k}{\sqrt{epoch\_num}}\alpha_0\\ \alpha=\frac{k}{\sqrt{t}}\alpha_0 \]
有时也会用一个离散下降的学习率,也就是某个步骤有某个学习率,一会之后,学习率减少了一半,一会儿减少一半,一会儿又一半,这就是离散下降(discrete stair cease)的意思。还有时候,人们也会手动控制 \(\alpha\),但这只有模型数量小的时候有用。

Batch 归一化 (Batch Norm)

Batch 归一化算法由 Sergey Loffe 和 Christian Szegedy 创造,简称为 BN,它会使参数搜索问题变得很容易,使神经网络对超参数的选择更加稳定,超参数的范围会更加庞大,工作效果也更好。

当训练一个模型时,我们曾经使用过归一化输入特征加快学习过程。而对于更深的模型,我们不禁有输入特征值 \(x\),还有各个层的激活值 \(a^{[1]},a^{[2]}\) 等等。那么归一化这些激活值也会使我们的训练更有效率。严格来说,Batch 归一化的不是激活值 \(a^{[l]}\) 而是 \(z^{[l]}\)

现在我们以第 \(l\) 层的归一化为例,在符号中省略上标 \(^{[l]}\)。于是我们有这一层隐藏单元的值,从 \(z^{(1)}\)\(z^{(m)}\),于是我们用下面的公式使每个 \(z^{(i)}\) 值规范化。为了使数值稳定,通常会在分母中加上 \(\epsilon\),以防 \(\sigma=0\)
\[ \mu=\frac{1}{m}\sum_{i=1}^mz^{(i)}\\ \sigma^2=\frac{1}{m}\sum_{i=1}^m(z^{(i)}-\mu)\\ z^{(i)}_{norm}=\frac{z^{(i)}-\mu}{\sqrt{\sigma^2+\epsilon}} \]
这样我们就把这些 \(z\) 值标准化,化为含平均值 0 和标准单位方差。但是我们不想让隐藏单元总是含有平均值 0 和方差 1,也许隐藏单元有了不同的分布会有意义,所以我们还要计算一个变量 \({\tilde{z}}^{(i)}= \gamma z_{\text{norm}}^{(i)} +\beta\)。其中 \(\gamma\)\(\beta\) 是我们需要学习的参数(这里的 \(\beta\) 与 Adam 等优化算法中的参数 \(\beta\) 不是同一个),正如更新权重一样,使用梯度下降或者优化算法,我们也会更新这两个参数。

通过赋予 \(\gamma\)\(\beta\) 其他值,我们可以构造含其他平均值和方差的隐藏单元值。如果 \(\gamma=\sqrt{\sigma^2+\epsilon}\)\(\beta=\mu\),那么\(\tilde{z}^{(i)}=z^{(i)}\)

一般来说,如果使用深度学习编程框架,一般一行代码就能实现 BN。比如在 TensorFlow 框架中,我们可以用函数 tf.nn.batch_normalization 来实现。

Softmax 回归

如果我们要进行多类型的预测的话,我们就需要用到 softmax 回归。

以下面这个例子进行说明。

于是为了分出上图中的四类,我们将建立一个神经网络,其输出层有 4 个或者说 \(C\) 个输出单元。

我们想要的是,输出层单元的数字告诉我们这 4 种类型中每个的概率有多大,因此这里的 \(\hat{y}\) 将是一个 \(4\times1\) 维向量,而且输出的四个数字加起来应该等于 1。

在神经网络的最后一层,我们将会像往常一样计算各层的线性部分,也就是 \(z^{[L]}=W^{[L]}a^{[L-1]}+b^{[L]}\)。然后我们应用 softmax 激活函数。首先,计算一个临时变量 \(t\),它等于 \(e^{z^{[L]}}\),也就是对所有元素求幂,然后对这个变量 \(t\) 进行归一化来输出 \(a^{[L]}\),也就是 \(a^{[L]}=\frac{t}{\sum_{i=1}^Ct_i}\)

以一个具体的例子来说,就是,假如我们算出的 \(z^{[L]}\) 的值如下,那么计算过程也就如下所示。
\[ z^{[L]}= \begin{bmatrix} 5 \\ 2 \\ - 1 \\ 3 \\ \end{bmatrix}\\ t =e^{z^{[L]}}=\begin{bmatrix} e^{5} \\ e^{2} \\ e^{- 1} \\ e^{3} \\ \end{bmatrix}=\begin{bmatrix} 148.4 \\ 7.4 \\ 0.4 \\ 20.1 \\ \end{bmatrix}\\ a^{[L]}=\frac{t}{\sum_{i=1}^4t_i}=\begin{bmatrix} 0.842 \\ 0.042 \\ 0.002 \\ 0.114 \\ \end{bmatrix} \]
softmax 这个名词的来源是与所谓 hardmax 对比而来的。hardmax 会把向量 \(z\) 变成 \(\begin{bmatrix} 1 \\ 0 \\ 0 \\ 0 \\ \end{bmatrix}\) 这种形式的向量。

假如说对于某个样本的目标输出,真实标签是 \(\begin{bmatrix} 0 \\ 1 \\ 0 \\ 0 \\ \end{bmatrix}\),但是输出的 \(\hat{y}=\begin{bmatrix} 0.3 \\ 0.2\\0.1\\0.4\end{bmatrix}\)。说明这个样本,神经网络的表现不佳。在 softmax 分类中,一般用到的损失函数是 \(L(\hat{y},y)=-\sum_{j=1}^Cy_j\log{\hat{y}_j}\)。用刚刚说的具体的数据代入的话,如下所示
\[ L(\hat{y},y)=-\sum_{j=1}^4y_j\log{\hat{y}_j}=-y_2\log{\hat{y}_2}=-\log{\hat{y}_2} \]
而我们试图不断使这个损失函数变小,也就需要使 \(\hat{y}_2\) 尽可能大。

对于整个训练集的代价函数 \(J\) 来说,仍然是每个样本的损失的均值。
\[ J(W^{[1]},b^{[1]},\dots)=\frac{1}{m}\sum_{i=1}^mL(\hat{y}^{(i)},y^{(i)}) \]

References

[1] Coursera深度学习教程中文笔记

[2] 深度学习 500 问

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章