共计 2264 个字符,预计需要花费 6 分钟才能阅读完成。
问题描述与网络结构
我们以一个简单的二分类问题为例:给定二维平面上的点,根据它们的坐标判断属于类别 0 还是类别 1。假设数据呈圆形分布,内圈为类别 0,外圈为类别 1。

网络结构采用单隐藏层的 BP 神经网络:
- 输入层:2 个神经元(对应 x,y 坐标)
- 隐藏层:3 个神经元,使用 Sigmoid 激活函数
- 输出层:1 个神经元,使用 Sigmoid 激活函数
前向传播实现
前向传播过程可以表示为:
- 输入层到隐藏层:
$$h = \sigma(W_1 X + b_1)$$ - 隐藏层到输出层:
$$\hat{y} = \sigma(W_2 h + b_2)$$
其中 $\sigma$ 是 Sigmoid 函数:
$$\sigma(z) = \frac{1}{1+e^{-z}}$$
损失函数选择与计算
我们使用交叉熵损失函数:
$$L = -\frac{1}{N}\sum_{i=1}^N [y_i\log(\hat{y}_i) + (1-y_i)\log(1-\hat{y}_i)]$$
反向传播推导
反向传播的核心是链式法则,计算损失对各个参数的梯度:
-
计算输出层梯度:
$$\frac{\partial L}{\partial W_2} = (\hat{y}-y) \cdot h^T$$
$$\frac{\partial L}{\partial b_2} = \hat{y}-y$$ -
计算隐藏层梯度:
$$\frac{\partial L}{\partial W_1} = [(\hat{y}-y)W_2^T \odot h \odot (1-h)] \cdot X^T$$
$$\frac{\partial L}{\partial b_1} = (\hat{y}-y)W_2^T \odot h \odot (1-h)$$
其中 $\odot$ 表示逐元素相乘。
权重更新
使用梯度下降法更新参数:
$$W = W – \eta \frac{\partial L}{\partial W}$$
$$b = b – \eta \frac{\partial L}{\partial b}$$
其中 $\eta$ 是学习率。
Python 实现
import numpy as np
import matplotlib.pyplot as plt
# 生成数据
np.random.seed(42)
N = 200
X = np.random.randn(N, 2)
radius = np.sqrt((X**2).sum(axis=1))
y = (radius > 1).astype(int)
# 网络参数
input_size = 2
hidden_size = 3
output_size = 1
learning_rate = 0.1
epochs = 10000
# 初始化权重
W1 = np.random.randn(input_size, hidden_size)
W2 = np.random.randn(hidden_size, output_size)
b1 = np.zeros((1, hidden_size))
b2 = np.zeros((1, output_size))
# 训练过程
losses = []
for epoch in range(epochs):
# 前向传播
h = 1 / (1 + np.exp(-(X.dot(W1) + b1)))
y_hat = 1 / (1 + np.exp(-(h.dot(W2) + b2)))
# 计算损失
loss = -np.mean(y * np.log(y_hat) + (1-y) * np.log(1-y_hat))
losses.append(loss)
# 反向传播
dL_dy_hat = (y_hat - y.reshape(-1,1)) / N
# 输出层梯度
dL_dW2 = h.T.dot(dL_dy_hat)
dL_db2 = np.sum(dL_dy_hat, axis=0, keepdims=True)
# 隐藏层梯度
dh = dL_dy_hat.dot(W2.T) * h * (1 - h)
dL_dW1 = X.T.dot(dh)
dL_db1 = np.sum(dh, axis=0, keepdims=True)
# 更新参数
W1 -= learning_rate * dL_dW1
b1 -= learning_rate * dL_db1
W2 -= learning_rate * dL_dW2
b2 -= learning_rate * dL_db2
if epoch % 1000 == 0:
print(f'Epoch {epoch}, Loss: {loss:.4f}')
# 绘制损失曲线
plt.plot(losses)
plt.xlabel('Epoch')
plt.ylabel('Loss')
plt.title('Training Loss')
plt.show()
实验结果分析
- 学习率影响:
- 学习率过大可能导致震荡甚至发散
- 学习率过小导致收敛缓慢
-
建议从 0.01-0.1 开始尝试
-
梯度消失 / 爆炸问题:
- 深层网络中,Sigmoid 的导数最大为 0.25,可能导致梯度逐层衰减
- 可以通过 ReLU 等激活函数缓解
-
权重初始化也很关键
-
激活函数比较:
- Sigmoid:输出范围(0,1),适合二分类
- Tanh:输出范围(-1,1),中心对称
- ReLU:计算简单,缓解梯度消失
避坑指南
- 梯度计算错误:
- 确保链式法则应用正确
-
建议先手推小例子验证
-
数值不稳定:
- 避免 log(0)等情况
-
可以加小常数 eps=1e-8
-
初始化问题:
- 不要全零初始化
-
推荐 Xavier 或 He 初始化
-
过拟合:
- 增加正则化项
- 使用验证集监控
延伸思考
- 如何扩展到多分类问题?
- 如何处理更复杂的非线性边界?
- 如何实现批量归一化(BatchNorm)?
- 如何结合动量 (Momentum) 优化训练?
通过这个简单的例子,我们完整实现了 BP 神经网络的反向传播算法。关键在于理解梯度计算的过程,并注意实现细节。希望这篇笔记能帮助你更好地掌握这一核心算法。
