原文:zh.annas-archive.org/md5/92179b2ab6cdecd2d1f691b24d96e09f

译者:飞龙

协议:CC BY-NC-SA 4.0

前言

关于

本节简要介绍了作者、本书的内容覆盖范围、开始所需的技术技能以及完成所有包含的活动和练习所需的硬件和软件要求。

关于本书

机器学习正迅速成为解决数据问题的首选方式,这要归功于大量的数学算法,这些算法可以发现我们看不见的模式。

应用深度学习与 PyTorch 将带领您深入了解深度学习及其算法和应用。本书从帮助您浏览深度学习和 PyTorch 的基础开始。一旦您熟悉了 PyTorch 语法并能够构建单层神经网络,您将逐步学习通过配置和训练卷积神经网络(CNN)来解决更复杂的数据问题。随着章节的推进,您将发现如何通过实现递归神经网络(RNN)来解决自然语言处理问题。

在本书结束时,您将能够应用您在学习过程中积累的技能和信心,使用 PyTorch 构建深度学习解决方案,解决您的业务数据问题。

关于作者

海雅特·萨莱 毕业于商业管理专业后,发现数据分析对理解和解决现实生活问题的重要性。此后,作为一名自学者,她不仅为全球多家公司担任机器学习自由职业者,还创立了一家旨在优化日常流程的人工智能公司。她还撰写了由 Packt Publishing 出版的《机器学习基础》。

目标

  • 检测多种数据问题,可以应用深度学习解决方案

  • 学习 PyTorch 语法并用其构建单层神经网络

  • 构建一个深度神经网络以解决分类问题

  • 开发风格迁移模型

  • 实施数据增强并重新训练您的模型

  • 使用递归神经网络构建文本处理系统

受众

应用深度学习与 PyTorch 适用于希望使用深度学习技术处理数据的数据科学家、数据分析师和开发人员。任何希望探索并实施 PyTorch 高级算法的人都会发现本书有用。具备 Python 的基本知识和机器学习基础是必需的。然而,了解 NumPy 和 pandas 将是有益但不是必要的。

方法

应用深度学习与 PyTorch 采用实际操作的方式,每章节都有一个完整的实例,从数据获取到结果解释全过程演示。考虑到所涉及概念的复杂性,各章节包含多个图形表示以促进学习。

硬件要求

为了最佳学习体验,我们建议使用以下硬件配置:

  • 处理器:Intel Core i3 或同等级别

  • 内存:4 GB RAM

  • 存储空间:35 GB 可用空间

软件需求

您还需要提前安装以下软件:

  • 操作系统:Windows 7 SP1 64 位、Windows 8.1 64 位或 Windows 10 64 位、Ubuntu Linux 或 OS X 的最新版本

  • 浏览器:Google Chrome/Mozilla Firefox 最新版本

  • Notepad++/Sublime Text 作为 IDE(可选,因为您可以使用浏览器中的 Jupyter 笔记本练习所有内容)

  • 已安装 Python 3.4+(最新版本为 Python 3.7)(来自 python.org

  • 需要的 Python 库(Jupyter、Numpy、Pandas、Matplotlib、BeautifulSoup4 等)

约定

文本中的代码词、数据库表名、文件夹名、文件名、文件扩展名、路径名、虚拟 URL、用户输入和 Twitter 句柄显示如下:“我们在这里使用 requires_grad 参数告诉 PyTorch 计算该张量的梯度。”

代码块如下所示:

a = torch.tensor([5.0, 3.0], requires_grad=True)
b = torch.tensor([1.0, 4.0])
ab = ((a + b) ** 2).sum()
ab.backward()

新术语和重要单词显示为粗体。屏幕上看到的单词,例如菜单或对话框中的内容,以如下形式出现在文本中:“要下载将使用的数据集,请访问 archive.ics.uci.edu/ml/datasets/default+of+credit+card+clients,然后点击 .xls 文件。”

安装代码包

将课程的代码包复制到您本地计算机上的一个文件夹,以便在学习本书时轻松访问。确切的位置取决于您操作系统的限制和个人偏好。

在本书的 GitHub 仓库中(github.com/TrainingByPackt/Applied-Deep-Learning-with-PyTorch),您可以找到一个 requirements.txt 文件,其中包含本书不同活动和练习所需的所有库和模块列表及其版本。

其他资源

本书的代码包也托管在 GitHub 上,链接为 github.com/TrainingByPackt/Applied-Deep-Learning-with-PyTorch

我们还有其他代码包,来自我们丰富的书籍和视频目录,可在 github.com/PacktPublishing/ 查看!

第一章:深度学习和 PyTorch 简介

学习目标

在本章结束时,您将能够:

  • 解释深度学习是什么,其重要性以及它如何适用于 AI 和 ML

  • 确定可以使用深度学习解决的数据问题类型

  • 通过理解该库的优缺点,区分 PyTorch 与其他机器学习库

  • 使用 PyTorch 创建单层神经网络

在本章中,我们将探讨深度学习与人工智能以及机器学习的共鸣。通过对 PyTorch 的介绍,我们将探索基本的编程练习,以应用 PyTorch 语法上的知识。

介绍

深度学习是机器学习的一个子集,专注于使用深度神经网络来解决复杂的数据问题。由于软件和硬件的进步允许收集和处理大量数据(我们谈论的是数百万甚至数十亿条记录),因此深度学习如今变得越来越流行,考虑到神经网络目前是唯一能够通过向模型提供更多数据来达到更高准确度水平的算法。

有了这个想法,对于更快的处理时间的需求是不可避免的。PyTorch 诞生于 2017 年,其主要特点在于利用 GPU 的力量来运行使用张量的数据。这使得算法能够以非常高的速度运行,并且同时为其用户提供了灵活性和标准的语法,以获得许多数据问题的最佳结果。

本书专注于使用 PyTorch 揭示神经网络的神秘,以消除隐含在神经网络架构复杂性周围的一些恐惧。

考虑到这一点,本章专注于介绍深度学习和 PyTorch 的主题。在这里,您将学习深度学习是什么,它如何适用于机器学习和人工智能的世界,它在一般条件下的工作原理,以及一些当前最流行的应用。此外,您还将了解 PyTorch 的工作原理,其主要模块和特征,以及对其用户提出的主要优缺点。

理解深度学习

要理解深度学习是什么,以及为什么它如今变得如此流行,首先定义人工智能和机器学习的概念是很重要的,以及深度学习如何融入这个世界。

外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传

图 1.1: 人工智能、机器学习和深度学习的图表

如前所示,人工智能AI)是一个涵盖机器学习和深度学习的通用类别。它指的是机器展示的任何智能,最终导致解决问题。这些技术包括遵循一组规则或逻辑,或从先前的数据中学习,等等。考虑到这一点,人工智能解决方案可能具有或不具有学习能力以实现最优解。

具有学习能力的人工智能解决方案属于机器学习的子集。简单来说,机器学习只是实现人工智能的一种方式,它由能够在没有明确编程的情况下学习的算法组成。这意味着算法能够解析数据、从中学习,并据此做出决策(预测)。这种机器学习方法称为“监督学习”,基本上意味着算法同时接收输入数据和目标值(期望的输出)。

另一种机器学习方法称为“无监督学习”,与前述方法相比,只输入数据,没有与输出相关的任何关系。这里算法的目标是理解手头的数据以寻找相似之处。

最后,深度学习是机器学习的一个子集,使用多层神经网络(大型神经网络),灵感来自于人类大脑的生物结构,在一个层中的神经元接收一些输入数据,处理它,并将输出发送到下一层。这些神经网络可以由数千个互连的节点(神经元)组成,大多数以不同的层次组织,其中一个节点连接到前一层中的几个节点,接收其输入数据,同时连接到下一层中的几个节点,将经过处理的输出数据发送给它们。

神经网络的结构和功能将在本书的后续部分进一步解释。

深度学习为什么重要?为什么变得流行?

总体而言,深度学习之所以流行是因为准确性问题。深度学习在非常复杂的数据问题上实现了比以往任何时候都更高的准确性水平。这种出色表现的能力已经达到了机器可以胜过人类的水平,这不仅使模型能够优化流程,还能提高其质量。由于这一点,在对安全至关重要的革命性领域,如自动驾驶汽车,准确性的进步是显著的。

尽管神经网络理论上几十年前就存在,但它们最近变得流行有两个主要原因:

  • 神经网络需要大量标记数据才能达到最优解,并且实际上利用这些数据。这意味着为了算法能够创建出优秀的模型,需要拥有数十万条记录(对于某些数据问题甚至需要数百万条),其中包含特征和目标值。

    注意

    标记数据指的是包含一组特征(描述一个实例的特征)和目标值(要实现的值)的数据。

外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传

图 1.2:深度学习在数据量方面与其他算法的性能比较

这种现象现在得以实现,得益于软件方面的进步,允许收集如此详细的数据,同时硬件方面的进步则允许对其进行处理。

  • 神经网络需要大量的计算能力来处理这些数据,正如前面提到的。这是至关重要的,否则传统网络的训练时间将需要数周(甚至更长时间),考虑到实现最佳模型的过程是基于试错的,需要尽可能高效地运行训练过程。

    通过使用 GPU,可以将神经网络的训练时间从几周缩短到几小时。

    注意

    此外,为了加速深度学习以利用大量的训练数据并构建最先进的模型,主要的云计算提供商(如 AWS、Microsoft Azure 和 Google)正在开发 FPGA(现场可编程门阵列)和 TPU(张量处理单元)。

深度学习的应用

深度学习正在彻底改变我们所知的技术,因为基于其应用的许多发展目前正在影响我们的生活。此外,据认为在接下来的 5 到 10 年内,许多处理过程的方式将发生根本性变化。

此外,深度学习可以应用于广泛的情况,从医疗和安全用途到更琐碎的任务,如给黑白图像上色或实时翻译文本。

以下是目前正在开发或正在使用的一些深度学习应用场景:

  • 自动驾驶车辆:谷歌等多家公司正在开发部分或完全自动驾驶的车辆,这些车辆通过使用数字传感器来识别周围的物体学习驾驶。

外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传

外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传

图 1.3:Google 的自动驾驶汽车
  • 医学诊断:深度学习正在重新定义这一行业,通过提高诊断脑部和乳腺癌等终末疾病的准确性。这是通过对新患者的图像进行分类来完成的,基于先前患者的标记图像,这些图像表明患者是否患有癌症。

  • 语音助手: 这可能是当今最流行的应用之一,因为不同的语音激活智能助手大量普及,例如苹果的 Siri、Google Home 和亚马逊的 Alexa。

外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传

图 1.4: 亚马逊的 Alexa 智能助手
  • 自动文本生成: 这涉及基于输入的句子生成新的文本。在电子邮件撰写中,这被广泛应用,其中电子邮件提供商根据迄今为止写入的文本向用户建议接下来的几个词。

外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传

图 1.5: Gmail 的文本生成功能
  • 广告: 这里的主要思想是通过针对正确的受众或创建更有效的广告等方法来增加广告投资的回报率。

  • 价格预测: 对于初学者来说,这是通过使用机器学习算法可以实现的典型示例。价格预测包括基于实际数据训练模型,包括在房地产领域中,物业特征及其最终价格,以便仅基于物业特征预测未来条目的价格。

PyTorch 简介

外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传

图 1.6: PyTorch 图书馆标志

PyTorch 是一个开源库,主要由 Facebook 的人工智能研究小组开发,作为 Torch 的 Python 版本,于 2017 年 1 月首次向公众发布。它利用图形处理单元GPU)的强大能力来加速张量的计算,从而加快复杂模型的训练时间。

该库具有 C++ 后端,与 Torch 深度学习框架结合,比起许多带有多个深度学习功能的本地 Python 库,可以实现更快的计算。另一方面,其前端是 Python,这一点帮助了它的流行,因为新手数据科学家可以轻松构建非常复杂的神经网络。而且,由于与 Python 的集成,可以将 PyTorch 与其他流行的 Python 包一起使用。

尽管这个库相对较新,但由于使用了来自该领域许多专家的反馈,它迅速获得了广泛的流行,这使它成为了为用户而创建的库。在下一节中讨论了使用它的许多优缺点。

优势

如今有几个库可用于开发深度学习解决方案,那么为什么选择 PyTorch?因为 PyTorch 是一个动态库,允许用户以非常灵活的方式开发可以适应每个特定数据问题的复杂架构。

因此,它已被大量研究人员和人工智能开发人员采纳,这使得它成为机器学习领域求职必备。

这里显示了需要强调的关键方面:

  • 易用性:关于 API,PyTorch 具有简单的界面,使得开发和运行模型非常容易。许多早期采用者认为它比其他库(如 TensorFlow)更直观。它具有 Pythonic 风格,这意味着它与 Python 集成,在许多开发者看来,即使对于许多开发者来说,这个库还是新的,但是也很直观,易于使用。这种集成还允许使用许多 Python 包,如 NumPy 和 SciPy,以扩展其功能。

  • 速度:PyTorch 利用 GPU 进行加速张量计算。这使得该库训练速度比其他深度学习库更快。当需要测试不同的近似值以获得最佳模型时,速度是至关重要的。此外,即使其他库也可能有使用 GPU 加速计算的选项,PyTorch 只需输入几行简单的代码就可以完成此操作。

    注意

    下面的 URL 包含了对不同深度学习框架的速度基准测试(考虑到在处理大量训练数据时,训练时间的差异显而易见):

    github.com/u39kun/deep-learning-benchmark

  • 便利性:PyTorch 非常灵活。它使用动态计算图,允许您在运行时更改网络。此外,它在构建架构时提供了极大的灵活性,因为很容易对传统架构进行调整。

  • 命令式:PyTorch 还是命令式的。每行代码都是单独执行的,允许您实时跟踪模型,以及以更方便的方式调试模型。

  • 预训练模型:最后,它包含许多预训练模型,非常易于使用,是解决某些数据问题的绝佳起点。

缺点

虽然优点很多,但仍然有一些需要考虑的缺点,这里进行了解释:

  • 社区小:与 TensorFlow 等其他库相比,这个库的适配者社区非常小。然而,尽管只有两年的时间向公众开放,PyTorch 如今已经是实施深度学习解决方案的第三大流行库,并且其社区日益壮大。

  • 文档不完善:考虑到该库的新颖性,文档不如 TensorFlow 等更成熟的库完整。然而,随着库的功能和能力的增加,文档正在不断扩展。此外,随着社区的持续增长,互联网上将会有更多的信息可用。

  • 不适用于生产环境:尽管有关该库的许多投诉集中在其无法用于生产的能力上,但在版本 1.0 发布后,该库包含了生产能力,可以导出最终模型并在生产环境中使用。

什么是张量?

与 NumPy 类似,PyTorch 使用张量来表示数据,这些张量是类似于矩阵的 n 维结构,如 图 1.7 所示,不同之处在于张量可以在 GPU 上运行,这有助于加速数值计算。此外,值得一提的是,对于张量来说,维度被称为秩。

![图 1.7:不同维度张量的视觉表示

![图 1.7:不同维度张量的视觉表示

图 1.7:不同维度张量的视觉表示

与矩阵相反,张量是包含在结构中可以与其他数学实体交互的数学实体。当一个张量转换另一个张量时,前者也携带自己的转换。

这意味着张量不仅仅是数据结构,而是容器,当提供一些数据时,它们可以与其他张量进行多线性映射。

练习 1:使用 PyTorch 创建不同秩的张量

注意

所有练习和活动将主要在 Jupyter 笔记本中开发。建议为不同的作业保留单独的笔记本,除非另有建议。

在此练习中,我们将使用 PyTorch 库创建一秩、二秩和三秩的张量。

注意

对于本章中的练习和活动,您需要安装 Python 3.6、Jupyter、Matplotlib 和 PyTorch 1.0。

注意

要克隆包含本书中所有练习和活动的存储库,请在导航到所需路径后,在您的 CMD 或终端中使用以下命令:

git clone https://github.com/TrainingByPackt/Applied-Deep-Learning-with-PyTorch.git

  1. 打开 Jupyter 笔记本以实现此练习。

    打开您的 CMD 或终端,导航至所需路径,并使用以下命令打开 Jupyter 笔记本:jupyter notebook

    注意

    此命令可能会根据您的操作系统及其配置而变化。

  2. 导入名为 torch 的 PyTorch 库:

    import torch
    
  3. 创建以下秩的张量:1、2 和 3。

    使用介于 0 和 1 之间的值填充您的张量。可以根据您的需求定义张量的大小,只要创建正确的秩即可:

    tensor_1 = torch.tensor([0.1,1,0.9,0.7,0.3])
    tensor_2 = torch.tensor([[0,0.2,0.4,0.6],[1,0.8,0.6,0.4]])
    tensor_3 = torch.tensor([[[0.3,0.6],[1,0]], [[0.3,0.6],[0,1]]])
    

    当使用启用 GPU 的机器时,请使用以下脚本创建张量:

    tensor_1 = torch.cuda.tensor([0.1,1,0.9,0.7,0.3])
    tensor_2 = torch.cuda.tensor([[0,0.2,0.4,0.6],[1,0.8,0.6,0.4]])
    tensor_3 = torch.cuda.tensor([[[0.3,0.6],[1,0]], [[0.3,0.6],[0,1]]])
    
  4. 使用 shape 属性打印每个张量的形状,就像您在 NumPy 数组中所做的那样:

    print(tensor_1.shape)
    print(tensor_2.shape)
    print(tensor_3.shape)
    

    每个张量的最终形状应如下:

    torch.Size([5])
    torch.Size([2, 4])
    torch.Size([2, 2, 2])
    

恭喜!您已成功创建不同秩的张量。

PyTorch 的关键元素

像任何其他库一样,PyTorch 有各种模块、库和包,用于开发不同的功能。在本节中,将解释构建深度神经网络所使用的三个最常用元素,并提供一个语法的简单示例。

PyTorch autograd 库

autograd库包含一种称为自动微分的技术。它的目的是数值计算函数的导数。这对我们将在下一章节学习的反向传播概念至关重要,这是在训练神经网络时执行的操作。

注意

后续章节将详细解释神经网络,包括训练模型所采取的不同步骤。

要计算梯度,只需调用backward()函数,如下所示:

a = torch.tensor([5.0, 3.0], requires_grad=True)
b = torch.tensor([1.0, 4.0])
ab = ((a + b) ** 2).sum()
ab.backward()

在上述代码中,创建了两个张量。我们在这里使用requires_grad参数告诉 PyTorch 计算该张量的梯度。然而,在构建您的神经网络时,此参数是不需要的。

接下来,使用两个张量的值定义了一个函数。最后,使用backward()函数计算了梯度。

PyTorch nn 模块

autograd库本身可以用来构建简单的神经网络,考虑到更复杂的部分(梯度计算)已经处理好。然而,这种方法可能会有些麻烦,因此引入了 nn 模块。

nn 模块是一个完整的 PyTorch 模块,用于创建和训练神经网络,通过使用不同的元素,可以进行非常简单和非常复杂的开发。例如,Sequential()容器允许轻松创建按预定义模块(或层)序列排列的网络架构,无需太多的知识。

注意

可以在后续章节进一步解释可以用于每种神经网络架构的不同层。

此外,该模块还具备定义损失函数以评估模型的能力,以及许多更高级的功能,本书将对其进行讨论。

将神经网络架构建立为一系列预定义模块的过程可以在几行代码中完成,如下所示:

import torch.nn as nn
model = nn.Sequential(nn.Linear(input_units, hidden_units),         nn.ReLU(),         nn.Linear(hidden_units, output_units),         nn.Sigmoid())
loss_funct = torch.nn.MSELoss()

首先,导入模块。接下来,定义模型架构。input_units指的是输入数据包含的特征数,hidden_units指的是隐藏层节点数,output_units指的是输出层节点数。

可以看出,网络的架构包含一个隐藏层,具有 ReLU 激活函数,以及一个具有 sigmoid 激活函数的输出层。

最后,将损失函数定义为均方误差MSE)。

注意

为了创建不遵循现有模块序列的模型,使用了自定义的 nn 模块。我们将在本书的后续部分介绍这些内容。

PyTorch 优化包

使用optim包定义优化器,该优化器将用于更新每次迭代中的参数(在接下来的章节中将进一步解释),使用autograd模块计算的梯度。在这里,可以从可用的优化算法中选择,例如 Adam、随机梯度下降SGD)和 RMSprop 等。

要设置要使用的优化器,以下代码行足以:

optimizer = torch.optim.SGD(model.parameters(), lr=0.01)

在这里,model.parameters()参数指的是先前创建的模型的权重和偏差,而lr指的是学习率,设置为0.01

接下来,显示运行 100 次迭代的优化过程,正如您所看到的,使用了 nn 模块创建的模型和由autograd库计算的梯度:

for i in range(100):
    # Call to to the model to perform a prediction
    y_pred = model(x)
    # Calculation of loss function based on y_pred and y
    loss = loss_func(y_pred, y)
    # Zero the gradients so that previous ones don't accumulate
    optimizer.zero_grad()
    # Calculate the gradients of the loss function
    loss.backward()
    # Call to the optimizer to perform an update of the parameters
    optimizer.step()

每次迭代中,调用模型以获取预测值(y_pred)。将该预测值和地面真值(y)馈送到损失函数中,以确定模型逼近地面真值的能力。

然后,将梯度归零,并使用backward()函数计算损失函数的梯度。

最后,调用step()函数来基于优化算法和先前计算的梯度更新权重和偏差。

活动 1:创建单层神经网络

对于此活动,我们将创建一个单层神经网络,这将是我们未来活动中创建深度神经网络的起点。让我们看看以下情景。

您正在申请一家主要技术公司的工作,并且已经通过了所有筛选面试。招聘过程的下一步是在面试中实时展示您的编程机器学习技能。他们要求您使用 PyTorch 构建一个单层神经网络:

  1. 导入所需的库。

  2. 创建随机值的虚拟输入数据(x)和只包含 0 和 1 的虚拟目标数据(y)。将数据存储在张量中。张量x的大小应为(100,5),而y的大小应为(100,1)。

    注意

    PyTorch 张量可以像 NumPy 数组一样操作。使用torch.randn(number_instances, number_features)创建x。使用torch.randint(low=0, high, size)创建y。请注意,randint是上限独占的。确保将y张量转换为FloatTensor类型,因为这是 nn 模块处理的默认类型。为此,请使用.type(torch.FloatTensor)

  3. 定义模型的架构并将其存储在名为model的变量中。记得创建单层模型。

  4. 定义要使用的损失函数。

    使用均方误差损失函数。

  5. 定义模型的优化器。

    使用 Adam 优化器。

  6. 运行 100 次迭代的优化。在每次迭代中,打印并保存损失值。

    注意

    使用以下代码行将每次迭代步骤的损失值附加到在 for 循环之外预先创建的列表(losses)中:losses.append(loss.item())

  7. 打印最终权重和偏置值的数值。应该有五个权重值(每个输入数据的特征一个)和一个偏置值:

    model.state_dict()
    
  8. 制作一条线图显示每次迭代步骤的损失值。

    注意

    此活动的解决方案可以在第 186 页找到。

总结

在过去几年中,人工智能这个词变得越来越流行。我们在电影中看到它,也在现实生活中看到它,它基本上指的是机器展示的任何形式的智能,目的是优化人类的任务。人工智能的一个子类专注于那些能够从数据中学习的算法,称为机器学习。

深度学习是机器学习的一个子集,灵感来源于人类大脑的生物结构。它使用深度神经网络通过大量数据解决复杂的数据问题。尽管理论几十年前就已经发展,但最近由于硬件和软件的进步,这些理论得以应用,使得可以收集和处理数百万条数据。

随着深度学习解决方案的普及,已经开发了许多深度学习库。其中,最近的一个是 PyTorch。PyTorch 使用 C++后端来加速计算,同时拥有 Python 前端,使得库易于使用。

它使用张量来存储数据,这些张量是类似于 n 级矩阵的结构,可以在 GPU 上运行以加快处理速度。它提供了三个主要元素,对于以较少的工作量创建复杂的神经网络架构非常有用。

autograd库可以计算函数的导数,这些导数被用作优化模型的权重和偏差的梯度。此外,nn模块帮助您轻松地将模型的架构定义为一系列预定义模块,并确定用于测量模型的损失函数。最后,optim包用于选择优化算法以更新先前计算的梯度。

第二章:神经网络的基本构建模块

学习目标

通过本章结束时,您将能够:

  • 识别神经网络的优缺点

  • 区分神经网络解剖学中的不同组成部分

  • 识别最流行的神经网络架构并了解它们主要用于什么

  • 使用技术准备数据输入到神经网络中

  • 使用简单的架构解决回归问题

  • 通过解决高偏差或高方差来提高模型的性能

在本章中,我们将探讨神经网络的基本构建模块。我们将探索不同的架构来解决各种任务。最后,我们将学习如何使用 PyTorch 构建神经网络。

简介

尽管神经网络理论几十年前就已经发展,概念起源于感知机,但是近年来已经创建了不同的架构来解决不同的数据问题。这主要是由于现实数据问题中可以找到的不同数据格式,如文本、音频和图像。本章的目的是介绍神经网络的主题及其主要优缺点,以便更好地理解何时以及如何使用它们。接下来,本章将详细解释最流行的神经网络架构的构建模块:人工神经网络ANNs)、卷积神经网络CNNs)和循环神经网络RNNs)。

随后,通过解决实际的回归问题来解释构建有效模型的过程。这包括准备输入到神经网络中的数据(也称为数据预处理)、定义要使用的神经网络架构,最后评估模型的性能,以确定如何改进以实现最佳解决方案。

前述过程将使用前一章学习的神经网络架构之一来完成,考虑到每个数据问题的解决方案应使用最适合该数据类型的架构。其他架构将在后续章节中用于解决涉及使用图像和文本序列作为输入数据的更复杂的数据问题。

注意

作为提醒,包含本章中使用的所有代码的 GitHub 存储库可以在以下链接找到:github.com/TrainingByPackt/Applied-Deep-Learning-with-PyTorch

简介神经网络

几十年前发展起来的神经网络需要从训练数据中学习,而不是按照一套规则编程来解决特定任务。学习过程可以遵循以下方法之一:

  • 监督学习:这是学习的最简单形式,因为它包括一个带标签的数据集,神经网络需要找到解释特征与目标之间关系的模式。学习过程中的迭代旨在最小化预测值与真实值之间的差异。一个例子是基于叶子属性对植物进行分类。

  • 无监督学习:与前述方法相比,无监督学习包括使用未标记数据(即没有目标值)训练模型。其目的是更好地理解输入数据,通常网络会接收输入数据,对其进行编码,然后从编码版本中重建内容,理想情况下保留相关信息。例如,给定一段文字,神经网络可以映射单词以输出实际关键的单词,这些单词可以用作描述段落的标签。

  • 强化学习:这种方法论包括利用手头的数据进行学习,其主要目标是在长期内最大化奖励函数。因此,决策不是基于即时奖励,而是基于整个学习过程中的累积奖励,例如将资源分配给不同任务,以期减少减缓整体性能的瓶颈。

    从提到的学习方法中,最常用的是监督学习,这也是后续章节中主要使用的学习方法。这意味着大多数练习、活动和示例将使用带标签的数据集作为输入数据。

神经网络是什么?

简而言之,神经网络是一种机器学习算法,其模型建立在人脑解剖学基础上,利用数学方程从训练数据的观察中学习模式。

然而,要真正理解神经网络通常遵循的训练过程背后的逻辑,首先理解感知器的概念非常重要。

感知器在 20 世纪 50 年代由 Frank Rosenblatt 开发,是一种人工神经元,类似于人脑中的神经元,接收多个输入并产生二进制输出,成为后续神经元的输入。它们是神经网络的基本构建块(就像神经元是人脑的构建块一样)。

外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传

图 2.1:感知器示意图

这里,X1、X2、X3 和 X4 表示感知器接收的不同输入,可能有任意数量的输入。灰色圆圈表示感知器,在这里处理输入以得出结果。

罗森布拉特还引入了权重(w1, w2, …, wn)的概念,这些是表达每个输入重要性的数字。输出可以是 0 或 1,这取决于输入的加权和是高于还是低于给定的阈值,可以作为感知器的参数设置,如下所示:

外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传

图 2.2:感知器输出方程

练习 2:执行感知器的计算

下面的练习不需要任何编程;相反,它包含简单的计算,以帮助你理解感知器的概念。要执行这些计算,请考虑以下情景。

下周五你的城里有个音乐节,但你病了,正考虑是否去参加(其中 0 表示你会去,1 表示你不会去)。为了做出决定,你决定考虑三个因素:

  • 天气会好吗?(X1)

  • 你有人可以一起去吗?(X2)

  • 你喜欢这里的音乐吗?(X3)

针对上述因素,如果问题的答案是是,则我们将使用 1,如果答案是否,则使用 0。另外,由于你病得很厉害,天气因素相关性很高,你决定给这个因素的权重是其他两个因素的两倍。因此,因素的权重为 4(w1)、2(w2)和 2(w3)。现在,考虑一个阈值为 5:

  1. 下周五天气不好,但你有人可以一起去音乐节,并且喜欢那里的音乐:

外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传

图 2.3:感知器的输出

考虑到输出低于阈值,最终结果将等于 1,意味着你不应该去音乐节,以避免病情加重的风险。

恭喜!你成功地执行了感知器的计算。

多层感知器

考虑到前述情况,多层网络的概念包括多个感知器(也称为节点或神经元)堆叠在一起的网络,如此处所示:

外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传

图 2.4:多层感知器的图示

神经网络中用于指代层次的符号如下:第一层也称为输入层,最后一层也称为输出层,中间的所有层称为隐藏层。

在这里,再次使用一组输入来训练模型,但不是将输入馈送到单个感知器,而是馈送到第一层中的所有感知器(神经元)。接下来,从该层获得的输出被用作下一层感知器的输入,依此类推,直到达到最终层,负责输出结果。

需要指出的是,感知器的第一层通过加权输入来处理简单的决策过程,而后续层可以根据前一层的输出处理更复杂和抽象的决策,因此深度神经网络(使用许多层的网络)在处理复杂数据问题时表现出色。

与传统的感知器不同,神经网络已经演变为能够在输出层具有一个或多个节点,以便将结果呈现为二进制或多类别。

神经网络的学习过程

一般来说,神经网络是多个神经元的连接,其中每个神经元计算一个线性函数以及一个激活函数,以便根据一些输入得出输出。此输出与权重相关联,代表其重要性水平,以供后续层次的计算使用。

此外,这些计算是在整个网络架构中进行的,达到最终输出。此输出用于确定网络性能与真实情况的比较,然后用于调整网络的不同参数,以重新开始计算过程。

考虑到这一点,神经网络的训练过程可以被视为一个迭代过程,通过网络的各层前进和后退,以达到一个最优结果,如下图所示,并将详细解释:

外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传

图 2.5:神经网络学习过程的图示

前向传播

这是通过网络架构从左到右进行的过程,同时使用输入数据进行计算,以得出可以与真实情况进行比较的预测。这意味着网络中的每个神经元都会根据其关联的权重和偏置转换输入数据(初始数据或来自前一层的数据),并将输出发送到下一层,直到达到最终层并进行预测。

每个神经元中进行的计算包括一个线性函数,该函数将输入数据乘以一些权重再加上偏置,然后通过激活函数传递。激活函数的主要目的是打破模型的线性性,这在考虑到大多数使用神经网络解决的现实生活数据问题不是线性定义,而是由复杂函数定义时非常关键。相关公式可以在这里找到:

外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传

图 2.6:每个神经元执行的计算

此处,如前所述,X 指输入数据,W 是确定输入数据重要性水平的权重,b 是偏置值,sigma(外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传)表示应用于线性函数的激活函数。

激活函数的作用在于向模型引入非线性。现今常用的激活函数如下:

  • Sigmoid:它呈 S 形,基本上将值转换为介于 0 和 1 之间的简单概率,sigmoid 函数得到的大多数输出将接近 0 和 1 的极端值:

外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传

图 2.7:Sigmoid 激活函数

外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传

图 2.8:Sigmoid 激活函数的图形表示
  • Softmax:与 sigmoid 函数类似,它计算一个事件在 n 个事件中的概率分布,这意味着其输出不是二元的。简单来说,该函数计算输出属于目标类别之一的概率,相对于其他类别:

外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传

图 2.9:Softmax 激活函数

考虑到其输出为概率,这种激活函数通常出现在分类网络的输出层中。

  • Tanh:该函数表示双曲正弦和双曲余弦之间的关系,其结果在 -1 到 1 之间。该激活函数的主要优势在于能更轻松地处理负值:

外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传

图 2.10:tanh 激活函数。

外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传

图 2.11:tanh 激活函数的图形表示
  • 修正线性单元函数(ReLU):基本上激活一个节点,条件是线性函数的输出大于 0,否则其输出将为 0。如果线性函数的输出大于 0,则该激活函数的结果将是其接收的原始数字:

外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传

图 2.12:ReLU 激活函数

传统上,该激活函数用于所有隐藏层:

外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传

图 2.13:ReLU 激活函数的图形表示

损失函数的计算

一旦前向传播完成,训练过程的下一步是通过比较预测结果与真实值来计算损失函数,以估计模型的误差。考虑到这一点,理想的值应为 0,这意味着两个值之间没有偏差。

这意味着在训练过程的每个迭代中,目标是通过改变参数(权重和偏置)来最小化损失函数,在前向传播期间执行计算。

再次强调,有多种损失函数可供选择。然而,用于回归和分类任务的最常用的损失函数在此有解释:

  • 均方误差(MSE):广泛用于衡量回归模型性能,MSE 函数计算实际值和预测值之间距离的平方和:

外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传

图 2.14: 均方误差损失函数

这里,n 是样本数,外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传 是实际值,而 外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传 是预测值。

  • 交叉熵/多类交叉熵:这个函数通常用于二元或多类分类模型。它衡量两个概率分布之间的差异;一个较大的损失函数将代表较大的差异。因此,这里的目标是尽量减少损失函数:

外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传

图 2.15: 交叉熵损失函数

再次强调,n 代表样本数。外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传 分别是实际值和预测值。

反向传播(反向传递)

训练过程的最后一步是沿着网络架构从右到左计算损失函数对每一层权重和偏置的偏导数,以便更新这些参数(权重和偏置),以便在下一次迭代步骤中减少损失函数。

此外,优化算法的最终目标是找到损失函数达到可能的最小值的全局极小值,如下图所示:

注意

提醒一下,局部极小值指的是函数定义域中的最小值。另一方面,全局极小值指的是整个函数定义域的最小值。

外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传

图 2.16: 通过迭代步骤优化损失函数。二维空间。

这里,最左边的点(A)是损失函数的初始值,在任何优化之前。最右边最底部的点(B)是经过多次迭代步骤后损失函数被最小化的值。从一个点到另一个点的过程称为步骤

然而,重要的是要提到,损失函数并不总是像前面的那样平滑,这可能在优化过程中引入达到局部极小值的风险。

这个过程也称为优化,有不同的算法以不同的方法达到相同的目标。接下来将解释最常用的优化算法。

梯度下降

梯度下降是数据科学家中最广泛使用的优化算法,也是许多其他优化算法的基础。在计算每个神经元的梯度后,权重和偏置会朝梯度的相反方向进行更新,更新步骤的大小由学习率控制(用于控制每次优化中的步骤大小),如下方程式所示。

在训练过程中,学习率至关重要,因为它可以防止权重和偏置的更新过度/不足,这可能会阻止模型达到收敛或延迟训练过程。

在梯度下降算法中,权重和偏置的优化如下所示:

外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传

外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传

图 2.17:梯度下降算法中参数的优化

在这里,α 表示学习率,dw/db 表示给定神经元中权重或偏置的梯度。这两个值的乘积从权重或偏置的原始值中减去,以惩罚那些导致计算大损失函数的较高值。

对梯度下降算法的改进称为随机梯度下降,它基本上遵循相同的过程,但不同之处在于它以随机批次方式获取输入数据,而不是一次性获取整个数据块,这可以提高训练时间同时达到卓越的性能。此外,这种方法允许使用更大的数据集,因为通过使用数据集的小批次作为输入,我们不再受到计算资源的限制。

优势和劣势

下面是神经网络的优势和劣势的解释。

优势

过去几年中,神经网络因四个主要原因变得越来越受欢迎:

  • 数据:神经网络以其利用大量数据的能力而广为人知,多亏了硬件和软件的进步,现在可以回忆和存储大量数据库。这使得神经网络在输入更多数据时展现出了其真正的潜力。

  • 复杂数据问题:正如前面所述,神经网络非常适合解决其他机器学习算法无法解决的复杂数据问题。这主要是因为它们能够处理大规模数据集并揭示复杂的模式。

  • 计算能力:技术的进步也提升了当今可用的计算能力,这对于训练使用数百万数据的神经网络模型至关重要。

  • 学术研究:由于前述三点,关于这一主题的大量学术研究可在互联网上找到,这不仅促进了每天新研究的涌现,还有助于保持算法及硬件/软件需求的最新性。

缺点

只因为使用神经网络有很多优点,并不意味着每个数据问题都应该用这种方式解决。这是一个常见的错误。没有一种算法适用于所有数据问题,选择算法应该依赖于可用的资源以及数据问题本身。

尽管神经网络被认为能够胜任几乎所有的机器学习算法,但重要的是也要考虑它们的缺点,以权衡对数据问题最为重要的因素。

  • 黑盒子:这是神经网络最常见的缺点之一。它基本上意味着神经网络如何以及为什么会得出特定的输出是未知的。例如,当神经网络错误地将一张猫的图片预测为狗时,无法知道错误的原因是什么。

  • 数据需求:为了达到最佳结果,神经网络需要大量的数据,这既是优点也是缺点。神经网络需要比传统机器学习算法更多的数据,这可能是某些数据问题中选择它们与其他算法之间的主要原因。当任务是有监督学习时,即数据需要被标记,这一问题变得尤为突出。

  • 训练时间:与前述的缺点相关联,对大量数据的需求也使得训练过程比传统机器学习算法更长,而在某些情况下这是不可选的。通过使用 GPU 可以缩短训练时间,因为 GPU 能加速计算过程。

  • 计算成本高昂:再次强调,神经网络的训练过程是计算成本高昂的。虽然一个神经网络可能需要数周才能收敛,其他机器学习算法则可能只需几小时或几分钟即可完成训练。所需的计算资源取决于手头数据的量以及网络的复杂性;更深层次的神经网络需要更长的训练时间。

    注意

    神经网络的架构种类繁多。本章将解释其中三种最常用的架构,并在后续章节中介绍它们的实际实现。然而,如果你希望了解其他架构,请访问www.asimovinstitute.org/neural-network-zoo/

人工神经网络简介

人工神经网络ANNs),也称为多层感知器,是多个感知器的集合,如前所述。这里重要的是提到,感知器之间的连接发生在层之间,其中一个层可以拥有任意多的感知器,并且它们都与前后层的所有其他感知器相连接。

网络可以有一个或多个层。具有超过四层的网络被认为是深度神经网络,并且通常用于解决复杂和抽象的数据问题。

ANN 通常由三个主要元素组成,在前面已经详细解释过,也可以在图 2.18中看到:

  1. 输入层:这是网络的第一层,传统上位于网络图表的最左侧。它在执行任何计算之前接收输入数据,并完成第一组计算,在这里最通用的模式被揭示。

    对于监督学习问题,输入数据由特征和目标值的一对组成。网络的任务是揭示输入和输出之间的相关性或依赖关系。

  2. 隐藏层:接下来,可以找到隐藏层。神经网络可以拥有尽可能多的隐藏层。层数越多,它可以处理的数据问题越复杂,但训练时间也会更长。也有一些神经网络架构完全不包含隐藏层,这在单层网络中是适用的情况。

    在每一层中,基于从前一层接收到的输入信息进行计算,输出一个预测结果,成为后续层的输入。

  3. 输出层:这是网络的最后一层,位于网络图表的最右侧。它在所有神经元处理数据后接收数据,以生成和显示最终预测。

    输出层可以有一个或多个神经元。前者是指解决方案是二进制形式的模型,即 0 或 1 的形式。另一方面,后者包括输出实例属于每个可能类标签(目标)的概率,这意味着该层将具有与类标签数量相同的神经元。

外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传

图 2.18:具有两个隐藏层的神经网络架构

卷积神经网络介绍

卷积神经网络(CNNs)主要用于计算机视觉领域,在这个领域,近几十年来,机器已经达到甚至超过人类能力的准确性水平,因此它们变得越来越受欢迎。

受人类大脑启发,CNN 搜索创建模型,利用不同组的神经元识别图像的不同方面。这些组应该能够相互通信,以便共同形成整体图像。

考虑到这一点,CNN 架构中的层分割它们的识别任务。第一层专注于简单的模式,网络末端的层使用这些信息揭示更复杂的模式。

例如,在识别图片中的人脸时,前几层专注于查找将一个特征与另一个分开的边缘。接下来的层强调人脸的某些特征,如鼻子。最后,最后几层使用这些信息将整个人脸拼合在一起。

通过使用滤波器或核心来激活一组神经元以在遇到特定特征时激活的想法,这是通过使用卷积神经网络结构的主要构建块之一来实现的。然而,它们并不是结构中唯一存在的元素,这就是为什么将提供对 CNN 的所有组成部分的简要解释:

当使用 CNN 时可能听说过的填充和步幅的概念,将在本书的后续章节中进行解释。

  1. 卷积层:在这些层中,图像(表示为像素矩阵)与滤波器之间进行卷积计算。这种计算产生特征图作为输出,最终作为下一层的输入。

    计算取图像矩阵的一个子部分,并执行值的乘法。然后,乘积的总和被设定为该图像部分的输出,如下图所示:

    外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传

    图 2.19:图像与滤波器之间的卷积操作

    这里,左侧的矩阵是输入数据,中间的矩阵是滤波器,右侧的矩阵是计算的输出。可以在这里看到通过红框突出显示的值进行的计算:

    外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传

    图 2.20:图像第一部分的卷积

    这种卷积乘法对图像的所有子部分进行。图 2.21 展示了相同示例的另一个卷积步骤:

    外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传

    图 2.21:卷积操作的进一步步骤

    卷积层的一个重要概念是,它们是不变的,每个滤波器都有一个特定的功能,在训练过程中不会变化。例如,负责检测耳朵的滤波器在整个训练过程中只专注于这个功能。

    此外,卷积神经网络通常会有多个卷积层,每个层都会根据所使用的滤波器专注于识别图像的特定特征。此外,需要指出的是,在两个卷积层之间通常有一个池化层。

  2. 池化层:尽管卷积层能够从图像中提取相关特征,但当分析复杂的几何形状时,其结果可能会变得庞大,这会使得训练过程在计算能力方面变得不可能。因此,池化层的发明就显得尤为重要。

    这些层不仅达到了减少卷积层输出的目标,而且实现了去除特征中存在的噪声,从而最终提高了模型的准确性。

    可应用两种主要类型的池化层,并且它们的理念在于检测在图像中表达出更强烈影响的区域,从而可以忽略其他区域:

    • 最大池化:此操作包括取矩阵中给定大小的子段,并将该子段中的最大数作为最大池化操作的输出。

外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传

图 2.22:最大池化操作

在上述图中,通过使用 3x3 的最大池化滤波器,得到了右侧的结果。在这里,黄色区域(左上角)的最大值为 4,而橙色区域(右上角)的最大值为 5。

  • 平均池化:类似地,平均池化操作会取矩阵的子段并输出符合规则的数字,这种情况下是该子段中所有数字的平均值。

外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传

图 2.23:平均池化操作

在这里,使用 3x3 的滤波器,我们得到了黄色区域(左上角)所有数字的平均值为 8.6,而橙色区域(右上角)的平均值为 9.6。

  1. 全连接层:最后,考虑到如果网络只能检测一组特征而无法将其分类到类别标签中,那么网络将毫无用处,因此在 CNN 的末端使用全连接层,将前一层(称为特征图)检测到的特征输出,并输出这些特征组属于类别标签的概率,这被用于进行最终预测。

    像人工神经网络一样,全连接层利用感知器根据给定的输入计算输出。此外,需要提到的是,卷积神经网络通常在架构末端有多个全连接层。

结合所有这些概念,可以得到卷积神经网络的传统结构,其中每种类型可以有任意多层,每个卷积层可以有任意多个滤波器(每个用于特定任务),池化层应具有相同数量的滤波器,如下图所示:

外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传

图 2.24:卷积神经网络结构图

递归神经网络简介

前述神经网络的主要限制在于它们仅通过考虑当前事件(正在处理的输入)来学习,而不考虑先前或随后的事件,这在我们人类的思考方式中是不方便的。例如,在阅读一本书时,通过考虑上一段落或更多的上下文,你可以更好地理解每个句子。

因此,考虑到神经网络的优化过程通常由人类完成,设计能够考虑输入和输出序列的网络变得至关重要,因此递归神经网络(RNNs)应运而生。它们是一种强大的神经网络类型,通过使用内部记忆解决复杂的数据问题。

简而言之,这些网络包含了其中的循环,允许信息在其内存中保留更长时间,即使正在处理后续信息。这意味着 RNN 中的感知器不仅将输出传递给下一个感知器,还会向自身传递一些信息,这对于分析下一个信息片段是有用的。这种记忆保持能力使得它们在预测接下来会发生什么方面非常准确。

递归神经网络的学习过程,类似于其他网络,试图映射输入(x)和输出(y)之间的关系,不同之处在于这些模型还考虑了全部或部分先前输入的历史。

RNNs 允许处理数据序列,可以是输入序列、输出序列,甚至同时处理两者,如下图所示:

外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传

图 2.25:递归神经网络处理的数据序列

在这里,每个框都是一个矩阵,箭头表示发生的函数。底部框是输入,顶部框是输出,中间框表示该点的 RNN 状态,保存了网络的记忆。

从左到右,以下是前述图表的解释:

  1. 典型的模型不需要 RNN 解决。它具有固定的输入和固定的输出。例如,这可以是图像分类。

  2. 这个模型接受一个输入并产生一系列的输出。例如,一个接收图像作为输入并输出图像标题的模型。

  3. 与上述相反,这个模型接受一系列的输入并生成一个单一的输出。这种类型的架构可以在情感分析问题中看到,其中输入是要分析的句子,输出是句子背后的预测情感。

  4. 最后两个模型接受一系列的输入并返回一系列的输出,不同之处在于第一个模型首先分析整个输入集,然后生成输出集。例如,在语言翻译中,需要先完全理解一种语言中的整个句子,然后再进行实际翻译。另一方面,第二个多对多模型同时分析输入并同时生成输出。例如,标记视频每一帧时。

数据准备

在收集数据之后,任何深度学习模型的开发的第一步,应该是数据的准备。这对于正确理解手头的数据并能够正确界定项目范围至关重要。

许多数据科学家未能这样做,导致模型性能不佳,甚至模型无用,因为它们根本不解决数据问题。

准备数据的过程可以分为三个主要任务:1)理解数据并处理任何潜在问题,2)重新缩放特征以确保不会由于错误引入偏差,以及 3)拆分数据以能够准确地衡量性能。所有这三个任务将在下一节中进一步解释。

注意

所有先前解释的任务在应用任何机器学习算法时基本相同,因为它们涉及到预先准备数据所需的技术。

处理混乱数据

这项任务主要包括执行探索性数据分析EDA)以理解可用的数据,并检测可能影响模型开发的潜在问题。

EDA 过程非常有用,因为它帮助开发人员发现对行动计划定义至关重要的信息。这些信息在这里解释:

  1. 数据量:这既涉及实例的数量,也涉及特征的数量。前者对于确定是否有必要甚至可能使用神经网络或深度神经网络解决数据问题至关重要,考虑到这类模型需要大量数据才能达到高精度水平。而后者则有助于确定是否在开发之前采用某些特征选择方法,以减少特征数量、简化模型并消除任何冗余信息。

  2. 目标特征:对于监督模型,数据需要被标记。考虑到这一点,选择目标特征(建立模型时希望达到的目标)非常重要,以评估特征是否存在许多缺失或异常值。此外,这有助于确定开发的目标,该目标应与可用数据保持一致。

  3. 噪声数据/异常值:噪声数据指的是明显不正确的数值,例如年龄为 200 岁的人。另一方面,异常值指的是虽然可能是正确的数值,但离均值很远,例如 10 岁的大学生。

    检测异常值并没有确切的科学方法,但有些方法学是被普遍接受的。假设一个正态分布的数据集,其中最流行的方法之一是将任何偏离所有数值均值约 3-6 个标准偏差的值定义为异常值,无论是正方向还是负方向。

    识别异常值的一个同样有效的方法是选择处于 99 分位和 1 分位的数值。

    处理此类数值非常重要,特别是当它们代表特征数据的 5%以上时,因为不处理可能会引入模型偏差。处理这些数值的方法与任何其他机器学习算法一样,要么删除异常值,要么使用均值或回归插补技术赋予新值。

  4. 缺失值:与前述情况类似,数据集中存在许多缺失值可能会引入模型偏差,考虑到不同模型会对这些值做出不同的假设。同样,当缺失值占特征值的 5%以上时,应通过删除或替换它们的方式进行处理,同样可以使用均值或回归插补技术。

  5. 定性特征:最后,检查数据集是否包含定性数据也是一个关键步骤,因为移除或编码数据可能会导致更准确的模型。

    此外,在许多研究开发中,会在同一数据上测试多个算法,以确定哪一个表现更好,而其中一些算法不能容忍使用定性数据,因此转换或编码它们以能够将所有算法用同样的数据进行输入显得尤为重要。

练习 3:处理混乱数据

注意

本章所有的练习将使用 UC Irvine 机器学习库中的Appliances energy prediction Dataset进行,可以通过以下 URL 下载,使用Data Folder超链接:archive.ics.uci.edu/ml/datasets/Appliances+energy+prediction

在这个练习中,我们将使用 Python 的一个受欢迎的包来探索手头的数据,并学习如何检测缺失值、异常值和定性值:

注意

对于本章中的练习和活动,您需要安装 Python 3.6、Jupyter、NumPy 和 Pandas(至少版本 0.21)。

  1. 打开 Jupyter 笔记本以实施这个练习。

    打开您的 cmd 或终端,导航到所需路径,并使用以下命令打开 Jupyter 笔记本:jupyter notebook

  2. 导入 pandas 库:

    import pandas as pd
    
  3. 使用 pandas 读取先前从 UC Irvine 机器学习库站点下载的包含数据集的 CSV 文件。

    然后,删除名为date的列,因为我们不打算在接下来的练习中考虑它。

    最后,打印 DataFrame 的头部:

    data = pd.read_csv("energydata_complete.csv")
    data = data.drop(columns=["date"])
    data.head()
    
  4. 检查数据集中的分类特征:

    cols = data.columns
    num_cols = data._get_numeric_data().columns
    list(set(cols) - set(num_cols))
    

    结果列表为空,这表明没有分类特征需要处理。

  5. 使用 Python 的isnull()sum()函数来查找数据集每一列中是否有缺失值:

    data.isnull().sum()
    

    此命令计算每列中的空值数量。对于正在使用的数据集,不应该有任何缺失值。

  6. 使用三个标准差作为测量值,以检测数据集中所有特征的异常值:

    outliers = {}
    for i in range(data.shape[1]):
        min_t = data[data.columns[i]].mean()(3 *             data[data.columns[i[[.std())
        max_t = data[data.columns[i]].mean() + (3 *             data[data.columns[i[[.std())
    
        count = 0
        for j in data[data.columns[i]]:
            if j < min_t or j > max_t:
                count += 1
    
        percentage = count / data.shape[0]
        outliers[data.columns[i]] = "%.3f" % percentage
    

    结果字典显示了数据集中所有特征的列表,以及异常值的百分比。从这些结果可以得出结论,由于异常值比例低于 5%,因此无需处理。

恭喜!您已成功探索了数据集并处理了潜在问题。

数据重缩放

尽管数据不需要重新缩放以供算法训练,但这是提高模型准确性的重要步骤。基本上是因为每个特征具有不同的比例可能会导致模型认为某个特征比其他特征更重要,因为它具有更高的数值。

举例来说,考虑两个特征:一个测量一个人有几个孩子,另一个说明这个人的年龄。尽管年龄特征可能有更高的数值,但在推荐学校的研究中,孩子数量特征可能更重要。

考虑到这一点,如果所有特征被等比例缩放,模型实际上可以根据目标特征的重要性给予更高的权重,而不是它们具有的数值。此外,它还可以通过消除模型学习数据的不变性来加速训练过程。

数据科学家中有两种主要的重新缩放方法,虽然没有选择其中一种的规则,但重要的是要强调它们应该单独使用(一种或另一种)。

可以在这里找到这两种方法的简要说明:

  • 归一化:这包括重新调整值,使得所有特征的所有值都在 0 到 1 之间,使用以下方程:

外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传

图 2.26:数据归一化
  • 标准化: 相反,这种缩放方法通过以下方程将所有值转换为均值为 0、标准差为 1。

外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传

图 2.27: 数据标准化

练习 4: 数据重新缩放

在本练习中,我们将对上一个练习中的数据进行重新缩放:

注意

使用与上一个练习中相同的 Jupyter 笔记本。

  1. 将特征与目标分开。这样做是为了只对特征数据进行重新缩放:

    X = data.iloc[:, 1:]
    Y = data.iloc[:, 0]
    
  2. 使用归一化方法对特征数据进行重新缩放。显示结果 DataFrame 的前几行以验证结果:

    X = (X - X.min()) / (X.max() - X.min())
    X.head()
    

恭喜!您已成功地对数据集进行了重新缩放。

数据分割

将数据集分为三个子集的目的是,使模型可以适当地进行训练、微调和测量,而不引入偏差。以下是每个集合的解释:

  • 训练集: 如其名称所示,此集合被馈送到神经网络进行训练。对于监督学习来说,它包括特征和目标值。考虑到神经网络需要大量数据进行训练,因此这通常是三个集合中最大的集合。

  • 验证集(开发集): 主要用于衡量模型的性能,以便调整超参数以提高性能。这个微调过程是为了找到能够获得最佳结果的超参数配置。

    尽管模型没有在这些数据上进行训练,但它间接影响了这些数据,这就是为什么不应该在这些数据上进行最终的性能评估,因为它可能会产生偏差。

  • 测试集: 这个集合对模型没有影响,因此用于对未见数据进行最终评估,这成为衡量模型在未来数据集上表现的指导。

没有关于将数据分成所提到的三个集合的完美比例的实际科学,考虑到每个数据问题都不同,并且开发深度学习解决方案通常需要试错方法。尽管如此,众所周知,较大的数据集(数十万和数百万个实例)的分割比例应为 98%/1%/1%,因为对于训练集来说,尽可能使用尽可能多的数据至关重要。对于较小的数据集,传统的分割比例是 60%/20%/20%。

练习 5: 分割数据集

在本练习中,我们将从上一个练习中的数据集中分割出三个子集。为了学习目的,我们将探索两种不同的方法。首先,将使用索引来分割数据集。接下来,将使用 scikit-learn 的train_test_split()函数来实现相同的目的,使用这两种方法都可以达到相同的结果:

注意

使用与上一个练习中相同的 Jupyter 笔记本。

  1. 打印数据集的形状,以确定要使用的拆分比例。

    X.shape
    

    此操作的输出应为(19735, 28)。这意味着可以使用 60%/20%/20%的拆分比例用于训练、验证和测试集。

  2. 获取用作训练集和验证集底限的值。这将用于使用索引拆分数据集:

    train_end = int(len(X) * 0.6)
    dev_end = int(len(X) * 0.8)
    
  3. 对数据集进行洗牌:

    X_shuffle = X.sample(frac=1)
    Y_shuffle = Y.sample(frac=1)
    
  4. 使用索引将洗牌后的数据集拆分为三个集合,分别用于特征和目标数据:

    x_train = X_shuffle.iloc[:train_end,:]
    y_train = Y_shuffle.iloc[:train_end]
    x_dev = X_shuffle.iloc[train_end:dev_end,:]
    y_dev = Y_shuffle.iloc[train_end:dev_end]
    x_test = X_shuffle.iloc[dev_end:,:]
    y_test = Y_shuffle.iloc[dev_end:]
    
  5. 打印所有三个集合的形状:

    print(x_train.shape, y_train.shape)
    print(x_dev.shape, y_dev.shape)
    print(x_test.shape, y_test.shape)
    

    前面操作的结果应如下所示:

    (11841, 27) (11841, )
    (3947, 27) (3947, )
    (3947, 27) (3947, )
    
  6. 从 scikit-learn 的model_selection模块导入train_test_split()函数:

    from sklearn.model_selection import train_test_split
    
  7. 拆分洗牌后的数据集:

    x_new, x_test_2, y_new, y_test_2 = train_test_split(X_shuffle, Y_shuffle, test_size=0.2, random_state=0)
    dev_per = x_test_2.shape[0]/x_new.shape[0]
    x_train_2, x_dev_2, y_train_2, y_dev_2 = train_test_split(x_new, y_new, test_size=dev_per, random_state=0)
    

    第一行代码执行初始拆分。该函数接受两个要拆分的数据集(X 和 Y)、test_size(测试集中包含的实例百分比)以及random_state(确保结果可重现)。该代码的结果是将每个数据集(X 和 Y)分为两个子集。

    为了创建一个额外的集合(验证集),我们将执行第二次拆分。前面代码的第二行负责确定用于第二次拆分的test_size,以便测试集和验证集具有相同的形状。

    最后,代码的最后一行执行第二次拆分,使用先前计算的值作为test_size

  8. 打印所有三个集合的形状:

    print(x_train_2.shape, y_train_2.shape)
    print(x_dev_2.shape, y_dev_2.shape)
    print(x_test_2.shape, y_test_2.shape)
    

    前面操作的结果应如下所示:

    (11841, 27) (11841, )
    (3947, 27) (3947, )
    (3947, 27) (3947, )
    

    可以看到,两种方法得到的结果集具有相同的形状。使用其中一种方法还是另一种方法是一种个人偏好。

恭喜!您已成功将数据集分割为三个子集。

活动 2:执行数据准备

对于接下来的活动,我们将准备一个包含多个属性的歌曲列表数据集,这些属性有助于确定歌曲发布的年份。这一数据准备步骤对本章节内后续活动至关重要。让我们看看以下场景。

您在音乐唱片公司工作,他们想要揭示区分不同时间段唱片的细节,因此他们已经整理了一个包含数据的数据集,该数据集包含了 515,345 条记录,发布年份从 1922 年到 2011 年不等。他们委托您准备数据集,以便输入神经网络使用。

注意

要下载数据集,请访问以下 UC Irvine Machine Learning Repository 的网址:archive.ics.uci.edu/ml/datasets/YearPredictionMSD

  1. 导入所需的库。

  2. 使用 pandas,加载文本文件。由于先前下载的文本文件与 CSV 文件的格式相同,因此可以使用read_csv()函数进行读取。确保将头部参数设置为None

  3. 验证数据集中是否存在任何定性数据。

  4. 检查缺失值。

    如果您在先前用于此目的的代码行中添加额外的sum()函数,您将得到整个数据集中缺失值的总和,而不是按列区分。

  5. 检查异常值。

  6. 将特征与目标数据分开。

  7. 使用标准化方法重新调整数据。

  8. 将数据分成三组:训练集、验证集和测试集。使用您喜欢的方法。

    注意

    正解可以在第 188 页找到。

构建深度神经网络

从一般角度来看,构建神经网络可以通过非常简单的方式实现,使用像 scikit-learn 这样的库(不适用于深度学习),它会为您完成所有数学运算,但灵活性不高;或者通过非常复杂的方式实现,从头编写训练过程的每一个步骤,或者使用更强大的框架,它可以在同一个地方允许两者的近似。如前所述,它有一个 nn 模块,专门用于使用顺序容器轻松预定义简单架构的实现,同时允许创建引入灵活性的自定义模块,以构建非常复杂的架构过程。

另一方面,PyTorch 是考虑了领域内许多开发者的输入构建的,具有允许在同一地方进行两者近似的优势。如前所述,它有一个 nn 模块,专门用于使用顺序容器轻松预定义简单架构的实现,同时允许创建引入灵活性的自定义模块,以构建非常复杂的架构过程。

在本节中,我们将进一步讨论使用顺序容器开发深度神经网络的使用,以揭开它们的复杂性。然而,在本书的后续章节中,我们将继续探讨更复杂和抽象的应用,这些应用也可以通过极少的努力实现。

正如前面提到的,顺序容器是一个模块,用于包含按顺序跟随的模块序列。它包含的每个模块都会对给定的输入应用一些计算,以得出结果。

可以在顺序容器内使用的一些最受欢迎的模块(层)以开发常规分类模型在这里进行解释:

注意

用于其他类型架构(如卷积神经网络和循环神经网络)的模块将在接下来的章节中进行解释。

  • True 默认情况下)作为参数。

  • False 默认情况下。

    Tanh:将逐元素 tanh 函数应用于包含输入数据的张量。它不接受任何参数。

    Sigmoid:将之前解释过的 sigmoid 函数应用于包含输入数据的张量。它不接受任何参数。

    Softmax:将 softmax 函数应用于包含输入数据的 n 维张量。输出经过重新缩放,使得张量元素位于介于零到一之间的范围内,并且总和为一。它接受作为参数应计算 softmax 函数的维度。

  • False 默认情况下。这种技术通常用于处理过拟合模型,稍后将进一步解释。

  • 归一化层:有不同的方法可以用来在顺序容器中添加归一化层。其中一些方法包括 BatchNorm1d、BatchNorm2d 和 BatchNorm3d。其背后的想法是对前一层的输出进行归一化,最终在较短的训练时间内达到类似的准确性水平。

练习 6:使用 PyTorch 构建深度神经网络

在这个练习中,我们将使用 PyTorch 库定义一个四层深度神经网络的架构,然后使用之前准备好的数据集进行训练:

注:

使用与之前练习中使用的相同的 Jupyter 笔记本。

  1. 从 PyTorch 库中导入所需的库,称为torch,以及来自 PyTorch 的nn模块:

    import torch
    import torch.nn as nn
    
    注:

    尽管不同的包和库根据实际学习目的在需要时被导入,但是始终将它们导入到代码的开头是一个良好的实践。

  2. 从之前的练习中分离出特征列和目标列,对于每个创建的数据集。另外,将最终的数据框转换为张量:

    x_train = torch.tensor(x_train.values).float()
    y_train = torch.tensor(y_train.values).float()
    x_dev = torch.tensor(x_dev.values).float()
    y_dev = torch.tensor(y_dev.values).float()
    x_test = torch.tensor(x_test.values).float()
    y_test = torch.tensor(y_test.values).float()
    
  3. 使用sequential()容器定义网络架构。确保创建一个四层网络。

    对于前三层使用 ReLU 激活函数,并且考虑到我们处理的是回归问题,最后一层不使用激活函数。

    每层的单元数应为:100、50、25 和 1:

    model = nn.Sequential(nn.Linear(x_train.shape[1], 100),
                          nn.ReLU(),
                          nn.Linear(100, 50),
                          nn.ReLU(),
                          nn.Linear(50, 25),
                          nn.ReLU(),
                          nn.Linear(25, 1))
    
  4. 将损失函数定义为均方误差:

    loss_function = torch.nn.MSELoss()
    
  5. 将优化器算法定义为 Adam 优化器:

    optimizer = torch.optim.Adam(model.parameters(), lr=0.01)
    
  6. 使用for循环在训练数据上进行 100 次迭代步骤训练网络:

    for i in range(100):
        y_pred = model(x_train)
        loss = loss_function(y_pred, y_train)
        print(i, loss.item())
        optimizer.zero_grad()
        loss.backward()
        optimizer.step()
    
  7. 为了测试模型,对测试集的第一个实例进行预测,并将其与基准值(目标值)进行比较:

    pred = model(x_test[0])
    print(y_test[0], pred)
    

    从这个结果可以看出,模型表现不佳,因为目标值与预测值差异很大。在本书的后续部分,您将学习如何提高模型的性能。

祝贺!您已成功创建并训练了一个深度神经网络,以解决回归问题。

活动 3:为回归问题开发深度学习解决方案

在下一个活动中,我们将创建并训练一个四层隐藏层神经网络,以解决之前活动中提到的回归问题。让我们看一下以下的场景:

您继续在音乐唱片公司工作,看到您在准备数据集方面做得很出色后,他们信任您来定义网络的架构和代码,并使用准备好的数据集进行训练:

注:

使用与之前活动中使用的相同的 Jupyter 笔记本。

  1. 导入所需的库。

  2. 从之前活动中分离特征和目标,针对所有三组数据。将数据框转换为张量。

  3. 定义网络的架构。可以尝试不同的层数和每层的单元数组合。

  4. 定义损失函数和优化算法。

  5. 使用for循环训练网络进行 100 次迭代。

  6. 通过在测试集的第一个实例上进行预测并将其与真实值进行比较来测试您的模型。

您的输出应该与以下类似:

外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传

图 2.28:活动的输出
注意

这项活动的解决方案可以在第 190 页找到。

概要

几十年前,由 Frank Rosenblatt 开发的理论孕育了神经网络。它始于感知器的定义,这是一个受人类神经元启发的单元,它接收数据作为输入并对其进行转换。它包括为输入数据分配权重以进行计算,以便最终结果要么是一种结果,要么是另一种,取决于结果。

神经网络最广为人知的形式是由一系列感知器组成的,这些感知器堆叠在一起形成层,其中一列感知器(层)的输出是下一列的输入。

根据这一点,解释了神经网络的典型学习过程。在这个主题上,有三个主要过程需要考虑:前向传播、损失函数的计算和反向传播。

这个过程的最终目标是通过更新伴随着神经网络每个输入值的权重和偏差来最小化损失函数。这通过一个迭代过程实现,可能需要几分钟、几小时,甚至几周,具体取决于数据问题的性质。

三种类型的神经网络的主要架构也进行了讨论:人工神经网络、卷积神经网络和循环神经网络。第一种用于解决传统分类问题,第二种因其解决计算机视觉问题(图像分类)的能力而广受欢迎,而最后一种能够处理序列数据,对于诸如语言翻译之类的任务非常有用。

第三章:使用 DNN 解决分类问题

学习目标

在本章结束时,您将能够:

  • 解释深度学习在银行业的应用

  • 区分为回归任务和分类任务构建神经网络

  • 应用 PyTorch 中的自定义模块概念来解决问题

  • 使用深度神经网络解决分类问题

  • 处理欠拟合或过拟合的模型

  • 部署 PyTorch 模型

在本章中,我们将专注于使用 DNN 解决简单的分类任务,以巩固我们对深度神经网络的知识。

引言

虽然深度神经网络(DNNs)可以用来解决回归问题,正如前一章所见,但它们更常用于解决分类任务,目标是从一系列选项中预测结果。

利用这些模型的一个领域是银行业。这主要是由于他们需要基于人口统计数据预测未来行为,以及确保长期盈利的主要目标。在银行业的一些应用包括评估贷款申请、信用卡批准、预测股市价格和通过分析行为检测欺诈。

本章将专注于使用深度人工神经网络解决银行业的分类问题,遵循到达有效模型所需的所有步骤:数据探索、数据准备、架构定义和模型训练、模型微调、错误分析,最后是最终模型的部署。

注意

提醒一下,在本章中使用的所有代码的 GitHub 仓库可以在以下网址找到:github.com/TrainingByPackt/Applied-Deep-Learning-with-PyTorch

问题定义

定义问题和构建模型或提高准确性同等重要。为什么呢?因为即使您可能使用了最强大的算法,并使用了最先进的方法来改进其结果,如果您解决了错误的问题或使用了错误的数据,这一切可能都是无用的。

此外,学会深入思考理解什么可以做和不能做,以及如何完成可以做的事情至关重要。特别是考虑到当我们学习应用机器学习或深度学习算法时,问题总是清楚呈现的,除了模型训练和性能改进外,不需要进一步的分析;另一方面,在现实生活中,问题通常令人困惑,数据也常常混乱。

因此,在本节中,您将学习如何根据组织需求和手头数据的最佳实践来定义问题。

为此,需要完成以下事项:

  • 理解问题的“什么”、“为什么”和“如何”。

  • 分析手头的数据以确定我们模型的一些关键参数,例如要执行的学习任务类型、必要的准备工作和性能指标的定义。

  • 进行数据准备以减少向模型引入偏见的概率。

银行业中的深度学习

类似于健康领域,银行和金融机构每天都处理大量信息,这些信息需要做出关键决策,这些决策不仅影响到他们自己组织的未来,还影响到信任他们的数百万个人的未来。

每一秒都在做出这些决策,在 1990 年代,银行业人士过去依赖于基本上利用人类专家知识来编码基于规则的程序的专家系统。毫不奇怪,这些程序表现不佳,因为它们要求所有信息或可能的情景在前期都进行编程,这使得它们对处理不确定性和高度变化的市场效率低下。

随着技术的进步以及获取客户数据的能力的提高,银行业已经引领了向更专业的系统过渡,这些系统利用统计模型来帮助做出此类决策。此外,由于银行需要同时考虑自身的盈利能力和客户的盈利能力,它们被认为是那些不断跟上技术进步以日益提高效率和准确性的行业之一。

如今,与医疗市场一样,银行和金融行业正在推动神经网络市场。这主要是因为神经网络能够利用大量先前数据来预测未来行为中的不确定性。这是人类专家知识基础系统无法实现的,考虑到人类大脑无法分析如此大量的数据。

以下简要介绍了银行和金融服务领域中使用深度学习的一些领域:

  • 贷款申请评估:银行根据不同因素向客户发放贷款,包括人口统计信息、信用历史等。他们在此过程中的主要目标是最小化客户违约贷款的数量(最小化失败率),从而最大化通过发放贷款获得的回报。

    神经网络被用来帮助决定是否批准贷款。通常使用以前未能按时还款的贷款申请人以及按时还款的贷款申请人的数据来训练它们。一旦建立了模型,想法就是将新申请人的数据输入到模型中,以便预测他们是否会还款,考虑到模型的重点是减少误判(即模型预测会违约贷款的客户,但实际上他们没有)。

    行业已知神经网络的失败率低于依赖人类专业知识的传统方法。

  • 欺诈检测: 对于银行和金融服务提供商来说,欺诈检测至关重要,现在比以往任何时候都更加重要,考虑到技术的进步,尽管使我们的生活更轻松,但也使我们在网上银行平台上面临更大的财务风险。

    在这个领域使用神经网络,具体来说是卷积神经网络(CNN),用于字符和图像识别,以检测图像中隐藏的抽象模式,以确定用户是否被替代。

  • .xls 文件。

探索数据集

在接下来的章节中,我们将专注于使用信用卡客户违约DCCC)数据集解决与信用卡付款相关的分类任务,该数据集已经从 UC Irvine Repository 网站上下载。

本节的主要目的是清楚地说明数据问题的什么、为什么和如何,这将有助于确定研究目的和评估指标。此外,在本节中,我们将详细分析手头的数据,以便识别准备数据时需要的一些步骤(例如,将定性特征转换为它们的数值表示)。

首先,让我们定义什么、为什么和如何。考虑到这一点,应该确保识别组织的真实需求:

什么: 构建一个能够确定客户是否会在即将到期的付款中违约的模型。

为什么: 能够预见下个月将收到的付款金额(以货币形式)。这将帮助公司确定该月的支出策略,此外还允许他们为每个客户定义应采取的行动,既确保那些将支付账单的客户未来的付款,又提高那些将违约客户的支付概率。

如何: 使用包含客户人口统计信息、信用历史和之前账单声明的历史数据来训练模型。在对输入数据进行训练后,该模型应能够确定客户是否有可能在下一个付款中违约。

考虑到这一点,似乎目标特征应该是一个说明客户是否会违约下一个付款的特征,这意味着学习任务是一个分类任务,因此损失函数应该能够测量这种类型学习的差异(例如,交叉熵函数,如前一章所述)。

一旦问题定义清楚,就需要确定最终模型的优先级。这意味着确定所有输出类是否同等重要。例如,一个测量肺部肿块是否恶性的模型应主要集中在最小化假阴性(模型预测为没有恶性肿块的患者,但实际上是恶性的肿块)。另一方面,一个用于识别手写字符的模型不应专注于一个特定字符,而应最大化在识别所有字符方面的性能。

考虑到这一点,以及为什么声明中的解释,信用卡客户违约数据集的模型优先级应该是最大化模型的整体性能,而不优先考虑任何类标签。这主要是因为为什么声明宣称,研究的主要目的应该更好地了解银行将收到的款项,并对可能违约付款的客户执行某些操作,以及对不会违约的客户执行不同的操作。

根据此,本案例研究中要使用的性能指标是准确度,其侧重点是最大化正确分类的实例。这指的是任何类标签的正确分类实例与总实例数之间的比率。

下表包含数据集中每个特征的简要解释,这可以帮助确定它们对研究目的的相关性,并确定需要执行的一些准备任务。

外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传

图 3.1:来自 DCCC 数据集的特征描述

外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传

图 3.2:来自 DCCC 数据集的特征描述,继续

综合考虑这些信息,可以得出结论,在 25 个特征(包括目标特征)中,有 2 个需要从数据集中移除,因为它们被认为与研究目的无关。请记住,对于本研究无关的特征可能在其他研究中是相关的。例如,关于私密卫生产品的研究可能认为性别特征是相关的。

此外,所有特征都是定量的,这意味着除了重新缩放它们之外,无需转换它们的值。目标特征也已转换为其数值表示,其中下次付款违约的客户表示为 1,而未违约的付款客户表示为 0。

数据准备

虽然在这方面有一些良好的实践,但在准备数据集以开发深度学习解决方案时,没有固定的步骤集,大多数情况下,需要采取的步骤将取决于手头的数据、要使用的算法以及研究的其他特性。

尽管如此,在开始训练模型之前,有一些必须遵循的关键方面作为良好的实践。其中大部分您已经从前一章中了解到,将针对所讨论的数据集进行修订,另外还要对目标特征的类别不平衡进行修订:

注意

在本节中将处理准备 DCCC 数据集的过程,并附上简要说明。随时打开 Jupyter 笔记本,复制这个过程,考虑到这将是后续活动的起点。

  • 使用skiprows参数移除 Excel 文件的第一行,该行不相关,因为它包含第二组标题。

    根据给定的代码行,得出以下结果:

外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传

图 3.3:DCCC 数据集的头部

数据集的形状是 30,000 行和 25 列,可以使用以下代码行获取:

print("rows:",data.shape[0]," columns:", data.shape[1])
  • 删除不相关的特征:通过对每个特征的分析,确定了两个特征与研究目的无关,因此应将其从数据集中移除。

    data_clean = data.drop(columns=["ID", "SEX"])
    data_clean.head()
    

    最终的数据集应包含 23 列,而不是原来的 25 列:

外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传

图 3.4:删除不相关特征后的 DCCC 数据集的头部
  • 检查缺失值:接下来是检查数据集是否存在缺失值,并计算它们在每个特征中所占的百分比,可以使用以下代码行完成:

    total = data_clean.isnull().sum()
    percent = (data_clean.isnull().sum()/
                     data_clean.isnull().count()*100)
    pd.concat([total, percent], axis=1, 
    keys=['Total', 'Percent']).transpose()
    

    第一行对数据集的每个特征的缺失值进行求和。接下来,计算每个特征中缺失值在所有值中的参与度。最后,将之前计算的两个值连接起来,以表格形式显示结果。结果显示在图 3.5中:

外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传

图 3.5:DCCC 数据集中缺失值的计数

从这些结果可以看出,数据集中没有缺失任何值,因此在这里不需要进一步的步骤。

  • BILL_AMT1BILL_AMT4,每个占总实例的 2.3%。

    这意味着考虑到它们的参与度太低,并且不太可能对最终模型产生影响,因此不需要进一步操作。

    检查类别不平衡:当目标特征中的类标签表示不均匀时,就会发生类别不平衡;例如,一个包含 90%未来未违约客户与 10%违约客户的数据集被认为是不平衡的。

    处理类别不平衡的几种方法,其中一些在这里解释:

    收集更多数据:尽管这并非总是可行的途径,但可能有助于平衡类别,或者允许删除过度表示类别而不严重减少数据集。

    更改性能指标:某些指标,如准确性,不适合用于衡量不平衡数据集的性能。因此,建议使用精确度或召回率等指标来衡量分类问题的性能。

    对数据集进行重新采样:这包括改变数据集以平衡各类别。可以通过两种不同的方式实现:1)添加欠表示类别的副本(称为过采样),或者,2)删除过度表示类别的实例(称为欠采样)。

    可以通过简单地计算目标特征中每个类别的出现次数来检测类别不平衡,如下所示:

    target = data_clean["default payment next month"]
    yes = target[target == 1].count()
    no = target[target == 0].count()
    print("yes %: " + str(yes/len(target)*100) + " - no %: " + str(no/len(target)*100))
    

    从前述代码中可以得出结论,违约支付客户的数量占数据集的 22.12%。这些结果也可以使用以下代码行显示在图中:

    import matplotlib.pyplot as plt
    fig, ax = plt.subplots()
    plt.bar("yes", yes)
    plt.bar("no", no)
    ax.set_yticks([yes,no])
    plt.show()
    

    这导致以下图表:

外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传

图 3.6:目标特征的类别计数

为了解决这个问题,并考虑到没有更多数据可以添加,并且性能指标实际上是准确性,需要进行数据重新采样。

以下是执行数据集过采样的代码片段,随机创建欠表示类别的重复行:

data_yes = data_clean[data_clean["default payment next month"]                       == 1]
data_no = data_clean[data_clean["default payment next month"]                      == 0]
over_sampling = data_yes.sample(no, replace=True, random_state                                 = 0)
data_resampled = pd.concat([data_no, over_sampling], axis=0)

首先,我们将每个类标签的数据分别放入独立的 DataFrame 中。接下来,我们使用 pandas 的sample()函数构建一个包含与过度表示的类别 DataFrame 相同数量的重复实例的新 DataFrame。

最后,使用concat()函数将过度表示类别的 DataFrame 和相同大小的新 DataFrame 连接起来。

通过计算整个数据集中每个类别的参与度,结果应显示出均衡的类别。此外,到目前为止数据集的最终形状应为(46728,23)。

  • 从目标中分离特征:我们将数据集分割成特征矩阵和目标矩阵,以避免重新调整目标值:

    X = data_clean.drop(columns=["default payment next month"])
    y = data_clean["default payment next month"] 
    
  • 重新调整数据:最后,我们重新调整特征矩阵的值,以避免向模型引入偏差:

    X = (X - X.min())/(X.max() - X.min())
    X.head()
    

    前面几行代码的结果显示在图 3.7中:

外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传

图 3.7:归一化后的特征矩阵
注意事项

注意,婚姻教育都是序数特征,意味着它们遵循一定的顺序或层次;在选择重新缩放方法时,请确保保持顺序。

为了方便后续活动使用准备好的数据集,特征(X)和目标(y)矩阵将连接成一个 pandas DataFrame,并保存到 CSV 文件中,使用以下代码:

final_data = pd.concat([X, y], axis=1)
final_data.to_csv("dccc_prepared.csv", index=False)

完成所有这些步骤后,DCCC 数据集已准备就绪(保存在新的 CSV 文件中),以用于训练模型,这将在接下来的章节中进行解释。

模型构建

一旦问题被定义,并且探索和准备手头的数据,就是定义模型的时候了。在进行先前分析之后,应处理网络架构、层类型、损失函数等的定义。这主要是因为在机器学习中没有“一刀切”的方法,尤其在深度学习中更是如此。

与分类任务不同,回归任务需要不同的方法,聚类、计算机视觉或机器翻译也是如此。因此,在接下来的章节中,您将找到解决分类任务的模型构建的关键特征,以及如何得到“好”的架构以及如何何时使用 PyTorch 中的自定义模块的解释。

用于分类任务的人工神经网络(ANN)

如前一章中的活动所示,用于回归任务的神经网络使用输出作为连续值,这就是为什么输出函数没有激活函数,只有一个输出节点(实际值)的原因,例如基于房屋特征和社区特征来预测房价的模型。

考虑到这一点,性能的测量应通过计算地面真实值与预测值之间的差异来完成,就像计算 125.3(预测值)与 126.38(地面真实值)之间的距离一样。如前所述,有许多方法可以衡量这种差异,其中均方误差MSE)或其变体均方根误差RMSE)是最常用的度量标准。

相反,分类任务的输出是某组输入特征属于每个输出标签或类别的概率,这是通过使用 Sigmoid(用于二分类)或 Softmax(用于多类分类)激活函数来完成的。此外,对于二分类任务,输出层应包含一个(用于 Sigmoid)或两个(用于 Softmax)输出节点,而对于多类分类任务,输出节点应等于类标签的数量。

能够计算属于每个输出类的可能性的这种能力,再加上argmax函数,将检索具有更高概率的类作为最终预测。

在 Python 中,argmax是一个函数,能够返回沿着轴的最大值的索引。

考虑到这一点,模型的性能应该是实例是否已被分类到正确的类别标签的问题,而不是与测量两个值之间的距离有关的任何事情,因此训练神经网络用于分类问题的使用不同的损失函数(交叉熵是最常用的),以及使用不同的性能指标,如准确率、精确率和召回率。

一个良好的架构

首先,正如本书中所解释的那样,重要的是理解手头的数据问题,以确定神经网络的一般拓扑结构。再次强调,普通的分类问题不需要与计算机视觉问题相同的网络架构。

一旦定义了这一点,并考虑到在确定隐藏层数量、其类型或每个层中单元数量方面没有正确答案,最好的方法是从一个初始架构开始,然后可以改进以增加性能。

为什么这么重要?因为有大量参数需要调整时,有时很难承诺并开始。这是不幸的,因为在训练神经网络时,有几种方法可以确定一旦训练和测试了初始架构后需要改进的内容。实际上,将数据集分成三个子集的整个目的是允许用一个集合训练数据集,用另一个集合测量和微调模型,并最终用一个之前未使用过的最终子集测量最终模型的性能。

考虑到所有这些,将解释以下一套惯例和经验法则,以帮助决策过程,定义人工神经网络的初始架构:

  • 输入层:这很简单 - 只有一个输入层,其单元数取决于训练数据的形状。具体来说,输入层中的单元数应该等于输入数据包含的特征数。

  • 隐藏层:隐藏层的数量可以不同。人工神经网络可以有一个、多个或者没有隐藏层。要选择合适的数量,重要的是考虑以下几点:

    数据问题越简单,需要的隐藏层就越少。请记住,可以线性可分的数据问题应该只有一个隐藏层。另一方面,随着深度学习的进步,现在可以使用许多隐藏层(没有限制)来解决非常复杂的数据问题。

    要开始的隐藏单元数应该在输入层单元数和输出层单元数之间。

  • 输出层:同样,任何人工神经网络只有一个输出层。它包含的单元数取决于要开发的学习任务以及数据问题。如前所述,对于回归任务,只会有一个单元,即预测值。另一方面,对于分类问题,单元数应等于可用的类标签数,考虑到模型的输出应该是一组特征属于每个类标签的概率。

  • 其他参数:传统上,应该将其他参数保留为网络的第一个配置的默认值。这主要是因为在考虑可能表现同样良好或更差但需要更多资源的更复杂近似方法之前,测试数据问题上最简单的模型是一种良好的实践。

一旦定义了初始架构,就是训练和评估模型性能的时候了,以便进行进一步的分析,这很可能会导致网络架构或其他参数值的更改,例如学习率的更改或添加正则化项。

PyTorch 自定义模块

PyTorch 的开发团队创建了自定义模块,以允许用户更进一步地灵活性。与前几章探讨的 Sequential 容器相反,只要希望构建更复杂的模型架构或者希望在每一层的计算中具有更多控制权,就应该使用自定义模块。

尽管如此,这并不意味着自定义模块方法只能在这种情况下使用。相反,一旦学会同时使用两种方法,选择在较不复杂的架构中使用哪种方法就成为一种偏好问题。

例如,以下代码片段展示了使用 Sequential 容器定义的两层神经网络:

import torch.nn as nn
model = nn.Sequential(nn.Linear(D_i, D_h),
                      nn.ReLU(),
                      nn.Linear(D_h, D_o),
                      nn.Softmax())

这里,D_i 指的是输入维度(输入数据中的特征),D_h 指的是隐藏层的节点数(隐藏维度),D_o 指的是输出维度。

使用自定义模块,可以构建一个等效的网络架构,如下所示:

import torch
from torch import nn, optim
import torch.nn.functional as F
class Classifier(nn.Module):
    def __init__(self, input_size):
        super().__init__()
        self.hidden_1 = nn.Linear(input_size, 100)
        self.hidden_2 = nn.Linear(100, 100)
        self.hidden_3 = nn.Linear(100, 50)
        self.hidden_4 = nn.Linear(50,50)
        self.output = nn.Linear(50, 2)

        self.dropout = nn.Dropout(p=0.1)
        #self.dropout_2 = nn.Dropout(p=0.1)

    def forward(self, x):
        z = self.dropout(F.relu(self.hidden_1(x)))
        z = self.dropout(F.relu(self.hidden_2(z)))
        z = self.dropout(F.relu(self.hidden_3(z)))
        z = self.dropout(F.relu(self.hidden_4(z)))
        out = F.log_softmax(self.output(z), dim=1)

        return out

需要提及的是,交叉熵损失函数要求网络输出是原始的(在通过 softmax 激活函数获得概率之前),这就是为什么在没有激活函数的情况下找到用于分类问题的神经网络架构是常见的。此外,在采用这种方法后,为了得到预测结果,需要在训练后将 softmax 激活函数应用于网络输出。

处理此限制的另一种方法是在输出层使用log_softmax激活函数。接下来,损失函数被定义为负对数似然损失(nn.NLLLoss)。最后,可以通过从网络输出中取指数来获取属于每个类标签的实际概率。这是本章活动中将要使用的方法。

一旦模型架构被定义,接下来的步骤将是编写负责在训练数据上训练模型并测量其在训练和验证集上性能的部分代码。

这里,将给出按步骤编码我们讨论过的内容的说明:

model = Classifier()
criterion = nn.NLLLoss()
optimizer = optim.Adam(model.parameters(), lr=0.005)
epochs = 10
batch_size = 100

如可见,第一步是定义在网络训练期间将使用的所有变量。

注意

提醒一下,“epochs”指的是整个数据集通过网络结构前后传递的次数。“batch_size”是单个批次(数据集的一个片段)中的训练样本数。最后,“iterations”指的是完成一个 epoch 所需的批次数。

接下来,首先使用一个for循环遍历之前定义的 epoch 次数。随后,使用一个新的for循环遍历总数据集的每个批次,直到一个 epoch 完成。在这个循环内,发生以下计算:

  1. 模型在训练集的一个批次上进行训练。这里得到一个预测。

  2. 通过比较上一步的预测和训练集(地面真实)的标签来计算损失。

  3. 梯度被归零,并且针对当前步骤再次计算。

  4. 根据梯度更新网络的参数。

  5. 在训练数据上计算模型的准确率如下:

    • 获取模型预测的指数,以获得给定数据属于每个类标签的概率。

    • 使用topk()方法获取具有较高概率的类标签。

    • 使用 scikit-learn 的指标部分计算准确率、精确率或召回率。您还可以探索其他性能指标。

  6. 关闭梯度计算,以验证当前模型在验证数据上的表现,具体操作如下:

    • 模型对验证集中的数据进行预测。

    • 通过比较前一个预测和验证集标签来计算损失函数。

    • 要计算模型在验证集上的准确率,使用与在训练数据上进行相同计算的步骤:

      train_losses, dev_losses, train_acc, dev_acc= [], [], [], []
      for e in range(epochs):
          X, y = shuffle(X_train, y_train)
          running_loss = 0
          running_acc = 0
          iterations = 0
          for i in range(0, len(X), batch_size):
              iterations += 1
              b = i + batch_size
              X_batch = torch.tensor(X.iloc[i:b,:].values).float()
              y_batch = torch.tensor(y.iloc[i:b].values)
              pred = model(X_batch)
              loss = criterion(pred, y_batch)
              optimizer.zero_grad()
              loss.backward()
              optimizer.step()
              running_loss += loss.item()
              ps = torch.exp(pred)
              top_p, top_class = ps.topk(1, dim=1)
              running_acc += accuracy_score(y_batch, top_class)
          dev_loss = 0
          acc = 0
          with torch.no_grad():
              pred_dev = model(X_dev_torch)
              dev_loss = criterion(pred_dev, y_dev_torch)
              ps_dev = torch.exp(pred_dev)
              top_p, top_class_dev = ps_dev.topk(1, dim=1)
              acc = accuracy_score(y_dev_torch, top_class_dev)
          train_losses.append(running_loss/iterations)
          dev_losses.append(dev_loss)
          train_acc.append(running_acc/iterations)
          dev_acc.append(acc)
          print("Epoch: {}/{}.. ".format(e+1, epochs),
                "Training Loss: {:.3f}.. ".format(running_loss/iterations),
                "Validation Loss: {:.3f}.. ".format(dev_loss),
                "Training Accuracy: {:.3f}.. ".format(running_acc/iterations),
                "Validation Accuracy: {:.3f}".format(acc))
      

上述代码片段将打印出训练集和验证集数据的损失和准确率。

活动 4:构建人工神经网络

为了此活动,使用先前准备好的数据集,我们将构建一个能够确定客户是否会违约下一个付款的四层模型。为此,我们将使用自定义模块的方法。

让我们看看以下场景:您在一家专门为全球各地的银行提供机器/深度学习解决方案的数据科学精品公司工作。他们最近接受了一个银行的项目,希望预测下个月不会收到的付款。探索性数据分析团队已经为您准备好了数据集,并要求您构建模型并计算模型的准确性:

  1. 导入以下库。

    import pandas as pd
    import numpy as np
    from sklearn.model_selection import train_test_split
    from sklearn.utils import shuffle
    from sklearn.metrics import accuracy_score
    import torch
    from torch import nn, optim
    import torch.nn.functional as F
    import matplotlib.pyplot as plt
    
    注意

    即使使用了种子,由于每个 epoch 之前训练集都会被洗牌,因此这些活动的确切结果也无法再现。

  2. 读取先前准备好的数据集,该数据集应命名为dccc_prepared.csv

  3. 将特征和目标分开。

  4. 使用 scikit-learn 的train_test_split函数,将数据集分割为训练集、验证集和测试集。使用 60/20/20%的分割比例。将random_state设置为 0。

  5. 将验证集和测试集转换为张量,考虑到特征矩阵应为 float 类型,而目标矩阵则不应为 float 类型。目前保持训练集未转换,因为它们将经历进一步的转换。

  6. 构建一个自定义模块类来定义网络的各层。包括一个 forward 函数,指定将应用于每个层输出的激活函数。在所有层中使用 ReLU,除了输出层,其中应使用log_softmax

  7. 定义训练模型所需的所有变量。将 epoch 数设置为 50,批量大小设置为 128。使用学习率 0.001。

  8. 使用训练集数据训练网络。使用验证集来衡量性能。因此,在每个 epoch 中保存训练集和验证集的损失和准确性。

    注意

    训练过程可能需要几分钟,具体取决于您的资源。添加打印语句是查看训练进程的良好实践。

  9. 绘制两个数据集的损失。

  10. 绘制两个数据集的准确性。

    注意

    此活动的解决方案可以在第 192 页找到。

处理欠拟合或过拟合模型

建立深度学习解决方案不仅仅是定义架构然后使用输入数据训练模型的问题;相反,大多数人认为那只是简单部分。创建高科技模型的艺术在于实现超过人类性能的高准确性水平。鉴于此,本节将介绍错误分析的主题,该主题通常用于诊断已训练模型,以发现哪些操作更有可能对模型的性能产生积极影响。

错误分析。

误差分析顾名思义是对训练和验证数据集的错误率进行初步分析。然后使用这个分析来确定改进模型性能的最佳方案。

为了执行误差分析,需要确定贝叶斯误差,也称为不可约误差,这是可达到的最小误差。几十年前,贝叶斯误差等同于人类误差,这意味着那时专家可以达到的最低误差水平。

如今,随着技术和算法的改进,估计这个值变得越来越困难,因为机器能够超越人类的表现,但我们无法衡量它们相比于人类能做得更好多少,因为我们只能理解到我们的能力所及。

通常将贝叶斯误差初步设置为人类误差,以执行误差分析。然而,这种限制并不是一成不变的,研究人员知道,超越人类表现也应该是一个最终目标。

执行误差分析的过程如下所示:

  1. 计算选择的指标来衡量模型的表现。这个度量应该在训练集和验证集上都计算。

  2. 使用这个度量,通过从 1 中减去先前计算的性能指标来计算每个集合的错误率。例如,使用以下方程式:外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传

    图 3.8:计算模型在训练集上错误率的方程式
  3. 从训练集误差(A)中减去贝叶斯误差。保存这个差值,将用于进一步分析。

  4. 从验证集错误(B)中减去训练集错误,并保存差值的值。

    • 取步骤 3 和 4 中计算的差异,并使用以下一组规则:

    • 如果步骤 3 中计算的差异高于其他差异,则模型欠拟合,也称为高偏差。

  5. 如果步骤 4 中计算的差异高于其他差异,则模型过拟合,也称为高方差:

外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传

图 3.9:展示如何执行误差分析的图表

我们解释的规则并不表明模型只能遭受提到的问题之一,而是高差异的那个问题对模型性能影响更大,修复它将更大程度地提高性能。

让我们解释如何处理每个问题:

  • 高偏差:欠拟合模型,或者受到高偏差影响的模型,是一种不能理解训练数据的模型,因此,它不能揭示模式并且不能与其他数据集泛化。这意味着该模型在任何数据集上的表现都不佳。

    为了减少影响模型的偏差,建议定义更大/更深的网络(增加隐藏层)或增加训练迭代次数。通过增加层数和增加训练时间,网络有更多资源来发现描述训练数据的模式。

  • 高方差:一个过拟合的模型或者受到高方差影响的模型,是指模型在泛化训练数据时出现困难;它过于深入学习训练数据的细节,包括异常值。这意味着模型在训练数据上表现过好,但在其他数据集上表现不佳。

    通常可以通过向训练集添加更多数据或在损失函数中添加正则化项来处理这种情况。第一种方法旨在强制网络泛化到数据,而不是理解少量示例的细节。另一种方法则通过惩罚具有更高权重的输入来忽略异常值,并平等考虑所有值。

考虑到这一点,处理影响模型的一个条件可能会导致另一个条件的出现或增加。例如,一个受到高偏差影响的模型,在处理后可能会改善其在训练数据上的表现,但在验证数据上却没有改善,这意味着模型开始受到高方差的影响,并需要采取另一组补救措施。

一旦对模型进行了诊断并采取了必要的措施来提高性能,应选择最佳模型进行最终测试。每个模型都应用于对测试集的预测(这是唯一不会影响模型构建的数据集)。

考虑到这一点,可以选择在测试数据上表现最佳的模型作为最终模型。这主要是因为在测试数据上的表现作为模型在未来未见数据集上性能的指标,这是最终目标。

练习 7:执行误差分析

使用前一活动计算的准确度指标,在本活动中我们将执行错误分析,帮助我们确定下一活动中需要执行的行动:

注意

此活动无需编码,而是要分析前一活动的结果。

  1. 假设贝叶斯错误率为 0.15,执行错误分析并诊断模型:

    Bayes error (BE) = 0.15
    Training set error (TSE) = 10.715 = 0.285
    Validation set error (VSE) = 10.706 = 0.294
    

    作为两个集合准确性的值(0.715 和 0.706),它们是在前一活动的最后迭代中获得的。

    Bias = TSE – BE = 0.135
    Variance = VSE – TSE = 0.009
    

    根据此,模型存在高偏差问题,意味着模型欠拟合。

  2. 确定要遵循以提高模型准确性的行动方案。

    为了提高模型的性能,可以采取以下两种行动方案:增加迭代次数并增加隐藏层和/或单元数。

    根据此,可以进行一系列测试,以达到最佳结果。

恭喜!您已成功进行了错误分析。

活动 5:改善模型性能

对于接下来的活动,我们将实施在练习中定义的操作,以减少影响模型性能的高偏差。让我们看一下以下情景:在将模型交付给您的队友后,他们对您的工作以及您组织代码的方式印象深刻(做得好!),但他们要求您尝试将性能提高到 80%,考虑到这是他们向客户承诺的。

注意

使用不同的笔记本进行此活动。在那里,您将再次加载数据集,并执行与上一个活动类似的步骤,不同之处在于训练过程将多次进行,以训练不同的架构和训练时间。

  1. 导入与上一个活动相同的库。

  2. 加载数据并将特征与目标分离。然后,将数据分成三个子集(训练、验证和测试),使用 60:20:20 的分割比例。最后,将验证和测试集转换为 PyTorch 张量,就像您在上一个活动中所做的那样。

  3. 考虑到模型正在遭受高偏差,重点应放在增加 epochs 的数量或通过添加额外的层或单位来增加网络的大小。目标应该是将测试集上的准确性近似到 80%。

    注意

    考虑到选择先进行哪项测试没有正确的方法,因此要有创造性和分析能力。如果模型架构的更改减少或消除了高偏差,但引入了高方差,则考虑保留这些更改,同时添加一些措施来对抗高方差。

  4. 绘制两组数据的损失和准确率。

  5. 使用表现最佳的模型,对测试集进行预测(在微调过程中不应使用)。通过计算模型在此集合上的准确性,将预测与实际情况进行比较。

    注意

    可在第 196 页找到此活动的解决方案。

部署您的模型

到目前为止,已经讨论并实践了用于常规回归和分类问题构建出色深度学习模型的关键概念和技巧。在现实生活中,模型不仅仅是为了学习目的而构建的。相反,当为除研究目的以外的目的训练模型时,主要思想是能够在未来重复使用它们,以对新数据执行预测,即使该模型未经训练,也应该表现出类似的良好性能。

在小型组织中,序列化和反序列化模型的能力就足够了。然而,当模型需要被大型企业、用户使用或用于改变重要且大型任务时,将模型转换为能在大多数生产环境中使用的格式,如 API、网站、在线和离线应用等,会是更好的做法。

根据此文,本节中我们将学习如何保存和加载模型,以及如何使用 PyTorch 的最新功能将您的模型转换为高度通用的 C++ 应用程序。

保存和加载您的模型

您可能想象,每次使用模型都重新训练是非常不实际的,尤其是考虑到大多数深度学习模型可能需要相当长的时间来训练(根据您的资源)。

相反,PyTorch 中的模型可以被训练、保存和重新加载,以进行进一步的训练或推断。这可以通过保存每个 PyTorch 模型层的参数(权重和偏置)到 state_dict 字典中来实现。

这里提供了关于如何保存和加载训练过的模型的逐步指南:

  1. 最初,模型的检查点仅包括模型的参数。然而,在加载模型时,不仅需要这些信息,还可能需要保存其他信息,例如输入单元的数量,这取决于分类器接受的参数。因此,第一步是定义要保存的信息:

    checkpoint = {"input": X_train.shape[1],
                  "state_dict": model.state_dict()}
    

    当加载模型时,将保存输入层中的单位数到检查点中将非常有用。

  2. 使用您选择的文本编辑器创建一个 Python 文件,导入 PyTorch 库,并包含创建模型网络架构的类。这样做是为了能够方便地将模型加载到不同于训练模型所用的工作表中。

  3. 使用 PyTorch 的 save() 函数保存模型:

    torch.save(checkpoint, "checkpoint.pth")
    

    第一个参数是先前创建的字典,第二个参数是要使用的文件名。

  4. 要加载模型,让我们创建一个执行三个主要操作的函数:

    def load_model_checkpoint(path):
        checkpoint = torch.load(path)
        model = final_model.Classifier(checkpoint["input"],
                           checkpoint["output"],
                          checkpoint["hidden"])
        model.load_state_dict(checkpoint["state_dict"])
        return model
    model = load_model_checkpoint("checkpoint.pth")
    

    函数接收保存的模型文件路径作为输入。首先加载检查点,接着使用保存在 Python 文件中的网络架构初始化模型。这里,final_model 是 Python 文件的名称,应该已经被导入到新的工作表中,而 Classifier() 是保存在该文件中的类的名称。此模型将具有随机初始化的参数。最后,将检查点中的参数加载到模型中。

    调用时,该函数返回经过训练的模型,现在可以用于进一步训练或进行推断。

PyTorch 用于 C++ 生产环境

根据框架名称,PyTorch 的主要接口是 Python 编程语言。这主要是因为许多用户偏爱这种编程语言,因为它的动态性和用于开发机器学习解决方案的易用性。

然而,在某些情况下,Python 的特性变得不利。这正是为生产环境开发的场景,其他编程语言被证明更有用的情况。例如,C++广泛用于机器/深度学习解决方案的生产目的。

鉴于此,PyTorch 最近提出了一个简单的方法,允许用户享受两个世界的好处。虽然他们继续以 Python 方式编程,但现在有可能将模型序列化为可以在 C++中加载和执行的表示形式,不依赖于 Python。

将 PyTorch 模型转换为 Torch Script 是通过 PyTorch 的 JIT(即时编译)模块完成的。通过将你的模型以及示例输入传递给torch.jit.trace()函数来实现,如下所示:

traced_script = torch.jit.trace(model, example)

这将返回一个脚本模块,可以像常规的 PyTorch 模块一样使用,如下所示:

prediction = traced_script(input)

上述操作将返回通过模型运行输入数据得到的输出。

活动 6:利用你的模型

对于这个活动,保存在前一活动中创建的模型。此外,保存的模型将加载到一个新的笔记本中供使用。我们将把模型转换为一个序列化的表示形式,可以在 C++上执行。让我们看一下以下的情景:哇!大家都对你改进模型的承诺以及最终版本感到非常满意,因此他们要求你保存模型,并将其转换为可以用于客户端构建在线应用程序的格式。

注意

这个活动将使用两个 Jupyter 笔记本。首先,我们将使用与前一活动相同的笔记本保存最终模型。接下来,我们将打开一个新的笔记本,用于加载保存的模型。

  1. 打开你用于前一活动的 Jupyter 笔记本。

  2. 保存一个包含定义你的最佳性能模块架构的类的 Python 文件。确保导入 PyTorch 所需的库和模块。命名为final_model.py

  3. 保存表现最佳的模型。确保保存每层单元的信息以及模型的参数。命名为checkpoint.pth

  4. 打开一个新的 Jupyter 笔记本。

  5. 导入 PyTorch,以及之前创建的 Python 文件。

  6. 创建一个加载模型的函数。

  7. 通过将以下张量输入到你的模型中进行预测。

    torch.tensor([[0.0606, 0.5000, 0.3333, 0.4828, 0.4000, 0.4000, 0.4000, 0.4000, 0.4000, 0.4000, 0.1651, 0.0869, 0.0980, 0.1825, 0.1054, 0.2807, 0.0016, 0.0000, 0.0033, 0.0027, 0.0031, 0.0021]]).float()
    
  8. 使用 JIT 模块转换模型。

  9. 通过输入相同的张量到你的模型的追踪脚本中执行预测。

    注意

    此活动的解决方案可在 202 页找到。

总结

在前几章涵盖了大部分理论知识之后,本章通过一个真实案例研究来巩固我们的知识。其想法是通过实践和动手操作来鼓励学习。

本章首先解释了深度学习在需要精确度的广泛行业中的影响。推动深度学习增长的主要行业之一是银行和金融,这些算法在诸如评估贷款申请、欺诈检测以及评估过去决策以预见未来行为等领域中得到应用,主要是由于这些算法在这些方面能够超越人类的表现。

本章使用了一个来自亚洲银行的真实数据集,目标是预测客户是否会违约。本章从解决方案的开发开始,通过解释定义数据问题的什么、为什么和如何的重要性,以及分析手头的数据来最大程度地利用它。

一旦数据根据问题定义准备好,本章探讨了定义“好”架构的想法。在这个主题中,即使有几个经验法则可以考虑,主要的要点是不要过度思考,构建一个初始架构以获得一些可以用于执行错误分析以改进模型性能的结果。

错误分析的概念包括分析模型在训练集和验证集上的错误率,以确定模型是否在更大比例上受到高偏差或高方差的影响。然后利用模型的这一诊断来修改模型的架构和一些学习参数,从而提高性能。

最后,本章探讨了两种利用表现最佳模型的主要方法。第一种方法是保存模型,然后将其重新加载到任何编码平台以继续训练或执行推断。另一种方法主要用于将模型投入生产,并通过使用 PyTorch 的 JIT 模块来实现,该模块创建了一个可以在 C++上运行的模型的序列化表示。

下一章中,我们将专注于使用深度神经网络解决简单的分类任务。

Logo

DAMO开发者矩阵,由阿里巴巴达摩院和中国互联网协会联合发起,致力于探讨最前沿的技术趋势与应用成果,搭建高质量的交流与分享平台,推动技术创新与产业应用链接,围绕“人工智能与新型计算”构建开放共享的开发者生态。

更多推荐