共计 1947 个字符,预计需要花费 5 分钟才能阅读完成。
为什么需要反向传播?
刚开始接触神经网络时,最让我困惑的就是:那么多参数(比如权重矩阵 Weight Matrix 和偏置 Bias),究竟该怎么调整才能让模型表现更好?手动计算每个参数的梯度简直是噩梦——尤其是当网络层数增加时,这种计算量会指数级增长。这就是反向传播(Backpropagation)算法要解决的核心问题:高效计算损失函数(Loss Function)对每个参数的梯度。
数学原理拆解:链式法则的威力
反向传播的本质是 链式法则(Chain Rule)的巧妙应用。我们以一个最简单的单隐藏层网络为例:
输入层 → 隐藏层(含 Sigmoid 激活)→ 输出层(线性变换)
假设损失函数用均方误差(MSE),那么对隐藏层权重 $W^{(1)}$ 的梯度计算步骤如下:
-
前向传播计算各层输出:
$$h = \sigma(W^{(1)}x + b^{(1)})$$
$$\hat{y} = W^{(2)}h + b^{(2)}$$ -
从输出层反向计算误差:
$$\frac{\partial L}{\partial \hat{y}} = 2(\hat{y} – y)$$ -
通过链式法则逐层回传:
$$\frac{\partial L}{\partial W^{(1)}} = \frac{\partial L}{\partial h} \cdot \frac{\partial h}{\partial z} \cdot \frac{\partial z}{\partial W^{(1)}}$$
其中 $z = W^{(1)}x + b^{(1)}$
(注:此处应为示意图链接)
Python 实现:手写 NumPy 版本
import numpy as np
# 定义网络结构
input_size = 3
hidden_size = 4
output_size = 1
# 初始化参数
W1 = np.random.randn(input_size, hidden_size) * 0.01
b1 = np.zeros((1, hidden_size))
W2 = np.random.randn(hidden_size, output_size) * 0.01
b2 = np.zeros((1, output_size))
# 前向传播
def forward(x):
z1 = np.dot(x, W1) + b1 # shape: (batch_size, hidden_size)
a1 = 1 / (1 + np.exp(-z1)) # Sigmoid 激活
z2 = np.dot(a1, W2) + b2 # shape: (batch_size, output_size)
return z2, a1
# 反向传播
def backward(x, y, z2, a1):
m = x.shape[0] # batch 大小
# 输出层梯度
dz2 = (z2 - y) / m # dL/dz2 shape 同 z2
dW2 = np.dot(a1.T, dz2) # shape 同 W2
db2 = np.sum(dz2, axis=0, keepdims=True)
# 隐藏层梯度
dz1 = np.dot(dz2, W2.T) * (a1 * (1 - a1)) # 注意 Sigmoid 导数
dW1 = np.dot(x.T, dz1) # shape 同 W1
db1 = np.sum(dz1, axis=0, keepdims=True)
return dW1, db1, dW2, db2
# 参数更新示例
learning_rate = 0.1
for epoch in range(100):
z2, a1 = forward(x_train)
dW1, db1, dW2, db2 = backward(x_train, y_train, z2, a1)
W1 -= learning_rate * dW1
b1 -= learning_rate * db1
W2 -= learning_rate * dW2
b2 -= learning_rate * db2
新手常踩的坑及解决方案
- 梯度消失问题:
- 现象:深层网络中梯度越来越小,导致浅层参数几乎不更新
-
解决方案:改用 ReLU 激活函数、残差连接(ResNet)、梯度裁剪
-
学习率设置不当:
- 现象:损失值震荡(过大)或下降过慢(过小)
-
经验值:通常从 0.01 开始尝试,配合学习率衰减(如每 10 轮×0.5)
-
数值不稳定:
- 现象:出现 NaN 或异常大的值
- 检查点:参数初始化范围、激活函数输出范围(如 Softmax 需要做数值稳定处理)
延伸思考与实践
- 如何扩展为 mini-batch 训练?
- 提示:在 backward()函数中注意梯度求平均的位置
-
进阶:尝试实现动量(Momentum)或 Adam 优化器
-
不同激活函数的影响:
- 任务:将代码中的 Sigmoid 改为 ReLU,观察训练曲线变化
- 思考:为什么深层网络更偏好 ReLU?
理解反向传播后,你会突然发现神经网络不再是个黑箱——那些神秘的梯度下降过程,原来都是扎实的微积分计算。建议亲手调试代码中的参数,比如把学习率调大 10 倍,观察损失值的变化规律,这种直观感受比任何理论讲解都更深刻。
