共计 2556 个字符,预计需要花费 7 分钟才能阅读完成。
从 XOR 问题理解前向传播
假设我们有一个简单的 XOR 问题,输入是二维数据,输出是 0 或 1。我们构建一个 2 -2- 1 的三层神经网络(输入层 2 个神经元,隐藏层 2 个神经元,输出层 1 个神经元)来解决这个问题。

- 前向传播过程:
- 输入层到隐藏层:z1 = W1 * x + b1,a1 = sigmoid(z1)
- 隐藏层到输出层:z2 = W2 * a1 + b2,a2 = sigmoid(z2)
- 计算损失:使用均方误差(MSE) L = 1/2*(y – a2)^2
反向传播数学推导
反向传播的核心是链式法则,我们以输出层到隐藏层的权重更新为例:
-
计算输出层误差:
δ2 = ∂L/∂a2 * ∂a2/∂z2 = (a2 – y) * sigmoid'(z2) -
计算隐藏层误差:
δ1 = (W2.T * δ2) ⊙ sigmoid'(z1) -
计算梯度:
∂L/∂W2 = δ2 * a1.T
∂L/∂b2 = δ2
∂L/∂W1 = δ1 * x.T
∂L/∂b1 = δ1
Python 实现
import numpy as np
# 网络初始化
def initialize_parameters():
W1 = np.random.randn(2, 2) * 0.01
b1 = np.zeros((2, 1))
W2 = np.random.randn(1, 2) * 0.01
b2 = np.zeros((1, 1))
return {'W1': W1, 'b1': b1, 'W2': W2, 'b2': b2}
# 前向传播
def forward_propagation(X, parameters):
W1, b1, W2, b2 = parameters['W1'], parameters['b1'], parameters['W2'], parameters['b2']
Z1 = np.dot(W1, X) + b1
A1 = 1/(1+np.exp(-Z1))
Z2 = np.dot(W2, A1) + b2
A2 = 1/(1+np.exp(-Z2))
cache = {'Z1': Z1, 'A1': A1, 'Z2': Z2, 'A2': A2}
return A2, cache
# 计算损失
def compute_cost(A2, Y):
m = Y.shape[1]
cost = np.sum(np.square(A2 - Y)) / (2*m)
return cost
# 反向传播
def backward_propagation(parameters, cache, X, Y):
m = X.shape[1]
W1, W2 = parameters['W1'], parameters['W2']
A1, A2 = cache['A1'], cache['A2']
dZ2 = (A2 - Y) * A2 * (1 - A2)
dW2 = np.dot(dZ2, A1.T) / m
db2 = np.sum(dZ2, axis=1, keepdims=True) / m
dZ1 = np.dot(W2.T, dZ2) * A1 * (1 - A1)
dW1 = np.dot(dZ1, X.T) / m
db1 = np.sum(dZ1, axis=1, keepdims=True) / m
grads = {'dW1': dW1, 'db1': db1, 'dW2': dW2, 'db2': db2}
return grads
# 参数更新
def update_parameters(parameters, grads, learning_rate=0.1):
W1 = parameters['W1'] - learning_rate * grads['dW1']
b1 = parameters['b1'] - learning_rate * grads['db1']
W2 = parameters['W2'] - learning_rate * grads['dW2']
b2 = parameters['b2'] - learning_rate * grads['db2']
return {'W1': W1, 'b1': b1, 'W2': W2, 'b2': b2}
避坑指南
- 学习率选择策略:
- 初始学习率可以从 0.1 开始尝试
- 使用学习率衰减:learning_rate = learning_rate * (1. / (1. + decay_rate * epoch))
-
尝试自适应学习率方法如 Adam
-
梯度检查方法:
- 计算数值梯度:对每个参数进行微小扰动计算损失变化
- 比较数值梯度与反向传播计算的理论梯度
-
差异应小于 1e-7
-
激活函数选择建议:
- 隐藏层推荐使用 ReLU 或 Leaky ReLU
- 输出层根据任务选择:二分类用 sigmoid,多分类用 softmax,回归用线性
可视化训练过程
使用 Matplotlib 绘制损失曲线和决策边界变化:
import matplotlib.pyplot as plt
# 训练后绘制损失曲线
plt.plot(loss_history)
plt.xlabel('Iterations')
plt.ylabel('Loss')
plt.title('Training Loss Curve')
plt.show()
# 绘制决策边界
x_min, x_max = X[0, :].min() - 0.5, X[0, :].max() + 0.5
y_min, y_max = X[1, :].min() - 0.5, X[1, :].max() + 0.5
h = 0.01
xx, yy = np.meshgrid(np.arange(x_min, x_max, h), np.arange(y_min, y_max, h))
Z, _ = forward_propagation(np.c_[xx.ravel(), yy.ravel()].T, parameters)
Z = Z.reshape(xx.shape)
plt.contourf(xx, yy, Z, cmap=plt.cm.Spectral)
plt.scatter(X[0, :], X[1, :], c=Y, cmap=plt.cm.Spectral)
plt.title('Decision Boundary')
plt.show()
思考与延伸
- 如何改进基础算法应对梯度消失?
- 使用 ReLU 等非饱和激活函数
- 引入残差连接(ResNet)
-
使用 Batch Normalization
-
批量训练与在线训练的差异:
- 批量训练:更稳定的梯度方向,但计算开销大
- 在线训练:对噪声更敏感,但能更快适应数据变化
- 折中方案:小批量 (mini-batch) 训练
通过这个完整的 BP 神经网络实现,我们不仅理解了反向传播的数学原理,还掌握了实际编码实现的技巧。建议读者尝试不同的网络结构、激活函数和超参数,观察它们对模型性能的影响。
正文完
