共计 2396 个字符,预计需要花费 6 分钟才能阅读完成。
反向传播算法是神经网络训练的核心引擎,它像一位精准的导航员,通过计算损失函数对每个权重的梯度,指导网络参数朝着误差减小的方向调整。下面我们拆解这个看似复杂的数学过程,用最直白的方式呈现从理论到代码的全路径。

原理推导
1. 网络结构定义
设单隐藏层网络结构如下(符号定义遵循通用惯例):
- 输入层:$\mathbf{x} = [x_1, x_2, …, x_n]^T$
- 隐藏层:$\mathbf{h} = \sigma(\mathbf{W}_1 \mathbf{x} + \mathbf{b}_1)$,其中 $\sigma$ 为 sigmoid 函数
- 输出层:$\mathbf{y} = \text{softmax}(\mathbf{W}_2 \mathbf{h} + \mathbf{b}_2)$
2. 损失函数选择
使用交叉熵损失(适合分类任务):
$$ L = -\sum_k t_k \log y_k $$
其中 $t_k$ 为 one-hot 编码的真实标签。
3. 关键梯度推导(链式法则实战)
输出层权重梯度 :
$$
\frac{\partial L}{\partial \mathbf{W}2} = \underbrace{\frac{\partial L}{\partial \mathbf{y}}}} – \mathbf{t})} \cdot \underbrace{\frac{\partial \mathbf{y}}{\partial (\mathbf{W2 \mathbf{h})}}}} \cdot \underbrace{\frac{\partial (\mathbf{W2 \mathbf{h})}{\partial \mathbf{W}_2}}
$$}^T
隐藏层权重梯度 (体现反向传播特性):
$$
\frac{\partial L}{\partial \mathbf{W}1} = \underbrace{\frac{\partial L}{\partial \mathbf{h}}}2^T (\mathbf{y}-\mathbf{t})} \cdot \underbrace{\frac{\partial \mathbf{h}}{\partial (\mathbf{W}_1 \mathbf{x})}}} \cdot \underbrace{\frac{\partial (\mathbf{W1 \mathbf{x})}{\partial \mathbf{W}_1}}
$$}^T
注意到 sigmoid 导数 $\sigma’ = \sigma(1-\sigma)$ 的特性,这正是梯度消失问题的根源。
代码实现
import numpy as np
def sigmoid(x):
return 1 / (1 + np.exp(-x))
def bp_nn(X, y, hidden_size=64, lr=0.1, epochs=100):
# 初始化权重(Xavier 初始化)W1 = np.random.randn(X.shape[1], hidden_size) * np.sqrt(2/X.shape[1])
b1 = np.zeros(hidden_size)
W2 = np.random.randn(hidden_size, y.shape[1]) * np.sqrt(2/hidden_size)
b2 = np.zeros(y.shape[1])
for _ in range(epochs):
# 前向传播
h = sigmoid(X.dot(W1) + b1)
y_pred = softmax(h.dot(W2) + b2)
# 反向传播
dy = y_pred - y # 输出层误差
grad_W2 = h.T.dot(dy)
grad_b2 = dy.sum(axis=0)
dh = dy.dot(W2.T) * h * (1 - h) # 隐藏层误差(注意 sigmoid 导数)grad_W1 = X.T.dot(dh)
grad_b1 = dh.sum(axis=0)
# 参数更新(加入梯度裁剪)for grad in [grad_W1, grad_W2]:
np.clip(grad, -1, 1, out=grad)
W1 -= lr * grad_W1
b1 -= lr * grad_b1
W2 -= lr * grad_W2
b2 -= lr * grad_b2
return W1, b1, W2, b2
代码关键点说明:
- 向量化计算:整个 batch 的矩阵运算取代逐样本 for 循环
- 梯度裁剪:防止梯度爆炸的实用技巧
- Xavier 初始化:缓解梯度消失的初始化策略
调试技巧
1. 学习率选择
经验公式:初始学习率可设为 $\frac{1}{\sqrt{\text{batch_size}}}$。当发现损失震荡时减小 10 倍,收敛过慢时增大 2 倍。
2. 梯度检验
用数值梯度验证解析梯度的正确性:
def check_grad(func, param, eps=1e-5):
grad = np.zeros_like(param)
it = np.nditer(param, flags=['multi_index'])
while not it.finished:
idx = it.multi_index
tmp = param[idx]
param[idx] = tmp + eps
f1 = func()
param[idx] = tmp - eps
f2 = func()
grad[idx] = (f1 - f2) / (2 * eps)
param[idx] = tmp
it.iternext()
return grad
3. 批量训练技巧
- 累积小批量梯度时,注意除以 batch_size 保持梯度尺度一致
- 使用随机 shuffle 避免样本顺序影响
思考题实践
要实现带动量的梯度下降,只需在参数更新阶段加入速度项:
# 初始化速度
v_W1, v_b1 = 0, 0
v_W2, v_b2 = 0, 0
momentum = 0.9
# 更新规则改为
v_W1 = momentum * v_W1 + lr * grad_W1
W1 -= v_W1
反向传播就像拆解一台精密钟表——当我们理解每个齿轮(公式)的咬合关系后,就能自己动手组装(实现)出完整的系统。现在你可以尝试调整隐藏层数,观察梯度传播的变化规律了。
