深度神经网络2——优化器选择、学习率消毒、正则化选择

内容分享5小时前发布
1 0 0

一、训练速度慢——更快的优化器

训练一个非常大的深度神经网络可能会非常缓慢。

目前接触过4种加快训练速度的方法:对连接权重应用良好的初始化策略,使用良好的激活函数,使用批量归一化,以及重用建立在辅助任务或无监督学习获得的预训练网络。

现在提出另一种加速训练的方式,对梯度下降做改进:优化器。将介绍流行的优化算法:动量优化,Nesterov加速梯度,AdaGrad,RMSProp,以及Adam及其变体

1、动量优化——SGD中的参数

其核心思想来自物理中的动量:想象一个球从斜坡上滚下,刚开始速度很慢,但速度会越来越快,直到达到最终速度(假设有摩擦力+空气阻力,速度不能无限增长),这就是动量优化背后的核心思想。

常规梯度下降法会在坡度平缓时采取小步,而在坡度大时采取大步,但永远不会关心之前的梯度是什么,永远不会加快速度,如果局部梯度很小,则它会下降得非常缓慢。

动量优化把梯度看成力的方向/加速度的方向,会累积之前的梯度:在每次迭代时,都会从动量向量(速度)m 减去 学习率乘以局部梯度(加速度),并通过加上该动量来更新权重。

为了模拟摩擦力,防止动量(速度)变得过大,该算法引入了一个新的超参数β,0代表高摩擦,退化为之前的梯度下降,1表示没有摩擦,β会设置为0-1之间的某个值,典型的β值是0.9。

动量优化公式:深度神经网络2——优化器选择、学习率消毒、正则化选择

可以轻松验证,如果梯度保持恒定,则最终速度(即权重更新的最大大小)等于该梯度乘以学习率 深度神经网络2——优化器选择、学习率消毒、正则化选择再乘以 深度神经网络2——优化器选择、学习率消毒、正则化选择(忽略符号)。例如,如果 深度神经网络2——优化器选择、学习率消毒、正则化选择,则最终速度等于梯度乘以学习率的10倍,因此动量优化最终比梯度下降快10倍!这使动量优化比梯度下降要更快地从”平台”逃脱。

在讨论线性回归的梯度下降时,当输入的尺寸差别非常巨大,代价函数将看起来像一个拉长的碗。梯度下降相当快地沿着陡峭的”斜坡”下降,但是沿着”山谷”下降需要很长时间。相反,动量优化将沿着”山谷”滚动得越来越快,直到达到”谷底”(最优解)。在不使用批量归一化的深度神经网络中,上面的层通常会得到尺寸差别较大的输入,因此使用动量优化可以在梯度方向改变频繁的维度上抑制震荡,使路径更加平滑,从而更快受凉并绕过局部优化问题。



# 在Keras中实现动量优化:只需要使用SGD优化器并设置其momentum超参数即可
optimizer_momentum = tf.keras.optimizers.SGD(learning_rate=0.001, momentum=0.9)

动量值取0.9通常在实践中效果很好,几乎总是比常规的梯度下降法更快。有时也会用到0.99,表示保留更多的历史动量,加速效果更强,但是可能需要更小的学习率。

2、Nesterov加速梯度——SGD中的参数

动量优化公式:深度神经网络2——优化器选择、学习率消毒、正则化选择

它是动量优化的一个小变体,会比常规动量优化快。Nesterov加速梯度(NAG)方法也称为Nesterov动量优化,它不是在局部位置 深度神经网络2——优化器选择、学习率消毒、正则化选择,而是在动量方向稍前方深度神经网络2——优化器选择、学习率消毒、正则化选择 处测量代价函数的梯度。 这种小的调整之所以有效是因为通常动量向量会指向正确的方向(即朝向最优值),因此使用在该方向上更远处而不是原始位置测得的梯度会稍微准确一些。

如下图所示(其中 深度神经网络2——优化器选择、学习率消毒、正则化选择代表在起点 深度神经网络2——优化器选择、学习率消毒、正则化选择 处测量的代价函数的梯度, 深度神经网络2——优化器选择、学习率消毒、正则化选择 代表 深度神经网络2——优化器选择、学习率消毒、正则化选择 点的梯度)。

深度神经网络2——优化器选择、学习率消毒、正则化选择

Nesterov动量优化更新最终稍微接近最优解。一段时间后,这些小的改进累积起来,NAG就比常规的动量优化要快得多。此外,请注意,当动量势头推动权重跨越谷底时,∇1继续推动越过谷底,而∇2则推回谷底。这有助于减少振荡,因此NAG收敛速度更快。



# 要使用NAG,只需在创建SGD优化器时设置nesterov=True即可
optimizer_nesterov = tf.keras.optimizers.SGD(learning_rate=0.001, momentum=0.9, nesterov=True)

更加直观的比喻,想象一个球从山上滚下来:

标准梯度下降:球只看自己脚下的坡度来决定往哪里滚。

动量法:球有一个速度。它根据脚下的坡度和当前速度来决定滚动。但因为它只看脚下,快到谷底时可能会因为速度太快而冲过谷底。

NAG:这是一个“聪明”的球。它先根据自己当前的速度预测一下自己下一秒会在什么位置,然后它提前看了一眼那个预测位置的坡度。如果预测位置的坡度已经向上(说明前面是谷底另一边),它就会提前减速;如果预测位置坡度依然向下,它就会放心地加速。这使得它的行为更加“未雨绸缪”。

3、AdaGrad算法——Adagrad优化器

AdaGrad(自适应梯度)算法的核心思想是:为模型中的每个参数自适应地调整学习率,也就是根据参数地历史梯度平方和来调整当前参数地学习率。这种自适应能力使其特别适合处理稀疏数据(即很多特征出现次数很少的数据)。

对于频繁更新(梯度大)的参数:它的学习率会变得较小,因为其历史梯度平方和较大。这使得参数的更新步伐更稳健,避免震荡。对于不频繁更新(梯度小)的参数:它的学习率会变得相对较大,因为其历史梯度平方和较小。这使得参数的更新步伐更大,加速学习。

每次迭代的更新规则:

计算当前批次的梯度:深度神经网络2——优化器选择、学习率消毒、正则化选择累计历史梯度的平方:深度神经网络2——优化器选择、学习率消毒、正则化选择 ,深度神经网络2——优化器选择、学习率消毒、正则化选择表示按元素相乘计算参数更新量:深度神经网络2——优化器选择、学习率消毒、正则化选择深度神经网络2——优化器选择、学习率消毒、正则化选择 表示学习率因子 更新参数:深度神经网络2——优化器选择、学习率消毒、正则化选择


optimizer_adagrad = tf.keras.optimizers.Adagrad(learning_rate=0.001)

4、RMSProp算法(AdaGrad的改进)——SMSProp优化器

RMSProp(Root Mean Square Propagation,均方根传播)完全是为了解决 AdaGrad 学习率过早衰减 的问题而设计的。

该算法的核心思想是:不再无脑地累加全部历史梯度平方,而是引入一个衰减率,让很久之前的梯度贡献逐渐减小,从而专注于最近一段时间的梯度规模。 这样,累积值 深度神经网络2——优化器选择、学习率消毒、正则化选择 就不会无限增大,学习率也就不会最终降为零。

每次迭代的更新规则:

计算当前批次的梯度:深度神经网络2——优化器选择、学习率消毒、正则化选择累计历史梯度的平方:深度神经网络2——优化器选择、学习率消毒、正则化选择 ,深度神经网络2——优化器选择、学习率消毒、正则化选择表示按元素相乘,深度神经网络2——优化器选择、学习率消毒、正则化选择是衰减率(动量),通常设置为0.9或0.99计算参数更新量:深度神经网络2——优化器选择、学习率消毒、正则化选择深度神经网络2——优化器选择、学习率消毒、正则化选择 表示学习率因子 更新参数:深度神经网络2——优化器选择、学习率消毒、正则化选择


optimizer_rmsprop = tf.keras.optimizers.RMSprop(learning_rate=0.001, rho=0.9)
优点 缺点
解决AdaGrad的致命缺陷:学习率不会单调下降至零,使得模型在训练后期也能有效学习

引入一个新的超参数衰减率 深度神经网络2——优化器选择、学习率消毒、正则化选择 ,需要手动调整。

不过这个参数通常很稳定,设置为0.9、0.99在大多数情况下都能很好的工作

继承了AdaGrad的优点:仍然为每个参数自适应调整学习率,在非平稳目标和稀疏梯度上表现良好 学习率的调整仍然依赖于历史的梯度平方,这意味着符号信息被丢失了(正负梯度一样对待),导致调整可能不够精确

5、Adam算法(动量+RMSProp)—— Adam优化器

结合了上边两种优化算法思想:动量和RMSProp。像动量优化一样,跟踪过去梯度的指数衰减平均值,就像RMSProp一样,它跟踪过去平方梯度的指数衰减平均值,相当于对梯度的均值和方差(不减平均值)估计。

初始化的时候 深度神经网络2——优化器选择、学习率消毒、正则化选择 都是初始化为0,之后的更新规则:

深度神经网络2——优化器选择、学习率消毒、正则化选择深度神经网络2——优化器选择、学习率消毒、正则化选择 表示时间步计数器计算当前小批量梯度:深度神经网络2——优化器选择、学习率消毒、正则化选择更新有偏一阶矩阵估计(动量):深度神经网络2——优化器选择、学习率消毒、正则化选择深度神经网络2——优化器选择、学习率消毒、正则化选择 就是动量优化中的 深度神经网络2——优化器选择、学习率消毒、正则化选择深度神经网络2——优化器选择、学习率消毒、正则化选择是一阶矩阵向量(类似动量中的速度变量)更新有偏二阶矩阵估计(梯度平方):深度神经网络2——优化器选择、学习率消毒、正则化选择深度神经网络2——优化器选择、学习率消毒、正则化选择 是RMSProp中的深度神经网络2——优化器选择、学习率消毒、正则化选择深度神经网络2——优化器选择、学习率消毒、正则化选择 是二阶矩阵向量(存储梯度平方的移动平均)。这里如果突然出现一个巨大梯度,那么深度神经网络2——优化器选择、学习率消毒、正则化选择会因为这个巨大梯度而瞬间飙升,但又可能因为后续的小梯度而快速下降。计算一阶矩的偏差矫正:深度神经网络2——优化器选择、学习率消毒、正则化选择计算二阶矩的偏差矫正:深度神经网络2——优化器选择、学习率消毒、正则化选择更新参数:深度神经网络2——优化器选择、学习率消毒、正则化选择

学习率(Learning Rate):这是最重要的超参数,通常需要尝试设置,如0.001, 0.0003等。Adam对初始学习率不那么敏感,但仍然需要设置。由于Adam是一种自适应学习率算法,像AdaGrad和RMSProp一样,因此几乎很少需要对学习率超参数进行调整。通常可以使用默认值0.001,这使得Adam甚至比梯度下降更易于使用。

深度神经网络2——优化器选择、学习率消毒、正则化选择:一阶矩的衰减率,通常设置为 0.9。控制动量部分的记忆程度。

深度神经网络2——优化器选择、学习率消毒、正则化选择:二阶矩的衰减率,通常设置为 0.999。控制梯度平方部分的记忆程度。

深度神经网络2——优化器选择、学习率消毒、正则化选择:一个非常小的数(例如 深度神经网络2——优化器选择、学习率消毒、正则化选择 ),为了防止分母为零,保证数值稳定性。通常不需要修改。



# Keras创建Adam优化器的方法:
optimizer_adam = tf.keras.optimizers.Adam(learning_rate=0.001, beta_1=0.9, beta_2=0.999)

6、AdaMax优化(Adam变种)—— Adamax优化器

AdaMax是Adam优化算法的一个直接扩展。

初始化的时候 深度神经网络2——优化器选择、学习率消毒、正则化选择 都是初始化为0,之后的更新规则:

深度神经网络2——优化器选择、学习率消毒、正则化选择深度神经网络2——优化器选择、学习率消毒、正则化选择 表示时间步计数器计算当前小批量梯度:深度神经网络2——优化器选择、学习率消毒、正则化选择更新有偏一阶矩阵估计(动量):深度神经网络2——优化器选择、学习率消毒、正则化选择深度神经网络2——优化器选择、学习率消毒、正则化选择 就是动量优化中的 深度神经网络2——优化器选择、学习率消毒、正则化选择深度神经网络2——优化器选择、学习率消毒、正则化选择是一阶矩阵向量(类似动量中的速度变量)更新有偏二阶矩阵估计(梯度平方):深度神经网络2——优化器选择、学习率消毒、正则化选择深度神经网络2——优化器选择、学习率消毒、正则化选择 是RMSProp中的深度神经网络2——优化器选择、学习率消毒、正则化选择深度神经网络2——优化器选择、学习率消毒、正则化选择 是二阶矩阵向量(存储梯度平方的移动平均)—— 这里改成取最大值,而不是像Adam优化器一样是加权平均,一旦有一个巨大梯度出现,他会在很长时间内主导,使得后续的学习率保持在一个较低的水平,从而使更新步伐更加稳定和保守。计算一阶矩的偏差矫正:深度神经网络2——优化器选择、学习率消毒、正则化选择计算二阶矩的偏差矫正: 这里的深度神经网络2——优化器选择、学习率消毒、正则化选择不在需要偏差矫正,是Adamax优化器的一个巨大优势。更新参数:深度神经网络2——优化器选择、学习率消毒、正则化选择,不在需要深度神经网络2——优化器选择、学习率消毒、正则化选择,因为深度神经网络2——优化器选择、学习率消毒、正则化选择 取最大值,理论上永远不会为0(只要初始深度神经网络2——优化器选择、学习率消毒、正则化选择 且有过非零梯度。这使得算法对深度神经网络2——优化器选择、学习率消毒、正则化选择不敏感)

优点 缺点
更简单稳定,由于使用了最大值操作,算法理论上更加稳定

实践表现不一:尽管理论上很优雅,但是在实际深度学习任务中(如训练大型的CNN和Transformer),AdaMax性能通常没有显著、一致的优于标准的Adam。

无需深度神经网络2——优化器选择、学习率消毒、正则化选择,消除了一个需要调整的超参数(虽然该参数在Adam中通常也不需要调整),实现了简化 虽然它对偶尔的大梯度不敏感,但一旦记录了一个异常的大梯度(由于数值不稳定或批次错误导致的),这个异常值会影响后续很多步的更新,导致收敛变慢
无需二阶偏差矫正,这是最大的实现优势。深度神经网络2——优化器选择、学习率消毒、正则化选择的更新方式使其在初始阶段不会有偏向0 的偏差,因此省去了计算和存储矫正引子 的步骤,使代码更加简洁,理论上也更加优美。

7、Nadam算法(Nesterov+Adam)—— Nadam优化器

该算法将Adam算法中的标准动量优化替换为Nesterov加速梯度。也就是将Adam中的动量项深度神经网络2——优化器选择、学习率消毒、正则化选择 用未来的动量进行更新深度神经网络2——优化器选择、学习率消毒、正则化选择 。这种优化算法在理论上收敛的比Adam稍微快一些。

优点 缺点
更快的收敛速度:这个是主要优势,通过引入Nesterov的前瞻性,通常收敛的比标准的Adam更快,尤其在训练初期阶段,能够直接、更迅速的朝着最优解的方向前进。

计算开销:虽然计算复杂度和Adam相同,都是O(m),但是其数学运算稍微多一点

可能更好的最终性能:在某些问题上,更快的收敛速度也意味值能够找到更优秀的极小值,从而获得更好的最终训练损失和测试精度。 超参数敏感性:由于融合了两种复杂算法,其行为可能对超参数(特别是深度神经网络2——优化器选择、学习率消毒、正则化选择)更加敏感
自动化的学习率调整:继承Adam的所有优点,自适应学习率。 并非总是最佳:对于一些数据集和模型架构,其优势可能不明显
处理病态条件问题:像Nesterov动量一样,在处理峡谷状的病态条件的时候表现更加鲁棒,震荡更小

理解和使用成本:对于使用者来说,其背后的原理比Adam更复杂,更难直观理解。

8、AdaW算法——AdaW优化器

该算法的核心思想是:修正权重衰减在Adam中的实现方式。AdamW的核心是为了解决一个关键问题:在原始Adam算法中,L2正则化(Weight Decay)的实现方式与纯SGD中的方式不同,从而导致权重衰减与自适应学习率之间产生不良的相互作用,最终可能损害模型的泛化能力。

在标准SGD中:权重衰减和梯度更新是完全分离的。更新规则为:深度神经网络2——优化器选择、学习率消毒、正则化选择,可以清晰地看到,权重衰减项 深度神经网络2——优化器选择、学习率消毒、正则化选择 是直接加在梯度上的。

在原始Adam中:为了实现权重衰减,通常也是采用同样的方式,将衰减项加到损失函数的梯度中:深度神经网络2——优化器选择、学习率消毒、正则化选择  。然后,这个合并后的“梯度” 深度神经网络2——优化器选择、学习率消毒、正则化选择 被送入Adam的动量计算和自适应学习率计算流程中。

在Adam中,自适应学习率会为每个参数调整更新幅度。这意味着权重衰减也被自适应学习率缩放了。对于梯度很大的参数,学习率会变小,导致其权重衰减也被减弱;而对于梯度很小的参数,学习率相对变大,权重衰减反而被增强。这与我们使用权重衰减的初衷相悖。我们的本意是对所有参数进行一个稳定、一致的衰减,与它们当前的梯度大小无关。

AdamW的解决方案非常直接和有效:将权重衰减与梯度更新完全分离开(Decouple)不再将权重衰减项加到梯度里在最后更新参数时,独立地、直接地从参数中减去权重衰减项。这样,权重衰减就恢复了它在SGD中的本来面目:一个独立于自适应学习率过程的、固定强度的正则化器。

步骤 原始Adam (带L2) AdamW
1. 计算损失梯度 深度神经网络2——优化器选择、学习率消毒、正则化选择,其中 深度神经网络2——优化器选择、学习率消毒、正则化选择 是正则化项 深度神经网络2——优化器选择、学习率消毒、正则化选择(关键区别:不再加 λθ)
2. 更新一阶矩 深度神经网络2——优化器选择、学习率消毒、正则化选择 (相同)深度神经网络2——优化器选择、学习率消毒、正则化选择
3. 更新二阶矩 深度神经网络2——优化器选择、学习率消毒、正则化选择 (相同) 深度神经网络2——优化器选择、学习率消毒、正则化选择
4. 偏差校正 深度神经网络2——优化器选择、学习率消毒、正则化选择 (相同)深度神经网络2——优化器选择、学习率消毒、正则化选择
5. 更新参数 深度神经网络2——优化器选择、学习率消毒、正则化选择

深度神经网络2——优化器选择、学习率消毒、正则化选择

(关键区别:在最后直接减去 λθ)

核心区别:

原始Adam+L2:
衰减项 → 影响梯度 → 被动量/自适应学习率处理 → 更新
。衰减被扭曲了。

AdamW:
(原始Adam更新) + (独立的衰减项)
。衰减是纯粹、一致的。

9、训练稀疏模型

刚刚讨论的所有优化算法都会产生密集模型,这意味着大多数参数都是非零的。如果在运行时需要一个非常快的模型,或者需要占用更少内存的模型,那么更可能使用一个稀疏模型。实现这一点的一个方法是像往常一样训练模型,然后去掉很小的权重(将它们设置为零)。但是,这通常不会导致非常稀疏的模型,而且能够会降低模型的性能。

一个更好的选择是在训练的时候使用强L1正则化,因为它会迫使优化器产生尽可能多的为零的权重。

如果这些技术仍然不都,在TensorFlow Model Optimization Toolkit (TF-MOT),它提供了一个剪枝API,能够根据连接的大小在训练期间迭代地删除连接。

二、学习率调度

学习率调度,也称为学习率退火(Learning Rate Annealing),指的是在训练过程中动态调整学习率的策略,而不是使用一个固定不变的值。其核心思想是:在训练的不同阶段,使用不同的学习率,以使模型更有效、更稳定地收敛到一个更优的解。

使用固定学习率主要存在两个问题:

学习率太大:在训练初期,较大的学习率可以加速收敛。但如果一直保持大学习率,在接近最优点时会发生“震荡”,无法收敛,甚至可能导致损失爆炸(Loss Exploding)。学习率太小:在训练后期,如果学习率仍然很小,收敛速度会非常慢,可能会陷入一个局部最优点而无法跳出。

学习率调度的优势:

训练初期:使用较大的学习率,快速接近最优解区域。训练中后期:逐渐减小学习率,让模型在最优点附近“精细调整”,最终稳定在一个更优的局部最小点或全局最小点(Global Minimum)。提高性能:实践证明,合适的学习率调度策略通常能比固定学习率获得更高的精度和更快的收敛速度。减少调参:某种程度上自动化了学习率的选择过程。

1、幂调度(多项式衰减)

(1)理论

其核心思想是将学习率随着训练步数或轮次的增加,以一个幂函数的形式进行衰减。它与指数衰减类似,但是衰减速度通常更平缓,为训练过程提供一个更可控、更平滑的学习率下降路径。

学习率的幂调度标准公式为 : 深度神经网络2——优化器选择、学习率消毒、正则化选择 ,其中深度神经网络2——优化器选择、学习率消毒、正则化选择表示在第 
t
 步(或第 
t
 个轮次)时的学习率,初始学习率 深度神经网络2——优化器选择、学习率消毒、正则化选择 和幂指数 c 和训练总步骤 s 为可调节的超参数。

c的值直接影响学习率曲线的形态:

深度神经网络2——优化器选择、学习率消毒、正则化选择


c = 1
线性衰减。学习率从 深度神经网络2——优化器选择、学习率消毒、正则化选择 开始,随着 
t
 的增加,直线下降至 0。
c < 1
(例如 c = 0.5 ):平方根衰减(或次线性衰减)。学习率在初期下降得相对较快,但后期会变得非常平缓,永远不会降到 0。
c > 1
(例如 c = 2 ):二次衰减(或超线性衰减)。学习率在初期下降得非常缓慢,但在训练接近尾声时(
t
 接近 
s
)会急剧下降至 0。

(2)代码应用

在应用的时候使用PloynomizlDecay()调度器,来实现幂调度。这个类是标准的幂调度器,其衰减完全由当前步数占总步数的比例决定。



# 标准幂调度,衰减完全由当前步数占总步数的比例决定
tf.keras.optimizers.schedeles.PloynomialDecay(
    initial_learning_rate=0.01,  # 初始学习率
    decay_steps=1000,            # 总迭代步数
    power=1.0                    # 幂次
    # end_learning_rate, 最终要达到的学习率,默认为 0
    # cycle 默认为 False,如果设置为True的话,当t>s的时候会重新开始循环,常用于重启调度
)

通常也可以使用InverseTimeDecay()调度器来进行,这个反时间衰减调度器,它的衰减速度还受到另一个超参数decay_rate衰减率的影响。

特性
PolynomialDecay

InverseTimeDecay
本质 标准的幂函数调度,衰减由步数比例和幂指数决定 反比衰减,衰减由衰减率和缩放后的步数决定
行为 确定性、有计划的衰减,有明确终点 渐进式、无终点的衰减
关键控制
power
 参数控制曲线形状

decay_rate
 控制衰减强度,初期影响巨大
如何选择 当你知道训练的总步数/轮数,并希望学习率按计划在结束时降到某个值时使用。 当你希望学习率持续衰减,并且可能希望通过 
decay_rate
 在训练初期快速降低学习率时使用。


import tensorflow as tf
import matplotlib.pyplot as plt
 
# 定义共同的参数
initial_learning_rate = 1.0
decay_steps = 20000
step = tf.range(0, 20000, 50)  # 模拟从0到20000步,每50步取一个点
 
# 创建 PolynomialDecay 调度器 (power=1 表示线性衰减)
poly_scheduler = tf.keras.optimizers.schedules.PolynomialDecay(
    initial_learning_rate,
    decay_steps,
    power=0.5,  # 平方根衰减
    # end_learning_rate=0.0  # 衰减到0
)
poly_lr = [poly_scheduler(s) for s in step]
 
# 创建 InverseTimeDecay 调度器 (尝试不同的decay_rate)
inv_scheduler_1 = tf.keras.optimizers.schedules.InverseTimeDecay(
    initial_learning_rate,
    decay_steps,
    decay_rate=0.5  # 衰减率
)
inv_lr_1 = [inv_scheduler_1(s) for s in step]
 
inv_scheduler_2 = tf.keras.optimizers.schedules.InverseTimeDecay(
    initial_learning_rate,
    decay_steps,
    decay_rate=5.0  # 更大的衰减率
)
inv_lr_2 = [inv_scheduler_2(s) for s in step]
 
# 绘图
plt.figure(figsize=(10, 6))
plt.plot(step, poly_lr, 'r-', label='PolynomialDecay (power=0.5.0)')
plt.plot(step, inv_lr_1, 'b--', label='InverseTimeDecay (decay_rate=0.5)')
plt.plot(step, inv_lr_2, 'g--', label='InverseTimeDecay (decay_rate=5.0)')
plt.axvline(x=decay_steps, color='k', linestyle=':', label='decay_steps')
plt.ylim(0, 1.1)
plt.xlabel('Training Steps')
plt.ylabel('Learning Rate')
plt.title('Comparison: PolynomialDecay vs. InverseTimeDecay')
plt.legend()
plt.grid(True)
plt.show()

深度神经网络2——优化器选择、学习率消毒、正则化选择

2、指数调度

该调度的核心思想是:在每个训练步骤或每个训练周期之后,将学习率乘以一个固定的衰减率,从而使学习率呈指数级下降

其公式可以写成:深度神经网络2——优化器选择、学习率消毒、正则化选择 ,学习率以每s步的速率逐渐变为原来的十分之一。幂调度降低学习率的速度越来越慢,而指数调度则使学习率每s步变为原来的十分之一。

在这个公式中:


深度神经网络2——优化器选择、学习率消毒、正则化选择
: 在第 
t
 步(或第 
t
 个周期)时的学习率。深度神经网络2——优化器选择、学习率消毒、正则化选择: 初始学习率,这是优化器开始训练时使用的学习率。0.1:是衰减率一个介于 0 和 1 之间的常数(例如 0.9, 0.95, 0.99)。这个值决定了学习率下降的速度。
值越接近 1(如 0.99),衰减速度越,学习率曲线越平缓。值越接近 0(如 0.5),衰减速度越,学习率曲线越陡峭。

t
: 当前的训练步骤(Step)或训练周期(Epoch)数。

指数调度在实际应用中可以通过ExponentialDecay()类进行定义:



lr_schedule = tf.keras.optimizers.schedules.ExponentialDecay(
    initial_learning_rate,
    decay_steps=20000,   # 每 20000 步衰减一次
    decay_rate=0.1,      # 衰减率为 0.1
    staircase=True       # 如果为True,则(step/decay_steps)为整数除法,阶梯式衰减;False为平滑连续衰减
)

下图为平滑staircase参数设置为True和设置为False的表现图:

深度神经网络2——优化器选择、学习率消毒、正则化选择

3、分段恒定调度

分段恒定调度的核心思想是在训练过程中,学习率在有些预定义的时间点(如特定的训练轮数或步骤)突然降低为一个新的、恒定的值、并保持该值到下一个时间点。和上图中staircase设置为True相似。
对一些轮次使用恒定的学习率(例如,对某5个轮次,使用η0=0.1),对另外一些轮次使用较小的学习率(例如,对某50个轮次,使用η0=0.001),以此类推。尽管这个方法可以很好地工作,但仍需要仔细研究以找出正确的学习率顺序以及使用它们的轮次。

在实际应用的过程中,keras提供PiecewiseConstantDecay()类实现分段恒定调度。



# 假设我们想在第 50000 步和第 80000 步改变学习率
lr_schedule = tf.keras.optimizers.schedules.PiecewiseConstantDecay(
    [50000, 80000],            # 以步数为单位
    [0.01, 0.005, 0.001]  # 三个区间对应的学习率
)
 
# 将定义好的调度器传给优化器
optimizer = keras.optimizers.Adam(learning_rate=lr_schedule)

深度神经网络2——优化器选择、学习率消毒、正则化选择

4、性能调度

性能调度的核心思想是:当模型的性能(如验证集损失或准确率)停止提升甚至开始恶化时,自动降低(减少)学习率,而不是一直使用固定的或按照预定计划变化的学习率。

该调度的好处:

跳出局部最优或鞍点:在训练后期,模型可能会进入一个局部最优点或一个平坦的鞍点区域。此时,梯度变得很小,权重更新也很微弱,训练陷入停滞。如果此时将学习率降低,可能会让模型有机会以更精细的步长“走出”这个平坦区域,找到更优的方向。加速收敛:一开始使用较大的学习率可以快速下降,后期使用较小的学习率可以精细调优,逼近最优解。这是一种自适应的方法,比手动设定学习率下降时机更智能。防止震荡:在接近最优解时,如果学习率太大,损失可能会在最小值附近来回震荡,无法收敛。适时降低学习率可以使优化过程更加稳定。

性能调度在实际应用中可以通过ReduceLROnPlateau()类进行定义:



lr_scheduler = ReduceLROnPlateau(
    monitor='val_loss',   # 要监控的指标:验证损失
    factor=0.5,           # 学习率减小的因子,设置为0.5 学习率减半
    patience=5,           # 如果 5 个 epoch val_loss 都不下降,就下降学习率
    verbose=1,            # 是否输出日志,0不输出,1输出
    mode='min',           # 监控指标模式:最小化 val_loss,也就是越小越好
    min_delta=0.0001,     # 因为有改善的最小变化,变化大于 0.0001 才算改善
    min_lr=1e-7           # 学习率最小不会低于 0.0000001
)

深度神经网络2——优化器选择、学习率消毒、正则化选择

如上图,在学习率下降之前的五个损失点没有明显的下降,因此学习率会根据定义的ReduceLRPnPlateau 类下降学习率。

5、一周期调度

一周期调度的核心思想是:先升温(从低学习率开始,线性增加到最大学习率)——之后在降温(从最大学习率线性降低到初始学习率)—— 最后衰减(在最后阶段)

使用周期调度的好处:

训练速度快:通常只需要传统调度方法 1/3 到 1/5 的训练轮数就能达到相同精度。性能更好:作为一种强正则化手段,它往往能帮助模型获得更好的泛化能力(测试集精度)。无需复杂调参:一旦确定了最大学习率(可以通过学习率探测找到),其他参数(如最小学习率、动量变化)通常可以使用推荐值,减少了超参数调整的负担。

由于在使用一周期调度的时候需要传入最大学习率,因此,需要使用学习率探测。其基本思想是:从一个很小的学习率开始,在每个批次后按指定的数增加学习率并记录损失值的变化,通过观察损失—学习率曲线,找到最佳学习率范围。



K = tf.keras.backend
 
class ExponentialLearningRate(tf.keras.callbacks.Callback):
    def __init__(self, factor):
        self.factor = factor
        self.rates = []
        self.losses = []
 
    def on_epoch_begin(self, epoch, logs=None):
        self.sum_of_epoch_losses = 0
 
    def on_batch_end(self, batch, logs=None):
        mean_epoch_loss = logs["loss"]  # the epoch's mean loss so far
        new_sum_of_epoch_losses = mean_epoch_loss * (batch + 1)
        batch_loss = new_sum_of_epoch_losses - self.sum_of_epoch_losses
        self.sum_of_epoch_losses = new_sum_of_epoch_losses
        lr = self.model.optimizer.learning_rate.numpy()
        self.rates.append(lr)
        self.losses.append(batch_loss)
        self.model.optimizer.learning_rate = lr * self.factor
 
# find_learning_rate() 函数使用 ExponentialLearningRate 回调函数训练模型,并返回学习率和相应的批次损失。最后,它将模型及其优化器恢复到初始状态。
import math
def find_learning_rate(model, X, y, epochs=1, batch_size=32, min_rate=1e-4,
                       max_rate=1):
    init_weights = model.get_weights() # 获取初始权重
    iterations = math.ceil(len(X) / batch_size) * epochs
    factor = (max_rate / min_rate) ** (1 / iterations)
    init_lr = K.get_value(model.optimizer.learning_rate)
    model.optimizer.learning_rate = min_rate
    exp_lr = ExponentialLearningRate(factor)
    history = model.fit(X, y, epochs=epochs, batch_size=batch_size,
                        callbacks=[exp_lr])
    model.optimizer.learning_rate = init_lr # 恢复初始学习率和初始权重
    model.set_weights(init_weights)
    return exp_lr.rates, exp_lr.losses
 
model.compile(loss="sparse_categorical_crossentropy",
              optimizer=tf.keras.optimizers.SGD(learning_rate=0.001),
              metrics=["accuracy"])
batch_size = 128
rates, losses = find_learning_rate(model, X_train, y_train, epochs=1,
                                   batch_size=batch_size)
plot_lr_vs_loss(rates, losses)

深度神经网络2——优化器选择、学习率消毒、正则化选择

观察曲线:理想的曲线应该显示:

最初:损失基本不变(学习率太小)中间:损失快速下降(最佳学习率范围)最后:损失开始上升(学习率太大)

选择最大学习率:选择损失开始上升之前的那个点,通常是曲线最陡峭的部分,这里选择深度神经网络2——优化器选择、学习率消毒、正则化选择



# OneCycleScheduler 自定义回调会在每个批次开始时更新学习率。
# 在训练的大约一半时间内线性增加学习率,然后线性降低回初始学习率,最后在训练的最后阶段线性降低至接近零。
class OneCycleScheduler(tf.keras.callbacks.Callback):
    def __init__(self, iterations, max_lr=1e-2, start_lr=None,
                 last_iterations=None, last_lr=None):
        self.iterations = iterations        # 总训练批次
        self.max_lr = max_lr                # 最大学习率
        self.start_lr = start_lr or max_lr / 10    # 起始学习率(默认为最大学习率的1/10)
        self.last_iterations = last_iterations or iterations // 10 + 1 # 最后衰减阶段批次数量
        self.half_iteration = (iterations - self.last_iterations) // 2 # 升温阶段的结束点
        self.last_lr = last_lr or self.start_lr / 1000                 # 最终学习率
        self.iteration = 0                                             # 当前迭代计数
 
    def _interpolate(self, iter1, iter2, lr1, lr2):
        return (lr2 - lr1) * (self.iteration - iter1) / (iter2 - iter1) + lr1
 
    def on_batch_begin(self, batch, logs):
        # 升温阶段
        if self.iteration < self.half_iteration:
            lr = self._interpolate(0, self.half_iteration, self.start_lr,
                                   self.max_lr)
        # 降温阶段
        elif self.iteration < 2 * self.half_iteration:
            lr = self._interpolate(self.half_iteration, 2 * self.half_iteration,
                                   self.max_lr, self.start_lr)
        # 最终衰减阶段
        else:
            lr = self._interpolate(2 * self.half_iteration, self.iterations,
                                   self.start_lr, self.last_lr)
        self.iteration += 1
        self.model.optimizer.learning_rate = lr

定义好一周期调度的类之后,实例化类并传入fit()函数中。

6、自定义调度

以上都是keras库中自带的调度器,能够直接调用,可以通过 tf.keras.optimizers.schedules 查看完整的列表:



import tensorflow as tf
for name in sorted(dir(tf.keras.optimizers.schedules)):  # dir返回方法内对象的所有内置方法
    if name[0] == name[0].lower():  # must start with capital letter
        continue
    scheduler_class = getattr(tf.keras.optimizers.schedules, name) # python的反射机制,通过字符串获取属性或方法
    print(f"• {name} – {scheduler_class.__doc__.splitlines()[0]}") # 根据换行符进行分割,并取第一行

如果不满足库中提供的调度函数可以自定义调度函数,有两种方法:

一种是使用Keras提供LearningRateScheduler回调类来进行自定义调度函数,该函数在每个epoch开始的时候被调用,之后根据自定义的调度函数计算出该epoch要使用的学习率,然后将其设置到优化器中。另一种是通过自定义回调函数类来更新学习率,这种情况一般是想要更频繁的更新学习率,不满足通过LearningRateScheduler类每个轮次开始的时候更新优化器。

使用LearningRateScheduler通常分为两步:

首先,定义一个调度函数(包含epoch当前的轮次、learning_rate当前学习率),该调度函数返回一个浮点数作为新的学习率。其次,将定义好的调度函数传入到LeaningRateScheduler回调中,并添加到 model.fit() 中。



# 不传入初始学习率和步数,直接在函数内定义好
def exponential_decay_fn(epoch):
    lr0 = 0.01
    drop = 0.1         # 衰减因子 gamma
    s = 20.0           # 每 10 个 epoch 衰减一次
    lr = lr0 * (drop ** (epoch // s))
    return lr
    
# 如果想配置初始学习率 和 步数,可以创建一个返回配置函数的函数(用闭包)
def exponential_decay(lr0, s):
    def exponential_decay_fn(epoch):
        return lr0 * 0.1 ** (epoch / s)
    return exponential_decay_fn
exponential_decay_fn = exponential_decay(lr0=0.01, s=20)
 
# 自定义调度函数
lr_scheduler = tf.keras.callbacks.LearningRateScheduler(exponential_decay_fn)
# 将自定义的调度函数应用到fit()方法中
history = model.fit(X_train, y_train, epochs=20,
                    validation_data=(X_valid, y_valid),
                    callbacks=[lr_scheduler])

使用自定义的回调类可以更加灵活的实现在训练过程中的特定阶段(如每个 epoch 开始/结束时、每个 batch 开始/结束时),动态地获取和设置优化器地学习率,从而实现任何想要地调度策略。

这里的实现步骤也是分为两步:

首先,定义一个回调类(继承 tensorflow.keras.callbacks.Callback),在自定义的类中定义函数 on_train_begin()、on_epoch_begin()、on_epoch_end()、on_batch_begin() 等等。其次,实例化回调类,并将实例化后的回调类添加到 model.fit() 中。



import tensorflow as tf
import matplotlib.pyplot as plt
import numpy as np
 
class CustomExponentialScheduler(tf.keras.callbacks.Callback):
    """
    自定义指数学习率调度回调
    """
    def __init__(self, initial_lr=0.01, decay_rate=0.96, decay_epochs=1, verbose=0):
        """
        初始化调度器
        :param initial_lr: 初始学习率
        :param decay_rate: 衰减率
        :param decay_epochs: 每隔多少个epoch衰减一次
        :param verbose: 是否打印学习率信息 (0: 不打印, 1: 打印)
        """
        super(CustomExponentialScheduler, self).__init__()
        self.initial_lr = initial_lr
        self.decay_rate = decay_rate
        self.decay_epochs = decay_epochs
        self.verbose = verbose
        
        # --- 需要保存的关键信息 ---
        self.history = {}  # 用于记录学习率变化历史
        self.epoch = 0     # 内部记录当前epoch数
 
    def on_train_begin(self, logs=None):
        # 获取模型的优化器并设置初始学习率
        if not hasattr(self.model.optimizer, 'lr'):
            raise ValueError('Optimizer must have a "lr" attribute.')
        tf.keras.backend.set_value(self.model.optimizer.lr, self.initial_lr)
        
        # 初始化记录
        self.history['lr'] = []
        self.history['epoch'] = []
        
        if self.verbose > 0:
            print(f"Training starts with initial learning rate: {self.initial_lr:.6f}")
 
    def on_epoch_begin(self, epoch, logs=None):
        # 保存当前epoch号
        self.epoch = epoch
 
    def on_epoch_end(self, epoch, logs=None):    # 每个epoch结束时调用,这是更新学习率的理想位置
        # 1. 从优化器获取当前学习率
        current_lr = float(tf.keras.backend.get_value(self.model.optimizer.lr))
        
        # 2. 记录当前的学习率和epoch
        self.history['lr'].append(current_lr)
        self.history['epoch'].append(epoch)
        
        # 3. 判断是否到了需要衰减的epoch
        if (epoch + 1) % self.decay_epochs == 0:
            # 4. 计算新的学习率: new_lr = lr * (decay_rate)^(epoch / decay_epochs)
            # 或者更简单的: new_lr = current_lr * decay_rate
            new_lr = current_lr * self.decay_rate
            # 5. 设置新的学习率回优化器
            tf.keras.backend.set_value(self.model.optimizer.lr, new_lr)
            
            if self.verbose > 0:
                print(f"
Epoch {epoch+1}: Learning rate decayed from {current_lr:.6f} to {new_lr:.6f}")
        else:
            if self.verbose > 0:
                print(f"
Epoch {epoch+1}: Learning rate is {current_lr:.6f} (No decay)")
 
        # 可选:将学习率也记录到logs中,这样它就会出现在训练历史里
        logs = logs or {}
        logs['lr'] = current_lr
 
    def plot_lr_history(self):
        """
        绘制学习率变化历史的方法
        """
        plt.figure(figsize=(10, 6))
        plt.plot(self.history['epoch'], self.history['lr'], 'b-', label='Learning Rate')
        plt.title('Custom Exponential Learning Rate Schedule')
        plt.xlabel('Epoch')
        plt.ylabel('Learning Rate')
        plt.legend()
        plt.grid(True)
        plt.yscale('log') # 使用对数坐标更直观地观察指数衰减
        plt.show()

三、通过正则化避免过拟合

深度神经网络通常具有数万个参数,有时有数百万个。这使它们非常灵活,意味着它们可以拟合各种各样的复杂数据集。但是,这种巨大的灵活性也使网络易于过拟合训练集。通常需要采用正则化防止这种情况。

之前已经实现了一项正则化技术:早停。而且“批量归一化”被设计用来解决不稳定梯度问题,但它也能起到很好的正则化作用。

其他流行的神经网络正则化技术:l1和l2正则化,以及dropout和最大范数正则化

1、L1和L2正则化

L2正则化被称为 “权重衰减” 或 “岭回归” ,它倾向于将所有权重都变小,并趋向于0(但通常不会完全等于0),偏向于分散、均匀的小权重模型。其数学公式为:新损失函数 = 原始损失函数 + (正则化强度/2)× 所有权重的平方和。

L1正则化被称为 “Lasso回归” ,它倾向于让一部分权重完全为0,而其他权重保持不为零。这个正则化会产生稀疏的权重矩阵,这说明模型会自动选择最重要的特征。因此,它在特征数量特别多,且只有少俩特征有作用的情况下特别有用。其数学公式为:新损失函数 = 原始损失函数 + 正则化强度 × 所有权重的绝对值之和。

相较于L1正则化,L2正则化更常用于约束神经网络的连接权重,如果想要稀疏模型(许多权重为0),则可以使用L1正则化。L2正则化在使用SGD、动量优化和Nesterov动量优化时很好,但不适用于Adam及其变体。如果想使用具有权重衰减的Adam,则不要使用L2正则化:改用AdamW。

(1)在每层中正则化


# L2正则化
tf.keras.layers.Dense(100, activation="relu",
                              kernel_initializer="he_normal",
                              kernel_regularizer=tf.keras.regularizers.L2(0.01))
# L1正则化
tf.keras.layers.Dense(100, activation="relu", 
                      kernel_initializer="he_normal", 
                      kernel_regularizer=tf.keras.regularizers.L1(0.01))
# L1L2组合正则化,更强的正则化
tf.keras.layers.Dense(100, activation="relu", 
                      kernel_initializer="he_normal", 
                      kernel_regularizer=tf.keras.regularizers.L1L2(0.01))

选择这些正则化的快速流程图:

深度神经网络2——优化器选择、学习率消毒、正则化选择

(2)使用functools.partial()自定义砖块

由于通常希望将相同的正则化函数应用于网络中的所有层,并在所有隐藏层中使用相同的激活函数和相同的初始化策略,因此你可能会发现自己在重复使用相同的参数。这使代码很难看且容易出错。

为了避免这种情况,可以尝试使用循环来重构代码。另一种选择是使用Python的functools.partial()函数,该函数允许你为带有任何默认参数值的任何可调用对象创建一个小的包装函数。利用循环或 
partial()
 这样的高级特性来编写干净、易维护、不易出错的代码。



from functools import partial
 
# 创建一个“定制化”的Dense层生成器
# 我提前把 activation, initializer, regularizer 这些参数都固定好了
# 这个新函数 MyDenseLayer 只需要接受 units(神经元数量)这一个参数了
MyDenseLayer = partial(tf.keras.layers.Dense,
                      activation="relu",
                      kernel_initializer="he_normal",
                      kernel_regularizer=tf.keras.regularizers.L2(0.01))
 
# 使用这个“模具”来轻松创建层
model = tf.keras.Sequential([
    MyDenseLayer(100), # 只需要关心神经元数量是100
    MyDenseLayer(100), // 再创建一个100个神经元的,配置自动同上
    MyDenseLayer(100), // 再创建一个
    tf.keras.layers.Dense(10, activation="softmax") // 输出层,不用这个模具
])

2、dropout

在标准的神经网络中,神经元之间会发展出复杂的相互依赖关系(co-adaptation)。这意味着某些神经元可能过度依赖于其他特定神经元的输出,而不是自己学习有鲁棒性的特征。这种依赖在训练数据上很有效,但会导致模型对训练数据过拟合,泛化到新数据的能力变差。

Dropout 的核心思想非常简单:在训练过程中,随机地“丢弃”(即暂时忽略)网络中的一部分神经元。在每个训练步骤中,每个神经元(包括输入神经元,但始终不包括输出神经元)都有暂时被“删除”(dropped out)的概率p,这意味着在这个训练步骤中它被完全忽略,但在下一步中可能处于活动状态。超参数p称为dropout率,通常设置为10%~50%:在循环神经网络中在20%~30%,在卷积神经网络中在40%~50%。训练后,神经元不再被“删除”。这就是全部内容(除了一个技术细节)

“丢弃”意味着:在前向传播和反向传播的过程中,被“丢弃”的神经元暂时不参与任何计算,其输入和输出都变为零。dropout的有效性:这种做法强迫网络不能依赖于任何单个或一小群神经元。因为任何一个神经元都可能随时“失灵”,所以网络必须学会让每一个神经元都尽可能独立地提取有用的特征,或者让多个神经元都能冗余地提取相同特征。这大大增强了模型的鲁棒性和泛化能力。



model = tf.keras.Sequential([
    tf.keras.layers.Flatten(input_shape=[28, 28]),
    tf.keras.layers.Dropout(rate=0.2),
    tf.keras.layers.Dense(100, activation="relu",
                          kernel_initializer="he_normal"),
    tf.keras.layers.Dropout(rate=0.2),
    tf.keras.layers.Dense(100, activation="relu",
                          kernel_initializer="he_normal"),
    tf.keras.layers.Dropout(rate=0.2),
    tf.keras.layers.Dense(10, activation="softmax")
])

Dropout中两种不同但等价的内部实现方法:

标准Dropout,就是说在训练的时候保留1-p概率的神经元,但是在预测的时候,会对使用dropout层的神经元的权重乘以1-p的概率进行缩放,这样做的目的是为了保证训练和预测的输出尺度一致。
输出尺度是指神经网络内部每一层(尤其是应用了dropout层)的输出信号的整体强度、幅度和数值范围。比喻:10个人中训练的时候2-3个人上班,预测的时候10个人上班,但是工作量是一定的,这个工作量就是输出尺度。如果尺度不一致,上一层输出的分布漂移了,下一层的权重处理起来就会非常低效,导致模型性能下降。缩放操作就是为了校正因神经元数量变化引起的分布偏移。
倒置Dropout,就是说在训练的时候保留1-p概率的神经元,并放大这些神经元的输出(乘以 1/(1-p) )。但是,在预测的时候,不用对dropout层的神经元的权重进行任何缩放,这样训练和预测就天然保持一致了。

特性 Dropout AlphaDropout
主要目的 防止过拟合 防止过拟合,且保持自归一化属性
适用激活函数 ReLU, LeakyReLU, Tanh 等 专为SELU设计
丢弃操作 置 0 置为一个非零常数 
α'
保留值的处理 缩放: 
x * 1/(1-p)
仿射变换: 
a * x + b
保持的属性 输出的期望(均值) 输出的均值方差
与BN的关系 常与Batch Normalization一起使用 替代BN,使SELU网络无需BN也能深度训练

3、蒙特卡洛(MC)dropout

MCdropout的实现过程:

保持 Dropout 开启:使用一个已经用 Dropout 训练好的模型。在进行预测时,不像往常那样关闭 Dropout,而是让它继续保持工作状态。这意味着每次进行前向传播时,网络都会随机丢弃一部分神经元,每次都会得到一个略有不同的输出。多次采样:对同一个输入数据 
x
,进行 
T
 次前向传播(例如 T=100 次)。由于 Dropout 的随机性,你会得到 
T
 个不同的预测结果 
{ŷ₁, ŷ₂, ..., ŷT}
。聚合结果:对这 
T
 个结果进行聚合,以得到最终的预测和不确定性估计。

回归任务(预测一个连续值):

最终预测:取这 
T
 个结果的均值作为最终的预测值。不确定性估计:计算这 
T
 个结果的方差。方差越大,说明模型对这个输入的预测越不确定(可能因为输入是没见过的异常数据,或处于不同类别的决策边界)。
分类任务(预测一个类别):
最终预测:取这 
T
 个结果(通常是概率向量)的均值,然后取概率最大的类别作为最终预测。不确定性估计:可以计算预测熵或概率向量的方差。熵越高或方差越大,不确定性越大。



# 开了dropout对数据集进行100次预测
y_probas = np.stack([model(X_test, training=True) for sample in range(100)]) 
# 预测后计算他们的平均值
y_proba = y_probas.mean(axis=0)
# 
y_pred = y_proba.argmax(axis=1)
accuracy = (y_pred == y_test).sum() / len(y_test)

使用的蒙特卡罗样本数量(在此样例中为100)是可以调整的超参数。数值越高,预测及其不确定性估计的精度就越高。但是,如果将其加倍,则推理时间也将加倍。此外,多于一定数量的样本,你几乎看不到任何改善。因此,你的工作就是在延迟和精度之间找到适当的平衡,具体取决于应用。

如果模型包含在训练过程中以特殊方式运行的其他层(例如BatchNormalization层),则不应像刚才那样强制采用训练模式(model(…, training=True))。相反,应该使用以下MCDropout类来替换Dropout层,这样只对Dropout层强制开启训练,而其他层仍然使用正确的推理模式



class MCDropout(tf.keras.layers.Dropout):
    def call(self, inputs, training=False):
        return super().call(inputs, training=True)

训练模式:BN 层用当前 mini-batch 的均值/方差,并且还会更新 moving mean/var。

推理模式:BN 层用累计的 moving mean/var(稳定的全局统计量),不再更新。 使用model(…, training=True)意味着BN层的推理也按训练模式进行,不符合BN层的工作性质

方法 优点 缺点 适用场景

model(X, training=True)
简单 影响所有层(包括BN) 只有Dropout层的简单模型

MCDropout
 自定义层
精确控制 需要修改模型定义 包含BN等特殊层的复杂模型


# 蒙特卡洛Dropout预测函数
def mc_dropout_predict(model, X, n_samples=100):
 
    # 收集多次预测结果
    predictions = []
    for _ in range(n_samples):
        pred = model.predict(X,verbose=0)
        # 等同于上边:pred = model(X)
        predictions.append(pred)
 
    predictions = np.array(predictions)  # 形状: (n_samples, n_samples, n_classes)
    mean_predictions = np.mean(predictions, axis=0)
    std_predictions = np.std(predictions, axis=0)
 
    return predictions, mean_predictions, std_predictions
 
# 指定蒙特卡洛样本数量
n_mc_samples = 100  # 这是您要指定的蒙特卡洛样本数量
 
# 执行蒙特卡洛Dropout预测
mc_predictions, mean_predictions, std_predictions = mc_dropout_predict(
    model2, X_test, n_samples=n_mc_samples
)
 
print(f"使用的蒙特卡洛样本数量: {n_mc_samples}")
print(f"预测结果形状: {mc_predictions.shape}")
print(f"平均预测形状: {mean_predictions.shape}")
print(f"不确定性度量形状: {std_predictions.shape}")
 
# 计算准确率(基于平均预测)
y_pred = np.argmax(mean_predictions, axis=1)
accuracy = np.mean(y_pred == y_test)
print(f"蒙特卡洛Dropout准确率: {accuracy:.4f}")
 
# 评估不确定性
mean_uncertainty = np.mean(std_predictions, axis=1)  # 每个样本的平均不确定性
print(f"平均不确定性: {np.mean(mean_uncertainty):.4f}")

4、最大范数正则化

最大范数正则化的核心思想:它不直接惩罚权重本身的大小(如L1/L2正则化),而是强制限制每个隐藏层神经元的权重向量的范数(通常是L2范数)不得超过一个预设的阈值c (如果超过了会被大会到 c 的位置)。

最大范数正则化不会把正则化损失项加到总体损失函数中。取而代之的是,通常在每个训练步骤后通过计算||w||2来实现:深度神经网络2——优化器选择、学习率消毒、正则化选择 

最大范数正则化会增加正则化的强度,并有助于降低过拟合风险。 最大范数正则化还可以帮助缓解不稳定的梯度问题(如果未使用“批量归一化”)。



tf.keras.layers.Dense(100, activation="relu", 
                           kernel_initializer="he_normal", 
                           kernel_constraint=tf.keras.constraints.max_norm(1.))

每次训练迭代后,模型的fit()方法会调用由max_norm()返回的对象,将层的权重传递给该对象,并获得返回的缩放权重,然后替换该层的权重。

如果需要,可以定义自己的自定义约束函数,并将其用作kernel_constraint。还可以通过设置bias_constraint参数来约束偏置项。

max_norm()函数的axis参数默认为0。密集层(Dense)通常具有形状为[输入数量,神经元数量]的权重,因此axis=0意味着最大范数约束将独立应用于每个神经元的权重向量。

如果要将最大范数约束用于卷积层,请确保正确设置max_norm()约束的axis参数(通常axis=[0,1,2])。

技术 主要优点 典型应用场景 注意事项
L2正则化 稳定,通用,防止权重过大 默认选择,几乎所有模型(线性模型,CNN,RNN) 需要调正则化系数 
λ
L1正则化 产生稀疏权重,进行特征选择 特征数量多,需要模型解释性或特征选择时 在零点不可微,可能不稳定
Dropout 非常强大,像模型集成 CNN的全连接层,大型网络 训练和预测行为不同;在RNN中需小心使用
早停 简单,免费,无需改变模型 始终使用!尤其适合训练耗时长的模型 需要验证集;可能停止过早
最大范数 稳定训练,防止权重爆炸 与Dropout配合;RNN;高学习率时 需要调范数阈值 
c
© 版权声明

相关文章

暂无评论

none
暂无评论...