共计 2474 个字符,预计需要花费 7 分钟才能阅读完成。
背景痛点:新手常见误区
很多初学者第一次接触反向传播时,容易产生两个典型误解:

- 混淆权重更新与偏导计算 :误以为反向传播直接更新权重,实际上它只计算梯度,权重更新由优化器(如 SGD)完成
- 忽略链式法则的逐层性 :错误认为可以一步求出输出层对任意权重的偏导,而实际上需要从输出层逐步回传误差
数学推导:单隐藏层网络示例
前向传播公式
设网络结构为输入层($x$)→隐藏层($h$)→输出层($\hat{y}$),使用 Sigmoid 激活函数:
$$
\begin{aligned}
z_h &= W_{xh}x + b_h \
h &= \sigma(z_h) \
z_y &= W_{hy}h + b_y \
\hat{y} &= \sigma(z_y)
\end{aligned}
$$
其中 $\sigma(z) = \frac{1}{1+e^{-z}}$,$W_{xh}$ 和 $W_{hy}$ 分别为输入到隐藏层和隐藏层到输出层的权重矩阵。
损失函数与反向传播
采用均方误差(MSE)损失:
$$
L = \frac{1}{2}(y – \hat{y})^2
$$
关键推导步骤(以 $W_{hy}$ 为例):
-
输出层梯度 :
$$
\frac{\partial L}{\partial W_{hy}} = \frac{\partial L}{\partial \hat{y}} \cdot \frac{\partial \hat{y}}{\partial z_y} \cdot \frac{\partial z_y}{\partial W_{hy}}
$$ -
逐项计算 :
- $\frac{\partial L}{\partial \hat{y}} = -(y – \hat{y})$
- $\frac{\partial \hat{y}}{\partial z_y} = \sigma(z_y)(1-\sigma(z_y))$(Sigmoid 导数特性)
-
$\frac{\partial z_y}{\partial W_{hy}} = h^T$
-
最终表达式 :
$$
\delta_y = (\hat{y} – y) \odot \sigma'(z_y) \
\nabla W_{hy} = h^T \delta_y
$$
隐藏层权重 $W_{xh}$ 的梯度计算需要继续链式求导,体现反向传播的「反向」特性。
Python 实现核心代码
import numpy as np
class BPNetwork:
def __init__(self, input_size, hidden_size):
# 初始化权重(Xavier 初始化)self.Wxh = np.random.randn(hidden_size, input_size) * np.sqrt(1/input_size)
self.Why = np.random.randn(1, hidden_size) * np.sqrt(1/hidden_size)
self.bh = np.zeros((hidden_size, 1))
self.by = np.zeros((1, 1))
def sigmoid(self, z):
return 1 / (1 + np.exp(-z))
def forward(self, x):
self.z_h = self.Wxh @ x + self.bh
self.h = self.sigmoid(self.z_h)
self.z_y = self.Why @ self.h + self.by
return self.sigmoid(self.z_y)
def backward(self, x, y, lr=0.1):
# 输出层梯度
error = self.y_pred - y
delta_y = error * self.y_pred * (1 - self.y_pred) # ⊙表示逐元素乘
grad_Why = delta_y @ self.h.T # 注意 h 需要转置
# 隐藏层梯度(关键链式传导)delta_h = (self.Why.T @ delta_y) * self.h * (1 - self.h)
grad_Wxh = delta_h @ x.T
# 参数更新
self.Why -= lr * grad_Why
self.Wxh -= lr * grad_Wxh
self.by -= lr * delta_y
self.bh -= lr * delta_h
避坑指南
- 梯度消失问题 :当输入值较大时,Sigmoid 导数接近 0,导致浅层权重更新缓慢。解决方法:
- 使用 ReLU 等非饱和激活函数
-
实施梯度裁剪(gradient clipping)
-
学习率设置 :建议从 0.01 开始尝试,观察损失曲线:
- 震荡剧烈→学习率过大
-
下降过慢→学习率过小
-
数据标准化 :输入特征应缩放至相近范围(如 [-1,1]),否则:
- 不同权重的梯度数量级差异大
- 需要为每个特征设置不同的学习率
验证实验
from sklearn.datasets import make_moons
import matplotlib.pyplot as plt
# 生成数据
X, y = make_moons(n_samples=1000, noise=0.1)
y = y.reshape(-1, 1) # 转为列向量
# 训练网络
net = BPNetwork(input_size=2, hidden_size=4)
losses = []
for epoch in range(1000):
total_loss = 0
for i in range(len(X)):
x_sample = X[i].reshape(-1, 1)
y_pred = net.forward(x_sample)
net.backward(x_sample, y[i], lr=0.1)
total_loss += 0.5 * (y_pred - y[i])**2
losses.append(total_loss / len(X))
# 可视化
plt.plot(losses)
plt.xlabel('Epoch')
plt.ylabel('MSE Loss')
plt.show()
关键总结
- 反向传播本质 :是链式法则的高效实现,避免重复计算中间梯度
- 矩阵求导要点 :注意各矩阵维度匹配,特别是转置操作的位置
- 工程实践 :建议先在小规模网络(如 2 - 4 个隐藏单元)上手动推导验证,再扩展到复杂结构
通过代码与公式的对照实现,可以深刻理解为什么深度学习框架能自动求导——其核心正是基于计算图的链式法则传播机制。
