Skip to content

Optimization

在深度学习中,优化(Optimization) 就是寻找一组模型参数,使得损失函数(Loss Function)的值达到最小的过程。

简单来说,每一个机器学习模型本质上都是寻找一组可以使得损失函数最小化的参数集合,优化的过程就是不断调整参数,来让损失函数最小化的过程。

换句话说,大多数模型都可以拆解为:

  • 模型结构:决定了你如何从输入数据来得到预测值
  • 损失函数:定义了结果的评断标准
  • 优化算法:决定了如何寻找损失最小化的参数集合

对于一次完整的深度学习模型(神经网络)训练,其完整过程可以理解为:

  1. 将数据集划分为若干份,分若干轮次(epoch)进行;这里需要进行数据归一化处理(Input Normalization
  2. 在循环开始前,给神经网络的参数\(\theta\)设置初始值(Initialization
  3. 在每一轮epoch开始前调整学习率(Learning Rate Scheduling),然后进入epoch(注:实际代码一般在epoch结束后):
    1. 在每一个epoch中,分批次导入数据(Batching): 1. 前向传播,即让数据进入模型,得到预测结果 * 这里可以进行Batch Normalization * 这里可以进行Regularization正则化,如Dropout,在传播过程中随机关闭神经元,防止模型死记硬背 2. 根据前向传播的预测结果和真实值,算出损失(loss) * 这里可以进行Regularization正则化,如L2权重衰减 3. 使用优化手段更新参数: 1. 抹掉上一个batch的残余梯度 2. 反向传播:通过链式法则算出当前loss对每个参数的梯度 3. 优化器(Optimizer)根据梯度和学习率修改参数\(\theta\)(即优化)
  4. epoch结束后,评估验证集,如果表现不错,就将参数保存下来,如果后面过拟合或者断电,可以回退到表现最好的版本上

上面我标记出的Initialization, Learning Rate Scheduling, Batching, Normalization, Regularization, Optimizer等都旨在于帮助损失函数更快、更稳、更好地找到损失函数的最小值,因而都算是Optimization的范畴。其中Regularization较为特殊,有时候会故意让训练集的损失变高一点,但其目的是为了换取模型在未见过的测试集上损失更低,从而防止模型在训练中过拟合。

在本章,我们将分别介绍以下学习优化手段:(如果把优化比作下山,找到最低的山谷)

  1. Initialization:优化的起点(决定了从山的哪个地方开始下坡)
  2. Batching:优化的采样(决定了你每次看多少路面来决定下一步)
  3. Regularization:优化的约束(防止你为了走近路而掉进训练数据的死胡同)
  4. Optimizer:优化的执行(你的双脚如何根据地形反馈移动)
  5. Learning Rate Scheduling:优化的节奏控制(决定随着距离终点的远近,步幅应当如何变化)
  6. Normalization:优化的路面修整(将崎岖不平的山路修整得平缓均匀,防止在陡坡上打滑,或平地上原地踏步)

梯度下降

梯度下降法是最早的基于梯度的算法,由数学家奥古斯丁-路易·柯西 (Augustin-Louis Cauchy)1847 年 提出。随机梯度下降法在梯度下降的基础上进行了实践优化,不再计算所有样本的平均梯度,而是每次只随机抽取 一个 (或一小批)样本来估算梯度并更新参数。这使得它在处理现代大规模数据集(如深度学习)时,收敛速度比传统方法快得多,且能跳出一些局部的陷阱。

梯度的概念

梯度是机器学习中最为核心的数学概念,也是优化算法的基石。在机器学习中,我们的目标通常是找到一个数学模型,使其在处理数据时的“误差”最小。这个寻找最小值的过程就是 优化问题 。想象你在山顶,想要最快下到山脚。如果你知道在每一个位置往哪个方向走是“下降”的,你就可以不断迈步,直到周围所有方向都是“上升”的,这时你就找到了一个局部最低点。梯度就是那个告诉你在当前位置,函数值变化最快的方向和程度的工具。

对于一个输入为多维向量 \(\boldsymbol{x} = (x_1, x_2, \dots, x_n)^\top\) 的标量函数 \(f(\boldsymbol{x})\),它在 \(\boldsymbol{x}\) 处的梯度定义为:

\[ \nabla_{\boldsymbol{x}} f(\boldsymbol{x}) = \left( \frac{\partial f}{\partial x_1}, \frac{\partial f}{\partial x_2}, \dots, \frac{\partial f}{\partial x_n} \right)^\top \]

梯度的每一维都是函数对该维度变量的偏导数。梯度本身也是一个向量,它的维度和自变量 \(\boldsymbol{x}\) 是一致的。

梯度向量 \(\nabla f\) 所指的方向,是函数在该点增加最快的方向。在几何上,梯度向量总是垂直于函数的等值线(或等值面)。既然梯度指向增长最快的方向,那么梯度的 反方向 (负梯度方向)就是函数值减小最快的方向。这就是为什么“梯度下降法”要沿着负梯度方向更新参数。

1769356262640

上图展示的是函数 \(f(x, y) = -x - 2y\) 在点 \((1, 1)\) 处的梯度示意图。图中那些平行的斜线是 等值线 ,每一条线代表函数值相等的一系列点。从图中可以看到,蓝色的箭头跨过的线最多,这表明在相同的步长(箭头长度)下,蓝色箭头(梯度方向)由于跨过了最多的等值线,意味着沿着这个方向走,函数值增加得最快。

“梯度指向增长最快方向”这一结论在非线性函数中同样成立。如果函数不是线性的,利用一元函数的泰勒展开思想,多元函数在某一点 \(\boldsymbol{x}_0\) 附近也可以进行 线性近似

\[ f(\boldsymbol{x}) \approx f(\boldsymbol{x}_0) + \nabla f(\boldsymbol{x}_0)^\top (\boldsymbol{x} - \boldsymbol{x}_0) \]

这意味着,只要我们观察的范围足够小(局部),任何平滑的复杂函数看起来都像是一块“平板”(线性函数)。因此,在这一点上,梯度依然是指向值上升最快的方向。

抛物面 (\(f(x, y) = x^2 + y^2\)):像一个碗,梯度会指向远离圆心的放射方向。

1769356443178

马鞍面 (\(f(x, y) = x^2 - y^2\)):形状像马鞍,梯度在不同区域的指向会发生剧烈变化。

1769356451222

正弦乘积面 (\(f(x, y) = \sin(x)\sin(y)\)):像起伏的蛋盒,梯度会指向各个“山峰”。

1769356458657

在处理损失函数的时候,以下公式经常使用:

  • 线性函数求导
    • \(\nabla_{\boldsymbol{x}} (\boldsymbol{y}^\top \boldsymbol{x}) = \boldsymbol{y}\)
    • \(\nabla_{\boldsymbol{x}} (\boldsymbol{x}^\top \boldsymbol{y}) = \boldsymbol{y}\)
    • \(\nabla_{\boldsymbol{x}} (\boldsymbol{A}\boldsymbol{x}) = \boldsymbol{A}^\top\)
  • 二次型求导
    • \(\nabla_{\boldsymbol{x}} (\boldsymbol{x}^\top \boldsymbol{x}) = 2\boldsymbol{x}\)
    • \(\nabla_{\boldsymbol{x}} (\boldsymbol{x}^\top \boldsymbol{A} \boldsymbol{x}) = (\boldsymbol{A} + \boldsymbol{A}^\top)\boldsymbol{x}\)
  • 范数与常数
    • \(\nabla_{\boldsymbol{x}} \|\boldsymbol{x}\|_2 = \frac{\boldsymbol{x}}{\|\boldsymbol{x}\|_2}\)
    • \(\nabla_{\boldsymbol{x}} (a\boldsymbol{x}) = a\boldsymbol{I}\) (其中 \(a\) 为标量,\(\boldsymbol{I}\) 为单位矩阵)

对于saddle point(鞍点)的情况,即便梯度 \(\nabla f(\boldsymbol{x}) = \mathbf{0}\),该点也不一定是极值点(如马鞍面),需要通过黑塞矩阵 \(\boldsymbol{H}_f\) 的性质进一步判定。相关内容详见后面的小节。

计算机的数值计算

更多的关于数值计算的内容参见数学/数值计算笔记。

数值解

深度学习的优化过程,本质上就是在寻找一个能让损失函数最小化的神经网络参数集合,这个过程几乎完全依赖于数值计算方法,尤其是基于梯度的迭代优化算法。具体来说,一个经典的神经网络学习优化过程如下

  1. 前向传播:输入数据,通过网络计算得到一个预测值。这个过程是大量的矩阵乘法和激活函数计算,是纯粹的数值计算。
  2. 计算损失:用损失函数来衡量预测值和真实值之间的差距,这同样是数值计算。
  3. 反向传播:为了调整参数来减少损失,我们需要计算损失函数对每一个参数的梯度(偏导数),常用的比如反向传播算法,这也是一种数值方法。
  4. 参数更新:使用一种优化算法,比如Adam,根据计算出来的梯度来微调网络中的所有参数。这是一个反复迭代的数值逼近过程。

上面所说的具体流程在后面的章节中一一学习,这里提出的原因是想说明:数值计算是机器学习以及深度学习的核心,必须要学习掌握,否则无法展开后面内容的学习。

绝大多数机器学习问题(比如训练一个复杂的神经网络)都极其复杂,根本不可能存在一个直接算出最优参数的完美公式。换句话说,机器学习算法要通过迭代过程(Iterative Process)来不断更新解的估计值,而不是通过解析过程(Analytical Solution)直接推导出一个公式。

常见的数值计算的应用场景:

  • 优化:在深度学习中,寻找一组能让损失函数最小化的参数
  • 线性方程组的求解:比如经典的线性回归模型中会遇到

上溢和下溢

我们已经知道机器学习和深度学习的核心就是一系列的数值计算。在理想的情况下,我们将通过成千上万次的迭代,让参数平稳地、逐步地向着损失函数的最低点收敛。然而,这个完美的数学流程并不能完美地在计算机上实现。首当其冲的问题就是上溢(Overflow)和下溢(Underflow):

  • Overflow 上溢:当一个数字太大,超过了计算机能表示的最大范围时,他就会变成一个无穷大(inf)或者一个无意义(NaN, Not a Number)的数字。
  • Underflow 下溢:当一个数字变得太接近于零,超出了计算机能表示的最小精度时,它就会被“四舍五入”成零。

举例来说:(先不要管具体方法是怎么实现的,只是帮助理解一下上溢和下溢)

  • 在反向传播中,梯度的计算是多个导数的连乘。如果网络很深,且激活函数的导数持续小于1,那么梯度在从后向前传播时会越乘越小,最终变得无限接近于零,从而被计算机当做零来处理。
  • 反过来讲,如果在反向传播计算中梯度持续大于1,那么连乘之后就会变得异常巨大,超出计算机能表示的最大值,最终变成inf或NaN。一旦某次计算更新中让算出来一个NaN,那么整个模型瞬间就被摧毁了,训练过程直接失败。

那这里肯定有人会问:python已经支持大整数运算,那么假设我们支持小数点后10万位的小数,计算时把他转换为整数不就好了吗?理论上是可以的,但是计算效率会非常之低。对于一个相当规模的深度学习训练,计算次数是上万亿次的。因此,数值计算速度非常重要。

现在一般的方法是直接把支持32位浮点数和64位浮点数计算的电路单元直接焊死在电路单元或者说电路板上。这个小东西被称为FPU(Floating-Point Unit)。这些电路在一个时钟周期内就能完成一次浮点数加法或乘法,速度快到极致。我们可以把FPU理解为厨师,他只做一件事:进行浮点数计算(加减乘除)。他做的飞快,是物理上真实存在的实体。在一个GPU中,分布着成千上万个这样的厨师,并通过CUDA架构来实现高效的协同工作。

FPU的物理限制是显而易见的,Overflow和Underflow的问题非常突出。举例来说,如果某一次计算中的输入向量是:\(x=[1000,990,980]\)

我们的FPU使用的标准是32位浮点数,一个float32能表示的最大数值是\(3.4 \times 10^{38}\),任何超过这个数的计算,FPU都会报告上溢(Overflow),并返回一个特殊值 inf

在这里,我们先不讨论为什么要用下面这个公式,这里只是展示overflow的情况下我们该怎么办:

\[ \mathrm{softmax}(\mathbf{x})_i = \frac{\exp(x_i)}{\sum_{j=1}^n \exp(x_j)}. \]

好了,现在第一步就超出FPU的限制了,第一个计算就没能得到结果。程序继续计算e的990次方、980次方,结果都是 inf,整个计算过程全面崩坏,我们不可能得到想要的结果。

为了解决这个问题,我们必然需要把输入向量x进行某种调整。只要我们能把数值调整为计算结果不会溢出就可以了,这种调整一般被称为“数值稳定”。

还是以上面的向量x为示例,举一个数值稳定的方法:

  1. 首先找到向量x中的最大值,max(x)=1000
  2. 对x中的每一个元素,减去这个最大值,得到新的向量\(z=[0,-10,-20]\)
  3. 计算新的softmax函数,上述变换不会改变softmax的最终概率值(因为贡献比例没有改变)
  4. 由于max(x)的巧妙设置,分母中永远都至少有一个exp(0)=1,而其他下溢的内容被记为0,这样就可以总能得到softmax的结果值而不会出错。换句话说,经过上述变换,上溢问题被解决了,而下溢在softmax计算中又是无害的,因此不会造成计算的崩坏。

当然,现在的深度学习框架如PyTorch和TensorFlow都已经把上述数值计算过程进行了封装,用户在调用计算时不会出现上下溢出问题。例如,调用 torch.nn.functional.softmax时,上面的数值稳定方法(框架中甚至使用了更加优异的数值稳定方法!)已经内嵌在函数中,我们直接调用就可以了。

病态条件

病态条件(Ill-conditioned)的意思是:对于一个函数,其输入的微小变化,将会显著地影响到其输出结果。病态条件在计算机的数值计算中是一个大麻烦,因为计算机在存储数字的时候总会有微小的舍入误差,比如把1/3存储为0.3333333,当这个微小的、不可避免的误差发生在一个病态条件的输入上时,会导致输出产生巨大的偏差,导致计算结果和真实值有天壤之别。

由于深度学习的核心人任务之一就是优化,即寻找一个能让损失函数最小化的参数集。我们可以把损失函数想象成一个非常复杂的、高低不平的地形表面,我们的目标是走到这个地形的最低点。在深度学习任务中,这个地形往往是病态的,他并不光滑,而是更像一个峡谷,在一些地方极其陡峭,而在另外一些地方又极其平缓,如下图所示:

1759274402466

这个病态的损失函数表面有如下特征:

  • 充满陡峭的悬崖,在这里损失函数在某个方向上会变化极快
  • 平缓的谷底,在这里损失函数在某个方向上的变化又会极慢

这种特点会导致我们的优化算法(比如梯度下降)在进入到这样的峡谷时,梯度在陡峭方向会非常大,在平缓方向又会非常小,从而导致在优化过程在峡谷两侧来回震荡,难以向真正的最低点前进。为了防止震荡,我们必须使用很小的学习率,但是这又会导致在平缓的方向上的前进速度变得极其缓慢,从而导致训练卡住或者不收敛。

这里要注意,上面的3D图只是一种比喻。真实的神经网络的损失函数,其参数量是一个维度极高的向量,远远不止三维。一个中等大小的模型都可能有数百万(百万维)甚至数十亿(十亿维)的参数。这里以及大多数教材中画3D峡谷图,是为了方便我们理解,是一种降维的可视化类比,帮助我们建立直观的理解。换句话说,3D可视图无论如何构建,都是极度简化的切片,有着严重的失真,但足够我们用来理解病态条件(峡谷)、局部最小值(小坑)等重要概念。

既然知道了病态的损失函数表面的这些特点,那么我们就可以针对性地来采用这样一种策略:

  • 当我们在平缓的谷底时,用较快的速度前进
  • 当我们在陡峭的悬崖时,用较慢的速度移动

上述策略其实就是如今人们常用的优化器(Optimizer) 的核心思想。这里要注意,并非每种优化器都有这种策略,传统的GD(梯度下降)、SGD(随机梯度下降)中没有这种动态变化能力,而Adam、带有Polyak动量的SGD则具备了这种能力。这种能力一般由动量(Momentum)自适应学习率(Adaptive Learning Rate) 组成,在后面我们会展开来详细讲解。在学习具备动态功能的高级优化器之前,我们还是要从最为传统的优化器开始,了解梯度下降等基本概念。

基于梯度的优化方法

Ian经典教材第四章中除了计算机的数值计算、梯度下降、Hessian外,还介绍了一些其他概念,比如最优步长、牛顿法、Lipschitz连续、凸优化等,如果后续遇到,我再回来补充。概念太多,没办法一下子全都记住,因此我们暂时先记住最重要的概念

  • 梯度是一个向量,永远指向函数值上升最快的方向
  • 梯度下降就是朝着梯度的反方向前进
  • Hessian矩阵描述了地形的曲率(弯曲程度),通过分析Hessian,我们可以知道当前位置是U型山谷(曲率小)、V型峡谷(曲率大),还是更复杂的鞍点
  • 当Hessian矩阵的最大曲率和最小曲率相差悬殊时,我们就遇到了病态地形。这种地形就好比一个两侧陡峭、谷底平缓的峡谷,会导致梯度下降收敛缓慢,让算法在峡谷两侧来回震荡等,时所有优化算法都要面对的头号敌人。
  • 泰勒一阶展开证明了为什么负梯度方向是有效的,二阶展开则揭示了曲率是如何影响优化效果的,并揭示了线性预测和真实情况之间的差距
  • 在深度学习这种高维空间中,我们遇到的梯度为零的点,绝大多数都是鞍点,而不是局部最小值。简单的梯度下降会在这里卡柱,而我们之后要学习的现代优化器则有逃离鞍点的机制。
  • 既然Hessian描述了地形,那么最理想的优化方法就是在每一步都同时利用梯度和Hessian来计算出完美的下一步。牛顿法虽然完美,但是计算成本过高,不适合深度学习,但这也是所有高级优化器试图模仿和近似的目标。

在Ian书中的第四章还有最后一部分“约束优化”,这一部分暂时先跳过。现在正在学的内容主要都是无约束优化,其目标是在一个没有任何限制的广阔空间里,找到函数的最低点;这是绝大多数深度神经网络如CNN,RNN,Transformer训练的标准场景。

而另一条重要分支:约束优化,也就是书中的这一节,目标是在一系列规则或限制条件下,找到函数的最低点。这个技术路线是KKT方法等,在一些特定的机器学习方法如支持向量机SVM,以及一些特定的深度学习课题中较为重要。

由于现在我们的关注点在无约束优化上,因此这一小节暂先跳过,等到需要学习的时候我们再返回来看。

接下来我将依次介绍各种梯度下降优化器,首先依据一阶或二阶,优化器可以大体分为两类:一阶优化器 First-Order Optimizers和二阶优化器。

我们先介绍一阶优化器,然后介绍二阶优化器。

一阶优化器(First-Order Optimizers) 按照发展的先后顺序,主要有以下内容:

  1. 普通梯度下降:Gradient Descent (1847), SGD (1951)
  2. 结合动量法:Polyak Momentum (1964), Nesterov Momentum (1983)
  3. 结合Adaptive LR: AdaGrad (2011), RMSProp (2012), AdaDelta (2012)
  4. 同时结合动量和自适应梯度: Adam (2014), AdamW (2017)

二阶优化器(Second-Order Optimizers) 按照发展的先后顺序,主要有以下内容:

  1. 纯粹二阶:牛顿法 (17世纪),准牛顿法L-BFGS (1989)
  2. 近似二阶:KFAC (2015),AdaHessian (2020)

Gradient Descent

在数学上,优化(Optimization) 指的是从所有可能的解决方案中,系统地选择并找出一个或者一组最优解的过程。放到函数上来说,一般指通过改变x,来最小化函数\(f(x)\);最大化本质就是最小化,因为可以通过\(-f(x)\)来采用同样的优化方法。

我们把要最小化的函数称为目标函数(objective function)准则(criterion)。当我们对其进行最小化时,这个函数有时也被叫做代价函数(cost function)损失函数(loss function)误差函数(error function)。在不同情境或论文中作者可能会更倾向于用某一个名称,但是他们的本质都是要最小化的目标函数。

对于使函数能取到最小值的自变量\(x\),我们记作\(x^{*} = \arg\min f(x)\)最大化的话就是\(x^{*} = \arg\max_{x} f(x)\)。在微积分中,对于函数\(y=f(x)\),它的导数(derivative)**记为\(f'(x)\)\(\frac{dx}{dy}\)。如果\(f'(x)>0\),说明在x点函数是向上倾斜的,趋势是递增的,也就是说函数在x点处继续增加,函数会变大;反之如果导数小于0,则是递减趋势。

由于我们的目标是找到函数的最小值,因此:

  • 在导数大于0时,函数是“上坡”,我们应该减少x,朝上坡的反向走
  • 在导数小于0时,函数是“下坡”,我们应该增大x,继续下坡

这个策略就叫做梯度下降(gradient descent)。在数学上,泰勒一阶展开证明了梯度下降为什么是有效的:

\[ f(x+\epsilon) \approx f(x) + \epsilon f'(x) \]

这个式子表明,在x处,如果我们行进一个微小的距离\(\epsilon\),那么行进后的函数大小近似等于原本x处的函数大小,加上原本x处的函数导数乘以这个微小距离。为了让新的函数值 f(x+ϵ) 小于旧的函数值 f(x),我们只需要让增量部分 ϵf(x) 为负数即可。而保证这一项为负数的最有效方法,就是让我们选择的‘行进方向’ ϵ 与导数 f(x)的符号相反。换句话说,泰勒一阶展开证明了梯度下降是有效策略。

\(f'(x)=0\)的时候,导数无法提供往哪个方向移动的信息,因此这个点被称为临界点(critical point)驻点(stationary point)

1759333058922

如上图所示,导数为0时可能会有三种情况:

  • Local Minimum 局部最小值
  • Local Maximum 局部最大值
  • Saddle Point 鞍点,或称为Inflection Point 拐点

在上述三种情况中,我们通过二阶导数来判断:

  • 如果二阶导数\(f''(x)>0\),那么表明函数是凹的,即山谷,局部最小点
  • 如果二阶导数\(f''(x)<0\),那么表明函数是凸的,即山峰,局部最大点
  • 如果二阶导数\(f''(x)=0\),那么表面函数是平坦的,像在马鞍的中心

在梯度下降中,局部最小点是理想的停止点,这表明我们找到了一个可能的解。而局部最大值和鞍点都是糟糕的停止点,因为在局部最大点和鞍点,我们无法判断该往哪边走是正确的。标准的梯度下降算法无法区分这三种情况,只要梯度为零他就会停下来。因此现代优化算法针对最大值和鞍点的问题,发展出了动量(Momentum)随机性(Stochasticity)二阶方法(Second-Order Methods) 等解决策略。

我们的最终目标是找到全局最小点(global minimum)。一个函数可能有一个全局最小点,也可能有多个,也可能没有全局最小点。想象一个左侧往无穷大、右侧往无穷小的函数图像\(f(x) = -x^3 + 3x\)

1759333775897

上面这个图像在右侧趋向于无穷小,在数学上称为无下界(Unbounded Below)。在梯度下降中,这种函数会导致发散(Divergence),即运行过程中未能收敛到一个稳定的解,而是趋向于一个无效的或者无限的结果。发散在函数值趋向于正无穷时也有可能产生,比如如果学习率(等一下就要讲到)设置的太高,步子迈太大,一步直接跨过了山谷的最低点而到达了对面更高的位置,下一步为了下山又跨的更远,导致越走越高。

现在我们来正式看一下梯度下降在深度学习中是如何应用的。上面我们讨论的都是一维函数,即一条线,但实际操作中我们面对的总是多变量函数。

对于一个多维输入的函数\(f: \mathbb{R}^n \to \mathbb{R}\)(这里的\(\mathbb{R}^n\)指函数的输入是一个n维向量),我们队最小值的概念有一个明确的意义:最小值必须是一维的标量。因为标量可以直接比较大小,而向量无法直接比较大小。这个标量通常也被称为损失(Loss)成本(Cost)

在最小值处,有对应着的一个n维向量,这个特定的多维向量就是我们的最优解(Optimal Solution)。换句话说,判断一个向量是否是最优解的标准,就是把他代入函数,然后看计算出来的标量是不是所有可能结果中最小的。

对于一维函数,我们已经知道,对函数求导,当导数为0时,我们就找到了一个特殊的点,这个点可能是极小值、极大值或者鞍点。而对于多维向量,我们用偏导数(partial derivative)对该函数进行求导,这个时候我们可以得到一个由原本输入向量中每个元素的偏导数组成的“包含所有偏导数的向量”,记为\(\nabla_{x} f(x)\),读作“对x的梯度(gradient)”。梯度的的第\(i\)个元素是\(f\)关于\(x_i\)的偏导数。在多维情况下,临界点是梯度中所有元素都为零的点。


举例说明一下,假设我们的多变量函数是\(f(x, y) = x^{2} + 2xy + y^{3}\),那么在这里我们就有输入向量:

\[ \mathbf{x} = \begin{bmatrix} x \\ y \end{bmatrix} \]

我们的目标是计算这个函数在任意点\((x,y)\)的梯度。根据定义,对于我们这个二维函数,他的梯度就是包含了所有偏导数的向量,即:

\[ \nabla_{X} f(x, y) = \begin{bmatrix} \text{对 }x\text{ 的偏导数} \\ \text{对 }y\text{ 的偏导数} \end{bmatrix} = \begin{bmatrix} \dfrac{\partial f}{\partial x} \\ \dfrac{\partial f}{\partial y} \end{bmatrix} \]

通过计算,我们可以分别得到对x的偏导数:

\[ \frac{\partial f}{\partial x} = 2x + 2y \]

以及对y的偏导数:

\[ \frac{\partial f}{\partial y} = 2x + 3y^{2} \]

我们把两个偏导数放回第一步的向量结构中,得到:

\[ \nabla_{X} f(x, y) = \begin{bmatrix} 2x + 2y \\ 2x + 3y^{2} \end{bmatrix} \]

这就是函数\(f(x,y)\)在任意点\((x,y)\)梯度公式。用这个公式,只要你把任何一个点的坐标代入进去,你就能很快算出那个点的梯度向量

比如说,在点\((3,2)\)处:

\[ \nabla_{X} f(x=3, y=2) = \begin{bmatrix} 10 \\ 18 \end{bmatrix} \]

这个梯度向量的含义是,函数f的值上升最快的方向,是朝着x轴走10个单位,同时朝着y轴走18个单位的那个方向。在二维平面上我们可以可视化地画出来,但是对于10000维的高维向量,我们只要用这个道理去找就行了,不需要把图像画出来找。换句话说,用这个方法,对于任意维度的多维向量,我们不需要画出图来就能判断一个点处的梯度形势。

对于上面这个方向,我们知道他是最f上升最快的方向,也就是最陡的上坡路。那么在梯度下降算法中,为了让函数值下降的最快,我们就要朝着这个梯度的反方向移动,也就是朝着\(\begin{bmatrix}-10 \\-18\end{bmatrix}\)的方向移动一小步。


对单变量函数求出来的导数,和多变量函数求出来的梯度向量,它们的每一个分量都是一“上坡指南”,指明了在各自维度上让函数值增加的方向和强度。这是导数的性质。因此,对于梯度向量,将所有维度的“上坡指南”合并成的合力方向,一定是函数整体上升最陡峭的方向。书中通过基于方向导数来寻找函数值下降最快方向的数学证明确认这了这一点(P76-77),这里不再展开。我们只需要记住:梯度永远指向最陡的上坡,它的反方向就是最陡的下坡。

既然我们知道了下坡方向,那么我们就可以沿着这个方向前进,来寻找最小值。首先我们确定一个固定步长大小的正标量,记为\(\epsilon\),这个值被称为学习率(learning rate)。

对于多维向量\(X\)

\[ X_{t+1} = X_t - \epsilon \nabla_{X} f(X_t) \]

这个公式就是最速下降法(method of steepest descent)或者说梯度下降(gradient descent) 的更新规则。

通过最速下降法迭代公式,我们可以不断寻找下坡路,直到找到一个完全平坦的地方。我们可以通过迭代的方式不断逼近该目标,或者直接通过求解方程组=0来直接跳到临界点(这种直接求解的方式叫做解析解)。在现实世界中,深度学习模型的函数往往极其复杂,梯度方程组也极其庞大,根本无法直接求解析解。因此我们才需要梯度下降这种看起来笨拙但却十分有效的迭代方法。

梯度下降算法只能用于连续空间中的优化问题。但是,对于很多参数离散的问题,比如整数规划、旅行商等,虽然无法直接计算梯度,但是可以用类似的思想,不断在当前位置找到一个最好的方向然后移动一小步来找到最优解。这个方法被称为爬山(hill climbing)算法。(之所以叫做爬山,是因为爬山算法经常用于最大化目标函数的问题,比如棋类游戏、遗传算法等;而梯度下降主要用于机器学习任务,在机器学习中我们的目标几乎总是寻找最小化损失函数。)


Jacobian

在上面的梯度下降中,我们提到用标量来衡量一个输入函数(比如包含神经网络所有参数x的多维向量)的最小值(即损失值L),记作\(f: \mathbb{R}^n \to \mathbb{R}\)

但是,即便我们的神经网络最终得到的是一个标量,在神经网络中间,我们面临的是若干个多维输入到多维输出的中间层,如下图所示:

1759368379528

这个中间过程表示为\(f: \mathbb{R}^n \to \mathbb{R}^m\),即n维输入到m维输出。那么对于这样的中间层,我们如何来了解每一个输入的变化,是如何影响到每一个输出的呢?如果用梯度向量,那么对于每一个输出变量,我们都要计算n个偏导数;那么对于m个输出变量,我们就要计算m * n个偏导数。那么如何组织这m*n个偏导数,系统性地表示和使用这海量的导数信息呢?Jacobian矩阵就是为了解决这个问题而诞生的。

Jacobian矩阵提供了一个完美的、紧凑的数学结构,即使用一个矩阵,将这 m * n 个偏导数整齐地收纳其中。通过收纳,我们就可以进一步利用线性代数中的矩阵乘法,简洁、高效地表达和计算链式法则。

具体来说,假设我们有一个函数 \(f\),它接受一个 \(n\) 维的向量输入

\[ x = [x_1, x_2, \ldots, x_n]^T \]

并产生一个 \(m\) 维的向量输出

\[ y = f(x) = [f_1(x), f_2(x), \ldots, f_m(x)]^T. \]

这里的 \(f_1, f_2, \ldots, f_m\)\(m\) 个独立的函数,它们共同组成了这个多维输出。

Jacobian 矩阵(通常记作 \(\mathbf{J}\))就是将所有输出函数对所有输入变量的一阶偏导数排列成一个矩阵。 它的标准表示如下:

\[ \mathbf{J} = \begin{bmatrix} \dfrac{\partial f_1}{\partial x_1} & \dfrac{\partial f_1}{\partial x_2} & \cdots & \dfrac{\partial f_1}{\partial x_n} \\ \dfrac{\partial f_2}{\partial x_1} & \dfrac{\partial f_2}{\partial x_2} & \cdots & \dfrac{\partial f_2}{\partial x_n} \\ \vdots & \vdots & \ddots & \vdots \\ \dfrac{\partial f_m}{\partial x_1} & \dfrac{\partial f_m}{\partial x_2} & \cdots & \dfrac{\partial f_m}{\partial x_n} \end{bmatrix}. \]

在该矩阵中:

  • 每一行代表一个输出函数对所有输入变量的偏导数;你可以把第\(i\)行看作是函数\(f_i\)的梯度(的转置)。
  • 每一列代表所有输出函数对一个输入变量的偏导数。

举一个从 \(\mathbb{R}^2\) 映射到 \(\mathbb{R}^3\) 的具体例子,假设输入向量是

\[ x = \begin{bmatrix} x_1 \\ x_2 \end{bmatrix}. \]

输出向量由以下三个函数定义:

\[ f_1(x_1, x_2) = x_1^2 + 5x_2 \]
\[ f_2(x_1, x_2) = 3x_1 \sin(x_2) \]
\[ f_3(x_1, x_2) = x_1^2 x_2 \]

根据定义,这个函数的 Jacobian 矩阵 \(\mathbf{J}\) 将是一个 \(3 \times 2\) 的矩阵。我们来逐行计算它的元素:

第一行(对 \(f_1\) 求偏导):

  • \[ \dfrac{\partial f_1}{\partial x_1} = 2x_1 \]
  • \[ \dfrac{\partial f_1}{\partial x_2} = 5 \]

第二行(对 \(f_2\) 求偏导):

  • \[ \dfrac{\partial f_2}{\partial x_1} = 3\sin(x_2) \]
  • \[ \dfrac{\partial f_2}{\partial x_2} = 3x_1\cos(x_2) \]

第三行(对 \(f_3\) 求偏导):

  • \[ \dfrac{\partial f_3}{\partial x_1} = x_2^2 \]
  • \[ \dfrac{\partial f_3}{\partial x_2} = 2x_1x_2 \]

现在,把这些结果填入矩阵中,我们就得到了这个函数的 Jacobian 矩阵的表达式:

\[ \mathbf{J} = \begin{bmatrix} 2x_1 & 5 \\ 3\sin(x_2) & 3x_1\cos(x_2) \\ x_2^2 & 2x_1x_2 \end{bmatrix}. \]

这个矩阵就完整地描述了在任意点 \((x_1, x_2)\) 处,输入向量的微小变化是如何传递并影响到输出向量的。


Hessian

在梯度下降中,我们要找到最陡峭的方向,然后反向下山。然后我们设定一个学习率,代表下山的步子有多大。那么接下来,我们面临这样一个问题:我们在下山的时候,下降的高度,和预期的是一样的吗?

想要检查梯度下降步骤是否达到预期,那么我们其实就要关注梯度下降的曲率。

1759369691308

在上图的三种情况中,绿色虚线就是我们之前提到的泰勒一阶展开\(f(x+\epsilon) \approx f(x) + \epsilon f'(x)\),也就是梯度下降算法所依赖的线性预测。根据这个预测,我们可以得到移动后的新位置\(x_{\text{new}} = x - \eta f'(x)\)

我们可以注意到,一阶泰勒展开用的是约等于,这代表它只是一个近似,而忽略了更高阶的信息。这里忽略掉的,即是梯度下降算法中忽视的曲率,也即造成绿色虚线和蓝色实现存在差距的原因。这个差距的大小和方向,由二阶导数决定。在上图中:

  • 左图代表的是\(f''(x)<0\)的情况,实际下降多于线性预期
  • 中图代表的是\(f''(x)=0\)的情况
  • 右图代表的是\(f''(x)>0\)的情况,实际下降小于线性预期

为了更加精确,我们可以进行泰勒二阶展开:

\[ f(x+\epsilon) \approx \underbrace{f(x)+\epsilon f'(x)}_{\text{梯度下降的线性预测}} \;+\; \underbrace{\tfrac{1}{2}\epsilon^2 f''(x)}_{\text{现实地形的曲率修正}} \]

这个公式清晰地揭示了所有秘密:

  • 梯度下降的预期,就是前半部分的线性预测
  • 现实与预期的差距,就是后半部分的修正项

可以看到,修正项的大小,由二阶导数来决定。我们把多维函数的二阶导数(second derivative)叫做Hessian矩阵:

\[ H(f)(x)_{i,j} = \frac{\partial^2}{\partial x_i \, \partial x_j} f(x). \]

Hessian矩阵等价于梯度的Jacobian矩阵。

到这里,我们可以对上面的内容进行一个小结:

  1. 我们有一个损失函数\(f(x)\),他的类型是多维输入对单一输出
  2. 我们队\(f(x)\)进行一阶导数求导,得到它的梯度向量\(\nabla f(x)\)
  3. 我们把梯度本身看做一个新函数\(g(x)\),它的输出是一个多维向量,因为神经网络是多层的,输入的多维向量进行一次求导后我们得到的是一个中间层;对这个中间层\(g(x)\)求导,可以得到Jacobian矩阵。
  4. Jacobian矩阵\(J_g\)的第\(i\)行、第\(j\)列的元素是\((\mathbf{J}_g)_{i,j} = \frac{\partial g_i}{\partial x_j}\),这里的\(g_i\)就是函数\(g\)输出向量的第\(i\)个分量,换句话说,也就是梯度向量的第\(i\)个分量,即\(\frac{\partial f}{\partial x_i}\),代入到原式中,得到:
\[ (\mathbf{J}_g)_{i,j} = \frac{\partial}{\partial x_j} \left( \frac{\partial f}{\partial x_i} \right) = \frac{\partial^2 f}{\partial x_j \, \partial x_i} \]

可以看到,这就是我们刚才讨论的Hessian矩阵。绝大多数情况下,我们不说“梯度的Jacobian矩阵”,而是直接说“Hessian矩阵”。


现在我们已经了解了梯度下降、Hessian矩阵,以及知道如何衡量下降时是否符合预期。上面我们提到了一维泰勒二阶展开:

\[ f(x+\epsilon) \approx f(x)+\epsilon f'(x) \;+\; \frac{1}{2}\epsilon^2 f''(x) \]

我们把上式扩展到多维的泰勒二阶展开:

\[ f(x) \approx f\bigl(x^{(0)}\bigr) + \bigl(x - x^{(0)}\bigr)^{T} g + \tfrac{1}{2}\bigl(x - x^{(0)}\bigr)^{T} \mathbf{H} \bigl(x - x^{(0)}\bigr) \]

这个式子就是在当前点\(x^{(0)}\)附近一个精确的地形地图,其中\(g\)时梯度,\(H\)时Hessian矩阵。你可以认为,这个公式用数学方法描述了局部地形的具体细节。

接着,我们把梯度下降的更新规则:

\[ x = x^{(0)} - \epsilon \mathbf{g} \]

带入到多维泰勒二阶展开中,即可得到:

\[ f\bigl(x^{(0)} - \epsilon \mathbf{g}\bigr) \approx \underbrace{f\bigl(x^{(0)}\bigr)}_{\text{当前损失}} \;-\; \underbrace{\epsilon \mathbf{g}^{T}\mathbf{g}}_{\text{一阶线性预期下降}} \;+\; \underbrace{\tfrac{1}{2}\epsilon^{2}\mathbf{g}^{T}\mathbf{H}\mathbf{g}}_{\text{二阶曲率修正项}} \]

这个预测公式可以预测出梯度下降的下一步后,损失值变成了多少。其中:

  • 当前损失:我们的起点
  • 线性预期下降:基于一阶导数的乐观预测
  • 二阶曲率修正:由Hessian主导的,决定现实与预期差距的关键项

在实际中,或者说一个常规的、标准的梯度下降训练循环中,我们不需要多此一举地计算这个泰勒预测值。一般来说,程序会用 x_new = x_old - εg 计算出新位置;然后把 x_new 扔进模型里,通过一次完整的前向传播,直接计算出真实、精确的新损失值 f(x_new)。换句话说,我们总是能得到真实值,那么为什么我们在这里要去讨论泰勒二阶展开和近似的预测公式呢?——对于优化器算法设计来说,这些都是非常关键的。

不过对于大多数人来说,并不需要自己去设计优化器,只需要了解大致的原理,知道如何选择优化器、不同的优化器之间有什么区别就够了。故而在现阶段,我们只需要对这些概念留有一个大致的印象。

一般来讲,Hessian的应用包括:

  • 判断临界点类型:通过检测临界点Hessian矩阵所有特征值的正负号来了解;如果所有特征值都为正,则为局部最小值;如果所有特征值都为负,则为局部最大值;如果特征值中又有正数又有负数,则处于鞍点。
  • 分析梯度下降的性能:利用泰勒二阶展开攻势可以计算出使得损失下降最多的最优步长;在最坏的情况下(梯度方向与最大曲率方向一致),最优步长由Hessian的特征值决定。

随机梯度下降

随机梯度下降 (Stochastic Gradient Descent, SGD)的理论基础源于一篇名为《一种随机逼近方法》(A Stochastic Approximation Method) 的论文,由Herbert Robbins和Sutton Monro于1951年提出。

SGD是一种快速的优化算法,核心思想是“随机看小部分,快速迭代”。其工作方式:不像标准方法看完所有数据再更新,SGD每次只随机抽取一小批(甚至一个)数据来计算梯度,并立即调整模型参数。在GD中,我们需要把所有的训练数据都看一遍,才能计算一次梯度,然后更新一次参数(走一步)。如果数据集有几百万张,那么走一步得等到猴年马月了。SGD是一个聪明的改进,每次随机抓取一小把数据(mini batch),比如64张图片,然后就用这一小把数据来估算一下梯度的大致方向,然后立刻走一步。

SGD的核心更新公式如下:

\[ \theta := \theta - \eta \cdot \nabla_{\theta} J(\theta; x^{(i)}, y^{(i)}) \]

其中:

  • \(\theta\):theta,表示模型的参数(包含所有权重weights和偏置biases的向量),我们的目标就是通过训练找到最优的theta
  • \(:=\) 表示赋值(Assignment)操作
  • \(\eta\):eta,表示学习率(Learning Rate),控制每次参数更新的步长。如果eta很大,参数更新的幅度就会很大。
  • $\nabla_{\theta} $ 表示梯度(Gradient)。梯度是一个向量,指向损失函数值上升最快的方向,因此梯度下降需要在前面加上负号。
  • \(J(\theta; x^{(i)}, y^{(i)})\) 表示损失函数(Loss Function)。这个函数用来衡量模型在单个训练样本\((x^{(i)}, y^{(i)})\)上的预测效果有多差。\(x^i\)是第\(i\)个训练样本的特征,\(y^i\)是第\(i\)个训练样本的真实标签。

可以看出,SGD的更新公式和传统GD相比,区别进在于批量的大小。传统的梯度下降发也叫批量梯度下降法(Batch Gradient Descent, BGD),其核心思想是每次更新参数时都要用全部的训练数据来计算梯度:\(\theta := \theta - \eta \cdot \nabla_{\theta} J(\theta)\)。而SGD的损失函数只针对一个样本,因此计算速度更快。

优点:

  • 快: 计算量小,特别适合大规模数据集。更新参数的频率极高,训练速度飞快。
  • 避免局部最优: 更新方向的随机“噪声”有助于跳出不够好的解决方案,寻找全局最优解。

缺点:

  • 不稳定: 下降路径震荡,不平滑。每一步的方向可能不太准,但总体趋势是往下走的。
  • 可能不收敛: 最终可能在最优点附近徘徊,而不是精确到达。

实际中常用的是“小批量梯度下降 (Mini-batch GD)”,它是SGD和标准梯度下降的折中,兼顾了速度和稳定性。我们可以从最纯粹的SGD定义中看到,每次更新模型参数只使用一个随机选择的训练样本,比如:

  • 一行表格数据
  • 一张图片及其标签
  • 一篇文章中的一句话,或者一系列文档中的一个文档

由于现代计算机硬件特别是GPU的并行处理数据能力较高,因此一般实践中我们每次使用一个小批量(mini-batch)的样本,比如32、64、或128个来计算梯度。除了计算效率更高外,只用1个训练样本可能会非常嘈杂和随机,导致训练过程震荡很剧烈。而使用一个批次的平均梯度,能让收敛过程更加稳定(更接近真实梯度)。在深度学习相关的议题和论文中,SGD一般指代的就是小批量梯度下降,而不是纯粹的随机梯度下降。

凸函数

上面我们已经提到了,梯度为零可能是在鞍点,因而不能说明函数已经取到了极值点。但是我们都知道,对于\(y = x^2\) 这样的二次函数,它的导数\(y' = 2x\) 是单调递增的。在导数为 0 的左侧,导数一直小于 0(函数下降);在右侧,导数一直大于 0(函数上升)。换句话说,导数为零的点必然是极小值点。这里要注意,在导数的坐标图上,导数为负的意思是越往右越低。

为了将这种直觉推广到多元函数,凸函数被提了出来。对于函数 \(f(x)\),如果对任意的 \(x_1, x_2\) 以及 \(0 < \alpha < 1\),均满足:

\[ \alpha f(x_1) + (1 - \alpha)f(x_2) \geq f(\alpha x_1 + (1 - \alpha)x_2) \]

那么 \(f\) 就是 凸函数

在优化算法(如梯度下降法)中,如果目标函数是凸函数,我们就能保证:函数图像没有乱七八糟的“起伏”或“鼓包”。局部最优解就是全局最优解 。换句话说,只要一直顺着梯度往下走,最终一定能找到那个唯一的最低点,而不会掉进其他的“小坑”里。

凸优化(Convex Optimization) 研究的核心确实就是如何寻找凸函数在特定约束条件下的 最小值 。在数学和工程界,凸优化被视为一种“金标准”,因为它拥有一个极其迷人的特性: 局部最优解就是全局最优解

很多传统工程和金融问题天然具备凸性,或者可以完美简化为凸函数。在这些领域,凸优化是统治级的工具。然而,对于深度学习问题,目标函数几乎全都是非凸(non-convex)的。

虽然神经网络的全局地形一般都是非凸的,但是如果我们把视野聚焦于一个极小的范围时,任何平滑的曲线都将看起来像是一个微型的小碗。这也是后面优化器的一个基本前提,比如说Adam优化器就把局部视为凸函数。

当然,我们要时刻提醒自己鞍点的存在。优化器使用动量(momentum)来跳过这些中心梯度为零的鞍点。具体的内容详见Optimizer这一节。

Optimizer

Momentum

Polyak动量

动量(Momentum),有时候也称为Heavy Ball Method,其核心概念在1964年由苏联数学家Boris Polyak提出。最初这种方法并非用在SGD上,而是用在标准GD上。Polyak介绍了一种“heavy ball”概念,也就是数学上的动量,可以加速收敛,特别是在沟壑或者病态场景(ill-conditioned scenarios)下。

在狭长的病态峡谷中,SGD会来回震荡,前进缓慢。动量(Momentum)就是为了解决这个问题而生的。动量在更新参数的时候,不仅考虑当前计算出的梯度,还参考上一步移动的方向和速度。其效果就是,在峡谷两侧来回震荡的时候,一左一右的梯度会因为惯性的存在而相互抵消,从而抑制了震荡。而在平缓的谷底,梯度方向很稳定,惯性会不断积累,从而加速冲刺。

一句话记住就是,Polyak动量=梯度+惯性。

Nesterov动量

Nesterov Momentum(Nesterov's Accelerated Gradient, NAG)是在1983年由另一位苏联数学家Yurii Nesterov提出的。

这是对Polyak动量的一个巧妙改进。其核心思想是,不急着在当前位置计算梯度,而是先假装按照现在的惯性往前走一步,在新的位置探探路,看看那里的梯度是什么样的,然后再结合惯性决定最终该怎么走。

一句话记住就是,Nesterov动=预判梯度+惯性,比标准动量更聪明


Adaptive Gradient

动量是从方向上解决问题,而自适应梯度(Adaptive Gradient)是从步长上解决问题。

AdaGrad

RMSProp

RMSProp也是一个自适应学习率的算法。

AdaDelta

10.14的课上讲的


Adam

Adam = Momentum + Adaptive Gradient

Adam优化器

AdamW


二阶优化器

二阶优化器(Second-Order Optimizers) 按照发展的先后顺序,主要有以下内容:

  1. 纯粹二阶:牛顿法 (17世纪),准牛顿法L-BFGS (1989)
  2. 近似二阶:KFAC (2015),AdaHessian (2020)

Batching

批处理。如果把深度学习比作赛车,那么:

类别 核心概念 在“驾驶”比喻中...
A. 比赛设定 (The Mechanics) 1. 损失函数 (Loss Function) 目的地 / 地图(你要去哪里)
2. 优化算法 (Optimizer) 引擎(决定如何利用动力)
3. 批处理 (Batching) 道路状况 / 油箱大小(路有多颠簸)
B. 驾驶策略 (The Strategies) 4. 初始化 (Initialization) 出厂设置(车辆初始状态)
5. 学习率调度 (LR Scheduling) 油门/刹车策略(控制速度)
6. 规范化 (Normalization) 主动悬挂(保持稳定)
7. 正则化 (Regularization) 导航系统(防止走岔路)

Ch.4 - Ch.7是我们在训练神经网络中常用的优化方法。

Initialization

Initialization Strategies

初始化策略决定了你需要初始化的随机数的范围标准差。如果用一个装随机数的盒子来类比的话,初始化策略决定了盒子的大小和边界。

Xavier Initialization

Xavier Initialization的核心思想是保持网络中输入和输出信号的方差相同,以确保信号在整个网络中不会发散或衰减。

最初主要用于Sigmoid或Tanh这种激活函数,因为它们在大值和小值区域接近线性,且输出均值接近于零。

计算公式(以均匀分布为例):

权重 \(W\) 从一个均匀分布 \(U(-a, a)\) 中采样,其中:

\[ a = \sqrt{\frac{6}{n_{in} + n_{out}}} \]
  • \(n_{in}\):当前层的输入神经元数量(扇入数,fan-in)。
  • \(n_{out}\):当前层的输出神经元数量(扇出数,fan-out)。

Kaiming Initialization

由Kaiming He等人提出,专门为 ReLU(Rectified Linear Unit) 激活函数设计。由于 ReLU 在负半轴的输出为零(即“杀死”了一半的神经元),它会导致信号的方差在每层传播后减半。Kaiming 初始化通过乘以 \(\sqrt{2}\) 来补偿 ReLU 带来的这种损失。

适用场景主要是ReLU及其变体(如Leaky ReLU)。

计算公式(以均匀分布为例):

权重 \(W\) 从一个均匀分布 \(U(-a, a)\) 中采样,其中:

\[ a = \sqrt{\frac{6}{n_{in}}} \]
  • 注意: 公式中只使用了输入神经元数量 \(n_{in}\)

Initialization Distributions

初始化权重策略决定了初始化随机数产生的形状。如果用装随机数的盒子来类比的话,分布决定了装入盒子的随机数的形状。

Uniform Distribution

均匀分布,权重值 \(W\) 在一个特定的区间 \([-\mathbf{A}, +\mathbf{A}]\)等概率地随机选取。权重值均匀地分布在设定的范围内。Xavier 和 Kaiming 的公式会计算出这个区间半宽 \(A\) 的值。

Normal Distribution

正态分布,权重值 \(W\) 从一个均值为 \(\mu\)、标准差为 \(\sigma\)* 的高斯(正态)分布中随机选取。靠近均值 (*\(\mu=0\)) 的权重值被选中的概率最高,离均值越远,被选中的概率越低。

在初始化时,均值通常设为 \(\mu=0\)。Xavier 和 Kaiming 的公式会用于计算标准差 \(\sigma\) 的值(例如 $ \sigma^2 = \frac{2}{n_{in} + n_{out}}$ 或 $ \sigma^2 = \frac{2}{n_{in}}$)。


Learning Rate Scheduling

在DNN训练中的另一大挑战就是设置合适的learning rate schedule。不合适的设置会导致:

  • 训练的不稳定
  • 过慢的convergence
  • convergence to suboptimal solutions

学习率调度器(Learning Rate Schedulers)在深度学习训练中非常重要,它通过动态调整学习率 ,帮助模型在训练初期快速学习,在后期稳定细致地优化,从而提高收敛速度最终模型性能

具体来说,学习率(Learning Rate) 是优化器(如SGD、Adam)更新模型参数时所迈出的步长。步子太大,可能会跳过最优解;步子太小,则收敛太慢。调度器(Schedulers)就是用来自动管理和调整这个步长的机制。它不是使用一个固定的学习率,而是根据预设的规则(如周期数、损失值变化)来改变它。

其具体作用如下:

  • 训练初期(Fast Progress Early On): 刚开始训练时,模型参数离最佳值很远,可以使用 较大的学习率 。这使得模型可以快速穿过平坦的损失区域,迅速找到损失函数的“大致方向”,从而实现 快速收敛
  • 训练后期(Stable Fine-Tuning Later): 随着训练的进行,模型参数越来越接近损失函数的最低点。这时如果继续使用大步长,模型就会在最优解附近来回 震荡 ,无法稳定收敛。因此,调度器会 降低学习率 ,让模型“迈小步”,进行 稳定、细致的优化(fine-tuning) ,确保能找到更精确的最低点。

最终,通过早期的大步长,模型能更快地达到一个可接受的性能水平;通过后期的微小步长,模型能更精确地定位到损失函数的全局(或局部)最小值,从而获得 更高的准确率和更低的损失

学习率调度器就像是 汽车的油门和刹车 。刚上路时(训练初期)加大油门(高学习率)迅速提速;快到目的地时(训练后期)轻踩刹车(低学习率)缓慢而精确地停到位。这种动态控制是深度学习中提高效率和精度的一个 关键技巧


Normalization

在深度学习中,Normalization(规范化)指的是在DL模型训练过程中,通过动态调整和重塑(如调整均值和方差)层激活值的分布,来稳定训练、加速收敛并提高模型性能的技术。

Regularization

可以把正则化分为这几类:

  • 显式正则化 (Explicit Regularization) :直接在 Loss 函数后面加一项。
    • L1 / L2 (Weight Decay)
    • Entropy Regularization
  • 结构/计算正则化 :改变网络结构或计算方式。
    • Dropout (随机让神经元罢工)
    • Label Smoothing (软化目标)
  • 过程正则化 :改变训练流程。
    • Early Stopping (提前结束)
    • Adversarial Training (对抗训练)
  • 数据正则化
    • Data Augmentation (数据增强)
    • Mixup / Cutout

临时

整体来说,深度学习训练的过程可以比作在一个漆黑的高纬度山脉中寻找最低的谷底。下面简要总结一下本笔记的主要内容。

第一章梯度下降告诉我们,梯度就是上坡最快的方向,所以我们就要沿着梯度相反的方向往下走。山谷很可能是病态的,也就是有的方向特别陡峭,有的地方又特别平缓。Hessian矩阵告诉我们脚下的地形是坑还是鞍点。SGD告诉我们不需要测量整座山,只需要随机看一小块路(mini-batch)就迈步,虽然摇摇晃晃但速度极快。

第二章Optimizer告诉我们,纯粹的SGD在病态峡谷中容易撞墙震荡。Momentum给登山者增加了惯性,可以冲过平原或抵消震荡。Adaptive Gradient则能根据地面的陡峭程度自动调整步长。Adam是集大成者,又有惯性又有自适应步长。

第三章Batching决定了一次带多少物资上路。Batch Size决定了梯度的准确性和计算速度的平衡。太小震荡剧烈,太大就算太慢且容易局部最优。

第四章初始位置投放,相当于我们选择一个合适的空降点。

第五章LR Scheduling,就是控制步伐的节奏。Warm-Up就是刚开始腿冷,先慢走热个身。Annealing/Decay就是中间大步快跑。Fine-tuning就是快到谷底时,小步挪动,防止错过最低点。

第六章Normalization就是地形的平整。就好比用一个推土机,把每一层的数据分布都拉回到均值为0、方差为1的标准状态。于是一座崎岖的山路就被推的很平,让训练变得更加简单。

第一章~第六章主要是让模型在训练集上表现得尽可能好,降低Training Loss。

第七章Regularization目的是让模型在测试集上表现得好,降低Test Loss,从而防止模型过拟合。

有空再把optimization课上学的这些主题和相关内容全都整理一下:(整理完以后再archive相关课件)

Syllabus Outline (tentative)

  1. Gradient Descent
    1. Optimization problem and convexity vs non-convexity
    2. Algorithms and their performance
    3. Gradient descent
    4. Properties of GD
  2. Deep Neural Network as an Optimization
    1. Introduction to a DNN model
    2. Training process as an optimization problem
    3. Loss functions, input and output data
    4. Surrogate loss and accuracy/performance measure
  3. Stochastic Gradient Descent
    1. Batch and minibatch
    2. Stochastic gradient descent
    3. Mean and variance of the gradient
    4. Potential issues of SGD (vanishing and exploding gradients), gradient clipping
    5. Properties of SGD
  4. Adaptive Gradient Descent
    1. AdaGrad and its properties
  5. Learning with Momentum/Acceleration
    1. K-step methods
    2. Polyak and Nesterov momentums
    3. Adam, RMSprop algorithms
    4. Properties of momentum algorithms
  6. Learning Rate Adaptation
    1. Learning rate schedule
    2. Learning with warm restart
    3. Impacts of learning rate adaptation
  7. Initialization
    1. Glorot and He initialization methods, keeping gradients in check
    2. Other initialization methods (unitary matrices)
  8. Normalization Methods
    1. Batch normalization
    2. Layer normalization and its variations
    3. Weight normalization
  9. Regularization
    1. Regularization formulation
    2. Weight decay and L2 regularization
    3. Drop out – implicit regularization
    4. Regularization as a way to impose/encourage constraints
  10. Second-order Learning 1. Newton’s method 2. AdaHessian: An adaptive second-order optimizer
  11. Meta-learning ( if time ) 1. Meta-learning as a two-level optimization 2. Model-agnostic meta-learning 3. First-order meta-learning

评论 #