神经网络优化:从基础到实践
1. 多类 SVM 损失与 Softmax CE 损失
多类 SVM 损失有一个特点,那就是它比较“懒惰”。一旦正确类别的得分超过错误类别得分一定的边界 $m$,损失就不再变化。即使正确类别的得分继续提高,损失也不会改变,这意味着它不会促使模型在这个点之后进一步改进。而 Softmax 交叉熵(CE)损失则不同,它会促使模型为正确类别争取无限高的得分。
2. 神经网络优化基础
- 损失函数 :神经网络模型定义了一个损失函数,用于估计网络输出的“糟糕程度”。在监督训练中,将特定训练实例的输出与该实例的已知输出(即真实标签,GT)进行比较,两者的差异就是损失。可以将各个训练数据实例的损失相加,得到整个训练数据的总损失。
- 损失表面 :损失是网络参数 $\vec{w}$ 和 $\vec{b}$ 的函数。可以想象一个维度为 $dim(\vec{w}) + dim(\vec{b})$ 的空间,在这个空间的每一点都对应一个总训练损失值,这样就形成了一个损失表面,其高度表示损失值。
- 优化目标 :优化的目标就是在这个损失表面上找到最低点。训练时,从网络参数的随机值开始,就像在损失表面上随机选一个点,然后沿着负梯度方向不断局部向下移动,希望最终能到达最小值点或一个足够低的点。
3. 优化的数学表达
- 参数更新公式 :设 $t$ 表示迭代次数,$\vec{w}_t$ 表示第 $t$ 次迭代的权重值,$\delta \vec{w}_t$ 表示第 $t$ 次迭代的权重更新值,$\vec{b}_t$ 和 $\delta \vec{b}_t$ 同理。参数更新公式如下:
- $\vec{w}_{t + 1} = \vec{w}_t - \delta \vec{w}_t$
- $\vec{b}_{t + 1} = \vec{b}_t - \delta \vec{b}_t$
- 梯度更新公式 :基本的更新是沿着负梯度方向进行的,公式为:
- $\delta \vec{w} t = \eta \nabla {\vec{w}} L(\vec{w}_t, \vec{b}_t)$
-
$\delta \vec{b}
t = \eta \nabla
{\vec{b}} L(\vec{w}_t, \vec{b}_t)$
其中 $L(\vec{w}_t, \vec{b}_t)$ 表示第 $t$ 次迭代的损失,$\eta$ 是学习率(LR)。学习率越大,每次更新时对权重和偏置的调整就越大;反之则越小。通常在训练开始时使用较大的学习率,接近最小值时使用较小的学习率,以避免越过最小值。在随机梯度下降(SGD)中,学习率通常在一个 epoch(即对整个训练数据的一次完整遍历)内保持不变,经过一个或多个 epoch 后再减小,这个过程称为学习率衰减。
4. 随机梯度下降(SGD)
- 计算梯度的问题 :损失函数在每个训练数据实例上的值都不同,理想情况下应该计算所有实例的损失并求平均,但这意味着每次迭代都要处理整个训练数据集,计算复杂度为 $O(n^2)$($n$ 是训练数据集的大小),对于大规模数据集来说,这种计算是非常昂贵的。
- SGD 的解决方案 :随机梯度下降不计算整个训练数据集的平均梯度,而是计算一个随机采样的子集(即小批量,minibatch)的平均损失来得到梯度,然后用这个梯度来更新权重和偏置参数。
5. PyTorch 实现 SGD
下面通过一个具体的例子,展示如何在 PyTorch 中实现 SGD。假设我们要构建一个模型,根据身高和体重数据预测 Statsville 居民是男性、女性还是儿童。
-
创建自定义数据集
:
from torch.utils.data import Dataset, DataLoader
class StatsvilleDataset(Dataset):
def __init__(self, X, y_gt):
self.X = X
self.y_gt = y_gt
def __len__(self):
return len(self.X)
def __getitem__(self, i):
return self.X[i], self.y_gt[i]
# 假设 X 是包含身高和体重数据的数据集,y_gt 是对应的标签
dataset = StatsvilleDataset(X, y_gt)
data_loader = DataLoader(dataset, batch_size=10, shuffle=True)
- 创建自定义神经网络模型 :
import torch
import torch.nn as nn
class Model(nn.Module):
def __init__(self, input_size, hidden_size, output_size):
super(Model, self).__init__()
self.linear1 = nn.Linear(input_size, hidden_size)
self.linear2 = nn.Linear(hidden_size, output_size)
self.softmax = nn.Softmax(dim=1)
def forward(self, X):
scores = self.linear2(self.linear1(X))
return scores
def predict(self, X):
scores = self.forward(X)
y_pred = torch.argmax(self.softmax(scores), dim=1)
return y_pred
def initialize_weights(m):
if isinstance(m, nn.Linear):
nn.init.xavier_uniform_(m.weight.data)
nn.init.constant_(m.bias.data, 0)
model = Model(input_size=2, hidden_size=100, output_size=3)
model.apply(initialize_weights)
- 定义损失函数和优化器 :
import torch.optim as optim
loss_fn = nn.CrossEntropyLoss()
optimizer = optim.SGD(model.parameters(), lr=0.02)
- 定义训练循环 :
def train_loop(epoch, data_loader, model, loss_fn, optimizer):
for X_batch, y_gt_batch in data_loader:
scores = model(X_batch)
loss = loss_fn(scores, y_gt_batch)
optimizer.zero_grad()
loss.backward()
optimizer.step()
- 运行训练循环 :
num_epochs = 2
for epoch in range(num_epochs):
train_loop(epoch, data_loader, model, loss_fn, optimizer)
6. 动量优化
- 梯度估计的问题 :在高维的实际损失表面上,不同点的梯度估计是有噪声的,并且方向不一致。就像峡谷的墙壁一样,损失表面并不平滑。而且通常是基于小批量进行梯度估计,这使得梯度估计更加嘈杂,方向难以对齐。不过,大多数梯度估计都有一个良好的下降分量,而非下降分量则是随机的。
- 动量的作用 :动量的思想是对当前迭代计算的梯度和上一次迭代的更新进行加权平均。公式如下:
- $\delta \vec{w} t = \gamma \delta \vec{w} {t - 1} + \eta \nabla_{\vec{w}} L(\vec{w}_t, \vec{b}_t)$
-
$\delta \vec{b}
t = \gamma \delta \vec{b}
{t - 1} + \eta \nabla_{\vec{b}} L(\vec{w}_t, \vec{b}_t)$
其中 $\gamma$ 和 $\eta$ 是小于 1 的正常数。这样做的好处是,如果遇到一个小的平坦区域,普通的梯度下降方法可能会陷入其中,因为该区域梯度为零,但使用动量可以得到非零的更新,从而使优化过程跳出这个局部平坦区域,继续向下进行。 - 动量公式展开 :将动量公式展开,可以得到:
-
$\delta \vec{w}
t = \eta \nabla
{\vec{w}} L(\vec{w}
t, \vec{b}_t) + \eta \gamma \nabla
{\vec{w}} L(\vec{w}
{t - 1}, \vec{b}
{t - 1}) + \cdots + \eta \gamma^t \nabla_{\vec{w}} L(\vec{w}_0, \vec{b}_0)$
这实际上是对过去迭代的梯度进行加权求和,且旧的梯度权重随着时间以 $\gamma$ 的幂次减小。不过,这些权重的总和并不等于 1,这是动量梯度下降的一个不太理想的特性,在后续的 Adam 算法中会进行改进。
7. 几何视角:恒定损失轮廓、梯度下降和动量
考虑一个只有两个元素的权重向量 $\vec{w} = \begin{bmatrix} w_0 \ w_1 \end{bmatrix}$ 且无偏置的网络,损失函数为 $L = |\vec{w}|^2 = w_0^2 + w_1^2$。其恒定损失轮廓是以原点为中心的同心圆,圆的半径表示损失大小。
-
梯度方向
:通过计算梯度 $\nabla_{\vec{w}} L = 2\begin{bmatrix} w_0 \ w_1 \end{bmatrix}$ 可知,梯度沿着半径方向,负梯度方向是径向向内的。这意味着沿着半径向内移动时,损失下降最快;沿着圆周移动时,损失保持不变。
-
优化轨迹
:优化过程就是从外圆(大半径,高损失)向里圆(小半径,低损失)移动,理想情况下,当到达原点时,优化停止。图 9.9 展示了有无动量情况下的优化轨迹,可以看到,使用动量的版本能在更少的步骤内到达更小的圆。
下面是一个简单的 mermaid 流程图,展示神经网络优化的基本流程:
graph TD;
A[初始化网络参数] --> B[计算损失和梯度];
B --> C{是否达到停止条件};
C -- 否 --> D[更新参数];
D --> B;
C -- 是 --> E[结束优化];
通过上述内容,我们了解了神经网络优化的基本概念、随机梯度下降算法以及如何在 PyTorch 中实现,还介绍了动量优化的原理和几何视角。这些知识对于理解和实现神经网络训练至关重要。在实际应用中,可以根据具体问题选择合适的优化方法和参数,以提高模型的性能。
神经网络优化:从基础到实践
8. 优化过程中的关键要点总结
为了更清晰地理解神经网络优化过程,下面将关键要点总结成表格:
|要点|详情|
| ---- | ---- |
|损失函数|衡量网络输出与真实标签的差异,是优化的目标函数|
|损失表面|由网络参数构成的高维空间中,每个点对应一个总训练损失值|
|优化目标|在损失表面上找到最低点,使网络输出与真实标签的差异最小|
|学习率|控制每次参数更新的步长,开始时可较大,接近最小值时应减小|
|随机梯度下降(SGD)|通过随机采样小批量数据计算梯度,减少计算量|
|动量优化|对当前梯度和上一次更新进行加权平均,帮助跳出局部平坦区域|
9. 不同优化方法的对比
下面对比一下普通梯度下降、随机梯度下降和动量优化三种方法:
|优化方法|优点|缺点|适用场景|
| ---- | ---- | ---- | ---- |
|普通梯度下降|能保证收敛到全局最优解(在凸函数情况下)|计算复杂度高,每次迭代需处理整个数据集|数据集较小的情况|
|随机梯度下降|计算效率高,通过小批量数据更新参数|梯度估计有噪声,收敛过程可能不稳定|大规模数据集|
|动量优化|能减少梯度估计的噪声影响,帮助跳出局部平坦区域|权重总和不为 1,有一定局限性|损失表面复杂,存在局部平坦区域的情况|
10. 优化过程中的注意事项
- 学习率调整 :学习率的选择对优化过程影响很大。如果学习率过大,可能会导致模型跳过最小值点,无法收敛;如果学习率过小,收敛速度会非常慢。可以采用学习率衰减策略,在训练初期使用较大的学习率,随着训练的进行逐渐减小学习率。
- 数据随机化 :在每个 epoch 结束后,对训练数据进行随机洗牌是非常重要的。这样可以避免模型对数据的顺序产生依赖,防止过拟合。
-
梯度清零
:在每次迭代中,需要调用
optimizer.zero_grad()来清除上一次迭代积累的梯度。否则,梯度会不断累加,导致参数更新异常。
11. 进一步优化思路
- 自适应学习率方法 :除了学习率衰减策略,还可以使用自适应学习率方法,如 Adagrad、Adadelta、RMSProp 和 Adam 等。这些方法可以根据参数的更新情况自动调整学习率,提高优化效率。
- 批量归一化(Batch Normalization) :在神经网络中插入批量归一化层,可以加速训练过程,减少梯度消失和梯度爆炸的问题,提高模型的泛化能力。
12. 总结与展望
神经网络优化是一个复杂而关键的过程,涉及到多个方面的知识和技巧。从多类 SVM 损失和 Softmax CE 损失的特点,到随机梯度下降、动量优化等算法的原理和实现,再到优化过程中的关键要点和注意事项,我们对神经网络优化有了更深入的理解。
在未来的研究和实践中,我们可以继续探索更高效、更稳定的优化算法,结合不同的优化策略,进一步提高神经网络模型的性能。同时,随着数据量的不断增加和模型复杂度的提高,优化算法的计算效率和资源利用率也将成为重要的研究方向。
下面是一个 mermaid 流程图,展示优化方法的选择思路:
graph TD;
A[数据集大小] --> B{小数据集};
B -- 是 --> C[普通梯度下降];
B -- 否 --> D{损失表面复杂};
D -- 是 --> E[动量优化];
D -- 否 --> F[随机梯度下降];
通过不断学习和实践,我们可以更好地掌握神经网络优化的技术,为解决各种实际问题提供更强大的工具。
超级会员免费看
5万+

被折叠的 条评论
为什么被折叠?



