Machine-Learning-Mastery-PyTorch-教程-六-

156 阅读46分钟

Machine Learning Mastery PyTorch 教程(六)

原文:Machine Learning Mastery

协议:CC BY-NC-SA 4.0

Pytorch 中的一维张量

原文:machinelearningmastery.com/one-dimensional-tensors-in-pytorch/

PyTorch 是一个基于 Python 语言的开源深度学习框架。它允许你构建、训练和部署深度学习模型,提供了很多灵活性和效率。

PyTorch 主要集中在张量操作上,而张量可以是数字、矩阵或多维数组。

在这个教程中,我们将对一维张量执行一些基本操作,因为它们是复杂的数学对象,也是 PyTorch 库的重要组成部分。因此,在深入研究更高级的概念之前,我们应该先了解基础知识。

在完成本教程后,你将:

  • 了解 PyTorch 中一维张量操作的基础知识。

  • 了解张量类型和形状,并执行张量切片和索引操作。

  • 能够对张量对象应用一些方法,如均值、标准差、加法、乘法等。

启动你的项目,使用我的书籍《Deep Learning with PyTorch》。它提供了带有工作代码自学教程

让我们开始吧!

Pytorch 中的一维张量

照片由Jo Szczepanska提供。部分权利保留。

一维张量的类型和形状

首先,让我们导入这个教程中将要使用的一些库。

import torch
import numpy as np 
import pandas as pd

如果你有其他编程语言的经验,理解张量的最简单方法是将其视为多维数组。因此,一维张量就是一个一维数组,或者说是一个向量。为了将整数列表转换为张量,请应用torch.tensor()构造函数。例如,我们将取一个整数列表并将其转换为不同的张量对象。

int_to_tensor = torch.tensor([10, 11, 12, 13])
print("Tensor object type after conversion: ", int_to_tensor.dtype)
print("Tensor object type after conversion: ", int_to_tensor.type())
Tensor object type after conversion:  torch.int64
Tensor object type after conversion:  torch.LongTensor

同样,你也可以将相同的方法torch.tensor()应用于将浮点列表转换为浮点张量。

float_to_tensor = torch.tensor([10.0, 11.0, 12.0, 13.0])
print("Tensor object type after conversion: ", float_to_tensor.dtype)
print("Tensor object type after conversion: ", float_to_tensor.type())
Tensor object type after conversion:  torch.float32
Tensor object type after conversion:  torch.FloatTensor

注意,需要转换为张量的列表元素必须具有相同的类型。此外,如果你想将列表转换为特定的张量类型,torch 也允许你这样做。例如,下面的代码行将整数列表转换为浮点张量。

int_list_to_float_tensor = torch.FloatTensor([10, 11, 12, 13])
int_list_to_float_tensor.type()
print("Tensor  type after conversion: ", int_list_to_float_tensor.type())
Tensor  type after conversion:  torch.FloatTensor

类似地,size()ndimension()方法允许你查找张量对象的大小和维度。

print("Size of the int_list_to_float_tensor: ", int_list_to_float_tensor.size())
print("Dimensions of the int_list_to_float_tensor: ",int_list_to_float_tensor.ndimension())
Size of the int_list_to_float_tensor:  torch.Size([4])
Dimensions of the int_list_to_float_tensor:  1

对于重塑张量对象,可以应用view()方法。它接受rowscolumns作为参数。举个例子,让我们使用这个方法来重塑int_list_to_float_tensor

reshaped_tensor = int_list_to_float_tensor.view(4, 1)
print("Original Size of the tensor: ", reshaped_tensor)
print("New size of the tensor: ", reshaped_tensor)
Original Size of the tensor:  tensor([[10.],
        [11.],
        [12.],
        [13.]])
New size of the tensor:  tensor([[10.],
        [11.],
        [12.],
        [13.]])

如你所见,view()方法已将张量的大小更改为torch.Size([4, 1]),其中有 4 行和 1 列。

在应用view()方法后,张量对象中的元素数量应保持不变,但你可以使用-1(例如reshaped_tensor**.**view(-1, 1))来重塑一个动态大小的张量。

将 Numpy 数组转换为张量

Pytorch 也允许你将 NumPy 数组转换为张量。你可以使用 torch.from_numpy 完成这个操作。让我们拿一个 NumPy 数组并应用这个操作。

numpy_arr = np.array([10.0, 11.0, 12.0, 13.0])
from_numpy_to_tensor = torch.from_numpy(numpy_arr)

print("dtype of the tensor: ", from_numpy_to_tensor.dtype)
print("type of the tensor: ", from_numpy_to_tensor.type())
dtype of the tensor:  torch.float64
type of the tensor:  torch.DoubleTensor

同样地,你可以将张量对象转换回 NumPy 数组。让我们用之前的例子展示如何做到这一点。

tensor_to_numpy = from_numpy_to_tensor.numpy()
print("back to numpy from tensor: ", tensor_to_numpy)
print("dtype of converted numpy array: ", tensor_to_numpy.dtype)
back to numpy from tensor:  [10\. 11\. 12\. 13.]
dtype of converted numpy array:  float64

将 Pandas Series 转换为张量

你也可以将 Pandas Series 转换为张量。为此,首先需要使用 values() 函数将 Pandas Series 存储为 NumPy 数组。

pandas_series=pd.Series([1, 0.2, 3, 13.1])
store_with_numpy=torch.from_numpy(pandas_series.values)
print("Stored tensor in numpy array: ", store_with_numpy)
print("dtype of stored tensor: ", store_with_numpy.dtype)
print("type of stored tensor: ", store_with_numpy.type())
Stored tensor in numpy array:  tensor([ 1.0000,  0.2000,  3.0000, 13.1000], dtype=torch.float64)
dtype of stored tensor:  torch.float64
type of stored tensor:  torch.DoubleTensor

此外,Pytorch 框架允许我们对张量做很多事情,例如它的 item() 方法从张量返回一个 Python 数字,而 tolist() 方法则返回一个列表。

new_tensor=torch.tensor([10, 11, 12, 13]) 
print("the second item is",new_tensor[1].item())
tensor_to_list=new_tensor.tolist()
print('tensor:', new_tensor,"\nlist:",tensor_to_list)
the second item is 11
tensor: tensor([10, 11, 12, 13])
list: [10, 11, 12, 13]

一维张量中的索引和切片

Pytorch 中的索引和切片操作与 Python 几乎相同。因此,第一个索引始终从 0 开始,最后一个索引小于张量的总长度。使用方括号访问张量中的任何数字。

tensor_index = torch.tensor([0, 1, 2, 3])
print("Check value at index 0:",tensor_index[0])
print("Check value at index 3:",tensor_index[3])
Check value at index 0: tensor(0)
Check value at index 3: tensor(3)

就像 Python 中的列表一样,你也可以在张量中的值上执行切片操作。此外,Pytorch 库还允许你更改张量中的某些值。

让我们举个例子来检查如何应用这些操作。

example_tensor = torch.tensor([50, 11, 22, 33, 44])
sclicing_tensor = example_tensor[1:4]
print("example tensor : ", example_tensor)
print("subset of example tensor:", sclicing_tensor)
example tensor :  tensor([50, 11, 22, 33, 44])
subset of example tensor: tensor([11, 22, 33])

现在,让我们改变 example_tensor 的索引 3 处的值。

print("value at index 3 of example tensor:", example_tensor[3])
example_tensor[3] = 0
print("new tensor:", example_tensor)
value at index 3 of example tensor: tensor(0)
new tensor: tensor([50, 11, 22,  0, 44])

想要开始使用 PyTorch 进行深度学习吗?

现在就开始我的免费电子邮件速成课程(附带示例代码)。

点击注册并获得课程的免费 PDF 电子书版本。

应用在一维张量上的一些函数

在这一节中,我们将回顾一些可以应用在张量对象上的统计方法。

最小值和最大值函数

这两种有用的方法用于在张量中找到最小值和最大值。以下是它们的工作原理。

我们将使用 sample_tensor 作为示例来应用这些方法。

sample_tensor = torch.tensor([5, 4, 3, 2, 1])
min_value = sample_tensor.min()
max_value = sample_tensor.max()
print("check minimum value in the tensor: ", min_value)
print("check maximum value in the tensor: ", max_value)
check minimum value in the tensor:  tensor(1)
check maximum value in the tensor:  tensor(5)

均值和标准差

在张量进行统计操作时,常常使用均值和标准差。你可以使用 Pytorch 中的 .mean().std() 函数应用这两个指标。

让我们用一个例子来看看这两个指标是如何计算的。

mean_std_tensor = torch.tensor([-1.0, 2.0, 1, -2])
Mean = mean_std_tensor.mean()
print("mean of mean_std_tensor: ", Mean)
std_dev = mean_std_tensor.std()
print("standard deviation of mean_std_tensor: ", std_dev)
mean of mean_std_tensor:  tensor(0.)
standard deviation of mean_std_tensor:  tensor(1.8257)

一维张量上的简单加法和乘法运算

在 Pytorch 中,可以轻松地对张量应用加法和乘法操作。在本节中,我们将创建两个一维张量来演示如何使用这些操作。

a = torch.tensor([1, 1])
b = torch.tensor([2, 2])

add = a + b
multiply = a * b

print("addition of two tensors: ", add)
print("multiplication of two tensors: ", multiply)
addition of two tensors:  tensor([3, 3])
multiplication of two tensors:  tensor([2, 2])

为了方便起见,以下是上述所有示例的综合,这样你可以一次尝试它们:

import torch
import numpy as np
import pandas as pd

int_to_tensor = torch.tensor([10, 11, 12, 13])
print("Tensor object type after conversion: ", int_to_tensor.dtype)
print("Tensor object type after conversion: ", int_to_tensor.type())

float_to_tensor = torch.tensor([10.0, 11.0, 12.0, 13.0])
print("Tensor object type after conversion: ", float_to_tensor.dtype)
print("Tensor object type after conversion: ", float_to_tensor.type())

int_list_to_float_tensor = torch.FloatTensor([10, 11, 12, 13])
int_list_to_float_tensor.type()
print("Tensor  type after conversion: ", int_list_to_float_tensor.type())

print("Size of the int_list_to_float_tensor: ", int_list_to_float_tensor.size())
print("Dimensions of the int_list_to_float_tensor: ",int_list_to_float_tensor.ndimension())

reshaped_tensor = int_list_to_float_tensor.view(4, 1)
print("Original Size of the tensor: ", reshaped_tensor)
print("New size of the tensor: ", reshaped_tensor)

numpy_arr = np.array([10.0, 11.0, 12.0, 13.0])
from_numpy_to_tensor = torch.from_numpy(numpy_arr)
print("dtype of the tensor: ", from_numpy_to_tensor.dtype)
print("type of the tensor: ", from_numpy_to_tensor.type())

tensor_to_numpy = from_numpy_to_tensor.numpy()
print("back to numpy from tensor: ", tensor_to_numpy)
print("dtype of converted numpy array: ", tensor_to_numpy.dtype)

pandas_series=pd.Series([1, 0.2, 3, 13.1])
store_with_numpy=torch.from_numpy(pandas_series.values)
print("Stored tensor in numpy array: ", store_with_numpy)
print("dtype of stored tensor: ", store_with_numpy.dtype)
print("type of stored tensor: ", store_with_numpy.type())

new_tensor=torch.tensor([10, 11, 12, 13]) 
print("the second item is",new_tensor[1].item())
tensor_to_list=new_tensor.tolist()
print('tensor:', new_tensor,"\nlist:",tensor_to_list)

tensor_index = torch.tensor([0, 1, 2, 3])
print("Check value at index 0:",tensor_index[0])
print("Check value at index 3:",tensor_index[3])

example_tensor = torch.tensor([50, 11, 22, 33, 44])
sclicing_tensor = example_tensor[1:4]
print("example tensor : ", example_tensor)
print("subset of example tensor:", sclicing_tensor)

print("value at index 3 of example tensor:", example_tensor[3])
example_tensor[3] = 0
print("new tensor:", example_tensor)

sample_tensor = torch.tensor([5, 4, 3, 2, 1])
min_value = sample_tensor.min()
max_value = sample_tensor.max()
print("check minimum value in the tensor: ", min_value)
print("check maximum value in the tensor: ", max_value)

mean_std_tensor = torch.tensor([-1.0, 2.0, 1, -2])
Mean = mean_std_tensor.mean()
print("mean of mean_std_tensor: ", Mean)
std_dev = mean_std_tensor.std()
print("standard deviation of mean_std_tensor: ", std_dev)

a = torch.tensor([1, 1])
b = torch.tensor([2, 2])
add = a + b
multiply = a * b
print("addition of two tensors: ", add)
print("multiplication of two tensors: ", multiply)

进一步阅读

PyTorch 在 TensorFlow 发布 2.x 版本前曾有着更简单的语法,直到 TensorFlow 采用 Keras。要学习 PyTorch 的基础知识,你可能想阅读 PyTorch 的教程:

特别是可以在张量教程页面找到 PyTorch 张量的基础知识:

PyTorch 还有很多适合初学者的书籍。由于工具和语法在不断发展,推荐更近期出版的书籍。一个例子是

摘要

在本教程中,你学会了如何在 PyTorch 中使用一维张量。

具体来说,你学到了:

  • PyTorch 中一维张量操作的基础

  • 关于张量类型和形状以及如何执行张量切片和索引操作

  • 如何在张量对象上应用一些方法,例如平均值、标准差、加法和乘法

PyTorch 教程:如何使用 Python 开发深度学习模型

原文:machinelearningmastery.com/pytorch-tutorial-develop-deep-learning-models/

进行深度学习的预测建模是现代开发者需要掌握的一项技能。

PyTorch 是由 Facebook 开发和维护的顶级开源深度学习框架。

从本质上讲,PyTorch 是一个数学库,允许你对基于图的模型进行高效计算和自动微分。虽然直接实现这一点具有挑战性,但幸运的是,现代的 PyTorch API 提供了类和习惯用法,使你可以轻松开发一系列深度学习模型。

在本教程中,你将发现一个逐步指导,帮助你在 PyTorch 中开发深度学习模型。

完成本教程后,你将了解:

  • Torch 和 PyTorch 之间的区别以及如何安装和确认 PyTorch 是否正常工作。

  • PyTorch 模型的五个生命周期步骤,以及如何定义、训练和评估模型。

  • 如何为回归、分类和预测建模任务开发 PyTorch 深度学习模型。

通过我的书籍开始你的项目《深度学习与 PyTorch》。它提供了自学教程可运行的代码

让我们开始吧。PyTorch 教程 - 如何开发深度学习模型

PyTorch 教程 – 如何开发深度学习模型

照片由 Dimitry B。保留了一些权利。

PyTorch 教程概述

本教程的重点是使用 PyTorch API 进行常见的深度学习模型开发任务;我们不会深入探讨深度学习的数学和理论。关于这些,我推荐从这本优秀的书籍开始

学习 Python 深度学习的最佳方式是动手实践。直接深入,你可以稍后再回顾理论。

我设计了每个代码示例以使用最佳实践,并使其独立,以便你可以直接复制并粘贴到你的项目中,并根据你的具体需求进行调整。这将使你比仅依赖官方文档快速掌握 API 领先一步。

这是一个大型教程,因此分为三个部分;它们是:

  1. 如何安装 PyTorch

    1. 什么是 Torch 和 PyTorch?

    2. 如何安装 PyTorch

    3. 如何确认 PyTorch 已安装

  2. PyTorch 深度学习模型生命周期

    1. 步骤 1:准备数据

    2. 步骤 2:定义模型

    3. 步骤 3:训练模型

    4. 步骤 4:评估模型

    5. 步骤 5:进行预测

  3. 如何开发 PyTorch 深度学习模型

    1. 如何为二类分类开发 MLP

    2. 如何为多类分类开发 MLP

    3. 如何为回归开发 MLP

    4. 如何为图像分类开发 CNN

你可以在 Python 中进行深度学习!

完成这个教程。最多花费 60 分钟!

你不需要完全理解一切(至少现在不需要)。你的目标是从头到尾运行教程并获得结果。你不需要在第一遍完全理解一切。在进行过程中列出你的问题。大量使用 API 文档来学习你正在使用的所有函数。

你不需要首先了解数学。数学是描述算法工作原理的紧凑方式,特别是线性代数、概率和微积分工具。这些不是你学习算法工作的唯一工具。你也可以使用代码,并通过不同的输入和输出探索算法行为。了解数学不会告诉你选择哪种算法或如何最佳配置它。只有通过精心控制的实验才能发现这一点。

你不需要知道算法如何工作。了解深度学习算法的限制和如何配置是重要的,但学习算法可以稍后再做。你需要在较长的时间内慢慢建立起这方面的知识。今天,先熟悉平台的使用。

你不需要成为 Python 程序员。如果你是新手,Python 语言的语法可能很直观。和其他语言一样,专注于函数调用(例如 function())和赋值(例如 a = "b")。这将让你快速掌握语言的基础知识。你是开发者;你知道如何快速掌握一门语言的基础知识。开始动手,详细内容稍后再深入了解。

你不需要成为深度学习专家。你可以稍后了解各种算法的优缺点,有很多教程可以帮助你了解深度学习项目的步骤。

1. 如何安装 PyTorch

在这一节中,你将了解 PyTorch 是什么,如何安装它以及如何确认安装正确。

1.1. Torch 和 PyTorch 是什么?

PyTorch 是一个由 Facebook 开发和维护的开源 Python 深度学习库。

该项目始于 2016 年,并迅速成为开发者和研究人员中流行的框架。

TorchTorch7)是一个用 C 语言编写的开源深度学习项目,通常通过 Lua 接口使用。它是 PyTorch 的前身项目,目前已不再积极开发。PyTorch 在其名称中包含了“Torch”,以示尊重先前的 torch 库,“Py”前缀表示新项目专注于 Python。

PyTorch API 简单且灵活,使其成为学术界和研究人员开发新深度学习模型和应用的最爱。广泛的使用导致了许多针对特定应用的扩展(例如文本、计算机视觉和音频数据),以及可能直接使用的预训练模型。因此,它可能是学术界使用的最受欢迎的库。

相比于更简单的接口如Keras,PyTorch 的灵活性以易用性为代价,特别是对于初学者。选择使用 PyTorch 而不是 Keras 会放弃一些易用性、稍微陡峭的学习曲线和更多的代码以获得更多的灵活性,也许还有一个更活跃的学术社区。

1.2. 如何安装 PyTorch

在安装 PyTorch 之前,请确保你已经安装了 Python,例如 Python 3.6 或更高版本。

如果你没有安装 Python,你可以使用 Anaconda 安装。这个教程将向你展示如何:

有许多方法可以安装 PyTorch 开源深度学习库。

在工作站上安装 PyTorch 最常见,也许最简单的方法是使用 pip。

例如,在命令行中,你可以输入:

sudo pip install torch

深度学习最流行的应用之一是计算机视觉,而 PyTorch 的计算机视觉包被称为“torchvision。”

强烈建议同时安装 torchvision,可以按照以下方法安装:

sudo pip install torchvision

如果你更愿意使用更具体于你的平台或包管理器的安装方法,你可以在这里查看完整的安装说明:

现在无需设置 GPU。

本教程中的所有示例在现代 CPU 上都能正常运行。如果你想为 GPU 配置 PyTorch,可以在完成本教程后进行。不要分心!

1.3. 如何确认 PyTorch 已安装

一旦 PyTorch 安装完成,确认库是否成功安装并且可以开始使用是很重要的。

不要跳过这一步。

如果 PyTorch 未正确安装或在此步骤中出现错误,你将无法在后续运行示例。

创建一个名为 versions.py 的新文件,并将以下代码复制粘贴到文件中。

# check pytorch version
import torch
print(torch.__version__)

保存文件,然后打开你的命令行并将目录更改为你保存文件的位置。

然后输入:

python versions.py

然后你应该看到类似以下的输出:

1.3.1

这确认了 PyTorch 已正确安装,并且我们都在使用相同的版本。

这还展示了如何从命令行运行 Python 脚本。我建议以这种方式从命令行运行所有代码,而不是从笔记本或 IDE 中运行。

2. PyTorch 深度学习模型生命周期

在本节中,你将了解深度学习模型的生命周期和你可以用来定义模型的 PyTorch API。

模型具有生命周期,这个非常简单的知识为建模数据集和理解 PyTorch API 提供了基础。

生命周期中的五个步骤如下:

  • 1. 准备数据。

  • 2. 定义模型。

  • 3. 训练模型。

  • 4. 评估模型。

  • 5. 做出预测。

让我们逐步仔细看看每个步骤。

注意:有许多方法可以使用 PyTorch API 实现这些步骤,虽然我旨在展示最简单的、最常见的或最惯用的方法。

如果你发现更好的方法,请在下面的评论中告诉我。

第一步:准备数据

第一步是加载和准备你的数据。

神经网络模型需要数值输入数据和数值输出数据。

你可以使用标准 Python 库来加载和准备表格数据,如 CSV 文件。例如,可以使用 Pandas 加载 CSV 文件,使用 scikit-learn 的工具对类别数据(如类别标签)进行编码。

PyTorch 提供了 Dataset 类,你可以扩展和自定义它来加载你的数据集。

例如,你的数据集对象的构造函数可以加载你的数据文件(例如 CSV 文件)。然后,你可以重写 len() 函数,该函数用于获取数据集的长度(行数或样本数),以及 getitem() 函数,该函数用于通过索引获取特定样本。

在加载数据集时,你还可以执行任何所需的转换,如缩放或编码。

下面提供了一个自定义 Dataset 类的骨架。

# dataset definition
class CSVDataset(Dataset):
    # load the dataset
    def __init__(self, path):
        # store the inputs and outputs
        self.X = ...
        self.y = ...

    # number of rows in the dataset
    def __len__(self):
        return len(self.X)

    # get a row at an index
    def __getitem__(self, idx):
        return [self.X[idx], self.y[idx]]

加载后,PyTorch 提供了 DataLoader 类,用于在训练和评估模型期间遍历 Dataset 实例。

可以为训练数据集、测试数据集甚至验证数据集创建 DataLoader 实例。

random_split() 函数 可以用来将数据集拆分为训练集和测试集。拆分后,可以将 Dataset 的选择行提供给 DataLoader,并设置批大小和数据是否每个 epoch 应该被打乱。

例如,我们可以通过传入数据集中选择的行样本来定义 DataLoader

...
# create the dataset
dataset = CSVDataset(...)
# select rows from the dataset
train, test = random_split(dataset, [[...], [...]])
# create a data loader for train and test sets
train_dl = DataLoader(train, batch_size=32, shuffle=True)
test_dl = DataLoader(test, batch_size=1024, shuffle=False)

一旦定义了,可以对 DataLoader 进行枚举,每次迭代产生一批样本。

...
# train the model
for i, (inputs, targets) in enumerate(train_dl):
	...

第二步:定义模型

下一步是定义模型。

在 PyTorch 中定义模型的惯用方法是定义一个扩展 Module 类 的类。

类的构造函数定义了模型的层和重写的forward()函数定义了如何通过模型的定义层进行前向传播的方法。

许多层都可用,例如用于全连接层的Linear,用于卷积层的Conv2d,以及用于池化层的MaxPool2d

激活函数也可以作为层来定义,例如ReLUSoftmaxSigmoid

下面是一个具有一个层的简单 MLP 模型的示例。

# model definition
class MLP(Module):
    # define model elements
    def __init__(self, n_inputs):
        super(MLP, self).__init__()
        self.layer = Linear(n_inputs, 1)
        self.activation = Sigmoid()

    # forward propagate input
    def forward(self, X):
        X = self.layer(X)
        X = self.activation(X)
        return X

给定层的权重也可以在构造函数中定义层之后初始化。

常见的示例包括XavierHe 权重初始化方案。例如:

...
xavier_uniform_(self.layer.weight)

步骤 3:训练模型

训练过程要求您定义一个损失函数和一个优化算法。

常见的损失函数包括以下内容:

关于损失函数的更多信息,请参阅教程:

使用随机梯度下降进行优化,标准算法由SGD 类提供,尽管还有其他版本的算法可用,例如Adam

# define the optimization
criterion = MSELoss()
optimizer = SGD(model.parameters(), lr=0.01, momentum=0.9)

训练模型涉及枚举用于训练数据集的DataLoader

首先,需要一个循环来迭代训练周期的数量。然后,需要一个内部循环来处理随机梯度下降的小批量。

...
# enumerate epochs
for epoch in range(100):
    # enumerate mini batches
    for i, (inputs, targets) in enumerate(train_dl):
    	...

模型的每次更新都遵循相同的一般模式,包括:

  • 清除最后的误差梯度。

  • 通过模型的输入进行前向传递。

  • 计算模型输出的损失。

  • 通过模型进行误差反向传播。

  • 更新模型以尝试减少损失。

例如:

...
# clear the gradients
optimizer.zero_grad()
# compute the model output
yhat = model(inputs)
# calculate loss
loss = criterion(yhat, targets)
# credit assignment
loss.backward()
# update model weights
optimizer.step()

步骤 4:评估模型

一旦模型适合,就可以在测试数据集上进行评估。

这可以通过使用DataLoader来处理测试数据集并收集测试集的预测值来实现,然后将预测值与测试集的预期值进行比较,并计算性能指标。

...
for i, (inputs, targets) in enumerate(test_dl):
    # evaluate the model on the test set
    yhat = model(inputs)
    ...

步骤 5:进行预测

适合的模型可以用来对新数据进行预测。

例如,您可能有一张单独的图片或一行数据,想要进行预测。

这要求您将数据封装在PyTorch Tensor数据结构中。

Tensor 只是 PyTorch 版本的 NumPy 数组,用于保存数据。它还允许您执行模型图中的自动微分任务,比如在训练模型时调用backward()

预测结果也将是一个 Tensor,尽管您可以通过分离 Tensor来从自动微分图中获取 NumPy 数组,并调用 NumPy 函数。

...
# convert row to data
row = Variable(Tensor([row]).float())
# make prediction
yhat = model(row)
# retrieve numpy array
yhat = yhat.detach().numpy()

现在我们已经熟悉了 PyTorch API 的高级别和模型生命周期,让我们看看如何从头开始开发一些标准的深度学习模型。

3. 如何开发 PyTorch 深度学习模型

在本节中,您将了解如何开发、评估和预测使用标准深度学习模型(包括多层感知器(MLP)和卷积神经网络(CNN))的方法。

多层感知器模型(简称 MLP)是一种标准的全连接神经网络模型。

它由节点层组成,其中每个节点与前一层的所有输出连接,并且每个节点的输出与下一层节点的所有输入连接。

MLP 是一个具有一个或多个全连接层的模型。这种模型适用于表格数据,即数据在表格或电子表格中的形式,每个变量对应一列,每个观测对应一行。您可能想用 MLP 探索三种预测建模问题,它们分别是二元分类、多类分类和回归。

让我们为每种情况在真实数据集上拟合一个模型。

注意:本节中的模型是有效的,但尚未经过优化。请尝试提升它们的性能。在下方评论区分享您的发现。

3.1. 如何开发用于二元分类的 MLP

我们将使用电离层二元(两类)分类数据集来演示 MLP 进行二元分类。

此数据集涉及根据雷达返回预测大气中是否存在结构。

数据集将使用 Pandas 自动下载,但您也可以在此处了解更多信息。

我们将使用一个LabelEncoder将字符串标签编码为整数值 0 和 1。模型将在 67%的数据上进行拟合,其余 33%将用于评估,使用train_test_split()函数进行拆分。

使用‘relu’激活函数和‘He Uniform’权重初始化是一种良好的实践。这种组合有助于克服梯度消失的问题,尤其是在训练深度神经网络模型时。有关 ReLU 的更多信息,请参阅教程:

该模型预测类别 1 的概率,并使用 sigmoid 激活函数。模型使用随机梯度下降进行优化,并力求最小化二元交叉熵损失

完整示例列在下方。

# pytorch mlp for binary classification
from numpy import vstack
from pandas import read_csv
from sklearn.preprocessing import LabelEncoder
from sklearn.metrics import accuracy_score
from torch.utils.data import Dataset
from torch.utils.data import DataLoader
from torch.utils.data import random_split
from torch import Tensor
from torch.nn import Linear
from torch.nn import ReLU
from torch.nn import Sigmoid
from torch.nn import Module
from torch.optim import SGD
from torch.nn import BCELoss
from torch.nn.init import kaiming_uniform_
from torch.nn.init import xavier_uniform_

# dataset definition
class CSVDataset(Dataset):
    # load the dataset
    def __init__(self, path):
        # load the csv file as a dataframe
        df = read_csv(path, header=None)
        # store the inputs and outputs
        self.X = df.values[:, :-1]
        self.y = df.values[:, -1]
        # ensure input data is floats
        self.X = self.X.astype('float32')
        # label encode target and ensure the values are floats
        self.y = LabelEncoder().fit_transform(self.y)
        self.y = self.y.astype('float32')
        self.y = self.y.reshape((len(self.y), 1))

    # number of rows in the dataset
    def __len__(self):
        return len(self.X)

    # get a row at an index
    def __getitem__(self, idx):
        return [self.X[idx], self.y[idx]]

    # get indexes for train and test rows
    def get_splits(self, n_test=0.33):
        # determine sizes
        test_size = round(n_test * len(self.X))
        train_size = len(self.X) - test_size
        # calculate the split
        return random_split(self, [train_size, test_size])

# model definition
class MLP(Module):
    # define model elements
    def __init__(self, n_inputs):
        super(MLP, self).__init__()
        # input to first hidden layer
        self.hidden1 = Linear(n_inputs, 10)
        kaiming_uniform_(self.hidden1.weight, nonlinearity='relu')
        self.act1 = ReLU()
        # second hidden layer
        self.hidden2 = Linear(10, 8)
        kaiming_uniform_(self.hidden2.weight, nonlinearity='relu')
        self.act2 = ReLU()
        # third hidden layer and output
        self.hidden3 = Linear(8, 1)
        xavier_uniform_(self.hidden3.weight)
        self.act3 = Sigmoid()

    # forward propagate input
    def forward(self, X):
        # input to first hidden layer
        X = self.hidden1(X)
        X = self.act1(X)
         # second hidden layer
        X = self.hidden2(X)
        X = self.act2(X)
        # third hidden layer and output
        X = self.hidden3(X)
        X = self.act3(X)
        return X

# prepare the dataset
def prepare_data(path):
    # load the dataset
    dataset = CSVDataset(path)
    # calculate split
    train, test = dataset.get_splits()
    # prepare data loaders
    train_dl = DataLoader(train, batch_size=32, shuffle=True)
    test_dl = DataLoader(test, batch_size=1024, shuffle=False)
    return train_dl, test_dl

# train the model
def train_model(train_dl, model):
    # define the optimization
    criterion = BCELoss()
    optimizer = SGD(model.parameters(), lr=0.01, momentum=0.9)
    # enumerate epochs
    for epoch in range(100):
        # enumerate mini batches
        for i, (inputs, targets) in enumerate(train_dl):
            # clear the gradients
            optimizer.zero_grad()
            # compute the model output
            yhat = model(inputs)
            # calculate loss
            loss = criterion(yhat, targets)
            # credit assignment
            loss.backward()
            # update model weights
            optimizer.step()

# evaluate the model
def evaluate_model(test_dl, model):
    predictions, actuals = list(), list()
    for i, (inputs, targets) in enumerate(test_dl):
        # evaluate the model on the test set
        yhat = model(inputs)
        # retrieve numpy array
        yhat = yhat.detach().numpy()
        actual = targets.numpy()
        actual = actual.reshape((len(actual), 1))
        # round to class values
        yhat = yhat.round()
        # store
        predictions.append(yhat)
        actuals.append(actual)
    predictions, actuals = vstack(predictions), vstack(actuals)
    # calculate accuracy
    acc = accuracy_score(actuals, predictions)
    return acc

# make a class prediction for one row of data
def predict(row, model):
    # convert row to data
    row = Tensor([row])
    # make prediction
    yhat = model(row)
    # retrieve numpy array
    yhat = yhat.detach().numpy()
    return yhat

# prepare the data
path = 'https://raw.githubusercontent.com/jbrownlee/Datasets/master/ionosphere.csv'
train_dl, test_dl = prepare_data(path)
print(len(train_dl.dataset), len(test_dl.dataset))
# define the network
model = MLP(34)
# train the model
train_model(train_dl, model)
# evaluate the model
acc = evaluate_model(test_dl, model)
print('Accuracy: %.3f' % acc)
# make a single prediction (expect class=1)
row = [1,0,0.99539,-0.05889,0.85243,0.02306,0.83398,-0.37708,1,0.03760,0.85243,-0.17755,0.59755,-0.44945,0.60536,-0.38223,0.84356,-0.38542,0.58212,-0.32192,0.56971,-0.29674,0.36946,-0.47357,0.56811,-0.51171,0.41078,-0.46168,0.21266,-0.34090,0.42267,-0.54487,0.18641,-0.45300]
yhat = predict(row, model)
print('Predicted: %.3f (class=%d)' % (yhat, yhat.round()))

运行示例首先报告训练和测试数据集的形状,然后拟合模型并在测试数据集上进行评估。最后,对单行数据进行预测。

注意:由于算法或评估过程的随机性质,或者数值精度的差异,你的结果可能会有所不同。考虑多次运行示例,并比较平均结果。

进一步阅读

本节提供了更多关于该主题的资源,以便你能够深入了解。

书籍

PyTorch 项目

API

总结

在本教程中,你发现了一个逐步指南,帮助你在 PyTorch 中开发深度学习模型。

具体来说,你学到了:

  • Torch 和 PyTorch 的区别,以及如何安装和确认 PyTorch 是否正常工作。

  • PyTorch 模型的五步生命周期以及如何定义、拟合和评估模型。

  • 如何开发用于回归、分类和预测建模任务的 PyTorch 深度学习模型。

你有什么问题吗?

在下方评论中提出你的问题,我会尽力回答。

保存和加载你的 PyTorch 模型

原文:machinelearningmastery.com/save-and-load-your-pytorch-models/

深度学习模型是数据的数学抽象,其中涉及大量参数。训练这些参数可能需要数小时、数天甚至数周,但之后,你可以利用结果在新数据上应用。这在机器学习中称为推理。了解如何将训练好的模型保存在磁盘上,并在以后加载以进行推理是很重要的。在这篇文章中,你将学习如何将 PyTorch 模型保存到文件中,并重新加载以进行预测。阅读完这一章后,你将知道:

  • PyTorch 模型中的状态和参数是什么

  • 如何保存模型状态

  • 如何加载模型状态

启动你的项目,参阅我的书籍《PyTorch 深度学习》。它提供了自学教程可运行的代码

开始吧。

保存和加载你的 PyTorch 模型

照片由Joseph Chan提供。保留一些权利。

概述

本文分为三部分,它们是

  • 构建一个示例模型

  • PyTorch 模型内部包含什么

  • 访问模型的state_dict

构建一个示例模型

让我们从一个非常简单的 PyTorch 模型开始。这是一个基于鸢尾花数据集的模型。你将使用 scikit-learn 加载数据集(目标是整数标签 0、1 和 2),并为这个多类分类问题训练一个神经网络。在这个模型中,你使用了 log softmax 作为输出激活函数,以便与负对数似然损失函数结合。这相当于没有输出激活函数结合交叉熵损失函数。

import torch
import torch.nn as nn
import torch.optim as optim
from sklearn.datasets import load_iris
from sklearn.model_selection import train_test_split

# Load data into NumPy arrays
data = load_iris()
X, y = data["data"], data["target"]

# convert NumPy array into PyTorch tensors
X = torch.tensor(X, dtype=torch.float32)
y = torch.tensor(y, dtype=torch.long)

# split
X_train, X_test, y_train, y_test = train_test_split(X, y, train_size=0.7, shuffle=True)

# PyTorch model
class Multiclass(nn.Module):
    def __init__(self):
        super().__init__()
        self.hidden = nn.Linear(4, 8)
        self.act = nn.ReLU()
        self.output = nn.Linear(8, 3)
        self.logsoftmax = nn.LogSoftmax(dim=1)

    def forward(self, x):
        x = self.act(self.hidden(x))
        x = self.logsoftmax(self.output(x))
        return x

model = Multiclass()

# loss metric and optimizer
loss_fn = nn.NLLLoss()
optimizer = optim.Adam(model.parameters(), lr=0.001)

# prepare model and training parameters
n_epochs = 100
batch_size = 5
batch_start = torch.arange(0, len(X), batch_size)

# training loop
for epoch in range(n_epochs):
    for start in batch_start:
        # take a batch
        X_batch = X_train[start:start+batch_size]
        y_batch = y_train[start:start+batch_size]
        # forward pass
        y_pred = model(X_batch)
        loss = loss_fn(y_pred, y_batch)
        # backward pass
        optimizer.zero_grad()
        loss.backward()
        # update weights
        optimizer.step()

使用如此简单的模型和小数据集,不应该花费太长时间完成训练。之后,我们可以通过使用测试集来验证该模型是否有效:

...
y_pred = model(X_test)
acc = (torch.argmax(y_pred, 1) == y_test).float().mean()
print("Accuracy: %.2f" % acc)

例如,它会打印出

Accuracy: 0.96

想要开始使用 PyTorch 进行深度学习吗?

现在立即注册我的免费电子邮件速成课程(附示例代码)。

点击注册,并获取课程的免费 PDF 电子书版本。

PyTorch 模型内部包含什么

PyTorch 模型是 Python 中的一个对象。它包含一些深度学习构建块,例如各种层和激活函数。它还知道如何将它们连接起来,以便从输入张量中生成输出。模型的算法在创建时是固定的,但它有可训练的参数,这些参数在训练循环中应被修改,以使模型更加准确。

你看到如何在设置优化器以进行训练循环时获取模型参数,即

optimizer = optim.Adam(model.parameters(), lr=0.001)

函数model.parameters()为你提供了一个生成器,依次引用每一层的可训练参数,形式为 PyTorch 张量。因此,你可以复制这些参数或覆盖它们,例如:

# create a new model
newmodel = Multiclass()
# ask PyTorch to ignore autograd on update and overwrite parameters
with torch.no_grad():
    for newtensor, oldtensor in zip(newmodel.parameters(), model.parameters()):
        newtensor.copy_(oldtensor)
# test with new model using copied tensor
y_pred = newmodel(X_test)
acc = (torch.argmax(y_pred, 1) == y_test).float().mean()
print("Accuracy: %.2f" % acc)

结果应该与之前完全相同,因为你通过复制参数使两个模型变得完全相同。

然而,情况并非总是如此。一些模型具有不可训练的参数。一个例子是许多卷积神经网络中常见的批归一化层。它的作用是在前一层产生的张量上应用归一化,并将归一化后的张量传递给下一层。它有两个参数:均值和标准差,这些参数在训练循环中从输入数据中学习,但不能被优化器训练。因此,这些参数不是model.parameters()的一部分,但同样重要。

访问模型的state_dict

要访问模型的所有参数,无论是否可训练,你可以从state_dict()函数中获取。从上面的模型中,你可以得到以下内容:

import pprint
pp = pprint.PrettyPrinter(indent=4)
pp.pprint(model.state_dict())

上面的模型产生了以下结果:

OrderedDict([   (   'hidden.weight',
                    tensor([[ 0.1480,  0.0336,  0.3425,  0.2832],
        [ 0.5265,  0.8587, -0.7023, -1.1149],
        [ 0.1620,  0.8440, -0.6189, -0.6513],
        [-0.1559,  0.0393, -0.4701,  0.0825],
        [ 0.6364, -0.6622,  1.1150,  0.9162],
        [ 0.2081, -0.0958, -0.2601, -0.3148],
        [-0.0804,  0.1027,  0.7363,  0.6068],
        [-0.4101, -0.3774, -0.1852,  0.1524]])),
                (   'hidden.bias',
                    tensor([ 0.2057,  0.7998, -0.0578,  0.1041, -0.3903, -0.4521, -0.5307, -0.1532])),
                (   'output.weight',
                    tensor([[-0.0954,  0.8683,  1.0667,  0.2382, -0.4245, -0.0409, -0.2587, -0.0745],
        [-0.0829,  0.8642, -1.6892, -0.0188,  0.0420, -0.1020,  0.0344, -0.1210],
        [-0.0176, -1.2809, -0.3040,  0.1985,  0.2423,  0.3333,  0.4523, -0.1928]])),
                ('output.bias', tensor([ 0.0998,  0.6360, -0.2990]))])

它被称为state_dict,因为模型的所有状态变量都在这里。它是来自 Python 内置collections模块的一个OrderedDict对象。PyTorch 模型中的所有组件都有一个名称,参数也是如此。OrderedDict对象允许你通过匹配名称将权重正确地映射回参数。

这就是你应该如何保存和加载模型:将模型状态提取到OrderedDict中,序列化并保存到磁盘。在推理时,你首先创建一个模型(不进行训练),然后加载状态。在 Python 中,序列化的本机格式是 pickle:

import pickle

# Save model
with open("iris-model.pickle", "wb") as fp:
    pickle.dump(model.state_dict(), fp)

# Create new model and load states
newmodel = Multiclass()
with open("iris-model.pickle", "rb") as fp:
    newmodel.load_state_dict(pickle.load(fp))

# test with new model using copied tensor
y_pred = newmodel(X_test)
acc = (torch.argmax(y_pred, 1) == y_test).float().mean()
print("Accuracy: %.2f" % acc)

你知道它有效,因为你没有训练的模型产生了与训练过的模型相同的结果。

确实,推荐的方式是使用 PyTorch API 来保存和加载状态,而不是手动使用 pickle:

# Save model
torch.save(model.state_dict(), "iris-model.pth")

# Create new model and load states
newmodel = Multiclass()
newmodel.load_state_dict(torch.load("iris-model.pth"))

# test with new model using copied tensor
y_pred = newmodel(X_test)
acc = (torch.argmax(y_pred, 1) == y_test).float().mean()
print("Accuracy: %.2f" % acc)

*.pth文件实际上是由 PyTorch 创建的一些 pickle 文件的压缩文件。推荐这样做,因为 PyTorch 可以在其中存储额外的信息。请注意,你仅保存了状态而不是模型。你仍然需要使用 Python 代码创建模型并将状态加载到其中。如果你还希望保存模型本身,你可以传入整个模型,而不是状态:

# Save model
torch.save(model, "iris-model-full.pth")

# Load model
newmodel = torch.load("iris-model-full.pth")

# test with new model using copied tensor
y_pred = newmodel(X_test)
acc = (torch.argmax(y_pred, 1) == y_test).float().mean()
print("Accuracy: %.2f" % acc)

但请记住,由于 Python 语言的特性,这并不会免除你需要保存模型代码的责任。上面的newmodel对象是你之前定义的Multiclass类的一个实例。当你从磁盘加载模型时,Python 需要详细知道这个类是如何定义的。如果你仅运行torch.load()这一行脚本,你将看到以下错误信息:

Traceback (most recent call last):
File "<stdin>", line 1, in <module>
File "/.../torch/serialization.py", line 789, in load
return _load(opened_zipfile, map_location, pickle_module, **pickle_load_args)
File "/.../torch/serialization.py", line 1131, in _load
result = unpickler.load()
File "/.../torch/serialization.py", line 1124, in find_class
return super().find_class(mod_name, name)
AttributeError: Can't get attribute 'Multiclass' on <module '__main__' (built-in)>

这就是为什么推荐只保存状态字典而不是整个模型的原因。

将所有内容整合在一起,以下是展示如何创建模型、训练它并保存到磁盘的完整代码:

import torch
import torch.nn as nn
import torch.optim as optim
from sklearn.datasets import load_iris
from sklearn.model_selection import train_test_split

# Load data into NumPy arrays
data = load_iris()
X, y = data["data"], data["target"]

# convert NumPy array into PyTorch tensors
X = torch.tensor(X, dtype=torch.float32)
y = torch.tensor(y, dtype=torch.long)

# split
X_train, X_test, y_train, y_test = train_test_split(X, y, train_size=0.7, shuffle=True)

# PyTorch model
class Multiclass(nn.Module):
    def __init__(self):
        super().__init__()
        self.hidden = nn.Linear(4, 8)
        self.act = nn.ReLU()
        self.output = nn.Linear(8, 3)
        self.logsoftmax = nn.LogSoftmax(dim=1)

    def forward(self, x):
        x = self.act(self.hidden(x))
        x = self.logsoftmax(self.output(x))
        return x

model = Multiclass()

# loss metric and optimizer
loss_fn = nn.NLLLoss()
optimizer = optim.Adam(model.parameters(), lr=0.001)

# prepare model and training parameters
n_epochs = 100
batch_size = 5
batch_start = torch.arange(0, len(X), batch_size)

# training loop
for epoch in range(n_epochs):
    for start in batch_start:
        # take a batch
        X_batch = X_train[start:start+batch_size]
        y_batch = y_train[start:start+batch_size]
        # forward pass
        y_pred = model(X_batch)
        loss = loss_fn(y_pred, y_batch)
        # backward pass
        optimizer.zero_grad()
        loss.backward()
        # update weights
        optimizer.step()

# Save model
torch.save(model.state_dict(), "iris-model.pth")

以下是如何从磁盘加载模型并进行推理的步骤:

import torch
import torch.nn as nn
import torch.optim as optim
from sklearn.datasets import load_iris
from sklearn.model_selection import train_test_split

# Load data into NumPy arrays
data = load_iris()
X, y = data["data"], data["target"]

# convert NumPy array into PyTorch tensors
X = torch.tensor(X, dtype=torch.float32)
y = torch.tensor(y, dtype=torch.long)

# PyTorch model
class Multiclass(nn.Module):
    def __init__(self):
        super().__init__()
        self.hidden = nn.Linear(4, 8)
        self.act = nn.ReLU()
        self.output = nn.Linear(8, 3)
        self.logsoftmax = nn.LogSoftmax(dim=1)

    def forward(self, x):
        x = self.act(self.hidden(x))
        x = self.logsoftmax(self.output(x))
        return x

# Create new model and load states
model = Multiclass()
with open("iris-model.pickle", "rb") as fp:
    model.load_state_dict(pickle.load(fp))

# Run model for inference
y_pred = model(X_test)
acc = (torch.argmax(y_pred, 1) == y_test).float().mean()
print("Accuracy: %.2f" % acc)

进一步阅读

本节提供了更多关于此主题的资源,帮助你深入了解。

总结

在这篇文章中,你学习了如何将训练好的 PyTorch 模型保存在磁盘中以及如何重新使用它。特别是,你学到了

  • 在 PyTorch 模型中,什么是参数和状态

  • 如何将模型的所有必要状态保存到磁盘

  • 如何从保存的状态重建一个可用的模型

使用 PyTorch 进行 LSTM 文本生成

原文:machinelearningmastery.com/text-generation-with-lstm-in-pytorch/

循环神经网络可以用于时间序列预测。在其中,创建了一个回归神经网络。它也可以被用作生成模型,通常是一个分类神经网络模型。生成模型的目标是从数据中学习某种模式,这样当它被提供一些提示时,它可以创建一个完整的输出,与学习的模式风格相同。

在本文中,你将发现如何使用 PyTorch 中的 LSTM 循环神经网络构建一个文本生成模型。完成本文后,你将了解:

  • 从哪里下载可以用来训练文本生成模型的免费语料库

  • 如何将文本序列问题框定为循环神经网络生成模型

  • 如何开发一个 LSTM 来生成给定问题的合理文本序列

启动你的项目,使用我的书籍Deep Learning with PyTorch。它提供自学教程可工作的代码

让我们开始吧!

使用 PyTorch 进行 LSTM 文本生成

照片由Egor Lyfar提供。部分权利保留。

概述

本文分为六个部分;它们是:

  • 生成模型是什么

  • 获取文本数据

  • 一个小的 LSTM 网络来预测下一个字符

  • 使用 LSTM 模型生成文本

  • 使用更大的 LSTM 网络

  • 使用 GPU 加速更快的训练

生成模型是什么

生成模型确实只是另一个能够创造新事物的机器学习模型。生成对抗网络(GAN)是其自身的一类。使用注意机制的 Transformer 模型也被发现对生成文本段落有用。

这只是一个机器学习模型,因为模型已经通过现有数据进行了训练,所以它从中学到了一些东西。取决于如何训练它,它们可以有很大不同的工作方式。在本文中,创建了一个基于字符的生成模型。这意味着训练一个模型,它将一系列字符(字母和标点符号)作为输入,下一个即时字符作为目标。只要它能够预测接下来的字符是什么,给定前面的内容,你就可以在循环中运行模型以生成一段长文本。

这个模型可能是最简单的一个。然而,人类语言是复杂的。你不应该期望它能产生非常高质量的输出。即便如此,你需要大量的数据并且长时间训练模型,才能看到合理的结果。

想要开始使用 PyTorch 进行深度学习吗?

现在参加我的免费电子邮件速成课程(附有示例代码)。

点击注册,并免费获取课程的 PDF 电子书版本。

获取文本数据

获取高质量的数据对成功的生成模型至关重要。幸运的是,许多经典文本已经不再受版权保护。这意味着你可以免费下载这些书籍的所有文本,并在实验中使用它们,例如创建生成模型。或许获取不再受版权保护的免费书籍的最佳地方是古腾堡计划。

在这篇文章中,你将使用童年时期喜欢的一本书作为数据集,刘易斯·卡罗尔的《爱丽丝梦游仙境》:

你的模型将学习字符之间的依赖关系和字符序列中的条件概率,这样你就可以生成全新且原创的字符序列。这个过程非常有趣,推荐用古腾堡计划中的其他书籍重复这些实验。这些实验不限于文本;你还可以尝试其他 ASCII 数据,如计算机源代码、LATEX、HTML 或 Markdown 中的标记文档等。

你可以免费下载这本书的完整 ASCII 格式文本(纯文本 UTF-8),并将其放置在你的工作目录中,文件名为wonderland.txt。现在,你需要准备数据集以进行建模。古腾堡计划为每本书添加了标准的页眉和页脚,这不是原始文本的一部分。在文本编辑器中打开文件并删除页眉和页脚。页眉是明显的,并以如下文本结束:

*** START OF THIS PROJECT GUTENBERG EBOOK ALICE'S ADVENTURES IN WONDERLAND ***

页脚是指在如下文本行之后的所有文本:

THE END

你应该剩下一个大约有 3,400 行文本的文本文件。

一个小型 LSTM 网络来预测下一个字符

首先,你需要对数据进行一些预处理,才能构建模型。神经网络模型只能处理数字,而不能处理文本。因此,你需要将字符转换为数字。为了简化问题,你还需要将所有大写字母转换为小写字母。

在下面,你打开文本文件,将所有字母转换为小写,并创建一个 Python 字典char_to_int来将字符映射为不同的整数。例如,书中的唯一已排序小写字符列表如下:

['\n', '\r', ' ', '!', '"', "'", '(', ')', '*', ',', '-', '.', ':', ';', '?', '[', ']',
'_', 'a', 'b', 'c', 'd', 'e', 'f', 'g', 'h', 'i', 'j', 'k', 'l', 'm', 'n', 'o', 'p',
'q', 'r', 's', 't', 'u', 'v', 'w', 'x', 'y', 'z', '\xbb', '\xbf', '\xef']

由于这个问题是基于字符的,“词汇表”是文本中曾用到的不同字符。

import numpy as np

# load ascii text and covert to lowercase
filename = "wonderland.txt"
raw_text = open(filename, 'r', encoding='utf-8').read()
raw_text = raw_text.lower()

# create mapping of unique chars to integers
chars = sorted(list(set(raw_text)))
char_to_int = dict((c, i) for i, c in enumerate(chars))

# summarize the loaded data
n_chars = len(raw_text)
n_vocab = len(chars)
print("Total Characters: ", n_chars)
print("Total Vocab: ", n_vocab)

这应该打印出:

Total Characters: 144574
Total Vocab: 50

你可以看到这本书大约有 150,000 个字符,并且当转换为小写时,词汇中只有 50 个不同的字符供网络学习——比字母表中的 26 个字符要多得多。

接下来,你需要将文本分为输入和目标。这里使用了 100 个字符的窗口。也就是说,使用字符 1 到 100 作为输入,你的模型将预测字符 101。如果使用 5 个字符的窗口,那么单词“chapter”将变成两个数据样本:

chapt -> e
hapte -> r

在这样的长文本中,可以创建无数窗口,这会生成一个包含大量样本的数据集:

# prepare the dataset of input to output pairs encoded as integers
seq_length = 100
dataX = []
dataY = []
for i in range(0, n_chars - seq_length, 1):
    seq_in = raw_text[i:i + seq_length]
    seq_out = raw_text[i + seq_length]
    dataX.append([char_to_int[char] for char in seq_in])
    dataY.append(char_to_int[seq_out])
n_patterns = len(dataX)
print("Total Patterns: ", n_patterns)

运行上述代码,你可以看到总共创建了 144,474 个样本。每个样本现在都是整数形式,使用char_to_int映射进行转换。然而,PyTorch 模型更喜欢浮点张量。因此,你应该将这些转换为 PyTorch 张量。由于模型将使用 LSTM 层,因此输入张量应为(样本,时间步,特征)的维度。为了帮助训练,规范化输入到 0 到 1 也是一个好主意。因此你有如下内容:

import torch
import torch.nn as nn
import torch.optim as optim

# reshape X to be [samples, time steps, features]
X = torch.tensor(dataX, dtype=torch.float32).reshape(n_patterns, seq_length, 1)
X = X / float(n_vocab)
y = torch.tensor(dataY)
print(X.shape, y.shape)

你现在可以定义你的 LSTM 模型。在这里,你定义了一个具有 256 个隐藏单元的单层 LSTM。输入是单一特征(即,一个字符对应一个整数)。在 LSTM 层之后添加了一个概率为 0.2 的 dropout 层。LSTM 层的输出是一个元组,其中第一个元素是每个时间步的 LSTM 单元的隐藏状态。这是隐藏状态如何随着 LSTM 单元接受每个时间步输入而演变的历史。假设最后的隐藏状态包含了最多的信息,因此仅将最后的隐藏状态传递到输出层。输出层是一个全连接层,用于为 50 个词汇产生 logits。通过 softmax 函数,logits 可以转换为类似概率的预测。

import torch.nn as nn
import torch.optim as optim
import torch.utils.data as data

class CharModel(nn.Module):
    def __init__(self):
        super().__init__()
        self.lstm = nn.LSTM(input_size=1, hidden_size=256, num_layers=1, batch_first=True)
        self.dropout = nn.Dropout(0.2)
        self.linear = nn.Linear(256, n_vocab)
    def forward(self, x):
        x, _ = self.lstm(x)
        # take only the last output
        x = x[:, -1, :]
        # produce output
        x = self.linear(self.dropout(x))
        return x

这是一个用于 50 类单字符分类的模型。因此应使用交叉熵损失函数。该模型使用 Adam 优化器进行优化。训练循环如下所示。为简化起见,没有创建测试集,但模型在每个 epoch 结束时会再次使用训练集进行评估,以跟踪进度。

这个程序可能会运行很长时间,尤其是在 CPU 上!为了保留工作的成果,保存了迄今为止找到的最佳模型以备将来使用。

n_epochs = 40
batch_size = 128
model = CharModel()

optimizer = optim.Adam(model.parameters())
loss_fn = nn.CrossEntropyLoss(reduction="sum")
loader = data.DataLoader(data.TensorDataset(X, y), shuffle=True, batch_size=batch_size)

best_model = None
best_loss = np.inf
for epoch in range(n_epochs):
    model.train()
    for X_batch, y_batch in loader:
        y_pred = model(X_batch)
        loss = loss_fn(y_pred, y_batch)
        optimizer.zero_grad()
        loss.backward()
        optimizer.step()
    # Validation
    model.eval()
    loss = 0
    with torch.no_grad():
        for X_batch, y_batch in loader:
            y_pred = model(X_batch)
            loss += loss_fn(y_pred, y_batch)
        if loss < best_loss:
            best_loss = loss
            best_model = model.state_dict()
        print("Epoch %d: Cross-entropy: %.4f" % (epoch, loss))

torch.save([best_model, char_to_dict], "single-char.pth")

运行上述代码可能会产生以下结果:

...
Epoch 35: Cross-entropy: 245745.2500
Epoch 36: Cross-entropy: 243908.7031
Epoch 37: Cross-entropy: 238833.5000
Epoch 38: Cross-entropy: 239069.0000
Epoch 39: Cross-entropy: 234176.2812

交叉熵几乎在每个 epoch 中都在下降。这意味着模型可能没有完全收敛,你可以训练更多的 epochs。当训练循环完成后,你应该会创建一个文件single-char.pth,其中包含迄今为止找到的最佳模型权重,以及此模型使用的字符到整数映射。

为了完整性,下面是将上述所有内容结合到一个脚本中的示例:

import numpy as np
import torch
import torch.nn as nn
import torch.optim as optim
import torch.utils.data as data

# load ascii text and covert to lowercase
filename = "wonderland.txt"
raw_text = open(filename, 'r', encoding='utf-8').read()
raw_text = raw_text.lower()

# create mapping of unique chars to integers
chars = sorted(list(set(raw_text)))
char_to_int = dict((c, i) for i, c in enumerate(chars))

# summarize the loaded data
n_chars = len(raw_text)
n_vocab = len(chars)
print("Total Characters: ", n_chars)
print("Total Vocab: ", n_vocab)

# prepare the dataset of input to output pairs encoded as integers
seq_length = 100
dataX = []
dataY = []
for i in range(0, n_chars - seq_length, 1):
    seq_in = raw_text[i:i + seq_length]
    seq_out = raw_text[i + seq_length]
    dataX.append([char_to_int[char] for char in seq_in])
    dataY.append(char_to_int[seq_out])
n_patterns = len(dataX)
print("Total Patterns: ", n_patterns)

# reshape X to be [samples, time steps, features]
X = torch.tensor(dataX, dtype=torch.float32).reshape(n_patterns, seq_length, 1)
X = X / float(n_vocab)
y = torch.tensor(dataY)

class CharModel(nn.Module):
    def __init__(self):
        super().__init__()
        self.lstm = nn.LSTM(input_size=1, hidden_size=256, num_layers=1, batch_first=True)
        self.dropout = nn.Dropout(0.2)
        self.linear = nn.Linear(256, n_vocab)
    def forward(self, x):
        x, _ = self.lstm(x)
        # take only the last output
        x = x[:, -1, :]
        # produce output
        x = self.linear(self.dropout(x))
        return x

n_epochs = 40
batch_size = 128
model = CharModel()

optimizer = optim.Adam(model.parameters())
loss_fn = nn.CrossEntropyLoss(reduction="sum")
loader = data.DataLoader(data.TensorDataset(X, y), shuffle=True, batch_size=batch_size)

best_model = None
best_loss = np.inf
for epoch in range(n_epochs):
    model.train()
    for X_batch, y_batch in loader:
        y_pred = model(X_batch)
        loss = loss_fn(y_pred, y_batch)
        optimizer.zero_grad()
        loss.backward()
        optimizer.step()
    # Validation
    model.eval()
    loss = 0
    with torch.no_grad():
        for X_batch, y_batch in loader:
            y_pred = model(X_batch)
            loss += loss_fn(y_pred, y_batch)
        if loss < best_loss:
            best_loss = loss
            best_model = model.state_dict()
        print("Epoch %d: Cross-entropy: %.4f" % (epoch, loss))

torch.save([best_model, char_to_int], "single-char.pth")

使用 LSTM 模型生成文本

由于模型已经经过良好的训练,使用训练好的 LSTM 网络生成文本相对简单。首先,你需要重新创建网络并从保存的检查点加载训练好的模型权重。然后你需要为模型创建一些提示以开始生成。提示可以是模型能够理解的任何内容。它是一个种子序列,用于给模型提供一个生成字符的起点。然后,将生成的字符添加到序列的末尾,并修剪掉第一个字符以保持一致的长度。这个过程会重复进行,直到你想要预测新的字符(例如,一段长度为 1000 个字符的序列)。你可以选择一个随机输入模式作为你的种子序列,然后在生成字符时打印它们。

生成提示的一个简单方法是从原始数据集中随机选择一个样本,例如,使用前一节获得的 raw_text,可以创建如下的提示:

seq_length = 100
start = np.random.randint(0, len(raw_text)-seq_length)
prompt = raw_text[start:start+seq_length]

但你需要提醒自己,你需要对其进行转换,因为这个提示是一个字符串,而模型期望的是一个整数向量。

整个代码仅如下所示:

import numpy as np
import torch
import torch.nn as nn

best_model, char_to_int = torch.load("single-char.pth")
n_vocab = len(char_to_int)
int_to_char = dict((i, c) for c, i in char_to_int.items())

# reload the model
class CharModel(nn.Module):
    def __init__(self):
        super().__init__()
        self.lstm = nn.LSTM(input_size=1, hidden_size=256, num_layers=1, batch_first=True)
        self.dropout = nn.Dropout(0.2)
        self.linear = nn.Linear(256, n_vocab)
    def forward(self, x):
        x, _ = self.lstm(x)
        # take only the last output
        x = x[:, -1, :]
        # produce output
        x = self.linear(self.dropout(x))
        return x
model = CharModel()
model.load_state_dict(best_model)

# randomly generate a prompt
filename = "wonderland.txt"
seq_length = 100
raw_text = open(filename, 'r', encoding='utf-8').read()
raw_text = raw_text.lower()
start = np.random.randint(0, len(raw_text)-seq_length)
prompt = raw_text[start:start+seq_length]
pattern = [char_to_int[c] for c in prompt]

model.eval()
print('Prompt: "%s"' % prompt)
with torch.no_grad():
    for i in range(1000):
        # format input array of int into PyTorch tensor
        x = np.reshape(pattern, (1, len(pattern), 1)) / float(n_vocab)
        x = torch.tensor(x, dtype=torch.float32)
        # generate logits as output from the model
        prediction = model(x)
        # convert logits into one character
        index = int(prediction.argmax())
        result = int_to_char[index]
        print(result, end="")
        # append the new character into the prompt for the next iteration
        pattern.append(index)
        pattern = pattern[1:]
print()
print("Done.")

运行此示例首先输出所使用的提示,然后输出每个生成的字符。例如,下面是此文本生成器的一次运行结果。提示是:

Prompt: "nother rush at the stick, and tumbled head
over heels in its hurry to get hold of it; then alice, th"

生成的文本是:

e was qot a litule soteet of thet was sh the thiee harden an the courd, and was tuitk a little toaee th thite ththe and said to the suher, and the whrtght the pacbit sese tha woode of the soeee, and the white rabbit ses ani thr gort to the thite rabbit, and then she was aoiinnene th the three baaed of the sueen and saed “ota turpe ”hun mot,”

“i don’t know the ter ano _enend to mere,” said the maccht ar a sore of great roaee. “ie you don’t teink if thet soued to soeed to the boeie the mooer, io you bane thing it wo
tou het bn the crur,
“h whsh you cen not,” said the manch hare.

“wes, it aadi,” said the manch hare.

“weat you tail to merer ae in an a gens if gre” ”he were thing,” said the maccht ar a sore of geeaghen asd tothe to the thieg harden an the could.
“h dan tor toe taie thing,” said the manch hare.

“wes, it aadi,” said the manch hare.

“weat you tail to merer ae in an a gens if gre” ”he were thing,” said the maccht ar a sore of geeaghen asd tothe to the thieg harden an t

让我们记录一些关于生成文本的观察。

  • 它可以发出换行符。原始文本将行宽限制为 80 个字符,而生成模型尝试复制这一模式。

  • 字符被分成类似单词的组,其中一些组是实际的英语单词(例如,“the”,“said”,和“rabbit”),但许多则不是(例如,“thite”,“soteet”,和“tha”)。

  • 有些词序列是有意义的(例如,“i don’t know the”),但许多词序列则没有意义(例如,“he were thing”)。

这个基于字符的模型产生这样的输出非常令人印象深刻。它让你感受到 LSTM 网络的学习能力。然而,结果并不完美。在下一节中,你将通过开发一个更大的 LSTM 网络来提高结果的质量。

使用更大的 LSTM 网络

记住,LSTM 是一种递归神经网络。它将一个序列作为输入,在序列的每一步中,输入与其内部状态混合以产生输出。因此,LSTM 的输出也是一个序列。在上述情况中,来自最后一个时间步的输出用于神经网络的进一步处理,而早期步骤的输出则被丢弃。然而,这并不一定是唯一的情况。你可以将一个 LSTM 层的序列输出视为另一个 LSTM 层的输入。这样,你就可以构建一个更大的网络。

类似于卷积神经网络,堆叠 LSTM 网络应该让早期的 LSTM 层学习低层次特征,而后期的 LSTM 层学习高层次特征。虽然这种方法可能并不总是有效,但你可以尝试一下,看看模型是否能产生更好的结果。

在 PyTorch 中,制作堆叠 LSTM 层很简单。让我们将上述模型修改为以下形式:

class CharModel(nn.Module):
    def __init__(self):
        super().__init__()
        self.lstm = nn.LSTM(input_size=1, hidden_size=256, num_layers=2, batch_first=True, dropout=0.2)
        self.dropout = nn.Dropout(0.2)
        self.linear = nn.Linear(256, n_vocab)
    def forward(self, x):
        x, _ = self.lstm(x)
        # take only the last output
        x = x[:, -1, :]
        # produce output
        x = self.linear(self.dropout(x))
        return x

唯一的变化是在 nn.LSTM() 的参数上:你将 num_layers=2 设为 2,而不是 1,以添加另一个 LSTM 层。但在这两个 LSTM 层之间,你还通过参数 dropout=0.2 添加了一个 dropout 层。用这个模型替换之前的模型就是你需要做的所有更改。重新运行训练,你应该会看到以下结果:

...
Epoch 34: Cross-entropy: 203763.0312
Epoch 35: Cross-entropy: 204002.5938
Epoch 36: Cross-entropy: 210636.5625
Epoch 37: Cross-entropy: 199619.6875
Epoch 38: Cross-entropy: 199240.2969
Epoch 39: Cross-entropy: 196966.1250

你应该会看到此处的交叉熵低于前一节。这意味着这个模型的表现更好。实际上,使用这个模型,你可以看到生成的文本看起来更有意义:

Prompt: "ll
say that ‘i see what i eat’ is the same thing as ‘i eat what i see’!”

“you might just as well sa"
y it to sea,” she katter said to the jury. and the thoee hardeners vhine she was seady to alice the was a long tay of the sooe of the court, and she was seady to and taid to the coor and the court.
“well you see what you see, the mookee of the soog of the season of the shase of the court!”

“i don’t know the rame thing is it?” said the caterpillar.

“the cormous was it makes he it was it taie the reason of the shall bbout it, you know.”

“i don’t know the rame thing i can’t gelp the sea,” the hatter went on, “i don’t know the peally was in the shall sereat it would be a teally.
the mookee of the court ”

“i don’t know the rame thing is it?” said the caterpillar.

“the cormous was it makes he it was it taie the reason of the shall bbout it, you know.”

“i don’t know the rame thing i can’t gelp the sea,” the hatter went on, “i don’t know the peally was in the shall sereat it would be a teally.
the mookee of the court ”

“i don’t know the rame thing is it?” said the caterpillar.

“the
Done.

不仅单词拼写正确,文本也更符合英语。由于交叉熵损失在你训练模型时仍在下降,你可以认为模型尚未收敛。如果你增加训练轮次,可以期待使模型变得更好。

为了完整性,下面是使用这个新模型的完整代码,包括训练和文本生成。

import numpy as np
import torch
import torch.nn as nn
import torch.optim as optim
import torch.utils.data as data

# load ascii text and covert to lowercase
filename = "wonderland.txt"
raw_text = open(filename, 'r', encoding='utf-8').read()
raw_text = raw_text.lower()

# create mapping of unique chars to integers
chars = sorted(list(set(raw_text)))
char_to_int = dict((c, i) for i, c in enumerate(chars))

# summarize the loaded data
n_chars = len(raw_text)
n_vocab = len(chars)
print("Total Characters: ", n_chars)
print("Total Vocab: ", n_vocab)

# prepare the dataset of input to output pairs encoded as integers
seq_length = 100
dataX = []
dataY = []
for i in range(0, n_chars - seq_length, 1):
    seq_in = raw_text[i:i + seq_length]
    seq_out = raw_text[i + seq_length]
    dataX.append([char_to_int[char] for char in seq_in])
    dataY.append(char_to_int[seq_out])
n_patterns = len(dataX)
print("Total Patterns: ", n_patterns)

# reshape X to be [samples, time steps, features]
X = torch.tensor(dataX, dtype=torch.float32).reshape(n_patterns, seq_length, 1)
X = X / float(n_vocab)
y = torch.tensor(dataY)

class CharModel(nn.Module):
    def __init__(self):
        super().__init__()
        self.lstm = nn.LSTM(input_size=1, hidden_size=256, num_layers=2, batch_first=True, dropout=0.2)
        self.dropout = nn.Dropout(0.2)
        self.linear = nn.Linear(256, n_vocab)
    def forward(self, x):
        x, _ = self.lstm(x)
        # take only the last output
        x = x[:, -1, :]
        # produce output
        x = self.linear(self.dropout(x))
        return x

n_epochs = 40
batch_size = 128
model = CharModel()

optimizer = optim.Adam(model.parameters())
loss_fn = nn.CrossEntropyLoss(reduction="sum")
loader = data.DataLoader(data.TensorDataset(X, y), shuffle=True, batch_size=batch_size)

best_model = None
best_loss = np.inf
for epoch in range(n_epochs):
    model.train()
    for X_batch, y_batch in loader:
        y_pred = model(X_batch)
        loss = loss_fn(y_pred, y_batch)
        optimizer.zero_grad()
        loss.backward()
        optimizer.step()
    # Validation
    model.eval()
    loss = 0
    with torch.no_grad():
        for X_batch, y_batch in loader:
            y_pred = model(X_batch)
            loss += loss_fn(y_pred, y_batch)
        if loss < best_loss:
            best_loss = loss
            best_model = model.state_dict()
        print("Epoch %d: Cross-entropy: %.4f" % (epoch, loss))

torch.save([best_model, char_to_int], "single-char.pth")

# Generation using the trained model
best_model, char_to_int = torch.load("single-char.pth")
n_vocab = len(char_to_int)
int_to_char = dict((i, c) for c, i in char_to_int.items())
model.load_state_dict(best_model)

# randomly generate a prompt
filename = "wonderland.txt"
seq_length = 100
raw_text = open(filename, 'r', encoding='utf-8').read()
raw_text = raw_text.lower()
start = np.random.randint(0, len(raw_text)-seq_length)
prompt = raw_text[start:start+seq_length]
pattern = [char_to_int[c] for c in prompt]

model.eval()
print('Prompt: "%s"' % prompt)
with torch.no_grad():
    for i in range(1000):
        # format input array of int into PyTorch tensor
        x = np.reshape(pattern, (1, len(pattern), 1)) / float(n_vocab)
        x = torch.tensor(x, dtype=torch.float32)
        # generate logits as output from the model
        prediction = model(x)
        # convert logits into one character
        index = int(prediction.argmax())
        result = int_to_char[index]
        print(result, end="")
        # append the new character into the prompt for the next iteration
        pattern.append(index)
        pattern = pattern[1:]
print()
print("Done.")

使用 GPU 加速训练

本文中的程序运行可能会非常慢。即使你有 GPU,也不会立刻看到改善。这是因为 PyTorch 的设计,它可能不会自动使用你的 GPU。然而,如果你有支持 CUDA 的 GPU,通过将重计算任务从 CPU 移开,你可以大大提高性能。

PyTorch 模型是一个张量计算程序。张量可以存储在 GPU 或 CPU 中。只要所有操作符在同一个设备上,就可以执行操作。在这个特定的示例中,模型权重(即 LSTM 层和全连接层的权重)可以移动到 GPU 上。这样,输入也应该在执行前移动到 GPU 上,输出也将存储在 GPU 中,除非你将其移动回去。

在 PyTorch 中,你可以使用以下函数检查是否有支持 CUDA 的 GPU:

torch.cuda.is_available()

它返回一个布尔值,指示你是否可以使用 GPU,这取决于你拥有的硬件模型、你的操作系统是否安装了适当的库以及你的 PyTorch 是否编译了相应的 GPU 支持。如果一切正常,你可以创建一个设备并将模型分配给它:

device = torch.device("cuda:0")
model.to(device)

如果你的模型在 CUDA 设备上运行,但输入张量不在,你会看到 PyTorch 抱怨并无法继续。要将张量移动到 CUDA 设备,你应该运行如下代码:

y_pred = model(X_batch.to(device))

.to(device) 部分将会起到魔法作用。但请记住,上述程序产生的y_pred也将位于 CUDA 设备上。因此,在运行损失函数时,你也应该做同样的操作。修改上述程序,使其能够在 GPU 上运行,将变成以下形式:

import numpy as np
import torch
import torch.nn as nn
import torch.optim as optim
import torch.utils.data as data

# load ascii text and covert to lowercase
filename = "wonderland.txt"
raw_text = open(filename, 'r', encoding='utf-8').read()
raw_text = raw_text.lower()

# create mapping of unique chars to integers
chars = sorted(list(set(raw_text)))
char_to_int = dict((c, i) for i, c in enumerate(chars))

# summarize the loaded data
n_chars = len(raw_text)
n_vocab = len(chars)
print("Total Characters: ", n_chars)
print("Total Vocab: ", n_vocab)

# prepare the dataset of input to output pairs encoded as integers
seq_length = 100
dataX = []
dataY = []
for i in range(0, n_chars - seq_length, 1):
    seq_in = raw_text[i:i + seq_length]
    seq_out = raw_text[i + seq_length]
    dataX.append([char_to_int[char] for char in seq_in])
    dataY.append(char_to_int[seq_out])
n_patterns = len(dataX)
print("Total Patterns: ", n_patterns)

# reshape X to be [samples, time steps, features]
X = torch.tensor(dataX, dtype=torch.float32).reshape(n_patterns, seq_length, 1)
X = X / float(n_vocab)
y = torch.tensor(dataY)

class CharModel(nn.Module):
    def __init__(self):
        super().__init__()
        self.lstm = nn.LSTM(input_size=1, hidden_size=256, num_layers=2, batch_first=True, dropout=0.2)
        self.dropout = nn.Dropout(0.2)
        self.linear = nn.Linear(256, n_vocab)
    def forward(self, x):
        x, _ = self.lstm(x)
        # take only the last output
        x = x[:, -1, :]
        # produce output
        x = self.linear(self.dropout(x))
        return x

n_epochs = 40
batch_size = 128
model = CharModel()
device = torch.device("cuda:0" if torch.cuda.is_available() else "cpu")
model.to(device)

optimizer = optim.Adam(model.parameters())
loss_fn = nn.CrossEntropyLoss(reduction="sum")
loader = data.DataLoader(data.TensorDataset(X, y), shuffle=True, batch_size=batch_size)

best_model = None
best_loss = np.inf
for epoch in range(n_epochs):
    model.train()
    for X_batch, y_batch in loader:
        y_pred = model(X_batch.to(device))
        loss = loss_fn(y_pred, y_batch.to(device))
        optimizer.zero_grad()
        loss.backward()
        optimizer.step()
    # Validation
    model.eval()
    loss = 0
    with torch.no_grad():
        for X_batch, y_batch in loader:
            y_pred = model(X_batch.to(device))
            loss += loss_fn(y_pred, y_batch.to(device))
        if loss < best_loss:
            best_loss = loss
            best_model = model.state_dict()
        print("Epoch %d: Cross-entropy: %.4f" % (epoch, loss))

torch.save([best_model, char_to_int], "single-char.pth")

# Generation using the trained model
best_model, char_to_int = torch.load("single-char.pth")
n_vocab = len(char_to_int)
int_to_char = dict((i, c) for c, i in char_to_int.items())
model.load_state_dict(best_model)

# randomly generate a prompt
filename = "wonderland.txt"
seq_length = 100
raw_text = open(filename, 'r', encoding='utf-8').read()
raw_text = raw_text.lower()
start = np.random.randint(0, len(raw_text)-seq_length)
prompt = raw_text[start:start+seq_length]
pattern = [char_to_int[c] for c in prompt]

model.eval()
print('Prompt: "%s"' % prompt)
with torch.no_grad():
    for i in range(1000):
        # format input array of int into PyTorch tensor
        x = np.reshape(pattern, (1, len(pattern), 1)) / float(n_vocab)
        x = torch.tensor(x, dtype=torch.float32)
        # generate logits as output from the model
        prediction = model(x.to(device))
        # convert logits into one character
        index = int(prediction.argmax())
        result = int_to_char[index]
        print(result, end="")
        # append the new character into the prompt for the next iteration
        pattern.append(index)
        pattern = pattern[1:]
print()
print("Done.")

与前一节中的代码进行比较,你应该能看到它们基本相同。除了 CUDA 设备检测行:

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

这将是你的 GPU,如果没有 CUDA 设备,则会回退到 CPU。随后,在几个关键位置添加了.to(device)以将计算移动到 GPU。

进一步阅读

这种字符文本模型是使用递归神经网络生成文本的流行方式。如果你有兴趣深入了解,下面还有更多资源和教程。

文章
论文
  • Ilya Sutskever、James Martens 和 Geoffrey Hinton。"使用递归神经网络生成文本"。在:第 28 届国际机器学习会议论文集。2011 年,美国华盛顿州贝尔维尤。
API

总结

在本文中,您了解了如何在 PyTorch 中开发 LSTM 递归神经网络进行文本生成。完成本文后,您将了解到:

  • 如何免费获取经典书籍文本作为机器学习模型的数据集

  • 如何训练 LSTM 网络处理文本序列

  • 如何使用 LSTM 网络生成文本序列如何使用 CUDA 设备优化 PyTorch 中的深度学习训练

在 PyTorch 中训练线性回归模型

原文:machinelearningmastery.com/training-a-linear-regression-model-in-pytorch/

线性回归是一种简单而强大的技术,用于基于其他变量预测变量的值。它通常用于建模两个或更多连续变量之间的关系,例如收入与年龄之间的关系,或体重与身高之间的关系。同样,线性回归也可以用于预测诸如价格或需求量等连续结果,这些结果是基于已知会影响这些结果的其他变量。

为了训练线性回归模型,我们需要定义一个成本函数和一个优化器。成本函数用于衡量模型与数据的拟合程度,而优化器决定了如何移动以改善这种拟合。

在上一个教程中,你学习了如何仅通过线性回归前向传播进行简单预测,在这里你将训练一个线性回归模型,并使用 PyTorch 更新其学习参数。特别地,你将学习:

  • 如何从头开始在 PyTorch 中构建一个简单的线性回归模型。

  • 如何在数据集上应用简单线性回归模型。

  • 如何在单个可学习参数上训练简单线性回归模型。

  • 如何在两个可学习参数上训练简单线性回归模型。

启动你的项目,参考我的书籍 Deep Learning with PyTorch。它提供了自学教程可运行的代码

那么,让我们开始吧!

在 PyTorch 中训练线性回归模型。

图片由 Ryan Tasto 提供。一些权利保留。

概述

本教程分为四部分;它们是

  • 准备数据

  • 构建模型和损失函数

  • 为单个参数训练模型

  • 为两个参数训练模型

准备数据

让我们导入一些将在本教程中使用的库,并制作一些实验数据。

import torch
import numpy as np
import matplotlib.pyplot as plt

我们将使用合成数据来训练线性回归模型。我们将初始化一个变量 X,其值从 5-555,并创建一个斜率为 5-5 的线性函数。请注意,这个函数稍后将由我们训练的模型进行估计。

...
# Creating a function f(X) with a slope of -5
X = torch.arange(-5, 5, 0.1).view(-1, 1)
func = -5 * X

此外,我们将使用 matplotlib 查看数据在折线图中的表现。

...
# Plot the line in red with grids
plt.plot(X.numpy(), func.numpy(), 'r', label='func')
plt.xlabel('x')
plt.ylabel('y')
plt.legend()
plt.grid('True', color='y')
plt.show()

线性函数的图示

由于我们需要模拟刚刚创建的真实数据,我们将向其中添加一些高斯噪声,以生成与 XX 同样大小的噪声数据,标准差保持在 0.4。这将通过使用 torch.randn(X.size()) 来完成。

...
# Adding Gaussian noise to the function f(X) and saving it in Y
Y = func + 0.4 * torch.randn(X.size())

现在,让我们使用下面的代码行来可视化这些数据点。

# Plot and visualizing the data points in blue
plt.plot(X.numpy(), Y.numpy(), 'b+', label='Y')
plt.plot(X.numpy(), func.numpy(), 'r', label='func')
plt.xlabel('x')
plt.ylabel('y')
plt.legend()
plt.grid('True', color='y')
plt.show()

数据点和线性函数

综合起来,以下是完整的代码。

import torch
import numpy as np
import matplotlib.pyplot as plt

# Creating a function f(X) with a slope of -5
X = torch.arange(-5, 5, 0.1).view(-1, 1)
func = -5 * X

# Adding Gaussian noise to the function f(X) and saving it in Y
Y = func + 0.4 * torch.randn(X.size())

# Plot and visualizing the data points in blue
plt.plot(X.numpy(), Y.numpy(), 'b+', label='Y')
plt.plot(X.numpy(), func.numpy(), 'r', label='func')
plt.xlabel('x')
plt.ylabel('y')
plt.legend()
plt.grid('True', color='y')
plt.show()

构建模型和损失函数

我们创建了用于输入模型的数据,接下来我们将基于一个简单的线性回归方程构建一个前向函数。注意,我们将在这里构建一个只训练单一参数(ww)的模型。稍后,在教程的下一部分,我们将添加偏差并对两个参数(wwbb)进行训练。模型前向传递的函数定义如下:

# defining the function for forward pass for prediction
def forward(x):
    return w * x

在训练步骤中,我们需要一个标准来衡量原始数据点和预测数据点之间的损失。这些信息对模型的梯度下降优化操作至关重要,并且在每次迭代后更新以计算梯度并最小化损失。通常,线性回归用于连续数据,其中均方误差(MSE)有效地计算模型损失。因此,MSE 度量是我们在这里使用的标准函数。

# evaluating data points with Mean Square Error.
def criterion(y_pred, y):
    return torch.mean((y_pred - y) ** 2)

想要开始使用 PyTorch 进行深度学习吗?

现在就接受我的免费电子邮件速成课程吧(附样例代码)。

点击注册并获取免费的 PDF 电子书版本课程。

对单一参数训练模型

经过这些准备后,我们准备开始模型训练。首先,参数ww需要随机初始化,例如设置为10-10

w = torch.tensor(-10.0, requires_grad=True)

接下来,我们将定义学习率或步长,一个空列表以存储每次迭代后的损失,以及我们希望模型训练的迭代次数。步长设置为 0.1,我们对模型进行 20 次迭代每个纪元。

step_size = 0.1
loss_list = []
iter = 20

当执行以下代码时,forward()函数接受输入并生成预测。criterian()函数计算损失并将其存储在loss变量中。根据模型损失,backward()方法计算梯度,w.data存储更新后的参数。

for i in range (iter):
    # making predictions with forward pass
    Y_pred = forward(X)
    # calculating the loss between original and predicted data points
    loss = criterion(Y_pred, Y)
    # storing the calculated loss in a list
    loss_list.append(loss.item())
    # backward pass for computing the gradients of the loss w.r.t to learnable parameters
    loss.backward()
    # updateing the parameters after each iteration
    w.data = w.data - step_size * w.grad.data
    # zeroing gradients after each iteration
    w.grad.data.zero_()
    # priting the values for understanding
    print('{},\t{},\t{}'.format(i, loss.item(), w.item()))

模型训练的输出如下所示。如你所见,模型损失在每次迭代后减少,训练参数(在本例中是ww)得到更新。

0,	207.40255737304688,	-1.6875505447387695
1,	92.3563003540039,	-7.231954097747803
2,	41.173553466796875,	-3.5338361263275146
3,	18.402894973754883,	-6.000481128692627
4,	8.272472381591797,	-4.355228900909424
5,	3.7655599117279053,	-5.452612400054932
6,	1.7604843378067017,	-4.7206573486328125
7,	0.8684477210044861,	-5.208871364593506
8,	0.471589595079422,	-4.883232593536377
9,	0.2950323224067688,	-5.100433826446533
10,	0.21648380160331726,	-4.955560684204102
11,	0.1815381944179535,	-5.052190780639648
12,	0.16599132120609283,	-4.987738609313965
13,	0.15907476842403412,	-5.030728340148926
14,	0.15599775314331055,	-5.002054214477539
15,	0.15462875366210938,	-5.021179676055908
16,	0.15401971340179443,	-5.008423328399658
17,	0.15374873578548431,	-5.016931533813477
18,	0.15362821519374847,	-5.011256694793701
19,	0.15357455611228943,	-5.015041828155518

我们还可以通过图示来查看损失如何减少。

# Plotting the loss after each iteration
plt.plot(loss_list, 'r')
plt.tight_layout()
plt.grid('True', color='y')
plt.xlabel("Epochs/Iterations")
plt.ylabel("Loss")
plt.show()

训练损失与迭代次数的关系

综合起来,以下是完整的代码:

import torch
import numpy as np
import matplotlib.pyplot as plt

X = torch.arange(-5, 5, 0.1).view(-1, 1)
func = -5 * X
Y = func + 0.4 * torch.randn(X.size())

# defining the function for forward pass for prediction
def forward(x):
    return w * x

# evaluating data points with Mean Square Error
def criterion(y_pred, y):
    return torch.mean((y_pred - y) ** 2)

w = torch.tensor(-10.0, requires_grad=True)

step_size = 0.1
loss_list = []
iter = 20

for i in range (iter):
    # making predictions with forward pass
    Y_pred = forward(X)
    # calculating the loss between original and predicted data points
    loss = criterion(Y_pred, Y)
    # storing the calculated loss in a list
    loss_list.append(loss.item())
    # backward pass for computing the gradients of the loss w.r.t to learnable parameters
    loss.backward()
    # updateing the parameters after each iteration
    w.data = w.data - step_size * w.grad.data
    # zeroing gradients after each iteration
    w.grad.data.zero_()
    # priting the values for understanding
    print('{},\t{},\t{}'.format(i, loss.item(), w.item()))

# Plotting the loss after each iteration
plt.plot(loss_list, 'r')
plt.tight_layout()
plt.grid('True', color='y')
plt.xlabel("Epochs/Iterations")
plt.ylabel("Loss")
plt.show()

对两个参数训练模型

我们还需要将偏差bb添加到我们的模型中,并对两个参数进行训练。首先,我们需要将前向函数更改如下。

# defining the function for forward pass for prediction
def forward(x):
    return w * x + b

由于我们有两个参数wwbb,我们需要将两者都初始化为一些随机值,如下所示。

w = torch.tensor(-10.0, requires_grad = True)
b = torch.tensor(-20.0, requires_grad = True)

虽然训练的所有其他代码保持不变,我们只需对两个可学习参数进行一些更改。

将学习率保持在 0.1,训练我们的模型两个参数 20 次迭代/纪元。

step_size = 0.1
loss_list = []
iter = 20

for i in range (iter):    
    # making predictions with forward pass
    Y_pred = forward(X)
    # calculating the loss between original and predicted data points
    loss = criterion(Y_pred, Y)
    # storing the calculated loss in a list
    loss_list.append(loss.item())
    # backward pass for computing the gradients of the loss w.r.t to learnable parameters
    loss.backward()
    # updateing the parameters after each iteration
    w.data = w.data - step_size * w.grad.data
    b.data = b.data - step_size * b.grad.data
    # zeroing gradients after each iteration
    w.grad.data.zero_()
    b.grad.data.zero_()
    # priting the values for understanding
    print('{}, \t{}, \t{}, \t{}'.format(i, loss.item(), w.item(), b.item()))

这是我们得到的输出结果。

0, 	598.0744018554688, 	-1.8875503540039062, 	-16.046640396118164
1, 	344.6290283203125, 	-7.2590203285217285, 	-12.802828788757324
2, 	203.6309051513672, 	-3.6438119411468506, 	-10.261493682861328
3, 	122.82559204101562, 	-6.029742240905762, 	-8.19227409362793
4, 	75.30597686767578, 	-4.4176344871521, 	-6.560757637023926
5, 	46.759193420410156, 	-5.476595401763916, 	-5.2394232749938965
6, 	29.318675994873047, 	-4.757054805755615, 	-4.19294548034668
7, 	18.525297164916992, 	-5.2265238761901855, 	-3.3485677242279053
8, 	11.781207084655762, 	-4.90494441986084, 	-2.677760124206543
9, 	7.537606239318848, 	-5.112729549407959, 	-2.1378984451293945
10, 	4.853880405426025, 	-4.968738555908203, 	-1.7080869674682617
11, 	3.1505300998687744, 	-5.060482025146484, 	-1.3627978563308716
12, 	2.0666630268096924, 	-4.99583625793457, 	-1.0874838829040527
13, 	1.3757448196411133, 	-5.0362019538879395, 	-0.8665863275527954
14, 	0.9347621202468872, 	-5.007069110870361, 	-0.6902718544006348
15, 	0.6530535817146301, 	-5.024737358093262, 	-0.5489290356636047
16, 	0.4729837477207184, 	-5.011539459228516, 	-0.43603143095970154
17, 	0.3578317165374756, 	-5.0192131996154785, 	-0.34558138251304626
18, 	0.28417202830314636, 	-5.013190746307373, 	-0.27329811453819275
19, 	0.23704445362091064, 	-5.01648473739624, 	-0.2154112160205841

同样,我们可以绘制损失历史。

# Plotting the loss after each iteration
plt.plot(loss_list, 'r')
plt.tight_layout()
plt.grid('True', color='y')
plt.xlabel("Epochs/Iterations")
plt.ylabel("Loss")
plt.show()

这是损失的图示效果。

训练具有两个参数的损失历史

将所有内容整合起来,这是完整的代码。

import torch
import numpy as np
import matplotlib.pyplot as plt

X = torch.arange(-5, 5, 0.1).view(-1, 1)
func = -5 * X
Y = func + 0.4 * torch.randn(X.size())

# defining the function for forward pass for prediction
def forward(x):
    return w * x + b

# evaluating data points with Mean Square Error.
def criterion(y_pred, y):
    return torch.mean((y_pred - y) ** 2)

w = torch.tensor(-10.0, requires_grad=True)
b = torch.tensor(-20.0, requires_grad=True)

step_size = 0.1
loss_list = []
iter = 20

for i in range (iter):    
    # making predictions with forward pass
    Y_pred = forward(X)
    # calculating the loss between original and predicted data points
    loss = criterion(Y_pred, Y)
    # storing the calculated loss in a list
    loss_list.append(loss.item())
    # backward pass for computing the gradients of the loss w.r.t to learnable parameters
    loss.backward()
    # updateing the parameters after each iteration
    w.data = w.data - step_size * w.grad.data
    b.data = b.data - step_size * b.grad.data
    # zeroing gradients after each iteration
    w.grad.data.zero_()
    b.grad.data.zero_()
    # priting the values for understanding
    print('{}, \t{}, \t{}, \t{}'.format(i, loss.item(), w.item(), b.item()))

# Plotting the loss after each iteration
plt.plot(loss_list, 'r')
plt.tight_layout()
plt.grid('True', color='y')
plt.xlabel("Epochs/Iterations")
plt.ylabel("Loss")
plt.show()

总结

在本教程中,您学习了如何在 PyTorch 中构建和训练一个简单的线性回归模型。特别是,您学到了:

  • 如何在 PyTorch 中从头开始构建一个简单的线性回归模型。

  • 如何在数据集上应用一个简单的线性回归模型。

  • 如何训练一个具有单个可学习参数的简单线性回归模型。

  • 如何训练一个具有两个可学习参数的简单线性回归模型。