目录
一、反向传播算法:梯度计算的 “高效引擎”
1. 链式法则:反向传播的数学基石
2. 反向传播的核心逻辑:从结果反向 “回溯”
3. 复杂函数实战:Sigmoid 函数的反向传播
二、参数初始化:模型训练的 “黄金起点”
1. 手动初始化:灵活定制参数分布
2. 经典初始化方法:Xavier 初始化
3. Module 模型的初始化技巧
4. 初始化核心原则
5. 初始化选择
三、优化算法全家桶:参数更新的 “高效策略”
1. 优化问题的核心挑战
2. 随机梯度下降(SGD):优化算法的 “基石”
SGD 实操代码(MNIST 数据集)
SGD 关键特性
3. 动量法(Momentum):给梯度下降加 “惯性”
核心公式
动量法实操代码(PyTorch 内置)
效果对比
4. 自适应学习率算法:给参数 “定制步长”
(1)Adagrad:梯度大降速,梯度小升速
(2)RMSProp:解决 Adagrad 后期收敛乏力
(3)Adadelta:无需手动设置学习率
(4)Adam:动量法 + RMSProp 的 “强强联合”
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) # 偏置初始化为02. 经典初始化方法: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) # 偏置设为04. 初始化核心原则
- 线性层:优先使用 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,过大易震荡,过小收敛慢。
- 梯度必须清空:每次反向传播前调用
深度学习的优化过程,核心是理解 “梯度如何计算”“参数如何初始化”“步长如何调整”。无论多么复杂的优化算法,都离不开这三大核心逻辑 —— 这是从 “新手” 到 “入门” 的关键一步。