共计 1723 个字符,预计需要花费 5 分钟才能阅读完成。
链式法则与权重更新原理
BP 算法的核心是链式法则。假设我们有 3 层网络(输入层、隐藏层、输出层),以输出层权重更新为例:

- 计算损失函数 L 对输出层权重 w 的偏导:
$$\frac{\partial L}{\partial w} = \frac{\partial L}{\partial a} \cdot \frac{\partial a}{\partial z} \cdot \frac{\partial z}{\partial w}$$ - a 是激活值,z 是加权输入
-
这个偏导反映了权重微小变化对最终损失的影响程度
-
对于 MSE 损失函数和 Sigmoid 激活的组合:
- $$\frac{\partial L}{\partial a} = 2(a-y)$$
- $$\frac{\partial a}{\partial z} = \sigma(z)(1-\sigma(z))$$
- $$\frac{\partial z}{\partial w} = a_{prev}$$
梯度下降的两种实现方式
- 标准梯度下降(Batch GD):
- 使用全部样本计算梯度
- 更新稳定但计算量大
-
适合小规模数据集
-
随机梯度下降(SGD):
- 每次随机选取单个样本
- 更新波动大但计算快
- 适合在线学习场景
实际常用的是 小批量梯度下降(Mini-batch GD),平衡了计算效率和稳定性。
Python 完整实现
import numpy as np
class NeuralNetwork:
def __init__(self, input_size, hidden_size):
# 初始化权重(注意尺度)self.W1 = np.random.randn(input_size, hidden_size) * 0.01
self.W2 = np.random.randn(hidden_size, 1) * 0.01
def sigmoid(self, z):
return 1/(1+np.exp(-z))
def sigmoid_derivative(self, z):
s = self.sigmoid(z)
return s * (1-s)
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)
self.a2 = self.sigmoid(self.z2)
return self.a2
def backward(self, X, y, learning_rate):
m = X.shape[0] # 样本数量
# 输出层梯度
dL_da2 = 2*(self.a2 - y)
da2_dz2 = self.sigmoid_derivative(self.z2)
dz2_dW2 = self.a1
# 梯度裁剪(防止爆炸)grad_W2 = np.clip(dz2_dW2.T.dot(dL_da2 * da2_dz2), -5, 5)
# 隐藏层梯度
dz2_da1 = self.W2
dL_da1 = (dL_da2 * da2_dz2).dot(dz2_da1.T)
da1_dz1 = self.sigmoid_derivative(self.z1)
dz1_dW1 = X
grad_W1 = np.clip(dz1_dW1.T.dot(dL_da1 * da1_dz1), -5, 5)
# 权重更新(带学习率衰减)self.W2 -= learning_rate * grad_W2 / m
self.W1 -= learning_rate * grad_W1 / m
避坑指南
- 权重初始化:
- 过大权重会导致 Sigmoid 饱和(梯度消失)
-
推荐使用 Xavier 初始化:
w = np.random.randn(n) * sqrt(1/n) -
学习率选择:
- 常用基准值:0.01~0.1
-
可配合指数衰减:
lr = initial_lr * (0.95 ** epoch) -
批量大小影响:
- 大批量(如 256)需要更大学习率
- 小批量(如 32)更新更频繁但波动大
思考题
如何修改代码实现带动量的梯度下降?提示:需要维护速度变量 v,更新规则变为:
v = momentum * v - learning_rate * grad
w += v
通过这个实现,我们完整走通了 BP 算法的数学原理到代码落地的全过程。建议读者尝试用不同激活函数(如 ReLU)和优化器进行扩展实验,这将帮助深入理解深度学习训练的动态过程。
正文完
