共计 1673 个字符,预计需要花费 5 分钟才能阅读完成。
为什么需要反向传播?
当我在学习单层感知机时,发现它连简单的异或问题都解决不了。这就像用直线永远画不出一个圆——单层网络的表达能力太有限了。于是多层神经网络应运而生,但新的问题来了:如何训练这些藏在中间的神经元?这就是误差反向传播(Backpropagation)要解决的核心问题。

数学原理拆解
想象神经网络像一条工厂流水线,数据从前端进入,经过层层加工,最后产出预测结果。反向传播就是当发现产品不合格时,逆向检查每道工序该负多少责任。具体来说:
-
损失函数定义 :
以均方误差为例:
$$L = \frac{1}{2}(y_{pred} – y_{true})^2$$ -
链式求导实战 :
以三层网络为例,计算隐藏层到输出层的权重梯度:
$$\frac{\partial L}{\partial W_2} = \frac{\partial L}{\partial a_3} \cdot \frac{\partial a_3}{\partial z_3} \cdot \frac{\partial z_3}{\partial W_2}$$
其中 $z_3$ 是加权输入,$a_3$ 是激活输出 -
参数更新规则 :
$$W_{new} = W_{old} – \eta \cdot \frac{\partial L}{\partial W}$$
这个 $\eta$ 就是传说中的学习率
Python 实现详解
import numpy as np
class NeuralNetwork:
def __init__(self, input_size, hidden_size, output_size):
# 用标准差为 0.1 的正态分布初始化权重
self.W1 = np.random.randn(input_size, hidden_size) * 0.1
self.W2 = np.random.randn(hidden_size, output_size) * 0.1
def sigmoid(self, x):
return 1 / (1 + np.exp(-x))
def forward(self, X):
self.z1 = np.dot(X, self.W1) # 第一层加权和
self.a1 = self.sigmoid(self.z1) # 第一层激活输出
self.z2 = np.dot(self.a1, self.W2)
return self.sigmoid(self.z2)
def backward(self, X, y, output, lr=0.1):
# 输出层误差
error = output - y
delta2 = error * output * (1 - output) # sigmoid 导数项
# 隐藏层误差(关键的反向传播步骤)delta1 = delta2.dot(self.W2.T) * self.a1 * (1 - self.a1)
# 更新权重
self.W2 -= lr * self.a1.T.dot(delta2)
self.W1 -= lr * X.T.dot(delta1)
实战避坑指南
- 学习率选择 :
- 太大:损失函数震荡不收敛(像蹦极停不下来)
- 太小:训练速度慢(像蜗牛爬山)
-
建议从 0.01 开始尝试
-
梯度消失问题 :
- 当使用 sigmoid 激活时,深层网络容易出现梯度指数级减小
-
解决方案:改用 ReLU 激活函数
-
维度检查清单 :
- 输入数据形状:(样本数, 特征数)
- 权重矩阵形状:(前一层神经元数, 后一层神经元数)
- 每次矩阵乘后记得 print(shape)
常见错误排查
- 症状 1 :损失值变成 NaN
- 可能原因:学习率过大导致数值爆炸
-
解决:减小学习率或加入梯度裁剪
-
症状 2 :准确率始终 50%
- 可能原因:权重初始化全为 0
-
解决:使用随机初始化
-
症状 3 :训练后期停滞
- 可能原因:陷入局部最优
- 解决:尝试加入动量 (momentum)
思考与延伸
- mini-batch 优化 :当前代码每次处理整个数据集,尝试修改为分批训练
- 激活函数实验 :将 sigmoid 换成 ReLU,观察梯度传播变化
- 优化算法进阶 :研究 Adam 相比 SGD 的优势与计算代价
通过这次实现,我深刻体会到反向传播就像神经网络的学习指南——它告诉每个参数:『你该为这个错误负多少责任,又该朝哪个方向改进』。虽然推导过程有点烧脑,但看到网络最终学会分类任务时,那种成就感真是太棒了!
