- 作者:老汪软件技巧
- 发表时间:2024-12-04 11:05
- 浏览量:
在深度学习中,训练神经网络离不开两个重要步骤:前向传播(Forward Propagation)和反向传播(Backward Propagation)。这两个步骤通过构建和遍历计算图,帮助我们高效地计算目标函数并更新模型参数。
接下来,我们将通过数学推导和示例,介绍这两个核心概念,并附加相应的代码示例加深理解。
1. 前向传播(Forward Propagation):从输入到输出的计算过程定义:
前向传播是指从输入层开始,按顺序依次计算神经网络各层输出的过程,直到最终输出结果(即预测值)。这一过程依赖网络结构和参数。
假设我们有一个简单的单隐藏层神经网络,其中:
数学公式:
隐藏层的中间变量:h=W1x\mathbf{h} = \mathbf{W}_1 \mathbf{x}h=W1x
隐藏层的激活值:z=σ(h)\mathbf{z} = \sigma(\mathbf{h})z=σ(h)
输出层的结果:y=W2z\mathbf{y} = \mathbf{W}_2 \mathbf{z}y=W2z
如果我们用均方误差(MSE)作为损失函数,其公式为:
L=12∥y−y^∥2\mathcal{L} = \frac{1}{2} \|\mathbf{y} - \hat{\mathbf{y}}\|^2L=21∥y−y^∥2
其中,y^\mathbf{\hat{y}}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}x,h,L),用边表示操作(如矩阵乘法和激活函数)。
上述示例中前向传播过程:隐藏层线性变换: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}h=W1x=[0.50.30.20.7][1.02.0]=[0.91.7]激活函数(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}z=ReLU(h)=max(0,h)=[0.91.7]输出层线性变换: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}ypred=W2z=[0.60.8][0.91.7]=[1.9]损失计算:Loss=12∑(ypred−ytrue)2=12(1.9−1.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.08Loss=21∑(ypred−ytrue)2=21(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. 反向传播(Backward Propagation):从输出到输入的梯度计算定义:
反向传播用于计算目标函数对模型参数的梯度。它依赖微积分的链式法则,从输出层开始,逐层向输入层计算各参数的偏导数。
核心步骤:1. 计算输出层的梯度
首先,我们需要计算输出层的权重 W2\mathbf{W}_2W2 对损失函数的影响。
计算过程:
公式:
∂L∂W2=(y−y^)z⊤\frac{\partial \mathcal{L}}{\partial \mathbf{W}_2} = (\mathbf{y} - \hat{\mathbf{y}}) \mathbf{z}^\top∂W2∂L=(y−y^)z⊤
2. 传播到隐藏层
接下来,我们需要将误差从输出层传播回隐藏层。这个步骤是反向传播的核心,我们将计算隐藏层激活 z\mathbf{z}z 对损失函数的影响。
∂L∂z=W2⊤(y−y^)\frac{\partial \mathcal{L}}{\partial \mathbf{z}} = \mathbf{W}_2^\top (\mathbf{y} - \hat{\mathbf{y}})∂z∂L=W2⊤(y−y^)
这个公式的含义是:损失函数对隐藏层激活值 z\mathbf{z}z 的梯度,可以通过将输出层的误差乘以权重矩阵 W2\mathbf{W}_2W2 的转置得到。这样,我们就知道了隐藏层激活值 z\mathbf{z}z 对损失函数的贡献。
3. 考虑激活函数的梯度
在反向传播时,必须考虑隐藏层的激活函数。这里我们使用的是 ReLU 激活函数,它的导数是按元素计算的。
所以,对于每个隐藏层的输出,我们需要乘以 ReLU 的导数来调整梯度。
计算过程:
∂L∂h=∂L∂z⊙σ′(h)\frac{\partial \mathcal{L}}{\partial \mathbf{h}} = \frac{\partial \mathcal{L}}{\partial \mathbf{z}} \odot \sigma'(\mathbf{h})∂h∂L=∂z∂L⊙σ′(h)
其中 ⊙\odot⊙ 代表逐元素乘法。通过这个步骤,我们就可以计算损失函数对隐藏层输入 h\mathbf{h}h 的梯度。
4. 计算隐藏层权重的梯度
最后,我们需要计算隐藏层权重 W1\mathbf{W}_1W1 对损失函数的影响。这个过程与输出层类似,但它涉及到隐藏层的输入。
公式:
∂L∂W1=∂L∂hx⊤\frac{\partial \mathcal{L}}{\partial \mathbf{W}_1} = \frac{\partial \mathcal{L}}{\partial \mathbf{h}} \mathbf{x}^\top∂W1∂L=∂h∂Lx⊤
这个公式的意义是,损失函数对隐藏层权重的梯度等于损失函数对隐藏层输入 h\mathbf{h}h 的梯度与输入向量 x\mathbf{x}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]]
"""
小结