优化模型参数
现在我们有了模型和数据,是时候通过在我们的数据上优化模型的参数来训练、验证和测试我们的模型了。训练模型是一个迭代过程;在每次迭代(纪元)中,模型会对输出进行猜测,计算其猜测中的误差(损失),收集误差相对于其参数的导数(如我们在前一个模块中所见),并使用梯度下降来优化这些参数。
先决代码
我们将加载之前模块中的代码,包括数据集和数据加载器以及构建模型的部分。
%matplotlib inline
import torch
from torch import nn
from torch.utils.data import DataLoader
from torchvision import datasets
from torchvision.transforms import ToTensor, Lambda
training_data = datasets.FashionMNIST(
root="data",
train=True,
download=True,
transform=ToTensor()
)
test_data = datasets.FashionMNIST(
root="data",
train=False,
download=True,
transform=ToTensor()
)
train_dataloader = DataLoader(training_data, batch_size=64)
test_dataloader = DataLoader(test_data, batch_size=64)
class NeuralNetwork(nn.Module):
def __init__(self):
super(NeuralNetwork, self).__init__()
self.flatten = nn.Flatten()
self.linear_relu_stack = nn.Sequential(
nn.Linear(28*28, 512),
nn.ReLU(),
nn.Linear(512, 512),
nn.ReLU(),
nn.Linear(512, 10),
nn.ReLU()
)
def forward(self, x):
x = self.flatten(x)
logits = self.linear_relu_stack(x)
return logits
model = NeuralNetwork()
设置超参数
超参数是可调节的参数,让您能够控制模型优化过程。不同的超参数值会影响模型的训练和准确度。
我们为训练定义以下超参数:
- 纪元数(Number of Epochs):整个训练数据集通过网络的次数。
- 批量大小(Batch Size):模型在每个纪元中看到的数据样本数量。这会迭代所需的批次数,以完成一个纪元。
- 学习率(Learning Rate):模型在搜索最佳权重以提高模型准确度时的步长。较小的值意味着模型找到最佳权重所需的时间更长。较大的值可能导致模型跨过并错过最佳权重,从而在训练过程中产生不可预测的行为。
添加优化循环
一旦设置好超参数,我们就可以使用优化循环来训练和优化我们的模型。优化循环的每次迭代称为一个纪元。
每个纪元由两个主要部分组成:
- 训练循环(Train Loop):遍历训练数据集并尝试收敛到最佳参数。
- 验证/测试循环(Validation/Test Loop):遍历测试数据集,以检查模型性能是否有所改善。
让我们来看看训练循环中使用的一些概念。可以跳到后面查看优化循环的完整实现。
添加损失函数
在给定一些训练数据时,我们未训练的网络不太可能给出正确的答案。损失函数衡量获得的结果与目标值之间的不相似度,而我们希望在训练过程中最小化的正是损失函数。为了计算损失,我们使用给定数据样本的输入进行预测,并将其与真实的数据标签值进行比较。
常见的损失函数包括:
nn.MSELoss:均方误差,通常用于回归任务。nn.NLLLoss:负对数似然损失,通常用于分类。nn.CrossEntropyLoss:结合了nn.LogSoftmax和nn.NLLLoss。
我们将模型的输出 logits 传递给 nn.CrossEntropyLoss,该函数对 logits 进行归一化并计算预测误差。
优化过程
优化是调整模型参数以减少每个训练步骤中模型误差的过程。优化算法定义了这一过程的执行方式(在这个示例中,我们使用随机梯度下降)。
所有的优化逻辑都封装在 optimizer 对象中。在这里,我们使用 SGD 优化器;PyTorch 中有许多不同的优化器,如 ADAM 和 RMSProp,它们适合不同类型的模型和数据。
我们通过注册需要训练的模型参数并传入学习率超参数来初始化优化器。
在训练循环中,优化过程分为三个步骤:
- 调用
optimizer.zero_grad()来重置模型参数的梯度。梯度默认会累加;为了防止重复计算,我们在每次迭代时显式地将其清零。 - 通过调用
loss.backward()进行反向传播,计算预测损失的梯度。PyTorch 会将损失相对于每个参数的梯度存储起来。 - 一旦获得梯度,我们调用
optimizer.step()来根据反向传播中收集的梯度调整参数。
完整实现
我们定义一个 train_loop 函数来循环执行优化代码,以及一个 test_loop 函数来评估模型在测试数据上的性能。
我们初始化损失函数和优化器,并将它们传递给 train_loop 和 test_loop。可以自由增加纪元数,以跟踪模型性能的提升。
Epoch 2
Epoch 3
Epoch 4
Epoch 5
Epoch 6
Epoch 7
Epoch 8
Epoch 9
Epoch 10
Done!
您可能注意到模型在最初并不是很好(这没关系)。尝试将循环运行更多的 epochs 或调整 learning_rate 为更大的数值。也有可能我们选择的模型配置对于这个类型的问题并不是最优的(确实不是)。后面的课程将更深入地探讨适用于视觉问题的模型结构。
保存模型
当您对模型的性能满意时,可以使用 torch.save 来保存它。PyTorch 模型将学习到的参数存储在一个名为 state_dict 的内部状态字典中。您可以使用 torch.save 方法将这些参数持久化: