Skip to content

Deep Learning Optimization

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

第一章梯度下降告诉我们,梯度就是上坡最快的方向,所以我们就要沿着梯度相反的方向往下走。山谷很可能是病态的,也就是有的方向特别陡峭,有的地方又特别平缓。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
  2. Optimization problem and convexity vs non-convexity
  3. Algorithms and their performance
  4. Gradient descent
  5. Properties of GD
  6. Deep Neural Network as an Optimization
  7. Introduction to a DNN model
  8. Training process as an optimization problem
  9. Loss functions, input and output data
  10. Surrogate loss and accuracy/performance measure
  11. Stochastic Gradient Descent
  12. Batch and minibatch
  13. Stochastic gradient descent
  14. Mean and variance of the gradient
  15. Potential issues of SGD (vanishing and exploding gradients), gradient clipping
  16. Properties of SGD
  17. Adaptive Gradient Descent
  18. AdaGrad and its properties
  19. Learning with Momentum/Acceleration
  20. K-step methods
  21. Polyak and Nesterov momentums
  22. Adam, RMSprop algorithms
  23. Properties of momentum algorithms
  24. Learning Rate Adaptation
  25. Learning rate schedule
  26. Learning with warm restart
  27. Impacts of learning rate adaptation
  28. Initialization
  29. Glorot and He initialization methods, keeping gradients in check
  30. Other initialization methods (unitary matrices)
  31. Normalization Methods
  32. Batch normalization
  33. Layer normalization and its variations
  34. Weight normalization
  35. Regularization
  36. Regularization formulation
  37. Weight decay and L2 regularization
  38. Drop out – implicit regularization
  39. Regularization as a way to impose/encourage constraints
  40. Second-order Learning
    1. Newton’s method
    2. AdaHessian: An adaptive second-order optimizer
  41. Meta-learning ( if time )
    1. Meta-learning as a two-level optimization
    2. Model-agnostic meta-learning
    3. First-order meta-learning

Ch.1 梯度下降

1.1 计算机的数值计算

数值解

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

  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) 组成,在后面我们会展开来详细讲解。在学习具备动态功能的高级优化器之前,我们还是要从最为传统的优化器开始,了解梯度下降等基本概念。

1.2 基于梯度的优化方法

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的特征值决定。

1.3 SGD 随机梯度下降

随机梯度下降 (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一般指代的就是小批量梯度下降,而不是纯粹的随机梯度下降。

Ch.2 Optimizer

2.1 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动=预判梯度+惯性,比标准动量更聪明


2.2 Adaptive Gradient

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

AdaGrad

RMSProp

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

AdaDelta

10.14的课上讲的


2.3 Adam

Adam = Momentum + Adaptive Gradient

Adam优化器

AdamW


2.4 二阶优化器

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

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

Ch.3 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是我们在训练神经网络中常用的优化方法。

Ch.4 Initialization

4.1 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}\)

4.2 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}}$)。


Ch.5 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) ,确保能找到更精确的最低点。

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

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


Ch.6 Normalization

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

Projects

Project.1 不同优化器对比实验

在入门章节,我们在MNIST手写数据集上进行了LeNet-5实验,了解了入门深度学习训练的过程。在学习优化器后,我们今天做一个新的实验,在CIFAR-10数据集上测试不同优化器的训练效果。

1759683747292

CIFAR-10相比于MNIST,类别数量并没有增加,还是10个类别,但是识别对象的复杂度明显有了一个巨大的提升:图片采用32x32像素的RGB三通道色彩图像,背景嘈杂,图像分辨率低。

我们使用如下三个架构来在MNIST和CIFAR10上进行实验:

  • A smiple MLP on MNIST
  • A simple CNN on CIFAR-10
  • VGG-13 on CIFAR-10

简单的MLP:(针对MNIST任务)

  • 输入1张1x28x28的图片,1就是1个通道(灰度图)
  • Flatten把二维图片压平成一个1x784的一维向量(也就是把像素方阵的所有像素排成一队)
  • 接着通过第一个隐藏层Gemm:这是神经网络的第一个全连接层,它接收长度为784的输入向量,并通过一个1024x784的权重矩阵B,将其线性变换为一个长度为1024的隐藏特征向量,这是网络从原始像素中学习抽象特征的开始
  • Relu:紧跟其后的激活函数,用于增加非线性,让网络能学习更复杂的关系
  • 接着跟着第二个隐藏层
  • 最后连接到最后一个Gemm,这是输出层,它将来自第二个隐藏层的1024的高级特征,把他们映射为10个输出值

1759972513735

三层CNN:(针对CIFAR任务)

  • 输入为1张RGB彩色3通道的32x32像素的图片
  • 数据从左到右依次经过三个相同的处理单元:Conv 卷积层 -> Relu 激活层 -> MaxPool 最大池化层
  • Flatten 展平层
  • Gemm 全连接层
  • 最终输出一个1x10的向量,对应10个类别的可能性,分数最高的就是预测结果

1759972003992

VGG13:(针对CIFAR任务)

  • 下图所示是VGG16,我们在任务中使用的是同一家族谱系的VGG13,并且针对CIFAR进行了简化。VGG是牛津大学视觉几何研究组(Visual Geometry Group )发明的,并以此为名。VGG13有10个卷积层+3个全连接层;VGG16有13个卷积层+3个全连接层。
  • VGG13/VGG16在卷积核尺寸上有着严格的设定:统一使用3x3尺寸的卷积核

1759973178745

具体的训练代码可以参见REPO:

项目报告参见:

结果如下:

Optimizer MLP on MNIST CNN on CIFAR-10 VGG13 on CIFAR-10
SGD 91.82% 50.64% 52.68%
AdaGrad 97.77% 69.98% 68.11%
RMSProp 98.29% 68.65% 73.61%
Adam 98.32% 70.15% 74.61%
Polyak_0.9 97.64% 73.00% 65.75%
Nesterov_0.9 97.62% 72.91% 66.02%

其中Adam整体表现最好,Polyak和Nesterov动量法在CNN on CIFAR-10任务上优于Adam。


Project.2 不同初始化和学习率调度器实验

数据集和DNN

ImageNet-50 (Customized)

ImageNet是一个用于图像分类任务的、相对较小的ImageNet数据集子集。ImageNet原始版本包含超过1400万张图像,覆盖了大约22000个类别,是推动深度学习在CV领域发展的关键数据集。

本Project筛选了来自于ImageNet的50个目标类别(来自ILSVRC-2012)。在preprocessing步骤,每个image都被转换为RGB格式,并resize到224 x 224的大小,来保证输入数据的一致性。每个类别大约有1300张照片,包含丰富的动物、植物和其他日常生活中耳熟能详的东西。

VGG-19

VGG-19是VGG(Visual Geometry Group)系列的卷积神经网络架构,以小尺寸的卷积核(3x3)和深层结构而闻名。

本Project部署了一个改进版本的VGG-19(由He et al改进的),与原版VGG-19相比,该改进版本在早期阶段采用了更大的convolutional layer,并增加了model的深度,来提升在ImageNet训练集上的efficiency和performance。

ResNet-34

ResNet-34是残差网络(Residual Network, ResNet) 架构中的一个特定版本,34代表这个特定网络结构中可学习的权重层(含卷积层、全连接层)的总数。

残差学习引入了残差块(Residual Block) 的概念,有时候也被称为捷径连接(Skip Connection)。每个残差块包含两个3x3的卷积层,然后是batch normalization和ReLU激活。残差块允许信息跳过一些层直接传递,从而缓解了DNN中梯度消失和网络退化的问题,这使得网络可以构建得非常深而保持训练效果。

1761496622789

上图展示了三种DNN网络架构的对比:

左侧 中间 右侧
VGG-19 普通34层 ResNet-34
CNN CNN CNN
无残差 无残差 有残差

.

项目目标与结果分析

分析研究weight initializatio methods和learning rate schedulers如何影响DNN model training。

Weight initialization决定了model的参数在训练开始前是如何设置的,其将影响model的:

  • 是否能收敛(converge)
  • 收敛速度(convergence speed)
  • 稳定性(stability)

Learning rate schedulers决定了梯度更新(gradient updates)的步长(step size),影响cost function如何适应training step和convergence performance。

Learning rate scheduler将严重影响:

  • training results:loss function
  • test results:test accuacy
Weight Initialization on VGG-19

在DNN训练中设置合适的初始值是一个巨大的挑战。不合适的初始值会导致梯度爆炸、梯度消失、收敛缓慢、degraded accuracy等问题。

本实验的目标是对比下列两种initialization strategies在VGG-19上的表现:

  • Xavier Initialization
  • Kaiming Initialization

以及下面两种distributions:

  • Uniform Distribution
  • Normal Distribution

在本次实验中,我们使用SGD optimizer来突出观测不同initialziation的表现。

因此实验应当由四组对照:

  • Xavier Initialization + Uniform Distribution
  • Xavier Initialization + Normal Distribution
  • Kaiming Initialization + Uniform Distribution
  • Kaiming Initialization + Normal Distribution

产出的结果包括:

  • Training loss vs. epochs
  • Training accuracy vs. epochs
  • Validation loss vs. epochs
  • Validation accuracy vs. epochs
  • Final test accuracy and test loss
LR Scheduling on ResNet-34

在DNN训练中的另一大挑战就是设置合适的learning rate schedule。不合适的设置会导致训练不稳定、收敛过慢、收敛到局部最优等。

在本次实验中,我们将使用ResNet model来探索不同学习率调度对训练的影响:

(1)Constant Learning Rate

恒定学习率,即学习率在整个训练过程中保持不变(不进行任何调度或调整)。这是最简单的基准方法。

(2)Exponential Learning Rate

Gradually reducing the LR from max to min according to some fixed schedule,即根据一些固定的调度表,逐步将学习率从最大值减小到最小值。

Exponential Learning Rate (指数学习率)是上述所描述的一种具体方法数,即让学习率按指数函数或几何级数衰减。

(3)Multistep Learning Rate

多步学习率也是固定调度表的一种,就是在预定的训练周期(epochs)点上,突然下降一个固定的因子(例如每隔 30 个周期降为原来的 0.1 倍)。

(4)Cyclic Learning Rate

Having multiple cycles of LR reduction from max to min,即具有从最大值到最小值多次循环衰减的学习率。

循环学习率是让学习率在一个预定的边界(最大值和最小值)之间周期性变化(通常呈三角形或正弦波)。

(5)Cosine Annealing Warm Restarts

带温暖重启的余弦退火也是上述从最大值到最小值多次循环衰减的学习率,其遵循余弦函数的形状衰减。当学习率达到最小值时,它会“温暖重启”并跳回最大值,然后再次开始余弦衰减。

在ResNet-34上,我们使用Adam优化器。初始化权重采用Kaiming Uniform Distribution(也是PyTorch默认的初始化方法)。


下面列出四种非常数的学习率调度器的具体参数设置要求:

Exponential (指数衰减):

  • 参数设置是 \(\text{Gamma} = 0.95\)
  • 作用机制是学习率在每个周期(或步数)结束后,都会乘以衰减因子 \(0.95\),实现学习率的平滑下降。

Multistep (多步衰减):

  • 参数设置是 \(\text{Milestones} = [40, 60, 80]\)\(\text{Gamma} = 0.1\)
  • 作用机制是学习率会在预先设定的第 \(40\)\(60\)\(80\) 个训练周期时骤降。
  • 在每个里程碑点,当前学习率将乘以 \(0.1\)

Cyclic (循环学习率):

  • 参数设置是 \(\text{base\_lr} = 5\text{e-}4\) (\(\text{最小学习率}\)),\(\text{max\_lr} = 5\text{e-}3\) (\(\text{最大学习率}\)),\(\text{step\_size\_up} = 2380\),以及 \(\text{mode} = \text{'triangular'}\)
  • 作用机制是学习率将在 \(0.0005\)\(0.005\) 之间周期性地循环变化。
  • 一个完整的上升或下降半周期需要 \(2380\) 步(迭代)。
  • 循环波形采用基础的 三角形模式

Cosine Annealing Warm Restarts (带温暖重启的余弦退火):

  • 参数设置是 \(\text{T\_0} = 10\)\(\text{T\_mult} = 3\)
  • 作用机制是学习率将遵循余弦函数进行衰减,并周期性地重启。
  • 初始周期长度 (\(\text{T\_0}\)) 为 \(10\) 个周期。
  • 周期乘法因子 (\(\text{T\_mult}\)) 为 \(3\)。每次学习率重启后,下一个衰减周期的时间长度将是上一次的 \(3\) 倍。

Project.3 Transformer on French-English

本实验主要用来探讨不同的Normalization and Regularization如何影响实验结果:

  • Normalization determines how the model's activations are scaled and shifted during training, which directly affect the shape of the layer input/output distributions (mean and variance) and can greatly influence whether the model will converge, the convergence speed, and the stability of learning.
  • Regularization methods help prevent overfitting and improve generalization ability by controlling model complexity and constraining parameter updates. They can prevent units from co-adapting or penalize large weights, leading to smoother functions that are less sensitive to input variations and therefore achieve better generalization on unseen test data.

简单来说就是:

  • Normalization通过调整模型内部激活值的分布(即均值和方差),来帮助模型更快、更稳定地收敛。
  • Regularization则是一项防止过拟合、提升泛化能力的技术;它通过控制模型的复杂度(比如惩罚过大的权重),使模型不过度依赖训练数据的细节,从而在未见过的测试数据上表现更好。

更加简单来说,就是:

  • Normalization让model学得动、学得快
  • Regularization让model学得好、考得好

IWSLT2017 Dataset

IWSLT2017是一个非常经典且大小适中的机器翻译数据集:

  • 来源:TED演讲的转录稿和翻译
  • Training Set: 210,000 sentence pairs
  • Validation Set: 890 pairs
  • Testing Set: 8,000 pairs

每一个sample都包含一个source sentence in French and its corresponding target translation in English.


Transformer Model

具体的网络架构可以参见DNN笔记。简单复习一下就是:

  • Transformer不依赖于传统的RNN的序列处理机制,可以高效捕捉长距离依赖,并且可以并行训练
  • Multi-head Attention Mechanism 多头(就是多角度的意思)注意力机制可以让model在处理一个词的时候,能够关注到句子中其他所有相关的词
  • Position-wise Feed-Forward Networks, FFNs会对每一个词独立加工,换句话说,句子中的每一个词都是单独而独立处理的
  • Transformer在把词的含义向量(Embedding)喂给model之前,会给它先加上一个位置向量(Positional Encoding)

Transformer中两个核心的组件:

  • Encoder 编码器:处理source sentence,在我们的项目中就是法语;编码器会生成一系列contextual representations(上下文表示),实现“理解”。
  • Decoder 解码器:接收目标语言句子,在我们的项目中就是英语;解码器的目标是学会生成,简单来说,就是通过看着解码器的理解来逐词生成对应的英语句子。

那我们来看一下在翻译任务上,Transformer是如何工作的:首先,在训练过程中,encoder先看到所有的法语,理解法语关系;然后decoder看着所有的法语关系,然后一个英语单词一个英语单词地看,每一次都要对着所有法语关系和训练集中强制性喂过来的下一个词进行关联记忆,记住法语关系和decoder已经知道的词语+decoder应该输出的下一个词之间的预测关系。

换句话说,在训练过程中:

  • INPUT:Context,即[法语理解] + [正确的英语开头,比如"The cat"]
  • TARGET:让模型“猜”中[下一个词"is"]。这里是强制让模型学习的。

训练结束后,model看到一段法语后,就能不断猜出下一个对应的英语单词,然后不断地对着这段法语和已经猜出的英语单词,继续猜测下一个单词,直到所有的单词都被猜测完毕。这个像滚雪球一样的过程就是自回归(Autoregressive)。


Baseline Model

在最初的 Transformer 配置中,dropout (0.1) 在“Add & Norm”步骤之前被应用,使用 Adam 优化器,并且没有额外的正则化方法。该模型使用 后置层规范化 (post-layer normalization,在残差连接之后应用),并在最初的 4,000 个更新步骤中执行 学习率预热 (learning rate warm-up)。权重是使用 Xavier 均匀分布 (Xavier Uniform distribution)进行初始化的。在本项目中,我们将以这个 基线模型 (baseline model)为基础,来探索不同正则化和规范化方法所带来的影响。

Dropout 是一种强大的技术,在训练时,它会以 10% 的概率(0.1)随机“丢弃”或“关闭”神经元。这迫使模型不能过度依赖任何一个神经元,而是要学习更鲁棒、更多样化的特征,从而 防止过拟合

在本次实验中,我们需要首先复现baseline model,其核心要点包括:

  1. Normalization: Post-layer Normalization
  2. Regularization: Dropout 0.1
  3. Optimizer: Adam
  4. LR Scheduling: Learning Rate Warm-Up
  5. Initialization: Xavier Uniform Distribution

成功复现Baseline实验后,我们将探索不同的方法。


BLEU

BLEU (Bilingual Evaluation Understudy) Score 是一种自动评估机器翻译质量的标准指标 ,它通过将模型生成的翻译与一个或多个人工翻译的“标准答案”进行比较,并计算两者之间共有的词组(n-grams)的重叠程度来打分。在您的项目中, BLEU 分数将是衡量模型好坏的最终“成绩单” :一个更高的 BLEU 分数意味着模型的翻译在用词和句子结构上更接近人类的翻译,即 翻译质量更好


实验环境设置

硬件配置:

  • Memory: 64GB
  • GPU: H200 - 140GB

实验完成内容:

  • 初步测试实验(在notebook中)
  • 正式实验:10个长时实验(epoch=50),总运行时长约40小时

评论 #