共计 2145 个字符,预计需要花费 6 分钟才能阅读完成。
背景与原理
反向传播(Backpropagation,简称 BP)是训练神经网络的核心算法,通过计算损失函数对网络参数的梯度,指导参数更新方向。理解 BP 算法的关键在于掌握链式求导法则的应用,以及如何在计算图中高效地传递梯度。

对于初学者来说,手动推导 BP 过程不仅能加深理解,还能帮助避免常见的实现错误。下面我们从最简单的单神经元开始,逐步扩展到全连接网络。
数学推导
单神经元示例
考虑一个具有 sigmoid 激活函数的神经元:
z = w*x + b
a = σ(z) = 1/(1+e^{-z})
假设损失函数为 L =(y-a)^2/2,我们需要计算∂L/∂w 和∂L/∂b:
- 计算输出层梯度:∂L/∂a = -(y-a)
- 通过激活函数:∂a/∂z = σ(z)(1-σ(z)) = a(1-a)
- 组合得到:∂L/∂z = (∂L/∂a)(∂a/∂z) = -(y-a)a(1-a)
- 最终参数梯度:
- ∂L/∂w = (∂L/∂z)*x
- ∂L/∂b = ∂L/∂z
全连接层推广
对于多层网络,梯度计算遵循相同的链式法则,但需要从输出层向输入层反向传递。关键公式:
δ^l = (W^{l+1}^T δ^{l+1}) ⊙ σ'(z^l)
∂L/∂W^l = δ^l a^{l-1}^T
∂L/∂b^l = δ^l
代码实现
import numpy as np
class NeuralNetwork:
def __init__(self, layer_sizes):
# 初始化权重和偏置
self.weights = [np.random.randn(y, x)/np.sqrt(x)
for x, y in zip(layer_sizes[:-1], layer_sizes[1:])]
self.biases = [np.zeros((y, 1)) for y in layer_sizes[1:]]
def sigmoid(self, z):
return 1/(1+np.exp(-z))
def sigmoid_prime(self, z):
s = self.sigmoid(z)
return s*(1-s)
def forward(self, x):
"""前向传播计算网络输出"""
activation = x
for w, b in zip(self.weights, self.biases):
z = np.dot(w, activation) + b
activation = self.sigmoid(z)
return activation
def backward(self, x, y):
"""反向传播计算梯度"""
# 前向传播并保存中间值
zs, activations = [], [x]
for w, b in zip(self.weights, self.biases):
z = np.dot(w, activations[-1]) + b
zs.append(z)
activations.append(self.sigmoid(z))
# 反向传播
nabla_w = [np.zeros(w.shape) for w in self.weights]
nabla_b = [np.zeros(b.shape) for b in self.biases]
# 输出层误差
delta = (activations[-1] - y) * self.sigmoid_prime(zs[-1])
nabla_b[-1] = delta
nabla_w[-1] = np.dot(delta, activations[-2].T)
# 隐藏层误差
for l in range(2, len(self.weights)+1):
z = zs[-l]
sp = self.sigmoid_prime(z)
delta = np.dot(self.weights[-l+1].T, delta) * sp
nabla_b[-l] = delta
nabla_w[-l] = np.dot(delta, activations[-l-1].T)
return nabla_w, nabla_b
案例演示:XOR 问题
让我们用 2 -2- 1 网络解决 XOR 问题:
-
输入数据:
X = [[0,0], [0,1], [1,0], [1,1]] y = [0, 1, 1, 0] -
第一次前向传播(随机权重):
- 输入[0,1],隐藏层输出[0.6, 0.4]
-
最终输出 0.55(期望 1)
-
反向传播计算:
- 输出层误差:0.55(1-0.55)(0.55-1) ≈ -0.11
-
隐藏层误差:[w11δ_out, w21δ_out] ⊙ [a1(1-a1), a2(1-a2)]
-
参数更新(学习率 0.5):
- 输出层权重变化:-0.11 * [0.6, 0.4]
- 隐藏层权重变化:按相同原理计算
常见问题分析
- 梯度消失:深层网络中,小梯度连乘会导致早期层更新缓慢。解决方案:
- 使用 ReLU 等非饱和激活函数
-
残差连接
-
梯度爆炸:大梯度连乘可能导致数值溢出。解决方案:
- 梯度裁剪
-
权重正则化
-
学习率选择:
- 太大:震荡或不收敛
- 太小:训练缓慢
- 建议:使用自适应优化器 (Adam) 或学习率衰减
最佳实践
-
梯度检查:用数值梯度验证反向传播实现
def gradient_check(network, x, y, epsilon=1e-7): # 实现数值梯度计算并与反向传播结果比较 -
批量归一化:加速训练并减少对初始化的敏感度
-
早停法:监控验证集性能防止过拟合
延伸思考
-
如何将 BP 算法扩展到卷积神经网络(CNN)?计算图会有哪些变化?
-
在循环神经网络 (RNN) 中,BPTT(随时间反向传播)与标准 BP 有何异同?
正文完
