目录
[一、反向传播算法:梯度计算的 "高效引擎"](#一、反向传播算法:梯度计算的 “高效引擎”)
[1. 链式法则:反向传播的数学基石](#1. 链式法则:反向传播的数学基石)
[2. 反向传播的核心逻辑:从结果反向 "回溯"](#2. 反向传播的核心逻辑:从结果反向 “回溯”)
[3. 复杂函数实战:Sigmoid 函数的反向传播](#3. 复杂函数实战:Sigmoid 函数的反向传播)
[二、参数初始化:模型训练的 "黄金起点"](#二、参数初始化:模型训练的 “黄金起点”)
[1. 手动初始化:灵活定制参数分布](#1. 手动初始化:灵活定制参数分布)
[2. 经典初始化方法:Xavier 初始化](#2. 经典初始化方法:Xavier 初始化)
[3. Module 模型的初始化技巧](#3. Module 模型的初始化技巧)
[4. 初始化核心原则](#4. 初始化核心原则)
[5. 初始化选择](#5. 初始化选择)
[三、优化算法全家桶:参数更新的 "高效策略"](#三、优化算法全家桶:参数更新的 “高效策略”)
[1. 优化问题的核心挑战](#1. 优化问题的核心挑战)
[2. 随机梯度下降(SGD):优化算法的 "基石"](#2. 随机梯度下降(SGD):优化算法的 “基石”)
[SGD 实操代码(MNIST 数据集)](#SGD 实操代码(MNIST 数据集))
[SGD 关键特性](#SGD 关键特性)
[3. 动量法(Momentum):给梯度下降加 "惯性"](#3. 动量法(Momentum):给梯度下降加 “惯性”)
[动量法实操代码(PyTorch 内置)](#动量法实操代码(PyTorch 内置))
[4. 自适应学习率算法:给参数 "定制步长"](#4. 自适应学习率算法:给参数 “定制步长”)
[(2)RMSProp:解决 Adagrad 后期收敛乏力](#(2)RMSProp:解决 Adagrad 后期收敛乏力)
[(4)Adam:动量法 + RMSProp 的 "强强联合"](#(4)Adam:动量法 + RMSProp 的 “强强联合”)
[5. 主流优化算法核心对比](#5. 主流优化算法核心对比)
深度学习模型的训练过程,本质是 "精准计算梯度 + 合理初始化参数 + 高效更新参数" 的闭环。反向传播算法解决了 "如何算梯度" 的问题,参数初始化决定了模型的 "起点",而各类优化算法则负责 "如何高效更新参数"。这篇文章会把这三大核心技术的原理讲透,用通俗的逻辑和实操代码,帮你彻底掌握深度学习优化的底层逻辑。
一、反向传播算法:梯度计算的 "高效引擎"
在简单模型中,我们可以手动计算参数梯度,但面对 100 层的深层网络,手动求导几乎不可能。反向传播算法正是为解决这个问题而生 ------ 它本质是链式求导法则的工程实现,也是 PyTorch 自动求导的核心,能高效计算每个参数的梯度。
1. 链式法则:反向传播的数学基石
链式法则是复合函数求导的核心。举个直观例子,假设函数 f(x, y, z) = (x + y) * z,我们令 q = x + y,则 f = q * z:
- 先求外层函数导数:
∂f/∂q = z,∂f/∂z = q; - 再求内层函数导数:
∂q/∂x = 1,∂q/∂y = 1; - 链式组合得到目标梯度:
∂f/∂x = ∂f/∂q * ∂q/∂x = z * 1,∂f/∂y = z * 1,∂f/∂z = q。
这个过程的核心的是:梯度可以逐层传递,将复杂函数的求导拆解为多个简单函数的求导乘积,这也是反向传播能高效计算深层网络梯度的关键。
2. 反向传播的核心逻辑:从结果反向 "回溯"
反向传播的本质是 "从损失函数出发,反向逐层计算每个参数的梯度",步骤如下:
- 前向传播:输入数据通过网络层计算,得到最终预测结果和损失函数值;
- 反向传播:从损失函数开始,计算损失对输出层的梯度,再逐层回溯,通过链式法则计算损失对每一层参数的梯度;
- 梯度存储 :将每个参数的梯度存储在对应的参数对象中(如 PyTorch 中参数的
grad属性),为后续参数更新做准备。
3. 复杂函数实战:Sigmoid 函数的反向传播
以 Sigmoid 函数 f(w, x) = 1/(1 + e^-(w₀x₀ + w₁x₁ + w₂)) 为例,反向传播会将其拆解为多个简单子步骤:
- 拆解函数为:
f = 1/a、a = 1 + b、b = e^c、c = -(w₀x₀ + w₁x₁ + w₂); - 从最外层开始求导:
∂f/∂a = -1/a²,梯度值沿计算图反向传递; - 逐层回溯计算:
∂f/∂b = ∂f/∂a * ∂a/∂b = -1/a²(a=1+b的导数为 1),再计算∂f/∂c = ∂f/∂b * ∂b/∂c = -1/a² * e^c; - 最终得到参数梯度:
∂f/∂w₀ = ∂f/∂c * ∂c/∂w₀ = -1/a² * e^c * (-x₀) = x₀ * (f * (1 - f))(利用 Sigmoid 函数的导数性质f' = f(1 - f)简化)。
这个过程充分体现了反向传播的优势:无论函数多复杂,只要拆解为简单运算,就能通过梯度传递高效求出所有参数的梯度。
二、参数初始化:模型训练的 "黄金起点"
参数初始化是模型训练的第一步,直接决定模型能否收敛。如果参数初始值过大,会导致激活函数输出饱和(如 Sigmoid 函数输出趋近于 0 或 1),梯度消失;如果初始值过小,梯度会被不断缩小,模型学习缓慢。
1. 手动初始化:灵活定制参数分布
PyTorch 支持直接通过 Tensor 或 NumPy 定制参数分布,适合需要精准控制参数特性的场景:
import numpy as np
import torch
from torch import nn
# 搭建简单Sequential模型
net = nn.Sequential(
nn.Linear(30, 40), # 输入30维,输出40维
nn.ReLU(),
nn.Linear(40, 10) # 输入40维,输出10维
)
# 1. 直接修改单层参数(均匀分布:3~5之间)
net[0].weight.data = torch.from_numpy(np.random.uniform(3, 5, size=(40, 30)))
# 2. 批量初始化所有线性层(正态分布:均值0,方差0.5)
for layer in net:
if isinstance(layer, nn.Linear): # 仅对线性层初始化
param_shape = layer.weight.shape
layer.weight.data = torch.from_numpy(np.random.normal(0, 0.5, size=param_shape))
layer.bias.data = torch.zeros_like(layer.bias.data) # 偏置初始化为0
2. 经典初始化方法:Xavier 初始化
Xavier 初始化是深度学习中常用的初始化方式,通过数学推导保证每层输入和输出的方差一致,有效避免梯度消失 / 爆炸:
-
公式:
w ~ Uniform[-√6/√(n_in + n_out), √6/√(n_in + n_out)],其中n_in是输入维度,n_out是输出维度; -
PyTorch 内置实现:直接调用
torch.nn.init模块,无需手动计算:from torch.nn import init
对第一层线性层应用Xavier均匀分布初始化
init.xavier_uniform_(net[0].weight)
3. Module 模型的初始化技巧
对于自定义的 Module 模型,可通过children()和modules()遍历网络层,其中modules()会递归遍历所有子层,更适合批量初始化:
class SimNet(nn.Module):
def __init__(self):
super(SimNet, self).__init__()
self.l1 = nn.Sequential(nn.Linear(30, 40), nn.ReLU())
self.l2 = nn.Sequential(nn.Linear(40, 10), nn.ReLU())
def forward(self, x):
x = self.l1(x)
return self.l2(x)
net = SimNet()
# 批量初始化所有线性层
for layer in net.modules():
if isinstance(layer, nn.Linear):
init.xavier_uniform_(layer.weight)
init.constant_(layer.bias, 0) # 偏置设为0
4. 初始化核心原则
- 线性层:优先使用 Xavier 或正态分布初始化,避免参数过大 / 过小;
- 偏置项:通常初始化为 0,简化初始状态的梯度计算;
- 激活函数适配:ReLU 激活函数可搭配 He 初始化(专门为 ReLU 设计,方差为 2/n_in),进一步提升训练稳定性。
5. 初始化选择
- 新手推荐 :不知道选什么时,用
init.xavier_uniform_(线性层 + Sigmoid/Tanh)或init.kaiming_uniform_(线性层 + ReLU),这两个是 "万能推荐"; - 手动调整:需要特定参数分布(比如要求权重在 3~5 之间)时,用 NumPy 生成对应数值,替换模型参数;
- 批量操作 :自定义 Module 模型时,用
net.modules()遍历所有线性层,批量初始化,不用一层一层改。
三、优化算法全家桶:参数更新的 "高效策略"
有了梯度和初始参数,下一步就是通过优化算法更新参数,找到损失函数的最小值。不同算法适用于不同场景,下面从基础到进阶逐一解析,附完整实操代码。
1. 优化问题的核心挑战
深度学习的损失函数通常是复杂的非凸函数,优化过程面临两大问题:
- 局部最小点:函数在局部区域是最小值,但全局并非最优,梯度为 0 导致参数停止更新;
- 鞍点:梯度为 0,但周围既有比它大的点也有比它小的点,同样会导致模型停滞。
随机梯度下降(SGD)通过随机选取批量数据计算梯度,能一定程度上跳出局部最小点和鞍点,是优化算法的基础。
2. 随机梯度下降(SGD):优化算法的 "基石"
SGD 的核心思想是 "沿着梯度反方向小步迭代",更新公式简洁直观:θ₁ = θ₀ - η·∇L(θ),其中η是学习率(步长),∇L(θ)是损失函数的梯度。
SGD 实操代码(MNIST 数据集)
import numpy as np
import torch
from torchvision.datasets import MNIST
from torch.utils.data import DataLoader
from torch import nn
from torch.autograd import Variable
# 数据预处理:标准化+拉平
def data_tf(x):
x = np.array(x, dtype='float32') / 255
x = (x - 0.5) / 0.5 # 标准化到-1~1,稳定梯度
x = x.reshape((-1,)) # 28×28图像拉平为784维向量
return torch.from_numpy(x)
# 加载数据
train_set = MNIST('./data', train=True, transform=data_tf, download=True)
train_data = DataLoader(train_set, batch_size=64, shuffle=True)
criterion = nn.CrossEntropyLoss() # 分类任务损失函数
# 搭建3层神经网络
net = nn.Sequential(nn.Linear(784, 200), nn.ReLU(), nn.Linear(200, 10))
# 手动实现SGD参数更新
def sgd_update(parameters, lr):
for param in parameters:
param.data = param.data - lr * param.grad.data # 沿梯度反方向更新
# 训练5轮
for e in range(5):
train_loss = 0
for im, label in train_data:
im = Variable(im)
label = Variable(label)
# 前向传播:计算预测值和损失
out = net(im)
loss = criterion(out, label)
# 反向传播:计算梯度
net.zero_grad() # 清空上一轮梯度(避免累加)
loss.backward() # 反向传播算梯度
# 参数更新
sgd_update(net.parameters(), lr=1e-2) # 学习率0.01
# 累计损失
train_loss += loss.data.item()
print(f'epoch: {e}, Train Loss: {train_loss / len(train_data):.6f}')
SGD 关键特性
- 优点:原理简单、计算量小,适合大规模数据;
- 缺点:学习率固定,对所有参数 "一视同仁",在损失函数陡峭区域易震荡,收敛速度慢;
- 关键参数:
- batch_size:批量越大,梯度越稳定但计算越慢,常用 64/128;
- 学习率:过小导致收敛极慢,过大导致损失震荡不下降(如 lr=1 时,损失维持在 2.3 左右)。
3. 动量法(Momentum):给梯度下降加 "惯性"
为解决 SGD 震荡问题,动量法引入 "速度" 概念,模拟物理中的惯性 ------ 参数更新不仅考虑当前梯度,还保留上一次的更新方向,减少震荡并加速收敛。
核心公式
- 速度更新:
vᵢ = γ·vᵢ₋₁ + η·∇L(θ),γ是动量参数(通常取 0.9,模拟惯性大小); - 参数更新:
θᵢ = θᵢ₋₁ - vᵢ。
动量法实操代码(PyTorch 内置)
# 直接使用PyTorch内置SGD+momentum
optimizer = torch.optim.SGD(net.parameters(), lr=1e-2, momentum=0.9)
# 训练逻辑(仅更新步骤变化)
for e in range(5):
train_loss = 0
for im, label in train_data:
im = Variable(im)
label = Variable(label)
out = net(im)
loss = criterion(out, label)
optimizer.zero_grad() # 清空梯度
loss.backward() # 计算梯度
optimizer.step() # 自动应用动量更新
train_loss += loss.data.item()
print(f'epoch: {e}, Train Loss: {train_loss / len(train_data):.6f}')
效果对比
动量法 5 轮训练后损失可降至 0.08 左右,而纯 SGD 约为 0.26,收敛速度提升明显 ------ 因为动量项让梯度方向一致的参数加速更新,方向多变的参数减速震荡。
4. 自适应学习率算法:给参数 "定制步长"
SGD 和动量法用固定学习率,但不同参数的梯度特性不同(如稀疏特征的梯度出现频率低、幅值小),自适应算法会动态调整每个参数的学习率,无需手动调参。
(1)Adagrad:梯度大降速,梯度小升速
核心逻辑:累计每个参数的梯度平方,用总梯度平方的平方根调整学习率 ------ 梯度大的参数学习率变小(避免震荡),梯度小的参数学习率变大(加速收敛):
- 学习率调整:
η' = η / √(s + ε),s是参数梯度平方的累计和,ε(1e-10)避免分母为 0; - 实操代码:
optimizer = torch.optim.Adagrad(net.parameters(), lr=1e-2)。
(2)RMSProp:解决 Adagrad 后期收敛乏力
Adagrad 的梯度平方累计会导致后期学习率趋近于 0,RMSProp 用指数加权移动平均替代累计求和,让后期仍能保持合理的学习率:
- 梯度平方更新:
s = α·s + (1 - α)·g²,α是移动平均系数(通常取 0.9); - 实操代码:
optimizer = torch.optim.RMSprop(net.parameters(), lr=1e-3, alpha=0.9)。
(3)Adadelta:无需手动设置学习率
Adadelta 是 Adagrad 的升级版,引入参数更新量的移动平均,彻底摆脱对学习率的依赖:
- 核心逻辑:用参数更新量的移动平均替代固定学习率,自适应调整更新幅度;
- 实操代码:
optimizer = torch.optim.Adadelta(net.parameters(), rho=0.9),rho是移动平均系数。
(4)Adam:动量法 + RMSProp 的 "强强联合"
Adam 结合了动量法的惯性特性和 RMSProp 的自适应学习率,是目前最常用的优化算法,还会对初始值进行偏差修正:
- 动量项更新:
v = β₁·v + (1 - β₁)·g(β₁=0.9); - 梯度平方更新:
s = β₂·s + (1 - β₂)·g²(β₂=0.999); - 偏差修正:
v̂ = v / (1 - β₁ᵗ),ŝ = s / (1 - β₂ᵗ)(t是迭代次数,修正初始值为 0 的偏差); - 实操代码:
optimizer = torch.optim.Adam(net.parameters(), lr=1e-3)。
5. 主流优化算法核心对比
| 优化算法 | 核心优势 | 适用场景 | 关键参数 |
|---|---|---|---|
| SGD | 计算高效、稳定 | 大规模数据、简单模型 | lr(学习率)、batch_size |
| 动量法 | 减少震荡、加速收敛 | 深层网络、损失函数震荡明显 | lr、momentum(0.9) |
| Adagrad | 适配稀疏数据 | 稀疏特征任务(如文本分类) | lr |
| RMSProp | 后期收敛稳定 | 复杂网络、Adagrad 效果不佳时 | lr、alpha(0.9) |
| Adadelta | 无需调学习率 | 新手入门、学习率难以确定时 | rho(0.9) |
| Adam | 综合性能最优 | 绝大多数场景(分类、回归、生成)*主要推荐 | lr(1e-3)、beta1(0.9)、beta2(0.999) |
四、核心知识点总结(新手必记)
- 反向传播:本质是链式求导的工程实现,从损失函数反向逐层计算参数梯度,是所有优化的基础;
- 参数初始化 :
- 线性层优先用 Xavier 初始化,避免梯度消失 / 爆炸;
- 偏置项通常初始化为 0,简化梯度计算;
- 自定义模型用
modules()批量初始化,高效便捷;
- 优化算法选型 :
- 新手首选 Adam,综合性能最优,无需复杂调参;
- 大规模数据用 SGD + 动量,平衡效率和效果;
- 稀疏数据用 Adagrad,自适应调整稀疏参数的学习率;
- 训练关键细节 :
- 梯度必须清空:每次反向传播前调用
zero_grad(),避免梯度累加; - 数据预处理:标准化(如归一化到 - 1~1)能稳定梯度,提升训练效率;
- 学习率选择:Adam 常用 1e-3,SGD 常用 1e-2,过大易震荡,过小收敛慢。
- 梯度必须清空:每次反向传播前调用
深度学习的优化过程,核心是理解 "梯度如何计算""参数如何初始化""步长如何调整"。无论多么复杂的优化算法,都离不开这三大核心逻辑 ------ 这是从 "新手" 到 "入门" 的关键一步。
