在 PyTorch 中构建回归模型

PyTorch 库用于深度学习。深度学习模型的一些应用是解决回归或分类问题。
在本帖中,您将了解如何使用 PyTorch 为回归问题开发和评估神经网络模型。

完成这篇文章后,您将了解:

  • 如何从 scikit-learn 加载数据并适配 PyTorch 模型
  • 如何使用 PyTorch 为回归问题创建神经网络
  • 如何通过数据预处理技术提高模型性能

通过我的《用PyTorch进行深度学习》一书来启动你的项目。它提供了包含可用代码自学教程


让我们开始吧。

在 PyTorch 中构建回归模型 由 Sam Deng 拍摄。部分权利保留。

数据集描述

在本教程中,您将使用 加州房价数据集

这是一个描述加州各地区房屋中值的数据集。每个数据样本是一个人口普查街区组。目标变量是 1990 年的房屋中值(以 10 万美元为单位),有 8 个输入特征,每个特征描述了房屋的某些信息。它们分别是:

  • MedInc:街区组的收入中位数
  • HouseAge:街区组的房屋年龄中位数
  • AveRooms:户均房间数
  • AveBedrms:户均卧室数
  • Population:街区组人口
  • AveOccup:户均人口
  • Latitude:街区组中心点纬度
  • Longitude:街区组中心点经度

此数据之所以特殊,是因为输入数据的尺度差异很大。例如,房屋的房间数通常很小,而街区组的人口通常很大。此外,大多数特征应该是正数,但经度必须是负数(因为这涉及到加州)。处理如此多样的数据对某些机器学习模型来说是一个挑战。

您可以从 scikit-learn 获取该数据集,而 scikit-learn 又从互联网实时下载。

构建模型和训练

这是一个回归问题。与分类问题不同,输出变量是一个连续值。对于神经网络,通常在输出层使用线性激活(即没有激活),这样输出范围理论上可以是负无穷到正无穷之间的任何值。

同样,对于回归问题,您永远不应该期望模型能够完美地预测值。因此,您应该关心预测值与实际值有多接近。您可以使用均方误差 (MSE) 或平均绝对误差 (MAE) 作为损失度量。但您也可能对均方根误差 (RMSE) 感兴趣,因为它是与输出变量相同单位的度量。

让我们尝试神经网络的传统设计,即金字塔结构。金字塔结构是指每层的神经元数量随着网络向输出的进展而减少。输入特征的数量是固定的,但您在第一层隐藏层设置大量神经元,并在后续层逐渐减少神经元数量。因为这个数据集中只有一个目标,所以最后一层应该只输出一个值。

一种设计如下:

要训练这个网络,您需要定义一个损失函数。MSE 是一个不错的选择。您还需要一个优化器,例如 Adam。

要训练此模型,您可以使用常规的训练循环。为了获得评估分数,让您确信模型有效,您需要将数据分成训练集和测试集。您可能还想通过跟踪测试集 MSE 来避免过拟合。以下是带有训练-测试拆分的训练循环:

在训练循环中,使用 tqdm 设置进度条,并在每次迭代中计算并报告 MSE。您可以通过将上面的 tqdm 参数 disable 设置为 False 来查看 MSE 的变化。

请注意,在训练循环中,每个 epoch 都使用训练集运行前向和后向步骤几次以优化模型权重,并在 epoch 结束时使用测试集评估模型。存储在列表 history 中的是来自测试集的 MSE。它也是评估模型的指标,最佳模型存储在变量 best_weights 中。

运行此代码后,您将获得恢复的最佳模型,并且最佳 MSE 存储在变量 best_mse 中。请注意,均方误差是预测值与实际值之差的平方的平均值。其平方根,即 RMSE,可以视为平均差值,在数值上更有用。

下面,您可以显示 MSE 和 RMSE,并绘制 MSE 的历史记录。它应该随着 epoch 的增加而下降。

该模型产生了

MSE 图看起来像这样。

将所有内容放在一起,完整的代码如下。

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

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

点击注册,同时获得该课程的免费PDF电子书版本。

通过预处理改进模型

在上文中,您看到 RMSE 为 0.68。实际上,通过在训练前对数据进行处理,可以轻松地改进 RMSE。此数据集的问题在于特征的多样性:有些范围很窄,有些范围很宽。有些是小的正数,有些是非常负的数。这确实对大多数机器学习模型不太友好。

一种改进方法是应用标准化器。它是将每个特征转换为其标准分数。换句话说,对于每个特征 $x$,您将其替换为:

$$
z = \frac{x – \bar{x}}{\sigma_x}
$$

其中 $\bar{x}$ 是 $x$ 的均值,$\sigma_x$ 是标准差。这样,每个转换后的特征都围绕 0 中心化,并且在一个较窄的范围内,大约 70% 的样本介于 -1 到 +1 之间。这可以帮助机器学习模型收敛。

您可以应用 scikit-learn 的标准化器。以下是如何修改上述代码的数据预处理部分:

请注意,标准化器是在训练-测试拆分后应用的。上面的 StandardScaler 是在训练集上拟合的,但应用于训练集和测试集。您不能将标准化器应用于所有数据,因为不应该向模型透露测试集中的任何信息。否则,您将引入数据泄露

除此之外,几乎没有什么需要改变:您仍然有 8 个特征(只是值不同)。您仍然使用相同的训练循环。如果您使用缩放后的数据训练模型,您应该会看到 RMSE 有所提高,例如:

虽然 MSE 历史记录呈类似的下降趋势,但 Y 轴显示缩放后确实有所改善:

然而,您需要在最后要小心:当您使用训练好的模型并将其应用于新数据时,您应该在将数据输入模型之前对输入数据应用缩放器。也就是说,推理应如下进行:

以下是完整的代码:

当然,模型仍有改进的空间。一种方法是将目标转换为对数尺度,或者等效地,使用平均绝对百分比误差 (MAPE) 作为损失函数。这是因为目标变量是房屋的价值,并且范围很广。对于相同的误差幅度,这对低价值的房屋来说问题更大。修改上述代码以获得更好的预测是您的练习。

总结

在本帖中,您了解了如何使用 PyTorch 构建回归模型。

您学会了如何逐步处理 PyTorch 中的回归问题,具体而言:

  • 如何加载和准备数据以在 PyTorch 中使用
  • 如何创建神经网络模型并选择回归的损失函数
  • 如何通过应用标准化器来提高模型准确性

开始使用PyTorch进行深度学习!

Deep Learning with PyTorch

学习如何构建深度学习模型

...使用新发布的PyTorch 2.0库

在我的新电子书中探索如何实现
使用 PyTorch进行深度学习

它提供了包含数百个可用代码自学教程,让你从新手变成专家。它将使你掌握:
张量操作训练评估超参数优化等等...

通过动手练习开启你的深度学习之旅


查看内容

在 PyTorch 中构建回归模型 的 13 条回复

  1. George 2023年2月20日 上午1:13 #

    关于模型评估

    第 92 行和第 935 行分别是:
    model.eval()
    with torch.no_grad()
    # 测试 5 个样本的推理
    for i in range(5)

    而验证评估不包含代码“with torch.no_grad():”
    见第 76 行
    model.eval()

    为什么在一个评估部分使用 torch.no.grad() 而在另一个部分不使用?
    PyTorch 讨论网站指出,“with torch.no_grad():” 代码可用于两者。

    • James Carmichael 2023年2月20日 上午10:06 #

      嗨 George…没有特定的原因或限制。您应该使这些部分保持一致,并告知我们您的发现。

  2. George 2023年2月23日凌晨2:12 #

    嗨 James,

    这不是程序的运行方式,也不是评估的调用时机。这不会告诉我太多信息。

    您需要了解的是 PyTorch 文档的内容,而不是依赖于特定模型如何运行。

  3. Arnold 2023年2月25日凌晨3:33 #

    在迭代 epoch 时,有没有办法关闭中间输出?谢谢。

  4. Dimple 2023年5月26日晚上5:32 #

    你好,James,

    我正在使用 PyTorch 为多输入多输出编写回归模型,首先我进行了输入和输出的归一化(转换),但在反归一化阶段,我的代码出现了错误,你能帮我调试一下吗?
    TypeError: ‘int’ object is not callable

  5. Jorge Osorio 2023年6月1日凌晨5:43 #

    非常感谢您的所有教程,它们对我帮助很大,您太棒了。

    • James Carmichael 2023年6月2日凌晨10:04 #

      Jorge,不客气!我们非常感谢您的支持和反馈!

  6. Nick 2023年10月20日下午1:24 #

    嗨 James,
    您能澄清一下:重塑 y_train 和 y_test 张量的目的是什么? ( y_test = torch.tensor(y_test, dtype=torch.float32).reshape(-1, 1) )

  7. Nick 2023年10月27日凌晨2:05 #

    谢谢,詹姆斯。
    我可能应该说清楚我的问题。我搞不清楚为什么要在那里使用 reshape

    y_train = torch.tensor(y_train, dtype=torch.float32).reshape(-1, 1)
    y_test = torch.tensor(y_test , dtype=torch.float32).reshape(-1, 1)

    将 torch.Size([14447]) 转换为 torch.Size([14447, 1]) 的意义何在?这有什么帮助?

留下回复

Machine Learning Mastery 是 Guiding Tech Media 的一部分,Guiding Tech Media 是一家领先的数字媒体出版商,专注于帮助人们了解技术。访问我们的公司网站以了解更多关于我们的使命和团队的信息。