共计 2061 个字符,预计需要花费 6 分钟才能阅读完成。
问题引入:为什么我的神经网络不收敛?
刚开始尝试用 BP 神经网络拟合 MNIST 数据集时,我遇到了两个经典问题:

- 梯度消失:当网络层数增加到 5 层时,模型完全停止学习,训练损失几乎不变。检查梯度发现前几层的权重更新幅度小于 1e-6
- 过拟合 :在 3 层网络中使用高学习率(0.1) 时,训练准确率很快达到 98%,但测试集准确率卡在 92%,差距随时间不断拉大
数学原理:反向传播的矩阵视角
设网络第 $l$ 层的权重矩阵为 $W^l$,输入为 $a^{l-1}$,则前向传播可表示为:
$$ z^l = W^l a^{l-1} + b^l $$
$$ a^l = \sigma(z^l) $$
对于交叉熵损失函数 $L$,反向传播时梯度计算遵循链式法则:
$$ \frac{\partial L}{\partial W^l} = \frac{\partial L}{\partial z^l} \cdot \frac{\partial z^l}{\partial W^l} = \delta^l \cdot (a^{l-1})^T $$
其中误差项 $\delta^l$ 的递推公式为:
$$ \delta^{l} = (W^{l+1})^T \delta^{l+1} \odot \sigma'(z^l) $$
这就是梯度消失的根源——当使用 Sigmoid 激活时,$\sigma'(z^l)$ 的最大值仅为 0.25,多层连乘会导致梯度指数级衰减。
Python 实现核心代码
1. 网络初始化(He 初始化)
def initialize_parameters(layer_dims):
params = {}
for l in range(1, len(layer_dims)):
# He 初始化:适应 ReLU 的 sqrt(2/n)缩放
params['W'+str(l)] = np.random.randn(layer_dims[l], layer_dims[l-1]) * np.sqrt(2/layer_dims[l-1])
params['b'+str(l)] = np.zeros((layer_dims[l], 1))
return params
2. 前向传播(含 ReLU 激活)
def forward_prop(X, params):
caches = []
A = X
L = len(params) // 2
for l in range(1, L):
A_prev = A
Z = np.dot(params['W'+str(l)], A_prev) + params['b'+str(l)]
A = np.maximum(0, Z) # ReLU
caches.append((A_prev, Z))
# 输出层用 Sigmoid
ZL = np.dot(params['W'+str(L)], A) + params['b'+str(L)]
AL = 1/(1+np.exp(-ZL))
caches.append((A, ZL))
return AL, caches
3. 反向传播(含梯度裁剪)
def backward_prop(AL, Y, caches, clip_threshold=5.0):
grads = {}
L = len(caches)
dZL = AL - Y # 交叉熵的梯度
# 梯度裁剪
dZL = np.clip(dZL, -clip_threshold, clip_threshold)
for l in reversed(range(L)):
A_prev, Z = caches[l]
m = A_prev.shape[1]
if l == L-1: # 输出层
dW = np.dot(dZL, A_prev.T) / m
db = np.sum(dZL, axis=1, keepdims=True) / m
else: # 隐藏层
dA = np.dot(params['W'+str(l+2)].T, dZ)
dZ = np.array(dA, copy=True)
dZ[Z <= 0] = 0 # ReLU 梯度
dW = np.dot(dZ, A_prev.T) / m
db = np.sum(dZ, axis=1, keepdims=True) / m
grads['dW'+str(l+1)] = dW
grads['db'+str(l+1)] = db
return grads
实验对比:MNIST 数据集表现
| 配置 | 训练准确率 | 测试准确率 | 备注 |
|---|---|---|---|
| 3 层网络 lr=0.1 | 98.2% | 92.1% | 明显过拟合 |
| 5 层网络 lr=0.01 | 89.3% | 88.7% | 梯度消失 |
| 3 层网络 +Dropout | 96.5% | 94.8% | 缓解过拟合 |
| 5 层网络 +BN | 97.1% | 95.3% | 解决梯度消失 |
避坑指南
- 梯度裁剪阈值:一般设置在 3.0-10.0 之间,可通过观察梯度直方图调整
- BN 层位置:应放在激活函数前,即:全连接 → BN → ReLU
- 早停法实现:当验证集损失连续 5 个 epoch 不下降时终止训练
延伸阅读
- PyTorch 的实现会更简洁,主要区别在于:
- 自动求导机制无需手动实现反向传播
- nn.Module 提供了更灵活的层组合方式
- 进阶技巧:
- 学习率预热(Warmup)
- SWA(随机权重平均)
- 自监督预训练
通过这个纯 NumPy 实现,相信你对神经网络的核心机制有了更直观的理解。接下来可以用 PyTorch/TensorFlow 等框架来验证这些概念,会发现它们本质上在做相同的事情,只是包装得更易用了。
正文完
