《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 ight|_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 ight|^2_2 ]

至于为什么只正则化参数 (w),而不再加上参数 (b) 呢?可以,但没必要。因为 (w) 通常是一个高纬度参数矢量,已经可以表达高偏差问题,(w) 可能包含有很多参数,而我们不可能拟合所有参数,(b) 只是单个数字,所以 (w) 几乎涵盖所有参数。如果加了参数 (b),其实也没太大影响。

(L2) 正则化是最常见的正则化类型,其实也有 (L1) 正则化。它加的不是 (L2) 范数,而是正则项 (frac{lambda}{m}) 乘以 (sum^{n_x}_{j=1}left|w ight|)。其中,(sum^{n_x}_{j=1}left|w ight|) 也被称为参数 (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 ight| ]

如果用的是 (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]} ight|^2})。这个矩阵范数 (left|W^{[l]} ight|^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]} ight|^2_F\ left|W^{[l]} ight|^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]}=egin{bmatrix} 1.5 & 0 \0 & 1.5 \end{bmatrix}),最后一项有不同的维度,也就是 (y= W^{[L]}egin{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]} = egin{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 ight]}}})

优化算法 (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 heta_1),得到第一天的温度值。以此类推。

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

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

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

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

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

理解指数加权平均

其实核心公式就是

[v_t=eta v_{t-1}+(1-eta) heta_t ]

现在我们使 (eta) 等于 0.9,把每个公式具体写出来,倒着写。

[v_{100}=0.9v_{99}+0.1 heta_{100}\ v_{99}=0.9v_{98}+0.1 heta_{99}\ v_{98}=0.9v_{97}+0.1 heta_{98}\ dots dots ]

那么 (v_{100}) 其实可以写作下面这个形式

[v_{100} = 0.1 heta_{100} + 0.1 imes 0.9 heta_{99} + 0.1 imes {(0.9)}^{2} heta_{98} + 0.1 imes {(0.9)}^{3} heta_{97} + 0.1 imes {(0.9)}^{4} heta_{96} + dots ]

假设我们有一些日期的温度,所以这是数据,(t) 为 100,99,98 等等,这就是数日的温度数值。

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

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

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

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

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

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

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

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

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

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

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

我们需要做的是,在每次迭代即第 (t) 次迭代的过程中,计算微分 (dW,db) 的移动平均数。也就是

[v_{dW}=eta v_{dW}+(1-eta)dW\ v_{db}=eta v_{db}+(1-eta)db ]

然后重新赋值权重

[W:=W-alpha v_{dW}\ b:=b-alpha v_{db} ]

这样平均过程中,纵轴上正负数相互抵消,平均值接近于 0。而横轴上,所有的微分都指向横轴方向,因此横轴方向的平均值仍然较大。

我们一般 (eta) 为 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}=eta S_{dW}+(1-eta)dW^2),其中平方是对于整个符号进行平方的。同样地,(S_{db}=eta S_{db}+(1-eta)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 中使用 (eta_1),RMSprop 中使用 (eta_2)。 即

[v_{dW}= eta_{1}v_{dW} + ( 1 - eta_{1})dW\ v_{db}= eta_{1}v_{db} + ( 1 - eta_{1})db ]

然后,使用 RMSprop 进行更新,即

[S_{dW}=eta_2 S_{dW}+(1-eta_2)(dW)^2\ S_{db}=eta_2 S_{db}+(1-eta_2)(db)^2 ]

一般使用 Adam 算法的时候,要计算偏差修正,即

[v_{dW}^{corrected}=frac{v_{dW}}{1-eta_1^t}\ v_{db}^{corrected}=frac{v_{db}}{1-eta_1^t}\ S_{dW}^{corrected}=frac{S_{dW}}{1-eta_1^t}\ S_{db}^{corrected}=frac{S_{db}}{1-eta_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) 很重要,经常需要调试。(eta_1) 常用的缺省值为 0.9。(eta_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,也许隐藏单元有了不同的分布会有意义,所以我们还要计算一个变量 ({ ilde{z}}^{(i)}= gamma z_{ ext{norm}}^{(i)} +eta)。其中 (gamma)(eta) 是我们需要学习的参数(这里的 (eta) 与 Adam 等优化算法中的参数 (eta) 不是同一个),正如更新权重一样,使用梯度下降或者优化算法,我们也会更新这两个参数。

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

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

Softmax 回归

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

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

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

我们想要的是,输出层单元的数字告诉我们这 4 种类型中每个的概率有多大,因此这里的 (hat{y}) 将是一个 (4 imes1) 维向量,而且输出的四个数字加起来应该等于 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]}= egin{bmatrix} 5 \ 2 \ - 1 \ 3 \ end{bmatrix}\ t =e^{z^{[L]}}=egin{bmatrix} e^{5} \ e^{2} \ e^{- 1} \ e^{3} \ end{bmatrix}=egin{bmatrix} 148.4 \ 7.4 \ 0.4 \ 20.1 \ end{bmatrix}\ a^{[L]}=frac{t}{sum_{i=1}^4t_i}=egin{bmatrix} 0.842 \ 0.042 \ 0.002 \ 0.114 \ end{bmatrix} ]

softmax 这个名词的来源是与所谓 hardmax 对比而来的。hardmax 会把向量 (z) 变成 (egin{bmatrix} 1 \ 0 \ 0 \ 0 \ end{bmatrix}) 这种形式的向量。

假如说对于某个样本的目标输出,真实标签是 (egin{bmatrix} 0 \ 1 \ 0 \ 0 \ end{bmatrix}),但是输出的 (hat{y}=egin{bmatrix} 0.3 \ 0.2\0.1\0.4end{bmatrix})。说明这个样本,神经网络的表现不佳。在 softmax 分类中,一般用到的损失函数是 (L(hat{y},y)=-sum_{j=1}^Cy_jlog{hat{y}_j})。用刚刚说的具体的数据代入的话,如下所示

[L(hat{y},y)=-sum_{j=1}^4y_jlog{hat{y}_j}=-y_2log{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 问

原文地址:https://www.cnblogs.com/IvyWong/p/12035648.html