刚开始学习神经网络时,真正容易让人混乱的往往不是某一个公式,而是这些概念之间的关系:前向传播、激活函数、损失函数、反向传播、优化器、学习率……它们分别解决什么问题,又如何组成一次完整训练?
先记住整篇文章最重要的一句话:
前向传播计算预测,损失函数衡量误差,反向传播计算梯度,优化器更新参数,学习率控制每次更新的尺度。
本文使用同一个二分类网络,从数值计算到公式推导完整走一遍训练流程。为了保持严谨,先约定几个术语:
- 参数(parameter):模型通过训练学习的量,例如权重和偏置。
- 超参数(hyperparameter):训练前人为设定的量,例如学习率、批大小和训练轮数。
- 梯度(gradient):目标函数对参数的偏导数组成的向量,描述目标函数上升最快的方向。
- 一次迭代(step / iteration):使用一个批次完成一次前向传播、反向传播和参数更新。
- 一轮训练(epoch):让训练集中的每个样本大致被使用一次。
1. 贯穿全文的例子
考虑一个只有单个样本的二分类任务。输入与标签分别为:
x=[x1x2]=[12],y=1
网络包含两个隐藏神经元和一个输出神经元,所有神经元都使用 Sigmoid 激活:
(x₁, x₂) → 两个隐藏神经元 (a₁, a₂) → 输出概率 ŷ
隐藏层参数为:
W=[w11w21w12w22]=[0.1−0.10.20.1],b=[b1b2]=[00.1]
这里采用“矩阵的第 j 行对应第 j 个隐藏神经元”的约定。例如,w12 表示输入 x2 到第 1 个隐藏神经元的权重。
输出层参数为:
v=[v1v2]=[0.3−0.4],b3=0.1
本文中的数值均保留四位小数,因此中间步骤可能存在轻微的舍入误差。
2. 神经网络究竟在学习什么?
神经网络本质上是一个带参数的函数。以最简单的线性模型为例:
y=wx+b
其中 x 是输入,y 是输出,w 和 b 是需要学习的参数。神经网络只是把更多线性变换与非线性激活组合起来:
fθ(x)=fL(fL−1(⋯f1(x)))
θ 表示网络中的全部参数。训练的目标不是“记住数据”,而是找到一组参数,使模型在未见数据上也能较好地近似输入到目标的映射。
一次训练迭代由以下步骤组成:
取出一个批次的数据
→ 前向传播,得到预测
→ 计算目标函数
→ 反向传播,得到梯度
→ 优化器更新参数
→ 进入下一次迭代
这条闭环会重复很多次,直到验证集指标不再改善、达到预定训练轮数,或触发其他停止条件。
3. 前向传播:由输入得到预测
前向传播使用当前参数从输入逐层计算模型输出。对本文的网络,隐藏层可以整体写成:
z=Wx+b,a=σ(z)
其中 σ(⋅) 按元素作用。展开第 1 个隐藏神经元:
z1a1=w11x1+w12x2+b1=0.1×1+0.2×2+0=0.5,=σ(z1)=σ(0.5)≈0.6225
第 2 个隐藏神经元为:
z2a2=w21x1+w22x2+b2=(−0.1)×1+0.1×2+0.1=0.2,=σ(z2)=σ(0.2)≈0.5498
输出层先计算 logit z3,再将它映射为正类概率 y^:
z3y^=v1a1+v2a2+b3=0.3×0.6225−0.4×0.5498+0.1≈0.0668,=σ(z3)≈0.5167
因此,模型当前认为该样本属于正类的概率约为 51.67%。真实标签为 y=1,预测方向虽然正确,但置信度很低。
4. 激活函数:为网络引入非线性
若各层都只进行线性变换,那么无论叠加多少层,整体仍然等价于一次线性变换:
W2(W1x+b1)+b2=W′x+b′
激活函数使网络能够表达非线性关系。下面是几种常见选择。
4.1 Sigmoid
σ(z)=1+e−z1,σ′(z)=σ(z)(1−σ(z))
Sigmoid 的值域是 (0,1),因此常用于二分类输出层。它在输入绝对值较大时会进入饱和区,导数接近 0,所以通常不再作为深层前馈网络隐藏层的默认选择。
4.2 Tanh
tanh(z)=ez+e−zez−e−z,tanh′(z)=1−tanh2(z)
Tanh 的值域是 (−1,1),输出以 0 为中心,但同样存在饱和区和梯度消失问题。它仍常见于部分循环结构和需要有界输出的场景。
4.3 ReLU 与 Leaky ReLU
ReLU(z)=max(0,z)
ReLU′(z)={1,0,z>0,z<0
ReLU 计算简单、正半轴不饱和,是许多网络隐藏层的常见默认选择。在 z=0 处它不可导,实际实现会指定一个次梯度;负半轴梯度始终为 0,可能导致“死亡 ReLU”。
Leaky ReLU 在负半轴保留一个很小的斜率:
LeakyReLU(z)={z,αz,z>0,z≤0,α>0
它可以缓解神经元长期没有梯度的问题,但 α 仍需设定。
4.4 Softmax
对于 K 个互斥类别,Softmax 将 logits z1,z2,…,zK 转换为概率分布:
softmax(z)i=∑j=1Kezjezi,i=1,2,…,K
各项均为正数且总和为 1。工程实现通常会先减去 maxizi,以避免指数运算溢出;这一操作不会改变 Softmax 的结果。
| 激活函数 | 常见位置 | 主要特点 | 主要风险 |
|---|
| Sigmoid | 二分类输出层 | 输出可解释为正类概率 | 饱和区梯度很小 |
| Tanh | 部分循环结构 | 零中心、有界 | 仍可能梯度消失 |
| ReLU | 隐藏层 | 简单、高效、正半轴不饱和 | 死亡 ReLU |
| Leaky ReLU | 隐藏层 | 负半轴保留梯度 | 需选择负半轴斜率 |
| Softmax | 单标签多分类输出层 | 输出类别概率分布 | 需注意数值稳定性 |
二分类通常使用 Sigmoid;单标签多分类通常使用 Softmax;多标签分类则常对每个标签独立使用 Sigmoid。
5. 损失函数与目标函数
前向传播产生预测后,需要用损失函数衡量预测与真实目标之间的差异。
5.1 均方误差
均方误差(Mean Squared Error, MSE)常用于回归任务。对包含 N 个样本的数据集:
MSE=N1i=1∑N(yi−y^i)2
平方会使较大的误差受到更重的惩罚。MSE 可导且形式简单,但对离群值较敏感。
5.2 二元交叉熵
本文是二分类问题,使用二元交叉熵(Binary Cross-Entropy, BCE):
ℓ(y,y^)=−[ylny^+(1−y)ln(1−y^)]
其中 y∈{0,1},y^∈(0,1)。当 y=1 时,损失化简为:
ℓ(1,y^)=−lny^
代入本文的预测 y^≈0.5167:
L=−ln(0.5167)≈0.6603
模型给真实类别的概率越小,交叉熵惩罚越大。在实际代码中,通常应使用直接接收 logits 的稳定实现,例如 PyTorch 的 BCEWithLogitsLoss,而不是先手动计算 Sigmoid 再取对数。
5.3 多分类交叉熵
对 C 个互斥类别,多分类交叉熵为:
ℓ(y,y^)=−c=1∑Cyclny^c
若 y 是 one-hot 向量,只有真实类别 k 对应的 yk=1,因此:
ℓ=−lny^k
5.4 从样本损失到训练目标
“损失函数”和“目标函数”经常被混用,但严格来说它们处于不同层次:
- 样本损失 ℓi:衡量单个样本的预测误差。
- 经验风险 L(θ):一个批次或整个训练集上的平均损失。
- 目标函数 J(θ):优化器真正最小化的量,可额外包含正则化项。
例如,带 L2 正则化的目标函数可以写成:
J(θ)=N1i=1∑Nℓi(θ)+2λ∥W∥22
λ≥0 控制正则化强度。正则化会限制参数过度增大,从而降低过拟合风险,但它不能代替独立验证集上的泛化评估。
6. 反向传播:用链式法则计算梯度
反向传播(backpropagation)是一种高效计算梯度的算法。它计算梯度,但不更新参数;参数更新由优化器完成。
6.1 链式法则
若 u=g(x) 且 z=f(u),则:
dxdz=dudzdxdu
神经网络是嵌套的复合函数。例如,参数 w11 影响损失 L 的路径为:
w₁₁ → z₁ → a₁ → z₃ → ŷ → L
因此:
∂w11∂L=∂y^∂L∂z3∂y^∂a1∂z3∂z1∂a1∂w11∂z1
反向传播会复用中间结果,而不是为每个参数单独重算整条路径,这正是它高效的原因。
6.2 输出层梯度
当 Sigmoid 与二元交叉熵组合时,损失对输出层 logit 的导数可以化简为:
∂z3∂L=y^−y
代入 y^≈0.5167 和 y=1:
δ3:=∂z3∂L=0.5167−1=−0.4833
因为:
z3=v1a1+v2a2+b3
所以输出层参数的梯度为:
∂v1∂L∂v2∂L∂b3∂L=δ3a1≈−0.4833×0.6225≈−0.3008,=δ3a2≈−0.4833×0.5498≈−0.2657,=δ3≈−0.4833
6.3 隐藏层梯度
对第 j 个隐藏神经元,误差信号为:
δj:=∂zj∂L=δ3vjaj(1−aj)
第 1 个隐藏神经元:
δ1=(−0.4833)×0.3×0.6225×(1−0.6225)≈−0.0341
由 z1=w11x1+w12x2+b1 可得:
∂w11∂L∂w12∂L∂b1∂L=δ1x1≈−0.0341,=δ1x2≈−0.0681,=δ1≈−0.0341
第 2 个隐藏神经元:
δ2=(−0.4833)×(−0.4)×0.5498×(1−0.5498)≈0.0479
从而:
∂w21∂L∂w22∂L∂b2∂L=δ2x1≈0.0479,=δ2x2≈0.0957,=δ2≈0.0479
梯度汇总如下:
| 参数 | 梯度 | 参数 | 梯度 |
|---|
| w11 | −0.0341 | w12 | −0.0681 |
| w21 | 0.0479 | w22 | 0.0957 |
| b1 | −0.0341 | b2 | 0.0479 |
| v1 | −0.3008 | v2 | −0.2657 |
| b3 | −0.4833 | | |
梯度的正负不表示参数“好”或“坏”。它只描述在当前位置轻微增大该参数时,损失的一阶变化方向。
7. 梯度下降:根据梯度更新参数
最基本的梯度下降更新规则为:
θ←θ−η∂θ∂J
其中 η>0 是学习率。梯度指向目标函数上升最快的方向,因此要沿其反方向更新。
令 η=0.1,以 v1 为例:
v1new=v1old−η∂v1∂L=0.3−0.1×(−0.3008)≈0.3301
全部参数更新后为:
| 参数 | 更新前 | 梯度 | 更新后 |
|---|
| w11 | 0.1000 | −0.0341 | 0.1034 |
| w12 | 0.2000 | −0.0681 | 0.2068 |
| b1 | 0.0000 | −0.0341 | 0.0034 |
| w21 | −0.1000 | 0.0479 | −0.1048 |
| w22 | 0.1000 | 0.0957 | 0.0904 |
| b2 | 0.1000 | 0.0479 | 0.0952 |
| v1 | 0.3000 | −0.3008 | 0.3301 |
| v2 | −0.4000 | −0.2657 | −0.3734 |
| b3 | 0.1000 | −0.4833 | 0.1483 |
使用这些新参数再次前向传播,会得到更大的正类概率和更小的损失。这只是沿局部负梯度方向走一步的结果,并不保证任意学习率下每一步都使损失下降。
8. Batch GD、SGD 与 Mini-batch GD
三者的核心差异,是一次参数更新使用多少训练样本来估计梯度。
| 方法 | 每次更新使用的数据 | 优点 | 局限 |
|---|
| Batch Gradient Descent | 整个训练集 | 梯度稳定 | 单次更新成本高,内存压力大 |
| Stochastic Gradient Descent | 单个样本 | 更新频繁,单步成本低 | 梯度噪声大,硬件利用率低 |
| Mini-batch Gradient Descent | 一个小批次 | 兼顾效率、稳定性和并行计算 | 需要选择批大小 |
现代深度学习训练通常使用 mini-batch。实践中,人们也经常把“使用 mini-batch 的普通随机梯度下降优化器”简称为 SGD,需要结合上下文判断其含义。
若批次 B 包含 B 个样本,批次平均损失为:
LB(θ)=B1i∈B∑ℓi(θ)
反向传播计算的是 ∇θLB,它是整个训练集梯度的带噪估计。
9. Adam:自适应地缩放每个参数的更新
Adam(Adaptive Moment Estimation)仍然依赖反向传播得到的梯度。它与普通 SGD 的区别在于:Adam 会维护梯度的一阶矩和二阶原点矩的指数移动平均,为不同参数自适应地缩放更新量。
令第 t 步梯度为:
gt=∇θJt(θt−1)
一阶矩估计为:
mt=β1mt−1+(1−β1)gt
二阶原点矩估计为:
st=β2st−1+(1−β2)gt2
其中平方按元素计算。由于 m0=s0=0,训练初期的估计会偏向 0,因此需要做偏差修正:
m^t=1−β1tmt,s^t=1−β2tst
最终更新规则为:
θt=θt−1−ηs^t+εm^t
常见默认值是 β1=0.9、β2=0.999 和 ε=10−8,但它们并非适用于所有任务的定律。
9.1 用 v1 演示第一次 Adam 更新
已知 g1=∂L/∂v1≈−0.3008,并令 m0=s0=0:
m1s1=0.9×0+0.1×(−0.3008)=−0.03008,=0.999×0+0.001×(−0.3008)2≈0.00009048
偏差修正后:
m^1=1−0.9−0.03008=−0.3008,s^1=1−0.9990.00009048≈0.09048
若 η=0.001,则:
v1new=0.3−0.0010.09048+10−8−0.3008≈0.3010
第一次更新时,偏差修正使 m^1=g1、s^1=g12,因此除极小的 ε 外,更新幅度接近学习率、方向由梯度符号决定。Adam 的自适应优势主要体现在累积了多步梯度历史之后。
9.2 常见优化器对比
| 优化器 | 核心机制 | 常见特点 |
|---|
| SGD | 直接使用当前批次梯度 | 简单、内存占用低,对学习率敏感 |
| SGD + Momentum | 累积梯度方向 | 减少震荡,加速沿一致方向前进 |
| RMSProp | 使用梯度平方的移动平均 | 为各参数自适应缩放步长 |
| Adam | 同时使用一阶矩与二阶原点矩 | 通常收敛快,对初始学习率相对宽容 |
| AdamW | 将权重衰减与梯度更新解耦 | Transformer 等模型中的常见选择 |
Adam 并不保证在所有任务上都优于 SGD。选择优化器时,应同时比较收敛速度、验证集指标、训练稳定性和计算资源。
10. 学习率:决定更新尺度
对普通梯度下降:
Δθ=−η∂θ∂J
学习率 η 是控制参数更新尺度的超参数:
- 过大:损失可能震荡、发散,甚至出现
NaN。
- 过小:训练进展缓慢,也可能在有限预算内无法到达较好区域。
下表给出常见现象与排查方向,但同一现象可能有多个原因,不能只凭一条曲线直接下结论。
| 训练现象 | 可能原因 | 优先排查 |
|---|
损失迅速发散或出现 NaN | 学习率过大、数值溢出、数据异常 | 降低学习率,检查输入与稳定损失实现 |
| 训练损失大幅震荡 | 学习率偏大、批次过小 | 降低学习率或增大批次 |
| 训练损失下降很慢 | 学习率偏小、梯度过小、模型容量不足 | 检查梯度与数据,再调整学习率 |
| 训练损失下降而验证损失上升 | 过拟合或数据分布差异 | 正则化、数据增强、早停、检查划分 |
| 后期长期停滞 | 学习率不合适或已接近当前配置上限 | 尝试衰减学习率,并检查模型与数据 |
所谓“推荐初始学习率”只能作为搜索起点。它还会受到优化器、批大小、归一化方式、模型规模和任务类型的共同影响。
11. 学习率调度:让不同阶段使用不同步长
训练早期通常需要较大的步长快速移动,训练后期则需要较小的步长精细调整。学习率调度器用于随训练进程改变全局学习率。
11.1 Step Decay
每隔固定步数将学习率乘以衰减系数 γ:
ηt=η0γ⌊t/Tstep⌋,0<γ<1
它简单直观,但衰减时点和幅度需要预先指定。
11.2 Exponential Decay
ηt=η0γt,0<γ<1
学习率连续、平滑地下降。若 γ 过小,学习率可能过早衰减到几乎无法更新。
11.3 Cosine Annealing
ηt=ηmin+21(ηmax−ηmin)(1+cosTπt),0≤t≤T
它将学习率从 ηmax 平滑降到 ηmin,是现代深度学习中的常见选择。
11.4 Warmup
线性 warmup 在训练初期逐步提高学习率:
ηt=ηtargetTwarmupt,0≤t≤Twarmup
Warmup 常用于 Transformer、大批量训练或初期梯度不稳定的任务。它通常与后续衰减策略组合使用,而不是独立覆盖整个训练过程。
11.5 ReduceLROnPlateau
当验证集指标连续若干轮没有改善时,将学习率乘以一个小于 1 的因子。它根据指标自适应调整,但效果依赖监控指标、耐心值(patience)、阈值和衰减因子的合理设置。
11.6 One Cycle
One Cycle 通常让学习率先从较小值升至峰值,再下降到远低于初始值的水平,并常与动量的反向变化配合。它可能加快训练并改善泛化,但需要合理估计最大学习率。
| 调度方法 | 学习率变化 | 适合场景 |
|---|
| Step Decay | 阶梯式下降 | 训练周期固定、基线实验 |
| Exponential Decay | 指数下降 | 希望持续平滑衰减 |
| Cosine Annealing | 余弦下降 | 深度网络常规训练 |
| Warmup | 初期逐渐升高 | Transformer、大批量训练 |
| ReduceLROnPlateau | 指标停滞后下降 | 难以预判衰减时点 |
| One Cycle | 先升后降 | 希望提高训练效率的成熟配置 |
学习率调度与 Adam 并不冲突。调度器控制全局基准学习率 ηt,Adam 再依据每个参数的梯度历史进行缩放:
实际更新=全局学习率×逐参数自适应缩放
12. 把训练闭环写成伪代码
下面的伪代码对应绝大多数监督学习训练程序:
for epoch in range(num_epochs):
model.train()
for x_batch, y_batch in train_loader:
optimizer.zero_grad() # 清除上一次迭代留下的梯度
logits = model(x_batch) # 前向传播
loss = criterion(logits, y_batch)
loss.backward() # 反向传播,计算梯度
optimizer.step() # 根据梯度更新参数
validate(model, val_loader) # 在验证集上评估泛化表现
scheduler.step() # 调度时点取决于调度器类型
需要注意:
model.train() 不是“执行训练”,而是让 Dropout、Batch Normalization 等模块进入训练模式。
loss.backward() 只计算并累积梯度;optimizer.step() 才更新参数。
- 某些调度器按迭代调用,某些按 epoch 调用;
ReduceLROnPlateau 还需要传入验证指标。
- 验证阶段不应更新参数,并应关闭不必要的梯度记录。
13. 常见概念辨析
| 易混概念 | 准确关系 |
|---|
| 前向传播 vs. 推理 | 两者都会计算输出;训练中的前向传播还会为求梯度保留必要的中间量 |
| 反向传播 vs. 梯度下降 | 反向传播计算梯度;梯度下降使用梯度更新参数 |
| 损失函数 vs. 目标函数 | 损失描述预测误差;目标函数是最终被最小化的完整表达式 |
| 参数 vs. 超参数 | 参数由训练学习;超参数由训练者设定或搜索 |
| Adam vs. 学习率调度 | Adam 逐参数缩放更新;调度器改变全局基准学习率 |
| epoch vs. iteration | epoch 是大致遍历一次训练集;iteration 是完成一次参数更新 |
最后,将全文压缩成一个闭环:
数据 → 前向传播 → 预测 → 损失/目标函数
→ 反向传播 → 梯度 → 优化器更新参数
→ 学习率调度 → 下一次迭代
理解神经网络训练的关键,不是孤立地记住每个名词,而是明确每个环节的输入和输出:前向传播输出预测,损失函数输出标量误差,反向传播输出梯度,优化器输出更新后的参数。只要这条数据流清晰,复杂网络的训练逻辑仍然是同一个闭环。