共计 2070 个字符,预计需要花费 6 分钟才能阅读完成。
背景介绍
反向传播(Backpropagation)是训练神经网络的核心算法,它通过计算损失函数对各层权重的梯度,指导网络参数更新。理解这一过程不仅能帮助我们调试模型,还能为自定义网络结构打下基础。本文将从数学原理到代码实现,带你完整走一遍反向传播的推导流程。

数学推导过程
1. 前向传播基础
假设我们有一个 3 层神经网络(输入层、隐藏层、输出层),用上标 (l) 表示层号:
- $z^{(l)}$ 表示第 l 层的加权输入
- $a^{(l)}$ 表示第 l 层的激活输出
- $W^{(l)}$ 是 l - 1 层到 l 层的权重矩阵
前向传播公式:
$$ z^{(l)} = W^{(l)}a^{(l-1)} + b^{(l)} $$
$$ a^{(l)} = \sigma(z^{(l)}) $$
其中 $\sigma$ 是激活函数(如 Sigmoid)。
2. 链式法则应用
定义损失函数 $L$ 后,我们需要计算 $\frac{\partial L}{\partial W^{(l)}}$。通过链式法则分解:
-
输出层误差项:
$$ \delta^{(L)} = \frac{\partial L}{\partial a^{(L)}} \odot \sigma'(z^{(L)}) $$ -
隐藏层误差传播:
$$ \delta^{(l)} = (W^{(l+1)T}\delta^{(l+1)}) \odot \sigma'(z^{(l)}) $$ -
权重梯度计算:
$$ \frac{\partial L}{\partial W^{(l)}} = \delta^{(l)}a^{(l-1)T} $$
3. 完整推导步骤
以均方误差损失函数为例:
-
计算输出层误差:
$$ \delta^{(3)} = (a^{(3)} – y) \odot \sigma'(z^{(3)}) $$ -
反向传播到隐藏层:
$$ \delta^{(2)} = (W^{(3)T}\delta^{(3)}) \odot \sigma'(z^{(2)}) $$ -
最终权重梯度:
$$ \nabla_{W^{(2)}}L = \delta^{(2)}a^{(1)T} $$
$$ \nabla_{W^{(3)}}L = \delta^{(3)}a^{(2)T} $$
Python 代码实现
import numpy as np
class NeuralNetwork:
def __init__(self, layers):
self.weights = [np.random.randn(y, x)*0.1
for x, y in zip(layers[:-1], layers[1:])]
def sigmoid(self, z):
return 1/(1+np.exp(-z))
def sigmoid_prime(self, z):
return self.sigmoid(z)*(1-self.sigmoid(z))
def backprop(self, x, y):
# 前向传播
activation = x
activations = [x]
zs = []
for w in self.weights:
z = np.dot(w, activation)
zs.append(z)
activation = self.sigmoid(z)
activations.append(activation)
# 反向传播
delta = (activations[-1] - y) * self.sigmoid_prime(zs[-1])
nabla_w = [np.zeros(w.shape) for w in self.weights]
nabla_w[-1] = np.dot(delta, activations[-2].T)
for l in range(2, len(self.weights)+1):
z = zs[-l]
delta = np.dot(self.weights[-l+1].T, delta) * self.sigmoid_prime(z)
nabla_w[-l] = np.dot(delta, activations[-l-1].T)
return nabla_w
常见问题与解决方案
1. 梯度消失 / 爆炸
当网络层数较深时,梯度可能在反向传播过程中指数级缩小或增大。解决方案:
- 使用 ReLU 等修正激活函数
- 采用批归一化(BatchNorm)
- 使用残差连接
2. 学习率选择
学习率太大导致震荡,太小收敛慢。建议:
- 使用学习率衰减策略
- 尝试自适应优化器(Adam 等)
性能优化技巧
-
动量法:
$$ v_{t} = \gamma v_{t-1} + \eta \nabla_\theta J(\theta) $$
$$ \theta = \theta – v_{t} $$ -
学习率预热:训练初期使用较小学习率,逐步增大
-
梯度裁剪:限制梯度最大值,防止爆炸
避坑指南
-
初始化陷阱:权重初始化为 0 会导致所有神经元相同
-
激活函数选择:
- 输出层:根据任务选择(分类用 softmax,回归用 linear)
-
隐藏层:优先 ReLU 及其变种
-
数值稳定性:
- 对 softmax 计算使用 log-sum-exp 技巧
- 添加微小值防止除零错误
思考题延伸
如何将算法扩展到批量训练场景?可以考虑:
- 计算批量数据的平均梯度
- 使用矩阵运算并行化处理
- 调整学习率与批量大小成比例
通过理解这些基础原理,你不仅能更好地使用现有框架,还能针对特定问题定制优化策略。
