¶
一个完整的模型训练过程包括数据准备、模型搭建、训练与优化等过程,每个过程又涉及若干不同的知识点,本篇中,我们通过一个示例,从整体层面介绍PyTorch的完整建模流程,通过这个例子,我们可以知道建模流程中的各个环节所要做的事情及其意义。在后续的博客中,我们将展开介绍各个过程环节涉及的相关知识内容。
1 数据准备¶
无论是构建何种模型,完成何种训练任务,都是建立在一定数据的基础上,所以,在建模之初,首先要做的就是准备数据。
PyTorch有两个与处理数据相关的模块:torch.utils.data.DataLoader
和 torch.utils.data.Dataset
,其中torch.utils.data.DataLoader
用于将数据集进行打包封装成一个可迭代对象,torch.utils.data.Dataset
存储有一些常用的数据集示例以及相关标签。
同时PyTorch针对不同的专业领域,也提供有不同的模块,例如 TorchText(自然语言处理), TorchVision(计算机视觉), TorchAudio(音频),这些模块中也都包含一些真实数据集示例。例如TorchVision模块中提供了CIFAR, COCO, FashionMNIST 数据集。
现在,我们使用FashionMNIST数据集,以服饰分类任务介绍PyTorch的建模流程。
我们导入必要的模块:
In [1]:import torch from torch import nn from torch.utils.data import DataLoader from torchvision import datasets from torchvision.transforms import ToTensor, Lambda, Compose import matplotlib.pyplot as plt
现在,我们下载数据,代码如下,我们通过torchvision中的datasets加载自带的FashionMNIST数据集,并通过root参数指定数据集存放在当前目录下的data
目录内,因为我在此前没有下载过,所以将download参数设为True,表示需要通过代码进行自动下载,如果你的网络不好,下载一直失败,那么,你可以从这里下载数据集,解压后放到root指定的目录中。
# 下载数训练据集 training_data = datasets.FashionMNIST( root="data", train=True, download=True, transform=ToTensor(), )
Downloading http://fashion-mnist.s3-website.eu-central-1.amazonaws.com/train-images-idx3-ubyte.gz Downloading http://fashion-mnist.s3-website.eu-central-1.amazonaws.com/train-images-idx3-ubyte.gz to data/FashionMNIST/raw/train-images-idx3-ubyte.gz
100%|██████████| 26421880/26421880 [1:19:01<00:00, 5572.61it/s]
Extracting data/FashionMNIST/raw/train-images-idx3-ubyte.gz to data/FashionMNIST/raw Downloading http://fashion-mnist.s3-website.eu-central-1.amazonaws.com/train-labels-idx1-ubyte.gz Downloading http://fashion-mnist.s3-website.eu-central-1.amazonaws.com/train-labels-idx1-ubyte.gz to data/FashionMNIST/raw/train-labels-idx1-ubyte.gz
100%|██████████| 29515/29515 [00:00<00:00, 45160.87it/s]
Extracting data/FashionMNIST/raw/train-labels-idx1-ubyte.gz to data/FashionMNIST/raw Downloading http://fashion-mnist.s3-website.eu-central-1.amazonaws.com/t10k-images-idx3-ubyte.gz Downloading http://fashion-mnist.s3-website.eu-central-1.amazonaws.com/t10k-images-idx3-ubyte.gz to data/FashionMNIST/raw/t10k-images-idx3-ubyte.gz
100%|██████████| 4422102/4422102 [2:31:30<00:00, 486.43it/s]
Extracting data/FashionMNIST/raw/t10k-images-idx3-ubyte.gz to data/FashionMNIST/raw Downloading http://fashion-mnist.s3-website.eu-central-1.amazonaws.com/t10k-labels-idx1-ubyte.gz Downloading http://fashion-mnist.s3-website.eu-central-1.amazonaws.com/t10k-labels-idx1-ubyte.gz to data/FashionMNIST/raw/t10k-labels-idx1-ubyte.gz
100%|██████████| 5148/5148 [00:00<00:00, 10844940.73it/s]
Extracting data/FashionMNIST/raw/t10k-labels-idx1-ubyte.gz to data/FashionMNIST/rawIn [7]:
# 下载测试数据集 test_data = datasets.FashionMNIST( root="data", train=False, download=True, transform=ToTensor(), )
下载后的数据集将保存在training_data和test_data两个对象中,这两个对象都是torch.utils.data.Dataset类的实例。关于FashionMNIST、Dataset、Dataloader,在后续的博客中,我们都会一一详细介绍。
In [8]:isinstance(training_data, torch.utils.data.Dataset), isinstance(test_data, torch.utils.data.Dataset)Out[8]:
(True, True)
接下来,我们需要将training_data和test_data封装成DataLoader类实例,封装成DataLoader类实例后,且支持迭代、自动分割成簇、下采样、打乱、多进程加载数据等操作。
In [11]:batch_size = 64 # 创建数据加载器 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("Shape of X [N, C, H, W]: ", X.shape) print("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
我们展示一下这些数据:
In [12]:from chb import show_image
chb是我放在pypi的一个开源库,里面是我平时会用的到一些小代码,show_image就是其中一个用于展示图片的方法。大家可以通过以下命令下载:
$ pip install chbIn [14]:
images, labels = next(iter(train_dataloader)) show_image([(img, lab) for img, lab in zip(images[:10], labels[:10]) ])
到此,对数据的处理过程已然完成,可以开始定义模型。由于我们使用的是人工神经网络模型,我们可以创建一个类,去继承nn.Module
,在类的构造方法中定义网络各个层。
2 模型搭建¶
In [6]:# 定义模型 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) ) def forward(self, x): x = self.flatten(x) logits = self.linear_relu_stack(x) return logits
为加速训练,我们可以使用GPU进行训练,不过有必要判断一下是否存在GPU设备,然后再将模型加载到设备中:
In [7]:# 判断是否存在GPU device = "cuda" if torch.cuda.is_available() else "cpu" print(f"Using {device} device") 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) ) )
3 训练与优化¶
数据集和模型都准备好之后,就可以开始训练和优化的过程。为完成模型训练,我们还需要为模型指定损失函数和优化器:
In [8]:# 交叉熵损失函数 loss_fn = nn.CrossEntropyLoss() # SGD方法进行优化 optimizer = torch.optim.SGD(model.parameters(), lr=1e-3)
在一次单独训练过程中,模型会通过预测结果与训练集标签进行校对,根据校对准确率进行反向传播,从而达到调整模型参数的目的。训练过程代码如下:
In [9]: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) # 计算误差 pred = model(X) loss = loss_fn(pred, y) # 反向传播 optimizer.zero_grad() loss.backward() optimizer.step() if batch % 100 == 0: loss, current = loss.item(), batch * len(X) print(f"loss: {loss:>7f} [{current:>5d}/{size:>5d}]")
在训练过程中,我们常常通过测试集来验证模型的准确率:
In [10]: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")
接下来,可以正式开始训练,我们先训练5轮:
In [13]: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!")
Epoch 1 ------------------------------- loss: 0.786908 [ 0/60000] loss: 0.856685 [ 6400/60000] loss: 0.633258 [12800/60000] loss: 0.849028 [19200/60000] loss: 0.738350 [25600/60000] loss: 0.734769 [32000/60000] loss: 0.812572 [38400/60000] loss: 0.795026 [44800/60000] loss: 0.797189 [51200/60000] loss: 0.763483 [57600/60000] Test Error: Accuracy: 72.3%, Avg loss: 0.758835 Epoch 2 ------------------------------- loss: 0.750288 [ 0/60000] loss: 0.828323 [ 6400/60000] loss: 0.602143 [12800/60000] loss: 0.825365 [19200/60000] loss: 0.716997 [25600/60000] loss: 0.709113 [32000/60000] loss: 0.787452 [38400/60000] loss: 0.778843 [44800/60000] loss: 0.775311 [51200/60000] loss: 0.743379 [57600/60000] Test Error: Accuracy: 73.4%, Avg loss: 0.737126 Epoch 3 ------------------------------- loss: 0.718289 [ 0/60000] loss: 0.802935 [ 6400/60000] loss: 0.575369 [12800/60000] loss: 0.805272 [19200/60000] loss: 0.698708 [25600/60000] loss: 0.687783 [32000/60000] loss: 0.764835 [38400/60000] loss: 0.764436 [44800/60000] loss: 0.756569 [51200/60000] loss: 0.725625 [57600/60000] Test Error: Accuracy: 74.4%, Avg loss: 0.717835 Epoch 4 ------------------------------- loss: 0.689940 [ 0/60000] loss: 0.779801 [ 6400/60000] loss: 0.551888 [12800/60000] loss: 0.787713 [19200/60000] loss: 0.682854 [25600/60000] loss: 0.669718 [32000/60000] loss: 0.744048 [38400/60000] loss: 0.751069 [44800/60000] loss: 0.740112 [51200/60000] loss: 0.709649 [57600/60000] Test Error: Accuracy: 75.2%, Avg loss: 0.700398 Epoch 5 ------------------------------- loss: 0.664450 [ 0/60000] loss: 0.758469 [ 6400/60000] loss: 0.531073 [12800/60000] loss: 0.772000 [19200/60000] loss: 0.668949 [25600/60000] loss: 0.654071 [32000/60000] loss: 0.724732 [38400/60000] loss: 0.738524 [44800/60000] loss: 0.725478 [51200/60000] loss: 0.694969 [57600/60000] Test Error: Accuracy: 75.9%, Avg loss: 0.684460 Done!
5轮训练完成,从输出结果可以看出,模型装瘸率逐渐提高。当然,如果要更高的准确率,我们需要更多轮次的训练,或者优化模型。
4 保存模型与加载¶
4.1 保存模型¶
保存模型常用的方法就是将模型序列化到本地进行离线保存模型各个参数,后续需要再次使用模型时,就不需要重新训练,直接加载即可。
In [14]:torch.save(model.state_dict(), "models/fashion_mnist/model.pth") print("Saved PyTorch Model State to model.pth")
Saved PyTorch Model State to model.pth
4.2 加载模型¶
之前保存在本地的模型可以再次加载出来,然后直接进行预测。
In [15]:model = NeuralNetwork() model.load_state_dict(torch.load("models/fashion_mnist/model.pth"))Out[15]:
<All keys matched successfully>
尝试使用刚加载的模型进行识别:
In [16]: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(): pred = model(x) predicted, actual = classes[pred[0].argmax(0)], classes[y] print(f'Predicted: "{predicted}", Actual: "{actual}"')
Predicted: "Ankle boot", Actual: "Ankle boot"
5 总结¶
上文中,为完成一个服饰分类任务,我们使用PyTorch构建并训练了一个网络模型。在此过程中,我们进行了数据准备、模型构建、模型训练与优化、模型保存与加载等等过程。对于初学者,这些过程中涉及的细节可能不甚了解,例如数据准备过程中为什么要使用torch.utils.data.DataLoader
和 torch.utils.data.Dataset
、模型构建过程,我们怎么去个性化定制网络结构、训练过程,我们怎么去配置优化器,优化器又有哪一些。别急,这些内容在我们后续的文章中,都有专门的内容介绍。通过本篇,你只需要记住一个完整的建模有哪些过程,作用是什么即可。