Pytorch到fastai的详细信息

#跳过
! [ -e /content ] && pip install -Uqq fastai  # 在Colab上升级fastai

逐步将原始PyTorch集成到fastai框架中。

在本教程中,我们将从头开始训练MNIST(类似于这里的简短教程),使用纯PyTorch,并逐步将其添加到fastai框架中。这样做的内容包括: - PyTorch数据加载器 - PyTorch模型 - PyTorch优化器

而在fastai中,我们将简单地使用训练循环(或Learner类)

在本教程中,由于一般人更习惯于显式导出,我们将在fastai库中使用显式导出,但也要理解你可以通过from fastai.vision.all import *自动获取所有这些导入。

通常,推荐这样做,因为库中有猴子补丁,但是这也可以避免,稍后会展示。

数据

如标题所述,我们将通过 torchvision 模块简单地加载数据集。

这包括加载数据集以及为 DataLoaders 准备数据(包括转换)。

首先,我们将进行导入:

import torch, torchvision
import torchvision.transforms as transforms

接下来,我们可以定义一些最小的转换,将原始的双通道图像转换为可训练的张量,并对其进行归一化:

均值和标准差来自MNIST数据集

tfms = transforms.Compose([transforms.ToTensor(),
                                 transforms.Normalize((0.1307,), (0.3081))
])

在最终创建我们的训练和测试DataLoaders之前,我们需要下载数据集并应用我们的转换。

from torchvision import datasets
from torch.utils.data import DataLoader

首先,让我们下载一个训练和测试(或在fastai框架中称为验证)数据集。

train_dset = datasets.MNIST('../data', train=True, download=True, transform=tfms)
valid_dset = datasets.MNIST('../data', train=False, transform=tfms)

接下来,我们将定义一些超参数,以便在创建各个 DataLoader 时传递。

我们将训练时的批量大小设置为 256,而在验证集时设置为 512。

我们还将使用一个工作线程,并固定内存:

train_loader = DataLoader(train_dset, batch_size=256, 
                          shuffle=True, num_workers=1, pin_memory=True)

test_loader = DataLoader(valid_dset, batch_size=512,
                         shuffle=False, num_workers=1, pin_memory=True)

现在我们有了原始的 PyTorch DataLoader。要在 fastai 框架中使用它们,只需将其包装在 fastai 的 DataLoaders 类中,该类可以接收任意数量的 DataLoader 对象并将它们组合成一个:

from fastai.data.core import DataLoaders
dls = DataLoaders(train_loader, test_loader)

我们现在已经为 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__(
            nn.Conv2d(1, 32, 3, 1), nn.ReLU(),
            nn.Conv2d(32, 64, 3, 1), 
            # 朝模特点点头
            nn.MaxPool2d(2), nn.Dropout2d(0.25),
            Flatten(), nn.Linear(9216, 128), nn.ReLU(),
            nn.Dropout2d(0.5), nn.Linear(128, 10), nn.LogSoftmax(dim=1)
        )

优化器

在fastai框架中,使用原生的PyTorch优化器变得非常简单,这要感谢OptimWrapper接口。

只需编写一个partial函数,其中将opt指定为torch优化器。

在我们的示例中,我们将使用Adam

from fastai.optimizer import OptimWrapper

from torch import optim
from functools import partial
opt_func = partial(OptimWrapper, opt=optim.Adam)

这就是在框架中制作一个有效优化器所需的一切。您不需要声明层组或任何类似的东西,这一切都发生在 Learner 类中,我们接下来将进行这部分!

训练

在fastai框架中的训练围绕着Learner类进行。这个类将我们之前声明的一切联系在一起,并允许使用许多不同的调度器和Callback快速进行训练。
导入Learner的基本方式是
from fastai.learner import Learner

由于我们在这个教程中使用显式导入,您会注意到我们将以不同的方式导入Learner。这是因为Learner在整个库中被大量猴子补丁,因此为了最好地利用它,我们需要通过导入模块来获取所有现有的补丁。

import fastai.callback.schedule # 要获取 `fit_one_cycle` 和 `lr_find`
Note

所有 Callbacks 仍然会正常工作,无论数据加载器的类型如何。建议在需要时使用 .all 导入,这样可以一次性导入所有回调以及与 Learner 相关的内容。

要构建Learner(最小化),我们需要传入DataLoaders、我们的模型、一个损失函数、可能还包括一些要使用的指标,以及一个优化器函数。

让我们从fastai导入accuracy指标:

from fastai.metrics import accuracy

我们也将使用 nll_loss 作为我们的损失函数。

import torch.nn.functional as F

并构建我们的 Learner

learn = Learner(dls, Net(), loss_func=F.nll_loss, opt_func=opt_func, metrics=accuracy)

现在一切都连在一起了,让我们通过 fit_one_cycle 函数训练我们的模型,使用 1e-2 的学习率进行一个纪元的训练。

需要注意的是,fastai 的训练循环会自动处理在训练过程中将张量移动到适当的设备,并将默认使用 GPU(如果可用)。当使用非 fastai 原生的单独 DataLoaders 时,它会查看模型的设备,以确定我们想要使用哪个设备进行训练。

要访问上述任何参数,我们需要查找类似命名的属性,例如 learn.dlslearn.modellearn.loss_func 等等。

现在让我们开始训练:

learn.fit_one_cycle(n_epoch=1, lr_max=1e-2)
epoch train_loss valid_loss accuracy time
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 推理。

下面我们将快速演示一个例子。

首先,让我们保存模型权重:

Note

通常在使用这种方法时,您还应该存储用于构建模型的源代码

learn.save('myModel', with_opt=False)
Path('models/myModel.pth')
Note

Learner.save 默认也会保存优化器的状态。在这样做时,权重位于 model 键中。我们将在本教程中将其设置为 false

您可以看到它向我们显示了存储我们训练权重的位置。接下来,让我们将其加载为一个与 Learner 不相关的独立 PyTorch 模型:

new_net = Net()
net_dict = torch.load('models/myModel.pth') 
new_net.load_state_dict(net_dict);

最后,让我们使用之前声明的 tfms 对单个图像进行预测。

在一般情况下,我们在与验证集相同的形式下对数据集进行预处理,fastai 也通过他们的 test_dltest_set 方法这样做。

由于下载的数据集没有单独的文件供我们使用,我们将从 fastai 下载仅包含 3 和 7 的数据集,并对其中一张图像进行预测:

from fastai.data.external import untar_data, URLs
data_path = untar_data(URLs.MNIST_SAMPLE)
100.14% [3219456/3214948 00:00<00:00]
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 图片。

single_image = data_path/'valid'/'3'/'8483.png'

在Pillow中打开它:

from PIL import Image
im = Image.open(single_image)
im.load();
im

接下来,我们将对验证集应用相同的转换。

tfmd_im = tfms(im); tfmd_im.shape
torch.Size([1, 28, 28])

我们将其设置为批量大小为1:

tfmd_im = tfmd_im.unsqueeze(0)
tfmd_im.shape
torch.Size([1, 1, 28, 28])

然后用我们的模型进行预测:

with torch.no_grad():
    new_net.cuda()
    tfmd_im = tfmd_im.cuda()
    preds = new_net(tfmd_im)

让我们来看看预测结果:

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,它会将此作为类别名称列表的索引。由于我们的标签是0-9,因此argmax就是我们的标签:

preds.argmax(dim=-1)
tensor([3], device='cuda:0')

我们可以看到它正确预测了标签为3!