import torch, torchvision
import torchvision.transforms as transforms
PyTorch 到 fastai 细节
在本教程中,我们将使用纯 PyTorch 从零开始训练 MNIST(类似于此处的简化教程),并逐步将其添加到 fastai 框架中。这意味着我们将使用: - PyTorch DataLoaders - PyTorch 模型 - PyTorch 优化器
而对于 fastai,我们将仅使用训练循环(或Learner
类)
在本教程中,由于人们通常更习惯于显式导出,我们将使用 fastai 库中的显式导出,但请理解您也可以通过执行 from fastai.vision.all import *
来自动获取所有这些导入。
通常也建议您这样做,因为库中存在猴子补丁 (monkey-patching),但这也可以避免,稍后将进行展示。
数据
如标题所述,我们将仅使用 torchvision
模块加载数据集。
这包括加载数据集,以及为 DataLoaders 做准备(包括 transforms)。
首先我们导入所需的库
接下来我们可以定义一些最小的 transforms,用于将原始双通道图像转换为可训练的 tensors 并对其进行归一化。
均值和标准差来自 MNIST 数据集。
= transforms.Compose([transforms.ToTensor(),
tfms 0.1307,), (0.3081))
transforms.Normalize(( ])
最后通过下载数据集并应用 transforms 来创建训练和测试(在 fastai 框架中称为验证)DataLoaders
。
from torchvision import datasets
from torch.utils.data import DataLoader
首先,让我们下载一个训练集和一个测试集(或在 fastai 框架中称为验证集)。
= datasets.MNIST('../data', train=True, download=True, transform=tfms)
train_dset = datasets.MNIST('../data', train=False, transform=tfms) valid_dset
接下来我们将定义一些超参数,以便在创建各个DataLoader
时传递给它们。
训练时我们将 batch size 设置为 256,验证集时设置为 512。
我们还将使用单个 worker 并固定内存 (pin the memory)。
= DataLoader(train_dset, batch_size=256,
train_loader =True, num_workers=1, pin_memory=True)
shuffle
= DataLoader(valid_dset, batch_size=512,
test_loader =False, num_workers=1, pin_memory=True) shuffle
现在我们有了原生的 PyTorch DataLoader
。要在 fastai 框架中使用它们,剩下的就是将其包装在 fastai DataLoaders
类中,该类只需接收任意数量的 DataLoader
对象并将它们合并为一个。
from fastai.data.core import DataLoaders
= DataLoaders(train_loader, test_loader) dls
我们现在已经为 fastai
准备好数据了!接下来让我们构建一个要使用的基本模型。
模型
这将是一个极其简单的 2 层卷积神经网络,外加一组模仿 fastai 生成的 head
的层。每个 head 中都包含一个 Flatten
层,它仅仅是调整输出的形状。我们在此模仿它。
from torch import nn
class Flatten(nn.Module):
"Flattens an input"
def forward(self, x): return x.view(x.size(0), -1)
然后是我们实际的模型。
class Net(nn.Sequential):
def __init__(self):
super().__init__(
1, 32, 3, 1), nn.ReLU(),
nn.Conv2d(32, 64, 3, 1),
nn.Conv2d(# A head to the model
2), nn.Dropout2d(0.25),
nn.MaxPool2d(9216, 128), nn.ReLU(),
Flatten(), nn.Linear(0.5), nn.Linear(128, 10), nn.LogSoftmax(dim=1)
nn.Dropout2d( )
优化器
借助 OptimWrapper
接口,在 fastai 框架中使用原生的 PyTorch 优化器变得极其简单。
只需编写一个 partial
函数,指定 opt
为一个 torch 优化器。
在我们的例子中,我们将使用 Adam
。
from fastai.optimizer import OptimWrapper
from torch import optim
from functools import partial
= partial(OptimWrapper, opt=optim.Adam) opt_func
这就是在框架中创建可工作的优化器所需的全部。您无需声明层组或类似的东西,所有这些都发生在 Learner
类中,接下来我们将进行介绍!
训练
fastai 框架中的训练围绕着 Learner
类进行。这个类将我们之前声明的所有内容关联起来,并允许使用许多不同的调度器和 Callback
快速进行训练。
导入 Learner
的基本方法是:
from fastai.learner import Learner
由于本教程中我们使用显式导出,您会注意到我们以不同的方式导入 Learner
。这是因为 Learner
在整个库中被大量地进行了猴子补丁 (monkey-patched),因此为了最好地利用它,我们需要通过导入模块来获取所有现有的补丁。
import fastai.callback.schedule # To get `fit_one_cycle`, `lr_find`
无论 dataloaders 的类型如何,所有的 Callbacks
仍然可以工作。建议在使用时使用 .all
导入,这样所有的 callbacks 和与 Learner
相关的所有内容都可以一次性导入。
为了构建 Learner(最小化配置),我们需要传入 DataLoaders
、我们的模型、损失函数、可能的一些指标,以及优化器函数。
让我们从 fastai 导入 accuracy
指标。
from fastai.metrics import accuracy
我们也将使用 nll_loss
作为我们的损失函数。
import torch.nn.functional as F
然后构建我们的 Learner
。
= Learner(dls, Net(), loss_func=F.nll_loss, opt_func=opt_func, metrics=accuracy) learn
现在所有内容都关联在一起了,让我们使用 One-Cycle 策略通过 fit_one_cycle
函数来训练我们的模型。我们还将使用 1e-2 的学习率训练一个周期。
值得注意的是,fastai 的训练循环会自动处理训练期间将 tensors 移动到适当设备的问题,并且如果 GPU 可用,默认会使用 GPU。当使用非 fastai 原生的独立 DataLoaders 时,它会查看模型的设备来确定我们希望使用哪个设备进行训练。
要访问上述任何参数,我们可以查看名称类似的属性,例如 learn.dls
、learn.model
、learn.loss_func
等等。
现在开始训练
=1, lr_max=1e-2) learn.fit_one_cycle(n_epoch
周期 | 训练损失 | 验证损失 | 准确率 | 时间 |
---|---|---|---|---|
0 | 0.137776 | 0.048324 | 0.983600 | 00:10 |
/usr/local/lib/python3.7/dist-packages/torch/nn/functional.py:718: UserWarning: Named tensors and all their associated APIs are an experimental feature and subject to change. Please do not use them for anything important until they are released as stable. (Triggered internally at /pytorch/c10/core/TensorImpl.h:1156.)
return torch.max_pool2d(input, kernel_size, stride, padding, dilation, ceil_mode)
现在我们已经训练了模型,让我们模拟将模型打包用于推理或各种预测方法。
导出和预测
要导出训练好的模型,您可以使用 learn.export
方法并结合 load_learner
将其重新加载回来,但需要注意的是,推理 API 的任何部分都将无法工作,因为我们没有使用 fastai 数据 API 进行训练。
相反,您应该保存模型权重,并执行原生的 PyTorch 推理。
我们将在下面快速演示一个例子。
首先,让我们保存模型权重。
通常在使用这种方法时,您还应该存储构建模型的源代码。
'myModel', with_opt=False) learn.save(
Path('models/myModel.pth')
Learner.save
默认也会保存优化器状态。在这种情况下,权重位于 model
键中。在本教程中,我们将此设置为 false
。
您可以看到它显示了我们训练好的权重存储的位置。接下来,让我们将其加载为一个独立的 PyTorch 模型,不与 Learner
关联。
= Net()
new_net = torch.load('models/myModel.pth')
net_dict ; new_net.load_state_dict(net_dict)
最后,让我们使用我们之前声明的 tfms
对单个图像进行预测。
通常在预测时,我们会以与验证集相同的形式预处理数据集,这也是 fastai 通过其 test_dl
和 test_set
方法所做的方式。
由于下载的数据集没有独立的文件供我们使用,我们将从 fastai 下载一组只有数字 3 和 7 的图片,并对其中一张图片进行预测。
from fastai.data.external import untar_data, URLs
= untar_data(URLs.MNIST_SAMPLE) data_path
data_path.ls()
(#3) [Path('/root/.fastai/data/mnist_sample/labels.csv'),Path('/root/.fastai/data/mnist_sample/valid'),Path('/root/.fastai/data/mnist_sample/train')]
我们将获取一张 valid
集中的图片。
= data_path/'valid'/'3'/'8483.png' single_image
使用 Pillow 打开它。
from PIL import Image
= Image.open(single_image)
im ; im.load()
im
接下来我们将应用与验证集相同的 transforms。
= tfms(im); tfmd_im.shape tfmd_im
torch.Size([1, 28, 28])
我们将它设置为一个 batch size 为 1 的批次。
= tfmd_im.unsqueeze(0) tfmd_im
tfmd_im.shape
torch.Size([1, 1, 28, 28])
然后使用我们的模型进行预测。
with torch.no_grad():
new_net.cuda()= tfmd_im.cuda()
tfmd_im = new_net(tfmd_im) preds
让我们看看预测结果。
preds
tensor([[-1.6179e+01, -1.0118e+01, -6.2008e+00, -4.2441e-03, -1.9511e+01,
-8.5174e+00, -2.2341e+01, -1.0145e+01, -6.8038e+00, -7.1086e+00]],
device='cuda:0')
这与 fastai 的输出不太一样,我们需要将其转换为类别标签才能使其类似。为此,我们只需对预测结果的第一个索引进行 argmax。
如果我们使用 fastai DataLoaders,它将把这个 argmax 作为类别名称列表中的索引。由于我们的标签是 0-9,所以 argmax *就是* 我们的标签。
=-1) preds.argmax(dim
tensor([3], device='cuda:0')
我们可以看到它正确地预测了标签 3!