共计 1443 个字符,预计需要花费 4 分钟才能阅读完成。
在深度学习中,BP 反向传播算法如同神经网络的 ” 心脏 ”,负责将误差信号从输出层逐层回传,指导网络权重的迭代更新。本文将手把手带你实现一个完整的 BP 案例,过程中会暴露初学者最容易踩的坑,也会分享实战中总结的调参技巧。

为什么手动推导 BP 总出错?
反向传播本质是复合函数求导的链式法则应用,但多层嵌套时容易出现两大问题:
- 梯度计算断层:当对 $\frac{\partial L}{\partial W^{(2)}}$ 求导时,新手常忘记中间变量 $\frac{\partial h}{\partial z}$ 的传递(其中 $h=σ(z)$ 是激活函数输出)
- 维度不匹配:权重矩阵 $W$ 的梯度 $\frac{\partial L}{\partial W}$ 必须与原矩阵同形,但推导时容易遗漏转置操作导致形状错误
从零实现 3 层神经网络
下面用 NumPy 实现输入层→隐藏层 (4 神经元)→输出层的经典结构,数据集采用异或问题(XOR) 作为示例:
import numpy as np
# 初始化参数(注意维度)W1 = np.random.randn(2, 4) * 0.01 # 输入→隐藏层权重
b1 = np.zeros((1, 4))
W2 = np.random.randn(4, 1) * 0.01 # 隐藏→输出层权重
b2 = np.zeros((1, 1))
# Sigmoid 激活函数
def sigmoid(x):
return 1 / (1 + np.exp(-x))
# 前向传播
def forward(X):
z1 = np.dot(X, W1) + b1 # 形状(N,4)
h1 = sigmoid(z1)
z2 = np.dot(h1, W2) + b2 # 形状(N,1)
y_pred = sigmoid(z2)
return y_pred, h1
# 交叉熵损失
def compute_loss(y_pred, y_true):
return -np.mean(y_true*np.log(y_pred) + (1-y_true)*np.log(1-y_pred))
反向传播时需特别注意梯度公式的矩阵表示(以下展示核心片段):
# 反向传播(关键步骤注释)def backward(X, y_true, y_pred, h1):
N = X.shape[0]
# 输出层梯度
dz2 = (y_pred - y_true) / N # dL/dz2 (N,1)
dW2 = np.dot(h1.T, dz2) # (4,1)
# 隐藏层梯度(链式法则核心)dh1 = np.dot(dz2, W2.T) # (N,4)
dz1 = dh1 * h1 * (1 - h1) # Sigmoid 导数项
dW1 = np.dot(X.T, dz1) # (2,4)
return dW1, dW2, dz1.mean(axis=0), dz2.mean(axis=0)
避坑指南
学习率与梯度爆炸
当学习率 $\eta$ 设置过大时,权重更新会出现震荡现象。建议:
- 初始学习率从 0.01 开始尝试
- 实施梯度裁剪:
grad = np.clip(grad, -1, 1)
激活函数选择
| 函数类型 | 梯度特性 | 适用场景 |
|---|---|---|
| Sigmoid | 最大 0.25 | 输出层 |
| tanh | 最大 1.0 | 隐藏层 |
| ReLU | 0 或 1 | 深层网络 |
实验发现,对于本例的浅层网络,Sigmoid 在输出层的表现优于 ReLU(后者容易出现神经元死亡)。
思考与实践
- Mini-Batch 改造 :尝试修改代码,将
X.shape[0]全量样本替换为batch_size=32的分批处理 - 激活函数实验:将隐藏层改为 ReLU 后,观察损失曲线是否出现阶梯式下降
通过这个案例,你会发现 BP 算法没有想象中那么神秘。亲手实现一次后,再使用 PyTorch 等框架时会更加理解其底层运作机制。
正文完
