第67课 - 反向传播算法详解
学习目标
- 理解神经网络中损失函数与权重更新之间的关系。
- 掌握链式法则(Chain Rule)在梯度计算中的应用。
- 掌握反向传播算法(Backpropagation)的核心步骤与数学原理。
- 能够手动计算一个简单神经网络的梯度,并理解梯度消失与梯度爆炸问题。
核心概念
在上一课,我们学习了多层感知机(MLP)的结构。网络如何知道如何调整它的权重(w)和偏置(b)来学习呢?答案就是反向传播算法。它是训练神经网络的核心引擎,本质上是一种高效计算损失函数相对于网络中所有权重梯度的方法。
1. 前向传播(Forward Pass)
数据从输入层开始,经过各层的线性变换(z = wx + b)和非线性激活函数(a = σ(z)),最终计算出预测值 ŷ。这个过程叫前向传播。
2. 损失函数(Loss Function)
前向传播结束后,我们使用损失函数(如均方误差 MSE)来衡量预测值 ŷ 与真实值 y 之间的差距。损失值 L 是一个标量。
3. 梯度与链式法则
我们的目标是调整权重 w 以最小化损失 L。根据梯度下降法,我们需要知道 ∂L/∂w(损失函数关于权重 w 的偏导数,即梯度)。梯度指明了损失函数上升最快的方向,因此我们向梯度的反方向更新权重。
计算梯度需要用到微积分的链式法则。例如,对于一个复合函数 y = f(g(x)),其导数 dy/dx = (dy/dg) * (dg/dx)。神经网络正是一个深度复合函数,我们需要从输出层开始,逐层向前“反向”计算梯度。
4. 反向传播算法步骤
算法的核心是逐层计算误差项(δ),然后利用它计算权重的梯度。
- 步骤1:前向传播,计算所有神经元的激活值
a和输出ŷ。 - 步骤2:计算输出层误差:
δ^L = ∂L/∂z^L(损失函数对输出层加权输入z的偏导数)。 - 步骤3:反向传播误差:对于第
l层,误差δ^l由下一层(l+1层)的误差δ^{l+1}计算得来:δ^l = ( (w^{l+1})^T · δ^{l+1} ) ⊙ σ'(z^l)。其中⊙表示逐元素相乘,σ'是激活函数的导数。 - 步骤4:计算梯度:权重的梯度
∂L/∂w^l = δ^l · (a^{l-1})^T,偏置的梯度∂L/∂b^l = δ^l。
代码示例
下面的代码手动实现了一个简单的三层神经网络(输入层、隐藏层、输出层)的反向传播算法。
import numpy as np
# 初始化一个简单的神经网络:输入层2个神经元,隐藏层3个神经元,输出层1个神经元
np.random.seed(42)
X = np.array([[0.5, 0.1], [0.2, 0.3], [0.4, 0.8], [0.7, 0.9]]) # 4个样本,2个特征
y = np.array([[0], [1], [1], [1]]) # 4个样本的真实标签
# 初始化权重和偏置
w1 = np.random.randn(2, 3) * 0.5 # 输入层到隐藏层权重
b1 = np.zeros((1, 3)) # 隐藏层偏置
w2 = np.random.randn(3, 1) * 0.5 # 隐藏层到输出层权重
b2 = np.zeros((1, 1)) # 输出层偏置
learning_rate = 0.1
# 定义 Sigmoid 激活函数及其导数
def sigmoid(z):
return 1 / (1 + np.exp(-z))
def sigmoid_derivative(z):
s = sigmoid(z)
return s * (1 - s)
# 定义均方误差损失函数
def mse_loss(y_true, y_pred):
return np.mean((y_true - y_pred) ** 2)
# 训练循环
for epoch in range(1000):
# === 步骤1: 前向传播 ===
z1 = X.dot(w1) + b1 # 隐藏层加权输入
a1 = sigmoid(z1) # 隐藏层激活值
z2 = a1.dot(w2) + b2 # 输出层加权输入
y_pred = sigmoid(z2) # 预测输出
# 计算损失
loss = mse_loss(y, y_pred)
if epoch % 100 == 0:
print(f'Epoch {epoch}, Loss: {loss:.4f}')
# === 步骤2 & 3: 反向传播 ===
# 计算输出层的误差项 δ2 (dL/dz2)
# 对于 MSE 和 Sigmoid,δ2 = (y_pred - y) * sigmoid_derivative(z2)
delta2 = (y_pred - y) * sigmoid_derivative(z2)
# 计算隐藏层的误差项 δ1
# δ1 = (w2 · δ2) ⊙ sigmoid_derivative(z1)
delta1 = delta2.dot(w2.T) * sigmoid_derivative(z1)
# === 步骤4: 计算梯度并更新参数 ===
# 计算权重和偏置的梯度
dW2 = a1.T.dot(delta2)
dB2 = np.sum(delta2, axis=0, keepdims=True)
dW1 = X.T.dot(delta1)
dB1 = np.sum(delta1, axis=0, keepdims=True)
# 使用梯度下降更新权重和偏置
w2 -= learning_rate * dW2
b2 -= learning_rate * dB2
w1 -= learning_rate * dW1
b1 -= learning_rate * dB1
print("训练完成!最终损失:", mse_loss(y, y_pred))
print("预测输出:", y_pred)
实践练习
练习1(简单):考虑一个只有1个输入、1个隐藏神经元(激活函数为 Sigmoid)和1个输出的极简网络。
- 前向传播方程:
a1 = σ(w1*x + b1),ŷ = w2*a1 + b2。 - 损失函数为 MSE:
L = 1/2 * (y - ŷ)²。 - 请手动推导:计算
∂L/∂w1和∂L/∂w2的表达式(要求用链式法则写出完整步骤)。
练习2(中等):修改上述代码示例中的网络,将其改为包含两个隐藏层(神经元数量自定),并实现完整的反向传播。观察增加网络深度后,训练是否变得更困难。
常见错误
- 混淆矩阵乘法顺序:在反向传播公式
δ^l = ( (w^{l+1})^T · δ^{l+1} ) ⊙ ...中,权重矩阵需要转置。错误使用w · δ会导致维度错误和错误的梯度计算。 - 梯度方向弄反:更新权重时,应该沿梯度的负方向(
w = w - lr * grad)。写成w = w + lr * grad会导致损失增加。 - 忽略偏置的梯度:偏置
b的梯度是δ^l,计算方法和权重不同(求和维度)。容易遗漏np.sum操作。 - 不正确的激活函数导数:使用 ReLU 等激活函数时,其导数在负区间为0,这会导致神经元“死亡”,梯度无法传播。需要在实现时特别注意。
小结
- 反向传播算法是神经网络学习的基石,它利用链式法则高效地计算损失函数关于所有参数的梯度。
- 算法分为 “前向传播计算输出与损失” 和 “反向传播计算梯度” 两个主要阶段。
- 链式法则是将深层网络的复杂梯度分解为一系列局部梯度的乘积。
- 理解并能手动推导反向传播过程,对于调试网络、理解梯度消失/爆炸等问题至关重要。梯度消失(常见于深层网络使用Sigmoid)和梯度爆炸是限制网络深度的主要原因之一,将在后续课程中探讨。
- 掌握了反向传播,你就掌握了训练神经网络的“核心密码”。下一步,我们将深入学习不同激活函数的特性,它们对反向传播中的梯度流有着直接而关键的影响。