查看全课大纲(第 28 / 93 节)
  1. 1.计算机视觉概述
  2. 2.计算机视觉应用场景
  3. 3.困难与挑战
  4. 4.开源库介绍
  5. 5.图像显示与存储
  6. 6.CLAHE
  7. 7.腐蚀膨胀与开闭运算
  8. 8.滤波与边缘填充
  9. 9.各种滤波
  10. 10.图像金字塔
  11. 11.傅里叶变换
  12. 12.短时傅里叶变换与小波变换
  13. 13.颜色特征与颜色直方图
  14. 14.几何特征_边缘
  15. 15.几何特征-角点与斑点
  16. 16.局部特征-SIFT特征描述子
  17. 17.局部特征-SURF特征描述子
  18. 18.局部特征-ORB特征描述子
  19. 19.其他特征描述子
  20. 20.传统图像分割技术
  21. 21.GraphCut
  22. 22.GrabCut
  23. 23.人脸检测的传统方法
  24. 24.行人检测的传统方法HOG+SVM
  25. 25.行人检测DPM
  26. 26.人工神经网络的架构
  27. 27.目标函数与梯度下降
  28. 28.误差反向传播
  29. 29.可手算的神经网络
  30. 30.改进目标函数与克服梯度消散
  31. 31.神经网络之传统神经网络与深度网络的差异
  32. 32.各种梯度下降算法
  33. 33.如何选择合适的梯度下降算法
  34. 34.避免过适应
  35. 35.CNN的架构
  36. 36.CNN的误差反向传播_卷积层
  37. 37.CNN的误差反向传播_池化层
  38. 38.分类所用的数据集
  39. 39.Alexnet详解
  40. 40.VGG详解
  41. 41.GoogLeNet_v1
  42. 42.BatchNormalization
  43. 43.GoogLeNet_v3v4
  44. 44.ResNet
  45. 45.ResNeXt
  46. 46.CNN设计准则
  47. 47.图像检索与推土机距离
  48. 48.基于颜色相似性
  49. 49.基于形状相似性
  50. 50.基于纹理相似性
  51. 51.基于局部特征相似性
  52. 52.DL的二值哈希编码
  53. 53.高维空间中检索KDTree
  54. 54.高维空间中检索LSH
  55. 55.目标识别任务综述
  56. 56.RCNN
  57. 57.SPP-Net
  58. 58.Fast R-CNN
  59. 59.Faster R-CNN
  60. 60.R-CNN系列模型回顾
  61. 61.R-FCN的设计思路
  62. 62.R-FCN的实现
  63. 63.YOLOv1
  64. 64.YOLOv2_9000
  65. 65.YOLOv3
  66. 66.图像分割应用场景与数据集
  67. 67.上采样的实现方式
  68. 68.FCN网络架构的由来
  69. 69.FCN构建细节
  70. 70.DeepLabv1
  71. 71.CRF
  72. 72.DeepLabv2
  73. 73.DeepLabv3
  74. 74.特定的医学影像分割任务
  75. 75.U-Net
  76. 76.3D U-Net
  77. 77.V-Net
  78. 78.DenseNet
  79. 79.病理切片分析任务及数据集
  80. 80.病理切片分析
  81. 81.图说任务及其传统处理方法
  82. 82.深度语言模型与RNN
  83. 83.LSTM及其变种
  84. 84.NIC模型
  85. 85.SAT模型
  86. 86.图说的性能指标
  87. 87.判别模型与生成模型
  88. 88.KL散度与JS散度
  89. 89.VAE
  90. 90.GAN的基本原理
  91. 91.DCGAN与CGAN
  92. 92.Wasserstein GAN
  93. 93.SRGAN

误差反向传播

小象实战讲义 · 基于深度学习的计算机视觉

在上一节我们学习了梯度下降法,它是优化神经网络权重的核心思想。然而,对于一个由多层神经元构成的复杂网络,如何高效地计算目标函数对每一层权重的梯度,是训练能否成功的关键。本节将深入讲解神经网络训练的基石——误差反向传播(Backpropagation, BP)算法,它将告诉我们如何将输出层的误差,一层层地“反向”传播回网络深处,从而计算出所有参数的梯度。

💡 核心导读

  • 链式法则:理解误差反向传播的数学核心,即如何通过中间变量逐层求导。
  • 输出层梯度:学习如何计算目标函数对隐藏层与输出层之间权重的偏导数。
  • 隐藏层梯度:掌握如何将误差进一步反向传播,计算输入层与隐藏层之间权重的偏导数。
  • 统一公式:总结误差反向传播的通用迭代公式,理解其“残差”与“输入”相乘的本质。
  • 算法意义:认识BP算法在神经网络发展史上的奠基性作用。

梯度下降与链式法则回顾

在开始推导之前,我们需要明确目标。神经网络的训练目标是最小化一个目标函数(或称损失函数)。对于分类问题,一个常见的目标函数是均方误差(Mean Squared Error, MSE)。假设网络有 c 个输出单元,对于单个样本,期望输出为 t = (t1, t2, ..., tc),网络实际输出为 z = (z1, z2, ..., zc),则目标函数 J 定义为:

J(w)=12∑k=1c(tk−zk)2J(\mathbf{w}) = \frac{1}{2} \sum_{k=1}^{c} (t_k - z_k)^2

其中,w 代表网络中所有的权重参数。我们的任务就是找到一组 w,使得 J(w) 最小。

梯度下降法告诉我们,要找到最小值,需要沿着目标函数梯度的反方向(即下降最快的方向)更新参数。参数 w 在第 m+1 轮的更新公式为:

w(m+1)=w(m)+Δw(m)=w(m)−η⋅∂J∂w\mathbf{w}^{(m+1)} = \mathbf{w}^{(m)} + \Delta \mathbf{w}^{(m)} = \mathbf{w}^{(m)} - \eta \cdot \frac{\partial J}{\partial \mathbf{w}}

其中,η 是学习率(步长),∂J/∂w 是目标函数对权重的梯度(偏导数向量)。问题的核心就转化为:如何高效地计算这个梯度 ∂J/∂w?

对于一个多层网络,J 与深层权重 w 的关系是嵌套了多层函数的复杂关系。直接求导非常困难。这时就需要用到微积分中的链式法则。

链式法则指出,如果 y 是 u 的函数,而 u 又是 x 的函数,即 y = f(u), u = g(x),那么 y 对 x 的导数可以通过中间变量 u 来传递:

dydx=dydu⋅dudx\frac{dy}{dx} = \frac{dy}{du} \cdot \frac{du}{dx}

在神经网络中,我们将大量使用链式法则,通过引入每一层神经元的加权输入和激活输出作为中间变量,将复杂的全局梯度分解为一系列简单的局部梯度相乘。

网络架构与符号定义

为了清晰地推导,我们以一个典型的三层前馈神经网络为例,它包括输入层、一个隐藏层和输出层。

  • 输入层:有 d 个神经元,输入向量记为 x = (x1, x2, ..., xd)。输入层神经元没有计算功能,仅传递信号。
  • 隐藏层:有 H 个神经元。第 j 个隐藏层神经元的加权输入(或称净输入)记为 net_j,其输出记为 y_j。
    • net_j = Σ_{i=1}^{d} w_{ji} * x_i (w_{ji} 是输入层第 i 个神经元到隐藏层第 j 个神经元的权重)
    • y_j = f(net_j) (f 是激活函数,如 sigmoid)
  • 输出层:有 c 个神经元。第 k 个输出层神经元的加权输入记为 net_k,其输出记为 z_k。
    • net_k = Σ_{j=1}^{H} w_{kj} * y_j (w_{kj} 是隐藏层第 j 个神经元到输出层第 k 个神经元的权重)
    • z_k = f(net_k)

我们的目标是计算目标函数 J 对两类权重 w_{kj} 和 w_{ji} 的偏导数 ∂J/∂w_{kj} 和 ∂J/∂w_{ji}。

输出层权重的梯度计算

首先计算离误差更近的权重 w_{kj}(隐藏层到输出层)的梯度。

根据链式法则,我们可以引入中间变量 net_k(输出层第 k 个神经元的加权输入):

∂J∂wkj=∂J∂netk⋅∂netk∂wkj\frac{\partial J}{\partial w_{kj}} = \frac{\partial J}{\partial net_k} \cdot \frac{\partial net_k}{\partial w_{kj}}

第一步:计算 ∂net_k / ∂w_{kj} 根据 net_k 的定义:net_k = w_{k1}y_1 + w_{k2}y_2 + ... + w_{kj}y_j + ...。当对特定的 w_{kj} 求偏导时,只有包含 w_{kj} 的项起作用,其他项视为常数,导数为0。因此: ∂netk∂wkj=yj\frac{\partial net_k}{\partial w_{kj}} = y_j 这一项非常直观:权重 w_{kj} 的系数就是来自隐藏层第 j 个神经元的输出 y_j。

第二步:计算 ∂J / ∂net_k J 与 net_k 没有直接关系,需要再引入中间变量 z_k(输出层第 k 个神经元的输出): ∂J∂netk=∂J∂zk⋅∂zk∂netk\frac{\partial J}{\partial net_k} = \frac{\partial J}{\partial z_k} \cdot \frac{\partial z_k}{\partial net_k}

  • 计算 ∂J / ∂z_k: 根据 J = 1/2 * Σ (t_k - z_k)^2,对 z_k 求偏导(注意求和号中只有第 k 项与 z_k 有关): ∂J∂zk=−(tk−zk)\frac{\partial J}{\partial z_k} = -(t_k - z_k) 这里的负号源于 (t_k - z_k) 的平方求导。

  • 计算 ∂z_k / ∂net_k: 根据 z_k = f(net_k),这就是激活函数 f 在 net_k 处的导数: ∂zk∂netk=f′(netk)\frac{\partial z_k}{\partial net_k} = f'(net_k)

因此,∂J / ∂net_k = -(t_k - z_k) * f'(net_k)。

第三步:合并结果 将第一、二步的结果代入链式法则: ∂J∂wkj=[−(tk−zk)f′(netk)]⋅yj\frac{\partial J}{\partial w_{kj}} = [-(t_k - z_k) f'(net_k)] \cdot y_j

为了简化表达式,我们定义一个非常重要的量——残差(δ,delta),对于输出层第 k 个神经元: δk≜(tk−zk)f′(netk)\delta_k \triangleq (t_k - z_k) f'(net_k) 注意,δ\delta 取 (t−z)f′(t-z)f' 的方向,因此下面梯度式带负号;更新时用 W←W−η⋅∂J/∂WW \leftarrow W - \eta \cdot \partial J/\partial W 自然体现。于是,梯度可以简洁地写为: ∂J∂wkj=−δk⋅yj\frac{\partial J}{\partial w_{kj}} = -\delta_k \cdot y_j

物理意义:输出层权重 w_{kj} 的梯度,等于输出层神经元 k 的残差 δ_k 与来自隐藏层神经元 j 的输入 y_j 的乘积的负值。δ_k 包含了该神经元的输出误差 (t_k - z_k) 和激活函数在该点的变化率 f'(net_k)。

隐藏层权重的梯度计算

接下来计算更靠内的权重 w_{ji}(输入层到隐藏层)的梯度。这需要将误差从输出层进一步反向传播到隐藏层。

同样使用链式法则,并引入隐藏层神经元 j 的加权输入 net_j 作为中间变量: ∂J∂wji=∂J∂netj⋅∂netj∂wji\frac{\partial J}{\partial w_{ji}} = \frac{\partial J}{\partial net_j} \cdot \frac{\partial net_j}{\partial w_{ji}}

第一步:计算 ∂net_j / ∂w_{ji} 与输出层情况类似,根据 net_j = Σ_i w_{ji} x_i,对特定的 w_{ji} 求偏导,结果是其系数 x_i: ∂netj∂wji=xi\frac{\partial net_j}{\partial w_{ji}} = x_i

第二步:计算 ∂J / ∂net_j 这是关键且稍复杂的一步。J 与 net_j 没有直接联系,我们引入隐藏层神经元 j 的输出 y_j 作为中间变量: ∂J∂netj=∂J∂yj⋅∂yj∂netj\frac{\partial J}{\partial net_j} = \frac{\partial J}{\partial y_j} \cdot \frac{\partial y_j}{\partial net_j}

  • 计算 ∂y_j / ∂net_j: 根据 y_j = f(net_j),这就是隐藏层激活函数的导数: ∂yj∂netj=f′(netj)\frac{\partial y_j}{\partial net_j} = f'(net_j)

  • 计算 ∂J / ∂y_j: 这是难点。隐藏层神经元 j 的输出 y_j 会馈送到所有输出层神经元(k=1 到 c)。因此,y_j 的变化会通过所有与之相连的权重 w_{kj} 影响所有输出 z_k,进而影响总误差 J。必须考虑所有路径的影响。 ∂J∂yj=∑k=1c∂J∂netk⋅∂netk∂yj\frac{\partial J}{\partial y_j} = \sum_{k=1}^{c} \frac{\partial J}{\partial net_k} \cdot \frac{\partial net_k}{\partial y_j} 这个求和体现了“全连接”的特性。

    • ∂J/∂net_k 我们在计算输出层梯度时已经求过了,它就是 -δ_k(注意,δ_k 本身不带负号,定义为 δ_k = (t_k - z_k)f'(net_k),因此 ∂J/∂net_k = -δ_k)。
    • ∂net_k/∂y_j 根据 net_k 的定义很简单:net_k = Σ_j w_{kj} y_j,所以 ∂net_k/∂y_j = w_{kj}。

    因此: ∂J∂yj=∑k=1c(−δk)⋅wkj=−∑k=1cδkwkj\frac{\partial J}{\partial y_j} = \sum_{k=1}^{c} (-\delta_k) \cdot w_{kj} = -\sum_{k=1}^{c} \delta_k w_{kj}

第三步:合并结果 将各部分组合起来: ∂J∂netj=(−∑k=1cδkwkj)⋅f′(netj)\frac{\partial J}{\partial net_j} = \left( -\sum_{k=1}^{c} \delta_k w_{kj} \right) \cdot f'(net_j)

同样,我们为隐藏层神经元 j 定义一个残差 δ_j: δj≜f′(netj)∑k=1cδkwkj\delta_j \triangleq f'(net_j) \sum_{k=1}^{c} \delta_k w_{kj} 注意,这里 δ_j 的定义与输出层 δ_k 不同。输出层的 δ_k 直接依赖于输出误差 (t_k - z_k);而隐藏层的 δ_j 依赖于下一层(输出层)所有神经元的残差 δ_k 的加权和,权重正是连接这两层的权重 w_{kj}。

于是,隐藏层权重的梯度可以简洁地写为: ∂J∂wji=−δj⋅xi\frac{\partial J}{\partial w_{ji}} = -\delta_j \cdot x_i

物理意义:隐藏层权重 w_{ji} 的梯度,等于隐藏层神经元 j 的残差 δ_j 与来自输入层神经元 i 的输入 x_i 的乘积的负值。δ_j 不是直接由最终误差计算,而是由后一层所有神经元的残差反向传播、加权求和后再乘以本层激活函数的导数得到。

误差反向传播的通用公式

总结上面的推导,我们可以得到误差反向传播算法的统一表述。

对于网络中的任意一层(记当前层为 l),假设它接收前一层(l-1层)的输入 a^{(l-1)},计算加权和 z^{(l)} = W^{(l)} a^{(l-1)},然后通过激活函数得到输出 a^{(l)} = f(z^{(l)})。

  1. 前向传播:计算每一层的 z^{(l)} 和 a^{(l)},直到得到最终输出 a^{(L)}(L 为输出层)。
  2. 计算输出层残差: δ(L)=(t−a(L))⊙f′(z(L))\delta^{(L)} = (t - a^{(L)}) \odot f'(z^{(L)}) 其中 ⊙ 表示逐元素相乘(Hadamard积)。
  3. 反向传播残差:对于 l = L-1, L-2, ..., 2(即从后往前逐层计算): δ(l)=(W(l+1))Tδ(l+1)⊙f′(z(l))\delta^{(l)} = (W^{(l+1)})^T \delta^{(l+1)} \odot f'(z^{(l)}) 这里 (W^{(l+1)})^T δ^{(l+1)} 正是下一层残差的加权和。
  4. 计算梯度:得到各层残差后,权重 W^{(l)} 的梯度为: ∂J∂W(l)=−δ(l)(a(l−1))T\frac{\partial J}{\partial W^{(l)}} = -\delta^{(l)} (a^{(l-1)})^T (这里 δ=(t−a)⊙f′\delta = (t-a)\odot f' 的定义已把“目标减预测”的方向含进去,故梯度带负号;若把残差定义为 (a−t)⊙f′(a-t)\odot f',负号就要反过来,两者只能取其一)。
  5. 更新权重:使用梯度下降法更新权重: W(l)←W(l)−η⋅∂J∂W(l)W^{(l)} \leftarrow W^{(l)} - \eta \cdot \frac{\partial J}{\partial W^{(l)}}

这个算法清晰地展示了“误差反向传播”的过程:误差信号 δ 从输出层开始,沿着网络反向传播,每经过一层,就利用该层的激活函数导数和来自后一层的权重矩阵进行转换。一旦计算出某一层的残差 δ,该层权重的梯度就简单地等于负的残差与前一层输出的外积(负号由 δ=(t−a)f′\delta=(t-a)f' 的定义带来)。

BP算法的意义与影响

误差反向传播算法因 Rumelhart、Hinton 和 Williams 在 1986 年发表的论文《Learning representations by back-propagating errors》而广为人知(其思想更早可追溯到 Werbos 等人在 1970 年代的工作)。这篇论文让 BP 从数学技巧变成了可操作的训练方法,解决了多层神经网络训练的核心难题,使得利用梯度下降法训练深度网络成为可能。

  • 高效性:BP算法利用链式法则,把“求所有参数的梯度”从“每个参数各做一次前向传播”的平方级代价,降到与一次前向传播同量级(代价线性于网络的边数),使得训练大规模网络变得可行。
  • 奠基性:它是现代深度学习几乎所有训练流程的基础。后续出现的各种优化器(如SGD、Adam)、正则化技术、乃至卷积神经网络(CNN)和循环神经网络(RNN)的训练,都建立在BP算出的梯度之上。
  • 局限性:标准的BP算法也存在一些挑战,如梯度消失/爆炸问题(在深层网络中,梯度在反向传播时可能指数级缩小或增大)、对初始值和学习率敏感、容易陷入局部极小值等。这些挑战也催生了ReLU激活函数、残差连接(ResNet)、批量归一化(BatchNorm)等一系列重要的深度学习技术。

📝 动手练一练

  1. 概念辨析:在误差反向传播中,输出层神经元的残差 δ_k 和隐藏层神经元的残差 δ_j 在计算公式上有何本质区别?这反映了网络训练的什么逻辑?
👉 点击查看参考答案

输出层残差 δ_k:直接由该神经元的目标输出与实际输出的误差 (t_k - z_k) 乘以激活函数在当前位置的导数 f'(net_k) 得到。它衡量的是该输出节点本身的“错误程度”。

隐藏层残差 δ_j:不由最终输出误差直接计算,而是由后一层所有神经元的残差 δ_k 反向传播回来,经过权重 w_{kj} 加权求和后,再乘以本层激活函数的导数 f'(net_j) 得到。即 δ_j = f'(net_j) * Σ_k (δ_k * w_{kj})。

反映的逻辑:这体现了“责任分配”的思想。输出层的误差是明确的,可以直接计算每个输出节点的责任(δ_k)。对于隐藏层节点,我们无法直接知道它“应该”输出多少才是对的,但我们可以根据它所连接的下一层节点的责任(δ_k)以及连接强度(w_{kj}) 来反推它的责任(δ_j)。连接越强(w_{kj} 越大)的下一层节点,其责任对当前隐藏层节点的影响就越大。这就像团队工作:最终成果(输出)的偏差,会逐级回溯到每个成员(神经元)身上,成员的责任大小取决于他/她对出错的下一级成员的影响程度。

  1. 手算练习:考虑一个极简网络:输入层1个节点(x=0.5),隐藏层1个节点(权重 w_ji=0.8,偏置 b_j=0.1,激活函数 f(x)=sigmoid(x)),输出层1个节点(权重 w_kj=1.5,偏置 b_k=0.2,激活函数同上)。给定目标输出 t=0.9。 a) 请进行前向传播,计算网络实际输出 z。 b) 假设使用MSE损失,计算输出层残差 δ_k(提示:sigmoid导数 f'(x)=f(x)*(1-f(x)))。 c) 计算隐藏层残差 δ_j。 d) 根据BP公式,计算损失 J 对权重 w_kj 和 w_ji 的梯度(学习率 η=0.1)。
👉 点击查看参考答案

a) 前向传播

  1. 隐藏层加权输入:net_j = w_ji * x + b_j = 0.8*0.5 + 0.1 = 0.5
  2. 隐藏层输出:y_j = sigmoid(net_j) = 1/(1+e^{-0.5}) ≈ 0.6225
  3. 输出层加权输入:net_k = w_kj * y_j + b_k = 1.5*0.6225 + 0.2 ≈ 1.13375
  4. 网络输出:z = sigmoid(net_k) = 1/(1+e^{-1.13375}) ≈ 0.7565

b) 输出层残差 δ_k 输出误差:(t - z) = 0.9 - 0.7565 = 0.1435 sigmoid在 net_k 处的导数:f'(net_k) = z*(1-z) = 0.7565*(1-0.7565) ≈ 0.1842 δ_k = (t - z) * f'(net_k) = 0.1435 * 0.1842 ≈ 0.02643

c) 隐藏层残差 δ_j 首先需要 f'(net_j) = y_j*(1-y_j) = 0.6225*(1-0.6225) ≈ 0.2350 δ_j = f'(net_j) * (δ_k * w_kj) = 0.2350 * (0.02643 * 1.5) ≈ 0.2350 * 0.039645 ≈ 0.00932

d) 计算梯度

  • 对 w_kj 的梯度:∂J/∂w_kj = -δ_k * y_j = -0.02643 * 0.6225 ≈ -0.01645 更新量(带学习率):Δw_kj = -η * ∂J/∂w_kj = -0.1 * (-0.01645) = 0.001645 更新后:w_kj_new ≈ 1.5 + 0.001645 = 1.501645

  • 对 w_ji 的梯度:∂J/∂w_ji = -δ_j * x = -0.00932 * 0.5 = -0.00466 更新量:Δw_ji = -η * ∂J/∂w_ji = -0.1 * (-0.00466) = 0.000466 更新后:w_ji_new ≈ 0.8 + 0.000466 = 0.800466

可以看到,经过一次BP更新,两个权重都得到了微调,目的是使下一次前向传播的输出 z 更接近目标值 0.9。

本章小结

本节我们深入推导了神经网络的核心训练算法——误差反向传播(BP)。我们从梯度下降的目标出发,利用链式法则,一步步推导出输出层和隐藏层权重的梯度计算公式,并最终将其统一为简洁的“残差 × 输入”的形式。BP算法的精髓在于它高效地实现了误差信号从网络输出端到输入端的反向流动,使得深度网络的端到端训练成为可能。

今天你几乎不需要手写这套公式:PyTorch、TensorFlow 的自动微分(autograd)会在前向传播时记录计算图,反向时自动套用本节这条残差递推。但正因为框架把它藏了起来,理解 δ 的来源、知道梯度为什么会消失,才更能帮你判断训练异常到底出在哪一层。

📋 行动清单

  • 理解链式法则的应用:确保能清晰说出在计算 ∂J/∂w_{kj} 和 ∂J/∂w_{ji} 时,分别引入了哪些中间变量,以及每一步求导的物理意义。
  • 掌握残差的定义与传播:能够区分输出层残差 δ_k 和隐藏层残差 δ_j 的计算公式,并理解 δ_j 是 δ_k 的加权和这一反向传播的本质。
  • 尝试手算推导:参照动手练一练中的极简网络,在纸上完整地演练一遍前向传播、误差计算、反向传播和权重更新的全过程,加深对公式的理解。

—— 小象教研组