共计 2539 个字符,预计需要花费 7 分钟才能阅读完成。
1. 前向传播与反向传播的必要性
神经网络的核心是通过前向传播计算预测值,再通过反向传播调整参数。对于一个单隐藏层的全连接网络,前向传播公式为:

$$
\begin{aligned}
z^{[1]} &= W^{[1]}X + b^{[1]} \
a^{[1]} &= \sigma(z^{[1]}) \
z^{[2]} &= W^{[2]}a^{[1]} + b^{[2]} \
a^{[2]} &= \sigma(z^{[2]})
\end{aligned}
$$
其中 $\sigma$ 表示 Sigmoid 激活函数。反向传播的目标是通过链式法则计算损失函数 $L$ 对各个参数的梯度,从而更新参数。
2. 链式法则的数学推导
以单样本的平方误差损失 $L = \frac{1}{2}(y – a^{[2]})^2$ 为例,推导关键梯度:
-
输出层梯度:
$$ \frac{\partial L}{\partial z^{[2]}} = -(y – a^{[2]}) \cdot \sigma'(z^{[2]}) $$ -
隐藏层梯度:
$$ \frac{\partial L}{\partial z^{[1]}} = W^{[2]T} \frac{\partial L}{\partial z^{[2]}} \odot \sigma'(z^{[1]}) $$ -
参数梯度:
$$
\begin{aligned}
\frac{\partial L}{\partial W^{[2]}} &= \frac{\partial L}{\partial z^{[2]}} a^{[1]T} \
\frac{\partial L}{\partial b^{[2]}} &= \frac{\partial L}{\partial z^{[2]}} \
\frac{\partial L}{\partial W^{[1]}} &= \frac{\partial L}{\partial z^{[1]}} X^T \
\frac{\partial L}{\partial b^{[1]}} &= \frac{\partial L}{\partial z^{[1]}}
\end{aligned}
$$
其中 $\odot$ 表示逐元素相乘。
3. Python 实现代码
import numpy as np
# Sigmoid 激活函数及其导数
def sigmoid(x):
return 1 / (1 + np.exp(-x))
def sigmoid_derivative(x):
s = sigmoid(x)
return s * (1 - s)
# 网络参数初始化
input_size = 3
hidden_size = 4
output_size = 1
W1 = np.random.randn(hidden_size, input_size) * 0.01
b1 = np.zeros((hidden_size, 1))
W2 = np.random.randn(output_size, hidden_size) * 0.01
b2 = np.zeros((output_size, 1))
# 前向传播
def forward(X):
Z1 = np.dot(W1, X) + b1
A1 = sigmoid(Z1)
Z2 = np.dot(W2, A1) + b2
A2 = sigmoid(Z2)
return Z1, A1, Z2, A2
# 反向传播
def backward(X, y, Z1, A1, Z2, A2):
m = X.shape[1]
# 输出层梯度
dZ2 = (A2 - y) * sigmoid_derivative(Z2)
dW2 = np.dot(dZ2, A1.T) / m
db2 = np.sum(dZ2, axis=1, keepdims=True) / m
# 隐藏层梯度
dZ1 = np.dot(W2.T, dZ2) * sigmoid_derivative(Z1)
dW1 = np.dot(dZ1, X.T) / m
db1 = np.sum(dZ1, axis=1, keepdims=True) / m
return dW1, db1, dW2, db2
# 训练循环
learning_rate = 0.1
for epoch in range(1000):
# 假设 X, y 是训练数据
Z1, A1, Z2, A2 = forward(X)
dW1, db1, dW2, db2 = backward(X, y, Z1, A1, Z2, A2)
# 参数更新
W1 -= learning_rate * dW1
b1 -= learning_rate * db1
W2 -= learning_rate * dW2
b2 -= learning_rate * db2
4. 避坑指南
梯度消失问题
当网络层数较深时,Sigmoid 函数的导数最大值仅为 0.25,多次连乘会导致梯度指数级减小。可以通过以下方法识别:
- 监控各层梯度范数
- 使用 ReLU 等替代激活函数
学习率设置
推荐采用以下策略:
- 初始学习率从 0.01 开始尝试
- 使用学习率衰减策略
- 结合验证集表现调整
数值梯度检验
实现梯度检查函数,比较解析梯度与数值梯度的差异:
def gradient_check(X, y, params, eps=1e-7):
grad_approx = []
for param in params:
param_shape = param.shape
param_flat = param.flatten()
grad_flat = np.zeros_like(param_flat)
for i in range(len(param_flat)):
old_val = param_flat[i]
param_flat[i] = old_val + eps
loss_plus = compute_loss(X, y)
param_flat[i] = old_val - eps
loss_minus = compute_loss(X, y)
grad_flat[i] = (loss_plus - loss_minus) / (2 * eps)
param_flat[i] = old_val
grad_approx.append(grad_flat.reshape(param_shape))
return grad_approx
5. 思考题
- 卷积层的链式法则需要考虑局部连接和权值共享特性,如何修改梯度计算公式?
- ReLU 激活函数的导数为分段函数,这对梯度计算和网络训练有什么影响?
- 在批量训练时,如何有效并行化梯度计算过程?
通过本文的推导和实现,读者应该能够深入理解 BP 算法的核心机制,并具备实现基本神经网络的能力。建议动手实现代码并尝试不同的网络结构和超参数,以加深理解。
