【NumPy】MNIST上深度学习计算过程理解:三、前向传播和反向传播
一、背景
基于对应链接示例上的深度学习过程,对每一步进行详细的说明,方便数据和实例的结合理解深度学习的过程。
示例的简单介绍:提供0-9的数字灰度照片进行深度学习训练,数据包括6万张训练图像和1万张测试图像以及对应的标签(即0-9的标签)。图像的像素为28*28,大小为784。
三层前馈神经网络,理解两个传播过程的基础:
- 输入层:784个节点,对应一张28x28图片展平后的784个像素值。
- 隐藏层:使用了16个神经元,并采用了ReLU激活函数引入非线性。
- 输出层:10个节点,对应数字0-9的十个类别,输出值最大的节点即预测结果。
- 核心参数:连接这些层的权重矩阵
weights_input_hidden和weights_hidden_output,是模型要通过学习来优化的部分。
总体示意图,左边是前向传播、右侧为反向传播

二、前向传播(预测)
前向传播是数据从输入层流向输出层,最终做出预测的过程。理解为一次“推理”或“猜测”。
# 1. 输入层 -> 隐藏层
hidden_layer_input = np.dot(training_images, weights_input_hidden)
hidden_layer_output = relu(hidden_layer_input)
# 2. 应用Dropout正则化(训练时)
dropout_mask = np.random.randint(0, 2, size=hidden_layer_output.shape)
hidden_layer_output *= dropout_mask
# 3. 隐藏层 -> 输出层
output_layer_input = np.dot(hidden_layer_output, weights_hidden_output)
final_output = output_layer_input # 输出层未使用激活函数
过程解读:
- 线性变换:输入数据分别与两个权重矩阵进行矩阵乘法(
np.dot)。 - 激活函数:隐藏层使用
relu函数,它将所有负数置零,这是神经网络能学习复杂模式的关键。 - 正则化:在训练时,对隐藏层输出随机“丢弃”(Dropout)一部分,将其置零,这是防止模型死记硬背(过拟合)的有效技巧。
- 得到预测:最终输出一个包含10个分数的向量,数值最大的那个位置就是模型预测的数字。
三、反向传播(学习)
模型做出预测后,需要与真实标签比较,计算出误差(损失),然后反向传播这个误差,来指导权重应该如何调整才能减少未来的误差。这个过程的核心是链式法则。
# 1. 计算输出层的误差梯度
output_error = final_output - training_labels
gradient_weights_hidden_output = np.dot(hidden_layer_output.T, output_error)
# 2. 计算隐藏层的误差梯度
hidden_error = np.dot(output_error, weights_hidden_output.T)
hidden_error[hidden_layer_input <= 0] = 0 # ReLU函数的导数
gradient_weights_input_hidden = np.dot(training_images.T, hidden_error)
# 3. 更新权重(梯度下降)
weights_hidden_output -= learning_rate * gradient_weights_hidden_output
weights_input_hidden -= learning_rate * gradient_weights_input_hidden
过程解读:
- 计算误差:用模型预测值减去真实标签的独热编码,得到输出层的误差 (
output_error)。 - 输出层梯度:误差
output_error反向传播到weights_hidden_output。根据链式法则,其梯度等于前一层(隐藏层)的输出转置与当前误差的矩阵乘积。 - 隐藏层梯度:误差继续反向传播到
weights_input_hidden。这里需要注意:- 先计算传递到隐藏层的误差 (
hidden_error)。 - 因为隐藏层使用了ReLU函数,其导数是:输入>0时为1,否则为0。所以要将
hidden_layer_input中小于等于0的位置对应的误差清零。 - 最终,
weights_input_hidden的梯度等于输入层数据的转置与处理后的隐藏层误差的乘积。
- 先计算传递到隐藏层的误差 (
- 更新权重:所有梯度计算完毕后,沿着梯度反方向(即减去梯度),以
learning_rate为步长,更新所有权重。这一步直接降低了损失函数的值。
四、数值计算示例
为了使计算清晰可循,我们构建一个“迷你网络”:
- 输入层:2个神经元(对应2个像素,原教程是784个)。
- 隐藏层:2个神经元(原教程是16个),使用ReLU激活函数。
- 输出层:2个神经元(原教程是10个,对应10个数字类别)。
- 训练样本:1个。输入
x = [0.2, 0.8],真实标签y_true = [0, 1](表示类别1)。
1. 初始化网络参数
我们随机初始化权重矩阵,并假设所有偏置为0以简化计算。
W1(连接输入层到隐藏层):[[0.5, -0.3], [0.1, 0.4]](形状:2x2)W2(连接隐藏层到输出层):[[0.2, -0.5], [0.6, 0.1]](形状:2x2)- 学习率
lr = 0.1
2. 前向传播(Forward Pass)
(1) 输入层 -> 隐藏层 (线性变换)z1 = x · W1 = [0.2, 0.8] · [[0.5, -0.3], [0.1, 0.4]]
计算过程:
- 第一个神经元:
0.2*0.5 + 0.8*0.1 = 0.1 + 0.08 = 0.18 - 第二个神经元:
0.2*(-0.3) + 0.8*0.4 = -0.06 + 0.32 = 0.26
结果:z1 = [0.18, 0.26]
(2) 隐藏层 -> 激活输出 (应用ReLU)a1 = relu(z1) = relu([0.18, 0.26]) = [0.18, 0.26] (因为所有值>0)
(3) 隐藏层 -> 输出层 (线性变换)z2 = a1 · W2 = [0.18, 0.26] · [[0.2, -0.5], [0.6, 0.1]]
计算过程:
- 第一个输出神经元:
0.18*0.2 + 0.26*0.6 = 0.036 + 0.156 = 0.192 - 第二个输出神经元:
0.18*(-0.5) + 0.26*0.1 = -0.09 + 0.026 = -0.064
结果:z2 = [0.192, -0.064](这就是网络的原始预测值)
(4) 计算损失 (Loss)
我们使用简单的均方误差(MSE)损失函数,原教程使用的是交叉熵损失,但MSE更便于本示例的梯度推导。L = 0.5 * (z2 - y_true)^2
计算过程:
- 对于第一个输出:
0.5 * (0.192 - 0)^2 = 0.5 * 0.036864 = 0.018432 - 对于第二个输出:
0.5 * (-0.064 - 1)^2 = 0.5 * (-1.064)^2 = 0.5 * 1.132096 = 0.566048
总损失:L = 0.018432 + 0.566048 = 0.58448
我们的目标是通过反向传播来降低这个损失值。
3. 反向传播(Backward Pass)
反向传播的核心是链式法则,目的是计算损失 L 对每个权重 (W1, W2) 的梯度 (∂L/∂W),然后沿梯度反方向更新权重。
(1) 计算输出层梯度 (∂L/∂W2)
- 首先求
L对z2的梯度:∂L/∂z2 = z2 - y_true = [0.192, -0.064] - [0, 1] = [0.192, -1.064] - 根据链式法则,
L对W2的梯度等于a1的转置与∂L/∂z2的矩阵乘积:∂L/∂W2 = a1^T · (∂L/∂z2)= [[0.18], [0.26]] · [0.192, -1.064]= [[0.18*0.192, 0.18*(-1.064)], [0.26*0.192, 0.26*(-1.064)]]= [[0.03456, -0.19152], [0.04992, -0.27664]]
(2) 计算隐藏层梯度 (∂L/∂W1)
这需要误差从输出层一步步传递回来。
- 首先,计算
L对a1的梯度:∂L/∂a1 = (∂L/∂z2) · W2^T= [0.192, -1.064] · [[0.2, 0.6], [-0.5, 0.1]]= [0.192*0.2 + (-1.064)*(-0.5), 0.192*0.6 + (-1.064)*0.1]= [0.0384 + 0.532, 0.1152 - 0.1064]= [0.5704, 0.0088] - 接着,计算
L对z1的梯度。因为a1 = relu(z1),所以∂a1/∂z1在z1>0时为1,否则为0。∂L/∂z1 = ∂L/∂a1 * relu'(z1) = [0.5704, 0.0088] * [1, 1] = [0.5704, 0.0088](因为z1两个值都>0) - 最后,计算
L对W1的梯度,等于输入x的转置与∂L/∂z1的矩阵乘积:∂L/∂W1 = x^T · (∂L/∂z1)= [[0.2], [0.8]] · [0.5704, 0.0088]= [[0.2*0.5704, 0.2*0.0088], [0.8*0.5704, 0.8*0.0088]]= [[0.11408, 0.00176], [0.45632, 0.00704]]
4. 更新权重(梯度下降)
使用计算出的梯度和学习率更新权重:新权重 = 旧权重 - 学习率 * 梯度
(1) 更新 W2:W2_new = W2 - lr * ∂L/∂W2= [[0.2, -0.5], [0.6, 0.1]] - 0.1 * [[0.03456, -0.19152], [0.04992, -0.27664]]= [[0.2 - 0.003456, -0.5 - (-0.019152)], [0.6 - 0.004992, 0.1 - (-0.027664)]]= [[0.196544, -0.480848], [0.595008, 0.127664]]
(2) 更新 W1:W1_new = W1 - lr * ∂L/∂W1= [[0.5, -0.3], [0.1, 0.4]] - 0.1 * [[0.11408, 0.00176], [0.45632, 0.00704]]= [[0.5 - 0.011408, -0.3 - 0.000176], [0.1 - 0.045632, 0.4 - 0.000704]]= [[0.488592, -0.300176], [0.054368, 0.399296]]
更多推荐
所有评论(0)