目录

数学与算法基础-最优化部分

最优化

优化器

梯度下降 (Gradient Descent,GD)

梯度下降法是最为经典的凸优化优化器,思想也非常明确:通过 loss 反向传导计算参数的梯度,参数往哪个方向跑可以让 loss 下降,就让参数往哪个方向更新:

<math xmlns="http://www.w3.org/1998/Math/MathML"> Δ W k = ∂ l o s s ∂ W k = ∂ l o s s ∂ Z n ∂ Z n ∂ Z n − 1 ⋅ ⋅ ⋅ ∂ Z k + 1 ∂ W k \Delta W_k= \frac{\partial {loss}} {\partial {W_k}}=\frac{\partial {loss}} {\partial {Z_{n}}}\frac{\partial {Z_n}} {\partial {Z_{n-1}}}···\frac{\partial {Z_{k+1}}} {\partial {W_k}} </math>ΔWk=∂Wk∂loss=∂Zn∂loss∂Zn−1∂Zn⋅⋅⋅∂Wk∂Zk+1

<math xmlns="http://www.w3.org/1998/Math/MathML"> W k ← W k − α Δ W k W_k←W_k−αΔW_k </math>Wk←Wk−αΔWk

解释

什么是梯度下降?

  • 想象你在一座山上,目标是找到山的最低点

  • 你每次都朝着最陡的方向走一小步

  • 这个"最陡的方向"就是梯度 [告诉我们损失函数在当前点的变化方向]

  • "走一小步"的步长就是学习率(α)

    • 太大:可能跳过最优点(就像下山时步子太大,可能跳过山谷)
    • 太小:收敛太慢(就像下山时步子太小,要走很久)
代码实战
python 复制代码
 def gradient_descent_example():
     # 初始点
     x = torch.tensor([2.0], requires_grad=True)
     learning_rate = 0.1  # 学习率α
     
     for step in range(5):
         # 前向计算
         y = 2 * x
         loss = y ** 2
         
         # 反向传播,计算梯度
         loss.backward()
         
         # 更新x:x = x - α * gradient
         with torch.no_grad():  # 更新时不需要计算梯度 不需要构建计算图
             x -= learning_rate * x.grad # 更新参数
             x.grad.zero_()  # 清除旧的梯度,为下一次迭代做准备
             # 因为每一步都是一个新的起点,需要重新计算下山的方向。
         
         print(f"Step {step}, x = {x.item():.4f}, loss = {loss.item():.4f}")
 ​
  • 正向传播就像是从山脚走到山顶的路径
  • 损失函数就是山顶的高度
  • 反向传播就是在找下山的最快路径
  • 梯度更新就是沿着这条路径走一小步

Adaptive Moment estimation (Adam)

普通梯度下降的问题:

  • 学习率固定,不够灵活
  • 每个参数用相同的学习率,不够合理
  • 容易陷入局部最小值

想象你在下山

  • 动量:你有一定的惯性,不会因为一个小石头就改变方向

    • <math xmlns="http://www.w3.org/1998/Math/MathML"> m t = β 1 m t − 1 + ( 1 − β 1 ) Δ W m_t=β_1m_{t−1}+(1−β_1)ΔW </math>mt=β1mt−1+(1−β1)ΔW
  • 自适应学习率:

    • <math xmlns="http://www.w3.org/1998/Math/MathML"> v t = β 2 v t − 1 + ( 1 − β 2 ) Δ W 2 v_t=β_2v_{t−1}+(1−β_2)ΔW^2 </math>vt=β2vt−1+(1−β2)ΔW2
    • 陡峭的地方(梯度大),你迈小步
    • 平缓的地方(梯度小),你迈大步
解释
  • 动量:记住历史梯度信息
  • 自适应学习率:不同参数有不同学习率
  • 偏差修正:解决训练初期的问题

<math xmlns="http://www.w3.org/1998/Math/MathML"> m t ^ = m t 1 − β 1 t \hat{\mathrm{m_t}}=\frac{\mathrm{m_t}}{1-\mathrm{\beta_1^t}} </math>mt^=1−β1tmt

<math xmlns="http://www.w3.org/1998/Math/MathML"> v t ^ = v t 1 − β 2 t \hat{\mathrm{v_t}}=\frac{\mathrm{v_t}}{1-\mathrm{\beta_2^t}} </math>vt^=1−β2tvt

<math xmlns="http://www.w3.org/1998/Math/MathML"> W t ← W t − 1 − α v t ^ + ϵ m t ^ \mathrm{W_t\leftarrow W_{t-1}-\frac{\alpha}{\sqrt{\hat{v_t}}+\epsilon}\hat{m_t}} </math>Wt←Wt−1−vt^ +ϵαmt^

实际使用中,通常 <math xmlns="http://www.w3.org/1998/Math/MathML"> β 1 = 0.9 , β 2 > 0.9 \beta_1=0.9,\beta_2>0.9 </math>β1=0.9,β2>0.9。BERT 源代码中,预训练的 <math xmlns="http://www.w3.org/1998/Math/MathML"> β 2 \beta_2 </math>β2为 0.98,微调的 <math xmlns="http://www.w3.org/1998/Math/MathML"> β 2 \beta_2 </math>β2为 0.999,其目的是为了减少对预训练中得到的原始参数结构的破坏,使收敛更为平缓。此外, <math xmlns="http://www.w3.org/1998/Math/MathML"> m 0 m_0 </math>m0 和 <math xmlns="http://www.w3.org/1998/Math/MathML"> v 0 v_0 </math>v0皆为初始化得来,因此训练时参数种子的设置往往对模型结果的影响较大。从上述公式可以看出,训练前期的学习率和梯度更新是比较激进的,到后期逐渐平稳。

代码实战
python 复制代码
 def adam_example():
     x = torch.tensor([2.0], requires_grad=True)
     
     # 初始化动量和自适应学习率
     # torch.zeros_like(x) 是创建一个与 x 形状相同,但所有元素都为0的新张量。
     m = torch.zeros_like(x)
     v = torch.zeros_like(x)
     beta1, beta2 = 0.9, 0.999
     learning_rate = 0.1
     epsilon = 1e-8
     
     for step in range(5):
         # 前向计算
         y = 2 * x
         loss = y ** 2
         loss.backward()
         
         with torch.no_grad():
             # 更新动量
             m = beta1 * m + (1 - beta1) * x.grad
             # 更新自适应学习率
             v = beta2 * v + (1 - beta2) * x.grad * x.grad
             
             # 偏差修正
             # 上述公式中的 t 就是代码中的 (step + 1)
             # 用 (step + 1) 是因为 step 从0开始,而公式中 t 从1开始
             m_hat = m / (1 - beta1 ** (step + 1))
             v_hat = v / (1 - beta2 ** (step + 1))
             
             # 参数更新
             x -= learning_rate * m_hat / (torch.sqrt(v_hat) + epsilon)
             x.grad.zero_()
             
         print(f"Step {step}, x = {x.item():.4f}, loss = {loss.item():.4f}")
 ​

梯度下降变种(AdamW、LAMB)

Adam Weight Decay Regularization (AdamW)

Adam 虽然收敛速度快,但没能解决参数过拟合的问题。学术界讨论了诸多方案,其中包括在损失函数中引入参数的 L2 正则项。这样的方法在其他的优化器中或许有效,但会因为 Adam 中自适应学习率的存在而对使用 Adam 优化器的模型失效。AdamW 的出现便是为了解决这一问题,达到同样使参数接近于 0 的目的。具体的举措,是在最终的参数更新时引入参数自身

<math xmlns="http://www.w3.org/1998/Math/MathML"> m t = β 1 m t − 1 + ( 1 − β 1 ) Δ W \mathrm{m_t=\beta_1m_{t-1}+(1-\beta_1)\Delta W} </math>mt=β1mt−1+(1−β1)ΔW

<math xmlns="http://www.w3.org/1998/Math/MathML"> v t = β 2 v t − 1 + ( 1 − β 2 ) Δ W 2 \mathrm{v_t~=\beta_2v_{t-1}~+~(1-\beta_2~)\Delta W^2} </math>vt =β2vt−1 + (1−β2 )ΔW2

<math xmlns="http://www.w3.org/1998/Math/MathML"> m t ^ = m t 1 − β 1 t \hat{\mathrm{m_t}}=\frac{\mathrm{m_t}}{1-\mathrm{\beta_1^t}} </math>mt^=1−β1tmt

<math xmlns="http://www.w3.org/1998/Math/MathML"> v t ^ = v t 1 − β 2 t \hat{\mathrm{v_t}}=\frac{\mathrm{v_t}}{1-\mathrm{\beta_2^t}} </math>vt^=1−β2tvt

<math xmlns="http://www.w3.org/1998/Math/MathML"> W t ← W t − 1 − α ( m t ^ v t ^ + ϵ + λ W t − 1 ) \mathrm{W_t~\leftarrow~W_{t-1}~-\alpha(\frac{\hat{m_t}}{\sqrt{\hat{v_t}}~+\epsilon}~+\lambda W_{t-1}~)} </math>Wt ← Wt−1 −α(vt^ +ϵmt^ +λWt−1 )

<math xmlns="http://www.w3.org/1998/Math/MathML"> λ λ </math>λ即为权重衰减因子,常见的设置为 0.005/0.01。这一优化策略目前正广泛应用于各大预训练语言模型。

解释
  • AdamW是 Adam 优化器的改进版本
  • 主要解决了模型过拟合的问题
  • 通过权重衰减(weight decay)实现
代码实战
python 复制代码
 def adamw_example():
     x = torch.tensor([2.0], requires_grad=True)
     m = torch.zeros_like(x)
     v = torch.zeros_like(x)
     beta1, beta2 = 0.9, 0.999
     learning_rate = 0.1
     weight_decay = 0.01  # 权重衰减系数
     epsilon = 1e-8
     
     for step in range(5):
         y = 2 * x
         loss = y ** 2
         loss.backward()
         
         with torch.no_grad():
             # Adam部分和之前一样
             m = beta1 * m + (1 - beta1) * x.grad
             v = beta2 * v + (1 - beta2) * x.grad * x.grad
             m_hat = m / (1 - beta1 ** (step + 1))
             v_hat = v / (1 - beta2 ** (step + 1))
             
             # AdamW的改进:添加权重衰减
             x -= learning_rate * (m_hat / (torch.sqrt(v_hat) + epsilon) + weight_decay * x)
             print(f"Step {step}, x = {x.item():.4f}, loss = {loss.item():.4f}")
             x.grad.zero_()

Layer-wise Adaptive Moments optimizer for Batching training (LAMB)

LAMB 优化器是 2019 年出现的一匹新秀,原论文标题后半部分叫做 "Training BERT in 76 Minutes",足以看出其野心之大。 LAMB 出现的目的是加速预训练进程,这个优化器也成为 NLP 社区为泛机器学习领域做出的一大贡献。在使用 Adam 和 AdamW 等优化器时,一大问题在于 batch size 存在一定的隐式上限,一旦突破这个上限,梯度更新极端的取值会导致自适应学习率调整后极为困难的收敛,从而无法享受增加的 batch size 带来的提速增益。LAMB 优化器的作用便在于使模型在进行大批量数据训练时,能够维持梯度更新的精度

<math xmlns="http://www.w3.org/1998/Math/MathML"> m t = β 1 m t − 1 + ( 1 − β 1 ) Δ W \mathrm{m_t=\beta_1m_{t-1}+(1-\beta_1)\Delta W} </math>mt=β1mt−1+(1−β1)ΔW

<math xmlns="http://www.w3.org/1998/Math/MathML"> v t = β 2 v t − 1 + ( 1 − β 2 ) Δ W 2 \mathrm{v_t~=\beta_2v_{t-1}~+~(1-\beta_2~)\Delta W^2} </math>vt =β2vt−1 + (1−β2 )ΔW2

<math xmlns="http://www.w3.org/1998/Math/MathML"> r t = m t v t + ϵ \mathrm{r_t~=~\frac{m_t}{\sqrt{v_t}~+\epsilon}} </math>rt = vt +ϵmt

<math xmlns="http://www.w3.org/1998/Math/MathML"> W t ← W t − 1 − α ⋅ ϕ ( ∣ ∣ W t − 1 ∣ ∣ ∣ ∣ r t + λ W t − 1 ∣ ∣ ) ( r t + λ W t − 1 ) \mathrm{W_t~\leftarrow~W_{t-1}~-\alpha\cdot\phi(\frac{||W_{t-1}||}{||r_t~+\lambda W_{t-1}||})(r_t~+\lambda W_{t-1})} </math>Wt ← Wt−1 −α⋅ϕ(∣∣rt +λWt−1∣∣∣∣Wt−1∣∣)(rt +λWt−1)

其中,\phi 是一个可选择的映射函数,一种是 <math xmlns="http://www.w3.org/1998/Math/MathML"> ϕ ( z ) = z ϕ ( z ) = z </math>ϕ(z)=z ,另一种则为起到归一化作用的 <math xmlns="http://www.w3.org/1998/Math/MathML"> ϕ ( z ) = m i n ⁡ ( m a x ⁡ ( z , γ 1 ) , γ u ) ϕ ( z ) = min ⁡ ( max ⁡ ( z , γ_1 ) , γ _u ) </math>ϕ(z)=min⁡(max⁡(z,γ1),γu) 。 <math xmlns="http://www.w3.org/1998/Math/MathML"> γ 1 γ_1 </math>γ1 和 <math xmlns="http://www.w3.org/1998/Math/MathML"> γ u γ_u </math>γu为预先设定的超参数,分别代表参数调整的下界和上界。这一简单的调整所带来的实际效果非常显著。

使用 AdamW 时,batch size 超过 512 便会导致模型效果 大幅下降, 但在 LAMB 下,batch size 可以直接提到 32,000 而不会导致精度损失

由于在下游微调预训练模型时,通常无需过大的数据集,因而 LAMB 仅在预训练环节使用。遗憾的是,LAMB 在 batch size 512 以下时无法起到显著作用,目前只能作为大体量财团的工具。

解释
  • 解决大批量训练的问题
  • 特别适合训练大型模型(如BERT)
  • 可以用更大的batch size来加速训练
代码实战
python 复制代码
 def lamb_example():
     x = torch.tensor([2.0], requires_grad=True)
     m = torch.zeros_like(x)
     v = torch.zeros_like(x)
     beta1, beta2 = 0.9, 0.999
     learning_rate = 0.1
     weight_decay = 0.01
     epsilon = 1e-8
     
     for step in range(5):
         y = 2 * x
         loss = y ** 2
         loss.backward()
         
         with torch.no_grad():
             # 计算动量(和Adam一样)
             m = beta1 * m + (1 - beta1) * x.grad
             v = beta2 * v + (1 - beta2) * x.grad * x.grad
             
             # 计算r_t
             r_t = m / (torch.sqrt(v) + epsilon)
             
             # LAMB的核心:计算范数比
             w_norm = x.norm()  # 参数有多"大"
             r_norm = (r_t + weight_decay * x).norm()  # 更新量有多"大"
             ratio = w_norm / (r_norm + epsilon) # 用范数的比值来调整学习率
             
             # 参数更新
             x -= learning_rate * ratio * (r_t + weight_decay * x)
             print(f"Step {step}, x = {x.item():.4f}, loss = {loss.item():.4f}")
             x.grad.zero_()
  • 范数

    • 用来衡量向量或矩阵"大小"的一种度量。

    • 其实就是向量的模长

      python 复制代码
       # 一维向量
       x = torch.tensor([3.0, 4.0])
       norm = x.norm()  # 结果是5.0(勾股定理:√(3² + 4²))# 二维向量(矩阵)
       x = torch.tensor([[1.0, 2.0], 
                        [3.0, 4.0]])
       norm = x.norm()  # 结果是√(1² + 2² + 3² + 4²) ≈ 5.477

选择建议:

  1. 一般任务:使用Adam
  2. 大模型训练:使用AdamW
  3. 大批量训练:使用LAMB(batch size > 512)
  4. 学习原理:从GD开始理解 记住:没有最好的优化器,只有最适合的优化器。选择要根据具体任务和场景来决定。

学习率调度(Cosine Annealing)

特点

  1. 学习率从初始值平滑地降到接近零
  2. 开始时学习率较大,快速探索
  3. 结束时学习率较小,精细调整
  4. 比固定学习率效果更好

使用场景:

  • 训练深度神经网络
  • 需要精细调整最终结果
  • 希望避免学习率突变

代码实战

<math xmlns="http://www.w3.org/1998/Math/MathML"> η t = η i n i t i a l ⋅ 1 2 ( 1 + cos ⁡ ( π ⋅ t T ) ) \eta_t = \eta_{initial} \cdot \frac{1}{2}(1 + \cos(\frac{\pi \cdot t}{T})) </math>ηt=ηinitial⋅21(1+cos(Tπ⋅t))

python 复制代码
 def get_cosine_lr(initial_lr, current_step, total_steps):
     """余弦退火学习率计算"""
     return initial_lr * 0.5 * (1 + math.cos(math.pi * current_step / total_steps))
 ​
 def adam_with_cosine_annealing():
     x = torch.tensor([2.0], requires_grad=True)
     m = torch.zeros_like(x)
     v = torch.zeros_like(x)
     beta1, beta2 = 0.9, 0.999
     initial_lr = 0.1  # 初始学习率
     total_steps = 20  # 总步数
     epsilon = 1e-8
     
     for step in range(total_steps):
         # 计算当前学习率
         current_lr = get_cosine_lr(initial_lr, step, total_steps)
         
         # 前向计算
         y = 2 * x
         loss = y ** 2
         loss.backward()
         
         with torch.no_grad():
             # Adam优化器部分
             m = beta1 * m + (1 - beta1) * x.grad
             v = beta2 * v + (1 - beta2) * x.grad * x.grad
             m_hat = m / (1 - beta1 ** (step + 1))
             v_hat = v / (1 - beta2 ** (step + 1))
             
             # 使用余弦退火的学习率更新参数
             x -= current_lr * m_hat / (torch.sqrt(v_hat) + epsilon)
             x.grad.zero_()
             
         print(f"Step {step}, lr = {current_lr:.4f}, x = {x.item():.4f}, loss = {loss.item():.4f}")
 ​
本文是转载文章,点击查看原文
如有侵权,请联系 xyy@jishuzhan.net 删除
相关推荐
_extraordinary_1 小时前
dfs刷题矩阵搜索问题
算法·矩阵·深度优先
co0t1 小时前
排序算法复习
数据结构·算法·排序算法
ChoSeitaku1 小时前
NO.58十六届蓝桥杯备战|基础算法-枚举|普通枚举|二进制枚举|铺地毯|回文日期|扫雷|子集|费解的开关|Even Parity(C++)
c++·算法·蓝桥杯
CoovallyAIHub2 小时前
清华YOLOE新发布:实时识别任何物体!零样本开放检测与分割
算法·计算机视觉
Aqua Cheng.2 小时前
京东--数据开发实习生--保险业务部门--一面凉经
java·数据结构·数据库·tcp/ip·算法·http·面试
烟锁池塘柳02 小时前
【数学建模】(启发式算法)蚁群算法(Ant Colony Optimization)的详解与应用
算法·数学建模·启发式算法
究极无敌暴龙战神3 小时前
代码随想录刷题
算法
Tony883 小时前
从回溯到动态规划
算法
椰椰椰耶3 小时前
【redis】集群 数据分片算法:哈希求余、一致性哈希、哈希槽分区算法
redis·算法·哈希算法
Anlici3 小时前
面试得知道的编程题 | 系列1:扁平化处理、过滤对象等👍
算法·面试