dl-pt-workshop-merge-0

68 阅读1小时+

PyTorch 人工智能研讨会(一)

原文:The Deep Learning with PyTorch Workshop

协议:CC BY-NC-SA 4.0

零、前言

关于本书

想要掌握最流行的深度学习机器学习库之一吗? 《PyTorch 深度学习研讨会》将帮助您做到这一点,即使您是从头开始,也可以迅速掌握使用 PyTorch 进行深度学习的知识。

毫不奇怪,由于自动驾驶汽车,聊天机器人和声控助手等智能应用使深度学习在过去几年中迅速普及,这使我们的生活更加轻松。 本书将带您进入深度学习领域,在这里您将使用 PyTorch 来了解神经网络架构的复杂性。

《PyTorch 深度学习研讨会》首先介绍了深度学习及其应用。 您将探索 PyTorch 的语法,并学习如何定义网络架构和训练模型。 接下来,您将学习三种主要的神经网络架构-卷积,人工和循环-甚至使用这些网络解决实际数据问题。 后面的章节将向您展示如何创建样式迁移模型以从两个图像中开发一个新图像,最后再带您了解 RNN 如何存储内存以解决关键数据问题。

到本书结尾,您将掌握 PyTorch 的基本概念,工具和库,以开发自己的深度神经网络和智能应用。

受众群体

对于想要使用 PyTorch 创建和训练深度学习模型的人来说,这本深度学习书非常理想。 对 Python 编程语言及其包的深入了解将帮助您更快地掌握本书中涉及的主题。

关于各章

“第 1 章”,“深度学习和 PyTorch 简介”,介绍了深度学习及其应用以及 PyTorch 的主要语法。 本章还显示了如何定义网络架构和训练模型。

“第 2 章”,“神经网络的构建块”,引入了神经网络的概念,并解释了当今的三种主要网络架构:人工神经网络,卷积神经网络和循环神经网络。 对于每种架构,都提供了训练过程和各层的说明。

“第 3 章”,“使用 DNN 的分类问题”,引入了要使用人工神经网络解决的现实数据问题。 探索了数据集的预处理以及定义和训练模型的过程,并通过使用误差分析提高了模型的准确率。

“第 4 章”,“卷积神经网络”,详细介绍了卷积神经网络。 使用现实生活中的数据问题,您将学习如何构建网络架构并对其进行训练,以及如何通过使用数据扩充和批量规范化来改善结果。

“第 5 章”,“样式迁移”,演示了执行样式迁移任务的过程,其中将两个图像作为输入来创建新图像,并使用来自两个输入图像的元素。

“第 6 章”,“使用 RNN 分析数据序列”,更详细地探讨了循环神经网络。 在本章中,使用序列数据作为输入解决了三个流行的数据问题。

约定

文本中的代码字,数据库表名称,文件夹名称,文件名,文件扩展名,路径名,虚拟 URL,用户输入和 Twitter 句柄如下所示:

“从 PyTorch 导入torchoptimize包,以及matplotlib

您在屏幕上看到的单词,例如在菜单或对话框中,也以相同的格式出现。

代码块设置如下:

import torch
import torch.optim as optim
import matplotlib.pyplot as plt

这样显示了新术语和重要单词:“本章还将探讨自然语言处理NLP)的概念。”

代码演示

跨多行的代码行使用反斜杠(\)分割。 执行代码时,Python 将忽略反斜杠,并将下一行中的代码视为当前行的直接延续。

例如:

history = model.fit(X, y, epochs=100, batch_size=5, verbose=1, \
                   validation_split=0.2, shuffle=False)

注释已添加到代码中,以帮助解释特定的逻辑位。 单行注释使用#符号表示,如下所示:

#打印数据集的大小

# Print the sizes of the dataset
print("Number of Examples in the Dataset = ", X.shape[0])
print("Number of Features for each example = ", X.shape[1])

多行注释用三引号引起来,如下所示:

"""
Define a seed for the random number generator to ensure the result will be reproducible
"""
seed = 1
np.random.seed(seed)
random.set_seed(seed)

硬件要求

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

  • 处理器:Intel Core i3 或同等产品
  • 内存:4 GB RAM
  • 储存空间:35 GB 可用空间

软件要求

您还需要预先安装以下软件:

  • 操作系统:Windows 7 SP1 64 位,Windows 8.1 64 位或 Windows 10 64 位,Ubuntu Linux 或最新版本的 macOS
  • 浏览器:Google Chrome/Mozilla Firefox(最新版本)
  • Notepad++/Sublime Text 作为 IDE(可选,因为您可以在浏览器中使用 Jupyter 笔记本练习所有操作)
  • 具有所需库的 Python 3.7(Jupyter,NumPy,pandas,Matplotlib,P 枕头,flask,xlrd 和 scikit-learn)
  • PyTorch 1.3+(最好是 PyTorch 1.4,带有或不带有 CUDA)

设置环境

在详细研究本书之前,我们需要设置特定的软件和工具。 在下一节中,我们将看到如何执行此操作。

在 Windows 和 macOS 上安装 Python

  1. 请访问以下链接下载 Python 3.7

  2. 在页面底部的文件标题下找到表格:

    对于 Windows,单击 64 位的 Windows x86-64 可执行安装程序或 32 位的 Windows x86 可执行安装程序

    对于 macOS,对于 macOS X 10.6 和更高版本,单击 macOS 64 位/32 位安装程序,对于 OS X 10.9 和更高版本,单击 macOS 64 位安装程序

  3. 运行已下载的安装程序。

  4. 您还可以使用 Anaconda 发行版安装 Python。 请遵循此链接中给出的说明以获取更多详细信息

在 Linux 上安装 Python

  1. 打开终端并输入以下命令:

    sudo apt-get install python3.7
    

您还可以使用 Anaconda 发行版安装 Python。 请遵循此链接中给出的说明以获取更多详细信息

安装 Pip

Python 3.7 的安装默认包含pip。 但是,可能是没有安装的情况。 要检查它是否已安装,请在终端或命令提示符下执行以下命令:

pip --version

由于计算机上的pip先前版本已经使用pip命令,因此您可能需要使用pip3命令。

如果您的计算机无法识别pip(或pip3)命令,请按照以下步骤进行安装:

  1. 要安装pip请访问以下链接并下载get-pip.py文件

  2. 然后,在终端或命令提示符上,使用以下命令进行安装:

    python get-pip.py
    

由于您机器上的 Python 先前版本已使用python命令,因此您可能需要使用python3 get-pip.py命令。

安装 PyTorch

要安装带有或不带有 CUDA 的 PyTorch,请按照以下步骤操作:

  1. 请访问以下链接
  2. 本地启动标题下,选择适用于您的选项。 这将为您提供在本地计算机上下载 PyTorch 所需执行的命令。 使用pip作为包下载 PyTorch。
  3. 复制命令并在终端或命令提示符中运行它。

安装库

pip预先安装了 Anaconda。 将 Anaconda 安装到计算机上后,可以使用pip安装所有必需的库,例如,pip install numpy。 或者,您可以使用pip install -r requirements.txt安装所有必需的库。 您可以在这个页面上找到requirements.txt文件。

练习和活动将在 Jupyter 笔记本中执行。 Jupyter 是一个 Python 库,可以通过与其他 Python 库相同的方式进行安装-也就是说,通过pip install jupyter可以安装,但是幸运的是,它已经预装了 Anaconda。 要打开笔记本,只需在终端或命令提示符中运行命令jupyter notebook

打开 Jupyter 笔记本

  1. 打开终端/命令提示符。

  2. 在“终端/命令提示符”中,转到您下载了该书的 GitHub 存储库的目录位置。

  3. 通过键入以下命令来打开 Jupyter 笔记本:

    jupyter notebook
    

    通过执行前面的命令,您将能够通过计算机的默认浏览器使用 Jupyter 笔记本。

访问代码文件

您可以在这个页面中找到本书的完整代码文件。 您还可以使用这个页面上的交互式实验室环境,直接在 Web 浏览器中运行许多活动和练习。

我们已尝试支持所有活动和练习的交互式版本,但对于不提供此支持的实例,我们也建议您进行本地安装。

如果您对安装有任何疑问或疑问,请给我们发送电子邮件至 workshops@packt.com

一、深度学习和 PyTorch 简介

概述

本章介绍了本书的两个主要主题:深度学习和 PyTorch。 在这里,您将能够探索深度学习的一些最受欢迎的应用,了解什么是 PyTorch,并使用 PyTorch 构建单层网络,这将是您将学习应用于现实生活的数据问题的起点。 在本章结束时,您将能够使用 PyTorch 的语法来构建神经网络,这在后续章节中将是必不可少的。

简介

深度学习是机器学习的子集,它专注于使用神经网络来解决复杂的数据问题。 如今,由于软件和硬件的进步使我们能够收集和处理大量数据(我们正在谈论数以亿计的条目),它变得越来越流行。 考虑到深度神经网络需要大量数据才能表现良好,这一点很重要。

深度学习的一些最著名的应用是自动驾驶汽车,流行的聊天机器人以及各种语音激活助手,这些将在本章中进一步说明。

PyTorch 于 2017 年推出,其主要特点是它使用图形处理单元GPU)来使用“张量”处理数据。 这使算法可以高速运行,同时,它为用户提供了灵活性和标准语法,可以为许多数据问题获得最佳结果。 此外,PyTorch 使用动态计算图,使您可以随时随地更改网络。 本书使用 PyTorch 揭开了神经网络神秘面纱,并帮助您了解神经网络架构的复杂性。

为什么选择深度学习?

在本节中,我们将介绍深度学习的重要性及其普及的原因。

深度学习是机器学习的子集,它使用多层神经网络(大型神经网络),受人脑的生物结构启发,其中一层中的神经元接收一些输入数据,对其进行处理, 并将输出发送到下一层。 这些神经网络可以由成千上万个相互连接的节点(神经元)组成,大多数情况下组织在不同的层中,其中一个节点连接到上一层中的多个节点,从那里接收输入数据,还连接到上一层中的几个节点。 下一层,在处理完输出数据后,它将输出数据发送到下一层。

深度学习的普及是由于其准确率。 对于诸如自然语言处理NLP)之类的复杂数据问题,它已达到比其他算法更高的准确率。 深度学习的出色表现能力已经达到了机器可以胜过人类的水平,例如在欺诈检测中。 深度学习模型不仅可以优化流程,而且可以提高其质量。 这意味着在诸如安全性之类的出于安全原因至关重要的革命性领域(例如自动驾驶汽车)的进步。

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

  • 神经网络需要并实际上利用大量标记数据来实现最佳解决方案。 这意味着,要使用该算法创建出色的模型,就需要成千上万甚至上百万个条目,其中包含特征和目标值。 例如,关于猫的图像识别,您拥有的图像越多,该模型能够检测的特征就越多,这使其变得更好。

    注意

    标记数据是指包含一组特征(描述实例的特征)和目标值(要实现的值)的数据; 例如,包含人口统计和财务信息的数据集,其目标特征确定一个人的工资。

    下图显示了在数据量方面深度学习相对于其他算法的表现:

Figure 1.1: Performance of deep learning against other algorithms

图 1.1:相对于其他算法的深度学习表现

如今,由于软件和硬件的进步使我们能够收集和处理这种粒度,这成为可能。

  • 神经网络需要相当大的计算能力才能处理如此大量的数据,而无需花费数周(甚至更长)的时间来进行训练。 由于获得最佳模型的过程是基于反复试验的,因此有必要能够尽可能高效地运行训练过程。

    今天,这可以通过使用 GPU 来实现,它可以将神经网络的训练时间从数周缩短至数小时。

    注意

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

深度学习的应用

深度学习正在革新技术,并且已经在影响我们的生活。 深度学习可应用于各种情况,从医疗和安全(例如欺诈检测)目的到更琐碎的任务,例如为黑白图像着色或实时翻译文本。

深度学习的一些正在开发中或正在使用的应用包括:

  • 自动驾驶汽车:诸如 Google 之类的多家公司一直在致力于开发部分或全部自动驾驶汽车,这些汽车通过使用数字传感器识别周围的物体来学习驾驶。
  • 医学诊断:深度学习通过提高诸如脑和乳腺癌等绝症的诊断准确率,正在对该行业产生影响。 这是通过根据以前有或没有癌症的患者的标记 X 射线对新患者的 X 射线(或其他诊断影像机制)进行分类来完成的。
  • 语音助手:由于各种语音激活的智能助手(例如 Apple 的 Siri,Google Home 和亚马逊的 Alexa)的激增,它可能是当今最受欢迎的应用之一。
  • 自动生成文本:这意味着根据输入的句子生成新文本。 这通常用于电子邮件编写中,其中电子邮件提供者根据已编写的文本向用户建议接下来的几个单词。
  • 广告:在商业世界中,深度学习通过瞄准合适的受众并制作更有效的广告来帮助提高广告系列的投资回报。 这样的一个例子是生成内容,以产生最新的和信息丰富的博客,以帮助吸引当前客户并吸引新客户。
  • 价格预测:对于初学者来说,这是使用机器学习算法可以实现的典型示例。 价格预测包括根据实际数据训练模型。 例如,在房地产领域,这将包括提供具有房地产特征及其最终价格的模型,以便能够仅基于房地产特征来预测未来入场券的价格。

PyTorch 简介

PyTorch 是一个开放源代码库,主要由 Facebook 的人工智能研究小组开发为 Python 版本的 Torch。

注意

Torch 是一个开放源代码,科学的计算框架,支持多种机器学习算法。

PyTorch 于 2017 年 1 月首次向公众发布。它使用 GPU 的功能来加速张量的计算,从而加快了复杂模型的训练时间。

该库具有 C++ 后端,并结合了 Torch 的深度学习框架,与具有许多深度学习功能的本机 Python 库相比,它可以提供更快的计算速度。 前端使用 Python,这有助于使其流行,从而使刚接触该库的数据科学家能够构建复杂的神经网络。 可以将 PyTorch 与其他流行的 Python 包一起使用。

尽管 PyTorch 相当新,但由于它是根据该领域许多专家的反馈开发的,因此迅速获得了普及。 这使得 PyTorch 成为对用户有用的库。

PyTorch 中的 GPU

GPU 最初是为了加速图形渲染中的计算而开发的,尤其是对于视频游戏等。 但是,由于它们能够帮助加快任何领域的计算速度,包括深度学习计算,它们最近变得越来越受欢迎。

有几种平台可以将变量分配给计算机的 GPU,其中计算统一设备架构CUDA)是最常用的平台之一。 CUDA 是 Nvidia 开发的计算平台,由于使用 GPU 来执行计算,因此可以加快计算密集型程序的速度。

在 PyTorch 中,可以通过使用torch.cuda包将变量分配给 CUDA,如以下代码片段所示:

x = torch.Tensor(10).random_(0, 10)
x.to("cuda")

在这里,第一行代码创建了一个张量,该张量填充有随机整数(介于 0 和 10 之间)。 第二行代码将该张量分配给 CUDA,以便所有与该张量有关的计算都由 GPU 而不是 CPU 处理。 要将变量分配回 CPU,请使用以下代码片段:

x.to("cpu")

在 CUDA 中,当解决深度学习数据问题时,优良作法是分配保存网络架构的模型以及输入数据。 这将确保在训练过程中执行的所有计算均由 GPU 处理。

但是,只有在您的计算机具有可用的 GPU 且您已将 CUDA 包安装了 PyTorch 的情况下,才能进行此分配。 要验证您是否能够在 CUDA 中分配变量,请使用以下代码段:

torch.cuda.is_available()

如果前一行代码的输出为True,那么您都准备开始在 CUDA 中分配变量。

注意

要与 CUDA 包一起安装 PyTorch,请访问 PyTorch 的网站,并确保选择包含 CUDA(两个版本)的选项

什么是张量?

与 NumPy 相似,PyTorch 使用张量表示数据。 张量是n尺寸的矩阵状结构,不同之处在于 PyTorch 张量可以在 GPU 上运行(而 NumPy 张量不能),这有助于加速数值计算。 对于张量,尺寸也称为秩。 下图显示了不同尺寸的张量的直观表示:

Figure 1.2: Visual representation of tensors of different dimensions

图 1.2:不同尺寸张量的可视化表示

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

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

类似于 NumPy 数组或任何其他类似矩阵的结构,PyTorch 张量可以具有所需的任意多个尺寸。 可以使用以下代码片段在 PyTorch 中定义一维张量(tensor_1)和二维张量(tensor_2):

tensor_1 = torch.tensor([1,1,0,2])
tensor_2 = torch.tensor([[0,0,2,1,2],[1,0,2,2,0]])

请注意,前面的代码片段中的数字没有含义。 重要的是不同维度的定义,其中用随机数填充。 根据前面的代码段,第一个张量在一个维度上的大小为 4,而第二张量在两个维度中的每个维度的大小为 5,可以通过验证张量变量的shape属性,如下所示:

tensor_1.shape

输出为torch.Size([4])

tensor_2.shape

输出为torch.Size([2], [5])

使用支持 GPU 的计算机时,将进行以下修改以定义张量:

tensor = torch.tensor([1,1,0,2]).cuda

使用 PyTorch 张量创建伪数据非常简单,类似于您在 NumPy 中执行的操作。 例如,torch.randn()返回一个用括号内指定尺寸的随机数填充的张量,而torch.randint()返回一个以整数填充的张量(最小和最大值可以定义)括号内定义的尺寸:

注意

此处显示的代码段使用反斜杠(\)将逻辑划分为多行。 执行代码时,Python 将忽略反斜杠,并将下一行中的代码视为当前行的直接延续。

example_1 = torch.randn(3,3)
example_2 = torch.randint(low=0, high=2, \
                          size=(3,3)).type(torch.FloatTensor)

可以看出,example_1是填充有随机数的二维张量,每个维的大小等于 3,而example_2是填充有 0,1 和 2 的二维张量(high参数是上限),每个尺寸的大小等于 3。

任何填充有整数的张量都必须转换为浮点数,以便我们可以将其馈送到任何 PyTorch 模型。

练习 1.01:使用 PyTorch 创建不同等级的张量

在本练习中,我们将使用 PyTorch 库创建秩为 1、2 和 3 的张量。 执行以下步骤以完成本练习:

注意

对于本章中的练习和活动,您将需要安装 Python 3.7,Jupyter 6.0,Matplotlib 3.1 和 PyTorch 1.3+(最好是 PyTorch 1.4,有或没有 CUDA)(如“前言”中的说明) )。 它们将主要在 Jupyter 笔记本电脑中开发,建议您为不同的作业保留一个单独的笔记本电脑,除非建议不要这样做。

  1. 导入名为torch的 PyTorch 库:

    import torch
    
  2. 创建以下等级的张量:123

    使用01之间的值填充张量。 张量的大小可以根据您的需要进行定义,前提是正确创建了等级:

    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,则可以使用 GPU 语法创建等效张量:

    tensor_1 = torch.tensor([0.1,1,0.9,0.7,0.3]).cuda()
    tensor_2 = torch.tensor([[0,0.2,0.4,0.6], \
                             [1,0.8,0.6,0.4]]).cuda()
    tensor_3 = torch.tensor([[[0.3,0.6],[1,0]], \
                             [[0.3,0.6],[0,1]]]).cuda()
    
  3. 就像使用 NumPy 数组一样,使用shape属性输出每个张量的形状:

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

    考虑到张量每个维度的大小可能会根据您的选择而变化,因此print语句的输出应如下所示:

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

    注意

    要访问此特定部分的源代码,请参考这里

    您也可以通过这里在线运行此示例。 您必须执行整个笔记本才能获得所需的结果。

    要访问此源代码的 GPU 版本,请参考这里。 此版本的源代码无法作为在线交互示例使用,需要通过 GPU 设置在本地运行。

您已经成功创建了不同等级的张量。

在下一节中,我们将讨论使用 PyTorch 的优缺点。

使用 PyTorch 的优点

如今有几个库可用于开发深度学习解决方案,那么为什么要使用 PyTorch? 答案是 PyTorch 是一个动态库,它允许用户极大的灵活性来开发可适应特定数据问题的复杂架构。

PyTorch 已被许多研究人员和人工智能开发人员采用,这使其成为机器学习工程师工具包中的重要工具。

要强调的关键方面如下:

  • 易于使用:就 API 而言,PyTorch 具有简单的接口,可轻松开发和运行模型。 许多早期采用者认为它比 TensorFlow 等其他库更直观。
  • 速度:使用 GPU 使该库的训练速度比其他深度学习库更快。 当必须测试不同的近似值以获得最佳的模型时,这特别有用。 此外,即使其他库也可以选择使用 GPU 加速计算,您也可以在 PyTorch 中通过键入几行简单的代码来实现。
  • 便利性:PyTorch 灵活。 它使用动态计算图,使您可以随时随地更改网络。 由于易于对常规架构进行调整,因此在构建架构时还具有极大的灵活性。
  • 急切执行:PyTorch 也是急切执行。 每行代码都是单独执行的,使您可以实时跟踪模型,并以方便的方式调试模型。
  • 预训练模型:最后,它包含许多易于使用的预训练模型,它们是某些数据问题的一个很好的起点。

使用 PyTorch 的缺点

尽管优点很多,但仍然存在一些要考虑的缺点,在这里进行了说明:

  • 小型社区:与其他库(例如 TensorFlow)相比,该库的适配器社区很小。 但是,它仅对公众开放了三年,今天,它已成为实现深度学习解决方案的最受欢迎的前五个库之一,并且它的社区每天都在增长。
  • 参差不齐的文档:与其他深度学习库相比,该库是一个相当新的文档,因此该文档并不完整。 但是,由于库的特性和功能正在增加,因此文档正在扩展。 此外,随着社区的不断发展,互联网上将提供更多信息。
  • 有关生产准备就绪的问题:尽管有关该库的许多投诉都集中在无法将其部署到生产中,但在发布 1.0 版之后,该库包含了生产能力,可以导出最终模型并在生产环境中使用它们。

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()

在前面的代码中,创建了两个张量。 我们在这里使用require_grad参数来告诉 PyTorch 计算该张量的梯度。 但是,在构建神经网络时,不需要此参数。

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

通过打印ab的梯度,可以确认仅对第一个变量(a)计算梯度,而对第二个(b)计算梯度,则会引发错误:

print(a.grad.data)

输出为Tensor([12., 14.])

print(b.grad.data)

输出如下:

AttributeError: 'NoneType' object has no attribute 'data'

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 = nn.MSELoss()

首先,导入模块。 然后,定义模型架构。 input_units表示输入数据包含的特征数量,hidden_​​units表示隐藏层的节点数量, output_units表示输出层的节点数量。

从前面的代码中可以看出,网络的架构包含一个隐藏层,其后是 ReLU 激活函数和一个输出层,然后是一个 Sigmoid 激活函数,从而使其成为两层网络。

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

注意

本书将介绍针对不同数据问题的最流行的损失函数。

要创建不遵循现有模块顺序的模型,请使用自定义nn模块。 我们将在本书后面介绍这些内容。

练习 1.02:定义单层架构

在本练习中,我们将使用 PyTorch 的nn模块为单层神经网络定义模型,并定义损失函数以评估模型。 这将是起点,以便您能够构建更复杂的网络架构来解决实际数据问题。 执行以下步骤以完成本练习:

  1. 从 PyTorch 导入torchnn模块:

    import torch
    import torch.nn as nn
    

    注意

    本练习中使用torch.manual_seed(0),以确保在本书的 GitHub 存储库中获得的结果具有可重复性。 但是,在出于其他目的训练网络时,不得定义种子。

    要了解有关 PyTorch 中种子的更多信息,请访问这里

  2. 定义输入数据的特征数为10input_units),输出层的节点数为1output_units)。

    input_units = 10
    output_units = 1
    
  3. 使用Sequential()容器,定义单层网络架构并将其存储在名为model的变量中。 确保定义一层,然后定义Sigmoid激活函数:

    model = nn.Sequential(nn.Linear(input_units, output_units), \
                          nn.Sigmoid())
    
  4. 打印模型以验证是否已相应创建:

    print(model)
    

    前面的代码段将显示以下输出:

    Sequential(
      (0): Linear(in_features=10, out_features=1, bias=True)
      (1): Sigmoid()
    )
    
  5. 将损失函数定义为 MSE 并将其存储在名为loss_funct的变量中:

    loss_funct = nn.MSELoss()
    
  6. 打印损失函数以验证是否已相应创建:

    print(loss_funct)
    

    运行前面的代码片段将显示以下输出:

    MSELoss()
    

    注意

    要访问此特定部分的源代码,请参考这里

    您也可以通过这里在线运行此示例。 您必须执行整个笔记本才能获得所需的结果。

您已经成功定义了单层网络架构。

PyTorch optim

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 the model to perform a prediction
    y_pred = model(x)
    # Calculation of loss function based on y_pred and y
    loss = loss_funct(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.03:训练神经网络

注意

对于本练习,请使用与上一个练习相同的 Jupyter 笔记本(“练习 1.02”,“定义单层架构”)。

在本练习中,我们将使用 PyTorch 的optim包,学习如何从上一练习中训练单层网络。 考虑到我们将使用虚拟数据作为输入,训练网络不会解决数据问题,但是将其用于学习目的。 执行以下步骤以完成本练习:

  1. 导入torch、PyTorch 的optim包和matplotlib

    import torch
    import torch.optim as optim
    import matplotlib.pyplot as plt
    
  2. 创建随机值的虚拟输入数据(x)和仅包含零和一的虚拟目标数据(y)。 张量x的大小应为(20, 10),而y的大小应为(20, 1)

    x = torch.randn(20,10)
    y = torch.randint(0,2, (20,1)).type(torch.FloatTensor)
    
  3. 将优化算法定义为 Adam 优化器。 将学习率设置为等于 0.01:

    optimizer = optim.Adam(model.parameters(), lr=0.01)
    
  4. 运行优化 20 次迭代,将损失值保存在变量中。 每五次迭代,输出损失值:

    losses = []
    for i in range(20):
        y_pred = model(x)
        loss = loss_funct(y_pred, y)
        losses.append(loss.item())
        optimizer.zero_grad()
        loss.backward()
        optimizer.step()
        if i%5 == 0:
            print(i, loss.item())
    

    输出应如下所示:

    0 0.25244325399398804
    5 0.23448510468006134
    10 0.21932794153690338
    15 0.20741790533065796
    

    前面的输出显示周期号以及损失函数的值,可以看出,该函数正在减小。 这意味着训练过程使损失函数最小化,这意味着模型能够理解输入特征和目标之间的关系。

  5. 绘制线图以显示每个周期的损失函数的值:

    plt.plot(range(0,20), losses)
    plt.show()
    

    输出应如下所示:

Figure 1.3: Loss function being minimized

图 1.3:损失函数被最小化

如您所见,损失函数已被最小化。

注意

要访问此特定部分的源代码,请参考这里

您也可以通过这里在线运行此示例。 您必须执行整个笔记本才能获得所需的结果。

这样,您就成功地训练了单层神经网络。

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

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

您是萨默维尔市市长的助理,人事部门要求您建立一个模型,该模型能够根据人们对本市服务的满意度来预测人们对当前政府是否满意。 为此,您已经决定使用 PyTorch 并根据之前的调查结果构建一个单层神经网络。 执行以下步骤以完成此活动:

注意

用于此活动的数据集来自 UC Irvine 机器学习存储库,该存储库可使用以下 URL 从数据文件夹超链接中下载也可以在本书的 GitHub 存储库中找到它

  1. 导入所需的库,包括用于读取 CSV 文件的 Pandas。

  2. 读取包含数据集的 CSV 文件。

    注意

    建议使用 Pandas 的read_csv函数加载 CSV 文件。 要了解有关此函数的更多信息,请访问这里

  3. 将输入特征与目标分开。 请注意,目标位于 CSV 文件的第一列中。 接下来,将值转换为张量,确保将值转换为浮点数。

    注意

    要切片 pandas DataFrame,请使用 pandas 的iloc方法。 要了解有关此方法的更多信息,请访问这里

  4. 定义模型的架构,并将其存储在名为model的变量中。 记住要创建一个单层模型。

  5. 定义要使用的损失函数。 在这种情况下,请使用 MSE 损失函数。

  6. 定义模型的优化器。 在这种情况下,请使用 Adam 优化器,并将学习率设为0.01

  7. 对 100 次迭代运行优化,保存每次迭代的损失值。 每 10 次迭代打印一次损失值。

  8. 绘制线图以显示每个迭代步骤的损失值。

    注意

    有关此活动的解决方案,请参阅第 236 页。

总结

深度学习是机器学习的一个子集,其灵感来自人脑的生物结构。 它使用深度神经网络通过使用大量数据来解决复杂的数据问题。 尽管该理论是数十年前开发的,但由于硬件和软件的进步使我们能够收集和处理数百万条数据,因此该理论最近得到了使用。

随着深度学习解决方案的普及,已经开发了许多深度学习库。 其中,最新的一种是 PyTorch。 PyTorch 使用 C++ 后端,这有助于加快计算速度,同时具有 Python 前端,以使该库易于使用。

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

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

在下一章中,我们将学习神经网络的构建块。 我们将介绍三种类型的学习过程以及三种最常见的神经网络类型。 对于每个神经网络,我们将学习网络架构的结构以及训练过程的工作方式。 最后,我们将了解数据准备的重要性并解决回归数据问题。

二、神经网络的构建块

概述

本章介绍了神经网络的主要组成部分,并解释了当今的三种主要神经网络架构。 此外,它解释了训练任何人工智能模型之前数据准备的重要性,并最终解释了解决回归数据问题的过程。 在本章的最后,您将牢固地掌握不同网络架构及其不同应用的学习过程。

简介

在上一章中,已经解释了为什么深度学习如今变得如此流行,并且 PyTorch 被介绍为开发深度学习解决方案的最受欢迎的库之一。 尽管已解释了使用 PyTorch 构建神经网络的主要语法,但在本章中,我们将进一步探讨神经网络的概念。

尽管神经网络理论是在几十年前发展起来的,但是自从感知器概念演变成神经网络理论以来,最近就已经创建了不同的架构来解决不同的数据问题。 这部分是由于在现实生活中的数据问题(例如文本,音频和图像)中可以找到不同的数据格式。

本章的目的是深入探讨神经网络及其主要优点和缺点的主题,以便您了解何时以及如何使用它们。 然后,我们将解释最流行的神经网络架构的构建块:人工神经网络ANN),卷积神经网络CNN)和循环神经网络RNN)。

接下来,将通过解决现实生活中的回归问题来说明建立有效模型的过程。 这包括准备要馈送到神经网络的数据(也称为数据预处理),定义要使用的神经网络架构以及评估模型的表现,目的是确定如何对其进行改进以实现最佳表现。 最佳解决方案。

上述过程将使用将在本章中讨论的一种神经网络架构来完成,同时考虑到每个数据问题的解决方案应使用对所讨论的数据类型表现最佳的架构进行。 其他架构将在后续章节中使用,以解决更复杂的数据问题,这些问题涉及使用图像和文本序列作为输入数据。

注意

本章中提供的所有代码都可以在以下位置找到

神经网络介绍

神经网络从训练数据中学习,而不是通过遵循一组规则进行编程来解决特定任务。 此学习过程可以遵循以下方法之一:

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

  • 无监督学习:与前面的方法相反,无监督学习包括使用未标记数据(意味着没有目标值)训练模型。 这样做的目的是为了更好地理解输入数据。 通常,网络获取输入数据,对其进行编码,然后从编码版本中重建内容,理想情况下保留相关信息。 例如,给定一个段落,神经网络可以映射单词,然后建议哪个单词对该段落最重要或最具描述性。 然后可以将它们用作标签。

  • 强化学习:这种方法包括从输入数据中学习,其主要目标是从长远来看最大化奖励函数。 这是通过从传入的数据中学习来实现的,而不是通过对静态数据进行训练(如在监督式学习中)来实现的。 因此,决策不是基于即时奖励,而是基于整个学习过程中累积的奖励。 这样的一个示例是一个模型,该模型将资源分配给不同的任务,其目的是最大程度地减少使总体表现变慢的瓶颈。

    注意

    从我们在这里提到的学习方法中,最常用的一种是有监督的学习,这将主要在后续部分中使用。 这意味着本章中的所有练习,活动和示例都将使用标记的数据集作为输入数据。

什么是神经网络?

正如我们之前所讨论的,神经网络是一种机器学习算法,它以人脑的解剖结构为模型,并使用数学方程式从训练数据得出的观察结果中学习模式。

但是,要真正理解神经网络通常遵循的训练过程背后的逻辑,重要的是要了解感知器的概念。

感知器由弗兰克·罗森布拉特(Frank Rosenblatt)在 1950 年代开发,是一种人造神经元,它需要多个输入并产生二进制输出,类似于人脑中的神经元。 然后,这成为后续感知器(神经元)的输入。 感知器是神经网络的基本组成部分(就像神经元是人脑的组成部分一样):

Figure 2.1: Diagram of a perceptron

图 2.1:感知器图

在这里,X1X2X3X4代表感知器的不同输入,并且这些可能是任何数字。 圆是感知器,在该处处理输入以达到输出。

Rosenblatt 还介绍了权重的概念(w1w2,...,wn),这些数字表示每个输入的重要性。 输出可以是 0 或 1,并且取决于输入的加权总和是高于还是低于给定阈值(由开发人员设置的数值限制或由数据问题的约束设置),该阈值可以设置为感知器的参数,如下所示:

Figure 2.2: Equation for the output of perceptrons

图 2.2:感知器输出方程

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

以下练习不需要任何编程。 相反,它由简单的计算组成,可帮助您理解感知器的概念。 要执行这些计算,请考虑以下情形。

下个星期五在您的镇上有一个音乐节,但是您生病了,想决定是否要去(0 表示您不参加,1 表示您要参加)。 您的决定取决于三个因素:

  • 天气会好吗? (X1
  • 你要和谁一起去? (X2
  • 音乐是您喜欢的吗? (X3

对于前面的因素,如果问题的答案为是,则将使用 1,如果答案为否,则将使用 0。 此外,由于您病得很重,因此与天气相关的因素非常重要,因此您决定赋予该因素比其他两个因素大两倍的权重。 因此,您决定因素的权重将为 4(w1),2(w2)和 2(w3)。 现在,考虑阈值 5:

  1. 考虑到下周五的天气不好,但是您要和某人一起去,并且您喜欢音乐节上的音乐,请根据提供的信息来计算感知器的输出:

Figure 2.3: Output of the perceptron

图 2.3:感知器的输出

考虑到输出小于阈值,最终结果将等于 0,这意味着您不应该参加音乐节以避免生病的风险。

您已经成功执行了感知器的计算,这是了解神经网络内部学习过程的起点。

多层感知器

考虑到我们在上一节中学到的知识,多层网络的概念由堆叠在一起的多个感知器(也称为节点或神经元)组成的网络组成,如下所示:

Figure 2.4: Diagram of a multi-layer perceptron

图 2.4:多层感知器图

注意

在神经网络中引用层的常规方法如下:

第一层是输入层,最后一层是输出层,中间的所有层都是隐藏层。

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

请注意,感知器的第一层通过对输入进行加权来处理简单的决策过程,而下一层可以根据前一层的输出来处理更复杂和抽象的决策,从而实现最新的表现。 复杂数据问题的深度神经网络(使用多层的网络)。

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

神经网络的学习过程

一般而言,神经网络由多个神经元组成,其中每个神经元都会计算线性函数以及激活函数,以根据某些输入得出输出(激活函数旨在打破线性关系,稍后在本章中有更详细的说明)。 该输出与权重相关联,该权重代表其重要性级别,并将在下一层中用于计算。

而且,这些计算是在整个网络架构中进行的,直到达到最终输出为止。 与地面实况相比,此输出用于确定网络的表现,然后将其用于调整网络的不同参数以重新开始计算过程。

考虑到这一点,神经网络的训练过程可以看作是迭代过程,该过程在网络的各个层中前进和后退以达到最佳结果,如下图所示(损失函数将在后面介绍) 在这一章当中):

Figure 2.5: Diagram of the learning process of a neural network

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

正向传播

这是从左到右遍历网络架构的过程,同时使用输入数据执行计算以得出可以与基本事实进行比较的预测。 这意味着网络中的每个神经元都会根据与之关联的权重和偏差来转换输入数据(初始数据或从上一层接收的数据),并将输出发送到下一层,直到达到最后一层并做出预测。

注意

在神经网络中,偏差是帮助移动每个神经元的激活函数的数值,以避免可能影响训练过程的零值。 它们在神经网络训练中的作用将在本章后面解释。

在每个神经元中执行的计算都包括一个线性函数,该函数会将输入数据乘以某个权重再加上一个偏差,然后将其传递给激活函数。 激活函数的主要目的是打破模型的线性,这是至关重要的,因为考虑到使用神经网络解决的大多数实际数据问题不是由一条线定义,而是由一个复杂的函数定义。 这些公式如下:

Figure 2.6: Calculations performed by each neuron

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

在这里,如前所述,X是指输入数据,W是确定输入数据重要程度的权重,b是偏置值和 sigma(σ)表示应用于线性函数的激活函数。

激活函数的目的是将非线性引入模型。 有不同的激活函数可供选择,当今最常用的激活函数列表如下:

  • Sigmoid:这是 Sigmoid,它基本上将值转换为 0 到 1 之间的简单概率,其中通过 Sigmoid 函数获得的大多数输出​​将接近 0 和 1 的极限。

Figure 2.7: Sigmoid activation function

图 2.7:Sigmoid 激活函数

下图显示了 Sigmoid 激活函数的图形表示:

Figure 2.8: Graphical representation of the sigmoid activation function

图 2.8:Sigmoid 激活函数的图形表示

  • Softmax:类似于 Sigmoid 函数,它计算n个事件的事件概率分布,这意味着其输出不是二进制的。 简而言之,此函数将计算输出与其他类别相比是目标类别之一的概率:

Figure 2.9: Softmax activation function

图 2.9:Softmax 激活函数

考虑到其输出是概率,因此通常在分类网络的输出层中找到此激活函数。

  • Tanh:此函数表示双曲正弦和双曲余弦之间的关系,结果介于 -1 和 1 之间。此激活函数的主要优点是可以更轻松地处理负值:

Figure 2.10: Tanh activation function

图 2.10:Tanh 激活函数

下图显示了 tanh 激活函数的图形表示:

Figure 2.11: Graphical representation of the tanh activation function

图 2.11:tanh 激活函数的图形表示

  • 整流线性函数(ReLU):如果线性函数的输出大于 0,则这基本上激活了一个节点; 否则,其输出将为 0。如果线性函数的输出大于 0,则此激活函数的结果将是其作为输入接收的原始数字:

Figure 2.12: ReLU activation function

图 2.12:ReLU 激活函数

按照惯例,此激活函数用于所有隐藏层。 我们将在本章的后续部分中了解有关隐藏层的更多信息。 下图显示了 ReLU 激活函数的图形表示:

Figure 2.13: Graphical representation of the ReLU activation function

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

损失函数的计算

一旦正向传播完成,训练过程的下一步就是计算损失函数,以通过比较预测相对于真实情况值的好坏来估计模型的误差。 考虑到这一点,要达到的理想值为 0,这意味着两个值之间没有差异。

这意味着训练过程的每次迭代的目标是通过更改在正向传播过程中用于执行计算的参数(权重和偏差)来最小化损失函数。

同样,有多种损失函数可供选择。 但是,用于回归和分类任务的最常用损失函数如下:

  • 均方误差(MSE):MSE 函数广泛用于衡量回归模型的表现,它计算真实情况值与预测值之间的距离之和:

Figure 2.14: MSE loss function

图 2.14:MSE 损失函数

此处,n是指样本数,y[i]是基本真值,y_hat[i]是预测值。

  • 交叉熵/多类交叉熵:此函数通常用于二进制或多类分类模型。 它测量两个概率分布之间的差异; 较大的损失函数将表示较大的差异。 因此,这里的目标是使损失函数最小化:

Figure 2.15: Cross-entropy loss function

图 2.15:交叉熵损失函数

同样,n是指样本数。 y[i]y_hat[i]分别是基本事实和预测值。

反向传播

训练过程的最后一步包括在网络架构中从右向左移动以计算损失函数相对于每一层的权重和偏差的偏导数(也称为梯度),以便更新这些参数(权重和偏差),以便在下一个迭代步骤中,损失函数较低。

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

注意

局部最小值是指函数域的一部分内的最小值。 另一方面,全局最小值是指函数整个域的最小值。

Figure 2.16: Loss function optimization through the iteration steps  in a two-dimensional space

图 2.16:二维空间中迭代步骤的损失函数优化

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

但是,重要的是要提到损失函数并不总是像前面的函数那样平滑,这可能会带来在优化过程中达到局部最小值的风险。

此过程也称为优化,并且有不同的算法在实现相同目标的方法上有所不同。 接下来将解释最常用的优化算法。

梯度下降

梯度下降是数据科学家中使用最广泛的优化算法,它是许多其他优化算法的基础。 计算完每个神经元的梯度后,权重和偏差会沿梯度的相反方向更新,应将其乘以学习率(用于控制每次优化中所采取步骤的大小),例如以下等式。

学习率在训练过程中至关重要,因为它防止权重和过冲/下冲偏差的更新,这可能会阻止模型分别收敛或延迟训练过程。

梯度下降算法中权重和偏差的优化如下:

Figure 2.17: Optimization of parameters in the gradient descent algorithm

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

此处,α是指学习率,dw / db表示给定神经元中权重或偏差的梯度。 从权重或偏差的原始值中减去两个值的乘积,以惩罚较高的值,这有助于计算较大的损失函数。

梯度下降算法的一种改进版本称为随机梯度下降,它基本上遵循相同的过程,区别在于它以随机批量而不是一个块的形式获取输入数据,从而缩短了训练时间,同时达到了出色的表现。 此外,此方法允许使用较大的数据集,因为通过将小批数据集用作输入,我们不再受计算资源的限制。

优缺点

以下是对神经网络的优缺点的解释。

优势

在最近几年中,神经网络变得越来越流行,其原因有四个:

  • 数据:神经网络以利用大量数据的能力而广为人知,并且由于硬件和软件的进步,现在可以收集和存储海量数据库。 随着更多数据输入到神经网络中,这使神经网络能够显示其真正的潜力。
  • 复杂数据问题:如前所述,神经网络非常适合解决其他机器学习算法无法解决的复杂数据问题。 这主要是由于它们具有处理大型数据集和发现复杂模式的能力。
  • 计算能力:技术的进步也增加了当今可用的计算能力,这对于训练使用数百万条数据的神经网络模型至关重要。
  • 学术研究:由于前面的三点,有关此主题的学术研究在互联网上得以广泛传播,这不仅促进了每天新研究的浸入,而且还有助于保持算法和硬件 /最新的软件要求。

劣势

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

尽管人们认为神经网络的表现优于几乎所有机器学习算法,但也必须考虑它们的缺点,以便您可以权衡最重要的数据问题。 让我们现在通过它们:

  • 黑盒:这是神经网络最常见的缺点之一。 从根本上讲,尚不清楚神经网络如何以及为什么达到特定输出。 例如,当神经网络错误地将猫的图片预测为狗时,就不可能知道错误的原因是什么。

  • 数据要求:为获得最佳结果而需要的大量数据可能是同一个优点和缺点。 神经网络比传统的机器学习算法需要更多的数据,这可能是在某些数据问题和其他算法之间进行选择的主要原因。 当监督手头的任务时,这将成为一个更大的问题,这意味着需要对数据进行标记。

  • 训练时间:由于上述缺点,对大量数据的需求也使训练过程比传统的机器学习算法持续时间更长,在某些情况下,这是不可行的。 使用 GPU 可以减少训练时间,从而加快计算速度。

  • 计算上非常昂贵:同样,神经网络的训练过程在计算上也很昂贵。 虽然一个神经网络可能需要花费数周的时间才能收敛,但其他机器学习算法却可能需要数小时或数分钟才能得到训练。 所需的计算资源量取决于手头的数据量以及网络的复杂性。 更深的神经网络需要更长的时间来训练。

    注意

    有各种各样的神经网络架构。 本章将解释三种最常用的方法,并在后续各章中对它们的实际实现进行说明。 但是,如果您想了解其他架构,请访问这里

人工神经网络简介

人工神经网络人工神经网络)也称为多层感知器,是多个感知器的集合。 感知器之间的连接通过层发生。 一层可以具有所需数量的感知器,并且它们都与先前和后续层中的所有其他感知器相连。

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

人工神经网络通常由三个主要元素组成,这些元素已在前面进行了解释,也可以在下图中看到:

  1. 输入层:这是网络的第一层,通常位于网络图形表示中最左侧。 在执行任何计算之前,它会接收输入数据,并完成第一组计算。 这是发现最普通模式的地方。

    对于监督学习问题,输入数据包含一对特征和目标。 网络的工作是发现特征与目标之间的相关性或依赖性。

  2. 隐藏层:接下来,可以找到隐藏层。 神经网络可以具有许多隐藏层,这意味着在输入层和输出层之间可以有任意数量的层。 它具有的层越多,可以解决的数据问题就越复杂,但训练时间也将更长。 还有一些神经网络架构根本不包含隐藏层,单层网络就是这种情况。

    在每一层中,都会根据从上一层作为输入接收的信息执行计算,然后将其用于输出将成为下一层输入的值。

  3. 输出层。这是网络的最后一层,位于网络图示的最右边。它接收网络中所有神经元处理数据后的数据,以做出最终预测。

    输出层可以具有一个或多个神经元。 前者是指解决方案是二进制的模型,其形式为 0 或 1s。 另一方面,后一种情况由模型组成,这些模型输出实例属于每个可能的类标签(目标变量具有的可能值)的概率,这意味着该层将具有与类标签一样多的神经元:

Figure 2.18: Architecture of a neural network with two hidden layers

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

卷积神经网络简介

卷积神经网络CNN)主要用于计算机视觉领域,在最近几十年中,机器达到的准确率水平超过了人类的能力。

CNN 创建的模型使用神经元的子组来识别图像的不同方面。 这些组应该能够相互通信,以便它们可以一起形成完整的图像。

考虑到这一点,CNN 的架构中的层划分了它们的识别任务。 第一层专注于琐碎的模式,而网络末端的层则使用该信息来揭示更复杂的模式。

例如,当识别图片中的人脸时,前几层专注于寻找将一个特征与另一个特征分开的边缘。 接下来,后续层强调面部的某些特征,例如鼻子。 最后,最后两层使用此信息将人的整个面孔放在一起。

当遇到某些特征时激活一组神经元的想法是通过使用过滤器(核)来实现的,过滤器(核)是 CNN 架构的主要组成部分之一。 但是,它们不是架构中存在的唯一元素,这就是为什么在此将对 CNN 的所有组件进行简要说明的原因:

注意

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

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

    该计算对过滤器形状相同的图像矩阵进行细分,然后对这些值进行乘法运算。 然后,将乘积之和设置为该图像部分的输出,如下图所示:

    Figure 2.19: Convolution operation between the image and filter

    图 2.19:图像和过滤器之间的卷积运算

    在这里,左边的矩阵是输入数据,中间的矩阵是过滤器,右边的矩阵是计算的输出。 在方框中突出显示的值所发生的计算可以在这里看到:

    Figure 2.20: Convolution of the first section of the image

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

    对图像的所有子部分都进行了卷积乘法。 下图显示了同一示例的另一个卷积步骤:

    Figure 2.21: A further step in the convolution operation

    图 2.21:卷积运算的进一步步骤

    卷积层的一个重要概念是它们是不变的,因此每个过滤器都将具有特定的函数,该函数在训练过程中不会发生变化。 例如,负责检测耳朵的过滤器将仅在整个训练过程中专门用于该函数。

    此外,考虑到所使用的过滤器,考虑到每个 CNN 都将专注于识别图像的特定特征或一组特征,因此 CNN 通常将具有多个卷积层。 通常,在两个卷积层之间有一个池化层。

  2. 池化层:尽管卷积层能够从图像中提取相关特征,但是当分析复杂的几何形状时它们的结果可能会变得非常巨大,这将使训练过程在计算能力方面成为不可能,因此发明了池化层。

    这些层不仅实现了减少卷积层输出的目标,而且还消除了已提取特征中存在的任何噪声,最终最终有助于提高模型的准确率。

    可以应用两种主要类型的池化层,其背后的想法是检测在图像中表现出较强影响的区域,以便可以忽略其他区域。

    最大池化:此操作包括将给定大小的矩阵的一个子部分作为该最大子集操作的输出,并采用该子部分中的最大数目:

    Figure 2.22: A max pooling operation

    图 2.22:最大池化操作

    在上图中,通过使用3 x 3最大池化过滤器,可以实现右侧的结果。 在此,黄色部分(左上角)的最大数量为 4,而橙色部分(右上角)的最大数量为 5。

    平均池化:类似地,平均池操作采用矩阵的各个子部分,并将符合规则的数字作为输出,在这种情况下,该数字是所讨论的子部分中所有数字的平均值:

    Figure 2.23: An average pooling operation

    图 2.23:平均池化操作

    在这里,使用3 x 3过滤器,我们得到 2.9,这是黄色部分(左上角)中所有数字的平均值,而 3.2 是橙色部分(右上角)中所有数字的平均值。 。

  3. 全连接层:最后,考虑到如果网络仅能够检测一组特征而不具有将其分类为类标签的能力,则该网络将无用,最后 CNN 使用全连接层采取前一层检测到的特征(称为特征映射),并输出属于类别标签的那组特征的概率,用于进行最终预测。

    像人工神经网络一样,全连接层使用感知器根据给定的输入来计算输出。 此外,至关重要的是要提到 CNN 在架构的末尾通常具有不止一个全连接层。

通过组合所有这些概念,可以获得 CNN 的常规架构。 每个类型可以有任意数量的层,每个卷积层可以具有任意数量的过滤器(每个过滤器用于特定任务)。 此外,池化层应具有与上一个卷积层相同数量的过滤器,如下图所示:

Figure 2.24: Diagram of the CNN architecture

图 2.24:CNN 架构图

循环神经网络简介

前述神经网络(ANN 和 CNN)的主要局限性在于,它们只能通过考虑当前事件(正在处理的输入)来学习,而不会考虑先前或后续事件,因此考虑到我们人类并不这样思考,这是不便的。 例如,当阅读一本书时,通过考虑上一段或更多段中的上下文,您可以更好地理解每个句子。

因此,并考虑到神经网络旨在优化传统上由人类完成的多个过程这一事实,至关重要的是考虑一个能够考虑输入和输出序列的网络,因此循环神经网络RNN)。 它们是一种强大的神经网络,可以通过使用内部存储器找到复杂数据问题的解决方案。

简而言之,这些网络中包含循环,即使在处理后续的一组信息时,这些循环也可以使信息在其内存中保留更长的时间。 这意味着 RNN 中的感知器不仅将输出传递到下一个感知器,而且还保留了一些信息给自己,这对于分析下一信息很有用。 这种内存保留功能使他们可以非常准确地预测下一步。

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

RNN 允许以输入序列,输出序列或什至同时以两种形式处理数据序列,如下图所示:

Figure 2.25: Sequence of data handled by RNNs

图 2.25:RNN 处理的数据序列

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

从左至右,上述各图可以解释如下:

  1. 一个不需要解决 RNN 的典型模型。 它具有固定的输入和固定的输出。 例如,这可以指图像分类。
  2. 该模型接受输入并产生一系列输出。 例如,以接收图像作为输入的模型; 输出应为图像标题。
  3. 与前面的模型相反,该模型采用一系列输入并产生单个结果。 在情感分析问题上可以看到这种类型的架构,其中输入是要分析的句子,输出是句子后面的预测情感。
  4. 最后的两个模型采用一系列输入,并返回一系列输出,不同之处在于第一个模型同时分析输入并生成输出。 例如,当视频的每个帧都被单独标记时。 另一方面,第二个多对多模型分析整个输入集,以生成输出集。 语言翻译就是一个例子,在进行实际翻译之前,需要先理解一种语言的整个句子。

数据准备

当然,在收集数据之后,开发任何深度学习模型的第一步都应该是准备数据。 如果我们希望了解手边的数据以正确地概述项目范围,那么这一点至关重要。

许多数据科学家没有这样做,这导致模型的表现很差,甚至导致模型无用,因为他们没有从一开始就回答数据问题。

数据准备过程可以分为三个主要任务:

  1. 了解数据并处理任何潜在问题
  2. 重新缩放特征,以确保不会因错误引入偏差
  3. 拆分数据以能够准确地衡量表现

所有这三个任务将在下一节中进一步说明。

注意

在应用任何机器学习算法时,考虑到它们指的是事先准备数据所需的技术,我们前面解释的所有任务几乎都是相同的。

处理混乱数据

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

EDA 流程很有用,因为它有助于开发人员发现对于定义操作过程至关重要的信息。 此处说明此信息:

  1. 数据量:这既指实例数,也指特征数。 前者对于确定是否有必要甚至有可能使用神经网络甚至是深度神经网络解决数据问题至关重要,因为考虑到此类模型需要大量数据才能达到较高的准确率。 另一方面,后者对于确定是否提前开发某些特征选择方法以减少特征数量,简化模型并消除任何冗余信息是否有用是有用的。

  2. 目标特征:对于受监督的模型,需要标记数据。 考虑到这一点,选择目标特征(我们希望通过构建模型来实现的目标)以评估特征是否具有许多缺失值或离群值非常重要。 此外,这有助于确定开发目标,该目标应与可用数据一致。

  3. 噪音数据/异常值:噪音数据是指明显不正确的值,例如 200 岁的人。 另一方面,离群值所指的值虽然可能是正确的,但与平均值相差甚远,例如,一个 10 岁的大学生。

    没有检测异常值的确切科学方法,但是有一些通常被接受的方法。 假设数据集呈正态分布,那么最受欢迎的数据集之一就是确定离平均值约 3-6 标准差的任何值。

    识别异常值的一种同样有效的方法是选择第 99 个百分点和第 1 个百分点的值。

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

  4. 缺失值:类似于上述内容,考虑到不同的模型会对这些值做出不同的假设,因此具有许多缺失值的数据集会给模型带来偏差。 同样,当缺失值占特征值的 5% 以上时,应再次使用均值或回归插补技术,通过消除或替换它们来处理它们。

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

    此外,在许多研究开发中,对同一数据测试了几种算法,以确定哪种算法表现更好,并且其中某些算法不能容忍使用定性数据,就像神经网络一样。 这证明了转换或编码它们以便能够向所有算法提供相同数据的重要性。

练习 2.02:处理混乱数据

注意

本章中的所有练习都将使用从 UC Irvine 机器学习存储库获得的电器能耗预测数据集完成,该数据集可从这里下载。 也可以在本书的 GitHub 存储库中找到它

电器能耗预测数据集包含 4.5 个月的数据,涉及低能耗建筑物中不同房间的温度和湿度测量,目的是预测某些电器使用的能耗。

在本练习中,我们将使用pandas(这是一个受欢迎的 Python 包)来探索手头的数据并学习如何检测缺失值,离群值和定性值。 执行以下步骤以完成本练习:

注意

对于本章中的练习和活动,您将需要在本地计算机上安装 Python 3.7,Jupyter 6.0,NumPy 1.17 和 Pandas 0.25。

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

  2. 导入 Pandas 库:

    import pandas as pd
    
  3. 使用 Pandas 读取 CSV 文件,其中包含我们从 UC Irvine 机器学习存储库站点下载的数据集。

    接下来,删除名为date的列,因为我们不想在以下练习中考虑它:

    data = pd.read_csv("energydata_complete.csv")
    data = data.drop(columns=["date"])
    

    最后,打印DataFrame的头部:

    data.head()
    

    输出应如下所示:

    Figure 2.26: Top instances of the Appliances energy prediction dataset

    图 2.26:设备能源预测数据集的主要实例

  4. 检查数据集中的分类特征:

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

    第一行生成数据集中所有列的列表。 接下来,包含数值的列也存储在变量中。 最后,通过从整个列列表中减去数字列,可以获得非数字列。

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

  5. 使用 Python 的isnull()sum()函数来查找数据集各列中是否缺少任何值:

    data.isnull().sum()
    

    此命令计算每列中空值的数量。 对于正在使用的数据集,不应缺少任何值,如在此处所示:

    Figure 2.27: Missing values count

    图 2.27:缺失值计数

  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
    outliers
    

    前面的代码段循环遍历数据集中的列,以评估每个异常值的存在。 它会继续计算最小和最大阈值,以便可以计算超出阈值之间范围的实例数。

    最后,它计算离群值的百分比(即离群值除以实例总数),以便输出显示每列此百分比的字典。

    通过打印结果字典(离群值),可以显示数据集中所有特征(列)的列表以及离群值的百分比。 根据结果​​,可以得出结论,无需考虑异常值,因为考虑到它们仅占数据的 5%,如以下屏幕截图所示:

    注意

    请注意,只要将变量放在笔记本中单元格的末尾,Jupyter 笔记本便可以打印变量的值,而无需使用打印函数。 在任何其他编程平台或任何其他情况下,请确保使用打印函数。

    例如,打印包含异常值的结果字典的等效方法(也是最佳实践)将使用print语句,如下所示:print(outliers)。 这样,在不同的编程平台上运行时,代码将具有相同的输出。

Figure 2.28: Outlier participation in each feature

图 2.28:离群参与每个特征

注意

要访问此特定部分的源代码,请参考这里

您也可以通过这里在线运行此示例。 您必须执行整个笔记本才能获得所需的结果。

您已经成功浏览了数据集并处理了潜在问题。

数据缩放

尽管不需要重新缩放数据就可以将其馈送到算法中进行训练,但是如果您希望提高模型的准确率,这是重要的一步。 这主要是因为每个特征具有不同的比例可能会导致模型假定给定特征比其他特征更重要,因为它具有更高的数值。

以两个特征为例,一个特征测量一个人的孩子数量,另一个特征描述一个人的年龄。 即使年龄特征可能具有较高的数值,但在推荐学校的研究中,孩子特征的数目可能更为重要。

考虑到这一点,如果所有特征均等地缩放,则模型实际上可以赋予那些相对于目标特征最重要的特征更高的权重,而不是赋予它们具有的数值。 此外,它还可以消除模型从数据不变性中学习的需要,从而有助于加快训练过程。

有两种主要的重新缩放方法在数据科学家中很流行,尽管没有选择一个或另一个的规则,但重要的是要强调它们必须单独使用(一个或另一个)。

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

  • 规范化:这包括重新缩放值,以便所有特征的所有值都介于零和一之间。 使用以下公式完成此操作:

Figure 2.29: Data normalization

图 2.29:数据标准化

  • 标准化:相反,此缩放方法将转换所有值,以使其平均值为 0 且其标准差等于 1。这可使用以下公式完成:

Figure 2.30: Data standardization

图 2.30:数据标准化

练习 2.03:重新缩放数据

在本练习中,我们将重新缩放上一练习中的数据。 请执行以下步骤:

注意

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

  1. 将特征与目标分开。 我们这样做只是为了重新特征特征数据:

    X = data.iloc[:, 1:]
    Y = data.iloc[:, 0]
    

    前面的代码片段获取数据并使用切片将特征与目标分离。

  2. 通过使用规范化方法重新缩放特征数据。 显示结果DataFrame的标题(即前五个实例)以验证结果:

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

    输出应如下所示:

Figure 2.31: Top instances of the normalized Appliances energy prediction dataset

图 2.31:标准化设备能源预测数据集的主要实例

注意

要访问此特定部分的源代码,请参考这里

您也可以通过这里在线运行此示例。 您必须执行整个笔记本才能获得所需的结果。

您已成功缩放数据集。

分割数据

将数据集分为三个子集的目的是,可以在不引入偏差的情况下对模型进行适当的训练,微调和测量。 这是每个集合的说明:

  • 训练集:顾名思义,该集合被馈送到要训练的神经网络。 对于监督学习,它由特征和目标值组成。 如前所述,考虑到神经网络需要训练大量数据,通常这是三者中最大的集合。

  • 验证集(开发集):该集主要用于测量模型的表现,以便对超参数进行调整以提高性能。 完成此微调过程是为了使我们可以配置能获得最佳结果的超参数。

    尽管没有对模型进行数据训练,但模型会对模型产生间接影响,这就是为什么不应该对模型进行最终表现评估的原因,因为它可能是有偏差的度量。

  • 测试集:该集对模型没有影响,这就是为什么它用于对看不见的数据进行模型的最终评估的原因,这成为模型在未来的数据集上的表现如何的指南。

考虑到每个数据问题都是不同的,并且开发深度学习解决方案通常需要反复试验的方法,因此没有理想的科学方法可以将数据分为上述三组。 尽管如此,众所周知,对于较大的数据集(数十万个实例),每个集合的分割比例应为 98:1:1,因为对于训练集使用尽可能多的数据至关重要。 对于较小的数据集,常规拆分比率为 60:20:20。

练习 2.04:拆分数据集

在本练习中,我们将前一练习的数据集分为三个子集。 为了学习的目的,我们将探索两种不同的方法。 首先,将使用索引分割数据集。 接下来,将 scikit-learn 的train_test_split()函数用于相同的目的,从而通过两种方法获得相同的结果。 执行以下步骤以完成本练习:

注意

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

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

    shape
    

    此操作的输出应为(19735, 27)。 这意味着可以使用 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, random_state=0)
    Y_shuffle = Y.sample(frac=1, random_state=0)
    

    使用 Pandassample函数,可以对特征和目标矩阵中的元素进行混洗。 通过将frac设置为 1,我们确保所有实例都经过打乱并在函数的输出中返回。 使用random_state参数,我们确保两个数据集均被混洗。

  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_shuffleY_shuffle:要拆分的数据集,即特征数据集,以及目标数据集(也称为XY

    test_size:测试集中要包含的实例的百分比

    random_state:用于确保结果的可重复性

    此行代码的结果是将每个数据集(XY)分为两个子集。

    要创建其他集(验证集),我们将执行第二次拆分。 前面代码的第二行负责确定要用于第二个拆分的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,)
    

    我们可以看到,两种方法的结果集具有相同的形状。 使用一种方法还是另一种方法是优先考虑的问题。

    注意

    要访问此特定部分的源代码,请参考这里

    您也可以通过这里在线运行此示例。 您必须执行整个笔记本才能获得所需的结果。

您已成功将数据集分为三个子集。

无法准备数据的缺点

尽管准备数据集的过程很耗时,并且在处理大型数据集时可能很累,但这样做的缺点更加不便:

  • 较长的训练时间:包含噪声,缺失值以及冗余或无关列的数据需要花费相当长的时间来训练,并且在大多数情况下,此时间延迟甚至比准备数据所需的时间更长。 例如,在数据准备期间,可以确定五列与该研究目的无关,这可能会大大减少数据集,从而大大减少训练时间。
  • 偏差的引入:未清理的数据通常包含错误或缺失值,可能会使模型偏离真实情况。 例如,缺少值可能会导致模型做出不正确的推断,进而导致创建不代表数据的模型。
  • 避免泛化:异常值和嘈杂的值会阻止模型对数据进行泛化,这对于构建代表当前训练数据以及未来看不见的数据的模型至关重要。 例如,包含年龄变量的数据集包含 100 岁以上人群的条目,则可能会导致模型说明那些实际上只代表人口很小一部分的用户。

活动 2.01:执行数据准备

在本活动中,我们将准备一个数据集,其中包含歌曲列表,每首歌曲具有几个有助于确定其发行年份的属性。 数据准备步骤对于本章的下一个活动至关重要。 让我们看一下以下情况。

您在一家音乐唱片公司工作,您的老板想揭示表示不同时间段唱片的细节,这就是为什么他们汇总了一个包含 515,345 条唱片的数据的数据集,发布年份从 1922 年到 2011 年。 要求您准备数据集,以便准备将其馈送到神经网络。 执行以下步骤以完成此活动:

注意

要下载此活动的数据集,请访问以下 UC Irvine 机器学习存储库 URL

引用:Dua, D. and Graff, C. (2019). UCI Machine Learning Repository [http://archive.ics.uci.edu/ml]. Irvine, CA: University of California, School of Information and Computer Science.

也可以在本书的 GitHub 存储库中找到它

  1. 导入所需的库。

  2. 使用 Pandas,加载.csv文件。

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

  4. 检查缺失值。

    您还可以添加一个附加的sum()函数,以获取整个数据集中的缺失值之和,而无需按列进行区分。

  5. 检查异常值。

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

  7. 使用标准化方法重新缩放数据。

  8. 将数据分为三组:训练,验证和测试。 使用您喜欢的任何一种方法。

    注意

    有关此活动的解决方案,请参阅第 239 页。

构建深度神经网络

一般而言,可以使用 scikit-learn(不适用于深度学习)之类的库在非常简单的级别上构建神经网络,该库可以为您执行所有数学运算而没有很大的灵活性,或者在非常复杂的级别上,通过从头开始编码训练过程的每个步骤,或使用更强大的框架,可以提供极大的灵活性。

PyTorch 的构建考虑了该领域许多开发人员的意见,其优点是可以将两个近似值放在同一位置。 正如我们前面提到的,它具有一个神经网络模块,该模块被构建为允许使用顺序容器对简单架构进行简单的预定义实现,同时允许创建自定义模块,从而为构建非常复杂的架构的过程引入灵活性 。

在本节中,我们将讨论使用顺序容器开发深度神经网络,以揭开其复杂性。 不过,在本书的后续章节中,我们将继续研究更复杂和抽象的应用,而这些应用也可以非常轻松地实现。

正如我们前面提到的,顺序容器是一个模块,它被构建为包含遵循顺序的模块序列。 它包含的每个模块都将对给定的输入进行一些计算以得出结果。

可以在顺序容器中使用一些最流行的模块(层)来开发常规分类模型,在这里进行了解释:

注意

在随后的章节中将解释用于其他类型的架构(例如 CNN 和 RNN)的模块。

  • 线性层:这将线性变换应用于输入数据,同时保持内部张量来容纳权重和偏差。 它接收输入样本的大小(数据集的特征数量或上一层的输出数量),输出样本的大小(当前层中的单元数量,即输出数量) ),以及是否在训练过程中使用偏差张量(默认设置为True)作为参数。

  • 激活函数:它们接收线性层的输出作为输入,以破坏线性。 如前所述,有几个激活函数可以添加到顺序容器中。 最常用的解释如下:

    ReLU:这将校正后的线性单元函数应用于包含输入数据的张量。 它接受的唯一参数是是否应在原位进行操作,默认情况下将其设置为False

    Tanh:这会将基于元素的 tanh 函数应用于包含输入数据的张量。 它不需要任何参数。

    Sigmoid:这将前面解释的 Sigmoid 函数应用于包含输入数据的张量。 它不需要任何参数。

    Softmax:将 softmax 函数应用于包含输入数据的 n 维张量。 重新缩放输出,以使张量的元素位于零与一之间的范围内,并且总和为一。 它采用应沿着其计算 softmax 函数的维度作为参数。

  • 丢弃层:此模块根据设置的概率将输入张量的某些元素随机归零。 它使用概率进行随机选择,以及是否应在原位进行操作,默认情况下将其设置为False作为参数。 该技术通常用于处理过拟合模型,稍后将对其进行详细说明。

  • 规范化层:可以使用不同的方法在顺序容器中添加规范化层。 其中一些包括BatchNorm1dBatchNorm2dBatchNorm3d。 其背后的想法是对来自上一层的输出进行归一化,最终在较短的训练时间上达到相似的精度水平。

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

在本练习中,我们将使用 PyTorch 库定义四层深度神经网络的架构,然后将使用我们在先前练习中准备的数据集对其进行训练。 请执行以下步骤:

注意

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

  1. 从 PyTorch 导入名为torch的 PyTorch 库,以及nn模块。

    import torch
    import torch.nn as nn
    

    注意

    此练习中使用了torch.manual_seed(0),以确保在本书的 GitHub 存储库中获得的结果具有可重复性。

  2. 对于我们在上一个练习中创建的每个集合,将特征列与目标分开。 此外,将最终的DataFrame转换为张量:

    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. 将损失函数定义为 MSE:

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

    optimizer = torch.optim.Adam(model.parameters(), lr=0.001)
    
  6. 使用for循环在训练数据上训练 1,000 个迭代步骤来训练网络:

    for i in range(1000):
        y_pred = model(x_train).squeeze()
        loss = loss_function(y_pred, y_train)
        optimizer.zero_grad()
        loss.backward()
        optimizer.step()
        if i%100 == 0:
            print(i, loss.item())
    

    注意

    squeeze()函数用于删除y_pred的附加尺寸,该尺寸已从大小[3000,1]转换为[3000]

    考虑到y_train是一维的,并且两个张量都必须具有相同的维数才能馈送到损失函数,这一点至关重要。

    运行前面的代码片段将产生类似于以下内容的输出:

    Figure 2.32: Loss value for different iteration steps

    图 2.32:不同迭代步骤的损失值

    可以看出,损失值随时间连续减小。

  7. 为了测试模型,对测试集的第一个实例进行预测,并与真实情况(目标值)进行比较。

    pred = model(x_test[0])
    print("Ground truth:", y_test[0].item(), \
          "Prediction:",pred.item())
    

    输出应类似于以下内容:

    Ground truth: 60.0 Prediction: 69.5818099975586
    

    如您所见,真实情况值(60)非常接近预测值(69.58)。

    注意

    要访问此特定部分的源代码,请参考这里

    您也可以通过这里在线运行此示例。 您必须执行整个笔记本才能获得所需的结果。

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

练习 2.02:开发回归问题的深度学习解决方案

在本活动中,我们将创建并训练一个神经网络来解决我们在上一活动中提到的回归问题。 让我们看一下场景。

您继续在唱片公司工作,在看到您准备好数据集的出色工作后,您的老板已经信任您定义网络架构以及使用准备好的数据集进行训练的任务。 执行以下步骤以完成此活动:

注意

使用与上一活动相同的 Jupyter 笔记本。

  1. 导入所需的库。
  2. 从目标中为我们在上一个活动中创建的所有三组数据拆分函数。 将DataFrame转换为张量。
  3. 定义网络的架构。 随意尝试层数和每层单元数的不同组合。
  4. 定义损失函数和优化器算法。
  5. 使用循环对训练网络进行 3,000 个迭代步骤。
  6. 通过对测试集的第一个实例执行预测并将其与基本事实进行比较来测试模型。

您的输出应类似于以下内容:

Ground truth: 1995.0 Prediction: 1998.0279541015625

注意

有关此活动的解决方案,请参见第 242 页。

总结

产生神经网络的理论是由弗兰克·罗森布拉特(Frank Rosenblatt)于几十年前提出的。 它始于感知器的定义,感知器是受人类神经元启发的单元,其将数据作为输入以对其进行转换。 感知器背后的理论包括将权重分配给输入数据以执行计算,以便最终结果取决于结果而为一件事或另一件事。

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

解释了神经网络的典型学习过程。 在此,需要考虑三个主要过程:前向传播,损失函数的计算和反向传播。

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

还讨论了三种主要类型的神经网络的主要架构:人工神经网络,卷积神经网络和循环神经网络。 第一个用于解决传统的分类或回归问题,第二个由于具有解决计算机视觉问题(例如图像分类)的能力而广受欢迎,第三个可按顺序处理数据,用于语言翻译等任务。

在下一章中,将讨论解决回归问题和分类数据问题之间的主要区别。 您还将学习如何解决分类数据问题,以及如何改善其表现以及如何部署模型。

三、使用 DNN 的分类问题

概述

在本章中,我们将看一个银行业中的实际例子,以解决分类数据问题。 您将学习如何利用 PyTorch 的自定义模块来定义网络架构并训练模型。 您还将探索误差分析的概念,以提高模型的表现。 最后,您将研究部署模型的不同方法,以便将来使用它。 到本章结束时,您将对该过程有深刻的了解,以便可以使用深度神经网络DNN)在 PyTorch 中。

简介

在上一章中,我们了解了 DNN 的构建块,并回顾了三种最常见的架构的特征。 此外,我们学习了如何使用 DNN 解决回归问题。

在本章中,我们将使用 DNN 解决分类任务,其目的是从一系列选项中预测结果。

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

本章将重点介绍使用深层人工神经网络ANN)解决分类库问题的步骤,并遵循建立有效模型所需的所有步骤:数据探索,数据准备, 架构定义和模型训练,模型微调,误差分析,以及最后部署最终模型。

注意

本章中提供的所有代码都可以在以下位置找到

问题定义

定义问题与建立模型或提高准确率一样重要。 这是因为,虽然您可以使用最强大的算法并使用最先进的方法来改善其结果,但是如果您解决错误的问题或使用错误的数据,这可能毫无意义。

至关重要的是,学习如何深入思考以了解可以做什么和不能做什么以及如何可以完成。 考虑到当我们在学习应用机器学习或深度学习算法时,大多数课程中出现的问题总是清晰定义的,因此除训练模型和提高其表现外无需进行进一步分析,这一点尤其重要。 另一方面,在现实生活中,问题常常令人困惑,数据经常混乱。

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

为此,您需要遵循的步骤如下:

  1. 了解问题的原因,原因和方式。
  2. 分析手头的数据,以确定我们模型的一些关键参数,例如要执行的学习任务的类型,必要的准备工作以及表现指标的定义。
  3. 执行数据准备,以减少将偏差引入模型的可能性。

银行的深度学习

银行和金融实体每天处理大量信息,这是必需的,以便它们可以做出至关重要的决定,这些决定不仅影响其组织的未来,而且影响数百万信任它们的个人的未来。

这些决定每秒钟做出一次,早在 1990 年代,银行部门的人们就过去依靠专家系统来编写基于规则的程序。 也就是说,基于人类专家知识的程序要制定一套要遵循的规则。 不足为奇的是,这样的程序不足,因为它们要求对所有信息或可能的情况进行预先编程,这使得它们无法有效应对不确定性和瞬息万变的市场。

随着技术的进步,银行业一直在向更专业的系统过渡,这些系统利用统计模型来帮助做出此类决策。 此外,由于银行既要考虑自身的盈利能力,也要考虑客户的盈利能力,因此在不断追随技术进步的众多行业中,银行被认为是杰出的。

如今,随着医疗保健市场的发展,银行和金融业正在驱动神经网络的市场。 这主要是由于神经网络具有通过使用大量先前数据来预测未来行为来处理不确定性的能力。 考虑到人脑无法分析如此大量的数据,这是基于专家知识的人类系统无法实现的。

此处简要介绍并解释了使用深度学习的银行和金融服务中的某些领域:

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

    神经网络用于帮助决定是否授予贷款。 通常使用以前未偿还贷款的贷款人以及按时偿还贷款的人的数据对他们进行训练。 创建模型后,想法是将新申请人的数据输入模型中,以便预测他们是否会偿还贷款,考虑到模型的重点应该是减少假正例的数量(模型预测的客户会拖欠贷款,但实际上没有)。

    在行业中,已知神经网络的故障率低于依靠人类专业知识的传统方法。

  • 欺诈检测:欺诈检测对于银行和金融服务提供商至关重要,考虑到技术的进步,尽管使我们的生活更轻松,但也使我们面临更大的金融风险。

    在该域中,特别是 CNN,使用神经网络进行字符和图像识别,以检测字符图像中的隐藏和抽象图案,以确定用户是否遭受欺诈。

  • 信用卡客户选择:为了长期保持盈利,信用卡提供商需要找到合适的客户。 例如,将信用卡批准给信用卡需求有限的客户(即不会使用的客户)将导致信用卡收入较低。

    另一方面,信用卡发行商也有兴趣预测客户是否会拖欠下一次付款。 这将帮助发卡机构提前知道将拖欠的资金数量,以便他们进行准备,以保持盈利。

    使用持有一张或几张信用卡的客户的历史数据来训练网络。 目标是创建能够确定新客户是否会充分利用信用卡,从而使收入取代成本的模型,以及能够预测付款行为的模型。

    注意

    本章其余部分将重点解决与信用卡使用有关的数据问题。 要下载将要使用的数据集,请转到这里,单击数据文件夹链接,然后下载.xls文件。 该数据集也可在本书的 GitHub 存储库中找到

探索数据集

在以下各节中,我们将重点关注使用信用卡客户默认值DCCC)数据集解决与信用卡付款有关的分类任务,该数据集先前是从 UC Irvine 储存库站点下载的。

本节的主要思想是清楚说明数据问题的内容,原因和方式,这将有助于确定研究目的和评估指标。 此外,我们将详细分析手头的数据,以识别数据准备过程中所需的一些步骤(例如,将定性特征转换为其数值表示形式)。

首先,让我们定义什么,为什么以及如何。 考虑到应该这样做以确定组织的实际需求:

内容:建立一个模型,该模型能够确定客户是否会拖欠即将到来的付款。

意义:能够预测下个月要收到的付款金额(以货币为单位)。 这将帮助公司确定该月的支出策略,并允许他们定义要与每个客户一起执行的操作,既可以确保将要付款的客户将来的付款,也可以提高将要付款的客户的付款可能性。 默认。

方式:使用历史数据包含人口统计信息,信用历史记录以及有或没有拖欠付款的客户以前的账单,以训练模型。 在对输入数据进行训练之后,该模型应该能够确定客户是否可能拖欠下一次付款。

考虑到这一点,似乎目标函数应该是说明客户是否会在下次付款时违约,这将导致二进制结果(是/否)。 这意味着要开发的学习任务是分类任务,因此损失函数应该能够测量这种学习类型的差异(例如,交叉熵函数,如上一章所述)。 。

明确定义问题后,您需要确定最终模型的优先级。 这意味着确定所有输出类别是否同样重要。 例如,建立模型的时候,测量肺部肿块是否恶变的模型应主要集中在使假负例最小化(模型预测为无恶性肿块,但实际上是恶性的患者)上。 识别手写字符不应只关注一个特定的字符,而应最大化其在平等地识别所有字符方面的表现。

考虑到这一点以及“意义”语句中的解释,信用卡客户默认数据集的模型优先级应该是在不优先考虑任何类别标签的情况下最大化模型的整体表现 。 这主要是因为“为什么”声明宣称研究的主要目的应该是更好地了解银行将要收到的资金,并对可能拖欠付款的客户采取某些措施(例如,提供将债务分成较小的付款),以及针对那些不会违约的人采取不同的行动(例如,提供优惠作为对行为良好的客户的奖励)。

据此,本案例研究中使用的表现指标是准确率,其重点是使正确分类的实例最大化。 这是指任何类别标签正确分类的实例与实例总数的比率。

下表简要说明了数据集中存在的每个特征,这些特征可以帮助确定它们与研究目的的相关性,并确定一些需要执行的准备任务:

Figure 3.1: A description of features from the DCCC dataset

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

Figure 3.2: A description of features from the DCCC dataset (continued)

图 3.2:DCCC 数据集中的特征描述(续)

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

此外,所有特征都是定量的,这意味着无需转换其值。 我们需要做的就是调整它们的规模。 目标特征也已转换为其数字表示形式,其中下一笔付款违约的客户用 1 表示,而未下一笔付款的客户用 0 表示。

数据准备

尽管在这方面有一些良好的做法,但是并没有固定的一组步骤来准备(预处理)数据集以开发深度学习解决方案,并且在大多数情况下,要采取的步骤取决于现有数据,要使用的算法以及研究的其他特征。

注意

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

但是,在开始训练模型之前,必须处理一些关键方面。 您已经从上一章中了解了其中的大多数内容,这些内容将应用于当前数据集,并在目标特征中添加了类不平衡的修订:

  • 看看数据:使用 Pandas 读取数据集后,打印数据集的标题。 这有助于确保已加载正确的数据集。 此外,它还提供了准备后转换数据集的证据。

    注意

    为了能够使用 Pandas 读取 Excel 文件,请确保已在计算机或虚拟环境上安装了xlrd。 要安装xlrd,您需要在 Anaconda 提示符下运行conda install -c anaconda xlrd

    以下是用于使用 Pandas 读取 Excel 文件并打印出数据集标题的代码段:

    import pandas as pd
    data = pd.read_excel("default of credit card clients.xls", \
                         skiprows=1)
    data.head()
    

    我们使用skipline参数删除 Excel 文件的第一行,该行无关,因为它包含第二组标题。

    执行代码后,将获得以下结果:

Figure 3.3: The head of the DCCC dataset

图 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 列,如以下屏幕截图所示:

Figure 3.4: The head of the DCCC dataset after removing irrelevant features

图 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()
    

    第一行对数据集的每个特征执行缺失值的总和。 接下来,我们计算每个特征缺失值的参与度。 最后,我们将先前计算的两个值连接起来,并将结果显示在表中。 结果如下:

Figure 3.5: The count of missing values in the DCCC dataset

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

根据这些结果,可以说数据集没有丢失任何值,因此这里不需要采取进一步的措施。

  • 检查异常值:正如我们在“第 2 章”,“神经网络的构建块”中提到的,有几种方法可以检查异常值。 但是,在本书中,我们将坚持使用标准差方法,将偏离均值三个标准差的值视为离群值。 使用以下代码,可以从每个特征中识别离群值,并计算它们在整个值集中所占的比例:

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

    这将导致一个字典,其中包含每个特征名称作为键,并且该值表示该特征离群值的比例。 从这些结果中,可以观察到包含更多异常值的特征是BILL_AMT1BILL_AMT4,它们各自占总数的 2.3%。

    这意味着,鉴于异常值对每个特征的参与度太低,因此不需要采取进一步的措施,因此它们不太可能对最终模型产生影响。

  • 检查类不平衡:当目标特征中的类标签未均等表示时,发生类不平衡; 例如,一个数据集包含 90% 的未拖欠下次付款的客户,而 10% 的客户未拖欠下一次付款,则被认为是不平衡的。

    有几种处理类不平衡的方法,其中一些方法在这里说明:

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

    更改表现指标:某些指标(例如准确率)不适用于测量不平衡数据集的表现。 反过来,建议使用诸如精度或召回分类问题之类的指标来衡量表现。

    重采样数据集:这需要修改数据集以平衡类。 可以通过两种不同的方式来完成此操作:在代表性不足的类中添加实例的副本(称为过采样),或删除代表性不足的类的实例(称为欠采样)。

    可以通过简单地计算target属性中每个类的出现来检测类不平衡,如下所示:

    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(figsize=(10,5))
    plt.bar("yes", yes)
    plt.bar("no", no)
    ax.set_yticks([yes,no])
    plt.xticks(fontsize=15)
    plt.yticks(fontsize=15)
    plt.show()
    

    结果如下图:

Figure 3.6: The count of classes of the target feature

图 3.6:目标特征的类数

为了解决此问题,并且鉴于没有更多数据要添加并且表现指标实际上是准确率的事实,有必要执行数据重采样。

以下是对数据集执行过采样的代码片段,随机创建了代表性不足的类的重复行:

```py
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,其中包含的重复表示实例数与过量表示的实例数据帧一样多。

注意

请记住, sample()函数的第一个参数(no)是指先前计算的过分代表类中的项目数。

最后, concat()函数用于连接过度代表的类的DataFrame和大小相同的新创建的DataFrame,以便创建要在后续步骤中使用的最终数据集。

使用新创建的数据集,可以再次计算整个数据集中目标特征中每个类标签的参与度,这现在应该反映出具有相同参与度的两个类标签的均等表示的数据集。 此时,数据集的最终形状应等于(46728,23)

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

    data_resampled = data_resampled.reset_index(drop=True)
    X = data_resampled.drop(columns=["default payment next month"])
    y = data_resampled ["default payment next month"]
    
  • 重新缩放数据:最后,我们重新缩放特征矩阵的值,以避免对模型造成偏差:

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

    前几行代码的结果如下:

Figure 3.7: The features matrix after being normalized

图 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

如“活动 2.02”中所示,从“第 2 章”,“神经网络的构建模块”开发用于回归问题的深度学习解决方案,为回归任务而构建的神经网络使用输出作为连续值,这就是为什么在输出层不具有激活函数且仅具有一个输出节点(实际值)的原因,就像在根据房屋和邻里的特征,构建用于预测房价的模型的情况下一样。

鉴于此,要测量与此类模型相关的表现,您应该计算真实情况值和预测值之间的差,例如,计算 125.3(预测)与 126.38(真实情况值)之间的距离 。 如前所述,有许多方法可以测量此差异,例如均方误差MSE),或另一种变化是均方根误差RMSE),是最常用的指标。

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

计算属于每个输出类别的输入特征的似然性的这种能力,再加上argmax函数,将以较高的概率检索类别作为最终预测。

注意

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

考虑到这一点,模型的表现应与实例是否已分类到正确的类别标签有关,而不是与测量两个值之间的距离有关—因此要使用不同的损失函数( 熵是最常用的),用于训练神经网络进行分类问题,以及使用不同的表现指标,例如准确率,准确率和召回率。

好的架构

如本章所述,了解当前的数据问题对于确定神经网络的一般拓扑非常重要。 同样,常规分类问题并不需要与计算机视觉相同的网络架构。

修改并准备好数据后,考虑到在确定隐藏层数或每层中的单元数方面没有正确答案,最好的方法是从初始架构开始(可以进行改进) 以提高性能)。

这一点很重要,因为有时需要使用大量参数进行调整,可能难以承诺某些事情并开始开发解决方案。 但是,考虑到这一点,在训练神经网络时,有几种方法可以确定一旦对初始架构进行了训练和测试,就需要改进哪些内容。 实际上,将您的数据集分为三个子集的全部原因是允许使用一组训练数据集,使用另一组测量和微调模型,最后使用一组模型测量最终模型的表现。 最终未使用过的子集。

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

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

  • 隐藏层:隐藏层的数量可能有所不同。 ANN 可以有一个隐藏层,也可以没有,也可以没有。 要选择正确的号码,请考虑以下几点:

    数据问题越简单,所需的隐藏层就越少。 请记住,线性可分离的简单数据问题应该只有一个隐藏层。 另一方面,可以并且应该使用许多隐藏层(没有限制)解决更复杂的数据问题。

    隐藏单元的数量应介于输入层中的单元数和输出层中的单元数之间。

  • 输出层:同样,任何 ANN 仅具有一个输出层。 它包含的单元数取决于要开发的学习任务以及数据问题。 对于回归任务,将只有一个单元,即预测值。 但是,对于分类问题,考虑到模型的输出应该是属于每个类别标签的一组特征的概率,单元数量应等于可用的类别标签的数量。

  • 其他参数:对于网络的第一种配置,其他参数应保留其默认值。 这主要是因为,在考虑可能表现相同或较差但需要更多资源的更复杂的近似值之前,最好先对数据问题进行最简单的模型测试。

一旦定义了初始架构,就该训练和测量模型的表现以进行进一步分析了,这很可能导致网络架构或其他参数值的更改,例如更改学习率或增加正则化项。

PyTorch 自定义模块

自定义模块由 PyTorch 的开发团队创建,以为用户提供更大的灵活性。 与我们在前几章中探讨的顺序容器相反,每当需要构建更复杂的模型架构,或者希望进一步控制每一层中的计算时,都应使用自定义模块 。

这并不意味着定制模块方法只能在这种情况下使用。 相反,一旦您学会了使用这两种方法,则选择较简单的数据问题时使用哪个方法(顺序容器或自定义模块)就成为优先考虑的问题。

以使用顺序容器定义的两层神经网络的以下代码段为例:

import torch
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
import torch.nn as nn
import torch.nn.functional as F
class Classifier(torch.nn.Module):
    def __init__(self, D_i, D_h, D_o):
        super(Classifier, self).__init__()
        self.linear1 = torch.nn.Linear(D_i, D_h)
        self.linear2 = torch.nn.Linear(D_h, D_o)
    def forward(self, x):
        z = F.relu(self.linear1(x))
        o = F.softmax(self.linear2(z))
        return o

可以看出,在该类的初始化方法内部定义了输入层和输出层。 接下来,定义执行计算的另一种方法。

注意

对于本章中的练习和活动,您将需要安装 Python 3.7,Jupyter 6.0,Matplotlib 3.1,PyTorch 1.3,NumPy 1.17,scikit-learn 0.21,Pandas 0.25 和 Flask 1.1。

练习 3.01:使用自定义模块定义模型的架构

使用前面解释的理论,我们将使用定制模块的语法定义模型的架构:

  1. 打开 Jupyter 笔记本并导入所需的库:

    import torch
    import torch.nn as nn
    import torch.nn.functional as F
    
  2. 定义输入,隐藏和输出尺寸的必要变量。 将它们分别设置为1052

    D_i = 10
    D_h = 5
    D_o = 2
    
  3. 使用 PyTorch 的自定义模块,创建一个名为Classifier的类并定义模型的架构,使其具有两个线性层-第一个层是 ReLU 激活函数,第二层是 Softmax 激活函数:

    class Classifier(torch.nn.Module):
        def __init__(self, D_i, D_h, D_o):
            super(Classifier, self).__init__()
            self.linear1 = torch.nn.Linear(D_i, D_h)
            self.linear2 = torch.nn.Linear(D_h, D_o)
        def forward(self, x):
            z = F.relu(self.linear1(x))
            o = F.softmax(self.linear2(z))
            return o
    
  4. 实例化该类,并将我们在“步骤 2”中创建的三个变量输入该类。打印模型。

    model = Classifier(D_i, D_h, D_o)
    print(model)
    

    print语句的输出应如下所示:

    Classifier(
      (linear1): Linear(in_features=10, out_features=5, bias=True)
      (linear2): Linear(in_features=5, out_features=2, bias=True)
    )
    

    注意

    要访问此特定部分的源代码,请参考这里

    您也可以通过这里在线运行此示例。 您必须执行整个笔记本才能获得所需的结果。

这样,您就可以使用 PyTorch 的自定义模块成功构建神经网络架构。 现在,您可以继续学习有关训练深度学习模型的过程。

定义损失函数并训练模型

重要的是要提到,交叉熵损失函数要求网络的输出是原始的(在通过使用softmax激活函数获得概率之前),这就是为什么通常会发现,用于分类问题的神经网络架构,没有针对输出层的激活函数。 此外,为了通过这种方法进行预测,必须在训练模型后将softmax激活函数应用于网络的输出。

解决此问题的另一种方法是对输出层使用log_softmax激活函数。 这样,损失函数可以定义为负对数似然损失(nn.NLLLoss)。 最后,可以通过从网络输出中获取指数来获得属于每个类别标签的一组特征的概率。 这是本章活动中将使用的方法。

一旦定义了模型架构,下一步将是对负责根据训练数据进行模型训练的部分进行编码,并在训练和验证集上测量其表现。

按照我们讨论的这些逐步说明进行操作的代码如下:

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

从前面的片段中可以看出,第一步是定义在训练网络时将使用的所有变量。

接下来,循环的第一个用于遍历我们之前定义的周期数。

请记住,周期是指整个数据集通过网络架构前后传递的次数。batch_size是指单个批量(数据集的一部分)中训练示例的数量。 最后,迭代是指完成一个周期所需的批量数量。

第二个for循环遍历总数据集的每个批量,直到完成一个周期为止。 在此循环中,发生以下计算:

  1. 在一批训练集上训练模型。 在此获得预测。

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

  3. 将梯度设置为零并针对当前步骤再次计算。

  4. 网络的参数基于梯度进行更新。

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

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

    使用topk方法可以获得较高的类标签。

    使用 scikit-learn 的“度量”部分,计算准确率,精确度或查全率。 您还可以探索其他表现指标。

将所有批量的训练数据输入模型后,将关闭梯度计算,以通过验证数据验证当前模型的表现,如下所示:

  1. 该模型对验证集中的数据执行预测。

  2. 通过将先前的预测与验证集中的标签进行比较来计算损失函数。

  3. 准确率是根据验证集计算得出的。 要在验证集上计算模型的准确率,请使用对训练数据进行相同计算所用的同一组步骤:

    注意

    以下代码段不会自行运行。 您将需要加载一个数据集并将其划分为不同的集合,并定义和实例化一个网络架构。 还需要定义损失函数和优化算法(在本章前面的部分中进行了说明)。

    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))
    

前面的代码片段将打印两组数据的损失和准确率。 在下面的活动中,我们将介绍的有关构建和训练 DNN 的所有概念都将付诸实践。

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

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

让我们看一下以下情况:您在一家数据科学精品店工作,该精品店专门为世界各地的银行提供机器/深度学习解决方案。 他们最近为一家银行开展了一个项目,该项目希望能够预测下个月将收到或将不会收到的款项。 探索性数据分析团队已经为您准备了数据集,他们已要求您构建模型并计算模型的准确率。 请按照以下步骤完成此活动:

  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
    

    注意

    即使使用种子,考虑到训练集在每个周期之前都经过了打乱,该活动的确切结果也无法重现。

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

  3. 将特征与目标分开。

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

  5. 考虑到特征矩阵应为浮点型,而目标矩阵则应为非浮点型,将验证和测试集转换为张量。 目前暂时不要转换训练集,因为它们将进行进一步的转换。

  6. 构建用于定义网络层的自定义模块类。 包括一个正向函数,该函数指定将应用于每层输出的激活函数。 对于除输出之外的所有层,请使用 ReLU,在此处应使用log_softmax

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

  8. 使用训练集的数据训练网络。 使用验证集来衡量表现。 为此,请保存每个周期中训练集和验证集的损失和准确率。

    注意

    训练过程可能需要几分钟,具体取决于您的资源。 如果您希望查看训练过程的进度,则添加打印语句是一种很好的做法。

  9. 绘制两组损失。

  10. 绘制两组数据的准确率。

最终图将如下所示:

Figure 3.8: A plot displaying the accuracy of the sets

图 3.8:显示集合精度的图

注意

有关此活动的解决方案,请参见第 245 页。

您现在已经成功地编程和训练了一个四层神经网络,该网络能够根据历史数据执行推理。 接下来,您将学习如何提高模型的表现,以便对看不见的数据产生可信赖的推断。

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

建立深度学习解决方案不仅是定义架构,然后使用输入数据训练模型的问题; 相反,大多数人同意这是容易的部分。 创建高科技模型的技巧要求达到超越人类表现的高精确度。 本节将介绍误差分析主题,该主题通常用于诊断经过训练的模型,以发现哪些操作更可能对模型的表现产生积极影响。

误差分析

误差分析是指对训练和验证数据集上的错误率进行的初始分析。 然后,使用此分析来确定改善模型表现的最佳措施。

为了执行误差分析,有必要确定贝叶斯误差(也称为不可约误差),它是可实现的最小误差。 几十年前,贝叶斯误差等同于人为误差,这意味着专家认为可以达到的最低误差级别。

如今,随着技术和算法的改进,由于机器具有超越人类表现的能力,因此难以估计此值。 没有办法衡量他们与人类相比能做的更好,因为我们只能了解我们的能力。

为了执行误差分析,通常首先将贝叶斯误差设置为等于人为误差。 然而,这种局限性并非一成不变,研究人员认为,超越人类的表现也应是最终目标。

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

  1. 计算选择的度量标准以衡量模型的表现。 应该在训练和验证数据集上计算该度量。

  2. 使用此度量,通过减去以前从 1 中计算出的表现指标来计算每个集合的错误率。以下面的等式为例:

    Figure 3.9: The equation to calculate the error rate of the model over the training set

    图 3.9:用于在训练集上计算模型错误率的方程式

  3. 从训练集误差A)中减去贝叶斯误差。 保存差异,将用于进一步分析。

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

  5. 取步骤 3 和步骤 4 中计算出的差值,并使用以下一组规则。

    如果在“步骤 3”中计算出的差大于在“步骤 4”中计算出的差,则该模型不适合,也称为遭受高偏差。

    如果在“步骤 4”中计算出的差大于在“步骤 3”中计算出的差,则模型过拟合,也称为遭受高方差,如下图所示:

Figure 3.10: Diagram showing how to perform error analysis

图 3.10:显示如何执行误差分析的图

这些规则并不表示该模型只能遭受上述问题之一(高偏差或高方差),而是通过误差分析检测到的问题对模型的表现影响更大,这意味着修复它会在很大程度上提高性能。

让我们解释一下如何处理这些问题:

  • 高偏差:欠拟合的模型或遭受高偏差的模型是无法理解训练数据的模型,因此无法发现模式并不能与其他数据集进行概括 。 这意味着该模型在任何数据集上的表现都不理想。

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

  • 高方差:过拟合模型或遭受高方差的模型是难以归纳训练数据的模型; 太好地学习了训练数据的细节(这意味着通过训练过程,模型从训练集中学习的信息也太好了,这意味着它无法推广到其他数据集),包括其异常值 。 这意味着该模型在训练数据上的表现很好,但在其他数据集上的表现却很差。

    通常可以通过将更多数据添加到训练集中,或通过将正则项添加到损失函数来解决此问题。 第一种方法旨在迫使网络将数据概括化,而不是理解少量示例的细节。 另一方面,第二种方法对权重较高的输入进行惩罚,以忽略异常值,并平等地考虑所有值。

鉴于此,处理影响模型的一种情况可能导致另一种情况出现或增加。 例如,遭受高偏差的模型在经过处理后,可能会在训练数据上而非在验证数据上提高其表现,这意味着该模型将开始遭受高方差,并且将需要另一组要采取的补救措施。

一旦诊断出模型并采取了必要的措施以改善表现,就应该选择最佳模型进行最终测试。 这些模型中的每一个都应用于对测试集(唯一不影响模型构建的集合)进行预测。

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

练习 3.02:执行误差分析

使用我们在上一个活动中计算的准确率指标,在此活动中,我们将执行误差分析,这将有助于我们确定在即将到来的活动中针对模型执行的操作。 请按照以下步骤完成此练习:

注意

该练习不需要进行任何编码,而是需要对先前活动的结果进行分析。

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

    Bayes error (BE) = 0.15
    Training set error (TSE) = 10.716 = 0.284
    Validation set error (VSE) = 10.71 = 0.29
    

    用作两组精度的值(0.7160.71)是在“活动 3.01”,“建立 ANN”:

    High bias = TSE – BE = 0.134
    High variance = VSE – TSE = 0.014
    

    据此,该模型存在高偏差,这意味着该模型不适合。

  2. 确定提高模型准确率所需的操作。

    为了提高模型的表现,可以遵循的两个操作过程是增加周期数并增加隐藏层数和/或单元数(每层中的神经元)。

    据此,可以执行一组测试以便获得最佳结果。

这样,您就成功地执行了误差分析。 该方法是开发最先进的深度学习解决方案的关键,该解决方案在看不见的数据上表现出色。

练习 3 .02:提高模型的表现

对于此活动,我们将实现在练习中定义的操作,以减少影响模型表现的高偏差。 请考虑以下情形:团队成员对您所做的工作和代码的组织印象深刻,但是考虑到他们对客户的承诺,他们却要求您尝试将表现提高到 80%。 请按照以下步骤完成此活动:

注意

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

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

  2. 加载数据并从目标拆分特征。 接下来,使用 60:20:20 的分割比例将数据分割为三个子集(训练,验证和测试)。 最后,将验证和测试集转换为 PyTorch 张量,就像在上一个活动中一样。

  3. 考虑到该模型存在较高的偏差,因此重点应放在通过向每个层添加其他层或单元来增加周期数或网络规模。 目的应该是将测试范围内的准确率近似为 80%。

    注意

    没有正确的方法来选择首先执行哪个测试,因此要有创造性和分析性。 如果模型架构中的更改减少或消除了高偏差但引入了高方差,则应考虑保留这些更改,但增加措施以应对高方差。

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

  5. 使用表现最佳的模型,对测试集进行预测(在微调过程中不应使用该预测)。 通过计算该组模型的准确率,将预测结果与真实情况进行比较。

预期输出:通过模型架构和此处定义的参数获得的精度应为 80% 左右。

注意

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

这样,您就可以使用误差分析成功改善模型的表现。 接下来,您将学习如何部署模型以在生产环境中使用它。

部署模型

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

在小型组织中,可以对模型进行序列化和反序列化。 但是,当模型要由大型公司,用户使用或更改非常重要的大型任务时,将模型转换为可以在大多数生产环境中使用的格式(例如 API, 网站以及在线和离线应用)。

在本节中,我们将学习如何保存和加载模型,以及如何使用 PyTorch 的最新功能将我们的模型转换为高度通用的 C++ 应用。 我们还将学习如何创建 API 以利用经过训练的模型。

保存和加载模型

就像您想象的那样,每次都要对模型进行重新训练是非常不切实际的,尤其是考虑到大多数深度学习模型可能需要花费很多时间进行训练(取决于您的资源)。

取而代之的是,可以训练,保存和重新加载 PyTorch 中的模型,以执行进一步的训练或进行推理。 可以考虑将 PyTorch 模型中每一层的参数(权重和偏差)保存到state_dict字典中来实现。

此处提供了有关如何保存和加载经过训练的模型的分步指南:

  1. 最初,模型的检查点将仅包含模型的参数。 但是,在加载模型时,这不是唯一需要的信息。 根据分类器采用的参数(即包含网络架构的类),可能有必要保存更多信息,例如** input **单元的数量。 考虑到这一点,第一步是定义要保存的信息:

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

    这会将输入层中的单元数保存到检查点中,这在加载模型时会派上用场。

  2. 使用 PyTorch 的save()函数保存模型。

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

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

  3. 使用您选择的文本编辑器,创建一个 Python 文件,该文件导入 PyTorch 库并包含创建模型的网络架构的类。 这样做是为了使您可以方便地将模型加载到新的工作表中,而无需使用用于训练模型的工作表。

  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()指的是该文件中保存的类的名称。 该模型将具有随机初始化的参数。 最后,将来自检查点的参数加载到模型中。

    调用时,此函数将返回已训练的模型,该模型现在可用于进一步的训练或执行推理。

用于生产的 C++ 和 PyTorch

按照框架的名称,PyTorch 的主要接口是 Python 编程语言。 这主要是由于在开发机器学习解决方案时,由于该语言的动态性和易用性,因此许多用户偏爱该编程语言。

但是,在某些情况下,Python 属性变得不利。 对于为生产而开发的模型,情况恰恰如此,在该模型中,其他编程语言被证明更有用。 C++ 就是这种情况,C++ 已广泛用于机器/深度学习解决方案的生产目的。

鉴于此,PyTorch 最近提出了一种简单的方法,使用户可以享受两全其美的好处。 尽管他们可以继续使用 Pythonic 进行编程,但是现在可以将模型序列化为可以从 C++ 加载和执行的表示形式,而无需依赖 Python。 这种表示称为 TorchScript。

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

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

请记住,名为model的变量应包含以前训练过的模型,而名为example的变量应包含希望提供给模型的特征以便执行预测。 这将返回一个脚本模块,可以将其用作常规的 PyTorch 模块,如下所示:

prediction = traced_script(input)

前面的代码将返回通过运行模型中的输入数据获得的输出。

构建 API

应用编程接口API)包含一个专门创建供其他程序使用的程序(与网站或界面相反,后者是由人为操纵的) 。 据此,API 在创建要在生产环境中使用的深度学习解决方案时使用,因为它们允许通过其他方式(例如网站)访问从运行模型(例如预测)获得的信息。

在本节中,我们将探讨 Web API(通过互联网与其他程序共享信息的 API)的创建。 该 API 的功能是加载先前保存的模型并根据一组给定的特征进行预测。 向 API 发出 HTTP 请求的程序可以访问此预测。

关键术语解释如下:

  • 超文本传输协议HTTP):这是在网络上传输数据的主要方式。 它使用方法来运作,这有助于确定数据传输的方式。 两种最常用的方法解释如下:

    POST:此方法允许您将消息中的数据从客户端(正在向 API 发出请求的 Web 浏览器或平台)发送到服务器(使用该信息运行的程序) 身体。

    GET:与POST方法相反,此方法将数据作为 URL 的一部分发送,这在发送大量数据时可能不方便。

  • Flask:这是一个为 Python 开发的库,可让您创建 API(以及其他东西)。

要使用 Flask 创建简单的 Web API,请执行以下步骤:

  1. 导入必要的库:

    import flask
    from flask import request
    import torch
    import final_model
    
  2. 初始化 Flask 应用:

    app = flask.Flask(__name__)
    app.config["DEBUG"] = True
    

    DEBUG配置在开发期间设置为True,但在生产中应设置为False

  3. 加载之前训练的模型。

    def load_model_checkpoint(path):
        checkpoint = torch.load(path)
        model = final_model.Classifier(checkpoint["input"])
        model.load_state_dict(checkpoint["state_dict"])
        return model
    model = load_model_checkpoint("checkpoint.pth
    
  4. 定义可访问 API 的路径,以及可用于向 API 发送信息以执行操作的方法。 该语法称为装饰器,应位于函数之前:

    @app.route('/prediction', methods=['POST'])
    
  5. 定义一个执行所需动作的函数。在这种情况下,该函数将获取发送到 API 的信息,并将其反馈给之前加载的模型,以执行预测。一旦获得预测,该函数应返回一个响应,该响应将作为 API 请求的结果显示。

    def prediction():
        body = request.get_json()
        example = torch.tensor(body['data']).float()
        pred = model(example)
        pred = torch.exp(pred)
        _, top_class_test = pred.topk(1, dim=1)
        top_class_test = top_class_test.numpy()
        return {"status":"ok", "result":int(top_class_test[0][0])}
    
  6. 运行 Flask 应用。以下命令可以开始使用 Web API。

    app.run(debug=True, use_reloader=False)
    

    同样,在开发过程中,DEBUG设置为Trueuse_reloader参数设置为False,以允许该应用在 Jupyter 笔记本上运行。 但是,不建议从 Jupyter 笔记本运行该应用。 这仅出于教学目的。 在现实生活中,应将use_reloader设置为True,并且应通过命令提示符实例或终端将应用作为 Python 文件运行。

练习 3.03:创建 Web API

使用 Flask,我们将创建一个 Web API,该 Web API 会在调用时接收一些数据,并返回一段显示在浏览器中的文本。 请按照以下步骤完成此练习:

  1. 在 Jupyter 笔记本中,导入所需的库。

    import flask
    from flask import request
    
  2. 初始化 Flask 应用。

    app = flask.Flask(__name__)
    app.config["DEBUG"] = True
    
  3. 定义 API 的路由,使其为/<name>。将方法设置为GET。接下来,定义一个函数,该函数接收一个参数(name)并返回一个字符串,该字符串包含一个h1标签,其中有HELLO字样,后面是函数接收的参数。

    @app.route('/<name>', methods=['GET'])
    def hello(name):
        return "<h1>HELLO {}</h1>".format(name.upper())
    
  4. 运行 Flask 应用。

    app.run(debug=True, use_reloader=False)
    

    前一行代码的输出如下所示:

    Figure 3.11: Warnings after executing the code

    图 3.11:执行代码后的警告

    它包含一些警告,这些警告指定了正在运行的 Flask 应用的条件,以及包含类似于以下内容的 URL 的一行文本:

    http://127.0.0.1:5000/
    
  5. 将网址复制到浏览器中,后面加上你的名字,如下。

    http://127.0.0.1:5000/your_name
    

    Enter,然后将加载一个简单的网站,该网站应类似于以下内容:

Figure 3.12: Result from performing a request to the API

图 3.12:执行对 API 的请求的结果

注意

要访问此特定部分的源代码,请参考这里

本部分当前没有在线交互示例,需要在本地运行。

这样,您就成功创建了一个 Web API。 通过允许模型与用户之间的轻松通信,此功能将成为您在生产环境中使用模型的关键。 在下一个活动中,将把在本章中学习到的模型的部署的不同概念付诸实践。

练习 3.0 3:利用模型

对于此活动,保存在上一个活动中创建的模型。 此外,保存的模型将被加载到新笔记本中以供使用。 接下来,我们将模型转换为可以在 C++ 上执行的序列化表示形式,并创建 Flask API。 让我们看一下以下情况:每个人都对您对改进模型的承诺以及模型的最终版本感到满意,因此他们要求您保存模型并将其转换为可用于构建模型的格式。 客户的在线申请。 请按照以下步骤完成此活动:

注意

该活动将使用三个 Jupyter 笔记本。 首先,我们将使用上一个活动中的同一笔记本来保存最终模型。 接下来,我们将打开一个新的笔记本,该笔记本将用于加载保存的模型。 最后,将使用第三个笔记本创建 API。

  1. 打开用于“活动 3.02”和“提高模型表现”的 Jupyter 笔记本。

  2. 复制包含最佳表现模型的架构的类,并将其保存在 Python 文件中。 确保导入 PyTorch 所需的库和模块。 将其命名为final_model.py

  3. 在 Jupyter 笔记本中,保存表现最佳的模型。 确保保存与输入单元有关的信息以及模型参数。 将其命名为checkpoint.pth

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

  5. 导入 PyTorch 以及我们在“步骤 2”中创建的 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. 通过从“步骤 7”中输入张量到模型的跟踪脚本中来执行预测。

  10. 打开一个新的 Jupyter 笔记本,然后导入使用 Flask 创建 API 所需的库以及加载已保存模型的库。

  11. 初始化 Flask 应用。

  12. 定义一个函数来加载保存的模型并实例化该模型。

  13. 定义 API 的路由,使其为/prediction,并定义方法为POST。 然后,定义将接收POST数据并将其馈送到模型以执行预测的函数。

  14. 运行 Flask 应用。

运行后,该应用将如下所示:

Figure 3.13: A screenshot of the app after running it

图 3.13:应用运行后的屏幕截图

注意

有关此活动的解决方案,请参阅第 257 页。

总结

在涵盖了前几章的大部分理论知识之后,本章使用了一个实际案例研究来巩固我们的知识。 这样做的目的是鼓励通过动手实践来学习。

本章首先介绍了深度学习对要求准确率的众多行业的影响。 推动深度学习发展的主要行业之一是银行和金融业,这些算法被用于诸如贷款申请评估,欺诈检测以及过去决策评估以预测未来行为等领域,这主要是由于在这些方面,算法具有超越人类表现的能力。

本章使用来自台湾一家银行的真实数据集,目的是预测客户是否会拖欠付款。 本章通过解释定义任何数据问题的内容,原因和方式以及分析手头的数据以对其进行最佳利用的重要性,开始开发针对此问题的解决方案。

根据问题定义准备好数据后,我们就探索了定义“良好”架构的想法。 即使可以考虑一些经验法则,但主要的收获还是要构建一个初始架构而又不去想太多,以便获得一些可用于执行误差分析以改善模型表现的结果。

误差分析的思想需要在训练和验证集上分析模型的误差率,以便确定模型是遭受更大的偏差还是承受更大的偏差。 然后,对模型的这种诊断将用于更改模型的架构和一些学习参数,这将导致表现的提高。

最后,我们探索了使用最佳表现模型的三种主要方法。 第一种方法是保存模型,然后将其重新加载到任何编码平台中,以便我们可以继续训练或执行推理。 第二种方法主要用于将模型投入生产,并通过使用 PyTorch 的 JIT 模块来实现,该模块创建可以在 C++ 上运行的模型的序列化表示。 最后,第三种方法包括创建一个可供其他程序访问的 API,以便它可以向模型发送信息或从模型接收信息。

在下一章中,我们将重点介绍使用 CNN 解决图像分类任务。