deeplearning.ai 的第二个课程名为 改进深度神经网络:超参数调整,正则化和优化 (Improving Deep Neural Networks: Hyperparameter tuning, Regularization and Optimization) ,这门课程教你使深度学习表现更好的“魔法”,而不是把神经网络当作一个黑箱,你会理解什么使得神经网络表现更好,而且能更系统地得到好的结果,你还会学到一些 tensorflow 知识。通过三周的学习,你将能够:
- 了解构建深度学习应用程序的行业最佳实践
- 能够有效地使用常见的神经网络技巧,包括初始化,L2 正则化和丢失正则化,批量归一化,梯度检查
- 能够实现和应用各种优化算法,例如小批量梯度下降,动量,PMSprop 和 Adam,并检查它们的收敛性
- 了解如何设置训练/开发/测试集和分析偏差/方差
- 能够在 TensorFlow 上实现神经网络
配置你的机器学习应用
训练/开发/测试集 (Train/Dev/Test sets)
假设我们有一组数据,我们将这组数据分成 训练集 train sets 和 开发集 dev sets(有时称作 hold-out 交叉验证集)和 测试集 test sets。
训练集用来训练模型,开发集用来评估不同超参数下的模型哪一个在开发集上效果最好,测试集用来评估最终的训练效果。
它们在一组数据中的比例是:
- 在“前机器学习时代”即小数据时代:训练集/测试集 = 70%/30%;训练集/开发集/测试集 = 60%/20%/20%,当数据为10000以下时,这是被认为最好的分法
- 在大数据时代:假设有一百万个样本,开发集只需要一万个即可,测试集也只需要一万个便可评估模型性能,所以此时的比例为 训练/开发/测试 = 98%/1%/1%
对于训练集和测试集的数据分布不匹配的问题,例如训练集来自网上爬取的精美图片,而训练集和开发集来自用户上传的模糊图片,这种情况的经验法则是:确保开发集和测试集的数据分布相同。
当不需要无偏估计时,可以没有测试集,这个时候开发集有时被称为“测试集”。
偏差 (bias) 和方差 (variance)
- 第一幅图中的分类器发生了欠拟合现象,我们称这个模型为“高偏差”
- 第三幅图发生了过拟合现象,我们称这个模型为“高方差”
假设训练一个识别猫的分类器,有以下四种结果:
- 第一个分类器训练集误差 1%,开发集 11%,则说明训练集拟合得较好,但是模型的泛化能力不行,也就是高方差,有可能发生过拟合现象
- 第二个分类器训练集误差 15%,开发集 16%,说明训练集拟合得不是很好,但是开发集误差与训练集相近,说明泛化能力较好,模型具有高偏差,有可能发生欠拟合现象
- 第三个分类器不仅训练集误差较大,而且开发集误差与训练集误差差距很多,说明此时模型既有高偏差,又有高方差,是最差的情况
- 第四个分类器训练集误差小,开发集误差也很小,此时的模型是低偏差、低方差,是最好的情况
这种分析方法基于人类识别出猫的误差约为零,如果图片模糊,连真人都无法识别出来,则理想误差(贝叶斯误差)就会非常高
用一张图片更直观地展现“高偏差,高方差”的情况:
一个好的分类器如虚线所示,作为直线分类器具有高偏差,但是紫色线表示的分类器不仅大部分是直线,而且在途中扭曲地绕过了两个错误的样本,这样使得它具有了高方差,所以这个分类器既具有高方差又有高偏差。
机器学习基本准测
在深度学习之前的时代中我们能用的工具不是很多,我们没有太多那种能够单独减小偏差或单独减小方差而不顾此失彼的工具。但在当前这个深度学习和大数据的时代,只要你能不断扩大所训练的网络的规模或不断获得更多数据,那扩大网络几乎总是能够减小偏差而不增大方差,而获得更多数据(用恰当的方式正则化)几乎总是能够减小方差而不增大偏差。有了这两步,再加上能够选取不同的网络来训练,我们就有了能够单独削减偏差或单独削减方差而不会过多影响另一个指标的能力,这样就不需小心地平衡两者。它能够解释为何深度学习在监督学习中如此有用以及为何在深度学习中,偏差与方差的权衡要不明显得多。
神经网络的正则化 (regularization)
如果你怀疑你的神经网络在数据上发生了过拟合,也就是存在高方差问题,需要首先尝试使用正则化,虽然获取更多数据也是解决高方差问题的一个很可靠的方法,但你并不是总能获取到更多的训练数据或者获取更多数据的代价太大,但使用正则化通常有助于防止过拟合并降低网络的误差。
L2 正则化
什么是 L2 正则化
假如考虑最简单的逻辑回归模型,我们的优化目标是找到参数 $w,b$ 使得代价函数 $J(w,b)$ 最小,其中 $w \in \mathbb{R^{n_x}} , b \in \mathbb{R},$
当发生过拟合问题时,我们对上式引入正则化项,L2 正则化则是使用 L2 范数:
- $\lambda$ 称之为正则化参数,是一个需要调优的超参数,通常在开发集上配置这个参数,尝试一系列的值找出最好的那个,lambda 是 python 保留的关键字,在编程中我们把它写为“lambd”,避免和保留关键字冲突
- $||w||_2$ 为 L2 范数 (欧几里德范数),且 $||w||_2^2=\sum\limits_{j=1}^{n_x}w_j^2=w^Tw$
- 我们通常省略 b 的相关项 $\frac{\lambda}{2m}b^2$,因为 w 往往是一个非常高维的参数矢量,几乎所有的参数都集中在 w 中,而 b 只是大量参数中的一个参数,即使加上了 b 的相关项也不会起到太大作用
- L1 正则化使用的是 L1 范数,正则化项为 $\frac{\lambda}{2m}\sum\limits_{j=1}^{n_x}|w_j|=\frac{\lambda}{2m}||w||_1$,使用 L1 正则化会使得模型变得“稀疏”,这意味着 w 中有很多 0,吴恩达认为 L1 并没有压缩模型的作用且 L2 正则化用的更多
对于 L 层的神经网络来说,正则化项为各层范数之和:
- 其中:$||W^{[l]}||^2_F=\sum\limits_{i=1}^{n^{[l-1]}}\sum\limits_{j=1}^{n^{[l]}}(W_{ij}^{[l]})^2,W:(n^{[l]},n^{[l-1]})$,即 $W$ 矩阵的每个元素的平方求和,这个矩阵的范数,称为矩阵的费罗贝尼乌斯范数 (注意,这不叫矩阵的 L2 范数),使用角标 F表示
- 式子最后加的正则化项也叫“惩罚项”,用来防止权重过大
那么该如何使用 L2 正则化呢,我们可以证明,在代价函数添加正则项之后,其对 $W^{[l]}$ 的偏导 $\frac{\partial J}{\partial W^{[l]}}=dW^{[l]}$ 也要添加一项:
然后更新参数即可:
我们可以看到矩阵 $W^{[l]}$前多了一个略小于1的系数,也就是说无论 $W$ 是多少,都让它变小一点,由于这个原因,L2 正则化有时被称为“权重衰减”
为什么 L2 正则化可以减小过拟合
例子1
假如目前发生了过拟合,是第三种情况,根据正则化公式:
假如我们把正则化参数 $\lambda$ 调得足够大,使得 $W$ 变得很小,接近于0,这样会使得隐藏单元的影响被消除了,因为如果权重是零,那么这个单元便可有可无,那么这个大的神经网络就被简化为一个很小的神经网络,如上图左上角红框框起来的部分,这种情况与逻辑回归单元很类似 (只是深度变深)。而逻辑回归正是图中第一种高偏差的情况,于是上述操作使得我们将模型从第三种过拟合的情况变成第一种高偏差的情况!如果我们找到一个合适的中间值 $\lambda$,那么就可以将过拟合状态变成中间那种“刚刚好”的情况!
例子2
对于上图的 tanh 激活函数,中间的一部分(红色的)接近于线性函数,如果 $\lambda$ 增大,则 $W^{[l]}$减小,由于$Z^{[l]}=W^{[l]}A^{[l-1]}+b^{[l]}$,则$Z^{[l]}$减小,更接近于在中间简单的线性部分取值,那么这个函数呈现相对线性,从而使神经网络只能计算一些离线性函数很近的相对简单的值,不能计算复杂的非线性函数,因此不太容易发生过拟合
我的理解是,线性函数可以类比为某种“直脑筋”,直来直去,大大咧咧,所以容易产生高偏差,而非线性我类比为那种想得很多的人,过度思虑,所以经常会把一些噪声学进模型里,造成过度的拟合,正如太过焦虑某个事情容易走火入魔,L2 正则化大概是往这种“过度思虑”的心态里加上一些更加坦率更加直接的力量来矫正它吧 :)
丢弃正则化 (Dropout Regularization )
什么是丢弃正则化
假设下图所示的神经网络发生过拟合:
使用随机失活技术来处理,首先为丢弃网络中的某个节点设置一个概率值,假设为 50%,遍历这个网络的每一层,对每一层的每一个结点作一次公平投币,使得这个结点有 50% 的几率被保留,50% 的几率被丢弃(取值为0),丢弃完这些结点后,我们得到一个小得多的网络,再进行反向传播,如下图所示:
丢弃正则化的一种实现——反向随机失活 (inverted dropout)
假设在神经网络的第 3 层上,即 $l=3$,首先设置一个向量 d3,它表示第 3 层的失活向量,它和 a3 的形状一样:
- np.random.randn() 产生的随机数介于 [0,1) 之间
- keep.prob 是某个结点被保留的概率,小于它的值都取 1,反之取 0,也就是说上面这条语句产生了一个维度是 (a3.shape[0],a3.shape[1]) 的只包含 0 或者 1 的矩阵,某个元素取 1 的概率是 keep.prob,这是一个筛选矩阵
接着我们用这个筛选矩阵来将 a3 随机失活:
- np.multiply() 是逐元素相乘
- 如果 a3 某个元素乘到了 d3 中对应的某个刚好是 1 的元素(概率为 keep.prob),那么这个值保留,如果乘到了刚好是 0 的元素(概率为 1-keep.prob),则这个值清零,即这个单元失活。也就是说: 每个结点都有 keep.prob 的概率被保留,1-keep.prob 的概率被失活
接着我们将 a3 除以保留概率 keep.prob:
- 假设 a3 有 50 个单元,keep.prob 为 0.8,则意味着平均会有 50×0.2=10 个单元失活清零,那么$z^{[4]}=w^{[4]}a^{[3]}+b^{[4]}$也会减少约 20%,所以为了不减少 z4 的期望值,我们需要除以 0.8,提供大约 20% 的校正值
注意:不要在预测使用随机失活算法!因为我们不想我们预测值的输出也是随机的,这样做只会给预测带来噪声!
为什么 dropout 正则化有效?
一个直觉:不能依赖任何一个特征,所以必须分散权重。
对于上面这个神经元来说,如果使用 dropout,这些输入会被随机丢弃,这就意味着,它不能依赖于任何一个特征,因为每一个都有可能被随机丢弃,或者说每一个输入都有可能随机失活,所以在特定的时候,就不愿把所有的赌注或权重只放在某一个输入上,因此这个神经元会更积极地使用这种方式对于每个输入给一个较小的权重。泛化这些权值有利于压缩这些权重的平方和(平方范数)。
就和 L2 正则化一样,使用 dropout 有助于收缩权值,防止过拟合。
使用 dropout 的注意事项
对于上图中的神经网络,每一层的权重矩阵维度都不一样,例如第一层是 (7,3),第二层是 (7,7),第三层是 (3,7),我们可以对每一层设置不同的留存率 (keep.prob),由于第二层有最大的 (7,7) 权值矩阵,参数最多,这一层最容易发生过拟合,于是我们可以在这一层设置最低的留存率,比如 0.5,而对于不那么担心会发生过拟合的层,例如权重矩阵较小的第三层,我们可以设为 0.7,而第四第五层我们完全不担心会发生过拟合,可以将留存率设为 1.0。另外在实际中,不对输入层进行随机失活。
最早对dropout技术的成功应用,是在计算机视觉领域,在这个领域中,输入层向量维度非常大,因为要包含每个像素点的值,几乎不可能有足够的数据,因此 dropout 在计算机视觉领域使用非常频繁,几乎已经成为一种默认了,但是除非算法已经过拟合了,所以不需考虑使用 dropout,所以相对计算机视觉领域,它在其他应用领域使用会少一些。
dropout 的一个缺点:代价函数变得不是那么明确,因为每次都有结点随机失活,所以代价函数定义不明确,也就是说你看不出它随着迭代的变化曲线,不能使用绘图的方法进行调参,这个时候可以先关闭 dropout,把留存率都设为 1,确保代价函数是单调递减的再打开 dropout。
其他正则化方法
数据集扩增 (data augmentation)
增加数据的方法:
- 图片水平翻转,可以使数据量翻倍
- 随机裁剪图片
- 数字的随机旋转或者变形
增加数据集可以做出额外的伪训练样本,但这些额外的伪训练样本能增加的信息量不如全新的、独立的猫照片多,但因为这么做只有一些计算代价,所以这是一个获得更多数据的廉价方式,因此可以算作正则化,减少了过拟合。
早终止法 (early stopping)
假设某个模型的代价函数随迭代次数在训练集上的变化(蓝线)和开发集误差(紫线)如上图所示,代价函数一直下降,而开发集误差先下降一段,然后开始增大,在最低点的那次迭代附近,神经网络表现最好,早终止法指的是,在这里把神经网络的训练停住,并选取这个最小开发集误差对应的参数值。
为什么有效?在迭代初期 w 很小,迭代后期 w 很大,正如 L2 正则化一样,通过停在半路,我们得到一个不大不小的 w 值,就能减小过拟合且不至于偏差太大了。
缺点:如果停止了梯度下降,意味着打断了优化代价函数 J 的过程,所以使得在降低代价函数这件事上做得就不够好,但同时你又想做到避免过拟合,也就是想用一个方法来干降低代价函数和减少过拟合这两件事情,而没有用不同方法来解决这两个问题,使问题更加复杂了。
优点:不用尝试大量的正则化参数 $\lambda$ 的值,只要运行一次梯度下降过程。
问题的优化 (optimization )
训练集的归一化 (Normalization)
什么是归一化
假设某个训练集输入只有两个维度,$x=(x_1,x_2)^T$,如下图所示:
将输入归一化有两个步骤:
将均值归零
$均值\mu = \frac{1}{m}\sum\limits ^m_{i=1}x^{(i)}\\x:=x- \mu $
于是训练集变成下图的样子,使样本点分布在原点周围:
我们可以注意到特征 $x_1$的方差比 $x_2$的方差大,即更加分散,于是我们进行第二步。
将方差归一化
$方差\sigma^2=\frac{1}{m} \sum\limits ^m_{i=1}(x^{(i)} )^2\\x=\frac{x}{\sqrt{\sigma^2}}$
注意:第一个式子本来应该是 $\sigma^2=\frac{1}{m} \sum\limits ^m_{i=1}(x^{(i)} - \mu)$,但是在第一步中 $x$ 已经减了 $\mu$,所以此处不需要
于是训练集变成下面图里的样子:
值得注意的是:当使用归一化对数据进行缩放时,训练集和测试集应该使用相同的 $\mu$ 和 $\sigma^2$ ,也就是从训练集计算出来的 $\mu$ 和 $ \sigma^2$
为什么要对数据归一化
假设 $x_1$ 的取值介于 1~10000,而 $x_2$ 的取值介于 0~1,那么会导致参数 w1 和 w2 的取值范围有很大不同,那么代价函数就如下图所示(两根轴用 w 和 b 代替):
这个代价函数就像一个“扁平的碗”,它的等高线就是一个长长的椭圆,那么梯度下降必须采用很小的步长,经历许多步,反复辗转才能下降到最小值,而经过归一化的代价函数如下图所示:
代价函数变成了一个更倾向正球体的图形,等高线也趋近于圆形,那么梯度下降能直接朝着最小值而去,而且采用更长的步长。
归一化适用于输入特征的尺寸非常不同的情况, 如果输入特征本来尺度就相近,那么这一步就不那么重要,不过因为归一化的步骤几乎从来没有任何害处,所以可以总是进行归一化。
梯度消失和梯度爆炸 (vanishing / exploding gradients)
梯度消失或爆炸指的是,当你在训练一个深度神经网络的时候,损失函数的导数或者说斜率,有时会变得非常大或者非常小甚至是呈指数级减小的一种现象。
为什么会发生梯度消失或梯度爆炸
有一个非常深的神经网络如下图所示:
假设激活函数为线性函数,即 $g(z)=z$,且 $b^{[l]}=0$,那么我们可以求出最终的预测值:$\hat y=W^{[1]}W^{[2]}W^{[3]}…W^{[L-1]}W^{[L]}X$,假设每个权重矩阵只比单位矩阵大一点点,例如 $W^{[l]}=\begin{bmatrix}
1.5 & 0\\
0 & 1.5
\end{bmatrix}$,那么 $\hat y = W^{[L]}{\begin{bmatrix}
1.5 & 0\\
0 & 1.5
\end{bmatrix}}^{L-1}X$,如果 L 很大即层数很深,那么激活函数值会呈指数级增长,最后的预测值发生爆炸。反之,如果每个权重矩阵只比单位矩阵小一点点,那么激活函数值会呈指数级减少,最后的预测值可能消失。同理,反向传播中的梯度也会在层数很大时指数级增长或者减少,这样会使得梯度下降变得非常非常慢。
如何改善梯度消失或爆炸——参数初始化
对于一个单神经元,如果我们忽略参数 b,那么 $z=w_1x_1+w_2x_2+…+w_nx_n$,我们不想让 z 变大或变小,如果 n 越大,那么 $w_i$ 就要越小,因为 z 是 n 个 $w_ix_i$ 的和,而 x 是不变的。一个解决办法是用方差为 $\frac{k}{n}$ 的正态分布初始化参数 w,其中 k 值取决于激活函数:
对于 tanh 函数的 “Xavier 初始化”:
- np.sqrt() 是所有元素开平方,* 是逐元素相乘
对于 ReLU 函数有 “He 初始化”:
- 另一种变体:
梯度检查 (gradient cheking)
梯度检查是检查反向传播中计算的梯度是否正确并找出错误的方法,使用梯度检查可以节省时间,调试代码,检验反向传播算法。
梯度检查的公式
我们用一个近似公式来计算代价函数对参数的梯度:
- 其中 $\varepsilon$ 是一个很小的数
- 这种双向导数公式比单向更精确
接下来:
把 $W^{[1]},b^{[1]},W^{[2]},b^{[2]},…,W^{[L]},b^{[L]}$ reshape 成一个大向量 $\theta$
把 $dW^{[1]},db^{[1]},dW^{[2]},db^{[2]},…,dW^{[L]},db^{[L]}$ reshape 成一个大向量 $d\theta$
梯度检查算法:
$for \quad each \quad i:\\ \quad \quad \ d\theta_{approx}{[i]}=\frac{J(\theta_1,\theta_2,…,\theta_i+\varepsilon,…)-J(\theta_1,\theta_2,…,\theta_i-\varepsilon,…)}{2\varepsilon}\\ \hspace{3.5cm} \approx d\theta[i]=\frac{\partial J}{\partial \theta_i}$
- 其中 $d\theta_{approx}[i]$ 指的用上面的公式计算的值,$d\theta[i]$ 指的是程序计算出的值
- $\theta[i]$ 指的是第 i 层的参数
检查 $d\theta_{approx}[i]$ 和 $d\theta[i]$ 是否相等,用下式来评估它们之间的差距:
- 其中分子为两个向量的欧几里德距离,值为两个向量的每个分量的差的平方之和再开方,分母为两个向量的欧几里德长度(L1 范数)之和
- 若 $\varepsilon=10^{-7}$,则可认为误差很小, 若 $\varepsilon$ 在 $10^{-5}$ 量级,则可能需要再三检查这个向量的每个分量,若在 $10^{-3}$ 量级,则很可能有错误,仔细检查每个部分,总之这个数字应该非常非常小
实现梯度检查的注意事项
- 不要在训练的时候进行梯度检查——只在 debug 阶段
- 如果算法没通过梯度检查,需要检查它的组成,找出漏洞,也就是如果 $d\theta_{approx}[i]$ 和 $d\theta[i]$ 差距很大,检查不同的 i 值,看看哪些 $d\theta_{approx}[i]$ 和 $d\theta[i]$ 很大
- 记着正则化,$J(\theta)= \frac {1}{m} \sum\limits_{i=1}^{m} L({\hat y}^{(i)},y^{(i)}) + \frac{\lambda}{2m}\sum\limits_{l=1}^{L}||W^{[l]}||^2_F$,在用公式计算时 $d\theta_{approx}$ 时,别忘了正则化项
- 不要在使用 dropout 时进行梯度检查,可以将 keep.prob 设为 1
- 反向传播算法在 w 和 b 接近 0 的时候是正确的,但是当 w 和 b 变大的时候,算法精确度有所下降,可以在进行几次训练的迭代后,再运行梯度检验