共计 1858 个字符,预计需要花费 5 分钟才能阅读完成。
引言:神经网络训练的基本流程
神经网络的学习过程可以概括为两个核心阶段:前向传播(Forward Propagation)和反向传播(Back Propagation)。前向传播负责计算预测值,反向传播则通过误差的反向传递来调整网络参数。这种机制使得神经网络能够从数据中学习复杂的模式。

正向传播与损失计算
在正向传播阶段,输入数据通过网络的每一层,经过线性变换和激活函数的处理,最终输出预测结果。以简单的全连接网络为例:
- 输入层接收原始数据
- 隐藏层进行特征提取和转换
- 输出层产生最终预测
损失函数(如均方误差 MSE)用于衡量预测值与真实值的差距:
$$
L = \frac{1}{2N}\sum_{i=1}^N(y_i – \hat{y}_i)^2
$$
反向传播的数学原理(链式法则)
反向传播的核心是链式法则(Chain Rule),它允许我们将复合函数的导数分解为多个简单导数的乘积。对于神经网络中的权重参数 $w$,梯度计算可以表示为:
$$
\frac{\partial L}{\partial w} = \frac{\partial L}{\partial \hat{y}} \cdot \frac{\partial \hat{y}}{\partial z} \cdot \frac{\partial z}{\partial w}
$$
其中 $z$ 是该神经元的加权输入。
权重更新的具体实现
通过反向传播计算出梯度后,我们使用梯度下降法更新权重:
$$
w_{new} = w_{old} – \eta \cdot \frac{\partial L}{\partial w}
$$
其中 $\eta$ 是学习率,控制着参数更新的步长。
代码实现与可视化
以下是完整的 Python 实现,包含详细注释:
import numpy as np
import matplotlib.pyplot as plt
class NeuralNetwork:
def __init__(self, input_size, hidden_size, output_size):
# 初始化权重
self.W1 = np.random.randn(input_size, hidden_size) * 0.01
self.W2 = np.random.randn(hidden_size, output_size) * 0.01
def forward(self, X):
# 前向传播
self.z1 = np.dot(X, self.W1)
self.a1 = self.sigmoid(self.z1)
self.z2 = np.dot(self.a1, self.W2)
return self.sigmoid(self.z2)
def backward(self, X, y, output, learning_rate):
# 反向传播
m = X.shape[0] # 样本数量
# 输出层误差
delta2 = (output - y) * self.sigmoid_derivative(output)
dW2 = np.dot(self.a1.T, delta2) / m
# 隐藏层误差
delta1 = np.dot(delta2, self.W2.T) * self.sigmoid_derivative(self.a1)
dW1 = np.dot(X.T, delta1) / m
# 更新权重
self.W2 -= learning_rate * dW2
self.W1 -= learning_rate * dW1
def sigmoid(self, x):
return 1 / (1 + np.exp(-x))
def sigmoid_derivative(self, x):
return x * (1 - x)
常见问题与调试技巧
-
梯度消失问题 :当网络层数较深时,梯度可能在反向传播过程中变得极小。解决方法包括使用 ReLU 激活函数、批归一化等。
-
学习率设置不当 :过大的学习率可能导致震荡,过小则收敛缓慢。建议使用学习率衰减或自适应优化器(如 Adam)。
-
权重初始化不当 :所有权重初始化为相同值会导致对称性问题。使用随机初始化(如 Xavier 初始化)可以避免这个问题。
总结与进阶学习建议
通过本文,你应该已经理解了 BP 算法的核心原理和实现方法。为了进一步巩固知识,建议尝试以下练习:
- 实现一个三层神经网络,并在 MNIST 数据集上进行测试
- 比较不同激活函数(如 ReLU、tanh)对训练效果的影响
- 尝试添加 L2 正则化项,观察其对过拟合的影响
思考题:
1. 为什么在反向传播时需要乘以激活函数的导数?
2. 如果某个神经元的输出接近 0 或 1,会对梯度计算产生什么影响?
3. 如何修改代码实现批量梯度下降(Batch GD)和小批量梯度下降(Mini-batch GD)?
