.. DO NOT EDIT. .. THIS FILE WAS AUTOMATICALLY GENERATED BY SPHINX-GALLERY. .. TO MAKE CHANGES, EDIT THE SOURCE PYTHON FILE: .. "beginner/nn_tutorial.py" .. LINE NUMBERS ARE GIVEN BELOW. .. only:: html .. note:: :class: sphx-glr-download-link-note Click :ref:`here ` to download the full example code .. rst-class:: sphx-glr-example-title .. _sphx_glr_beginner_nn_tutorial.py: `torch.nn` 具体是什么? ============================ **Authors:** Jeremy Howard, `fast.ai `_. Thanks to Rachel Thomas and Francisco Ingham. .. GENERATED FROM PYTHON SOURCE LINES 10-33 我们建议将本教程作为笔记本(notebook)运行。请点击页面顶部的链接,下载笔记本(``.ipynb``)文件。 PyTorch 提供了优雅设计的模块和类 `torch.nn `_ , `torch.optim `_ , `Dataset `_ , 以及 `DataLoader `_ , 以帮助你创建和训练神经网络。 为了充分利用它们的功能,并通过自定义对应模块或类,来解决特定问题,需要理解它们的具体功能。为此,我们将首先在 MNIST 数据集上训练一个基本的神经网络,而不使用这些模型的任何特性; 我们最初只使用最基本的 PyTorch 张量功能。然后,我们将逐步添加``torch.nn``、``torch.optim``、``Dataset`` 或 ``DataLoader``中的一个特性,展示每个部分的作用,以及如何使用它们让代码更简洁或更灵活。 **本教程假定你已经安装了 PyTorch,并且熟悉张量操作的基础知识。**(如果你熟悉 Numpy 数组操作,你会发现这里使用的 PyTorch 张量操作几乎相同)。 MNIST 数据集设置 ---------------- 我们将使用经典的 `MNIST `_ 数据集, 该数据集包含手绘数字(0到9之间)的黑白图像。 我们将使用 `pathlib `_ 来处理路径(Python 3 标准库的一部分),并使用 `requests `_ 下载数据集。 我们只会在使用模块时才导入它们,因此你可以清楚地看到每个步骤中正在使用的内容。 .. GENERATED FROM PYTHON SOURCE LINES 33-49 .. code-block:: default 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) .. GENERATED FROM PYTHON SOURCE LINES 50-52 这个数据集是 numpy 数组格式的,并且使用 pickle 存储, 这是一个 Python 特有的用于序列化数据的格式。 .. GENERATED FROM PYTHON SOURCE LINES 52-59 .. code-block:: default 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") .. GENERATED FROM PYTHON SOURCE LINES 60-61 每张图像的尺寸为 28 x 28,并以长度为 784(=28x28)的展平行存储。让我们来看看其中一张;我们需要先将其重塑为二维。 .. GENERATED FROM PYTHON SOURCE LINES 61-73 .. code-block:: default from matplotlib import pyplot import numpy as np pyplot.imshow(x_train[0].reshape((28, 28)), cmap="gray") # ``pyplot.show()`` 在不使用 Colab 时使用 try: import google.colab except ImportError: pyplot.show() print(x_train.shape) .. GENERATED FROM PYTHON SOURCE LINES 74-75 PyTorch 使用 ``torch.tensor`` 而不是 numpy 数组,所以我们需要转换我们的数据。 .. GENERATED FROM PYTHON SOURCE LINES 75-86 .. code-block:: default 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()) .. GENERATED FROM PYTHON SOURCE LINES 87-100 从零开始的神经网络(不使用 ``torch.nn``) -------------------------------------------- 首先,我们只使用 PyTorch 张量操作创建一个模型。我们假设你已经熟悉神经网络的基础知识。(如果不熟悉,可以在 `course.fast.ai `_ 学习。) PyTorch 提供方法来创建 随机 或 零 填充的张量,我们将使用这些方法为一个简单的线性模型创建权重和偏置。 这些只是常规的张量,有一个非常特别的附加功能:我们告诉 PyTorch 它们需要梯度。PyTorch 会记录在张量上完成的所有操作,以便在反向传播期间 *自动* 计算梯度! 对于权重,我们在初始化 **之后** 设置 ``requires_grad``,因为我们不希望初始化步骤包括在梯度中。(注意,PyTorch 中的尾随 ``_`` 表示操作是在原地执行。) .. 注意:: 我们在这里使用 `Xavier 初始化 `_ (通过乘以 ``1/sqrt(n)``)初始化权重。 .. GENERATED FROM PYTHON SOURCE LINES 100-107 .. code-block:: default import math weights = torch.randn(784, 10) / math.sqrt(784) weights.requires_grad_() bias = torch.zeros(10, requires_grad=True) .. GENERATED FROM PYTHON SOURCE LINES 108-112 由于 PyTorch 能够自动计算梯度,我们可以使用任何标准的 Python 函数(或可调用对象)作为模型! 让我们编写一个简单的矩阵乘法和广播加法,来创建一个简单的线性模型。我们还需要编写一个激活函数 `log_softmax`。 PyTorch 提供了许多预先编写的损失函数、激活函数等,你仍可以使用普通的 Python 编写自己的函数。 PyTorch 会为你的函数自动创建 GPU 或矢量化 CPU 代码。 .. GENERATED FROM PYTHON SOURCE LINES 112-119 .. code-block:: default def log_softmax(x): return x - x.exp().sum(-1).log().unsqueeze(-1) def model(xb): return log_softmax(xb @ weights + bias) .. GENERATED FROM PYTHON SOURCE LINES 120-122 在上面的代码中,``@`` 表示矩阵乘法操作。在一个数据批次上调用我们的函数(在本例中为64张图像)。 这就是一次 *前向传递*。请注意,由于我们在开始时设置权重为随机数值,此时预测结果准确性较低。 .. GENERATED FROM PYTHON SOURCE LINES 122-130 .. code-block:: default 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) .. GENERATED FROM PYTHON SOURCE LINES 131-134 如你所见,``preds`` 张量不仅包含张量值,还包含梯度函数。在稍后的反向传播过程中会用到它。 让我们实现 negative log-likelihood 作为损失函数(同样,我们可以只使用标准的 Python): .. GENERATED FROM PYTHON SOURCE LINES 134-141 .. code-block:: default def nll(input, target): return -input[range(target.shape[0]), target].mean() loss_func = nll .. GENERATED FROM PYTHON SOURCE LINES 142-143 让我们使用我们的随机模型来检查损失(loss),这样我们就可以看到在之后进行反向传播后,预测结果准确率是否有所提升。 .. GENERATED FROM PYTHON SOURCE LINES 143-148 .. code-block:: default yb = y_train[0:bs] print(loss_func(preds, yb)) .. GENERATED FROM PYTHON SOURCE LINES 149-151 我们还要实现一个函数来计算我们模型的准确率。 对于每个预测结果,如果具有最大值的索引与目标值匹配,则预测是正确的。 .. GENERATED FROM PYTHON SOURCE LINES 151-156 .. code-block:: default def accuracy(out, yb): preds = torch.argmax(out, dim=1) return (preds == yb).float().mean() .. GENERATED FROM PYTHON SOURCE LINES 157-158 检查我们随机模型的准确率,这样我们就可以看到随着损失的改善,准确率是否有所提高。 .. GENERATED FROM PYTHON SOURCE LINES 158-161 .. code-block:: default print(accuracy(preds, yb)) .. GENERATED FROM PYTHON SOURCE LINES 162-176 现在可以运行一个训练循环。对于每次迭代: - 选择一个大小为 ``bs`` 的批量数据 - 使用模型进行预测 - 计算损失 - ``loss.backward()`` 更新模型的梯度,即更新 ``weights`` 和 ``bias``。 我们现在使用这些梯度来更新权重(weights)和偏置(bias)。我们在 ``torch.no_grad()`` 上下文管理器中执行此操作, 因为我们不希望这些操作记录为下一次梯度计算的一部分。你可以在 `这里 `_ 阅读有关 PyTorch 的 Autograd 如何记录操作的更多信息。 然后,我们将梯度设置为零,以便我们准备进行下一次循环。否则,我们的梯度将记录所有已发生的操作(即 ``loss.backward()`` *添加* 梯度到已有的梯度中,而不是替换它们)。 .. 提示:: 您可以使用标准的 Python 调试器逐步执行 PyTorch 代码,从而可以检查每个步骤中的各种变量值(去除 ``set_trace()`` 的注释)。 .. GENERATED FROM PYTHON SOURCE LINES 176-199 .. code-block:: default 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_() .. GENERATED FROM PYTHON SOURCE LINES 200-202 我们已经从零开始创建并训练了一个最小的神经网络(使用逻辑回归,没有隐藏层)。 让我们检查一下损失和准确率,并将它们与之前得到的结果进行比较,预计损失会减少,准确率会提高。 .. GENERATED FROM PYTHON SOURCE LINES 202-205 .. code-block:: default print(loss_func(model(xb), yb), accuracy(model(xb), yb)) .. GENERATED FROM PYTHON SOURCE LINES 206-219 使用 ``torch.nn.functional`` ------------------------------ 现在我们将重构代码,使其与之前做的事情相同,只是我们将开始利用 PyTorch 的 ``nn`` 类,使其更简洁和灵活。 从这里开始的每一步,都让我们的代码变得更短、更易理解和更灵活。 第一步也是最简单的一步是通过用 ``torch.nn.functional``(通常按惯例导入为命名空间 ``F``) 中的激活和损失函数替换我们手写的激活和损失函数,从而使我们的代码更简短。该模块包含 ``torch.nn`` 库中的所有函数。 除了各种损失和激活函数,你还会看到一些创建神经网络的便捷函数,比如池化函数。 (还有用于卷积、线性层等的函数,但正如我们将看到的,这些通常更适合使用库的其他模块来处理。) 如果你使用negative log likelihood loss 和 log softmax activation,那么 PyTorch 提供了一个结合了两者的单一函数 ``F.cross_entropy``。所以我们可以从模型中移除激活函数。 .. GENERATED FROM PYTHON SOURCE LINES 219-227 .. code-block:: default import torch.nn.functional as F loss_func = F.cross_entropy def model(xb): return xb @ weights + bias .. GENERATED FROM PYTHON SOURCE LINES 228-229 我们不再在 ``model`` 函数中调用 ``log_softmax``。查看下损失和准确率是否与之前结果一致: .. GENERATED FROM PYTHON SOURCE LINES 229-232 .. code-block:: default print(loss_func(model(xb), yb), accuracy(model(xb), yb)) .. GENERATED FROM PYTHON SOURCE LINES 233-239 使用 ``nn.Module`` 重构 ----------------------------- 接下来,我们将使用 ``nn.Module`` 和 ``nn.Parameter``,以实现更清晰和简洁的训练循环。 我们将继承 ``nn.Module``(它本身是一个类,能够跟踪状态)。在这种情况下,我们想创建一个类来保存我们的权重、偏置和forward方法。 我们将会使用 ``nn.Module`` 的属性和方法(例如 ``.parameters()`` 和 ``.zero_grad()``)。 .. GENERATED FROM PYTHON SOURCE LINES 239-254 .. code-block:: default # .. 注意:: ``nn.Module``(大写 M)是 PyTorch 特有的概念,是使用PyTorch过程中大量使用的类。 # ``nn.Module`` 不要与 Python 概念的(小写 ``m``)`module `_ 混淆。 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 .. GENERATED FROM PYTHON SOURCE LINES 255-256 由于我们现在使用的是对象而不是仅仅使用函数,我们首先要创建模型对象: .. GENERATED FROM PYTHON SOURCE LINES 256-259 .. code-block:: default model = Mnist_Logistic() .. GENERATED FROM PYTHON SOURCE LINES 260-262 现在我们可以像之前一样计算损失。请注意,``nn.Module`` 对象可以像函数一样使用(即它们是*可调用的*), PyTorch 会自动调用我们的 ``forward`` 方法。 .. GENERATED FROM PYTHON SOURCE LINES 262-265 .. code-block:: default print(loss_func(model(xb), yb)) .. GENERATED FROM PYTHON SOURCE LINES 266-288 在之前的训练循环中,我们必须按名称更新每个参数的值,并手动将每个参数的梯度分别清零,如下所示: .. code-block:: python 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`` 定义的方法) 来使这些步骤更简洁,防止忘记处理某些参数导致错误,尤其是当我们实现一个更复杂的模型时: .. code-block:: python with torch.no_grad(): for p in model.parameters(): p -= p.grad * lr model.zero_grad() 将训练循环包装在一个 ``fit`` 函数中,这样可以多次运行它。 .. GENERATED FROM PYTHON SOURCE LINES 288-307 .. code-block:: default 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() .. GENERATED FROM PYTHON SOURCE LINES 308-310 Let's double-check that our loss has gone down: 让我们查看下训练后,损失是否下降了: .. GENERATED FROM PYTHON SOURCE LINES 310-313 .. code-block:: default print(loss_func(model(xb), yb)) .. GENERATED FROM PYTHON SOURCE LINES 314-320 使用 ``nn.Linear`` 重构 ---------------------------- 我们继续重构代码。使用 PyTorch 类 `nn.Linear `_ 来实现线性层, 不再手动定义和初始化 ``self.weights`` 和 ``self.bias``,以及计算 ``xb @ self.weights + self.bias``。 PyTorch 具有多种预定义的层,可以大大简化我们的代码,并且提高执行速度。 .. GENERATED FROM PYTHON SOURCE LINES 320-329 .. code-block:: default class Mnist_Logistic(nn.Module): def __init__(self): super().__init__() self.lin = nn.Linear(784, 10) def forward(self, xb): return self.lin(xb) .. GENERATED FROM PYTHON SOURCE LINES 330-331 初始化模型对象,并计算损失数值 .. GENERATED FROM PYTHON SOURCE LINES 331-335 .. code-block:: default model = Mnist_Logistic() print(loss_func(model(xb), yb)) .. GENERATED FROM PYTHON SOURCE LINES 336-337 调用 ``fit`` 方法进行训练模型 .. GENERATED FROM PYTHON SOURCE LINES 337-343 .. code-block:: default fit() # 查看训练结果 print(loss_func(model(xb), yb)) .. GENERATED FROM PYTHON SOURCE LINES 344-365 使用 ``torch.optim`` 重构 ------------------------------ PyTorch ``torch.optim``包含多种优化算法 。我们可以使用优化器的 ``step`` 方法进行优化步骤,无需手动更新每个参数。 之前的优化步骤: .. code-block:: python with torch.no_grad(): for p in model.parameters(): p -= p.grad * lr model.zero_grad() 重构为: .. code-block:: python opt.step() opt.zero_grad() (在下个训练循环开始前,我们需调用 ``optim.zero_grad()`` 方法,将参数的梯度重置为0。) .. GENERATED FROM PYTHON SOURCE LINES 365-368 .. code-block:: default from torch import optim .. GENERATED FROM PYTHON SOURCE LINES 369-370 定义创建模型和优化器的方法如下: .. GENERATED FROM PYTHON SOURCE LINES 370-393 .. code-block:: default 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)) .. GENERATED FROM PYTHON SOURCE LINES 394-404 使用 Dataset 重构 ------------------------------ PyTorch 有一个抽象的 Dataset 类。Dataset 可以是任何具有 ``__len__`` 函数(由 Python 的标准 ``len`` 函数调用) 和 ``__getitem__`` 函数(作为索引方式)的对象。 `教程 `_ 详细介绍了创建一个自定义 ``FacialLandmarkDataset`` 类作为 ``Dataset`` 子类的例子。 PyTorch 的 `TensorDataset `_ 是一个包装张量的 Dataset,为我们提供了一种迭代、索引和沿张量的第一个维度切片的方式,使我们在训练时更容易同时访问自变量和因变量。 .. GENERATED FROM PYTHON SOURCE LINES 404-407 .. code-block:: default from torch.utils.data import TensorDataset .. GENERATED FROM PYTHON SOURCE LINES 408-409 使用``TensorDataset`` 对 ``x_train`` 和 ``y_train`` 进行包装, 让我们更容易对数据进行遍历和切片操作。 .. GENERATED FROM PYTHON SOURCE LINES 409-412 .. code-block:: default train_ds = TensorDataset(x_train, y_train) .. GENERATED FROM PYTHON SOURCE LINES 413-427 之前我们需要单独处理 ``x``、``y`` 两组数值。 .. code-block:: python xb = x_train[start_i:end_i] yb = y_train[start_i:end_i] 现在可以合并处理: .. code-block:: python xb,yb = train_ds[i*bs : i*bs+bs] .. GENERATED FROM PYTHON SOURCE LINES 427-442 .. code-block:: default 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)) .. GENERATED FROM PYTHON SOURCE LINES 443-448 使用 ``DataLoader`` 重构 ------------------------------ 你可以从任何 ``Dataset`` 创建一个 ``DataLoader``,而后由 ``DataLoader`` 负责对数据分批。 我们不必再去实现分批代码,如 ``train_ds[i*bs : i*bs+bs]``,``DataLoader`` 会自动为我们提供每批数据。 .. GENERATED FROM PYTHON SOURCE LINES 448-454 .. code-block:: default from torch.utils.data import DataLoader train_ds = TensorDataset(x_train, y_train) train_dl = DataLoader(train_ds, batch_size=bs) .. GENERATED FROM PYTHON SOURCE LINES 455-469 之前我们编写分批代码如下: .. code-block:: python for i in range((n-1)//bs + 1): xb,yb = train_ds[i*bs : i*bs+bs] pred = model(xb) 现在,我们的循环变得更加简洁,``(xb, yb)`` 自动从DataLoader中加载: .. code-block:: python for xb,yb in train_dl: pred = model(xb) .. GENERATED FROM PYTHON SOURCE LINES 469-483 .. code-block:: default 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)) .. GENERATED FROM PYTHON SOURCE LINES 484-498 通过使用 PyTorch 中 ``nn.Module``、``nn.Parameter``、``Dataset`` 和 ``DataLoader``, 我们实现的训练循代码量并且更容易理解。现在让我们尝试增加一些创建实际有效模型所需的基本功能。 添加验证集 ----------------------- 在第一部分中,我们只是实现了使用数据进行训练的逻辑。 实际应用中,还需要`验证集 `_,以确定我们的模型是否存在过拟合问题。 打乱训练数据是 `十分必要的 `_, 以防止批次之间的相关性和过拟合。而验证数据集则无需进此操作,无论打乱与否,验证损失值是相同的,而且打乱操作需要消耗额外的时间,没有实际意义。 我们将为验证集使用的批量大小设为训练集的两倍。因为验证集不需要进行反向传播,因此需要的内存较少(不需要存储梯度)。 因此我我们可以配置较大单批数量,提高计算速度。 .. GENERATED FROM PYTHON SOURCE LINES 498-505 .. code-block:: default 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) .. GENERATED FROM PYTHON SOURCE LINES 506-510 我们在每个 epoch 结束时计算并打印损失值。 (请注意,我们在训练之前总是调用 ``model.train()``,在推断之前调用 ``model.eval()``, 因为 ``nn.BatchNorm2d`` 和 ``nn.Dropout`` 层会使用,来确保其结果正确。) .. GENERATED FROM PYTHON SOURCE LINES 510-529 .. code-block:: default 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)) .. GENERATED FROM PYTHON SOURCE LINES 530-536 创建 fit() 和 get_data() ---------------------------------- 我们在计算训练集和验证集的损失类似的代码,抽取一个独立的函数 ``loss_batch``,用于计算一个批次的损失。 训练集传入一个优化器,并使用它执行反向传播,对于验证集,则不传入优化器,不执行反向传播。 .. GENERATED FROM PYTHON SOURCE LINES 536-548 .. code-block:: default 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) .. GENERATED FROM PYTHON SOURCE LINES 549-550 ``fit `` 在每个训练循环中计算训练和验证损失 .. GENERATED FROM PYTHON SOURCE LINES 550-568 .. code-block:: default 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) .. GENERATED FROM PYTHON SOURCE LINES 569-570 ``get_data`` 返回训练和验证数据集的DataLoader。 .. GENERATED FROM PYTHON SOURCE LINES 570-578 .. code-block:: default def get_data(train_ds, valid_ds, bs): return ( DataLoader(train_ds, batch_size=bs, shuffle=True), DataLoader(valid_ds, batch_size=bs * 2), ) .. GENERATED FROM PYTHON SOURCE LINES 579-580 现在,我们获取数据加载器和拟合模型的整个过程可以用 3 行代码来实现: .. GENERATED FROM PYTHON SOURCE LINES 580-585 .. code-block:: default 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) .. GENERATED FROM PYTHON SOURCE LINES 586-597 你可以使用这三行基本代码来训练各种各样的模型。让我们看看是否可以用来训练一个卷积神经网络(CNN)。 CNN ------------- 现在我们将使用三个卷积层构建我们的神经网络。因为前面部分的函数都不假设任何关于模型形式的东西, 所以我们可以在不做任何修改的情况下使用它们来训练一个 CNN。 我们将使用 PyTorch 预定义的 `Conv2d `_ 类作为我们的卷积层。 我们定义一个具有 3 个卷积层的 CNN。每个卷积层后面跟着一个 ReLU。最后,我们执行平均池化。 (注意,``view`` 是 PyTorch 版的 Numpy ``reshape``) .. GENERATED FROM PYTHON SOURCE LINES 597-615 .. code-block:: default 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 .. GENERATED FROM PYTHON SOURCE LINES 616-618 `Momentum `_ 是 stochastic gradient descent 的一种变体,通过统计更新记录来提升训练速度 .. GENERATED FROM PYTHON SOURCE LINES 618-624 .. code-block:: default model = Mnist_CNN() opt = optim.SGD(model.parameters(), lr=lr, momentum=0.9) fit(epochs, model, loss_func, opt, train_dl, valid_dl) .. GENERATED FROM PYTHON SOURCE LINES 625-632 使用 ``nn.Sequential`` ------------------------ 我们可以使用 ``torch.nn`` 中的 `Sequential `_ 类 来帮助我们简化代码。`` Sequential`` 提供了一种更简单的编写神经网络的方式,其会按顺序运行定义中包含的每个模块。 我们可以创建一个 ``自定义层``,例如,PyTorch 没有的 view层: .. GENERATED FROM PYTHON SOURCE LINES 632-645 .. code-block:: default 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) .. GENERATED FROM PYTHON SOURCE LINES 646-647 使用 ``Sequential`` 创建模型十分简单: .. GENERATED FROM PYTHON SOURCE LINES 647-664 .. code-block:: default 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) .. GENERATED FROM PYTHON SOURCE LINES 665-671 包装 ``DataLoader`` ----------------------------- 我们编写的 CNN 十分简洁,但仅适用于MNIST,因为: - 它假设输入是一个 28 * 28 长的向量 - 它假设最终的 CNN 网格大小是 4 * 4 (我们使用的平均池化核大小) .. GENERATED FROM PYTHON SOURCE LINES 671-695 .. code-block:: default # 让我们去除这两个假设,使我们的模型适用于任何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) .. GENERATED FROM PYTHON SOURCE LINES 696-698 接下来,为了让我们定义我们想要的输出张量的大小,而非 *输入* 张量,我们可以用 `nn.AdaptiveAvgPool2d` 替换 `nn.AvgPool2d`。 从而使我们的模型可适用于任何大小的输入。 .. GENERATED FROM PYTHON SOURCE LINES 698-712 .. code-block:: default 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) .. GENERATED FROM PYTHON SOURCE LINES 713-714 让我们查看下结果: .. GENERATED FROM PYTHON SOURCE LINES 714-717 .. code-block:: default fit(epochs, model, loss_func, opt, train_dl, valid_dl) .. GENERATED FROM PYTHON SOURCE LINES 718-722 使用 GPU --------------- 在拥有 CUDA 的 GPU的环境中,你可以使用它来加速代码。首先检查你的GPU在PyTorch中是否正常工作: .. GENERATED FROM PYTHON SOURCE LINES 722-725 .. code-block:: default print(torch.cuda.is_available()) .. GENERATED FROM PYTHON SOURCE LINES 726-727 然后创建 device 对象: .. GENERATED FROM PYTHON SOURCE LINES 727-731 .. code-block:: default dev = torch.device( "cuda") if torch.cuda.is_available() else torch.device("cpu") .. GENERATED FROM PYTHON SOURCE LINES 732-733 修改 ``preprocess`` 步骤,将数据移动至 GPU 上: .. GENERATED FROM PYTHON SOURCE LINES 733-743 .. code-block:: default 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) .. GENERATED FROM PYTHON SOURCE LINES 744-745 最后,将模型加载到 GPU 中。 .. GENERATED FROM PYTHON SOURCE LINES 745-749 .. code-block:: default model.to(dev) opt = optim.SGD(model.parameters(), lr=lr, momentum=0.9) .. GENERATED FROM PYTHON SOURCE LINES 750-751 运行速度会提升很多: .. GENERATED FROM PYTHON SOURCE LINES 751-754 .. code-block:: default fit(epochs, model, loss_func, opt, train_dl, valid_dl) .. GENERATED FROM PYTHON SOURCE LINES 755-774 总结 ----------------- 我们使用 PyTorch 编写了一个可以用于多种模型训练的实现,完整的训练代码 `mnist_sample notebook `__. 后续还可尝试增加其他功能,例如数据增强、超参数调优、监控训练、迁移学习等等。 这些功能在fastai库中都有提供,该库是使用本教程中所示的相同设计方法开发的,为希望进一步改进模型的从业人员提供下一步指导。 我们学习了如何使用 ``torch.nn``,``torch.optim``,``Dataset``, and ``DataLoader``。现在让我们总结一下: - ``torch.nn``: + ``Module``: 创建一个类似于函数的可调用对象,其中包含了状态数据(如神经网络层权重)。它可以自动对包含的参数,进行梯度归零和更新权重等操作。 + ``Parameter``: 对张量进行包装,使 ``Module`` 对象在进行反向传播时,可更新权重参数(仅设置 `requires_grad=True` 参数时生效)。 + ``functional``: 包含多种激活函数、损失函数,以及无状态的卷积层和线性层等的实现。 - ``torch.optim``: 包含多种优化器,例如 ``SGD``,在反向传播过程中优化权重参数(``Parameter``)。 - ``Dataset``: 对 ``__len__`` 和 a ``__getitem__`` 方法的抽象接口定义,包含 ``TensorDataset`` 等 PyTorch 实现类。 - ``DataLoader``: 对 ``Dataset`` 进行封装,提供分批遍历数据集的能力。 .. rst-class:: sphx-glr-timing **Total running time of the script:** ( 0 minutes 0.000 seconds) .. _sphx_glr_download_beginner_nn_tutorial.py: .. only:: html .. container:: sphx-glr-footer sphx-glr-footer-example .. container:: sphx-glr-download sphx-glr-download-python :download:`Download Python source code: nn_tutorial.py ` .. container:: sphx-glr-download sphx-glr-download-jupyter :download:`Download Jupyter notebook: nn_tutorial.ipynb ` .. only:: html .. rst-class:: sphx-glr-signature `Gallery generated by Sphinx-Gallery `_