目录

torch.nn 到底是什么?

创建时间: 2018年12月26日 |上次更新时间: 2024-12-03 |上次验证: Nov 05, 2024

作者:杰里米·霍华德,fast.ai。感谢 Rachel Thomas 和 Francisco Ingham。

我们建议将本教程作为笔记本而不是脚本运行。要下载 notebook () 文件, 单击页面顶部的链接。.ipynb

PyTorch 提供了设计优雅的模块和类 torch.nntorch.optimDataset 、 和 DataLoader 来帮助您创建和训练神经网络。 为了充分利用自己的力量并进行定制 对于您的问题,您需要真正了解它们是什么 行为。为了发展这种理解,我们首先要训练基本的神经网络 在 MNIST 数据集上,而不使用这些模型中的任何特征;我们会的 最初只使用最基本的 PyTorch 张量功能。然后,我们将 一次从 、 、 或 增量添加一个功能,准确显示每个部分的作用以及它是如何 使代码更简洁或更灵活。torch.nntorch.optimDatasetDataLoader

本教程假定您已经安装了 PyTorch,并且熟悉 以及 Tensor 运算的基础知识。(如果您熟悉 Numpy 数组 作,您会发现此处使用的 PyTorch 张量作几乎相同)。

MNIST 数据设置

我们将使用经典的 MNIST 数据集 它由手绘数字(介于 0 和 9 之间)的黑白图像组成。

我们将使用 pathlib 来处理路径(Python 3 标准库的一部分),并将 使用 requests 下载数据集。我们只会 import 模块,这样你就可以准确地看到正在发生的事情 在每个点使用。

from pathlib import Path
import requests

DATA_PATH = Path("data")
PATH = DATA_PATH / "mnist"

PATH.mkdir(parents=True, exist_ok=True)

URL = "https://github.com/pytorch/tutorials/raw/main/_static/"
FILENAME = "mnist.pkl.gz"

if not (PATH / FILENAME).exists():
        content = requests.get(URL + FILENAME).content
        (PATH / FILENAME).open("wb").write(content)

这个数据集是 numpy 数组格式,并使用 pickle 存储。 一种特定于 Python 的格式,用于序列化数据。

import pickle
import gzip

with gzip.open((PATH / FILENAME).as_posix(), "rb") as f:
        ((x_train, y_train), (x_valid, y_valid), _) = pickle.load(f, encoding="latin-1")

每张图像都是 28 x 28 的,并存储为长度扁平的行 784 (=28x28) 的分辨率。让我们来看看一个;我们需要将其重塑为 2D 第一。

from matplotlib import pyplot
import numpy as np

pyplot.imshow(x_train[0].reshape((28, 28)), cmap="gray")
# ``pyplot.show()`` only if not on Colab
try:
    import google.colab
except ImportError:
    pyplot.show()
print(x_train.shape)
nn 教程
(50000, 784)

PyTorch 使用 , 而不是 numpy 数组,因此我们需要 转换我们的数据。torch.tensor

import torch

x_train, y_train, x_valid, y_valid = map(
    torch.tensor, (x_train, y_train, x_valid, y_valid)
)
n, c = x_train.shape
print(x_train, y_train)
print(x_train.shape)
print(y_train.min(), y_train.max())
tensor([[0., 0., 0.,  ..., 0., 0., 0.],
        [0., 0., 0.,  ..., 0., 0., 0.],
        [0., 0., 0.,  ..., 0., 0., 0.],
        ...,
        [0., 0., 0.,  ..., 0., 0., 0.],
        [0., 0., 0.,  ..., 0., 0., 0.],
        [0., 0., 0.,  ..., 0., 0., 0.]]) tensor([5, 0, 4,  ..., 8, 4, 8])
torch.Size([50000, 784])
tensor(0) tensor(9)

从零开始的神经网络(没有torch.nn)

让我们首先使用 PyTorch 张量作创建一个模型。我们假设 您已经熟悉神经网络的基础知识。(如果您不是,您可以 在 course.fast.ai) 学习它们。

PyTorch 提供了创建随机张量或零填充张量的方法,我们将 用于创建简单线性模型的权重和偏差。这些只是常规的 张量,并添加了一个非常特殊的功能:我们告诉 PyTorch 它们需要一个 梯度。这会导致 PyTorch 记录对张量完成的所有作, 这样它就可以自动计算反向传播期间的梯度!

对于权重,我们在初始化设置,因为我们 不希望该步骤包含在渐变中。(请注意,尾随 PyTorch 表示该作是就地执行的。requires_grad_

注意

我们在这里使用 Xavier 初始化(乘以 )初始化权重。1/sqrt(n)

import math

weights = torch.randn(784, 10) / math.sqrt(784)
weights.requires_grad_()
bias = torch.zeros(10, requires_grad=True)

由于 PyTorch 能够自动计算梯度,我们可以 使用任何标准 Python 函数(或可调用对象)作为模型!所以 让我们只写一个普通的矩阵乘法和广播加法 创建简单的线性模型。我们还需要一个激活函数,所以 我们将编写log_softmax并使用它。请记住:尽管 PyTorch 提供大量预先编写的损失函数、激活函数和 等等,您可以使用普通 Python 轻松编写自己的代码。PyTorch 将 甚至为您的函数创建快速的 GPU 或矢量化 CPU 代码 自然而然。

def log_softmax(x):
    return x - x.exp().sum(-1).log().unsqueeze(-1)

def model(xb):
    return log_softmax(xb @ weights + bias)

在上面,代表矩阵乘法运算。我们将调用 我们的函数处理一批数据(在本例中为 64 张图像)。这是 一次向前传球。请注意,我们的预测不会比 random 在这个阶段,因为我们从 random 权重开始。@

bs = 64  # batch size

xb = x_train[0:bs]  # a mini-batch from x
preds = model(xb)  # predictions
preds[0], preds.shape
print(preds[0], preds.shape)
tensor([-2.5452, -2.0790, -2.1832, -2.6221, -2.3670, -2.3854, -2.9432, -2.4391,
        -1.8657, -2.0355], grad_fn=<SelectBackward0>) torch.Size([64, 10])

如您所见,张量不仅包含张量值,还包含一个 gradient 函数。我们稍后将使用它来执行反向传播。preds

让我们实现负对数似然作为损失函数 (同样,我们可以使用标准 Python):

def nll(input, target):
    return -input[range(target.shape[0]), target].mean()

loss_func = nll

让我们用随机模型检查我们的损失,看看我们是否有所改善 稍后进行反向传播传递。

yb = y_train[0:bs]
print(loss_func(preds, yb))
tensor(2.4020, grad_fn=<NegBackward0>)

我们还实现一个函数来计算模型的准确性。 对于每个预测,如果具有最大值的索引与 target 值,则预测是正确的。

def accuracy(out, yb):
    preds = torch.argmax(out, dim=1)
    return (preds == yb).float().mean()

让我们检查一下随机模型的准确性,这样我们就可以看看我们的 准确性随着我们损失的改善而提高。

print(accuracy(preds, yb))
tensor(0.0938)

我们现在可以运行训练循环。对于每次迭代,我们将:

  • 选择一小批数据(大小为bs)

  • 使用模型进行预测

  • 计算损失

  • loss.backward()更新模型的梯度(在本例中为 )和 。weightsbias

现在,我们使用这些梯度来更新权重和偏差。我们这样做 ,因为我们不需要这些 要记录的动作,用于我们下一次计算梯度。您可以阅读 有关 PyTorch 的 Autograd 如何记录作的更多信息,请参阅此处torch.no_grad()

然后,我们将 gradients 设置为 0,以便我们为下一个循环做好准备。 否则,我们的梯度将记录所有作的运行计数 那已经发生了(即 将渐变添加到任何 已经存储,而不是替换它们)。loss.backward()

提示

您可以使用标准 python 调试器逐步执行 PyTorch 代码,允许您检查每个步骤中的各种变量值。 在下面取消注释以尝试一下。set_trace()

from IPython.core.debugger import set_trace

lr = 0.5  # learning rate
epochs = 2  # how many epochs to train for

for epoch in range(epochs):
    for i in range((n - 1) // bs + 1):
        #         set_trace()
        start_i = i * bs
        end_i = start_i + bs
        xb = x_train[start_i:end_i]
        yb = y_train[start_i:end_i]
        pred = model(xb)
        loss = loss_func(pred, yb)

        loss.backward()
        with torch.no_grad():
            weights -= weights.grad * lr
            bias -= bias.grad * lr
            weights.grad.zero_()
            bias.grad.zero_()

就是这样:我们已经创建并训练了一个最小的神经网络(在本例中为 逻辑回归,因为我们没有隐藏层)完全从头开始!

让我们检查一下损失和准确性,并将其与我们得到的进行比较 早些时候。我们预计损失将减少,并且准确性将达到 已经增加,他们也有。

print(loss_func(model(xb), yb), accuracy(model(xb), yb))
tensor(0.0813, grad_fn=<NegBackward0>) tensor(1.)

torch.nn.functional

我们现在将重构我们的代码,以便它只执行与以前相同的作 我们将开始利用 PyTorch 的类来使其更简洁 和灵活。从这里开始的每一步,我们应该将代码设为一个或多个 OF:更短、更容易理解和/或更灵活。nn

第一步也是最简单的一步是通过将 手写的 activation 和 loss 函数以及 from (通常按照约定导入到命名空间中)。此模块 包含库中的所有函数(而 library 包含类)。以及广泛的损失和激活 函数,您还可以在此处找到一些用于创建神经的便捷函数 网络,例如池化函数。(还有做卷积的函数, 线性层等,但正如我们将看到的,这些通常使用 库的其他部分。torch.nn.functionalFtorch.nn

如果您使用负对数似然损失和对数 softmax 激活, 然后 Pytorch 提供了一个函数,该函数将 两个。因此,我们甚至可以从模型中删除激活函数。F.cross_entropy

import torch.nn.functional as F

loss_func = F.cross_entropy

def model(xb):
    return xb @ weights + bias

请注意,我们不再调用 function 。让我们 确认我们的 loss 和 accuracy 和以前一样:log_softmaxmodel

print(loss_func(model(xb), yb), accuracy(model(xb), yb))
tensor(0.0813, grad_fn=<NllLossBackward0>) tensor(1.)

重构使用nn.Module

接下来,我们将使用 和 ,以获得更清晰、更 简洁的训练循环。We 子类(它本身是一个类和 能够跟踪状态)。在本例中,我们想要创建一个类,该类 保存前进步骤的权重、偏差和方法。 具有 属性和方法的数量(如 和 ) 我们将使用。nn.Modulenn.Parameternn.Modulenn.Module.parameters().zero_grad()

注意

nn.Module(大写 M)是 PyTorch 特有的概念,并且是一个 类。 不要与 Python 混淆 (小写)模块的概念, 这是一个可以导入的 Python 代码文件。nn.Modulem

from torch import nn

class Mnist_Logistic(nn.Module):
    def __init__(self):
        super().__init__()
        self.weights = nn.Parameter(torch.randn(784, 10) / math.sqrt(784))
        self.bias = nn.Parameter(torch.zeros(10))

    def forward(self, xb):
        return xb @ self.weights + self.bias

由于我们现在使用的是对象,而不仅仅是使用函数,因此 首先必须实例化我们的模型:

现在我们可以用和以前一样的方式计算损失。请注意,对象就像它们是函数一样使用(即它们是可调用的),但在幕后 Pytorch 会自动调用我们的方法。nn.Moduleforward

print(loss_func(model(xb), yb))
tensor(2.3096, grad_fn=<NllLossBackward0>)

以前,对于我们的训练循环,我们必须更新每个参数的值 by name 的 Grad 中,并分别手动将每个参数的 grad 归零,如下所示:

with torch.no_grad():
    weights -= weights.grad * lr
    bias -= bias.grad * lr
    weights.grad.zero_()
    bias.grad.zero_()

现在我们可以利用 model.parameters() 和 model.zero_grad() (它们 都由 PyTorch 为 ) 定义,以使这些步骤更简洁 并且不太容易出现忘记某些参数的错误,特别是 如果我们有一个更复杂的模型:nn.Module

with torch.no_grad():
    for p in model.parameters(): p -= p.grad * lr
    model.zero_grad()

我们将我们的小训练循环包装在一个函数中,以便我们可以运行它 稍后再来。fit

def fit():
    for epoch in range(epochs):
        for i in range((n - 1) // bs + 1):
            start_i = i * bs
            end_i = start_i + bs
            xb = x_train[start_i:end_i]
            yb = y_train[start_i:end_i]
            pred = model(xb)
            loss = loss_func(pred, yb)

            loss.backward()
            with torch.no_grad():
                for p in model.parameters():
                    p -= p.grad * lr
                model.zero_grad()

fit()

让我们仔细检查一下我们的损失是否下降了:

print(loss_func(model(xb), yb))
tensor(0.0821, grad_fn=<NllLossBackward0>)

重构使用nn.Linear

我们将继续重构我们的代码。而不是手动定义和 初始化 和 和 计算 ,我们将改用 Pytorch 类 nn.Linear 的 linear 层,它为我们完成了所有这些工作。Pytorch 有多种类型的 预定义层可以大大简化我们的代码,并且通常使 也更快。self.weightsself.biasxb  @ self.weights + self.bias

class Mnist_Logistic(nn.Module):
    def __init__(self):
        super().__init__()
        self.lin = nn.Linear(784, 10)

    def forward(self, xb):
        return self.lin(xb)

我们实例化我们的模型,并以与以前相同的方式计算损失:

model = Mnist_Logistic()
print(loss_func(model(xb), yb))
tensor(2.3313, grad_fn=<NllLossBackward0>)

我们仍然能够使用与以前相同的方法。fit

fit()

print(loss_func(model(xb), yb))
tensor(0.0819, grad_fn=<NllLossBackward0>)

重构使用torch.optim

Pytorch 还有一个包含各种优化算法的包。 我们可以使用 optimizer 中的方法来向前迈出一步,而不是 手动更新每个参数。torch.optimstep

这将让我们替换之前手动编码的优化步骤:

with torch.no_grad():
    for p in model.parameters(): p -= p.grad * lr
    model.zero_grad()

而是只使用:

(optim.zero_grad()将梯度重置为 0,我们需要在 计算下一个小批量的梯度。

from torch import optim

我们将定义一个小函数来创建我们的模型和优化器,因此我们 将来可以重复使用它。

def get_model():
    model = Mnist_Logistic()
    return model, optim.SGD(model.parameters(), lr=lr)

model, opt = get_model()
print(loss_func(model(xb), yb))

for epoch in range(epochs):
    for i in range((n - 1) // bs + 1):
        start_i = i * bs
        end_i = start_i + bs
        xb = x_train[start_i:end_i]
        yb = y_train[start_i:end_i]
        pred = model(xb)
        loss = loss_func(pred, yb)

        loss.backward()
        opt.step()
        opt.zero_grad()

print(loss_func(model(xb), yb))
tensor(2.2659, grad_fn=<NllLossBackward0>)
tensor(0.0810, grad_fn=<NllLossBackward0>)

使用 Dataset 进行重构

PyTorch 有一个抽象的 Dataset 类。数据集可以是具有 一个函数(由 Python 的标准函数调用)和 一个函数作为索引的一种方式。本教程将介绍一个创建自定义类的一个很好的示例 作为 的子类。__len__len__getitem__FacialLandmarkDatasetDataset

PyTorch 的 TensorDataset 是一个包装张量的 Dataset。通过定义索引的长度和方式, 这也为我们提供了一种沿第一个 Tensor 的维度。这将使您更容易访问 自变量和因变量与我们训练的同一行。

from torch.utils.data import TensorDataset

两者 和 可以组合成一个 。 这将更容易迭代和切片。x_trainy_trainTensorDataset

以前,我们必须分别迭代 和 values 的小批量:xy

xb = x_train[start_i:end_i]
yb = y_train[start_i:end_i]

现在,我们可以一起执行这两个步骤:

xb,yb = train_ds[i*bs : i*bs+bs]
model, opt = get_model()

for epoch in range(epochs):
    for i in range((n - 1) // bs + 1):
        xb, yb = train_ds[i * bs: i * bs + bs]
        pred = model(xb)
        loss = loss_func(pred, yb)

        loss.backward()
        opt.step()
        opt.zero_grad()

print(loss_func(model(xb), yb))
tensor(0.0826, grad_fn=<NllLossBackward0>)

重构使用DataLoader

PyTorch 负责管理批处理。您可以 从任何 . 让作更轻松 迭代批处理。而不必使用 , 它会自动给我们每个小批量。DataLoaderDataLoaderDatasetDataLoadertrain_ds[i*bs : i*bs+bs]DataLoader

from torch.utils.data import DataLoader

train_ds = TensorDataset(x_train, y_train)
train_dl = DataLoader(train_ds, batch_size=bs)

以前,我们的循环迭代了像这样的 batchs:(xb, yb)

for i in range((n-1)//bs + 1):
    xb,yb = train_ds[i*bs : i*bs+bs]
    pred = model(xb)

现在,我们的循环更加清晰,因为它是从数据加载器自动加载的:(xb, yb)

for xb,yb in train_dl:
    pred = model(xb)
model, opt = get_model()

for epoch in range(epochs):
    for xb, yb in train_dl:
        pred = model(xb)
        loss = loss_func(pred, yb)

        loss.backward()
        opt.step()
        opt.zero_grad()

print(loss_func(model(xb), yb))
tensor(0.0818, grad_fn=<NllLossBackward0>)

感谢 PyTorch 的 、 、 和 、 我们的训练循环现在大大缩小了,并且更易于理解。让我们 现在尝试添加在实践中创建有效模型所需的基本功能。nn.Modulenn.ParameterDatasetDataLoader

添加验证

在第 1 部分中,我们只是试图建立一个合理的训练循环 用于我们的训练数据。实际上,您应该始终拥有 验证集,按顺序 来确定您是否过度拟合。

对训练数据进行随机排序对于防止 batchs 和过度拟合之间的相关性非常重要。另一方面, 无论我们是否对验证集进行随机排序,验证损失都是相同的。 由于 shuffle 需要额外的时间,因此对验证数据进行 shuffle 是没有意义的。

我们将对验证集使用两倍的批处理大小 那是为了训练集。这是因为验证集不会 需要反向传播,因此占用的内存更少(它不需要 存储渐变)。我们利用这一点来使用更大的 batch size 并更快地计算损失。

train_ds = TensorDataset(x_train, y_train)
train_dl = DataLoader(train_ds, batch_size=bs, shuffle=True)

valid_ds = TensorDataset(x_valid, y_valid)
valid_dl = DataLoader(valid_ds, batch_size=bs * 2)

我们将在每个 epoch 结束时计算并打印验证损失。

(请注意,我们总是在训练之前和推理之前调用,因为这些被 和 等层使用,以确保这些不同阶段的适当行为。model.train()model.eval()nn.BatchNorm2dnn.Dropout

model, opt = get_model()

for epoch in range(epochs):
    model.train()
    for xb, yb in train_dl:
        pred = model(xb)
        loss = loss_func(pred, yb)

        loss.backward()
        opt.step()
        opt.zero_grad()

    model.eval()
    with torch.no_grad():
        valid_loss = sum(loss_func(model(xb), yb) for xb, yb in valid_dl)

    print(epoch, valid_loss / len(valid_dl))
0 tensor(0.3048)
1 tensor(0.2872)

创建 fit() 和 get_data()

我们现在要做一些自己的重构。由于我们经历了类似的 处理两次计算训练集和 validation set 中,让我们将其转换为自己的函数 ,该函数 计算一个批次的损失。loss_batch

我们为训练集传入一个优化器,并使用它来执行 backprop 的对于验证集,我们没有传递优化器,因此 方法不执行反向传播。

def loss_batch(model, loss_func, xb, yb, opt=None):
    loss = loss_func(model(xb), yb)

    if opt is not None:
        loss.backward()
        opt.step()
        opt.zero_grad()

    return loss.item(), len(xb)

fit运行必要的作来训练我们的模型并计算 每个 epoch 的训练和验证损失。

import numpy as np

def fit(epochs, model, loss_func, opt, train_dl, valid_dl):
    for epoch in range(epochs):
        model.train()
        for xb, yb in train_dl:
            loss_batch(model, loss_func, xb, yb, opt)

        model.eval()
        with torch.no_grad():
            losses, nums = zip(
                *[loss_batch(model, loss_func, xb, yb) for xb, yb in valid_dl]
            )
        val_loss = np.sum(np.multiply(losses, nums)) / np.sum(nums)

        print(epoch, val_loss)

get_data返回训练集和验证集的 dataloader。

def get_data(train_ds, valid_ds, bs):
    return (
        DataLoader(train_ds, batch_size=bs, shuffle=True),
        DataLoader(valid_ds, batch_size=bs * 2),
    )

现在,我们获取数据加载器并拟合 model 可以在 3 行代码中运行:

train_dl, valid_dl = get_data(train_ds, valid_ds, bs)
model, opt = get_model()
fit(epochs, model, loss_func, opt, train_dl, valid_dl)
0 0.2939354367017746
1 0.3258970756947994

您可以使用这 3 行基本代码来训练各种模型。 让我们看看我们是否可以使用它们来训练卷积神经网络 (CNN)!

切换到 CNN

我们现在要构建具有三个卷积层的神经网络。 因为上一节中的任何函数都不假定任何关于 模型形式,我们将能够使用它们来训练 CNN,而无需进行任何修改。

我们将使用 PyTorch 的预定义 Conv2d 类 作为我们的卷积层。我们定义了一个具有 3 个卷积层的 CNN。 每个卷积后跟一个 ReLU。最后,我们执行 平均池。(请注意,这是 PyTorch 的 Numpy 的viewreshape)

class Mnist_CNN(nn.Module):
    def __init__(self):
        super().__init__()
        self.conv1 = nn.Conv2d(1, 16, kernel_size=3, stride=2, padding=1)
        self.conv2 = nn.Conv2d(16, 16, kernel_size=3, stride=2, padding=1)
        self.conv3 = nn.Conv2d(16, 10, kernel_size=3, stride=2, padding=1)

    def forward(self, xb):
        xb = xb.view(-1, 1, 28, 28)
        xb = F.relu(self.conv1(xb))
        xb = F.relu(self.conv2(xb))
        xb = F.relu(self.conv3(xb))
        xb = F.avg_pool2d(xb, 4)
        return xb.view(-1, xb.size(1))

lr = 0.1

动量是 随机梯度下降,也考虑了以前的更新 并且通常会带来更快的训练。

model = Mnist_CNN()
opt = optim.SGD(model.parameters(), lr=lr, momentum=0.9)

fit(epochs, model, loss_func, opt, train_dl, valid_dl)
0 0.3646130460739136
1 0.26228193019628526

nn.Sequential

torch.nn有另一个方便的类,我们可以用它来简化我们的代码: Sequential . 对象在 sequential 方式。这是编写神经网络的一种更简单的方法。Sequential

为了利用这一点,我们需要能够轻松地从给定函数定义自定义层。例如,PyTorch 不会 有一个 View Layer,我们需要为我们的网络创建一个。 将创建一个层,然后我们在使用 定义网络时可以使用该层。LambdaSequential

class Lambda(nn.Module):
    def __init__(self, func):
        super().__init__()
        self.func = func

    def forward(self, x):
        return self.func(x)


def preprocess(x):
    return x.view(-1, 1, 28, 28)

创建的模型很简单:Sequential

model = nn.Sequential(
    Lambda(preprocess),
    nn.Conv2d(1, 16, kernel_size=3, stride=2, padding=1),
    nn.ReLU(),
    nn.Conv2d(16, 16, kernel_size=3, stride=2, padding=1),
    nn.ReLU(),
    nn.Conv2d(16, 10, kernel_size=3, stride=2, padding=1),
    nn.ReLU(),
    nn.AvgPool2d(4),
    Lambda(lambda x: x.view(x.size(0), -1)),
)

opt = optim.SGD(model.parameters(), lr=lr, momentum=0.9)

fit(epochs, model, loss_func, opt, train_dl, valid_dl)
0 0.3330025281429291
1 0.22993727023601532

包皮DataLoader

我们的 CNN 相当简洁,但它仅适用于 MNIST,因为:
  • 它假设输入是一个 28*28 长的向量

  • 它假设最终的 CNN 网格大小为 4*4(因为这是我们使用的平均池化内核大小)

让我们摆脱这两个假设,这样我们的模型就可以使用任何 2D 单通道图像。首先,我们可以通过以下方式删除初始 Lambda 层 将数据预处理移动到生成器中:

def preprocess(x, y):
    return x.view(-1, 1, 28, 28), y


class WrappedDataLoader:
    def __init__(self, dl, func):
        self.dl = dl
        self.func = func

    def __len__(self):
        return len(self.dl)

    def __iter__(self):
        for b in self.dl:
            yield (self.func(*b))

train_dl, valid_dl = get_data(train_ds, valid_ds, bs)
train_dl = WrappedDataLoader(train_dl, preprocess)
valid_dl = WrappedDataLoader(valid_dl, preprocess)

接下来,我们可以用 替换 ,它 允许我们定义所需的输出张量的大小,而不是 我们拥有的 Importing Tensor。因此,我们的模型将与任何 size 输入。nn.AvgPool2dnn.AdaptiveAvgPool2d

model = nn.Sequential(
    nn.Conv2d(1, 16, kernel_size=3, stride=2, padding=1),
    nn.ReLU(),
    nn.Conv2d(16, 16, kernel_size=3, stride=2, padding=1),
    nn.ReLU(),
    nn.Conv2d(16, 10, kernel_size=3, stride=2, padding=1),
    nn.ReLU(),
    nn.AdaptiveAvgPool2d(1),
    Lambda(lambda x: x.view(x.size(0), -1)),
)

opt = optim.SGD(model.parameters(), lr=lr, momentum=0.9)

让我们试一试:

fit(epochs, model, loss_func, opt, train_dl, valid_dl)
0 0.3212135115623474
1 0.21439074140787123

使用 GPU

如果您有幸能够使用支持 CUDA 的 GPU(您可以 从大多数云提供商处以大约 0.50 美元/小时的价格租用一个)您可以 使用它来加快您的代码速度。首先检查您的 GPU 是否正常工作 Pytorch 中:

True

然后为其创建一个 device 对象:

让我们更新以将批次移动到 GPU:preprocess

def preprocess(x, y):
    return x.view(-1, 1, 28, 28).to(dev), y.to(dev)


train_dl, valid_dl = get_data(train_ds, valid_ds, bs)
train_dl = WrappedDataLoader(train_dl, preprocess)
valid_dl = WrappedDataLoader(valid_dl, preprocess)

最后,我们可以将模型移动到 GPU。

model.to(dev)
opt = optim.SGD(model.parameters(), lr=lr, momentum=0.9)

您现在应该会发现它运行得更快:

fit(epochs, model, loss_func, opt, train_dl, valid_dl)
0 0.18173173379898072
1 0.1716031593978405

结束语

我们现在有一个通用的数据管道和训练循环,您可以将其用于 使用 Pytorch 训练多种类型的模型。了解训练模型有多么简单 现在可以了,看看 mnist_sample 笔记本

当然,您需要添加很多内容,例如数据增强、 超参数优化、监控训练、迁移学习等。 这些功能在已开发的 fastai 库中可用 使用本教程中所示的相同设计方法,提供自然的 希望进一步发展其模型的从业者下一步。

在本教程开始时,我们承诺将通过示例来解释 、 、 和 。那么让我们总结一下 我们所看到的:torch.nntorch.optimDatasetDataLoader

  • torch.nn:

    • Module: 创建一个行为类似于函数的可调用对象,但也可以 包含状态(例如神经网络层权重)。它知道它是什么 包含并可以归零它们的所有梯度,循环遍历它们以进行权重更新等。Parameter

    • Parameter:一个张量的包装器,告诉 A 它具有权重 需要在反向传播期间更新。仅更新设置了 requires_grad 属性的张量Module

    • functional: 一个模块(通常按照约定导入到命名空间中) 它包含激活函数、损失函数等,以及 non-stateful 层的版本,例如卷积层和线性层。F

  • torch.optim:包含优化器,例如 ,用于更新权重 ofSGDParameter

  • Dataset:具有 a 和 a 的对象的抽象接口 , 包括 Pytorch 提供的类,例如__len____getitem__TensorDataset

  • DataLoader:获取 any 并创建一个返回数据批次的迭代器。Dataset

脚本总运行时间:(0 分 36.632 秒)

由 Sphinx-Gallery 生成的图库

文档

访问 PyTorch 的全面开发人员文档

查看文档

教程

获取面向初学者和高级开发人员的深入教程

查看教程

资源

查找开发资源并解答您的问题

查看资源