本文是我学习吴恩达(Andrew Ng)《Deep Learning Specialization》后整理的笔记,记录第二门课前两周 Improving Deep Neural Networks 的核心内容。
前一篇已经把深层神经网络的基本记号、前向传播、反向传播和向量化形式固定下来。这一部分主要处理训练结果该如何诊断,过拟合如何控制,深层网络数值上为什么会不稳定,以及参数更新怎样做得更快。
训练诊断 数据划分与判断顺序 深层网络进入实践阶段后,先要解决的不是再换一个模型,而是先判断问题出在哪里。训练集、开发集、测试集在这里承担的是不同角色:
训练集用于拟合参数; 开发集用于选择结构、超参数和训练策略; 测试集只在最后用于评估最终泛化效果。 这个划分本身并不新,但在深层网络里更值得强调。网络规模、超参数和训练技巧都明显变多,如果没有单独的开发集,很多调节动作就会直接污染最终评估。
判断顺序通常也是固定的。先看训练误差,再看开发误差。若训练误差已经偏高,问题更接近高偏差;若训练误差不高,但开发误差明显更高,问题更接近高方差。若两者都高,说明模型拟合能力和泛化能力都还不够稳定。
因此,这一部分真正建立的是一套诊断框架,而不是单独某个公式。后面的正则化、dropout、初始化和优化算法,都应放在这套诊断顺序之后理解。
偏差 方差 与基本处理配方 偏差和方差在机器学习课程中已经出现过,这里不再重复定义。
高偏差表示模型对训练集本身的拟合还不够。常见处理方向包括:
增大网络容量; 训练更久; 改善优化过程; 调整网络结构。 高方差表示模型已经把训练集学得比较好,但泛化到开发集时误差上升明显。常见处理方向包括:
增加数据; 使用更强的正则化; 调整开发集分布; 控制模型复杂度。 这里更重要的是处理顺序。先判断是偏差问题还是方差问题,再决定后续动作。若这个顺序颠倒,很多技巧会被用错位置,例如高偏差阶段过早加强正则化,往往只会让训练更难。
稳定训练 L2 正则化 L2 正则化是在原有代价函数基础上加入权重惩罚项。若第 l l l 层的参数记为 W [ l ] W^{[l]} W [ l ] ,则正则化后的目标函数写成
J = 1 m ∑ i = 1 m L ( y ^ ( i ) , y ( i ) ) + λ 2 m ∑ l = 1 L ∥ W [ l ] ∥ F 2 J = \frac{1}{m}\sum_{i=1}^{m}\mathcal{L}\bigl(\hat{y}^{(i)}, y^{(i)}\bigr) + \frac{\lambda}{2m}\sum_{l=1}^{L}\left\|W^{[l]}\right\|_F^2 J = m 1 i = 1 ∑ m L ( y ^ ( i ) , y ( i ) ) + 2 m λ l = 1 ∑ L W [ l ] F 2 其中:
m m m 是样本数;L \mathcal{L} L 是单样本损失;λ \lambda λ 是正则化强度;∥ W [ l ] ∥ F 2 \|W^{[l]}\|_F^2 ∥ W [ l ] ∥ F 2 是第 l l l 层权重矩阵的 Frobenius 范数平方。这一项直接约束的是权重规模,因此反向传播时,每层梯度会相应多出一项:
d W [ l ] ← d W [ l ] + λ m W [ l ] dW^{[l]} \leftarrow dW^{[l]} + \frac{\lambda}{m}W^{[l]} d W [ l ] ← d W [ l ] + m λ W [ l ] 代入梯度下降以后,参数更新写成
W [ l ] : = W [ l ] − α ( d W [ l ] + λ m W [ l ] ) W^{[l]} := W^{[l]} - \alpha\left(dW^{[l]} + \frac{\lambda}{m}W^{[l]}\right) W [ l ] := W [ l ] − α ( d W [ l ] + m λ W [ l ] ) 这个式子里,α \alpha α 是学习率。若只看更新效果,会发现每一步都在把权重往更小的范围压,因此课程里也常把它和 weight decay 联系起来。
Dropout dropout 不是改损失函数,而是在训练阶段对隐藏单元做随机稀疏化。设第 l l l 层激活为 A [ l ] A^{[l]} A [ l ] ,保留概率为 keep_prob,则训练时可写成
D [ l ] ∼ Bernoulli ( keep_prob ) D^{[l]} \sim \text{Bernoulli}(\text{keep\_prob}) D [ l ] ∼ Bernoulli ( keep_prob ) A [ l ] : = A [ l ] ∗ D [ l ] A^{[l]} := A^{[l]} * D^{[l]} A [ l ] := A [ l ] ∗ D [ l ] 若采用 inverted dropout,还需要再除以保留概率:
A [ l ] : = A [ l ] ∗ D [ l ] keep_prob A^{[l]} := \frac{A^{[l]} * D^{[l]}}{\text{keep\_prob}} A [ l ] := keep_prob A [ l ] ∗ D [ l ] 这里:
D [ l ] D^{[l]} D [ l ] 是与 A [ l ] A^{[l]} A [ l ] 同维度的随机掩码矩阵;每个元素取 0 0 0 或 1 1 1 ; keep_prob 控制该层保留单元的比例。这种写法对应的含义很直接。训练时,同一层并不是每次都让所有单元参与计算,而是随机保留其中一部分。inverted dropout 再用 keep_prob \text{keep\_prob} keep_prob 做缩放,使测试时不需要额外调整输出尺度。
如果把它放进实现里,最容易写错的不是原理,而是掩码维度和缩放位置。dropout 只在训练阶段使用,测试阶段应关掉;做梯度检查时也应先关掉 dropout,否则前向传播本身带有随机性,数值近似会失去可比性。
其他稳定训练的做法 除了正则化和 dropout,第一周还补了几项更偏训练过程的处理。
第一项是输入归一化。若输入特征写成 x x x ,均值为 μ \mu μ ,标准差为 σ \sigma σ ,常见形式是
x : = x − μ σ x := \frac{x-\mu}{\sigma} x := σ x − μ 这个式子并不新,但在深层网络里很实用。不同特征量级差距过大时,参数空间里的更新会很不均匀,归一化能够让训练更平稳。
第二项是梯度消失和梯度爆炸。深层网络反向传播时,梯度会沿层连续相乘。若乘到很多层以后整体量级越来越小,就会出现梯度消失;若整体量级越来越大,就会出现梯度爆炸。这个问题和层数、激活函数以及初始化方式都有关系。
第三项是权值初始化。初始化不能全部为零,否则同一层各单元会保持完全相同的更新轨迹。更合理的做法,是让参数以较小随机值开始,同时让不同层的尺度尽量保持在合理范围内。课程这一部分主要是在给后面的 Xavier 和 He 初始化作铺垫,因此这里先把目标固定住即可:既要打破对称性,又要避免激活值和梯度在传播中迅速失控。
梯度近似与梯度检查 梯度检查是一类调试工具。若把所有参数压成一个向量 θ \theta θ ,对其中某一维做很小的扰动 ε \varepsilon ε ,则数值梯度近似可以写成
∂ J ∂ θ ≈ J ( θ + ε ) − J ( θ − ε ) 2 ε \frac{\partial J}{\partial \theta} \approx \frac{J(\theta+\varepsilon)-J(\theta-\varepsilon)}{2\varepsilon} ∂ θ ∂ J ≈ 2 ε J ( θ + ε ) − J ( θ − ε ) 这里:
θ \theta θ 表示待检查的参数;ε \varepsilon ε 是很小的扰动量;右边是中心差分近似。 若把反向传播得到的梯度记为 d θ d\theta d θ ,数值梯度记为 d θ approx d\theta_{\text{approx}} d θ approx ,则二者差异常写成
difference = ∥ d θ − d θ approx ∥ 2 ∥ d θ ∥ 2 + ∥ d θ approx ∥ 2 \text{difference} = \frac{\left\|d\theta-d\theta_{\text{approx}}\right\|_2} {\left\|d\theta\right\|_2+\left\|d\theta_{\text{approx}}\right\|_2} difference = ∥ d θ ∥ 2 + ∥ d θ approx ∥ 2 ∥ d θ − d θ approx ∥ 2 这个比值足够小时,说明反向传播实现大体正确。梯度检查只适合调试,因为代价很高,不能放进日常训练循环里。
优化算法 小批量梯度下降 标准梯度下降每次更新都使用全部训练样本。若样本数很大,这种做法在深层网络里会很慢。mini-batch 的思路,是把训练集拆成若干批小样本,每次只用其中一个 batch 做前向传播、反向传播和参数更新。
若一个批次包含 B B B 个样本,则这一轮更新中用到的是
X { t } ∈ R n x × B , Y { t } ∈ R n y × B X^{\{t\}} \in \mathbb{R}^{n_x \times B}, \qquad Y^{\{t\}} \in \mathbb{R}^{n_y \times B} X { t } ∈ R n x × B , Y { t } ∈ R n y × B 这里花括号上标 { t } {\{t\}} { t } 只表示第 t t t 个 mini-batch。公式本身和整批训练完全一致,只是把样本子集换成了更小的一块。
小批量训练以后,代价函数曲线通常不再像整批梯度下降那样平滑,而会有一定抖动。这一点不表示算法错了,而是因为每一步看到的数据子集不同。课程里这一段的重点,就是让读者从整批更新过渡到更快的批量更新。
指数加权平均 指数加权平均是后面一串优化器的基础。若序列写成 x t x_t x t ,其滑动平均写成 v t v_t v t ,则有
v t = β v t − 1 + ( 1 − β ) x t v_t = \beta v_{t-1} + (1-\beta)x_t v t = β v t − 1 + ( 1 − β ) x t 其中 β ∈ [ 0 , 1 ) \beta \in [0,1) β ∈ [ 0 , 1 ) 控制平滑程度。β \beta β 越大,平均结果越平滑,也越依赖更久以前的历史值;β \beta β 越小,结果对当前值更敏感。
若初始时取 v 0 = 0 v_0 = 0 v 0 = 0 ,早期会有偏小问题,因此可做偏差修正:
v t corrected = v t 1 − β t v_t^{\text{corrected}} = \frac{v_t}{1-\beta^t} v t corrected = 1 − β t v t 这一步在迭代初期更有意义,因为随着 t t t 增大,β t \beta^t β t 会趋近于 0 0 0 ,修正项影响会越来越弱。
Momentum 与 RMSprop Momentum 先对梯度做指数加权平均,再用这个平滑后的方向更新参数。若第 l l l 层权重梯度为 d W [ l ] dW^{[l]} d W [ l ] ,则常写成
V d W [ l ] = β V d W [ l ] + ( 1 − β ) d W [ l ] V_{dW^{[l]}} = \beta V_{dW^{[l]}} + (1-\beta)dW^{[l]} V d W [ l ] = β V d W [ l ] + ( 1 − β ) d W [ l ] W [ l ] : = W [ l ] − α V d W [ l ] W^{[l]} := W^{[l]} - \alpha V_{dW^{[l]}} W [ l ] := W [ l ] − α V d W [ l ] 偏置 b [ l ] b^{[l]} b [ l ] 也有完全对应的写法。这里的 V d W [ l ] V_{dW^{[l]}} V d W [ l ] 可以看成梯度的平滑轨迹。参数不再只看当前一步的梯度,而是综合最近一段时间的方向。
RMSprop 则对梯度平方做指数加权平均。常见写法为
S d W [ l ] = β S d W [ l ] + ( 1 − β ) ( d W [ l ] ) 2 S_{dW^{[l]}} = \beta S_{dW^{[l]}} + (1-\beta)\bigl(dW^{[l]}\bigr)^2 S d W [ l ] = β S d W [ l ] + ( 1 − β ) ( d W [ l ] ) 2 W [ l ] : = W [ l ] − α d W [ l ] S d W [ l ] + ε W^{[l]} := W^{[l]} - \alpha \frac{dW^{[l]}}{\sqrt{S_{dW^{[l]}}}+\varepsilon} W [ l ] := W [ l ] − α S d W [ l ] + ε d W [ l ] 其中 ε \varepsilon ε 是很小的常数,用来避免分母为零。RMSprop 的作用是根据梯度历史尺度,对不同方向的更新幅度做自适应缩放。
Adam Adam 可以看成 Momentum 和 RMSprop 的合并形式。它同时维护一阶矩和二阶矩。对第 l l l 层参数,有
V d W [ l ] = β 1 V d W [ l ] + ( 1 − β 1 ) d W [ l ] V_{dW^{[l]}} = \beta_1 V_{dW^{[l]}} + (1-\beta_1)dW^{[l]} V d W [ l ] = β 1 V d W [ l ] + ( 1 − β 1 ) d W [ l ] S d W [ l ] = β 2 S d W [ l ] + ( 1 − β 2 ) ( d W [ l ] ) 2 S_{dW^{[l]}} = \beta_2 S_{dW^{[l]}} + (1-\beta_2)\bigl(dW^{[l]}\bigr)^2 S d W [ l ] = β 2 S d W [ l ] + ( 1 − β 2 ) ( d W [ l ] ) 2 然后分别做偏差修正:
V d W [ l ] corrected = V d W [ l ] 1 − β 1 t S d W [ l ] corrected = S d W [ l ] 1 − β 2 t V_{dW^{[l]}}^{\text{corrected}} = \frac{V_{dW^{[l]}}}{1-\beta_1^t} \qquad S_{dW^{[l]}}^{\text{corrected}} = \frac{S_{dW^{[l]}}}{1-\beta_2^t} V d W [ l ] corrected = 1 − β 1 t V d W [ l ] S d W [ l ] corrected = 1 − β 2 t S d W [ l ] 最终更新式为
W [ l ] : = W [ l ] − α V d W [ l ] corrected S d W [ l ] corrected + ε W^{[l]} := W^{[l]} - \alpha \frac{V_{dW^{[l]}}^{\text{corrected}}} {\sqrt{S_{dW^{[l]}}^{\text{corrected}}}+\varepsilon} W [ l ] := W [ l ] − α S d W [ l ] corrected + ε V d W [ l ] corrected 偏置参数同样完全对应。这里:
β 1 \beta_1 β 1 控制一阶矩平滑;β 2 \beta_2 β 2 控制二阶矩平滑;ε \varepsilon ε 保证分母稳定;t t t 是当前迭代步数。课程里给出的常用默认值是 β 1 = 0.9 \beta_1=0.9 β 1 = 0.9 、β 2 = 0.999 \beta_2=0.999 β 2 = 0.999 、ε ≈ 10 − 8 \varepsilon\approx10^{-8} ε ≈ 1 0 − 8 。这组写法也是后面很多深度学习实现中的常见默认起点。
学习率衰减与参数空间 优化器之外,学习率本身也可以随训练过程变化。学习率衰减的目标很直接:前期更新步子可以大一些,后期接近较优区域时再逐渐减小。一个常见形式是
α t = α 0 1 + decay_rate ⋅ t \alpha_t = \frac{\alpha_0}{1+\text{decay\_rate}\cdot t} α t = 1 + decay_rate ⋅ t α 0 其中 α 0 \alpha_0 α 0 是初始学习率,α t \alpha_t α t 是第 t t t 轮使用的学习率。不同衰减策略形式不完全一样,但核心都一样:让训练后期的更新更稳。
至于局部最优,课程里更强调高维参数空间中的另一类现象。深层网络训练时,更常见的问题往往不是掉进一个很差的严格局部极小点,而是停在鞍点附近,或者落进很平坦的区域,使优化速度明显变慢。这一节的作用,是修正对参数空间形状的直觉,而不是再引入新的更新公式。