Pytorch学习-TORCH.NN

Pytorch学习:TORCH.NN文档翻译

仅供个人学习使用,参考总结了一些文档,在下面内容中会注释。


文章目录

  • Pytorch学习:TORCH.NN文档翻译
  • 前言
  • WHAT IS TORCH.NN REALLY?
  • 一· MNIST数据下载
  • 二. 神经网络从无到有 (no torch.nn)
  • 三. 使用torch.nn.functional
  • 四. 使用 nn.Module 重构代码
  • 五. 使用 nn.Linear 重构代码
  • 六. 使用optim重构代码
  • 七. 使用 Dataset 处理数据
  • 八. 使用 DataLoader 加载数据
  • 九. 添加测试集
  • 十. 创建 fit() 和 get_data()
  • 十一. 使用卷积神经网络
  • 十二. 使用nn.Sequential 搭建网络
  • 十三. 封装 DataLoader
  • 十四. 使用GPU
  • 十五. 总结


前言

TORCH.NN文档翻译:帮助学习torch实现神经网络模型的搭建(https://pytorch.org/tutorials/beginner/nn_tutorial.html)


WHAT IS TORCH.NN REALLY?

PyTorch提供了设计优雅的模块和类:torch.nntorch.optimDatasetDataLoader 来帮助您创建和训练神经网络。为了充分利用这些模块和类,并针对你的问题进行自定义,你需要真正确切地了解他们在做什么。为了深入理解,我们将首先在 MNIST 数据集上训练基本的神经网络,而不使用这些模块中的任何特征;我们最初将只使用最基本的 PyTorch tensor函数,然后,我们将每次递增地从torch.nntorch.optimDatasetDataLoader中添加一个特性,准确地展示每一部分的功能,以及它如何使代码更简洁或更灵活。

本教程假设您已经安装了PyTorch,并且熟悉tensor操作的基础知识。(如果您熟悉Numpy数组操作,您会发现这里使用的PyTorch tensor操作几乎相同)。

一· MNIST数据下载

我们将使用经典的 MNIST 手写数据集(0到9之间的黑白图像)。

我们将使用pathlib库来处理路径(Python3自带的标准库),并使用request下载数据集。我们将只在使用模块时才导入它们,这样你就可以清楚地看到在每个点上使用了什么。

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/master/_static/"
FILENAME = "mnist.pkl.gz"

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

该数据集采用 numpy 数组格式,并且使用 pickle 存储,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")

每个训练图片分辨率都是28x28,并存储为一个长度为784 (=28x28)的行。我们首先需要把它转换成2维(2d)的图像(28x28)。

from matplotlib import pyplot
import numpy as np

pyplot.imshow(x_train[0].reshape((28, 28)), cmap="gray")
print(x_train.shape)

Pytorch学习-TORCH.NN_第1张图片
Out:

(50000,784)

PyTorch使用 torch.tensor,而不是 numpy 数组,所以我们需要转换数据类型。

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
x_train, x_train.shape, y_train.min(), y_train.max()
print(x_train, y_train)
print(x_train.shape)
print(y_train.min(), y_train.max())

Out:

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)

二. 神经网络从无到有 (no torch.nn)

我们首先创建一个只使用 PyTorch tensor 运算的模型。我们假设你已经熟悉了神经网络的基础知识。(如果你不是,你可以在 course.fast.ai中学习它们)。

PyTorch提供了创建随机或 zero-filled tensor 的方法,我们将使用这些方法为一个简单的线性模型创建权重和偏差。这些只是普通张量,有一个特别的补充:我们告诉PyTorch它们需要一个梯度。这使得PyTorch记录对张量所做的所有操作,以便在反向传播过程中自动计算梯度。

对于 weights,我们在初始化之后设置requires_grad,因为我们不希望梯度中包含该步骤。(注意 PyTorch 中末尾的 _表示操作 在in-place中执行 。)

NOTE
我们用 Xavier initialisation来初始化weights(通过乘以1/sqrt(n))。

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

由于PyTorch能够自动计算梯度,我们可以使用任何标准的Python函数(或可调用对象)作为模型。所以让我们写一个简单的矩阵乘法和加法来创建一个简单的线性模型。我们还需要一个激活函数,因此我们将编写log_softmax并使用它。请记住:尽管PyTorch提供了许多预先编写的 loss functions、activation functions等,但您可以使用普通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张图像)调用函数。这是一次向前传递。注意,在这个阶段,我们的预测不会比随机更好,因为我们从随机权重开始的。

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)

Out:

tensor([-3.0996, -1.8860, -2.5834, -2.1306, -2.3398, -2.4134, -1.9404, -2.8883,
        -2.5544, -1.9238], grad_fn=<SelectBackward>) torch.Size([64, 10])

如你所见 preds tensor不仅包含张量值,还包含一个梯度函数。稍后我们将使用它来做反向传播。
我们把 negative log-likelihood 作为损失函数,又叫交叉熵损失函数(同样,我们可以使用标准Python):

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

loss_func = nll

我们用loss检验随机模型,稍后就能看到再反向传播之后是否会改善模型性能。

yb = y_train[0:bs]
print(loss_func(preds, yb))

Out:

tensor(2.3493, grad_fn=<NegBackward>)

我们用一个准确度函数来计算我们的模型的精度。对于每个预测,如果最大值的索引与目标值匹配,则预测是正确的。

def accuracy(out, yb):
    preds = torch.argmax(out, dim=1) # 得到最大值的索引
    return (preds == yb).float().mean()

我们检查一下我们的随机模型的准确性,这样我们就可以看到我们的准确性是否会随着损失的提升而提高。

print(accuracy(preds, yb))

Out:

tensor(0.0938)

现在我们循环训练模型。对于每一次迭代,我们都会这样做:
  • 选择一小批数据(大小为一个batch size)
  • 使用模型进行预测
  • 计算损失函数
  • 反向传播更新参数 weights 和 bias

我们现在使用 torch.no_grad() 更新参数,以避免参数更新过程被记录求导函数中。你可以在这里阅读更多关于PyTorch的Autograd如何记录操作的内容。
然后我们将梯度设置为0,以便为下一个循环做好准备。否则,导数会在原来的基础上累加(即loss.backward()将梯度添加到已经存储的内容中,而不是替换它们)。

TIP
您可以使用标准的 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))

Out:

tensor(0.0831, grad_fn=<NegBackward>) tensor(1.)

三. 使用torch.nn.functional

下面,我们将重构代码,使之与以前一样,只是我们将开始利用 PyTorch 的nn类,使其更简洁和灵活。接下来的每一步,我们都应该使代码,更短,更容易理解,和/或更灵活。

第一步 也是最简单的步骤是用torch.nn.functional中的激活和损失函数替换编写的激活和损失函数(它通常被导入到名称空间 F 中:import torch.nn.functional as F),从而缩短代码。这个模块包含了 torch.nn 库中的所有功能。(也包含大量的其它类)。除了大量的损失函数和激活函数之外,还有很多创建神经网络的便捷函数,例如pooling函数。(也有用于卷积层、线性层等的函数)

如果使用 log likelihood loss 和 log softmax activation,那么Pytorch提供了一个F.cross_entropy函数,它结合了这两个函数。所以我们甚至可以把激活函数与损失函数从模型中移除。

import torch.nn.functional as F

loss_func = F.cross_entropy

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

注意,我们不再调用log_softmax。让我们确认一下我们的损失和精度是否和以前一样。

print(loss_func(model(xb), yb), accuracy(model(xb), yb))

Out:

tensor(0.0831, grad_fn=<NllLossBackward>) tensor(1.)

四. 使用 nn.Module 重构代码

接下来,我们将使用nn.Modulenn.Parameter,使训练过程更清晰、更简洁。我们创建子类 nn.Module(它本身是一个类,能够跟踪状态)。在本例中,我们希望创建一个为下一步保存权重、偏差和方法的类。nn.Module 有许多我们能调用的属性和方法(如.parameters().zero_grad())

NOTE
nn.Module(大写M)是 PyTorch 特有的概念,也是我们将经常使用的类。不要将nn.Module与 Python 概念中的(小写的m) module混淆,它是一个可以导入的Python代码文件。

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

我们现在使用的是一个对象而不是一个函数,所以我们首先必须实例化我们的模型。

model = Mnist_Logistic()

现在我们可以像以前一样计算损失。注意nn.Module对象被当作函数来调用,Pytorch会自动调用对象内部的forward方法。

print(loss_func(model(xb), yb))

Out:

tensor(2.4790, grad_fn=<NllLossBackward>)

在之前的训练中,我们必须按名称更新每个参数的值,并分别手动清除每个参数的梯度,如下所示:

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))

Out:

tensor(0.0831, grad_fn=<NllLossBackward>)

五. 使用 nn.Linear 重构代码

我们将改用 Pytorch 类 nn.Linear创建线性层,而不是人工定义和初始化self.weightsself.bias,和计算xb @ self.weights + self.bias。Pytorch 有许多类型的预定义层,它们可以极大地简化我们的代码,而且通常也会使代码运行得更快。

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))

Out:

tensor(2.4207, grad_fn=<NllLossBackward>)

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

fit()

print(loss_func(model(xb), yb))

Out:

tensor(0.0814, grad_fn=<NllLossBackward>)

六. 使用optim重构代码

Pytorch还有一个包含各种优化算法的包torch.optim。我们可以使用优化器中的step方法来进一步执行步骤,而不是手动更新每个参数。这将让我们替换之前手工编码的优化步骤:

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

仅仅使用:

opt.step()
opt.zero_grad()

optiml .zero_grad()将梯度重置为0,我们需要在为下一个 minibatch 计算梯度之前调用它。

from torch import optim

我们将定义一个小函数来创建我们的模型和优化器。

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

model, opt = get_model()

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))

Out:

tensor(2.3872, grad_fn=<NllLossBackward>)
tensor(0.0823, grad_fn=<NllLossBackward>)

七. 使用 Dataset 处理数据

PyTorch有一个抽象数据集类。数据集可以是任何具有__len__函数(由Python的标准len函数调用)和__getitem__函数作为其索引方式的数据集。这个教程创建一个自定义FacialLandmarkDataset类作为Dataset的子类的示例。

PyTorch 的 TensorDataset是一个包含张量的数据集。通过定义长度和索引方式,这也为我们提供了一种沿着张量的第一个维度进行迭代、索引和切片的方式。这将使我们更容易在同一行代码中访问自变量和因变量。

from torch.utils.data import TensorDataset

x_trainy_train都可以组合在一个TensorDataset中,这将更容易迭代和切片。

train_ds = TensorDataset(x_train, y_train)

以前,我们必须分别迭代x和y的一个batch.

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))

Out:

tensor(0.0805, grad_fn=<NllLossBackward>)

八. 使用 DataLoader 加载数据

Pytorch的DataLoader负责批量加载数据。您可以在任何 Dataset 创建DataLoaderDataLoader 使Dataset 更容易在batches中迭代,不必使用train_ds[i*bs: i*bs+bs]DataLoader 会自动为我们提供每个小batch。

from torch.utils.data import DataLoader

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

之前我们读取数据的方式:

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

现在

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() #loss反向传播
        opt.step() #下一步求导
        opt.zero_grad() #导数清零

print(loss_func(model(xb), yb))

Out:

tensor(0.0827, grad_fn=<NllLossBackward>)

通过使用nn.Module, nn.Parameter, DatasetDataLoader, 我们的训练模型已经得到了很大的改进。接下来让我们开始模型的测试部分。

九. 添加测试集

在前面,我们只是尝试建立一个合理的循环训练,用于我们的训练集数据。实际上,您还应该有一个验证集,以便识别是否过拟合。

打乱训练数据防止batches间相关性和过拟合是很重要的。另一方面,不管是否打乱验证集,验证损失都是相同的。因为打乱需要额外的时间,所以打乱验证数据是没有意义的。

我们将为验证集设置两倍于训练集的batches大小。这是因为验证集不需要反向传播,因此占用的内存更少(它不需要存储梯度)。我们利用这一点来使用更大的批处理规模,并更快地计算损失。

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))

Out:

0 tensor(0.3443)
1 tensor(0.2742)

十. 创建 fit() 和 get_data()

现在我们继续改进。由于我们计算过训练集和验证集的损失两次,我们将其放入自己的函数loss_batch中,该函数计算一个 batch 的损失。

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

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返回训练和验证集的dataloaders。

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

现在,我们获得数据加载器和拟合模型的整个过程可以在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)

Out:

0 0.36388149040937423
1 0.28438832886219023

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

十一. 使用卷积神经网络

我们现在要用三个卷积层来构建我们的神经网络。因为前面的函数都没有假设模型的任何形式,所以我们可以使用它们来训练CNN,而不需要任何修改。

我们将使用 Pytorch 预定义的 Conv2d类作为卷积层。我们定义一个有3个卷积层的CNN。每个卷积后面跟着一个ReLU。最后,我们加一个平均池化层。(注意,该 view 是 PyTorch 版本的 numpy reshape(变形))

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)

Out:

0 0.32859203605651854
1 0.257646977686882

十二. 使用nn.Sequential 搭建网络

torch.nn还有另一个可用来简化代码的类:SequentialSequential对象以顺序的方式运行其包含的每个moudle。这是我们神经网络的一种更简单的写法。

要利用这一点,我们需要能够轻松地从给定的函数定义一个custom layer。例如,PyTorch没有 view 层,我们需要为我们的网络创建一个。Lambda将创建一个层,我们可以在使用Sequential定义网络时使用它。

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)

Out:

0 0.4616120267510414
1 0.24237275631427765

十三. 封装 DataLoader

我们的 CNN 相当简洁,但它只适用于 MNIST ,因为

  • 它假设输入是一个28*28长的向量
  • 假设最终的CNN卷积核大小为4*4(这是我们使用的平均池化层核的大小)

让我们去掉这两个假设,这样我们的模型就可以处理任何 2d 单通道图像。首先,我们可以通过将数据预处理移动到生成器中来删除初始的 Lambda 层(数据预处理层代替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):
        batches = iter(self.dl)
        for b in batches:
            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)

接下来,我们可以用nn.AdaptiveAvgPool2d替换nn.AvgPool2d,它允许我们定义我们想要的输出张量的维度,而不是我们的输入张量。因此,我们的模型可以处理任何大小的输入。

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)

Out:

0 0.3511354235649109
1 0.24440611186027528

十四. 使用GPU

你需要有一个GPU。

首先检测设备是否正常支持GPU:

print(torch.cuda.is_available())

Out:

True

创建一个设备对象:

dev = torch.device(
    "cuda") if torch.cuda.is_available() else torch.device("cpu")

让我们更新preprocess,将batch移动到GPU:

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)

Out:

0 0.1826939118027687
1 0.17859829986691475

十五. 总结

现在我们有了一个通用的数据 pipline 和模型训练方法,您可以使用 Pytorch 来训练多种类型的模型。要了解现在训练模型是多么简单,请回顾一下本博客。

当然,还有很多东西需要添加,比如数据增强、超参数调优、监督训练、迁移学习等等。这些特性可以在 fastai 库中获得。

我们在本教程的开始承诺,我们将通过示例解释每个torch.nn,torch.optim,Dataset,和DataLoader。让我们总结一下。

torch.nn

  • Module:创建一个像函数一样的可调用的对象,但也可以包含各种网络状态(如神经网络层权重)。可以使用parameter获取模型参数 (Parameter (s)),并可以使所有参数的梯度为零,并通过循环它们来更新权重,等等。
  • Parameter:一个张量的封装器(打包模型中需要更新的参数),它告诉 Module 它有权重,需要在反向传播期间更新。只有带有requires_grad属性集的张量才会被更新。
  • functional:一个模块(通常导入到F中),包含激活函数、损失函数等,以及非状态层,如卷积层和线性层。

torch.optim:包含诸如 SGD 之类的优化器,它在反向传播中更新 Parameter 的权重。

Dataset:一个包含__len____getitem__等函数的抽象接口,包括 Pytorch 提供的类,如TensorDataset
DataLoader:输入任意的 Dataset 并按批(batch)迭代输出数据。

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


参考资料

WHAT IS TORCH.NN REALLY?
深入理解 TORCH.NN

你可能感兴趣的:(pytorch)