PyTorch 快速入门#

本节将介绍机器学习中常见任务的 API。

from set_env import temp_dir  # 加载环境并导入临时目录路径
项目根目录:/media/pc/data/lxw/ai/torch-book

处理数据#

PyTorch 提供两种基础工具来处理数据torch.utils.data.DataLoadertorch.utils.data.DatasetDataset 用于存储样本及其对应的标签,而 DataLoader 则在 Dataset 周围包装可迭代对象。

import torch
from torch import nn
from torch.utils.data import DataLoader
from torchvision import datasets
from torchvision.transforms import v2

PyTorch 提供了针对特定领域的库,例如 TorchTextTorchVisionTorchAudio,这些库都包含了数据集。在本教程中使用 TorchVision 数据集。

torchvision.datasets 模块包含了许多现实世界视觉数据的 Dataset 对象,例如 CIFAR、COCO(完整列表在这里)。在本教程中,使用的是 FashionMNIST 数据集。每个 TorchVision 数据集都包括两个参数:transformtarget_transform,分别用于修改样本和标签。

transform = v2.Compose(
    v2.ToImage(),
    v2.ToDtype(torch.float32, scale=True),
)
# 从公开数据集下载训练数据
training_data = datasets.FashionMNIST(root=temp_dir/"data", train=True, download=True, transform=transform,)
# 从公开数据集下载测试据
test_data = datasets.FashionMNIST(root=temp_dir/"data", train=False, download=True, transform=transform,)

备注

  • v2.ToImage 将张量、ndarray 或 PIL图像转换为 tv_tensors.Image;此操作不会缩放值。v2.ToImage 变换不支持 torchscript。

  • v2.ToDtype 将输入转换为特定的数据类型,对于图像或视频,可选择性地进行数值缩放。

    • dtype (torch.dtype 或 dict of TVTensor -> torch.dtype) – 要转换的数据类型。如果传入的是 torch.dtype,例如 torch.float32,则只有图像和视频将被转换为该数据类型:这是为了与 ConvertImageDtype 兼容。可以传递字典来指定每个 tv_tensor 的转换,例如 dtype={tv_tensors.Image: torch.float32, tv_tensors.Mask: torch.int64, "others":None}"others" 键可以用作任何其他 tv_tensor 类型的通用捕获器,而 None 表示不进行转换。

    • scale (bool, 可选) – 是否对图像或视频的值进行缩放。请参阅 Dtype 和预期值范围。默认值:False

    • 张量图像的值的预期范围由其数据类型隐式定义。浮点型数据类型的张量图像,其值应在 \([0, 1]\) 区间内。整数型数据类型的张量图像,其值应在 \([0, \text{MAX_DTYPE}]\) 区间内,其中 \(\text{MAX_DTYPE}\) 是该数据类型所能表示的最大值。通常,torch.uint8 数据类型的图像,其值应在 \([0, 255]\) 区间内。

Dataset 作为参数传递给 DataLoaderDataLoader 封装了对数据集的可迭代对象,并支持自动批处理、采样、洗牌以及多进程数据加载。

定义了批次大小为64,即 dataloader 的每个可迭代元素将返回包含64个特征和标签的批次。

batch_size = 64

# Create data loaders.
train_dataloader = DataLoader(training_data, batch_size=batch_size)
test_dataloader = DataLoader(test_data, batch_size=batch_size)

for X, y in test_dataloader:
    print(f"Shape of X [N, C, H, W]: {X.shape}")
    print(f"Shape of y: {y.shape} {y.dtype}")
    break
Shape of X [N, C, H, W]: torch.Size([64, 1, 28, 28])
Shape of y: torch.Size([64]) torch.int64

创建模型#

在 PyTorch 中定义神经网络,创建继承自 torch.nn.Module 的类。在 __init__ 函数中定义网络的层,并在 forward 函数中指定数据如何通过网络传递。为了加速神经网络中的运算,如果可用,将它移动到 GPU 或 MPS。

# Get cpu, gpu or mps device for training.
device = (
    "cuda"
    if torch.cuda.is_available()
    else "mps"
    if torch.backends.mps.is_available()
    else "cpu"
)
print(f"Using {device} device")

# Define model
class NeuralNetwork(nn.Module):
    def __init__(self):
        super().__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)
        )

    def forward(self, x):
        x = self.flatten(x)
        logits = self.linear_relu_stack(x)
        return logits

model = NeuralNetwork().to(device)
print(model)
Using cuda device
NeuralNetwork(
  (flatten): Flatten(start_dim=1, end_dim=-1)
  (linear_relu_stack): Sequential(
    (0): Linear(in_features=784, out_features=512, bias=True)
    (1): ReLU()
    (2): Linear(in_features=512, out_features=512, bias=True)
    (3): ReLU()
    (4): Linear(in_features=512, out_features=10, bias=True)
  )
)

优化模型参数#

为了训练模型,需要损失函数和优化器。

loss_fn = nn.CrossEntropyLoss()
optimizer = torch.optim.SGD(model.parameters(), lr=1e-3)

在单次训练循环中,模型对训练数据集(以批次形式输入)进行预测,并通过反向传播预测误差来调整模型参数。

def train(dataloader, model, loss_fn, optimizer):
    size = len(dataloader.dataset)
    model.train()
    for batch, (X, y) in enumerate(dataloader):
        X, y = X.to(device), y.to(device)

        # Compute prediction error
        pred = model(X)
        loss = loss_fn(pred, y)

        # Backpropagation
        loss.backward()
        optimizer.step()
        optimizer.zero_grad()

        if batch % 100 == 0:
            loss, current = loss.item(), (batch + 1) * len(X)
            print(f"loss: {loss:>7f}  [{current:>5d}/{size:>5d}]")

还通过测试数据集来检验模型的性能,以确保其正在有效学习。

def test(dataloader, model, loss_fn):
    size = len(dataloader.dataset)
    num_batches = len(dataloader)
    model.eval()
    test_loss, correct = 0, 0
    with torch.no_grad():
        for X, y in dataloader:
            X, y = X.to(device), y.to(device)
            pred = model(X)
            test_loss += loss_fn(pred, y).item()
            correct += (pred.argmax(1) == y).type(torch.float).sum().item()
    test_loss /= num_batches
    correct /= size
    print(f"Test Error: \n Accuracy: {(100*correct):>0.1f}%, Avg loss: {test_loss:>8f} \n")

训练过程通过多次迭代(即“轮次”)进行。 在每一轮中,模型学习参数以提高预测的准确性。在每个轮次打印出模型的准确度和损失值;希望看到随着每一轮次的增加,准确度提高而损失减少。

epochs = 5
for t in range(epochs):
    print(f"Epoch {t+1}\n-------------------------------")
    train(train_dataloader, model, loss_fn, optimizer)
    test(test_dataloader, model, loss_fn)
print("Done!")
Hide code cell output
Epoch 1
-------------------------------
loss: 2.306069  [   64/60000]
loss: 2.299073  [ 6464/60000]
loss: 2.277852  [12864/60000]
loss: 2.264928  [19264/60000]
loss: 2.252309  [25664/60000]
loss: 2.210188  [32064/60000]
loss: 2.226893  [38464/60000]
loss: 2.189837  [44864/60000]
loss: 2.190420  [51264/60000]
loss: 2.148874  [57664/60000]
Test Error: 
 Accuracy: 33.1%, Avg loss: 2.151322 

Epoch 2
-------------------------------
loss: 2.164296  [   64/60000]
loss: 2.159659  [ 6464/60000]
loss: 2.098582  [12864/60000]
loss: 2.110010  [19264/60000]
loss: 2.069656  [25664/60000]
loss: 1.996943  [32064/60000]
loss: 2.034728  [38464/60000]
loss: 1.951787  [44864/60000]
loss: 1.966385  [51264/60000]
loss: 1.888368  [57664/60000]
Test Error: 
 Accuracy: 55.5%, Avg loss: 1.886714 

Epoch 3
-------------------------------
loss: 1.922163  [   64/60000]
loss: 1.893041  [ 6464/60000]
loss: 1.772466  [12864/60000]
loss: 1.812631  [19264/60000]
loss: 1.713909  [25664/60000]
loss: 1.656085  [32064/60000]
loss: 1.688545  [38464/60000]
loss: 1.582536  [44864/60000]
loss: 1.619781  [51264/60000]
loss: 1.514278  [57664/60000]
Test Error: 
 Accuracy: 59.5%, Avg loss: 1.524411 

Epoch 4
-------------------------------
loss: 1.592879  [   64/60000]
loss: 1.555723  [ 6464/60000]
loss: 1.403057  [12864/60000]
loss: 1.475842  [19264/60000]
loss: 1.371099  [25664/60000]
loss: 1.354005  [32064/60000]
loss: 1.381042  [38464/60000]
loss: 1.295066  [44864/60000]
loss: 1.339796  [51264/60000]
loss: 1.244596  [57664/60000]
Test Error: 
 Accuracy: 63.4%, Avg loss: 1.262004 

Epoch 5
-------------------------------
loss: 1.336847  [   64/60000]
loss: 1.317122  [ 6464/60000]
loss: 1.151755  [12864/60000]
loss: 1.258062  [19264/60000]
loss: 1.145909  [25664/60000]
loss: 1.157517  [32064/60000]
loss: 1.191936  [38464/60000]
loss: 1.117336  [44864/60000]
loss: 1.163072  [51264/60000]
loss: 1.086031  [57664/60000]
Test Error: 
 Accuracy: 64.8%, Avg loss: 1.098684 

Done!

保存模型#

保存模型的常见方法是序列化内部状态字典(包含模型参数)

torch.save(model.state_dict(), temp_dir/"model.pth")
print("Saved PyTorch Model State to model.pth")
Saved PyTorch Model State to model.pth

加载模型#

加载模型的过程包括重建模型结构并将状态字典载入其中。

model = NeuralNetwork().to(device)
model.load_state_dict(torch.load(temp_dir/"model.pth", weights_only=True))
<All keys matched successfully>

此模型现在可用于进行预测。

classes = [
    "T-shirt/top",
    "Trouser",
    "Pullover",
    "Dress",
    "Coat",
    "Sandal",
    "Shirt",
    "Sneaker",
    "Bag",
    "Ankle boot",
]

model.eval()
x, y = test_data[0][0], test_data[0][1]
with torch.no_grad():
    x = x.to(device)
    pred = model(x)
    predicted, actual = classes[pred[0].argmax(0)], classes[y]
    print(f'Predicted: "{predicted}", Actual: "{actual}"')
Predicted: "Ankle boot", Actual: "Ankle boot"