专栏文章

反向传播

算法·理论参与者 3已保存评论 2

文章操作

快速查看文章及其快照的属性,并进行相关操作。

当前评论
2 条
当前快照
1 份
快照标识符
@mlia4q61
此快照首次捕获于
2026/02/12 01:04
上周
此快照最后确认于
2026/02/19 01:13
17 小时前
查看原文

反向传播

反向传播是使神经网络能够学习的最基础、最重要的算法之一,原论文只有短短四页,却深刻地改变了世界,本文章是反向传播的入门文章。

前置知识

前向传播

对于某层 lljj 个神经元的激活值 aj(l)a_j^{(l)},可以表示为:
aj(l)=σ(zj(l))a_j^{(l)}=\sigma(z_j^{(l)})
其中 σ\sigma 是激活函数,zj(l)z_j^{(l)} 为该神经元的加权输入:
zj(l)=k=1nl1wjk(l)ak(l1)+bj(l)z_j^{(l)}=\sum_{k=1}^{n_{l-1}}w_{jk}^{(l)}a_k^{(l-1)}+b_j^{(l)}
综合起来,可以得到:
aj(l)=σ(k=1nl1wjkak(l1)+bj(l))a_j^{(l)}=\sigma\left(\sum_{k=1}^{n_{l-1}}w_{jk}a_k^{(l-1)}+b_j^{(l)}\right)
也就是,只要知道前一层的所有神经元激活值,根据权重和偏置项,就可以计算出下一层所有神经元的激活值。通过这样层层传递,我们就可以从输入层一步步计算到输出层,这个过程叫做前向传播

损失函数

我们通过损失函数来评价一个神经网络的好坏,也就是它的输出是否符合预期,评价的依据就是样本(或数据)。
如果神经网络的输出与数据接近,我们就说这个神经网络不错,反之则说明这个神经网络有待提升。常用的做法是使用一个可微的函数来衡量网络输出与真实值之间的差异。对于一组数据,我们的损失函数常常定义为所有样本损失的平均:
C(w)=1nk=1nCk(w)C(\vec{w})=\frac{1}{n}\sum_{k=1}^nC_k(\vec{w})
其中 CC 是损失函数,w\vec{w} 是神经网络的所有参数,CkC_k 是第 kk 个样本的“误差”,nn 是样本的数量。
计算损失的方法有很多,以均方损失(MSE)为例,CkC_k 可以表示为:
Ck(w)=j=1nL(aj(L)yj)2C_k(\vec{w})=\sum_{j=1}^{n_{L}}(a_j^{(L)}-y_j)^2
其中 yjy_j 就是样本预期的输出,nLn_L 指的是第 LL 层(输出层)神经元数量。可能有疑惑的是这里还有一个平方,它的作用是统一符号,相较于绝对值它能让我们更敏感于大的损失,忽略较小损失的影响。
以上部分不详细展开,如果你阅读时感到吃力,请查阅相关资料作为补充。

方向导数

方向导数就是对于函数 ff,其在点 x\vec{x} 处沿着某一向量 v\vec{v} 方向进行一个非常微小的变化时,函数值的变化率。
需要注意的是,方向导数是单侧极限(t0+t \to 0^+),因此它仅描述沿 v\vec{v} 正方向的变化率,与偏导数不同。
下面给出方向导数的定义式(下文的 v\vec{v} 都是单位向量):
fvx=limt0+f(x+tv)f(x)t\left.\frac{\partial f}{\partial \vec{v}}\right|_{\vec{x}}=\lim_{t\to0^+}\frac{f(\vec{x}+t\vec{v})-f(\vec{x})}{t}
如果 ff 可微,我们可以用全微分代替 f\partial f,使用方向角代替各个方向的变化量:
fvx=limt0+fx(x)tcosα1+fy(x)tcosα2+...+fn(x)tcosαnt\left.\frac{\partial f}{\partial \vec{v}}\right|_{\vec{x}}=\lim_{t\to0^+}\frac{\frac{\partial f}{\partial x}(\vec{x})t\cos\alpha_1+\frac{\partial f}{\partial y}(\vec{x})t\cos\alpha_2+...+\frac{\partial f}{\partial n}(\vec{x})t\cos\alpha_n}{t}
发现 tt 可以被消掉,整理,得:
fvx=fx(x)cosα1+fy(x)cosα2+...+fn(x)cosαn\left.\frac{\partial f}{\partial \vec{v}}\right|_{\vec{x}}=\frac{\partial f}{\partial x}(\vec{x})\cos\alpha_1+\frac{\partial f}{\partial y}(\vec{x})\cos\alpha_2+...+\frac{\partial f}{\partial n}(\vec{x})\cos\alpha_n
同时,我们还可以将右式写为向量点乘的形式:
fvx=(fx(x)fy(x)fn(x))(cosα1cosα2cosαn)\left.\frac{\partial f}{\partial \vec{v}}\right|_{\vec{x}}= \left(\begin{array}{c} \frac{\partial f}{\partial x}(\vec{x})\\ \frac{\partial f}{\partial y}(\vec{x})\\ \vdots\\ \frac{\partial f}{\partial n}(\vec{x}) \end{array}\right) \cdot \left(\begin{array}{c} \cos\alpha_1\\ \cos\alpha_2\\ \vdots\\ \cos\alpha_n \end{array}\right)
可以发现左侧的向量只关于 ff 的偏导在点 x\vec{x} 的取值,与方向向量 v\vec{v} 无关,也就是只要求导数的点固定,无论向何方向求偏导数,左边的向量保持不变。
显然,当向量与左侧的向量方向一致时,方向导数最大(因为是向量点积)。这告诉我们,使得某点方向导数最大的方向,实际上就是该点对于各个坐标轴求偏导的值所组成的向量(左侧的向量),我们称这个向量为该点的梯度

梯度

梯度定义为偏导组成的向量值函数,它是一个函数,指示函数变化最快的方向。
f=(fx1,fx2,,fxn)T\nabla f = \left( \frac{\partial f}{\partial x_1}, \frac{\partial f}{\partial x_2}, \dots, \frac{\partial f}{\partial x_n} \right)^T
ff 在点 x\vec{x} 的梯度就是 f\nabla f 在点 x\vec{x} 的取值:
f(x)=(fx1(x),fx2(x),,fxn(x))T\nabla f(\vec{x}) = \left( \frac{\partial f}{\partial x_1}(\vec{x}), \frac{\partial f}{\partial x_2}(\vec{x}), \dots, \frac{\partial f}{\partial x_n}(\vec{x}) \right)^T
通过梯度,我们也可以简化方向导数的定义:
fvx=f(x)v\left.\frac{\partial f}{\partial \vec{v}}\right|_{\vec{x}}=\nabla f(\vec{x}) \cdot \vec{v}

梯度下降

相信在理解梯度后你一定联想到了损失函数,实际上我们正是通过梯度得知如何调整神经网络的各个参数,使得损失函数变小,这个算法叫做梯度下降算法。
具体的,我们只需要求出在当前的梯度 f(w)\nabla f(\vec{w}),随后就可以更新权重:
wwαC(w)\vec{w} \gets \vec{w} - \alpha \cdot \nabla C(\vec{w})
这里需要解释两点:
  1. 为什么是减号 —— 梯度是增长最快的方向,所以我们要取负,让其沿着减小最快的方向前进,我们当然不希望我们的人工智能越学越傻。
  2. α\alpha 是什么含义 —— 梯度本质是一个方向,而沿着这个方向走多远由 α\alpha 决定,它是一个正实数,叫做学习率(或步长),这个值不宜过大也不宜过小,过大可能会导致振荡甚至发散,过小导致模型收敛过慢。
根据使用样本的数量,梯度下降有以下常见变体:
  • 批量梯度下降(BGD):使用全部样本计算梯度,更新稳定但计算开销大;
  • 随机梯度下降(SGD):每次随机使用一个样本计算梯度并更新参数,计算效率高但更新波动较大;
  • 小批量梯度下降(Mini-batch GD):每次使用一小批样本计算梯度,是前述两种方法的折中,最为常用。

反向传播

既然我们通过梯度下降算法来对参数进行优化,那我们就需要能求出每个参数对损失函数的梯度,也就是 Cw\frac{\partial C}{\partial w}Cb\frac{\partial C}{\partial b}
神经网络通常很大,如果暴力求解,计算量就会是个天文数字,并且效率极低,所以我们就需要反向传播,它的作用就是快速地求解出梯度。
它是如何做到的?

误差

我们需要一个叫做误差 δ\delta 的小东西帮助我们求出梯度(因为误差比梯度更好求和推导,我们在下文会推导出误差和梯度的关系),它实际上就是某个加权输入对总损失的敏感程度(当然就是偏导数),定义为:
δj(l)=Czj(l)\delta_j^{(l)}=\frac{\partial C}{\partial z_j^{(l)}}
嗯,思考一下,在刚开始,我们能求出哪些神经元的误差?
当然是输出层,以均方损失为例,我们带入:
δj(L)=Caj(L)σ(zj(L))=2(aj(L)yj)σ(zj(L))\delta_j^{(L)}=\frac{\partial C}{\partial a_j^{(L)}}\cdot\sigma^{'}(z_j^{(L)})=2(a_j^{(L)}-y_j)\cdot\sigma^{'}(z_j^{(L)})
再次提醒,这个式子只是在 MSE 情况下的特例,不要当成公理!
最后的式子每项都可求,那么输出层的每个神经元的误差就都可求,并且很有意思的是它的含义就是(预测值 - 真实值)× 激活函数在该点的变化率,也就是“误差”的字面含义。

反向,传播!

那么,我们就知道了输出层所有神经元的误差,趁火打劫,是否可以求出输出层的前一层、前两层的误差,最后到输入层呢?这就是“反向传播”名字的由来。
继续推导,也就是我们只需要能通过 l+1l+1 层的误差求出 ll 层的误差,那么整个神经网络的误差就都已知了(有点数学归纳法的意思?)
我们需要一个你一定学过的东西:链式法则(莫名想到宋浩的全都不考)
可以自己尝试画一个路径图,会清晰很多:
δj(l)=Czj(l)=k=1nl+1Czk(l+1)zk(l+1)zj(l)\delta _j ^{(l)}=\frac{\partial C}{\partial z_j^{(l)}}=\sum_{k=1}^{n_{l+1}}\frac{\partial C}{\partial z_k^{(l+1)}}\cdot\frac{\partial z_k^{(l+1)}}{\partial z_j^{(l)}}
看起来有点吓人,但是能够拆成两项,左面是 Czk(l+1)\frac{\partial C}{\partial z_k^{(l+1)}},这不正是 δk(l+1)\delta_k^{(l+1)}
所以我们就只需要算第二项,也就是 zk(l+1)zj(l)\frac{\partial z_k^{(l+1)}}{\partial z_j^{(l)}} 的值,也很简单,因为我们知道:
zk(l+1)=m=1nlwkm(l+1)am(l)+bk(l+1)z_k^{(l+1)}=\sum_{m=1}^{n_{l}}w_{km}^{(l+1)}a_m^{(l)}+b_k^{(l+1)}
我们发现,只有当 m=jm=jzj(l)z_j^{(l)} 才会对 zk(l+1)z_k^{(l+1)} 产生贡献,那么复杂度一下就减少了,再次应用链式法则:
zk(l+1)zj(l)=zk(l+1)aj(l)aj(l)zj(l)=wkj(l+1)σ(zj(l))\frac{\partial z_k^{(l+1)}}{\partial z_j^{(l)}}=\frac{\partial z_k^{(l+1)}}{\partial a_j^{(l)}}\cdot \frac{\partial a_j^{(l)}}{\partial z_j^{(l)}}=w_{kj}^{(l+1)}\cdot \sigma^{'}(z_j^{(l)})
将这两部分带回原式:
δj(l)=k=1nl+1δk(l+1)wkj(l+1)σ(zj(l))\delta _j ^{(l)}=\sum_{k=1}^{n_{l+1}}\delta_k^{(l+1)}\cdot w_{kj}^{(l+1)}\cdot \sigma^{'}(z_j^{(l)})
σ(zj(l))\sigma^{'}(z_j^{(l)}) 是个常数,与 kk 无关,那么就变成:
δj(l)=σ(zj(l))k=1nl+1δk(l+1)wkj(l+1)\delta _j ^{(l)}=\sigma^{'}(z_j^{(l)})\sum_{k=1}^{n_{l+1}}\delta_k^{(l+1)}w_{kj}^{(l+1)}
至此,反向传播的核心部分就搞定了,但是你在网上查阅可能发现,它的公式和目前的有出入,因为我们需要引入另一个小东西:线性代数。

线性代数的表示方法

那么就把一些东西转换成向量和矩阵,定义:
δ(l)\delta^{(l)} 是第 ll 层所有误差构成的向量,也就是 δ(l)=(δ1(l),δ2(l),...,δnl(l))T\delta^{(l)}=(\delta^{(l)}_1,\delta^{(l)}_2,...,\delta^{(l)}_{n_{l}})^T
Wl+1W^{l+1}ll 层到 l+1l+1 层的权重构成的 nl+1×nln_{l+1}\times n_l 维矩阵,那么 wkjl+1=Wkjl+1w_{kj}^{l+1}=W_{kj}^{l+1}
\odot 表示逐元素相乘,也就是 Hadamard 积。
那么再观察上面的公式,求和部分实际上就是 δ(l)\delta^{(l)}(Wl+1)T(W^{l+1})^T 的乘法,也就是
k=1nl+1δk(l+1)wkj(l+1)=(Wl+1)Tδl+1\sum_{k=1}^{n_{l+1}}\delta_k^{(l+1)}w_{kj}^{(l+1)}=(W^{l+1})^T\delta^{l+1}
最后,也就能得到公式:
δ(l)=((Wl+1)Tδ(l+1))σ(z(l))\delta^{(l)} = \left((W^{l+1})^T \delta^{(l+1)}\right) \odot \sigma'(z^{(l)})
这个公式非常优美,并且很清晰。可能你会有疑问为什么是转置矩阵,因为我们干的就是“反向”传播,这么一看太漂亮了。

从误差到梯度

洋洋洒洒这么多推导,可能你会有些头晕,这是正常的。接下来我们干点简单的事情,就是看看误差和梯度究竟有什么关系。
首先是看 Cbj(l)\frac{\partial C}{\partial b_{j}^{(l)}},这个很简单,因为 zj(l)=...+bj(l)z_j^{(l)} = ... + b_j^{(l)},所以正好就是误差本身:
Cbj(l)=Czj(l)zj(l)bj(l)=δj(l)1=δj(l)\frac{\partial C}{\partial b_{j}^{(l)}}=\frac{\partial C}{\partial z_{j}^{(l)}}\cdot \frac{\partial z_j^{(l)}}{\partial b_{j}^{(l)}}=\delta_j^{(l)}\cdot1=\delta_j^{(l)}
然后是权重 Cwjk(l)\frac{\partial C}{\partial w_{jk}^{(l)}},同样是链式法则:
Cwjk(l)=Czj(l)zj(l)wjk(l)=δj(l)ak(l1)\frac{\partial C}{\partial w_{jk}^{(l)}}=\frac{\partial C}{\partial z_j^{(l)}}\cdot\frac{\partial z_j^{(l)}}{\partial w_{jk}^{(l)}}=\delta_j^{(l)}\cdot a_k^{(l-1)}
也就是所有参数都可以用误差来简单的表示,至此,整个反向传播算法我们就已经推导完毕。
总结,反向传播的精髓,就是通过计算输出层的误差,沿着网络从后往前(反向)把误差分给上一层的神经元,不断传播。而对于每个神经元,都能通过它分到的误差快速计算出自己的梯度,进而快速更新神经网络的参数。

AIGC 声明

AIGC 声明:本文使用 AI 工具进行错误查找(技术性错误、错别字等),文章全部内容都 AI 生成,放心食用。

评论

2 条评论,欢迎与作者交流。

正在加载评论...