前言
如果只看 PyTorch 代码,训练神经网络似乎很简单:写一个 forward,算出 loss,调用 loss.backward(),再让优化器更新参数。但这几行代码背后,其实压缩了深度学习最核心的一条数学主线:
正向传播负责把输入变成预测,反向传播负责把误差变成每个参数应该调整的方向。
理解这条主线之后,再看 CNN、ResNet、Transformer 或 LoRA,很多公式就不再像突然冒出来的符号。它们本质上都在回答同一个问题:信息如何向前流动,梯度又如何向后流动。
从一个神经元开始
一个最简单的神经元可以写成:
$$ z = w^\top x + b $$其中 $x$ 是输入向量,$w$ 是权重向量,$b$ 是偏置。线性部分 $z$ 再经过一个非线性激活函数:
$$ a = \sigma(z) $$如果没有激活函数,很多层线性变换叠在一起仍然只是一个线性变换,网络无法表示复杂的非线性关系。激活函数的作用,就是让每一层都能在“线性组合”之外引入弯曲和分段变化。
一层网络在做什么
把一个神经元扩展成一层全连接网络,可以写成矩阵形式:
$$ \begin{aligned} z^{(l)} &= W^{(l)} a^{(l-1)} + b^{(l)} \\\\ a^{(l)} &= \sigma\left(z^{(l)}\right) \end{aligned} $$这里的上标 $(l)$ 表示第 $l$ 层。$a^{(l-1)}$ 是上一层输出,$W^{(l)}$ 和 $b^{(l)}$ 是这一层参数,$a^{(l)}$ 是这一层输出。
从这个角度看,神经网络并不是一个神秘黑盒,而是一串函数复合:
$$ x \xrightarrow{f_1} a^{(1)} \xrightarrow{f_2} a^{(2)} \xrightarrow{f_3} \cdots \xrightarrow{f_L} \hat{y} $$正向传播就是沿着这条链从左到右计算。
损失函数把预测变成训练信号
网络的输出 $\hat{y}$ 本身还不能告诉我们参数该怎么改。我们需要一个损失函数衡量预测和真实标签 $y$ 的差距:
$$ L = \ell(\hat{y}, y) $$例如回归任务常用均方误差:
$$ L = \frac{1}{2}\left(\hat{y} - y\right)^2 $$分类任务常用交叉熵。不同损失函数会给出不同训练信号,但共同点是:训练最终要让 $L$ 变小。
反向传播要解决什么问题
训练时,我们真正关心的是每个参数对损失的影响:
$$ \begin{aligned} \frac{\partial L}{\partial W^{(l)}}, \quad \frac{\partial L}{\partial b^{(l)}} \end{aligned} $$这些导数告诉我们:如果某个参数稍微变化,损失会如何变化。梯度下降会沿着让损失下降的方向更新参数:
$$ W^{(l)} \leftarrow W^{(l)} - \eta \frac{\partial L}{\partial W^{(l)}} $$其中 $\eta$ 是学习率。反向传播的作用,就是高效算出这些梯度。
链式法则是反向传播的核心
先看一个只有两个函数复合的例子:
$$ \begin{aligned} u &= f(x) \\\\ L &= g(u) \end{aligned} $$如果想知道 $x$ 对 $L$ 的影响,需要把两段局部变化乘起来:
$$ \begin{aligned} \frac{\partial L}{\partial x} &= \frac{\partial L}{\partial u} \frac{\partial u}{\partial x} \end{aligned} $$神经网络只是把这件事重复很多次。对于第 $l$ 层,反向传播会先拿到来自后一层的上游梯度,再乘上本层的局部导数,把梯度传给前一层。
用一个两层网络看完整过程
设一个两层网络为:
$$ \begin{aligned} z^{(1)} &= W^{(1)}x + b^{(1)} \\\\ a^{(1)} &= \sigma\left(z^{(1)}\right) \\\\ z^{(2)} &= W^{(2)}a^{(1)} + b^{(2)} \\\\ \hat{y} &= z^{(2)} \\\\ L &= \frac{1}{2}\left(\hat{y} - y\right)^2 \end{aligned} $$正向传播从 $x$ 一直算到 $L$。反向传播则从损失开始:
$$ \begin{aligned} \frac{\partial L}{\partial \hat{y}} &= \hat{y} - y \end{aligned} $$第二层的参数梯度可以写成:
$$ \begin{aligned} \frac{\partial L}{\partial W^{(2)}} &= \frac{\partial L}{\partial z^{(2)}} \left(a^{(1)}\right)^\top \\\\ \frac{\partial L}{\partial b^{(2)}} &= \frac{\partial L}{\partial z^{(2)}} \end{aligned} $$要继续传回第一层,需要先得到第一层输出 $a^{(1)}$ 的梯度:
$$ \begin{aligned} \frac{\partial L}{\partial a^{(1)}} &= \left(W^{(2)}\right)^\top \frac{\partial L}{\partial z^{(2)}} \end{aligned} $$再穿过激活函数:
$$ \begin{aligned} \frac{\partial L}{\partial z^{(1)}} &= \frac{\partial L}{\partial a^{(1)}} \odot \sigma'\left(z^{(1)}\right) \end{aligned} $$最后得到第一层参数的梯度:
$$ \begin{aligned} \frac{\partial L}{\partial W^{(1)}} &= \frac{\partial L}{\partial z^{(1)}}x^\top \\\\ \frac{\partial L}{\partial b^{(1)}} &= \frac{\partial L}{\partial z^{(1)}} \end{aligned} $$这里的 $\odot$ 表示逐元素相乘。
为什么深层网络会难训练
如果网络很深,梯度从输出层传回输入层时,会不断乘上每一层的局部导数。抽象地看:
$$ \begin{aligned} \frac{\partial L}{\partial x} &= \frac{\partial L}{\partial a^{(L)}} \frac{\partial a^{(L)}}{\partial a^{(L-1)}} \cdots \frac{\partial a^{(1)}}{\partial x} \end{aligned} $$如果这些局部导数的尺度长期偏小,梯度可能越来越接近 0,这就是梯度消失;如果尺度长期偏大,梯度可能迅速放大,这就是梯度爆炸。
这也是为什么 ResNet 不是凭空出现的结构技巧。残差连接提供了更直接的前向信息路径和反向梯度路径,让深层网络的优化问题更容易处理。理解了反向传播之后,再读为什么 ResNet 能训练 152 层,会更容易看懂它为什么强调 shortcut 和恒等映射。
和矩阵微积分的关系
本文刻意只保留了神经网络训练所需的基础数学直觉。严格地说,当 $x$、$a^{(l)}$、$W^{(l)}$ 都是向量或矩阵时,反向传播会涉及 Jacobian、向量-Jacobian 乘积和张量形状推理。
如果你想继续看更严谨的矩阵版本,可以读 线性代数进阶:矩阵微积分如何服务反向传播。那篇文章会解释为什么深度学习框架通常不会显式构造完整 Jacobian,而是高效计算反向传播真正需要的梯度乘积。
总结
正向传播回答“给定输入和参数,模型会输出什么”;反向传播回答“为了让损失变小,每个参数应该往哪个方向调整”。
神经网络训练的核心不是某个单独公式,而是一条完整链路:输入经过层层函数复合得到预测,预测通过损失函数变成误差,误差再通过链式法则反向分配给每一层参数。掌握这条链路之后,再进入 CNN、ResNet 和更复杂的模型结构会自然很多。
参考
- Deep Learning - Ian Goodfellow, Yoshua Bengio, Aaron Courville
- Neural Networks and Deep Learning - Michael Nielsen
- Automatic Differentiation in Machine Learning: a Survey - Baydin et al.