一、背景

基于对应链接示例上的深度学习过程,对每一步进行详细的说明,方便数据和实例的结合理解深度学习的过程。

示例的简单介绍:提供0-9的数字灰度照片进行深度学习训练,数据包括6万张训练图像和1万张测试图像以及对应的标签(即0-9的标签)。图像的像素为28*28,大小为784。

三层前馈神经网络,理解两个传播过程的基础:

  • 输入层:784个节点,对应一张28x28图片展平后的784个像素值。
  • 隐藏层:使用了16个神经元,并采用了ReLU激活函数引入非线性。
  • 输出层:10个节点,对应数字0-9的十个类别,输出值最大的节点即预测结果。
  • 核心参数:连接这些层的权重矩阵 weights_input_hiddenweights_hidden_output,是模型要通过学习来优化的部分。

总体示意图,左边是前向传播、右侧为反向传播

Diagram showing operations detailed in this tutorial (The input imageis passed into a Hidden layer that creates a weighted sum of outputs.The weighted sum is passed to the Non-linearity, then regularization andinto the output layer. The output layer creates a prediction which canthen be compared to existing data. The errors are used to calculate theloss function and update weights in the hidden layer and outputlayer.)

二、前向传播(预测)

前向传播是数据从输入层流向输出层,最终做出预测的过程。理解为一次“推理”或“猜测”。

# 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 # 输出层未使用激活函数

过程解读

  1. 线性变换:输入数据分别与两个权重矩阵进行矩阵乘法(np.dot)。
  2. 激活函数:隐藏层使用relu函数,它将所有负数置零,这是神经网络能学习复杂模式的关键。
  3. 正则化:在训练时,对隐藏层输出随机“丢弃”(Dropout)一部分,将其置零,这是防止模型死记硬背(过拟合)的有效技巧。
  4. 得到预测:最终输出一个包含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

过程解读

  1. 计算误差:用模型预测值减去真实标签的独热编码,得到输出层的误差 (output_error)。
  2. 输出层梯度:误差 output_error 反向传播到 weights_hidden_output。根据链式法则,其梯度等于前一层(隐藏层)的输出转置与当前误差的矩阵乘积。
  3. 隐藏层梯度:误差继续反向传播到 weights_input_hidden。这里需要注意:
    • 先计算传递到隐藏层的误差 (hidden_error)。
    • 因为隐藏层使用了ReLU函数,其导数是:输入>0时为1,否则为0。所以要将hidden_layer_input中小于等于0的位置对应的误差清零。
    • 最终,weights_input_hidden 的梯度等于输入层数据的转置与处理后的隐藏层误差的乘积。
  4. 更新权重:所有梯度计算完毕后,沿着梯度反方向(即减去梯度),以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)

  • 首先求 Lz2 的梯度: ∂L/∂z2 = z2 - y_true = [0.192, -0.064] - [0, 1] = [0.192, -1.064]
  • 根据链式法则,LW2 的梯度等于 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)
这需要误差从输出层一步步传递回来。

  • 首先,计算 La1 的梯度: ∂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]
  • 接着,计算 Lz1 的梯度。因为 a1 = relu(z1),所以 ∂a1/∂z1z1>0 时为1,否则为0。
    ∂L/∂z1 = ∂L/∂a1 * relu'(z1) = [0.5704, 0.0088] * [1, 1] = [0.5704, 0.0088] (因为z1两个值都>0)
  • 最后,计算 LW1 的梯度,等于输入 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]]

更多推荐