四、网络优化方法



前言

  • 前面我们学习了用哪些激活函数以及用哪些损失函数来对预测结果进行评估,接下来,我们继续学习如何对神经网络进行优化。

一、梯度下降

  • 梯度下降法是一种寻找使损失函数最小化的方法。
  • 从数学上的角度来看,梯度的方向是函数增长速度最快的方向,那么梯度的反方向就是函数减少最快的方向,所以有:
    W i j n e w = W i j o l d − η ∂ E ∂ W i j W_{ij}^{new} = W_{ij}^{old} - η \frac{∂E}{∂W_{ij}} Wijnew=WijoldηWijE
    • E E E 代表损失函数的值
    • W i j W_{ij} Wij 表示计算图中第 i j ij ij 的权重
    • η η η 是学习率。

1.1 几种梯度下降的方式的对比

  • 在进行模型训练时,有三个基础的概念:

    • 1.Epoch: 使用全部数据对模型进行以此完整训练,训练轮次
    • 2.Batch_size: 使用训练集中的小部分样本对模型权重进行以此反向传播的参数更新,每次训练每批次样本数量
    • 3.Iteration: 使用一个 Batch 数据对模型进行一次参数更新的过程
  • 假设数据集有 50000 个训练样本,现在选择 Batch Size = 256 对模型进行训练。

    • 每个 Epoch 要训练的图片数量:50000
    • 训练集具有的 Batch 个数: 50000 256 + 1 \frac{50000}{256}+1 25650000+1 = 196
    • 每个 Epoch 具有的 Iteration 个数:196
    • 10个 Epoch 具有的 Iteration 个数:1960
  • 在深度学习中,梯度下降的几种方式的根本区别就在于 Batch Size不同,如下表所示:

梯度下降方式Training Set SizeBatch SizeNumber of Batches
BGDNN1
SGDN1N
Mini-BatchNBN/B+1

1.2 梯度下降优化办法

1.2.1 指数加权平均

1.2.1.1 原理
  • 指数移动加权平均则是参考各数值,并且各数值的权重都不同,距离越远的数字对平均数计算的贡献就越小(权重较小),距离越近则对平均数的计算贡献就越大(权重越大)。
  • 指数加权平均的基本思想是对过去的观测值赋予递减的权重,最近的观测值权重最高,而较早的观测值权重逐渐降低。
    v t = { v 1 t = 0 β v t − 1 + ( 1 − β ) x t t > 0 v_t = \begin{cases} v_1 & t = 0 \\ βv_{t-1}+(1-β)x_t & t>0 \end{cases} vt={v1βvt1+(1β)xtt=0t>0
    • v t v_t vt 是时间步 t t t 处的指数加权平均值
    • x t x_t xt 是时间步 t t t 处的实际观测值
    • β β β 是一个介于 0 和 1 之间的超参数,控制过去观测值的权重衰减速率。
      • β β β 越接近 1,对过去观测值的依赖越大,平滑效果越好,但响应新数据的速度会变慢。
1.2.1.2 数学计算
  • 示例数据

    • 假设我们有以下每天的温度数据(单位:摄氏度):
    • x x x = [30,32,31,33,30,32,34,35,33,31]
  • 参数设置

    • 我们选择一个 β β β值,例如 β β β = 0.9这个参数决定了过去观测值的权重衰减速率。
  • 计算指数加权平均

    • 我们从第一个观测值开始,初始化 v 0 v_0 v0 为 0(或者可以选择 x 1 x_1 x1作为初始值)。
    • v 0 v_0 v0 = 0
    • 然后,按照公式递归地计算每个时间步的指数加权平均值:
    • β v t − 1 + ( 1 − β ) x t βv_{t-1}+(1-β)x_t βvt1+(1β)xt
  • 计算过程

    • t = 1
      • v 1 v_1 v1 = 0.9×0+0.1×30=3.0
    • t = 2
      • v 2 v_2 v2 = 0.9×3.0+0.1×32=5.9
    • t = 3
      • v 3 v_3 v3 = 0.9×5.9+0.1×31=8.41
    • t = 4
      • v 4 v_4 v4 = 0.9×8.41+0.1×33=10.869
    • t = 5
      • v 5 v_5 v5 = 0.9×10.869+0.1×30=12.7821
    • t = 6
      • v 6 v_6 v6 = 0.9×12.7821+0.1×32=14.70389
    • t = 7
      • v 7 v_7 v7 = 0.9×14.70389+0.1×34=16.633501
    • t = 8
      • v 8 v_8 v8 = 0.9×16.633501+0.1×35=18.4701509
    • t = 9
      • v 9 v_9 v9 = 0.9×18.4701509+0.1×33=20.12313581
    • t = 10
      • v 1 0 v_10 v10 = 0.9×20.12313581+0.1×31=21.610822229
  • 结果

    • 计算得到的指数加权平均值为: v = v= v=[3.0, 5.9, 8.41, 10.869, 12.7821, 14.70389, 16.633501, 18.4701509, 20.12313581, 21.610822229]
1.2.1.3 代码演示

代码如下(示例):

import numpy as np
import matplotlib.pyplot as plt
plt.rcParams['font.sans-serif'] = ['SimHei']  # 用来正常显示中文标签
plt.rcParams['axes.unicode_minus'] = False  # 用来正常显示负号


# 原始温度数据
x = [30, 32, 31, 33, 30, 32, 34, 35, 33, 31]

# 参数设置
beta = 0.9

# 初始化指数加权平均值
v = [0]

# 计算指数加权平均值
for t in range(1, len(x)):
    v_t = beta * v[t-1] + (1 - beta) * x[t]
    v.append(v_t)

# 绘制图像
plt.figure(figsize=(10, 6))
plt.plot(x, label='原始温度数据', marker='o', linestyle='-')
plt.plot(v, label='经过加权平均处理的数据', marker='x', linestyle='--')


# 设置 x 轴刻度
plt.xticks(ticks=range(len(x)), labels=[f'第 {i+1} Day' for i in range(len(x))])

# 添加标题和标签
plt.title('温度数据原数据与经过指数加权平均值处理后的数据对比')
plt.xlabel('天')
plt.ylabel('温度(°C)')
plt.legend()

# 显示图像
plt.grid(True)
plt.show()

在这里插入图片描述

1.2.1.4 优点
  • 平滑梯度:指数加权平均可以平滑梯度,减少训练过程中的波动,有助于更快地收敛。
  • 响应新数据:尽管依赖于过去的数据,但指数加权平均能够快速响应新的梯度信息,从而适应数据的变化。
1.2.1.5 缺点
  • 超参数选择:需要仔细选择 β β β,不同的任务可能需要不同的 β β β 值。
  • 初始偏差:在训练初期,指数加权平均可能会有较大的偏差。

1.2.2 动量算法Momentum

  • 动量算法常用于加速梯度下降算法的收敛过程。
  • 它通过累积过去梯度的方向来加快学习速度,从而帮助模型更快地达到最优解。
1.2.2.1 原理
  • 动量算法的主要思想是在参数更新时不仅考虑当前梯度方向,还考虑了之前梯度的方向,这样可以避免在平坦区域或鞍点附近震荡,有助于模型更快地穿越这些区域。
  • 更新公式
    θ t + 1 = θ t − η ⋅ ▽ J ( θ t ) θ_{t+1}=θ_t - η \cdot ▽J(θ_t) θt+1=θtηJ(θt)
  • 其中, θ t θ_t θt 是参数在第 t t t 次迭代时的值, η η η 是学习率, ▽ J ( θ t ) ▽ J(θ_t) J(θt) 是损失函数关于参数 θ θ θ 在第 t t t 次迭代时的梯度。
  • 动量算法引入了一个“速度”变量 v v v,这个变量会累积过去的梯度信息。动量算法的更新规则如下:
    • 1.计算速度 v v v
      • v t = γ v t − 1 + η ⋅ ▽ J ( θ t ) v_t=γ v_{t-1} + η \cdot ▽J(θ_t) vt=γvt1+ηJ(θt)
      • 其中, γ γ γ 是动量项(通常设置为0.9), v 0 = 0 v_0=0 v0=0 η η η 是学习率, ▽ J ( θ t ) ▽ J(θ_t) J(θt) 是损失函数对参数的梯度
    • 2.更新参数 θ θ θ
      • θ t + 1 = θ t − v t θ_{t+1}=θ_t-v_t θt+1=θtvt
1.2.2.2 数学计算
  • 例子背景

    • 假设我们的损失函数 J ( θ ) J(θ) J(θ)形状像一个山谷,具有以下特点:
      • 损失函数在某些方向上变化平缓,而在其他方向上变化陡峭。
      • 存在一个全局最小值点,位于山谷的底部。
  • 标准梯度下降

    • 首先,我们来看一下标准梯度下降算法的表现。假设初始参数 θ = θ= θ=(5,5),学习率 η η η = 0.1。每次迭代中,参数更新公式为:
      θ t + 1 = θ t − η ⋅ ▽ J ( θ t ) θ_{t+1}=θ_t - η \cdot ▽J(θ_t) θt+1=θtηJ(θt)
    • 由于损失函数在某些方向上变化平缓,梯度在这些方向上会非常小。因此,标准梯度下降可能会在这些平缓区域中缓慢移动,甚至可能在接近最小值点时出现震荡。
  • 动量算法

    • 现在,我们来看看动量算法如何改善这种情况。假设动量系数 γ γ γ = 0.9,学习率 η η η = 0.1。动量算法的更新步骤如下:

      • 初始化速度 v 0 = ( 0 , 0 ) v_0=(0, 0) v0=(0,0)
      • 对于每个迭代 t t t :
        • 1.计算速度 v v v v t = γ v t − 1 + η ⋅ ▽ J ( θ t ) v_t=γ v_{t-1} + η \cdot ▽J(θ_t) vt=γvt1+ηJ(θt)
        • 2.更新参数 θ θ θ θ t + 1 = θ t − v t θ_{t+1}=θ_t-v_t θt+1=θtvt
  • 具体迭代过程

    • 假设我们在第 t t t 次迭代时,参数为 θ t = θ_t= θt= (3,4),梯度为 ∇ J ( θ t ) ∇J(θ_t) J(θt) = (−0.5,−0.8)。
      • 计算速度 v v v
        • v t = 0.9 ⋅ v t − 1 + 0.1 ⋅ ▽ J ( θ t ) v_t=0.9 \cdot v_{t-1} + 0.1 \cdot ▽J(θ_t) vt=0.9vt1+0.1J(θt)
        • 假设 v t − 1 v_{t-1} vt1 = (0.1, 0.2)
        • v t v_t vt = 0.9(0.1, 0.2)+0.1(−0.5,−0.8)
        • v t v_t vt = (0.04, 0.10)
      • 更新参数
        • θ t + 1 = θ t − v t θ_{t+1}=θ_t-v_t θt+1=θtvt
        • θ t + 1 = ( 3 , 4 ) − ( 0.04 , 0.10 ) θ_{t+1}=(3, 4)-(0.04, 0.10) θt+1=(3,4)(0.04,0.10)
  • 优势体现

    • 在这个例子中,动量算法通过累积过去的速度 v v v,即使在梯度较小的情况下也能保持一定的更新速度。这有助于参数更快地穿过平缓区域,减少震荡,并且更稳定地向全局最小值点靠近。
1.2.2.3 代码演示

代码如下(示例):

import torch
import matplotlib.pyplot as plt

def test03():
    # 1 初始化权重参数
    w = torch.tensor([1.0], requires_grad=True, dtype=torch.float32)
    y = ((w ** 2) / 2.0).sum()
    # 2 实例化优化方法,SGD指定参数beta=0.9
    optimizer = torch.optim.SGD([w], lr=0.01, momentum=0.9)
    # 3 第一次更新计算梯度,并对参数进行更新
    optimizer.zero_grad()
    y.backward()
    optimizer.step()
    print('第1次: 梯度w.grad: %f,更新后的权重:%f' % (w.grad.numpy(), w.detach().numpy()))
    # 4 第2次更新 计算梯度,并对参数进行更新
    # 使用更新后的参数机选输出结果
    y = ((w ** 2) / 2.0).sum()
    optimizer.zero_grad()
    y.backward()
    optimizer.step()
    print('第2次: 梯度w.grad: %f, 更新后的权重:%f' % (w.grad.numpy(), w.detach().numpy()))

>>>1: 梯度w.grad: 1.0000000000, 更新后的权重:0.99000000952: 梯度w.grad: 0.9900000095, 更新后的权重:0.9711000323

1.2.2.4 优点
  • 加速收敛:动量算法能够帮助梯度下降过程更快地穿过平坦区域,因为累积的速度会在这些区域积累,从而加快参数更新的步伐。
  • 减少震荡:在接近最小值点时,梯度可能会变得很小且方向不稳定,导致参数更新频繁震荡。动量算法通过累积过去梯度的方向,可以在一定程度上抵消这种震荡,使优化过程更加稳定。
  • 克服局部极小值:在某些情况下,动量可以帮助算法越过浅层的局部极小值,继续向全局极小值前进。
1.2.2.5 缺点
  • 虽然动量算法有很多优点,但在使用时也需要注意一些问题,比如参数的调整。如果动量系数 γ γ γ 设置得过大,可能会导致算法过于依赖于历史梯度,反而造成更新步长过大,难以收敛到最优点。因此,在实际应用中需要根据具体任务调整学习率 η η η 和动量系数 γ γ γ

1.2.3 AdaGrad

  • 是一种自适应学习率的优化算法,它在训练过程中为每个参数分配不同的学习率。与传统的固定学习率方法相比,AdaGrad 能够更好地处理稀疏数据和非平稳目标函数
1.2.3.1 原理
  • AdaGrad 的核心思想是为每个参数维护一个累积梯度平方和,并使用这个累积值来调整每个参数的学习率。具体来说,对于第 t t t 次迭代,参数 θ i θ_i θi 的更新公式为:
  • 更新公式
    θ i ( t + 1 ) = θ i ( t ) − η G i , i t + ε ⋅ g i t θ_{i}^{(t+1)}=θ_i^{(t)} - \frac{η}{\sqrt {G_{i,i}^{t}+ε}} \cdot g_i^{t} θi(t+1)=θi(t)Gi,it+ε ηgit
  • 其中:
    • θ i ( t ) θ_i^{(t)} θi(t) 是在第 t t t 次迭代时参数 θ i θ_i θi 的值
    • η η η 是初始学习率
    • g i t g_i^{t} git 是在第 t t t 次迭代时参数 θ i θ_i θi 的梯度
    • G i , i t G_{i,i}^{t} Gi,it 是在第 i i i 个参数的梯度平方的累计和,即 G i , i t = ∑ τ = 1 ( t ) ( g i τ ) 2 G_{i,i}^{t}= \sum_{τ=1}^{(t)}(g_i^{τ})^2 Gi,it=τ=1(t)(giτ)2
    • ε ε ε 是一个很小的常数,用于防止除零错误,通常取 1 0 − 8 10^{-8} 108
1.2.3.2 代码演示

代码如下(示例):

import torch
import matplotlib.pyplot as plt

def test04():
    # 1 初始化权重参数
    w = torch.tensor([1.0], requires_grad=True, dtype=torch.float32)
    y = ((w ** 2) / 2.0).sum()
    # 2 实例化优化方法:adagrad优化方法
    optimizer = torch.optim.Adagrad([w], lr=0.01)
    # 3 第1次更新 计算梯度,并对参数进行更新
    optimizer.zero_grad()
    y.backward()
    optimizer.step()
    print('第1次: 梯度w.grad: %f, 更新后的权重:%f' % (w.grad.numpy(), w.detach().numpy()))
    # 4 第2次更新 计算梯度,并对参数进行更新
    # 使用更新后的参数机选输出结果
    y = ((w ** 2) / 2.0).sum()
    optimizer.zero_grad()
    y.backward()
    optimizer.step()
    print('第2次: 梯度w.grad: %f, 更新后的权重:%f' % (w.grad.numpy(), w.detach().numpy()))
1.2.3.3 优点
  • 自适应学习率:AdaGrad 为每个参数分配不同的学习率,能够更好地处理不同特征的尺度差异。
  • 处理稀疏数据:对于稀疏数据,AdaGrad 能够更有效地更新稀疏特征的参数,因为这些特征的梯度平方累积值较小,导致学习率较大。
1.2.3.4 缺点
  • 学习率衰减过快:由于梯度平方的累积和不断增加,学习率会逐渐变小,可能导致训练过程提前停止。
  • 内存消耗:需要存储每个参数的梯度平方累积和,增加了内存开销。

1.2.4 RMSProp

  • RMSProp 优化算法是对 AdaGrad 的优化. 最主要的不同是,其使用指数移动加权平均梯度替换历史梯度的平方和。
1.2.4.1 原理
  • RMSProp 的核心思想是为每个参数维护一个按元素平方的梯度的移动平均值,并使用这个移动平均值来调整每个参数的学习率。具体来说,对于第 t t t 次迭代,参数 θ i θ_i θi 的更新公式为:
    θ i ( t + 1 ) = θ i ( t ) − η E [ g 2 ] i ( t ) + ε ⋅ g i ( t ) θ_{i}^{(t+1)}=θ_i^{(t)} - \frac{η}{\sqrt {E{[g^2]}_{i}^{(t)}+ε}} \cdot g_i^{(t)} θi(t+1)=θi(t)E[g2]i(t)+ε ηgi(t)
  • 其中:
    • θ i ( t ) θ_i^{(t)} θi(t) 是在第 t t t 次迭代时参数 θ i θ_i θi 的值
    • η η η 是初始学习率
    • g i t g_i^{t} git 是在第 t t t 次迭代时参数 θ i θ_i θi 的梯度
    • E [ g 2 ] i ( t ) E{[g^2]}_{i}^{(t)} E[g2]i(t) 是在第 i i i 个参数的梯度平方的移动平均值,通常使用指数加权平均来计算:
      E [ g 2 ] i ( t ) = β E [ g 2 ] i ( t − 1 ) + ( 1 − β ) ( g i ( t ) ) 2 E{[g^2]}_{i}^{(t)}=βE{[g^2]}_{i}^{(t-1)}+(1-β)(g_{i}^{(t)})^2 E[g2]i(t)=βE[g2]i(t1)+(1β)(gi(t))2
      • 其中, β β β 是衰减率,通常取 0.9
    • ε ε ε 是一个很小的常数,用于防止除零错误,通常取 1 0 − 8 10^{-8} 108
1.2.4.2 代码演示

代码如下(示例):

import torch
import matplotlib.pyplot as plt

def test05():
    # 1 初始化权重参数
    w = torch.tensor([1.0], requires_grad=True, dtype=torch.float32)
    y = ((w ** 2) / 2.0).sum()
    # 2 实例化优化方法:RMSprop算法,其中alpha对应这beta
    optimizer = torch.optim.RMSprop([w], lr=0.01, alpha=0.9)
    # 3 第1次更新 计算梯度,并对参数进行更新
    optimizer.zero_grad()
    y.backward()
    optimizer.step()
    print('第1次: 梯度w.grad: %f, 更新后的权重:%f' % (w.grad.numpy(), w.detach().numpy()))
    # 4 第2次更新 计算梯度,并对参数进行更新
    # 使用更新后的参数机选输出结果
    y = ((w ** 2) / 2.0).sum()
    optimizer.zero_grad()
    y.backward()
    optimizer.step()
    print('第2次: 梯度w.grad: %f, 更新后的权重:%f' % (w.grad.numpy(), w.detach().numpy()))
1.2.4.3 优点
  • 自适应学习率:RMSProp 为每个参数分配不同的学习率,能够更好地处理不同特征的尺度差异。
  • 处理稀疏数据:对于稀疏数据,RMSProp 能够更有效地更新稀疏特征的参数,因为这些特征的梯度平方累积值较小,导致学习率较大。
  • 防止学习率过快衰减:与 AdaGrad 相比,RMSProp 使用移动平均值而不是累积和,避免了学习率过快衰减的问题。
1.2.4.4 缺点
  • 超参数敏感性:RMSProp 有几个超参数需要调优。这些超参数的选择对优化性能有显著影响,但它们的最优值可能因问题而异,需要通过实验来确定。这增加了模型调参的复杂性。
  • 内存消耗:RMSProp 需要为每个参数维护一个梯度平方的移动平均值,这会增加内存消耗。对于大规模模型,这可能会成为一个问题,尤其是在资源有限的环境中。
  • 对初始学习率的依赖:RMSProp 的性能对初始学习率的选择较为敏感。如果初始学习率设置不当,可能会导致优化过程收敛缓慢或无法收敛。虽然可以通过学习率调度器(如学习率衰减策略)来部分解决这个问题,但这增加了额外的复杂性。
  • 缺乏全局视角:RMSProp 主要关注局部梯度的变化,缺乏对全局优化路径的考虑。这可能导致在某些复杂优化问题中,RMSProp 无法找到全局最优解,而只是停留在局部最优解。

1.2.5 Adam

  • Adam优化算法(Adaptive Moment Estimation,自适应矩估计)将 Momentum 和 RMSProp 算法结合在一起。
    • 修正梯度: 使⽤梯度的指数加权平均
    • 修正学习率: 使⽤梯度平⽅的指数加权平均
    • 权重衰减:直接作用域参数更新步骤,这实际上改变了梯度计算的方式,可能导致与预期的行为有所偏差
1.2.5.1 代码演示

代码如下(示例):

import torch
import matplotlib.pyplot as plt

def test06():
    # 1 初始化权重参数
    w = torch.tensor([1.0], requires_grad=True)
    y = ((w ** 2) / 2.0).sum()
    # 2 实例化优化方法:Adam算法,其中betas是指数加权的系数
    optimizer = torch.optim.Adam([w], lr=0.01, betas=[0.9, 0.99])
    # 3 第1次更新 计算梯度,并对参数进行更新
    optimizer.zero_grad()
    y.backward()
    optimizer.step()
    print('第1次: 梯度w.grad: %f, 更新后的权重:%f' % (w.grad.numpy(), w.detach().numpy()))
    # 4 第2次更新 计算梯度,并对参数进行更新
    # 使用更新后的参数机选输出结果
    y = ((w ** 2) / 2.0).sum()
    optimizer.zero_grad()
    y.backward()
    optimizer.step()
    print('第2次: 梯度w.grad: %f, 更新后的权重:%f' % (w.grad.numpy(), w.detach().numpy()))
1.2.5.2 Adam 的优点
  • 自适应学习率:Adam 为每个参数分配不同的学习率,能够更好地处理不同特征的尺度差异。
  • 处理稀疏数据:对于稀疏数据,Adam 能够更有效地更新稀疏特征的参数。
  • 快速收敛:结合了 Momentum 和 RMSProp 的优点,能够在多种情况下快速收敛。
  • 对初始学习率的鲁棒性:相对于其他优化算法,Adam 对初始学习率的选择较为鲁棒。

1.2.6 AdamW

  • AdamW在Adam的基础上做了一些改进,尤其是在权重衰减(weight decay)的处理上有所不同。
    • 改进点:AdamW 的主要改进在于正确地实现了权重衰减。它将权重衰减作为 L2 正则化项显式地加入到损失函数中,而不是像Adam那样修改梯度。因此,AdamW可以更精确地控制模型复杂度,并有助于防止过拟合。
    • 分离更新:AdamW 明确区分了梯度更新和权重衰减更新。这意味着即使没有梯度(即梯度为零),也会应用权重衰减。这种做法使得AdamW更符合理论上的优化实践。
    • 性能提升:实验表明,在某些任务上,尤其是当使用较大学习率时,AdamW 的表现优于Adam。这是因为 AdamW 更好地保持了优化器的特性,同时提供了有效的正则化效果。

1.2.7 Nesterov 动量

1.2.7.1 原理
  • 在计算梯度之前先根据当前的速度做一个预测性的移动,然后再基于这个新位置的梯度来调整最终的更新方向。
  • 这相当于给模型一个“预见未来”的能力,从而避免不必要的震荡并提高收敛效率。
1.2.7.2 公式
  • 预测性移动: θ ′ = θ t − γ v t − 1 θ' = θ_t-γv_{t-1} θ=θtγvt1
  • 计算梯度: g t = ▽ θ J ( θ ′ ) g_t=▽_θJ(θ') gt=θJ(θ)
  • 更新速度: v t = γ v t − 1 + η g t v_t=γv_{t-1}+ηg_t vt=γvt1+ηgt
  • 更新参数: θ t + 1 = θ t − v t θ_{t+1}=θ_t-v_t θt+1=θtvt
1.2.7.3 公式解读
  • NAG首先利用之前的动量 v t − 1 v_{t-1} vt1 对参数做一个临时的更新到 θ ′ θ' θ ,然后在此基础上计算新的梯度 g t g_t gt
  • 这样做的好处是可以更好地捕捉即将到来的地形变化,提前调整更新策略,减少震荡,使得优化路径更为平滑
1.2.7.3 Nesterov 动量的优势
  • 更有效的探索:NAG能够更快地识别出正确的下降方向,因为它提前考虑了动量的影响,减少了无效的探索。
  • 更好的稳定性:由于提前感知到了可能的变化,NAG可以在遇到陡峭的斜坡时及时减速,避免了过度振荡的问题。
  • 加速收敛:特别是在复杂的非凸优化问题中,NAG往往能提供更快的收敛速度和更高的最终性能。

1.2.8 NAdam

1.2.8.1 原理
  • NAdam(Nesterov-accelerated Adaptive Moment Estimation)结合了 Nesterov 动量和 Adam 优化器的特点。它实质上是在 Adam 的基础上添加了 Nesterov 预读(预读步),这有助于加速收敛。
1.2.8.2 主要特性
  • 引入Nesterov动量:与传统的动量方法不同,Nadam使用了Nesterov动量,即先根据当前动量预测下一步的位置,再计算该位置处的梯度来进行更新。这有助于更准确地捕捉梯度变化趋势。
  • 平滑梯度更新:Nadam能够提供更加平滑的梯度更新路径,减少了震荡,从而加快了收敛速度并提高了最终模型的质量。
  • 保持Adam的优点:仍然保留了Adam自适应学习率的优势,适用于各种类型的机器学习任务,包括深度神经网络训练。
1.2.8.3 应用场景:
  • NAdam适合于那些需要更快收敛或者希望利用动量效应来加速训练过程的任务。
  • 特别是在处理复杂的数据集时,Nadam可能会表现出更好的稳定性和效率。

1.2.9 RAdam

1.2.9.1 原理
  • RAdam是为了解决Adam在训练初期由于方差估计偏差大而导致的学习不稳定问题而设计的。
  • 具体来说,Adam依赖于二阶矩(平方梯度的指数加权平均)来调整学习率,但在训练早期这些估计往往不够准确,可能导致不稳定的更新。
1.2.9.2 主要特性
  • 动态修正机制:RAdam引入了一个称为“rectification”(校正)的过程,用于评估二阶矩估计是否足够可靠。如果估计不可靠,则会临时忽略自适应学习率调整,转而采用固定学习率进行更新。随着训练的推进,当估计变得可靠后,再逐渐恢复到正常的自适应学习率调整。
  • 提高训练稳定性:通过这种方式,RAdam能够在训练初期提供更稳定的学习过程,减少因为初始阶段不良估计带来的负面影响。
  • 自动调节:RAdam可以在不需要手动调整的情况下,根据训练进程自动决定何时应用自适应学习率,简化了超参数调优工作。
1.2.9.3 应用场景
  • RAdam特别适用于那些对训练初期稳定性要求较高的任务,比如大规模图像分类、目标检测等视觉任务以及自然语言处理中的长序列建模。
  • 此外,对于一些难以找到合适学习率的情况,RAdam也能提供帮助

1.2.10 对比NAdam和RAdam

  • NAdam:通过集成Nesterov动量来改进Adam,提供了更平滑的梯度更新路径和潜在的更快收敛速度。
  • RAdam:专注于解决Adam训练初期不稳定的问题,通过动态修正机制确保训练过程更加平稳,尤其在训练开始阶段表现更好。

二、反向传播

2.1 基本思想

  • 反向传播算法分为两个主要步骤:前向传播(Forward Pass)和反向传播(Backward Pass):
    • 前向传播(Forward Pass):
      • 输入数据通过网络的每一层,逐层传递,直到输出层。
      • 每一层的输出作为下一层的输入。
      • 最终得到网络的预测输出。
    • 反向传播(Backward Pass):
      • 计算损失函数关于网络输出的梯度。
      • 从输出层开始,逐层向前计算每个参数的梯度。
      • 使用链式法则(Chain Rule)将梯度从输出层传递回输入层。
      • 更新网络参数,以最小化损失函数。
        在这里插入图片描述

2.2 运算过程

  • 对下边的神经网络来进行反向传播
    请添加图片描述
  • 前向传播的运算过程
    在这里插入图片描述
  • 然后进行反向传播
    在这里插入图片描述
  • 梯度(导数)已经计算出来,下面进行反向传播与参数更新:

在这里插入图片描述

  • 如果要想求误差 E E E w 1 w_1 w1 的导数,误差 E E E w 1 w_1 w1 的求导路径不止一条,这会稍微复杂一点,但换汤不换药,计算过程如下所示:
    在这里插入图片描述

2.3 代码演示

代码如下(示例):

import torch
from torch import nn
from torch import optim

# 创建神经网络类
class Model(nn.Module):
    # 初始化参数
    def __init__(self):
        # 调用父类方法
        super(Model, self).__init__()
        # 创建网络层
        self.linear1 = nn.Linear(2, 2)
        self.linear2 = nn.Linear(2, 2)
        # 初始化神经网络参数
        self.linear1.weight.data = torch.tensor([[0.15, 0.20], [0.25, 0.30]])
        self.linear2.weight.data = torch.tensor([[0.40, 0.45], [0.50, 0.55]])
        self.linear1.bias.data = torch.tensor([0.35, 0.35])
        self.linear2.bias.data = torch.tensor([0.60, 0.60])

    # 前向传播方法
    def forward(self, x):
        # 数据经过第一层隐藏层
        x = self.linear1(x)
        # 计算第一层激活值
        x = torch.sigmoid(x)
        # 数据经过第二层隐藏层
        x = self.linear2(x)
        # 计算第二层激活值
        x = torch.sigmoid(x)
        return x    


if __name__ == '__main__':
    # 定义网络输入值和目标值
    inputs = torch.tensor([[0.05, 0.10]])
    target = torch.tensor([[0.01, 0.99]])
    # 实例化神经网络对象
    model = Model()
    output = model(inputs)
    print("output-->", output)
    loss = torch.sum((output - target) ** 2) / 2    # 计算误差
    print("loss-->", loss)
    # 优化方法和反向传播算法
    optimizer = optim.SGD(model.parameters(), lr=0.5)
    optimizer.zero_grad()
    loss.backward()
    print("w1,w2,w3,w4-->", model.linear1.weight.grad.data)
    print("w5,w6,w7,w8-->", model.linear2.weight.grad.data)
    optimizer.step()
    # 打印神经网络参数
    print(model.state_dict())

三、学习率衰减办法

3.1 等间隔学习率衰减

在这里插入图片描述

代码如下(示例):

def test_StepLR():
    # 0.参数初始化
    LR = 0.1  # 设置学习率初始化值为0.1
    iteration = 10
    max_epoch = 200
    # 1 初始化参数
    y_true = torch.tensor([0])
    x = torch.tensor([1.0])
    w = torch.tensor([1.0], requires_grad=True)
    # 2.优化器
    optimizer = optim.SGD([w], lr=LR, momentum=0.9)
    # 3.设置学习率下降策略
    scheduler_lr = optim.lr_scheduler.StepLR(optimizer, step_size=50, gamma=0.5)
    # 4.获取学习率的值和当前的epoch
    lr_list, epoch_list = list(), list()

    for epoch in range(max_epoch):
        lr_list.append(scheduler_lr.get_last_lr()) # 获取当前lr
        epoch_list.append(epoch) # 获取当前的epoch
        for i in range(iteration):  # 遍历每一个batch数据
            loss = ((w*x-y_true)**2)/2.0 # 目标函数
            optimizer.zero_grad()
            # 反向传播
            loss.backward()
            optimizer.step()
        # 更新下一个epoch的学习率
        scheduler_lr.step()
    # 5.绘制学习率变化的曲线
    plt.plot(epoch_list, lr_list, label="Step LR Scheduler")
    plt.xlabel("Epoch")
    plt.ylabel("Learning rate")
    plt.legend()
    plt.show()

3.2 指定间隔学习率衰减

在这里插入图片描述

代码如下(示例):

def test_MultiStepLR():
    torch.manual_seed(1)
    LR = 0.1
    iteration = 10
    max_epoch = 200
    weights = torch.randn((1), requires_grad=True)
    target = torch.zeros((1))
    print('weights--->', weights, 'target--->', target)
    optimizer = optim.SGD([weights], lr=LR, momentum=0.9)
    # 设定调整时刻数
    milestones = [50, 125, 160]
    # 设置学习率下降策略
    scheduler_lr = optim.lr_scheduler.MultiStepLR(optimizer, milestones=milestones, gamma=0.5)
    lr_list, epoch_list = list(), list()

    for epoch in range(max_epoch):
        lr_list.append(scheduler_lr.get_last_lr())
        epoch_list.append(epoch)
        for i in range(iteration):
            loss = torch.pow((weights - target), 2)
            optimizer.zero_grad()
            # 反向传播
            loss.backward()
            # 参数更新
            optimizer.step()
        # 更新下一个epoch的学习率
        scheduler_lr.step()
    plt.plot(epoch_list, lr_list, label="Multi Step LR Scheduler\nmilestones:{}".format(milestones))
    plt.xlabel("Epoch")
    plt.ylabel("Learning rate")
    plt.legend()
    plt.show()

3.3 按指数学习率衰减

在这里插入图片描述

代码如下(示例):

def test_ExponentialLR():
    # 0.参数初始化
    LR = 0.1  # 设置学习率初始化值为0.1
    iteration = 10
    max_epoch = 200
    # 1 初始化参数
    y_true = torch.tensor([0])
    x = torch.tensor([1.0])
    w = torch.tensor([1.0], requires_grad=True)
    # 2.优化器
    optimizer = optim.SGD([w], lr=LR, momentum=0.9)
    # 3.设置学习率下降策略
    gamma = 0.95
    scheduler_lr = optim.lr_scheduler.ExponentialLR(optimizer, gamma=gamma)
    # 4.获取学习率的值和当前的epoch
    lr_list, epoch_list = list(), list()

    for epoch in range(max_epoch):
        lr_list.append(scheduler_lr.get_last_lr())
        epoch_list.append(epoch)
        for i in range(iteration):  # 遍历每一个batch数据
            loss = ((w * x - y_true) ** 2) / 2.0
            optimizer.zero_grad()
            # 反向传播
            loss.backward()
            optimizer.step()
        # 更新下一个epoch的学习率
        scheduler_lr.step()
    # 5.绘制学习率变化的曲线
    plt.plot(epoch_list, lr_list, label="Multi Step LR Scheduler")
    plt.xlabel("Epoch")
    plt.ylabel("Learning rate")
    plt.legend()
    plt.show()

四、正则化办法

4.1 Dropout正则化

  • Dropout 是一种常用的正则化技术,广泛应用于深度学习模型中,特别是神经网络。
  • 它的主要目的是减少模型的过拟合风险,提高模型的泛化能力。
  • 通过在训练过程中随机“丢弃”(即设置为零)一部分神经元,Dropout 可以强制模型学习更多的独立特征,从而提高模型的鲁棒性。

4.1.1 Dropout 的基本思想

  • 随机丢弃神经元:在每次前向传播时,随机选择一部分神经元,将其输出设置为零,同时将剩余神经元的输出放大,以保持整体输出的期望不变。
  • 训练和推理的区别:在训练过程中启用 Dropout,在推理(即测试或部署)过程中禁用 Dropout,以确保模型的输出稳定。

4.1.2 Dropout 的工作原理

  • 训练阶段
    • 随机生成掩码:生成一个与当前层神经元数量相同的随机二值向量(掩码),每个元素以概率 p p p 被设置为 1,以概率 1 − p 1-p 1p 被设置为 0。
    • 应用掩码:将生成的掩码与当前层的输出相乘,从而将部分神经元的输出设置为零。
    • 缩放输出:为了保持整体输出的期望不变,通常将未被丢弃的神经元的输出乘以 1 p \frac{1}{p} p1
  • 推理阶段
    • 禁用 Dropout:在推理阶段,不使用 Dropout,直接使用所有神经元的输出。
    • 保持输出一致:为了确保训练和推理阶段的输出一致,通常在推理阶段将输出乘以 p p p

4.1.3 代码演示

代码如下(示例):

import torch
import torch.nn as nn
import torch.optim as optim
import numpy as np
import matplotlib.pyplot as plt

# 定义一个简单的神经网络
class SimpleNN(nn.Module):
    def __init__(self, input_size, hidden_size, output_size, dropout_prob):
        super(SimpleNN, self).__init__()
        self.fc1 = nn.Linear(input_size, hidden_size)
        self.relu = nn.ReLU()
        self.dropout = nn.Dropout(p=dropout_prob)
        self.fc2 = nn.Linear(hidden_size, output_size)
    
    def forward(self, x):
        out = self.fc1(x)
        out = self.relu(out)
        out = self.dropout(out)  # 在训练时启用 Dropout
        out = self.fc2(out)
        return out

# 参数设置
input_size = 2
hidden_size = 10
output_size = 1
dropout_prob = 0.5
learning_rate = 0.01
num_epochs = 1000

# 创建模型
model = SimpleNN(input_size, hidden_size, output_size, dropout_prob)

# 损失函数和优化器
criterion = nn.MSELoss()
optimizer = optim.SGD(model.parameters(), lr=learning_rate)

# 输入数据和标签
X = torch.tensor([[0, 0], [0, 1], [1, 0], [1, 1]], dtype=torch.float32)
Y = torch.tensor([[0], [1], [1], [0]], dtype=torch.float32)

# 训练循环
for epoch in range(num_epochs):
    model.train()  # 设置模型为训练模式
    optimizer.zero_grad()
    outputs = model(X)
    loss = criterion(outputs, Y)
    loss.backward()
    optimizer.step()
    
    if (epoch + 1) % 100 == 0:
        print(f'Epoch [{epoch+1}/{num_epochs}], Loss: {loss.item():.4f}')

# 测试
model.eval()  # 设置模型为评估模式
with torch.no_grad():
    test_outputs = model(X)
    print("Predictions:")
    print(test_outputs)

4.1.4 Dropout 的优点

  • 减少过拟合:通过随机丢弃神经元,Dropout 强制模型学习更多的独立特征,减少了对特定训练样本的依赖。
  • 提高泛化能力:Dropout 使模型更加鲁棒,提高了在未见过的数据上的表现。
  • 简化模型选择:Dropout 可以作为一种替代方法,减少对复杂正则化技术的需求,简化模型选择过程。

4.2 批量归一化(BN层)

  • 批量归一化(Batch Normalization,简称 BN)是一种广泛应用于深度学习中的技术,旨在加速训练过程并提高模型的泛化能力。BN 通过规范化每个批次中输入的分布,使得网络在训练过程中更加稳定,从而减少内部协变量偏移(Internal Covariate Shift)的问题。

4.2.1 批量归一化的基本思想

  • 内部协变量偏移:在深度神经网络中,每一层的输入分布会随着训练过程中的参数更新而发生变化,这种现象称为内部协变量偏移。内部协变量偏移会导致训练过程不稳定,从而影响模型的收敛速度和性能。
  • 规范化:批量归一化通过规范化每个批次中输入的分布,使得每一层的输入分布更加稳定,从而减少内部协变量偏移的影响。

4.2.2 数学公式

  • 在这里插入图片描述
    f ( x ) = γ ⋅ x − μ B σ B 2 + ε + β f(x)=γ \cdot \frac{x-μ_B}{\sqrt{σ_B^2+ε}}+β f(x)=γσB2+ε xμB+β
    • γ γ γ β β β 是可学习的参数,它相当于对标准化后的值做了一个线性变换, γ γ γ 为系数, β β β 为偏置
    • ε ε ε 通常指为 1e-5 也就是 1 0 − 5 10^{-5} 105,避免分母为 0
    • μ B μ_B μB 表示变量的均值
    • σ B 2 σ_B^2 σB2 表示变量的方差

4.2.3 代码演示

代码如下(示例):

import torch
import torch.nn as nn
import torch.optim as optim
import numpy as np
import matplotlib.pyplot as plt

# 定义一个简单的神经网络
class SimpleNN(nn.Module):
    def __init__(self, input_size, hidden_size, output_size):
        super(SimpleNN, self).__init__()
        self.fc1 = nn.Linear(input_size, hidden_size)
        self.bn1 = nn.BatchNorm1d(hidden_size)  # 添加批量归一化层
        self.relu = nn.ReLU()
        self.fc2 = nn.Linear(hidden_size, output_size)
    
    def forward(self, x):
        out = self.fc1(x)
        out = self.bn1(out)  # 应用批量归一化
        out = self.relu(out)
        out = self.fc2(out)
        return out

# 参数设置
input_size = 2
hidden_size = 10
output_size = 1
learning_rate = 0.01
num_epochs = 1000

# 创建模型
model = SimpleNN(input_size, hidden_size, output_size)

# 损失函数和优化器
criterion = nn.MSELoss()
optimizer = optim.SGD(model.parameters(), lr=learning_rate)

# 输入数据和标签
X = torch.tensor([[0, 0], [0, 1], [1, 0], [1, 1]], dtype=torch.float32)
Y = torch.tensor([[0], [1], [1], [0]], dtype=torch.float32)

# 训练循环
for epoch in range(num_epochs):
    model.train()  # 设置模型为训练模式
    optimizer.zero_grad()
    outputs = model(X)
    loss = criterion(outputs, Y)
    loss.backward()
    optimizer.step()
    
    if (epoch + 1) % 100 == 0:
        print(f'Epoch [{epoch+1}/{num_epochs}], Loss: {loss.item():.4f}')

# 测试
model.eval()  # 设置模型为评估模式
with torch.no_grad():
    test_outputs = model(X)
    print("Predictions:")
    print(test_outputs)

4.2.4 批量归一化的优点

  • 加速训练:通过规范化输入分布,BN 使得每一层的输入分布更加稳定,从而加速训练过程。
  • 提高泛化能力:BN 可以减少模型对特定训练样本的依赖,提高模型的泛化能力。
  • 允许使用更高的学习率:BN 使得模型对学习率的敏感性降低,可以使用更高的学习率进行训练。
  • 减少对权重初始化的依赖:BN 减少了对权重初始化的敏感性,使得模型更容易训练。

总结

  • 这篇文章中我们对优化网络的各种办法做了详细解释,在不同数据集和条件下,我们要对网络做不同的优化来提高模型的准确率。
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值