Skip to content

第 4 章:前向传播

把第 1-3 章的知识组装起来,走一遍完整的"输入图片 → 输出概率"过程。

4.1 前向传播的本质

"前向传播"(Forward Propagation)就是数据从左到右流过网络的过程:

图片像素值           隐藏层特征           输出概率
X (n,784)  →第一层→  z1 (n,50)  →第二层→  y (n,10)

每一步都做:线性变换(矩阵乘法 + 偏置)→ 非线性激活

下面我们一步步推导,假设 batch size = n(同时处理 n 张图片)。


4.2 第一步:输入数据准备

原始图片是 28×28 的灰度图,像素值在 0-255 之间:

原始像素矩阵 (28×28):        展平成向量 (784,):
┌─────────────────────┐      [0, 0, 0, 5, 18, 235, 240, ...]
│  0   0   0   0  ...  │  →
│  0  12  89 240  ...  │      归一化到 [0,1]:
│  ...                  │  →  [0.0, 0.0, 0.0, 0.02, 0.07, 0.92, 0.94, ...]
└─────────────────────┘

当我们处理 n 张图片时,把它们堆叠成一个矩阵:

X = shape (n, 784)

第 1 张图片的像素 → [0.0, 0.0, 0.02, ..., 0.94]   第 1 行
第 2 张图片的像素 → [0.1, 0.0, 0.00, ..., 0.00]   第 2 行
...
第 n 张图片的像素 → [0.0, 0.3, 0.85, ..., 0.12]   第 n 行

4.3 第二步:第一层(输入层 → 隐藏层)

线性变换

a1(n,50)=X(n,784)W1(784,50)+b1(50,)

维度验证:

X @ W1:
(n, 784) @ (784, 50) → (n, 50)    ✓ 中间 784=784 消掉
           ↑      ↑
         相等,合法

+ b1:
(n, 50) + (50,) → (n, 50)         ✓ 广播:b1 复制 n 份

结果含义: a1 的第 i 行第 j 列 = 第 i 张图片在第 j 个隐藏节点的"原始线性输出"(还没有激活)。

展开来看,a1[i,j] 的计算:

a1[i, j] = X[i, 0]*W1[0, j] + X[i, 1]*W1[1, j] + ... + X[i, 783]*W1[783, j] + b1[j]
           ↑                                                                      ↑
        第i张图片的第0个像素乘以                                                 第j个节点的偏置
        连接到第j个节点的权重

这正是我们在第 1 章说的"加权求和 + 偏置",只不过矩阵乘法一次算出了 n×50 个这样的值。

激活函数(Sigmoid)

z1(n,50)=sigmoid(a1(n,50))

逐元素应用 Sigmoid,shape 不变:

a1[i, j] → σ(a1[i, j]) → z1[i, j]

每个元素独立计算:z1[i,j] = 1 / (1 + exp(-a1[i,j]))

z1 的每个值都在 (0, 1) 之间。它代表:第 i 张图片在第 j 个隐藏节点上被"激活"的程度。


4.4 第三步:第二层(隐藏层 → 输出层)

线性变换

a2(n,10)=z1(n,50)W2(50,10)+b2(10,)

维度验证:

z1 @ W2:
(n, 50) @ (50, 10) → (n, 10)    ✓ 中间 50=50 消掉

+ b2:
(n, 10) + (10,) → (n, 10)       ✓ 广播

a2 的第 i 行,是第 i 张图片对 10 个类别的原始得分(logits)——可以是任意实数,还不是概率。

激活函数(Softmax)

y(n,10)=softmax(a2(n,10))

注意: Softmax 是行内归一化——对每张图片(每一行)的 10 个值做归一化,让它们之和等于 1:

a2 的第 i 行: [-2.1,  0.5,  3.2,  -0.8,  1.1,  0.3,  -1.5,  8.7,  0.2,  -0.4]
                                                                ↑ 数字7的分数最高

softmax 之后:
y 的第 i 行: [0.00, 0.00, 0.01, 0.00, 0.01, 0.00, 0.00, 0.97, 0.00, 0.00]
                                                                ↑ 97% 的概率是数字7

每行之和 = 1 ✓

y[i,c] 的含义:网络认为第 i 张图片是数字 c 的概率。


4.5 完整前向传播一览

把所有步骤放在一起,加上详细的维度标注:

输入:  X              shape: (n, 784)

第一层:
  a1 = X @ W1 + b1   shape: (n, 784) @ (784, 50) + (50,) = (n, 50)
  z1 = sigmoid(a1)   shape: (n, 50)   [逐元素,shape不变]

第二层:
  a2 = z1 @ W2 + b2  shape: (n, 50) @ (50, 10) + (10,) = (n, 10)
  y  = softmax(a2)   shape: (n, 10)   [逐行归一化,shape不变]

输出:  y              shape: (n, 10)

4.6 数值例子(n=2,简化到 3 个像素和 2 个隐藏节点,3 个类别)

用小数字走一遍,看清楚每步在干什么。

参数设置(假设已经训练好):

python
W1 = [[0.1, -0.2],    # shape: (3, 2)
      [0.3,  0.4],
      [-0.1, 0.5]]

b1 = [0.1, -0.1]       # shape: (2,)

W2 = [[0.5, -0.3, 0.8],  # shape: (2, 3)
      [-0.2, 0.6, 0.1]]

b2 = [0.0, 0.1, -0.1]    # shape: (3,)

输入(2张图片,每张3个像素):

python
X = [[0.5, 0.2, 0.8],   # 第1张图片  shape: (2, 3)
     [0.1, 0.9, 0.3]]   # 第2张图片

第一层:

a1 = X @ W1 + b1

X @ W1 的计算:
  a1[0,0] = 0.5*0.1 + 0.2*0.3 + 0.8*(-0.1) = 0.05+0.06-0.08 = 0.03
  a1[0,1] = 0.5*(-0.2) + 0.2*0.4 + 0.8*0.5 = -0.10+0.08+0.40 = 0.38
  a1[1,0] = 0.1*0.1 + 0.9*0.3 + 0.3*(-0.1) = 0.01+0.27-0.03 = 0.25
  a1[1,1] = 0.1*(-0.2) + 0.9*0.4 + 0.3*0.5 = -0.02+0.36+0.15 = 0.49

加偏置 b1 = [0.1, -0.1]:
  a1 = [[0.03+0.1, 0.38-0.1],   = [[0.13, 0.28],
         [0.25+0.1, 0.49-0.1]]      [0.35, 0.39]]

z1 = sigmoid(a1):
  σ(0.13) = 1/(1+e^{-0.13}) ≈ 0.532
  σ(0.28) = 1/(1+e^{-0.28}) ≈ 0.570
  σ(0.35) = 1/(1+e^{-0.35}) ≈ 0.587
  σ(0.39) = 1/(1+e^{-0.39}) ≈ 0.596

z1 = [[0.532, 0.570],
       [0.587, 0.596]]   shape: (2, 2)

第二层:

a2 = z1 @ W2 + b2

z1 @ W2:
  a2[0,0] = 0.532*0.5 + 0.570*(-0.2) = 0.266 - 0.114 = 0.152
  a2[0,1] = 0.532*(-0.3) + 0.570*0.6 = -0.160 + 0.342 = 0.182
  a2[0,2] = 0.532*0.8 + 0.570*0.1 = 0.426 + 0.057 = 0.483
  a2[1,0] = 0.587*0.5 + 0.596*(-0.2) = 0.294 - 0.119 = 0.175
  a2[1,1] = 0.587*(-0.3) + 0.596*0.6 = -0.176 + 0.358 = 0.182
  a2[1,2] = 0.587*0.8 + 0.596*0.1 = 0.470 + 0.060 = 0.530

加偏置 b2 = [0.0, 0.1, -0.1]:
  a2 = [[0.152, 0.282, 0.383],
         [0.175, 0.282, 0.430]]

softmax 对每行归一化:
第1张图片:
  e^0.152 ≈ 1.164, e^0.282 ≈ 1.326, e^0.383 ≈ 1.467
  总和 = 3.957
  y[0] = [1.164/3.957, 1.326/3.957, 1.467/3.957]
       ≈ [0.294, 0.335, 0.371]    → 预测为类别2(概率37.1%)

第2张图片:
  e^0.175 ≈ 1.191, e^0.282 ≈ 1.326, e^0.430 ≈ 1.537
  总和 = 4.054
  y[1] = [1.191/4.054, 1.326/4.054, 1.537/4.054]
       ≈ [0.294, 0.327, 0.379]    → 预测为类别2(概率37.9%)

4.7 前向传播在代码中的实现

python
# model.py 中的 forward 方法
def forward(self, X):
    W1, b1 = self.params['W1'], self.params['b1']
    W2, b2 = self.params['W2'], self.params['b2']

    a1 = X @ W1 + b1      # (n,784)@(784,50) + (50,) = (n,50)
    z1 = sigmoid(a1)      # (n,50) → (n,50) [逐元素]

    a2 = z1 @ W2 + b2     # (n,50)@(50,10) + (10,) = (n,10)
    y  = softmax(a2)      # (n,10) → (n,10) [逐行归一化]

    return y

代码和数学公式是一一对应的,没有任何"神秘"操作。


4.8 小结

前向传播的每一步:

X (n,784)
  ↓ @ W1(784,50)
a1 (n,50)  ← 50个隐藏节点的加权求和
  ↓ sigmoid(逐元素)
z1 (n,50)  ← 每个值压缩到(0,1),表示"激活程度"
  ↓ @ W2(50,10)
a2 (n,10)  ← 10个类别的原始分数(logits)
  ↓ softmax(逐行)
y  (n,10)  ← 概率分布,每行和为1,取最大值即预测类别

下一章,我们来解决:怎么衡量网络的预测有多"错"?——损失函数。


← 第 3 章 | 返回目录 | 第 5 章:损失函数 →

基于 Kaggle MNIST 数据集,使用纯 numpy 从零实现