上一篇:[[SwiGLU]] 下一篇:[[SGD]]
我们构建一个典型神经网络训练例子,包含非线性激活函数(tanh)和softmax输出,完整展示一次前向传播、反向传播和参数更新过程,包括所有中间步骤、公式和数值。
import torch
import torch.nn as nn
import torch.optim as optim
# 设置随机种子以确保结果可复现
torch.manual_seed(0)
# 1. 构建神经网络结构
class SimpleNN(nn.Module):
def __init__(self):
super(SimpleNN, self).__init__()
# 隐藏层:2个输入,2个神经元
self.fc1 = nn.Linear(2, 2)
# 输出层:2个神经元,分类为2类
self.fc2 = nn.Linear(2, 2)
def forward(self, x):
# 前向传播
x = torch.tanh(self.fc1(x)) # 使用tanh激活函数
x = self.fc2(x) # 不使用激活函数,softmax会在损失计算时进行
return x
# 2. 准备数据
# 输入数据
x_data = torch.tensor([[1.0, 2.0]], dtype=torch.float32)
# 标签(one-hot encoding)
y_data = torch.tensor([[1.0, 0.0]], dtype=torch.float32) # 类别1
# 3. 初始化神经网络和损失函数
model = SimpleNN()
# 交叉熵损失函数,softmax会在损失内部计算
criterion = nn.CrossEntropyLoss()
# 4. 使用优化器(SGD和Adam)
optimizer_sgd = optim.SGD(model.parameters(), lr=0.1)
optimizer_adam = optim.Adam(model.parameters(), lr=0.1)
# 5. 训练模型并查看输出
# 训练一轮,使用SGD优化器
optimizer_sgd.zero_grad()
output_sgd = model(x_data)
loss_sgd = criterion(output_sgd, torch.argmax(y_data, dim=1))
loss_sgd.backward()
optimizer_sgd.step()
print("SGD 更新后的模型参数:")
print("fc1.weight:", model.fc1.weight)
print("fc1.bias:", model.fc1.bias)
print("fc2.weight:", model.fc2.weight)
print("fc2.bias:", model.fc2.bias)
# 训练一轮,使用Adam优化器
optimizer_adam.zero_grad()
output_adam = model(x_data)
loss_adam = criterion(output_adam, torch.argmax(y_data, dim=1))
loss_adam.backward()
optimizer_adam.step()
print("\nAdam 更新后的模型参数:")
print("fc1.weight:", model.fc1.weight)
print("fc1.bias:", model.fc1.bias)
print("fc2.weight:", model.fc2.weight)
print("fc2.bias:", model.fc2.bias)
🧠 网络结构
一个两层神经网络,用于分类任务(2个类别):
输入层(特征):
- 输入向量 $\mathbf{x} = \begin{bmatrix} x_1 \ x_2 \end{bmatrix} \in \mathbb{R}^2$
隐藏层(2个神经元):
- 激活函数:$\tanh$
- 权重矩阵 $W^{[1]} \in \mathbb{R}^{2 \times 2}$,偏置 $\mathbf{b}^{[1]} \in \mathbb{R}^2$
输出层(2个神经元):
- 激活函数:softmax
- 权重矩阵 $W^{[2]} \in \mathbb{R}^{2 \times 2}$,偏置 $\mathbf{b}^{[2]} \in \mathbb{R}^2$
🧾 初始化与样本数据
输入:
$$ \mathbf{x} = \begin{bmatrix} 1.0 \ 2.0 \end{bmatrix} $$
标签:
- 类别 1:目标标签 one-hot 表示: $$ \mathbf{y} = \begin{bmatrix} 1 \ 0 \end{bmatrix} $$
初始化参数(设定为简单可计算数):
$$ W^{[1]} = \begin{bmatrix} 0.1 & 0.2 \ 0.3 & 0.4 \end{bmatrix}, \quad \mathbf{b}^{[1]} = \begin{bmatrix} 0.0 \ 0.0 \end{bmatrix} $$ $$ W^{[2]} = \begin{bmatrix} 0.5 & 0.6 \ 0.7 & 0.8 \end{bmatrix}, \quad \mathbf{b}^{[2]} = \begin{bmatrix} 0.0 \ 0.0 \end{bmatrix} $$
✅ 第一步:前向传播(Forward Pass)
1.1 基础公式
$$ \mathbf{z}^{[1]} = W^{[1]} \cdot \mathbf{x} + \mathbf{b}^{[1]} , \text{ } \mathbf{a}^{[1]} = f^{[1]}(\mathbf{z}^{[1]}), \text{ }\ f^{[1]} \text{ here is tanh.} $$ $$ \mathbf{z}^{[2]} = W^{[2]} \cdot \mathbf{a}^{[1]} + \mathbf{b}^{[2]} , \text{ } \mathbf{a}^{[2]} = f^{[2]}(\mathbf{z}^{[2]}), \text{ }\ f^{[2]} \text{ here is softmax.} $$
$$ \hat{\mathbf{y}} = \text{softmax}(\mathbf{z}^{[2]}) \text{, }\ \hat{\mathbf{y}} \text{ is used in the place of}\ \mathbf{a}^{[2]} \text{ to represent the estimated class.} $$
$$ L = - \sum y_i \log(\hat{y}_i) $$
1.2 隐藏层计算:
$$ \mathbf{z}^{[1]} = W^{[1]} \cdot \mathbf{x} + \mathbf{b}^{[1]} = \begin{bmatrix} 0.1 & 0.2 \ 0.3 & 0.4 \end{bmatrix} \begin{bmatrix} 1.0 \ 2.0 \end{bmatrix}
\begin{bmatrix} 0.1 + 0.4 \ 0.3 + 0.8 \end{bmatrix}
\begin{bmatrix} 0.5 \ 1.1 \end{bmatrix} $$
$$ \mathbf{a}^{[1]} = \tanh(\mathbf{z}^{[1]}) = \begin{bmatrix} \tanh(0.5) \ \tanh(1.1) \end{bmatrix} \approx \begin{bmatrix} 0.4621 \ 0.8005 \end{bmatrix} $$
1.3 输出层线性计算:
$$ \mathbf{z}^{[2]} = W^{[2]} \cdot \mathbf{a}^{[1]} + \mathbf{b}^{[2]} = \begin{bmatrix} 0.5 & 0.6 \ 0.7 & 0.8 \end{bmatrix} \begin{bmatrix} 0.4621 \ 0.8005 \end{bmatrix}
\begin{bmatrix} 0.5 \cdot 0.4621 + 0.6 \cdot 0.8005 \ 0.7 \cdot 0.4621 + 0.8 \cdot 0.8005 \end{bmatrix} \approx \begin{bmatrix} 0.23105 + 0.4803 \ 0.32347 + 0.6404 \end{bmatrix}
\begin{bmatrix} 0.7114 \ 0.9639 \end{bmatrix} $$
1.4 softmax 输出:
$$ \hat{\mathbf{y}} = \text{softmax}(\mathbf{z}^{[2]}) = \frac{e^{\mathbf{z}^{[2]}}}{\sum e^{\mathbf{z}^{[2]}}} $$
计算: $$ e^{0.7114} \approx 2.037, \quad e^{0.9639} \approx 2.622 $$ $$ \hat{\mathbf{y}} = \begin{bmatrix} \frac{2.037}{2.037 + 2.622} \ \frac{2.622}{2.037 + 2.622} \end{bmatrix} \approx \begin{bmatrix} 0.4378 \ 0.5622 \end{bmatrix} $$
📉 第二步:计算损失(交叉熵)
公式: $$ L = - \sum y_i \log(\hat{y}_i) $$
计算: $$ \mathbf{y} = \begin{bmatrix} 1 \ 0 \end{bmatrix}, \quad \hat{\mathbf{y}} = \begin{bmatrix} 0.4378 \ 0.5622 \end{bmatrix} $$
$$ L = - \log(0.4378) \approx 0.825 $$
🔁 第三步:反向传播(完整步骤)
对于任何一层(包括第一层),误差项 $\delta^{[l]}$ 定义是:
$$
\delta^{[l]} := \frac{\partial L}{\partial \mathbf{z}^{[l]}}
$$
即,损失函数 $L$ 对该层线性输出 $\mathbf{z}^{[l]}$ 的偏导数。
即使这是一张非常深的网络,从最终输出层一路往回传播,第一层的误差项依然是整个损失函数对该层的影响程度。
想象你训练一个 10 层网络,输出预测有误。你回头问第一层:“你对这个错误要负多少责任?”
这时候 $\delta^{[1]}$ 就是这个“责任值”。
数学上更严谨地说:
$$ \delta^{[1]} = \frac{\partial L}{\partial \mathbf{z}^{[1]}} = \frac{\partial L}{\partial \mathbf{z}^{[L]}} \cdot \frac{\partial \mathbf{z}^{[L]}}{\partial \mathbf{z}^{[L-1]}} \cdot \cdots \cdot \frac{\partial \mathbf{z}^{[2]}}{\partial \mathbf{z}^{[1]}} $$
这个链条贯穿整个网络,把损失函数的信息层层反向传导回第一层。
有了$\delta^{[l]} = \frac{\partial L}{\partial \mathbf{z}^{[l]}}$这样的定义,结合$\mathbf{z}^{[l]} = \mathbf{a}^{[l-1]} \cdot W^{[l]} + \mathbf{b}^{[l]}$就可以计算出$L$对于任意一层的$W$和$\mathbf{b}$的偏导数:
$$ \frac{\partial L}{\partial W^{[l]}} = \ \frac{\partial L}{\partial \mathbf{z}^{[l]}} \cdot \ \frac{\partial \mathbf{z}^{[l]}}{\partial W^{[l]}} = \ \delta^{[l]} \cdot {\mathbf{a}^{[l-1]}}^T $$ $$ \frac{\partial L}{\partial \mathbf{b}^{[l]}} = \ \frac{\partial L}{\partial \mathbf{z}^{[l]}} \cdot \ \frac{\partial \mathbf{z}^{[l]}}{\partial \mathbf{b}^{[l]}} = \ \delta^{[l]} $$
3.1 输出层
公式根据 [[#1.1 基础公式 |基础公式]] 部分反向求导: $$ \delta^{[2]} = \frac{\partial L}{\partial \mathbf{z}^{[2]}} = \hat{\mathbf{y}} - \mathbf{y} $$ (推导过程在关于Softmax导数的计算过程部分) $$ \frac{\partial L}{\partial W^{[2]}} = \frac{\partial L}{\partial z^{[2]}} \cdot \frac{\partial z^{[2]}}{\partial W^{[2]}} = \delta^{[2]} \cdot (\mathbf{a}^{[1]})^T , \ \frac{\partial L}{\partial b^{[2]}} = \frac{\partial L}{\partial z^{[2]}} \cdot \frac{\partial z^{[2]}}{\partial b^{[2]}} = \delta^{[2]} $$
计算:
交叉熵 + softmax 导数: $$ \delta^{[2]} = \frac{\partial L}{\partial z^{[2]}} = \hat{\mathbf{y}} - \mathbf{y} = \begin{bmatrix} 0.4378 - 1 \ 0.5622 - 0 \end{bmatrix} = \begin{bmatrix} -0.5622 \ 0.5622 \end{bmatrix} $$
梯度 w.r.t 参数: $$ \frac{\partial L}{\partial W^{[2]}} = \delta^{[2]} \cdot \left(\mathbf{a}^{[1]}\right)^T = \begin{bmatrix} -0.5622 \ 0.5622 \end{bmatrix} \begin{bmatrix} 0.4621 & 0.8005 \end{bmatrix} = \begin{bmatrix} -0.260 & -0.450 \ 0.260 & 0.450 \end{bmatrix} $$
$$ \frac{\partial L}{\partial \mathbf{b}^{[2]}} = \delta^{[2]} = \begin{bmatrix} -0.5622 \ 0.5622 \end{bmatrix} $$
3.2 隐藏层
反向传播的目标是计算损失函数 $L$ 关于各层参数的梯度,关键在于先求出每层的“误差项”。定义误差项$\delta^{[l]} = \frac{\partial L}{\partial \mathbf{z}^{[l]}}$,对于输出层 $L$,我们可以直接计算:
$$ \delta^{[l]} = \frac{\partial L}{\partial \mathbf{z}^{[l]}} = \ \frac{\partial L}{\partial \mathbf{z}^{[l+1]}} \cdot \ \frac{\partial \mathbf{z}^{[l+1]}}{\partial \mathbf{a}^{[l]}} \cdot \ \frac{\partial \mathbf{a}^{[l]}}{\partial \mathbf{z}^{[l]}} $$
我们知道:
$$ \frac{\partial L}{\partial \mathbf{z}^{[l+1]}} = \delta^{[l+1]} \tag{根据delta定义} $$ $$ \frac{\partial \mathbf{z}^{[l+1]}}{\partial \mathbf{a}^{[l]}} = (W^{[l+1]})^T \tag{a = a’W + b} $$ $$ \frac{\partial \mathbf{a}^{[l]}}{\partial \mathbf{z}^{[l]}} = f’(\mathbf{z}^{[l]}) \tag{激活函数} $$ 得到通用公式:
$$ \delta^{[l]} = \frac{\partial L}{\partial \mathbf{z}^{[l]}} = \left( (W^{[l+1]})^T \cdot \delta^{[l+1]} \right) \odot f’(\mathbf{z}^{[l]}) $$ 那么此处:
$$ \delta^{[1]} = \left( (W^{[2]})^T \cdot \delta^{[2]} \right) \odot \tanh’(\mathbf{z}^{[1]}) $$
计算: $$ (W^{[2]})^T = \begin{bmatrix} 0.5 & 0.7 \ 0.6 & 0.8 \end{bmatrix} $$ $$ (W^{[2]})^T \cdot \delta^{[2]} = \begin{bmatrix} 0.5 \cdot -0.5622 + 0.7 \cdot 0.5622 \ 0.6 \cdot -0.5622 + 0.8 \cdot 0.5622 \end{bmatrix} \approx \begin{bmatrix} 0.1124 \ 0.1124 \end{bmatrix} $$
$$ \tanh’(\mathbf{z}^{[1]}) = 1 - \tanh^2(\mathbf{z}^{[1]}) \Rightarrow \tanh’(\mathbf{z}^{[1]}) = \tanh’(\begin{bmatrix} 0.5 \ 1.1 \end{bmatrix}) = \begin{bmatrix} 0.7864 \ 0.3592 \end{bmatrix} $$ $$ \delta^{[1]} = \left( (W^{[2]})^T \cdot \delta^{[2]} \right) \odot \tanh’(\mathbf{z}^{[1]}) = \begin{bmatrix} 0.1124 \cdot 0.7864 \ 0.1124 \cdot 0.3592 \end{bmatrix} \approx \begin{bmatrix} 0.0884 \ 0.0404 \end{bmatrix} $$ $$ \frac{\partial L}{\partial W^{[1]}} = \delta^{[1]} \cdot \mathbf{x}^T = \begin{bmatrix} 0.0884 \ 0.0404 \end{bmatrix} \begin{bmatrix} 1.0 & 2.0 \end{bmatrix}
\begin{bmatrix} 0.0884 & 0.1768 \ 0.0404 & 0.0808 \end{bmatrix} $$ $$ \frac{\partial L}{\partial \mathbf{b}^{[1]}} = \delta^{[1]} = \begin{bmatrix} 0.0884 \ 0.0404 \end{bmatrix} $$
🧮 第四步:参数更新(学习率 $\eta = 0.1$)
更新 $W^{[2]}, b^{[2]}$:
$$ W^{[2]} := W^{[2]} - \eta \cdot \frac{\partial L}{\partial W^{[2]}} \Rightarrow \begin{bmatrix} 0.5 & 0.6 \ 0.7 & 0.8 \end{bmatrix}
0.1 \cdot \begin{bmatrix} -0.260 & -0.450 \ 0.260 & 0.450 \end{bmatrix}
\begin{bmatrix} 0.526 & 0.645 \ 0.674 & 0.755 \end{bmatrix} $$
$$ b^{[2]} := b^{[2]} - \eta \cdot \frac{\partial L}{\partial b^{[1]}} = \begin{bmatrix} 0 \ 0 \end{bmatrix}
0.1 \cdot \begin{bmatrix} -0.5622 \ 0.5622 \end{bmatrix}
\begin{bmatrix} 0.05622 \ -0.05622 \end{bmatrix} $$
更新 $W^{[1]}, b^{[1]}$:
$$ W^{[1]} := W^{[1]} - \eta \cdot \frac{\partial L}{\partial W^{[1]}} = \begin{bmatrix} 0.09116 & 0.18232 \ 0.29596 & 0.39192 \end{bmatrix} $$
$$ b^{[1]} := b^{[1]} - \eta \cdot \frac{\partial L}{\partial b^{[1]}} = \begin{bmatrix} 0 \ 0 \end{bmatrix}
0.1 \cdot \begin{bmatrix} 0.0884 \ 0.0404 \end{bmatrix}
\begin{bmatrix} -0.0884 \ -0.0404 \end{bmatrix} $$
关于Softmax导数的计算过程
1. 理解Softmax函数
首先,我们需要明确什么是Softmax函数。Softmax函数通常用于多分类问题的输出层,它将一个实数向量转换为一个概率分布。具体来说,对于一个输入向量 $\mathbf{z} = [z_1, z_2, \ldots, z_n]$,Softmax函数的第 $i$ 个输出为:
$$ a_i = \text{softmax}(z_i) = \frac{e^{z_i}}{\sum_{j=1}^{n} e^{z_j}} $$
其中,$a_i$ 表示第 $i$ 个类别的预测概率,满足 $\sum_{i=1}^{n} a_i = 1$ 且 $a_i > 0$。
2. Softmax的导数
在反向传播中,我们需要计算损失函数对每个 $z_i$ 的导数。假设我们使用交叉熵损失函数(这是与Softmax搭配的常见损失函数),那么我们需要计算 $\frac{\partial a_i}{\partial z_j}$ 对于所有的 $i$ 和 $j$。
2.1 情况一:$i = j$
首先考虑 $\frac{\partial a_i}{\partial z_i}$:
$$ a_i = \frac{e^{z_i}}{\sum_{k} e^{z_k}} = \frac{e^{z_i}}{S}, \quad \text{其中} \quad S = \sum_{k} e^{z_k} $$
使用商的导数法则:
$$ \frac{\partial a_i}{\partial z_i} = \frac{e^{z_i} \cdot S - e^{z_i} \cdot e^{z_i}}{S^2} = \frac{e^{z_i}(S - e^{z_i})}{S^2} = \frac{e^{z_i}}{S} \cdot \frac{S - e^{z_i}}{S} = a_i (1 - a_i) $$
2.2 情况二:$i \neq j$
现在考虑 $\frac{\partial a_i}{\partial z_j}$:
$$ a_i = \frac{e^{z_i}}{S} $$
对 $z_j$ 求导:
$$ \frac{\partial a_i}{\partial z_j} = \frac{0 \cdot S - e^{z_i} \cdot e^{z_j}}{S^2} = \frac{-e^{z_i} e^{z_j}}{S^2} = -\frac{e^{z_i}}{S} \cdot \frac{e^{z_j}}{S} = -a_i a_j $$
2.3 综合
因此,Softmax函数的导数可以表示为:
$$ \frac{\partial a_i}{\partial z_j} = \begin{cases} a_i (1 - a_j) & \text{if } i = j, \ -a_i a_j & \text{if } i \neq j. \end{cases} $$
或者更简洁地:
$$ \frac{\partial a_i}{\partial z_j} = a_i (\delta_{ij} - a_j) $$
其中,$\delta_{ij}$ 是Kronecker delta,当 $i = j$ 时为1,否则为0。
3. 结合交叉熵损失
通常,Softmax与交叉熵损失结合使用。交叉熵损失定义为:
$$ L = -\sum_{k} y_k \log a_k $$
其中,$\mathbf{y}$ 是真实的one-hot标签向量。我们需要计算 $\frac{\partial L}{\partial z_i}$:
$$ \frac{\partial L}{\partial z_i} = \sum_{k} \frac{\partial L}{\partial a_k} \cdot \frac{\partial a_k}{\partial z_i} $$
计算 $\frac{\partial L}{\partial a_k}$:
$$ \frac{\partial L}{\partial a_k} = -\frac{\partial \sum_{k} y_k \cdot { \log a_k}}{\partial a_k} = -\frac{y_k}{a_k} $$
因此:
$$ \frac{\partial L}{\partial z_i} = \sum_{k} -\frac{y_k}{a_k} \cdot \frac{\partial a_k}{\partial z_i} = -\sum_{k} \frac{y_k}{a_k} \cdot a_k (\delta_{ki} - a_i) = -\sum_{k} y_k (\delta_{ki} - a_i) $$
展开求和:
$$ \frac{\partial L}{\partial z_i} = -\left( \sum_{k} y_k \delta_{ki} - \sum_{k} y_k a_i \right) = -\left( y_i - a_i \sum_{k} y_k \right) $$
因为 $\mathbf{y}$ 是one-hot向量,$\sum_{k} y_k = 1$,所以:
$$ \frac{\partial L}{\partial z_i} = -(y_i - a_i) = a_i - y_i $$
4. 总结
Softmax的导数: $$ \frac{\partial a_i}{\partial z_j} = a_i (\delta_{ij} - a_j) $$
结合交叉熵损失的梯度: $$ \frac{\partial L}{\partial z_i} = a_i - y_i $$
这个结果非常简洁,意味着在反向传播时,我们只需要计算Softmax的输出 $\mathbf{a}$ 与真实标签 $\mathbf{y}$ 的差值,就可以得到损失函数对输入 $\mathbf{z}$ 的梯度。
5. 示例
假设我们有:
$$ \mathbf{z} = [z_1, z_2, z_3] = [2, 1, 0.1] $$
计算Softmax:
$$ S = e^{2} + e^{1} + e^{0.1} \approx 7.389 + 2.718 + 1.105 \approx 11.212 $$ $$ a_1 = \frac{e^{2}}{S} \approx \frac{7.389}{11.212} \approx 0.659 $$ $$ a_2 = \frac{e^{1}}{S} \approx \frac{2.718}{11.212} \approx 0.242 $$ $$ a_3 = \frac{e^{0.1}}{S} \approx \frac{1.105}{11.212} \approx 0.099 $$
假设真实标签 $\mathbf{y} = [1, 0, 0]$(即第一个类别),则:
$$ \frac{\partial L}{\partial \mathbf{z}} = \mathbf{a} - \mathbf{y} \approx [0.659 - 1, 0.242 - 0, 0.099 - 0] = [-0.341, 0.242, 0.099] $$
6. 为什么这个结果合理?
- 对于正确类别($y_i = 1$),梯度为 $a_i - 1$。因为 $a_i$ 是预测概率,我们希望它接近1,所以梯度为负,促使 $z_i$ 增加。
- 对于其他类别($y_j = 0$),梯度为 $a_j$。我们希望 $a_j$ 接近0,所以梯度为正,促使 $z_j$ 减少。
7. 数值稳定性
在实际实现中,为了避免数值溢出(因为指数函数增长很快),通常会进行如下调整:
$$ \text{softmax}(z_i) = \frac{e^{z_i - \max(\mathbf{z})}}{\sum_{j} e^{z_j - \max(\mathbf{z})}} $$
减去最大值不会改变Softmax的输出,因为:
$$ \frac{e^{z_i - c}}{\sum_{j} e^{z_j - c}} = \frac{e^{z_i} / e^{c}}{\sum_{j} e^{z_j} / e^{c}} = \frac{e^{z_i}}{\sum_{j} e^{z_j}} $$
9. 验证
计算Jacobian矩阵:
$$ \frac{\partial a_1}{\partial z_1} = a_1 (1 - a_1) \approx 0.659 \times 0.341 \approx 0.225 $$ $$ \frac{\partial a_1}{\partial z_2} = -a_1 a_2 \approx -0.659 \times 0.242 \approx -0.159 $$ $$ \frac{\partial a_1}{\partial z_3} = -a_1 a_3 \approx -0.659 \times 0.099 \approx -0.065 $$
类似地计算其他部分,应与代码输出一致。
10. 结论
Softmax函数的导数可以通过以下方式计算:
对于单个输出 $a_i$ 对输入 $z_j$ 的偏导: $$ \frac{\partial a_i}{\partial z_j} = a_i (\delta_{ij} - a_j) $$
当与交叉熵损失结合时,损失对输入的梯度为: $$ \frac{\partial L}{\partial z_i} = a_i - y_i $$
这种简洁的形式使得在神经网络的反向传播中计算梯度非常高效。
🧠 第二轮训练(基于更新后的参数)
我们在第一轮训练后参数已更新,现在用相同的输入 $\mathbf{x} = [1.0, 2.0]$ 进行第二轮完整训练,依旧手动执行前向传播 → 损失计算 → 反向传播 → 梯度更新,然后对比 SGD 与 Adam。
🔄 当前参数(第1轮之后)
隐藏层:
$$ W^{[1]} = \begin{bmatrix} 0.09116 & 0.18232 \ 0.29596 & 0.39192 \end{bmatrix}, \quad \mathbf{b}^{[1]} = \begin{bmatrix} -0.00884 \ -0.00404 \end{bmatrix} $$
输出层:
$$ W^{[2]} = \begin{bmatrix} 0.526 & 0.645 \ 0.674 & 0.755 \end{bmatrix}, \quad \mathbf{b}^{[2]} = \begin{bmatrix} 0.05622 \ -0.05622 \end{bmatrix} $$
✅ 第二轮前向传播
1. 隐藏层线性组合:
$$ \mathbf{z}^{[1]} = W^{[1]} \cdot \mathbf{x} + \mathbf{b}^{[1]} = \begin{bmatrix} 0.09116 & 0.18232 \ 0.29596 & 0.39192 \end{bmatrix} \begin{bmatrix} 1.0 \ 2.0 \end{bmatrix} + \begin{bmatrix} -0.00884 \ -0.00404 \end{bmatrix} $$
$$
\begin{bmatrix} 0.09116 + 0.36464 - 0.00884 \ 0.29596 + 0.78384 - 0.00404 \end{bmatrix}
\begin{bmatrix} 0.447 \ 1.0758 \end{bmatrix} $$
$$ \mathbf{a}^{[1]} = \tanh(\mathbf{z}^{[1]}) \approx \begin{bmatrix} \tanh(0.447) \ \tanh(1.0758) \end{bmatrix} \approx \begin{bmatrix} 0.4196 \ 0.7912 \end{bmatrix} $$
2. 输出层线性组合:
$$ \mathbf{z}^{[2]} = W^{[2]} \cdot \mathbf{a}^{[1]} + \mathbf{b}^{[2]} = \begin{bmatrix} 0.526 & 0.645 \ 0.674 & 0.755 \end{bmatrix} \begin{bmatrix} 0.4196 \ 0.7912 \end{bmatrix} + \begin{bmatrix} 0.05622 \ -0.05622 \end{bmatrix} $$
$$
\begin{bmatrix} 0.2207 + 0.5103 + 0.0562 \ 0.2827 + 0.5974 - 0.0562 \end{bmatrix}
\begin{bmatrix} 0.7872 \ 0.8239 \end{bmatrix} $$
3. softmax 预测:
$$ \hat{\mathbf{y}} = \text{softmax}(\mathbf{z}^{[2]}) = \frac{e^{\mathbf{z}^{[2]}}}{\sum e^{\mathbf{z}^{[2]}}} $$
$$ e^{0.7872} \approx 2.197, \quad e^{0.8239} \approx 2.279 \Rightarrow \text{sum} = 4.476 $$
$$ \hat{\mathbf{y}} = \begin{bmatrix} 2.197 / 4.476 \ 2.279 / 4.476 \end{bmatrix}
\begin{bmatrix} 0.4908 \ 0.5092 \end{bmatrix} $$
📉 损失函数(交叉熵)
$$ L = -\log(\hat{y}_1) = -\log(0.4908) \approx 0.711 $$
🔁 第二轮反向传播
输出层误差:
$$ \delta^{[2]} = \hat{\mathbf{y}} - \mathbf{y} = \begin{bmatrix} -0.5092 \ 0.5092 \end{bmatrix} $$
$$ \frac{\partial L}{\partial W^{[2]}} = \delta^{[2]} \cdot (\mathbf{a}^{[1]})^T = \begin{bmatrix} -0.2138 & -0.4028 \ 0.2138 & 0.4028 \end{bmatrix} $$
$$ \frac{\partial L}{\partial \mathbf{b}^{[2]}} = \delta^{[2]} = \begin{bmatrix} -0.5092 \ 0.5092 \end{bmatrix} $$
反传到隐藏层:
$$ (W^{[2]})^T \cdot \delta^{[2]} \approx \begin{bmatrix} 0.526 & 0.674 \ 0.645 & 0.755 \end{bmatrix} ^T \cdot \begin{bmatrix} -0.5092 \ 0.5092 \end{bmatrix} \Rightarrow \begin{bmatrix} (0.526)(-0.5092) + (0.674)(0.5092) \ (0.645)(-0.5092) + (0.755)(0.5092) \end{bmatrix} \approx \begin{bmatrix} 0.0754 \ 0.0561 \end{bmatrix} $$
$$ \tanh’(z^{[1]}) = 1 - \tanh^2(z^{[1]}) \approx \begin{bmatrix} 1 - 0.4196^2 \ 1 - 0.7912^2 \end{bmatrix} \approx \begin{bmatrix} 0.8239 \ 0.3739 \end{bmatrix} $$
$$ \mathbf{b} = \delta^{[1]} = (W^{[2]})^T \cdot \delta^{[2]} \odot \tanh’(z^{[1]}) = \ \begin{bmatrix} 0.0754 \cdot 0.8239 \ 0.0561 \cdot 0.3739 \end{bmatrix} \approx \begin{bmatrix} 0.0621 \ 0.0210 \end{bmatrix} $$ $$ \frac{\partial L}{\partial W^{[1]}} = \delta^{[1]} \cdot \mathbf{x}^T = \begin{bmatrix} 0.0621 \ 0.0210 \end{bmatrix} \begin{bmatrix} 1.0 & 2.0 \end{bmatrix}
\begin{bmatrix} 0.0621 & 0.1242 \ 0.0210 & 0.0420 \end{bmatrix} $$
🔧 参数更新对比(SGD vs Adam)
✅ SGD 更新:
学习率 $\eta = 0.1$,直接更新:
$$ W^{[2]} := W^{[2]} - 0.1 \cdot \frac{\partial L}{\partial W^{[2]}} \quad \Rightarrow W^{[2]}_{\text{new}} \approx \begin{bmatrix} 0.5474 & 0.6853 \ 0.6526 & 0.7147 \end{bmatrix} $$
$$ W^{[1]}_{\text{new}} \approx \begin{bmatrix} 0.08495 & 0.16988 \ 0.29386 & 0.38772 \end{bmatrix} $$
✅ Adam 更新(基于前一轮梯度,手动计算第2步)
Adam 参数:$\beta_1 = 0.9, \beta_2 = 0.999, \epsilon = 10^{-8}$
定义第1轮梯度 $g_1$、第2轮梯度 $g_2$,执行:
- 更新一阶动量:
$$ m_t = \beta_1 m_{t-1} + (1 - \beta_1) g_t $$
- 更新二阶动量:
$$ v_t = \beta_2 v_{t-1} + (1 - \beta_2) g_t^2 $$
- 偏差修正:
$$ \hat{m}_t = \frac{m_t}{1 - \beta_1^t}, \quad \hat{v}_t = \frac{v_t}{1 - \beta_2^t} $$
- 更新参数:
$$ \theta := \theta - \eta \cdot \frac{\hat{m}_t}{\sqrt{\hat{v}_t} + \epsilon} $$
🧮 为保持精确,这部分计算建议用代码执行,因为涉及到前后梯度积累,平方与根号传播(数值计算非常繁琐)。如果你愿意,我可以在下一步将 Adam 更新过程写成 Python 代码,用实际数值跑出更新结果。
📊 SGD vs Adam 简要对比(在此例中)
| 项目 | SGD | Adam |
|---|---|---|
| 学习率调节 | 固定 | 每个参数自适应调整 |
| 动量记忆 | 无 | 使用指数滑动平均(避免震荡) |
| 收敛速度 | 较慢,易受梯度影响 | 通常更快,初期更稳定 |
| 实现复杂度 | 简单 | 需要跟踪 $m, v$,计算偏差修正 |
| 本例中表现 | 收敛但偏移较大 | 可以避免 overshoot(如果正确初始化并运行多轮) |
🌱 所有参数的更新是基于同一个正向传播的结果
在一次训练迭代中(一次 forward-backward pass):
- 你先进行正向传播,用当前的 $W^{[1]}, b^{[1]}, W^{[2]}, b^{[2]}$ 计算出输出 $\hat{y}$。
- 然后根据 $\hat{y}$ 和真实标签 $y$ 计算损失 $L$。
- 然后再从输出层往回,通过反向传播依次算出每层的梯度 $\frac{\partial L}{\partial W^{[2]}}$、$\frac{\partial L}{\partial W^{[1]}}$,都是基于这次 forward 的 $L$。
- 最后,在这一次迭代中一起更新所有参数(如用 SGD):
$$ W^{[2]} \leftarrow W^{[2]} - \eta \cdot \frac{\partial L}{\partial W^{[2]}}, \quad W^{[1]} \leftarrow W^{[1]} - \eta \cdot \frac{\partial L}{\partial W^{[1]}} $$
1. 确实,更新 $W^{[2]}, b^{[2]}$ 后,当前的 $a^{[1]}$ 下,loss 会变小。
- 第二层的参数是直接作用于最终输出的,它们对损失函数的影响是最直接、最强的。
- 所以只更新 $W^{[2]}, b^{[2]}$ 也能降低 loss,这是梯度下降的正常现象。
2. 但网络是一个整体,第一层的参数也影响最终 loss
虽然在这一轮中,$a^{[1]}$ 是固定的,但你不能认为它是“最优”的:
- $W^{[1]}, b^{[1]}$ 控制了 $a^{[1]}$,进而影响了整个网络的输入空间。
- 如果 $W^{[1]}$ 不更新,网络的表达能力就被限制在当前的 $a^{[1]}$ 上。
3. 关于“用力过猛”——确实可能发生,但这正是学习率 $\eta$ 的职责所在
- 如果学习率太大,更新 $W^{[1]}$ 会导致 $a^{[1]}$ 剧烈变化,从而让模型震荡甚至发散,loss 变大。
- 但如果学习率合适,小步更新 $W^{[1]}$,虽然 $a^{[1]}$ 改变,但整个网络能找到一个更优的路径继续下降。
这种 局部变动影响全局的连锁效应,是深度网络训练的核心挑战之一,也正是为什么我们:
- 使用 小学习率
- 使用 mini-batch 梯度下降 来减少波动
- 有时还会使用 梯度裁剪(gradient clipping)或动量优化器(如 Adam) 等手段来稳定训练
4. 是的,更新 $W^{[1]}$ 会让 $a^{[1]}$ 变动,可能使 loss 一时不降甚至上升。但:
- 这并不是“用力过猛”,而是模型试图找到更优的特征表达方式;
- 只要学习率合适,并在连续多次迭代中观察趋势,整体 loss 会继续下降;
- 更新 $W^{[1]}$ 是必要的,因为不更新的话,模型就无法适应更复杂的输入分布。
上一篇:[[SwiGLU]] 下一篇:[[SGD]]