共计 3083 个字符,预计需要花费 8 分钟才能阅读完成。
神经网络前向传播的数学表示
在开始讨论反向传播之前,我们需要先理解神经网络的前向传播过程。假设我们有一个简单的三层全连接网络(输入层、隐藏层、输出层),其数学表示如下:

- 输入层到隐藏层:
$$h = \sigma(W_1x + b_1)$$ - 隐藏层到输出层:
$$y = \sigma(W_2h + b_2)$$
其中,$\sigma$ 是 sigmoid 激活函数,$W$ 代表权重矩阵,$b$ 代表偏置项。
损失函数与梯度下降原理
训练神经网络的目标是最小化损失函数。对于二分类问题,我们通常使用交叉熵损失函数:
$$E = -\frac{1}{N}\sum_{i=1}^N [y_i\log(\hat{y_i}) + (1-y_i)\log(1-\hat{y_i})]$$
梯度下降法的核心思想是通过计算损失函数对参数的梯度,然后沿着梯度相反的方向更新参数:
$$w_{new} = w_{old} – \eta \frac{\partial E}{\partial w}$$
其中,$\eta$ 是学习率。
BP 算法的链式法则推导
反向传播算法的精髓在于通过链式法则高效计算梯度。我们以输出层权重 $W_2$ 为例:
-
计算损失对输出的梯度:
$$\frac{\partial E}{\partial \hat{y}} = \frac{\hat{y} – y}{\hat{y}(1-\hat{y})}$$ -
计算输出对激活输入的梯度:
$$\frac{\partial \hat{y}}{\partial z_2} = \hat{y}(1-\hat{y})$$ -
计算激活输入对权重的梯度:
$$\frac{\partial z_2}{\partial W_2} = h$$
通过链式法则,我们得到:
$$\frac{\partial E}{\partial W_2} = \frac{\partial E}{\partial \hat{y}} \frac{\partial \hat{y}}{\partial z_2} \frac{\partial z_2}{\partial W_2} = (\hat{y}-y)h$$
类似的推导可以应用于其他参数。
Python 实现示例
下面是使用 NumPy 实现的简单全连接网络:
import numpy as np
class NeuralNetwork:
def __init__(self, input_size, hidden_size, output_size):
# 初始化权重
self.W1 = np.random.randn(input_size, hidden_size) * 0.1
self.b1 = np.zeros((1, hidden_size))
self.W2 = np.random.randn(hidden_size, output_size) * 0.1
self.b2 = np.zeros((1, output_size))
def sigmoid(self, x):
return 1 / (1 + np.exp(-x))
def sigmoid_derivative(self, x):
return x * (1 - x)
def forward(self, X):
self.h = self.sigmoid(np.dot(X, self.W1) + self.b1)
self.y_pred = self.sigmoid(np.dot(self.h, self.W2) + self.b2)
return self.y_pred
def backward(self, X, y, learning_rate):
# 计算输出层梯度
d_loss = (self.y_pred - y) / (self.y_pred * (1 - self.y_pred))
d_z2 = self.y_pred * (1 - self.y_pred)
d_W2 = np.dot(self.h.T, d_loss * d_z2)
d_b2 = np.sum(d_loss * d_z2, axis=0, keepdims=True)
# 计算隐藏层梯度
d_h = np.dot(d_loss * d_z2, self.W2.T)
d_z1 = d_h * self.h * (1 - self.h)
d_W1 = np.dot(X.T, d_z1)
d_b1 = np.sum(d_z1, axis=0, keepdims=True)
# 更新参数
self.W2 -= learning_rate * d_W2
self.b2 -= learning_rate * d_b2
self.W1 -= learning_rate * d_W1
self.b1 -= learning_rate * d_b1
# 训练过程示例
X = np.array([[0,0], [0,1], [1,0], [1,1]]) # 输入
Y = np.array([[0], [1], [1], [0]]) # 输出 (XOR 问题)
nn = NeuralNetwork(2, 4, 1)
for epoch in range(10000):
y_pred = nn.forward(X)
nn.backward(X, Y, 0.1)
# 每 1000 次打印一次损失
if epoch % 1000 == 0:
loss = -np.mean(Y * np.log(y_pred) + (1-Y) * np.log(1-y_pred))
print(f'Epoch {epoch}, Loss: {loss:.4f}')
常见问题分析
梯度消失 / 爆炸
当网络层数较深时,梯度可能在反向传播过程中变得非常小(消失)或非常大(爆炸)。这主要是因为 sigmoid 函数的导数最大值为 0.25,经过多层连乘后会迅速变小。解决方案包括:
- 使用 ReLU 等激活函数
- 使用批量归一化
- 合理的权重初始化
学习率选择
学习率太大可能导致震荡不收敛,太小则训练缓慢。建议:
- 从 0.1 或 0.01 开始尝试
- 使用学习率衰减策略
- 考虑自适应优化器 (如 Adam)
数值梯度验证
为了验证我们反向传播实现的正确性,可以计算数值梯度进行对比:
def numerical_gradient(f, x, eps=1e-4):
grad = np.zeros_like(x)
for idx in np.ndindex(x.shape):
tmp_val = x[idx]
# f(x + eps)
x[idx] = tmp_val + eps
fx_plus = f()
# f(x - eps)
x[idx] = tmp_val - eps
fx_minus = f()
grad[idx] = (fx_plus - fx_minus) / (2 * eps)
x[idx] = tmp_val
return grad
训练过程可视化
我们可以绘制损失曲线来监控训练过程:
import matplotlib.pyplot as plt
losses = []
nn = NeuralNetwork(2, 4, 1)
for epoch in range(10000):
y_pred = nn.forward(X)
nn.backward(X, Y, 0.1)
loss = -np.mean(Y * np.log(y_pred) + (1-Y) * np.log(1-y_pred))
losses.append(loss)
plt.plot(losses)
plt.xlabel('Epoch')
plt.ylabel('Loss')
plt.title('Training Loss Curve')
plt.show()
进阶思考题
- 如何修改网络结构来解决梯度消失问题?
- 除了交叉熵损失函数,还有哪些常见的损失函数适用于不同任务?
- 当训练数据量很大时,如何优化反向传播的计算效率?
总结
通过本文,我们从数学推导到代码实现全面讲解了反向传播算法。理解 bp 算法的关键在于掌握链式法则的应用和梯度传播的方向。在实际应用中,还需要考虑各种优化技巧和工程实践。希望这篇入门教程能帮助你建立对反向传播的直观理解,为进一步学习深度学习打下坚实基础。
