02-快速入门:使用PyTorch进行机器学习和深度学习的基本工作流程(笔记+代码)

2023-11-24 16:47:40 浏览数 (2)

文为[PyTorch工作流基础[1]]的学习笔记,对原文进行了翻译和编辑,本系列课程介绍和目录在《使用PyTorch进行深度学习系列》课程介绍[2]。 文章将最先在我的博客[3]发布,其他平台因为限制不能实时修改。 在微信公众号内无法嵌入超链接,可以点击阅读原文[4]获得更好的阅读体验。

目录

  • 1. 数据(准备和加载)
    • 将数据拆分为训练集和测试集
  • 2. 构建模型
    • 检查 PyTorch 模型的内容
    • 使用 `torch.inference_mode()` 进行预测
  • 3. 训练模型
    • 在 PyTorch 中创建损失函数和优化器
    • 在 PyTorch 中创建优化循环
    • 训练循环
    • 测试循环
  • 4. 使用经过训练的 PyTorch 模型进行预测(推理)
  • 5. 保存和加载 PyTorch 模型
    • 保存 PyTorch 模型的 `state_dict()`
    • 加载已保存的 PyTorch 模型的 `state_dict
  • 6. 合并代码
    • 6.1 数据
    • 6.2 构建 PyTorch 线性模型
    • 6.3 训练
    • 6.4 进行预测
    • 6.5 保存模型
  • 7.拓展资料
  • 8.感谢

在本章,我们将通过训练和使用线性回归模型来介绍标准 PyTorch 工作流程。

PyTorch 工作流程

我们将得到 torchtorch.nnnn 代表神经网络,这个包包含在 PyTorch 中创建神经网络的构建块)和 matplotlib

代码语言:javascript复制
import torch
from torch import nn # nn contains all of PyTorch's building blocks for neural networks
import matplotlib.pyplot as plt

# Check PyTorch version
torch.__version__
>>> '2.0.1'

1. 数据(准备和加载)

机器学习中的“数据”几乎可以是你能想象到的任何东西。数字表(如大型 Excel 电子表格)、任何类型的图像、视频、音频文件(如歌曲或播客)、蛋白质结构、文本等。

machine-learning-a-game-of-two-parts

我们将使用线性回归来创建具有已知参数(可以通过模型学习的东西)的数据,然后使用 PyTorch 来查看是否可以构建模型来使用梯度下降(gradient descent)来估计这些参数。

代码语言:javascript复制
# 创建已知参数
weight = 0.7
bias = 0.3

# 创建数据
start = 0
end = 1
step = 0.02
X = torch.arange(start, end, step).unsqueeze(dim=1)
y = weight * X   bias

X[:10], y[:10]

输出[3]:

代码语言:javascript复制
(tensor([[0.0000],
         [0.0200],
         [0.0400],
         [0.0600],
         [0.0800],
         [0.1000],
         [0.1200],
         [0.1400],
         [0.1600],
         [0.1800]]),
 tensor([[0.3000],
         [0.3140],
         [0.3280],
         [0.3420],
         [0.3560],
         [0.3700],
         [0.3840],
         [0.3980],
         [0.4120],
         [0.4260]]))

现在我们将着手构建一个可以学习 X (特征)和 y (标签)之间关系的模型。

将数据拆分为训练集和测试集

机器学习项目中最重要的步骤之一是创建训练和测试集(以及需要时的验证集)。

通常,数据集可以分为三类:训练集(Training Set)、验证集(Validation Set)和测试集(Test Set)。它们各自具有不同的作用和用途。

  1. 训练集(Training Set),占比约60-80%: 训练集是用来训练深度学习模型的主要数据集。它包含了大量的样本数据,用于模型的参数优化和学习。通过在训练集上反复迭代训练,模型可以逐渐学习到数据的特征、模式和规律,从而提高其性能和准确性。
  2. 验证集(Validation Set),占比约10~20%: 验证集是用于模型的选择和调优的数据集。在训练过程中,我们需要对模型进行调整和参数的选择,以使其在未见过的数据上达到最佳的表现。验证集提供了一个独立的样本集,用于评估模型在未知数据上的性能。通过在验证集上验证模型的准确性和泛化能力,我们可以调整模型的超参数、网络架构或其他相关参数,进而改善模型的表现。
  3. 测试集(Test Set),占比约10~20%:: 测试集是用于评估训练好的模型性能的数据集。它与训练集和验证集是相互独立的,包含了模型之前未见过的样本数据。通过将测试集输入已经训练好的模型,并对其进行预测和分类,我们可以对模型的性能进行客观评估。测试集的结果可以提供对模型在真实世界数据上的表现估计,判断模型是否能够很好地泛化和应用于实际场景。

现在,我们可以手动切分 Xy 张量来创建它们。

代码语言:javascript复制
train_split = int(0.8 * len(X)) # 80% of data used for training set, 20% for testing 
X_train, y_train = X[:train_split], y[:train_split]
X_test, y_test = X[train_split:], y[train_split:]

len(X_train), len(y_train), len(X_test), len(y_test)
>>> (40, 40, 10, 10)

现在我们有 40 个用于训练的样本 ( X_trainy_train ) 和 10 个用于测试的样本 ( X_testy_test )。

我们创建的模型将尝试学习 X_trainy_train 之间的关系,然后我们将评估它在 X_testy_test的表现。

但现在我们的数据只是页面上的数字。让我们创建一个函数来可视化它。

代码语言:javascript复制
def plot_predictions(train_data=X_train, 
                     train_labels=y_train, 
                     test_data=X_test, 
                     test_labels=y_test, 
                     predictions=None):
  """
  Plots training data, test data and compares predictions.
  """
  plt.figure(figsize=(10, 7))

  # Plot training data in blue
  plt.scatter(train_data, train_labels, c="b", s=4, label="Training data")
  
  # Plot test data in green
  plt.scatter(test_data, test_labels, c="g", s=4, label="Testing data")

  if predictions is not None:
    # Plot the predictions in red (predictions were made on the test data)
    plt.scatter(test_data, predictions, c="r", s=4, label="Predictions")

  # Show the legend
  plt.legend(prop={"size": 14});

No description has been provided for this image

Note: 在机器学习中的进行可视化是一个好方法。

2. 构建模型

现在我们已经有了一些数据,让我们构建一个模型来使用蓝点来预测绿点。

我们使用 PyTorch 复制标准线性回归模型。如果您不熟悉 Python 类的使用,我建议阅读 Python 3 中的面向对象编程指南[5]

代码语言:javascript复制
# 创建一个线性回归模型类
class LinearRegressionModel(nn.Module):  # <- 继承PyTorch中nn.Module(神经网络)类
    def __init__(self):
        super().__init__()
        self.weights = nn.Parameter(torch.randn(1,  # <- 从随机权重开始(这将随着模型学习而调整)
                                                dtype=torch.float),  # <- PyTorch默认使用float32类型
                                                requires_grad=True)  # <- 是否可以使用梯度下降来更新此值?

        self.bias = nn.Parameter(torch.randn(1,  # <- 从随机偏差开始(这将随着模型学习而调整)
                                            dtype=torch.float),  # <- PyTorch默认使用float32类型
                                            requires_grad=True)  # <- 是否可以使用梯度下降来更新此值?

    # forward方法定义模型的计算过程
    def forward(self, x: torch.Tensor) -> torch.Tensor:  # <- "x" 是输入数据(例如训练/测试特征)
        return self.weights * x   self.bias  # <- 这是线性回归公式(y = mx   b)

我们来拆解上述代码:

PyTorch 有四个基本模块,您可以使用它来创建您可以想象的几乎任何类型的神经网络。

它们是 `torch.nn`[6], `torch.optim`[7], `torch.utils.data.Dataset`[8]`torch.utils.data.DataLoader`[9].。现在,我们将重点关注前两个,稍后再讨论另外两个。

PyTorch模块

它有什么用?

torch.nn

包含计算图的所有构建块(本质上是以特定方式执行的一系列计算)。

torch.nn.Parameter

存储可与 nn.Module 一起使用的张量。如果自动计算 requires_grad=True 梯度(用于通过梯度下降更新模型参数),这通常被称为“自动梯度”。

torch.nn.Module

所有神经网络模块的基类,神经网络的所有构建块都是子类。如果你在PyTorch中构建神经网络,你的模型应该子类化 nn.Module 。需要实现 forward() 方法。

torch.optim

包含各种优化算法(这些算法告诉存储在 nn.Parameter 中的模型参数如何最好地改变以改善梯度下降,从而减少损失)。

def forward()

所有的 nn.Module 子类都需要一个 forward() 方法,这定义了将对传递给特定 nn.Module 的数据进行的计算(例如:上述线性回归公式)。

a pytorch linear model with annotations

资源:在 PyTorch Cheat Sheet[10] 中查看更多这些基本模块及其用例。

检查 PyTorch 模型的内容

代码语言:javascript复制
torch.manual_seed(42)

# 创建一个模型的实例化对象
model_0 = LinearRegressionModel()

# 检查Parameter(s) 
list(model_0.parameters())

>>> 
[Parameter containing:
 tensor([0.3367], requires_grad=True),
 Parameter containing:
 tensor([0.1288], requires_grad=True)]

我们还可以使用 `.state_dict()`[11] 获取模型的状态(模型包含的内容)。

代码语言:javascript复制
# List named parameters 
model_0.state_dict()
>>> OrderedDict([('weights', tensor([0.3367])), ('bias', tensor([0.1288]))])

请注意 model_0.state_dict() 中的 weightsbias 的值是随机分配的初始值。

本质上,我们希望从随机参数开始,让模型将它们更新为最适合我们数据的参数(我们在创建直线数据时设置的硬编码 weightbias 值) 。

因为我们的模型从随机值开始,所以现在它的预测能力很差。

使用 torch.inference_mode() 进行预测

为了检查这一点,我们可以将测试数据 X_test 传递给它,看看它预测 y_test 的准确程度。当我们将数据传递给模型时,它将通过模型的 forward() 方法并使用我们定义的计算生成结果。

In [10]:

代码语言:javascript复制
# Make predictions with model
with torch.inference_mode(): 
    y_preds = model_0(X_test)

# Note: in older PyTorch code you might also see torch.no_grad()
# with torch.no_grad():
#   y_preds = model_0(X_test)

您可能注意到我们使用 `torch.inference_mode()`[12] 作为上下文管理器来进行预测。

torch.inference_mode() 关闭了很多东西(例如梯度跟踪,这对于训练是必需的,但对于推理不是必需的)以使前向传递(数据通过 forward() 方法)更快。

注意:在较旧的 PyTorch 代码中,您可能还会看到 torch.no_grad() 用于推理。虽然 torch.inference_mode()torch.no_grad() 执行类似的操作,但 torch.inference_mode() 更新、可能更快并且更受欢迎。

我们已经做了一些预测,让我们看看它们是什么样子的。

In [11]::

代码语言:javascript复制
# Check the predictions
print(f"Number of testing samples: {len(X_test)}") 
print(f"Number of predictions made: {len(y_preds)}")
print(f"Predicted values:n{y_preds}")

Out[11]:

代码语言:javascript复制
Number of testing samples: 10
Number of predictions made: 10
Predicted values:
tensor([[0.3982],
        [0.4049],
        [0.4116],
        [0.4184],
        [0.4251],
        [0.4318],
        [0.4386],
        [0.4453],
        [0.4520],
        [0.4588]])

请注意每个测试样本都有一个预测值。

对于我们的直线,一个 X 值映射到一个 y 值。

然而,机器学习模型非常灵活。您可以将 100 个 X 值映射到一个、两个、三个或 10 个 y 值。例如使用100的特征 X 去判断两到三个类别 y

我们的预测仍然是页面上的数字,让我们使用上面创建的 plot_predictions() 函数将它们可视化。

In [12]:

代码语言:javascript复制
plot_predictions(predictions=y_preds)

plot_predictions

Out[13]:

代码语言:javascript复制
tensor([[0.4618],
        [0.4691],
        [0.4764],
        [0.4836],
        [0.4909],
        [0.4982],
        [0.5054],
        [0.5127],
        [0.5200],
        [0.5272]])

因为的模型只是使用随机参数值来进行预测,这些预测的红点看起来完全不准,同时我们的模型也没有经过反向传播等多次训练,所以数据会偏离的非常厉害。

3. 训练模型

现在我们的模型正在使用随机参数进行计算进行预测,它基本上是猜测的(随机)。 为了解决这个问题,我们可以更新其内部参数(我也将参数称为模式),即我们使用 nn.Parameter()torch.randn() 随机设置的 weightsbias 值, 更好地代表数据。

我们可以对此进行硬编码(因为我们知道默认值 weight=0.7bias=0.3 ),但是就没意义了。很多时候您不知道模型的理想参数是什么。相反,编写代码来查看模型是否可以尝试自行解决这些问题要有趣得多。

在 PyTorch 中创建损失函数和优化器

为了让我们的模型能够自行更新其参数,我们需要在代码中添加更多内容。创建一个损失函数loss function,也是一个优化器optimizer

功能

作用

在PyTorch如何使用?

常用方法

损失函数

衡量模型预测的错误程度(例如 y_preds )与真值标签(例如, y_test )。越低越好。

PyTorch在 torch.nn[13] 中有很多内置的损失函数。

平均绝对误差(MAE)用于回归问题( torch.nn.L1Loss()[14] )。二进制交叉熵用于二进制分类问题( torch.nn.BCELoss()[15] )。

优化器

告诉模型如何更新其内部参数以最大限度地降低损失。

您可以在 torch.optim[16] 中找到各种优化函数实现。

随机梯度下降( torch.optim.SGD()[17] )。Adam优化器( torch.optim.Adam() )。

有关梯度下降法可以观看【【官方双语】深度学习之梯度下降法 Part 2 】[18]了解更多信息:

对于我们的问题,由于我们要预测一个数字,因此我们使用 PyTorch 中的 MAE(位于 torch.nn.L1Loss() 下)作为损失函数。

平均绝对误差(MAE,在 PyTorch 中: torch.nn.L1Loss )测量两点(预测和标签)之间的绝对差异,然后取所有示例的平均值。

我们将使用 SGD, torch.optim.SGD(params, lr) ,其中:

  • params 是您要优化的目标模型参数(例如我们之前随机设置的 weightsbias 值)。
  • lr 是您希望优化器更新参数的学习率,每一步优化器应该改变参数的程度由学习率控制。较高的学习率会导致更大的参数更新,可以加快收敛速度,但可能会导致不稳定性增加。较低的学习率会导致较小的参数更新,可能需要更长的时间才能达到收敛。学习率被认为是一个超参数(因为它是由机器学习工程师设置的)。学习率的常见起始值是 0.010.0010.0001 ,但是,这些值也可以随着时间的推移进行调整(这称为学习率调度[19])。

In [14]: :

代码语言:javascript复制
# Create the loss function
loss_fn = nn.L1Loss() # MAE loss is same as L1Loss

# Create the optimizer
optimizer = torch.optim.SGD(params=model_0.parameters(), # parameters of target model to optimize
                            lr=0.01) # 学习率 learning rate 

在 PyTorch 中创建优化循环

现在我们有了损失函数和优化器,现在是创建训练循环(和测试循环)的时候了。

通过多次让机器去学习 featureslabels 之间的关系成为训练循环测试循环则用于评估模型在训练数据上是否准确(模型在训练期间永远不会看到测试数据)。

其中每一个都称为“循环”"loop",因为我们希望我们的模型查看(循环)每个数据集中的每个样本。

训练循环

对于训练循环,我们将构建以下步骤:

pytorch training loop annotated

Step

步骤名称

作用

代码示例

1

向前传播 Forward pass

该模型一次遍历所有训练数据,执行其 forward() 函数计算。

model(x_train)

2

Calculate the loss 计算损失

将模型的输出(预测)与实际情况进行比较,并进行评估,以查看它们的错误程度。

loss = loss_fn(y_pred, y_train)

3

Zero gradients 归零梯度

优化器的梯度设置为零(默认情况下会累积),因此可以针对特定的训练步骤重新计算它们。

optimizer.zero_grad()

4

Perform backpropagation on the loss 对损失执行反向传播

计算每个要更新的模型参数的损失梯度(每个参数带有 requires_grad=True )。这被称为反向传播,因此称为“向后”。

loss.backward()

5

Update the optimizer (gradient descent) 更新优化器(梯度下降)

使用 requires_grad=True 更新关于损失梯度的参数,以改善它们。

optimizer.step()

希望这符合你的需求!

**Note: **关于上述步骤的顺序

  • 上面是一个很好的默认顺序,但您可能会看到略有不同的顺序。一些经验法则:
    1. 在对其执行反向传播 ( loss.backward() ) 之前计算损失 ( loss = ... )
    2. 在步进 ( optimizer.step() ) 之前将梯度为零 ( optimizer.zero_grad() )。
    3. 对损失执行反向传播 ( loss.backward() ) 后,步进优化器 ( optimizer.step() )。

如需帮助了解机器学习的原理比如反向传播,梯度下降等只是,强烈建议观看观看官方双语深度学习之反向传播算法 上/下 Part 3 】[20]

测试循环

至于测试循环(评估我们的模型),典型步骤包括:

Forward pass,Calculate the loss,Calulate evaluation metrics (optional)

PyTorch testing loop

请注意,测试循环不包含执行反向传播 ( loss.backward() ) 或步进优化器 ( optimizer.step() ),这是因为模型中的参数在测试期间没有更改,它们已经已经计算过了。对于测试,我们只对模型前向传递的输出感兴趣。 让我们将上述所有内容放在一起,并训练我们的模型 100 个 epoch(前向传递数据),我们将每 10 个 epoch 对其进行评估。

In [15]:

代码语言:javascript复制
torch.manual_seed(42)

# Set the number of epochs (how many times the model will pass over the training data)
epochs = 100

# Create empty loss lists to track values
train_loss_values = []
test_loss_values = []
epoch_count = []

for epoch in range(epochs):
    ### Training

    # Put model in training mode (this is the default state of a model)
    model_0.train()

    # 1. Forward pass on train data using the forward() method inside 
    y_pred = model_0(X_train)
    # print(y_pred)

    # 2. Calculate the loss (how different are our models predictions to the ground truth)
    loss = loss_fn(y_pred, y_train)

    # 3. Zero grad of the optimizer
    optimizer.zero_grad()

    # 4. Loss backwards
    loss.backward()

    # 5. Progress the optimizer
    optimizer.step()

    ### Testing

    # Put the model in evaluation mode
    model_0.eval()

    with torch.inference_mode():
      # 1. Forward pass on test data
      test_pred = model_0(X_test)

      # 2. Caculate loss on test data
      test_loss = loss_fn(test_pred, y_test.type(torch.float)) # predictions come in torch.float datatype, so comparisons need to be done with tensors of the same type

      # Print out what's happening
      if epoch % 10 == 0:
            epoch_count.append(epoch)
            train_loss_values.append(loss.detach().numpy())
            test_loss_values.append(test_loss.detach().numpy())
            print(f"Epoch: {epoch} | MAE Train Loss: {loss} | MAE Test Loss: {test_loss} ")

Out[15]:

代码语言:javascript复制
Epoch: 0 | MAE Train Loss: 0.31288138031959534 | MAE Test Loss: 0.48106518387794495 
Epoch: 10 | MAE Train Loss: 0.1976713240146637 | MAE Test Loss: 0.3463551998138428 
Epoch: 20 | MAE Train Loss: 0.08908725529909134 | MAE Test Loss: 0.21729660034179688 
Epoch: 30 | MAE Train Loss: 0.053148526698350906 | MAE Test Loss: 0.14464017748832703 
Epoch: 40 | MAE Train Loss: 0.04543796554207802 | MAE Test Loss: 0.11360953003168106 
Epoch: 50 | MAE Train Loss: 0.04167863354086876 | MAE Test Loss: 0.09919948130846024 
Epoch: 60 | MAE Train Loss: 0.03818932920694351 | MAE Test Loss: 0.08886633068323135 
Epoch: 70 | MAE Train Loss: 0.03476089984178543 | MAE Test Loss: 0.0805937647819519 
Epoch: 80 | MAE Train Loss: 0.03132382780313492 | MAE Test Loss: 0.07232122868299484 
Epoch: 90 | MAE Train Loss: 0.02788739837706089 | MAE Test Loss: 0.06473556160926819 

看起来我们的损失随着每个时期的推移而下降,让我们绘制它来找出答案。

In [16]::

代码语言:javascript复制
# Plot the loss curves
plt.plot(epoch_count, train_loss_values, label="Train loss")
plt.plot(epoch_count, test_loss_values, label="Test loss")
plt.title("Training and test loss curves")
plt.ylabel("Loss")
plt.xlabel("Epochs")
plt.legend();

No description has been provided for this image

损失是衡量模型错误程度的标准,因此越低越好。

由于我们的损失函数和优化器,模型的内部参数( weightsbias )随着每次epoch迭代而更新,以更好地反映数据中的基础模式。损失曲线显示损失随着时间的推移而下降。

让我们检查模型的 .state_dict() ,看看我们的模型与我们为权重和偏差设置的原始值有多接近。

In [17]::

代码语言:javascript复制
# 查找模型参数
print("现在模型的内部参数( `weights` 和 `bias` ):")
print(model_0.state_dict())
print("n原始模型的内部参数( `weights` 和 `bias` )")
print(f"weights: {weight}, bias: {bias}")

代码语言:javascript复制
现在模型的内部参数( `weights` 和 `bias` ):
OrderedDict([('weights', tensor([0.5784])), ('bias', tensor([0.3513]))])

原始模型的内部参数( `weights` 和 `bias` )
weights: 0.7, bias: 0.3

我们的模型非常接近计算 weightbias 的精确原始值(如果我们训练它更长时间,它可能会更接近)。

这就是机器学习和深度学习的整体思想,有一些理想值来描述我们的数据,我们可以训练一个模型以编程方式计算它们,而不是手动计算它们。

4. 使用经过训练的 PyTorch 模型进行预测(推理)

使用 PyTorch 模型进行预测(也称为执行推理)时需要记住三件事:

  1. 将模型设置为评估模式 ( model.eval() )。
  2. 使用推理模式上下文管理器 ( with torch.inference_mode(): ... ) 进行预测。
  3. 所有预测都应使用同一设备上的对象进行(例如仅 GPU 上的数据和模型或仅 CPU 上的数据和模型)。

前两项确保 PyTorch 在训练期间,不需要的计算和设置都被关闭。第3个确保您不会遇到跨设备错误。

In [18]: 在[18]中:

代码语言:javascript复制
# 1. 将模型设置为评估模式
model_0.eval()

# 2. 设置为推理模式
with torch.inference_mode():
  # 3. 确保所有的对象在同一设备
  # 以防万一,可以使用to(device)同一设备
  # model_0.to(device)
  # X_test = X_test.to(device)
  y_preds = model_0(X_test)
  y_preds

Out[18]:

代码语言:javascript复制
tensor([[0.8141],
        [0.8256],
        [0.8372],
        [0.8488],
        [0.8603],
        [0.8719],
        [0.8835],
        [0.8950],
        [0.9066],
        [0.9182]])

好的!我们已经使用经过训练的模型进行了一些预测,现在绘制出来看看?

In [19]:

代码语言:javascript复制
plot_predictions(predictions=y_preds)

No description has been provided for this image

看上去不错。

5. 保存和加载 PyTorch 模型

要在 PyTorch 中保存和加载模型,有三种主要方法[21](以下所有内容均取自 PyTorch 保存和加载模型指南):

以下是返回的 Markdown 表格:

PyTorch方法

它有什么用?

torch.save

使用Python的pickle实用程序将序列化对象复制到磁盘。可以使用torch.save保存模型、张量和其他各种Python对象(如字典)。

torch.load

使用pickle的unpickle功能将pickle Python对象文件(如模型、张量或字典)重新打包并加载到内存中。您还可以设置将对象加载到哪个设备(CPU、GPU等)。

torch.nn.Module.load_state_dict

使用已保存的state_dict()对象加载模型的参数字典(model.state_dict())。

注意:正如 Python 的 `pickle` 文档中[22]所述, pickle 模块不安全。这意味着您应该只解封(加载)您信任的数据。这也适用于加载 PyTorch 模型。仅使用您信任的来源保存的 PyTorch 模型。

保存 PyTorch 模型的 state_dict()

保存和加载模型以进行推理(进行预测)的推荐方法[23]是保存和加载模型的 state_dict()

我们通过步骤保存:

  1. 使用 Python 的 pathlib 模块创建一个名为 models 的目录,用于保存模型。os模块也可以。
  2. 创建一个文件路径来保存模型。
  3. 我们将调用 torch.save(obj, f) ,其中 obj 是目标模型的 state_dict()f 是保存模型的文件名。

注意:PyTorch 保存的模型或对象通常以 .pt.pth 结尾,例如 saved_model_01.pth

In [20]:

代码语言:javascript复制
from pathlib import Path

# 1. 创建和设定用来保存 模型 的文件夹 models
MODEL_PATH = Path("models")
MODEL_PATH.mkdir(parents=True, exist_ok=True)

# 2.定义model文件路径
MODEL_NAME = "01_pytorch_workflow_model_0.pth"
MODEL_SAVE_PATH = MODEL_PATH / MODEL_NAME

# 3. 保存模型的 state dict 
print(f"Saving model to: {MODEL_SAVE_PATH}")
torch.save(obj=model_0.state_dict(), # only saves the models learned parameters
           f=MODEL_SAVE_PATH)

加载已保存的 PyTorch 模型的 `state_dict

使用 torch.nn.Module.load_state_dict(torch.load(f)) 加载它,其中 f 是我们保存的模型 state_dict()

为什么在 torch.nn.Module.load_state_dict() 内调用 torch.load()

因为我们只保存了模型的 state_dict() (这是学习参数的字典),而不是整个模型,所以我们首先必须使用 torch.load() 加载 state_dict() ,然后通过将 state_dict() 转换为我们模型的新实例(它是 nn.Module 的子类)。

为什么不保存整个模型?

然而,保存整个模型[24]而不仅仅是 state_dict() 更直观,引用 PyTorch 文档(斜体是我的):

这种方法(保存整个模型)的缺点是序列化数据绑定到特定的类以及保存模型时使用的确切目录结构...... 因此,在其他项目中使用或重构后,您的代码可能会以各种方式损坏。

因此,我们使用灵活的方法仅保存和加载 state_dict() ,它基本上也是模型参数的字典。

让我们通过创建 LinearRegressionModel() 的另一个实例来测试它,它是 torch.nn.Module 的子类,因此将具有内置方法 load_state_dict()

In [22]:

代码语言:javascript复制
# 实例化模型 同时也会重新生成weight和bias
loaded_model_0 = LinearRegressionModel()

# 加载模型
loaded_model_0.load_state_dict(torch.load(f=MODEL_SAVE_PATH))

我们对加载的模型进行了预测,并且对比与之前的预测是否相同。

In [23]: 在[23]中:

代码语言:javascript复制
# 1. 打开评估模式
loaded_model_0.eval()

# 2. 预测
with torch.inference_mode():
    loaded_model_preds = loaded_model_0(X_test) 
    
# 和之前的模型作对比
y_preds == loaded_model_preds

Out[24]:

代码语言:javascript复制
tensor([[True],
        [True],
        [True],
        [True],
        [True],
        [True],
        [True],
        [True],
        [True],
        [True]])

看起来加载的模型预测与之前的模型预测(保存之前进行的预测)相同。

注意: 还有更多保存和加载 PyTorch 模型的方法,但我将把这些留作课外和进一步阅读。有关更多信息,请参阅 PyTorch 保存和加载模型指南[25]

6. 合并代码

导入库和设置device变量。

代码语言:javascript复制
# Import PyTorch and matplotlib
import torch
from torch import nn # nn contains all of PyTorch's building blocks for neural networks
import matplotlib.pyplot as plt

# Check PyTorch version
print(torch.__version__)

# Setup device agnostic code
device = "cuda" if torch.cuda.is_available() else "cpu"
print(f"Using device: {device}")

6.1 数据

(1)加载数据

首先,我们将对一些 weightbias 值进行硬编码。然后我们将创建 0 到 1 之间的数字范围,这些将是我们的 X 值。最后,我们将使用 X 值以及 weightbias 值通过线性回归公式创建 yy = weight * X bias )。

In [27]:

代码语言:javascript复制
# Create weight and bias
weight = 0.7
bias = 0.3

# Create range values
start = 0
end = 1
step = 0.02

# Create X and y (features and labels)
X = torch.arange(start, end, step).unsqueeze(dim=1) # without unsqueeze, errors will happen later on (shapes within linear layers)
y = weight * X   bias 
X[:10], y[:10]

Out[27]:

代码语言:javascript复制
(tensor([[0.0000],
         [0.0200],
         [0.0400],
         [0.0600],
         [0.0800],
         [0.1000],
         [0.1200],
         [0.1400],
         [0.1600],
         [0.1800]]),
 tensor([[0.3000],
         [0.3140],
         [0.3280],
         [0.3420],
         [0.3560],
         [0.3700],
         [0.3840],
         [0.3980],
         [0.4120],
         [0.4260]]))

(2)分割数据集
代码语言:javascript复制
# Split data
train_split = int(0.8 * len(X))
X_train, y_train = X[:train_split], y[:train_split]
X_test, y_test = X[train_split:], y[train_split:]

len(X_train), len(y_train), len(X_test), len(y_test)


(3)可视化
代码语言:javascript复制
# 用我们上文定义过的plot_predictions函数可视化
plot_predictions(X_train, y_train, X_test, y_test)

No description has been provided for this image

6.2 构建 PyTorch 线性模型

我们将创建与之前相同风格的模型,除了这一次,我们将使用 `nn.Linear(in_features, out_features)`[26] 来手动定义模型的权重和偏差参数,而不是使用 nn.Parameter() 手动定义模型的权重和偏差参数为了我们。其中 in_features 是输入数据的维度数, out_features 是您希望将其输出到的维度数。在我们的例子中,这两个都是 1 因为我们的数据每个标签 ( y ) 有 1 输入特征 ( X )。

comparison of nn.Parameter Linear Regression model and nn.Linear Linear Regression model

使用 nn.Parameter 与使用 nn.Linear 创建线性回归模型。还有很多 torch.nn 模块具有预构建计算的示例,包括许多流行且有用的神经网络层。

In [30]: 在[30]中:

代码语言:javascript复制
# 继承 nn.Module 类构建模型
class LinearRegressionModelV2(nn.Module):
    def __init__(self):
        super().__init__()
        # 使用 nn.Linear() 创建mo'xing
        self.linear_layer = nn.Linear(in_features=1, 
                                      out_features=1)
    
    # 定义向前传播的计算方式
    def forward(self, x: torch.Tensor) -> torch.Tensor: 
        return self.linear_layer(x)

# 可选,使用manual_seed设置固定的随机值
torch.manual_seed(42)
model_1 = LinearRegressionModelV2()
model_1, model_1.state_dict()

Out[30]:

代码语言:javascript复制
(LinearRegressionModelV2(
   (linear_layer): Linear(in_features=1, out_features=1, bias=True)
 ),
 OrderedDict([('linear_layer.weight', tensor([[0.7645]])),
              ('linear_layer.bias', tensor([0.8300]))]))

注意 model_1.state_dict() 的输出, nn.Linear() 层为我们创建了一个随机的 weightbias 参数。现在让我们使用 .to(device) 将模型放在传递到可用的 GPU 上。

In [31]: 在[31]中:

代码语言:javascript复制
# Check model device
print(next(model_1.parameters()).device)

# 传递到我们之前定义的device上
model_1.to(device) 
print(next(model_1.parameters()).device)

>>>
device(type='cpu')
device(type='cuda', index=0)

6.3 训练

代码语言:javascript复制
# Create loss function
loss_fn = nn.L1Loss()

# Create optimizer
optimizer = torch.optim.SGD(params=model_1.parameters(), # optimize newly created model's parameters
                            lr=0.01)

代码语言:javascript复制
torch.manual_seed(42)

# Set the number of epochs 
epochs = 1000 

# Put data on the available device
# Without this, error will happen (not all model/data on device)
X_train = X_train.to(device)
X_test = X_test.to(device)
y_train = y_train.to(device)
y_test = y_test.to(device)

for epoch in range(epochs):
    ### Training
    model_1.train() # train mode is on by default after construction

    # 1. Forward pass
    y_pred = model_1(X_train)

    # 2. Calculate loss
    loss = loss_fn(y_pred, y_train)

    # 3. Zero grad optimizer
    optimizer.zero_grad()

    # 4. Loss backward
    loss.backward()

    # 5. Step the optimizer
    optimizer.step()

    ### Testing
    model_1.eval() # put the model in evaluation mode for testing (inference)
    # 1. Forward pass
    with torch.inference_mode():
        test_pred = model_1(X_test)
    
        # 2. Calculate the loss
        test_loss = loss_fn(test_pred, y_test)

    if epoch % 100 == 0:
        print(f"Epoch: {epoch} | Train loss: {loss} | Test loss: {test_loss}")

OUT:

代码语言:javascript复制
Epoch: 0 | Train loss: 0.5551779866218567 | Test loss: 0.5739762187004089
Epoch: 100 | Train loss: 0.006215683650225401 | Test loss: 0.014086711220443249
Epoch: 200 | Train loss: 0.0012645035749301314 | Test loss: 0.013801801018416882
Epoch: 300 | Train loss: 0.0012645035749301314 | Test loss: 0.013801801018416882
Epoch: 400 | Train loss: 0.0012645035749301314 | Test loss: 0.013801801018416882
Epoch: 500 | Train loss: 0.0012645035749301314 | Test loss: 0.013801801018416882
Epoch: 600 | Train loss: 0.0012645035749301314 | Test loss: 0.013801801018416882
Epoch: 700 | Train loss: 0.0012645035749301314 | Test loss: 0.013801801018416882
Epoch: 800 | Train loss: 0.0012645035749301314 | Test loss: 0.013801801018416882
Epoch: 900 | Train loss: 0.0012645035749301314 | Test loss: 0.013801801018416882

注意:由于机器学习的随机性,根据您的模型是在 CPU 还是 GPU 上训练,您可能会得到略有不同的结果(不同的损失和预测值)。即使您在任一设备上使用相同的随机种子,情况也是如此。如果差异很大,您可能需要查找错误,但是,如果差异很小(理想情况下是这样),您可以忽略它。

这个损失看起来相当低。

让我们检查我们的模型已经学习的参数,并将它们与我们硬编码的原始参数进行比较。

In [35]:

代码语言:javascript复制
# 查询模型的参数
from pprint import pprint # pprint = pretty print, see: https://docs.python.org/3/library/pprint.html 
print("现在模型的内部参数( `weights` 和 `bias` ):")
pprint(model_1.state_dict())
print("n原始模型的内部参数( `weights` 和 `bias` )")
print(f"weights: {weight}, bias: {bias}")


OUT:

代码语言:javascript复制
现在模型的内部参数( `weights` 和 `bias` ):
OrderedDict([('linear_layer.weight', tensor([[0.6968]], device='cuda:0')),
             ('linear_layer.bias', tensor([0.3025], device='cuda:0'))])

原始模型的内部参数( `weights` 和 `bias` )
weights: 0.7, bias: 0.3

6.4 进行预测

代码语言:javascript复制
# 1. 将模型设置为评估模式
model_0.eval()

# 2. 设置为推理模式
with torch.inference_mode():
  # 3. 确保所有的对象在同一设备
  # 以防万一,可以使用to(device)同一设备
  # model_0.to(device)
  # X_test = X_test.to(device)
  y_preds = model_0(X_test)
  y_preds

OUT:

代码语言:javascript复制
tensor([[0.8600],
        [0.8739],
        [0.8878],
        [0.9018],
        [0.9157],
        [0.9296],
        [0.9436],
        [0.9575],
        [0.9714],
        [0.9854]], device='cuda:0')

现在让我们绘制模型的预测。

注意:许多数据科学库(例如 pandas、matplotlib 和 NumPy)无法使用存储在 GPU 上的数据。因此,当尝试使用这些库之一中的函数且张量数据未存储在 CPU 上时,您可能会遇到一些问题。要解决此问题,您可以在目标张量上调用 .cpu() 以在 CPU 上返回目标张量的副本。

In [37]::

代码语言:javascript复制
# plot_predictions(predictions=y_preds) # -> won't work... data not on CPU

# Put data on the CPU and plot it
plot_predictions(predictions=y_preds.cpu())

No description has been provided for this image

6.5 保存模型

代码语言:javascript复制
from pathlib import Path

# 1. Create models directory 
MODEL_PATH = Path("models")
MODEL_PATH.mkdir(parents=True, exist_ok=True)

# 2. Create model save path 
MODEL_NAME = "01_pytorch_workflow_model_1.pth"
MODEL_SAVE_PATH = MODEL_PATH / MODEL_NAME

# 3. Save the model state dict 
print(f"Saving model to: {MODEL_SAVE_PATH}")
torch.save(obj=model_1.state_dict(), # only saving the state_dict() only saves the models learned parameters
           f=MODEL_SAVE_PATH)

7.拓展资料:

  1. 强烈推荐:3brown1blue[27]的关于深度学习的三个视频,深入浅出的方式展示了深度学习的神经网络结构在做什么:
    1. 【【官方双语】深度学习之神经网络的结构 Part 1 ver 2.0】 https://www.bilibili.com/video/BV1bx411M7Zx/?share_source=copy_web&vd_source=bbeafbcfe326916409d46b815d8cb3a3
    2. 【【官方双语】深度学习之梯度下降法 Part 2 ver 0.9 beta】 https://www.bilibili.com/video/BV1Ux411j7ri/?share_source=copy_web&vd_source=bbeafbcfe326916409d46b815d8cb3a3
    3. 【【官方双语】深度学习之反向传播算法 上/下 Part 3 ver 0.9 beta】 https://www.bilibili.com/video/BV16x411V7Qg/?share_source=copy_web&vd_source=bbeafbcfe326916409d46b815d8cb3a3
  2. 从数学原理解释深度学习的书籍:《动手学深度学习(第二版)》[28],结合本章可以阅读3.1线性回归[29]

8.感谢

感谢原作者 Daniel Bourke,访问https://www.learnpytorch.io/[30]可以阅读英文原文,点击原作者的Github仓库:https://github.com/mrdbourke/pytorch-deep-learning/[31]可以获得帮助和其他信息。

本文同样遵守遵守 MIT license[32],不受任何限制,包括但不限于权利

使用、复制、修改、合并、发布、分发、再许可和/或出售。但需标明原始作者的许可信息:renhai-lab:https://cdn.renhai-lab.tech/

参考资料

[1]

[PyTorch工作流基础: https://www.learnpytorch.io/01_pytorch_workflow/

[2]

《使用PyTorch进行深度学习系列》课程介绍: https://cdn.renhai-lab.tech/archives/DL-Home

[3]

我的博客: https://cdn.renhai-lab.tech/categories/deep-learning

[4]

阅读原文: https://cdn.renhai-lab.tech/archives/DL-02-pytorch-workflow

[5]

Python 3 中的面向对象编程指南: https://www.runoob.com/python3/python3-class.html

[6]

torch.nn: https://pytorch.org/docs/stable/nn.html

[7]

torch.optim: https://pytorch.org/docs/stable/optim.html

[8]

torch.utils.data.Dataset: https://pytorch.org/docs/stable/data.html#torch.utils.data.Dataset

[9]

torch.utils.data.DataLoader: https://pytorch.org/docs/stable/data.html

[10]

PyTorch Cheat Sheet: https://pytorch.org/tutorials/beginner/ptcheat.html

[11]

.state_dict(): https://pytorch.org/docs/stable/generated/torch.nn.Module.html#torch.nn.Module.state_dict

[12]

torch.inference_mode(): https://pytorch.org/docs/stable/generated/torch.inference_mode.html

[13]

torch.nn: https://pytorch.org/docs/stable/nn.html#loss-functions

[14]

torch.nn.L1Loss(): https://pytorch.org/docs/stable/generated/torch.nn.L1Loss.html

[15]

torch.nn.BCELoss(): https://pytorch.org/docs/stable/generated/torch.nn.BCELoss.html

[16]

torch.optim: https://pytorch.org/docs/stable/optim.html

[17]

torch.optim.SGD(): https://pytorch.org/docs/stable/generated/torch.optim.SGD.html#torch.optim.SGD

[18]

【【官方双语】深度学习之梯度下降法 Part 2 】: https://www.bilibili.com/video/BV1Ux411j7ri/?share_source=copy_web&vd_source=bbeafbcfe326916409d46b815d8cb3a3

[19]

学习率调度: https://pytorch.org/docs/stable/optim.html#how-to-adjust-learning-rate

[20]

官方双语深度学习之反向传播算法 上/下 Part 3 】: https://www.bilibili.com/video/BV16x411V7Qg/?share_source=copy_web&vd_source=bbeafbcfe326916409d46b815d8cb3a3

[21]

三种主要方法: https://pytorch.org/tutorials/beginner/saving_loading_models.html#saving-loading-model-for-inference

[22]

Python 的 pickle 文档中: https://docs.python.org/3/library/pickle.html

[23]

推荐方法: https://pytorch.org/tutorials/beginner/saving_loading_models.html#saving-loading-model-for-inference

[24]

保存整个模型: https://pytorch.org/tutorials/beginner/saving_loading_models.html#save-load-entire-model

[25]

PyTorch 保存和加载模型指南: https://pytorch.org/tutorials/beginner/saving_loading_models.html#saving-and-loading-models

[26]

nn.Linear(in_features, out_features): https://pytorch.org/docs/stable/generated/torch.nn.Linear.html

[27]

3brown1blue: https://space.bilibili.com/88461692

[28]

《动手学深度学习(第二版)》: http://zh.d2l.ai/

[29]

3.1线性回归: http://zh.d2l.ai/chapter_linear-networks/linear-regression.html#id2

[30]

https://www.learnpytorch.io/: https://www.learnpytorch.io/

[31]

https://github.com/mrdbourke/pytorch-deep-learning/: https://github.com/mrdbourke/pytorch-deep-learning/

[32]

MIT license: https://github.com/renhai-lab/pytorch-deep-learning/blob/cb770bbe688f5950421a76c8b3a47aaa00809c8c/LICENSE

[33]

我的博客: https://cdn.renhai-lab.tech/

[34]

我的GITHUB: https://github.com/renhai-lab

[35]

我的GITEE: https://gitee.com/renhai-lab

[36]

我的知乎: https://www.zhihu.com/people/Ing_ideas

0 人点赞