共计 2902 个字符,预计需要花费 8 分钟才能阅读完成。
前言
作为深度学习的基础,反向传播算法(Backpropagation)是每个初学者必须掌握的技能。刚开始接触时,可能会觉得数学推导复杂难懂。本文将从最基础的数学原理出发,结合 Python 代码实现,带你一步步理解这个核心算法。

核心概念
前向传播与反向传播
神经网络的学习过程可以看作是两个阶段的循环:
- 前向传播 :输入数据通过网络层层传递,最终得到预测值
- 反向传播 :计算预测值与真实值的误差,并将这个误差反向传播回网络,用于调整各层参数
梯度下降与链式法则
- 梯度下降 :通过计算损失函数对参数的梯度,沿着梯度反方向调整参数,使损失函数值减小
- 链式法则 :反向传播的核心数学工具,用于计算复合函数的导数
数学推导
以一个简单的单隐藏层神经网络为例,推导权重更新的过程。
网络结构
- 输入层:$x$
- 隐藏层:$h = \sigma(W_1x + b_1)$
- 输出层:$y = W_2h + b_2$
- 损失函数:$L = \frac{1}{2}(y – t)^2$,其中 $t$ 是真实值
反向传播推导
-
计算输出层梯度:
$$\frac{\partial L}{\partial y} = y – t$$
$$\frac{\partial L}{\partial W_2} = \frac{\partial L}{\partial y} \cdot \frac{\partial y}{\partial W_2} = (y-t)h^T$$ -
计算隐藏层梯度:
$$\frac{\partial L}{\partial h} = W_2^T(y-t)$$
$$\frac{\partial L}{\partial z} = \frac{\partial L}{\partial h} \cdot \sigma'(z)$$
$$\frac{\partial L}{\partial W_1} = \frac{\partial L}{\partial z}x^T$$ -
参数更新:
$$W_1 = W_1 – \eta \frac{\partial L}{\partial W_1}$$
$$W_2 = W_2 – \eta \frac{\partial L}{\partial W_2}$$
Python 实现
下面是一个简单的神经网络实现,包含完整的前向传播和反向传播过程。
import numpy as np
class SimpleNN:
def __init__(self, input_size, hidden_size, output_size):
# 初始化权重
self.W1 = np.random.randn(input_size, hidden_size) * 0.01
self.b1 = np.zeros(hidden_size)
self.W2 = np.random.randn(hidden_size, output_size) * 0.01
self.b2 = np.zeros(output_size)
def sigmoid(self, x):
return 1 / (1 + np.exp(-x))
def sigmoid_derivative(self, x):
return self.sigmoid(x) * (1 - self.sigmoid(x))
def forward(self, x):
# 前向传播
self.z1 = np.dot(x, self.W1) + self.b1
self.h = self.sigmoid(self.z1)
self.z2 = np.dot(self.h, self.W2) + self.b2
return self.z2
def backward(self, x, y, t, learning_rate=0.01):
# 反向传播
m = x.shape[0] # 样本数量
# 输出层梯度
dL_dy = (y - t) / m
dL_dW2 = np.dot(self.h.T, dL_dy)
dL_db2 = np.sum(dL_dy, axis=0)
# 隐藏层梯度
dL_dh = np.dot(dL_dy, self.W2.T)
dL_dz1 = dL_dh * self.sigmoid_derivative(self.z1)
dL_dW1 = np.dot(x.T, dL_dz1)
dL_db1 = np.sum(dL_dz1, axis=0)
# 参数更新
self.W1 -= learning_rate * dL_dW1
self.b1 -= learning_rate * dL_db1
self.W2 -= learning_rate * dL_dW2
self.b2 -= learning_rate * dL_db2
def train(self, X, Y, epochs=1000, learning_rate=0.01):
losses = []
for epoch in range(epochs):
# 前向传播
y_pred = self.forward(X)
# 计算损失
loss = np.mean(0.5 * (y_pred - Y) ** 2)
losses.append(loss)
# 反向传播
self.backward(X, y_pred, Y, learning_rate)
if epoch % 100 == 0:
print(f'Epoch {epoch}, Loss: {loss}')
return losses
可视化展示
使用 matplotlib 绘制训练过程中的损失函数变化曲线:
import matplotlib.pyplot as plt
# 创建并训练网络
input_size = 2
hidden_size = 4
output_size = 1
nn = SimpleNN(input_size, hidden_size, output_size)
# 生成简单数据
X = np.array([[0, 0], [0, 1], [1, 0], [1, 1]])
Y = np.array([[0], [1], [1], [0]]) # XOR 问题
# 训练并获取损失历史
losses = nn.train(X, Y, epochs=1000, learning_rate=0.1)
# 绘制损失曲线
plt.plot(losses)
plt.xlabel('Epoch')
plt.ylabel('Loss')
plt.title('Training Loss Curve')
plt.show()
避坑指南
- 梯度消失问题 :
- 现象:深层网络中,梯度在反向传播时变得越来越小
-
解决方案:使用 ReLU 等激活函数替代 sigmoid;尝试残差连接
-
学习率设置不当 :
- 现象:损失函数震荡不收敛或下降过慢
-
解决方案:尝试不同的学习率(如 0.1, 0.01, 0.001);使用学习率衰减策略
-
权重初始化不当 :
- 现象:网络无法正常学习
- 解决方案:使用 Xavier 或 He 初始化方法替代随机初始化
扩展思考
- 尝试实现不同激活函数的反向传播:
- ReLU: $f(x) = max(0, x)$
- LeakyReLU: $f(x) = max(0.01x, x)$
-
Tanh: $f(x) = \frac{e^x – e^{-x}}{e^x + e^{-x}}$
-
扩展网络结构:
- 增加隐藏层数量
-
尝试不同的损失函数(交叉熵损失等)
-
实现 mini-batch 梯度下降:
- 将数据集分成小批次进行训练
- 比较与全批次训练的差异
结语
通过本文的学习,你应该已经掌握了反向传播的基本原理和实现方法。建议动手实践代码,尝试修改网络结构和参数,观察训练效果的变化。深度学习的学习曲线虽然陡峭,但只要理解了这些基础概念,后面的路会越走越宽。
