动手学人工智能-多层感知机7-前向传播、反向传播与计算图

453 阅读2分钟

在深度学习中,训练神经网络离不开两个重要步骤:前向传播(Forward Propagation)和反向传播(Backward Propagation)。这两个步骤通过构建和遍历计算图,帮助我们高效地计算目标函数并更新模型参数。

接下来,我们将通过数学推导和示例,介绍这两个核心概念,并附加相应的代码示例加深理解。

1. 前向传播(Forward Propagation):从输入到输出的计算过程

定义:

前向传播是指从输入层开始,按顺序依次计算神经网络各层输出的过程,直到最终输出结果(即预测值)。这一过程依赖网络结构和参数。

假设我们有一个简单的单隐藏层神经网络,其中:

  • 输入向量为 xRd\mathbf{x} \in \mathbf{R}^d
  • 隐藏层权重为 W1Rh×d\mathbf{W}_1 \in \mathbf{R}^{h \times d}
  • 输出层权重为 W2Ro×h\mathbf{W}_2 \in \mathbf{R}^{o \times h}
  • 激活函数为 σ()\mathbf{\sigma}(\cdot)

数学公式:

  1. 隐藏层的中间变量:h=W1x\mathbf{h} = \mathbf{W}_1 \mathbf{x}

  2. 隐藏层的激活值:z=σ(h)\mathbf{z} = \sigma(\mathbf{h})

  3. 输出层的结果:y=W2z\mathbf{y} = \mathbf{W}_2 \mathbf{z}

如果我们用均方误差(MSE)作为损失函数,其公式为:

L=12yy^2\mathcal{L} = \frac{1}{2} \|\mathbf{y} - \hat{\mathbf{y}}\|^2

其中,y^\mathbf{\hat{y}} 是目标值。

示例代码:

import numpy as np

# 初始化输入、权重和目标
x = np.array([1.0, 2.0])  # 输入向量
W1 = np.array([[0.5, 0.2], [0.3, 0.7]])  # 隐藏层权重
W2 = np.array([[0.6, 0.8]])  # 输出层权重
y_true = np.array([1.5])  # 目标值


# 激活函数(ReLU)
def relu(x):
    return np.maximum(0, x)


# 前向传播
h = np.dot(W1, x)  # 隐藏层中间变量
print(h)  # [0.9 1.7]
z = relu(h)  # 激活
print(z)  # [0.9 1.7]
y_pred = np.dot(W2, z)  # 输出层结果

# 损失计算
loss = 0.5 * np.sum((y_pred - y_true) ** 2)
print(f"预测值: {y_pred}, 损失: {loss}")
# 输出:预测值: [1.9], 损失: 0.08000000000000006

2. 计算图:让计算过程一目了然

计算图 是用于可视化神经网络计算过程的工具。它用节点表示变量(如 x,h,L\mathbf{x},\mathbf{h},\mathcal{L}),用边表示操作(如矩阵乘法和激活函数)。

上述示例中前向传播过程:

  1. 隐藏层线性变换:h=W1x=[0.50.20.30.7][1.02.0]=[0.91.7]\mathbf{h} = W_1 \mathbf{x} = \begin{bmatrix} 0.5 & 0.2 \\ 0.3 & 0.7 \end{bmatrix} \begin{bmatrix} 1.0 \\ 2.0 \end{bmatrix} = \begin{bmatrix} 0.9 \\ 1.7 \end{bmatrix}
  2. 激活函数(ReLU):z=ReLU(h)=max(0,h)=[0.91.7]\mathbf{z} = \text{ReLU}(\mathbf{h}) = \max(0, \mathbf{h}) = \begin{bmatrix} 0.9 \\ 1.7 \end{bmatrix}
  3. 输出层线性变换:ypred=W2z=[0.60.8][0.91.7]=[1.9]\mathbf{y}_{\text{pred}} = W_2 \mathbf{z} = \begin{bmatrix} 0.6 & 0.8 \end{bmatrix} \begin{bmatrix} 0.9 \\ 1.7 \end{bmatrix} = \begin{bmatrix} 1.9 \end{bmatrix}
  4. 损失计算:Loss=12(ypredytrue)2=12(1.91.5)2=0.08\text{Loss} = \frac{1}{2} \sum (\mathbf{y}_{\text{pred}} - y_{\text{true}})^2 = \frac{1}{2} (1.9 - 1.5)^2 = 0.08

以下是计算图结构的具体表示:

输入层 (x1, x2)
       |
       v
[加权和 W1] ---> h1, h2 (隐藏层线性变换)
                         |
                         v
                  [激活函数 ReLU]
                         |
                         v
                  z1, z2 (隐藏层激活值)
                         |
                         v
               [加权和 W2] ---> y_pred (输出层结果)
                         |
                         v
                 [损失函数 Loss]

3. 链式法则:反向传播的核心

在深度学习中,反向传播是依赖于微积分的链式法则来计算梯度的。链式法则是一种在复合函数求导时常用的技巧。它的核心思想是,如果我们有多个函数嵌套在一起,那么求这个复合函数的导数时,需要将每个函数的导数相乘

链式法则公式:

假设有两个函数 f(x)f(x)g(x)g(x),它们组成一个复合函数 y=f(g(x))y=f(g(x)),那么链式法则告诉我们,yyxx 的导数可以按以下公式计算:

dydx=dydgdgdx\frac{dy}{dx} = \frac{dy}{dg} \cdot \frac{dg}{dx}

意思就是,我们首先计算 yygg 的导数,然后再乘以 ggxx 的导数。

为什么链式法则很重要?

在神经网络的反向传播过程中,我们不仅需要计算每一层的梯度,还需要将梯度从输出层逐步传播到输入层。每一层的输出不仅依赖于该层的输入,还可能依赖于更早一层的输入。因此,链式法则帮助我们一步步地从最终的损失函数(目标函数)回溯到每一层,计算出每一层的梯度。

示例:链式法则在前向和反向传播中的应用

假设我们有一个简单的神经网络,由两个函数组成:

  1. z=Wx+bz=W \cdot x + b,这是神经网络中线性变换的部分。
  2. y=σ(z)y=\sigma(z),这里的 σ\sigma 是激活函数(比如 ReLU、Sigmoid 等)。

假设我们要计算损失函数 L\mathcal{L} 对权重 WW 的梯度。在计算过程中,我们可以用链式法则分步计算各个部分的导数。

  1. 首先计算损失函数对输出 yy 的导数:Ly\frac{\partial \mathcal{L}}{\partial y}
  2. 然后计算输出 yyzz 的导数(因为 y=σ(z)y=\sigma(z)):yz=σ(z)\frac{\partial y}{\partial z} = \sigma'(z)
  3. 接着计算 zzWW 的导数(因为 𝑧=𝑊𝑥+b𝑧=𝑊 \cdot 𝑥 + b):zW=x\frac{\partial z}{\partial W} = x

通过链式法则,我们将这些部分的导数连接起来,最终可以得到损失函数对权重 𝑊𝑊 的总导数:

LW=LyyzzW\frac{\partial \mathcal{L}}{\partial W} = \frac{\partial \mathcal{L}}{\partial y} \cdot \frac{\partial y}{\partial z} \cdot \frac{\partial z}{\partial W}
具体示例:

假设我们有一个简单的例子,网络的输入为 x=2x=2,权重为 W=3W=3,偏置 b=1b=1,激活函数为 ReLU,即 σ(x)=max(0,z)\sigma(x)=max(0,z)

前向传播:

  1. 计算 𝑧=𝑊𝑥+𝑏=32+1=7𝑧=𝑊 \cdot 𝑥+𝑏=3⋅2+1=7
  2. 计算 𝑦=σ(𝑧)=max(0,7)=7𝑦=\sigma(𝑧)=max(0,7)=7

反向传播:

假设损失函数 L=(y1)2\mathcal{L}=(y−1)^2,目标值为 1。我们计算损失对权重 WW 的梯度。

  1. 计算损失函数对 yy 的导数:
Ly=2(y1)=2(71)=12\frac{\partial \mathcal{L}}{\partial y} = 2(y - 1) = 2(7 - 1) = 12
  1. 计算输出 yyzz 的导数:
yz=σ(z)=1(因为z=7>0)\frac{\partial y}{\partial z} = \sigma'(z) = 1 \quad (\text{因为} \, z = 7 > 0)
  1. 计算 zzWW 的导数:
zW=x=2\frac{\partial z}{\partial W} = x = 2

最后,使用链式法则将这些部分相乘,得到损失对权重 WW 的梯度:

LW=1212=24\frac{\partial \mathcal{L}}{\partial W} = 12 \cdot 1 \cdot 2 = 24

4. 反向传播(Backward Propagation):从输出到输入的梯度计算

定义:

反向传播用于计算目标函数对模型参数的梯度。它依赖微积分的链式法则,从输出层开始,逐层向输入层计算各参数的偏导数。

核心步骤:

1. 计算输出层的梯度

首先,我们需要计算输出层的权重 W2\mathbf{W}_2 对损失函数的影响。

  • L=12yy^2=12W2zy^2\mathcal{L} = \frac{1}{2} \|\mathbf{y} - \hat{\mathbf{y}}\|^2=\frac{1}{2} \|\mathbf{W}_2 \mathbf{z} - \hat{\mathbf{y}}\|^2
  • y\mathbf{y} 是真实标签(目标值),y^\hat{\mathbf{y}} 是网络预测的输出。
  • 损失函数 L\mathcal{L} 计算的是预测值和真实标签之间的差异,我们希望这个差异尽量小。

计算过程:

  • 首先,计算输出层的误差:yy^\mathbf{y} - \hat{\mathbf{y}}。这就是预测值和实际标签之间的差距。
  • 接着,我们计算梯度:LW2\frac{\partial \mathcal{L}}{\partial \mathbf{W}_2},也就是损失函数对权重 W2\mathbf{W}_2 的偏导数。这个梯度告诉我们权重 W2\mathbf{W}_2 如何变化才能减少损失。它等于输出误差和隐藏层激活值 z\mathbf{z} 的外积。

公式:

LW2=(yy^)z\frac{\partial \mathcal{L}}{\partial \mathbf{W}_2} = (\mathbf{y} - \hat{\mathbf{y}}) \mathbf{z}^\top
2. 传播到隐藏层

接下来,我们需要将误差从输出层传播回隐藏层。这个步骤是反向传播的核心,我们将计算隐藏层激活 z\mathbf{z} 对损失函数的影响。

  • 我们知道输出层的误差是 yy^\mathbf{y} - \hat{\mathbf{y}},而权重 W2\mathbf{W}_2 连接了隐藏层和输出层。
  • 使用链式法则,反向传播的梯度从输出层反向传递到隐藏层。梯度的传播公式为:
Lz=W2(yy^)\frac{\partial \mathcal{L}}{\partial \mathbf{z}} = \mathbf{W}_2^\top (\mathbf{y} - \hat{\mathbf{y}})

这个公式的含义是:损失函数对隐藏层激活值 z\mathbf{z} 的梯度,可以通过将输出层的误差乘以权重矩阵 W2\mathbf{W}_2 的转置得到。这样,我们就知道了隐藏层激活值 z\mathbf{z} 对损失函数的贡献。

3. 考虑激活函数的梯度

在反向传播时,必须考虑隐藏层的激活函数。这里我们使用的是 ReLU 激活函数,它的导数是按元素计算的。

  • ReLU 函数的导数 σ(h)\sigma'(\mathbf{h}) 是:
    • 如果 h>0\mathbf{h} > 0,则 σ(h)=1\sigma'(\mathbf{h}) = 1
    • 如果 h0\mathbf{h} \leq 0,则 σ(h)=0\sigma'(\mathbf{h}) = 0

所以,对于每个隐藏层的输出,我们需要乘以 ReLU 的导数来调整梯度。

计算过程:

  • 我们已经得到了损失函数关于 z\mathbf{z} 的梯度,接下来需要将这个梯度通过激活函数的导数传递回去。我们通过按元素相乘的方式进行操作,即:
Lh=Lzσ(h)\frac{\partial \mathcal{L}}{\partial \mathbf{h}} = \frac{\partial \mathcal{L}}{\partial \mathbf{z}} \odot \sigma'(\mathbf{h})

其中 \odot 代表逐元素乘法。通过这个步骤,我们就可以计算损失函数对隐藏层输入 h\mathbf{h} 的梯度。

4. 计算隐藏层权重的梯度

最后,我们需要计算隐藏层权重 W1\mathbf{W}_1 对损失函数的影响。这个过程与输出层类似,但它涉及到隐藏层的输入。

  • 损失函数对 W1\mathbf{W}_1 的梯度可以通过损失函数对 h\mathbf{h} 的梯度乘以输入向量 x\mathbf{x} 的转置来得到。

公式:

LW1=Lhx\frac{\partial \mathcal{L}}{\partial \mathbf{W}_1} = \frac{\partial \mathcal{L}}{\partial \mathbf{h}} \mathbf{x}^\top

这个公式的意义是,损失函数对隐藏层权重的梯度等于损失函数对隐藏层输入 h\mathbf{h} 的梯度与输入向量 x\mathbf{x} 的外积。外积的作用是将梯度与输入数据结合起来,告诉我们如何调整权重以减少损失。

示例代码:

# 反向传播
grad_y_pred = y_pred - y_true  # 输出层梯度
grad_W2 = np.dot(grad_y_pred.reshape(-1, 1), z.reshape(1, -1))  # 输出层权重梯度

grad_z = np.dot(W2.T, grad_y_pred)  # 隐藏层输出的梯度
grad_h = grad_z * relu_grad(h)  # 隐藏层中间变量的梯度
grad_W1 = np.dot(grad_h.reshape(-1, 1), x.reshape(1, -1))  # 隐藏层权重梯度

print(f"隐藏层权重梯度: {grad_W1}")
"""
隐藏层权重梯度: [[0.24 0.48]
 [0.32 0.64]]
"""
print(f"输出层权重梯度: {grad_W2}")
"""
输出层权重梯度: [[0.36 0.68]]
"""

小结

  • 前向传播: 从输入到输出,逐层计算中间变量和最终结果。
  • 反向传播: 从输出到输入,逐层计算梯度并更新参数。
  • 计算图: 为理解前向和反向过程提供了直观的依赖关系展示。
  • 训练过程: 前向传播与反向传播相辅相成,存储中间值使训练内存需求高于预测。