Keras-深度学习研讨会-全-

Keras 深度学习研讨会(全)

原文:annas-archive.org/md5/b451b2e8c375a884062f55f9c0743702

译者:飞龙

协议:CC BY-NC-SA 4.0

前言

关于本书

新的经历可能令人畏惧,但这次不会!这本深度学习初学者指南将帮助你从零开始探索深度学习,使用 Keras 并开始训练你的第一个神经网络。

Keras 与其他深度学习框架的最大不同之处在于其简单性。Keras 拥有超过二十万的用户,在行业和研究界的应用程度超过了任何其他深度学习框架。

Keras 深度学习工作坊首先介绍了使用 scikit-learn 包的机器学习基本概念。在学习如何执行构建神经网络所需的线性变换之后,你将使用 Keras 库构建你的第一个神经网络。随着学习的深入,你将学会如何构建多层神经网络,并识别模型是否对训练数据出现欠拟合或过拟合。通过实际操作,你将学习如何使用交叉验证技术评估模型,然后选择最佳超参数以优化模型表现。最后,你将探索递归神经网络,并学习如何训练它们以预测序列数据中的值。

在本书的结尾,你将掌握自信训练自己神经网络模型所需的技能。

读者对象

如果你已经了解数据科学和机器学习的基础,并希望开始学习人工神经网络和深度学习等先进的机器学习技术,那么本书非常适合你。为了更有效地掌握本书中解释的深度学习概念,具有 Python 编程经验以及一定的统计学和逻辑回归知识是必需的。

关于章节

第一章使用 Keras 进行机器学习简介,将通过使用 scikit-learn 包向你介绍机器学习的基本概念。你将学习如何为模型构建准备数据,并使用一个真实世界的数据集训练一个逻辑回归模型。

第二章机器学习与深度学习的区别,将介绍传统机器学习算法与深度学习算法之间的区别。你将学习构建神经网络所需的线性变换,并使用 Keras 库构建你的第一个神经网络。

第三章使用 Keras 进行深度学习,将扩展你对神经网络构建的知识。你将学习如何构建多层神经网络,并能够识别模型是否对训练数据出现欠拟合或过拟合的情况。

第四章使用 Keras 包装器进行交叉验证评估模型,将教你如何使用 Keras 包装器与 scikit-learn 配合,将 Keras 模型纳入 scikit-learn 工作流中。你将应用交叉验证来评估模型,并使用该技术选择最佳的超参数。

第五章提升模型准确度,将介绍各种正则化技术,以防止模型过拟合训练数据。你将学习不同的方法来搜索最优的超参数,从而获得最高的模型准确度。

第六章模型评估,将演示多种方法来评估你的模型。除了准确度之外,你还将学习更多的模型评估指标,包括灵敏度、特异性、精确度、假阳性率、ROC 曲线和 AUC 分数,以了解你的模型表现如何。

第七章卷积神经网络的计算机视觉,将介绍如何使用卷积神经网络构建图像分类器。你将学习卷积神经网络架构的所有组成部分,并构建图像处理应用程序来进行图像分类。

第八章迁移学习与预训练模型,将向你介绍如何将一个模型的学习迁移到其他应用中。你将通过使用不同的预训练模型,并稍作修改,以适应不同的应用场景。

第九章使用循环神经网络的序列建模,将教你如何构建使用序列数据的模型。你将了解循环神经网络的架构,并学习如何训练它们,以预测序列数据中的后续值。你将通过预测各种股票价格的未来值来测试你的知识。

约定

代码中的单词、数据库表名、文件夹名称、文件名、文件扩展名、路径名称、虚拟 URL、用户输入和 Twitter 用户名如下所示:

"sklearn 有一个名为 train_test_split 的类,它提供了用于拆分数据的功能。"

屏幕上显示的文字,例如菜单或对话框中的内容,也以相同的格式显示。

一段代码如下所示:

# import libraries
import pandas as pd
from sklearn.model_selection import train_test_split

新术语和重要词汇将以如下方式展示:

"字典包含多个元素,类似于列表,但每个元素作为键值对进行组织。"

代码展示

跨多行的代码通过反斜杠( \ )进行拆分。执行代码时,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)

设置你的开发环境

在我们详细探讨这本书之前,我们需要安装特定的软件和工具。在接下来的部分中,我们将看到如何进行设置。

安装 Anaconda

本课程中,我们将使用 Anaconda;它是一个 Python 发行版,内置了包管理器,并预安装了常用于机器学习和科学计算的包。

要安装 Anaconda,请在 docs.anaconda.com/anaconda/install/ 的官方安装页面上找到你需要的版本(Windows、macOS 或 Linux)。按照适用于你操作系统的安装说明进行操作。

一旦安装了 Anaconda,你可以通过 Anaconda Navigator 或 Anaconda Prompt 与它进行交互。有关如何使用这些工具的说明,请访问 docs.anaconda.com/anaconda/user-guide/getting-started/

要验证安装是否正确,可以在 CMD / Terminal 中执行 anaconda-navigator 命令。如果安装正确,这将打开 Anaconda Navigator。

安装库

pip 已预安装在 Anaconda 中。一旦 Anaconda 安装在你的计算机上,所有必需的库都可以通过 pip 安装,例如,pip install numpy。或者,你可以使用 pip install –r requirements.txt 安装所有必需的库。你可以在 packt.live/3hhZ2v9 找到 requirements.txt 文件。

练习和活动将在 Jupyter Notebooks 中执行。Jupyter 是一个 Python 库,可以像安装其他 Python 库一样安装——也就是说,通过 pip install jupyter,但幸运的是,它在 Anaconda 中已预安装。

运行 Jupyter Notebook

你可以通过 Anaconda Navigator 中的适当链接启动 Jupyter,或者在 Anaconda Prompt / CMD / Terminal 中执行命令 jupyter notebook

Jupyter 将在你的浏览器中打开,你可以在其中导航到你的工作目录,并创建、编辑和运行你的代码文件。

访问代码文件

你可以在 packt.live/2OL5E9t 找到本书的完整代码文件。你也可以通过使用互动实验环境 packt.live/2CXyFLS 直接在你的网页浏览器中运行许多活动和练习。

我们尽力支持所有活动和练习的互动版本,但我们也建议你进行本地安装,以防这种支持不可用的情况。

本书中使用的高质量彩色图像可以在 packt.live/2u9Tno4 找到。

如果你在安装过程中遇到任何问题或有任何疑问,请通过电子邮件联系我们:workshops@packt.com

第一章:1. 使用 Keras 的机器学习介绍

概述

本章介绍了 Python 中的机器学习。我们将使用实际数据集来演示机器学习的基础知识,包括为机器学习模型进行数据预处理,并使用 scikit-learn 构建逻辑回归模型进行分类。然后,我们将通过在模型中加入正则化技术,提升我们的模型构建技巧,并通过模型评估指标来评估模型的表现。到本章结束时,你将能够自信地使用 Python 中的 scikit-learn 库构建分类任务模型,并有效地评估这些模型的性能。

介绍

机器学习是利用机器模拟人类任务的科学,并让机器随着时间的推移提高其执行任务的能力。通过将现实世界事件的观察数据输入到机器中,机器可以发展出优化目标函数的模式和关系,例如二分类任务的准确度或回归任务的误差。

通常,机器学习的价值在于机器能够学习大规模数据集中高度复杂和非线性的关系,并且能够多次复制这种学习的结果。机器学习算法的一个分支在学习与大规模、通常是非结构化数据集(如图像、音频和文本数据)相关的复杂非线性关系方面显示出了很大的潜力——人工神经网络ANNs)。然而,人工神经网络的编程、训练和评估可能很复杂,对于该领域的初学者来说可能会感到有些令人畏惧。Keras 是一个 Python 库,提供了构建、训练和评估人工神经网络的简单入门方法,对于学习机器学习的人来说非常有用。

以将包含狗或猫的图片数据集分类为相应类型的任务为例。对于人类来说,这是简单的,准确度很可能会非常高。然而,每张图片的分类可能需要大约一秒钟,且要扩大这一任务的规模只能通过增加人力来实现,这可能并不现实。虽然对于机器来说,达到与人类相同的准确度可能比较困难(但并非不可能),但机器可以每秒处理大量的图像,且通过增加单台机器的处理能力或优化算法效率,任务的规模扩展可以轻松实现:

图 1.1:将图像分类为狗或猫是人类的简单任务,但对机器来说相当困难

](https://github.com/OpenDocCN/freelearn-dl-pt5-zh/raw/master/docs/dl-keras-ws/img/B15777_01_01.jpg)

图 1.1:将图像分类为狗或猫是人类的简单任务,但对机器来说相当困难

虽然分类狗和猫的简单任务对我们人类来说可能很简单,但用于创建分类狗和猫的机器学习模型的相同原理可以应用于其他人类可能会遇到困难的分类任务。一个例子就是识别磁共振成像(MRI)中的肿瘤。对人类而言,这项任务需要一位具有多年经验的医疗专业人员,而机器可能只需要一个标注过的图像数据集。以下图像显示了大脑的 MRI 图像,其中一些图像包含肿瘤:

图 1.2:一个非平凡的分类任务——大脑的 MRI 图像,其中一些图像包含肿瘤的存在

图 1.2:一个非平凡的分类任务——大脑的 MRI 图像,其中一些图像包含肿瘤的存在

数据表示

我们构建模型是为了从我们正在训练的数据中学习某些信息,并了解数据集特征之间的关系。这种学习可以在我们遇到新观察时为我们提供帮助。然而,我们必须意识到,我们在现实世界中互动的观察和训练机器学习模型所需的数据格式是非常不同的。处理文本数据就是一个典型的例子。当我们阅读文本时,我们能够理解每个单词,并根据每个单词与周围单词的关系来应用上下文——这并非易事。然而,机器无法解释这些上下文信息。除非它们被专门编码,否则它们根本不知道如何将文本转换为可以作为数值输入的内容。因此,我们必须以适当的方式表示数据,通常是通过将非数值数据类型——例如,将文本、日期和类别变量转换为数值型数据。

数据表

大部分输入机器学习问题的数据是二维的,可以表示为行或列。图像是一个很好的例子,它可能是三维甚至四维的。每个图像的形状将是二维的(高度和宽度),所有图像加在一起将增加第三维度,颜色通道(红色、绿色和蓝色)将增加第四维度:

图 1.3:一张彩色图像及其作为红、绿、蓝图像的表示

图 1.3:一张彩色图像及其作为红、绿、蓝图像的表示

以下图展示了来自 UCI 数据库的数据集中的几行数据,该数据集记录了购物网站不同用户的在线会话活动。数据集的列代表会话活动的不同属性和页面的常规属性,而行代表不同用户的各个会话。名为 Revenue 的列表示用户是否通过购买网站上的产品结束了会话。

注意

记录购物网站各个用户在线会话活动的数据集可以在这里找到:packt.live/39rdA7S

分析数据集的一个目标可能是尝试利用提供的信息预测某个用户是否会从网站购买产品。然后,我们可以通过将我们的预测与名为Revenue的列进行比较来检查是否正确。长期来看,使用我们的模型可以识别会话或网页的关键特征,这些特征可能会预测购买意图:

![图 1.4:一张显示在线购物者购买意图数据集前 20 个实例的图片]

](https://github.com/OpenDocCN/freelearn-dl-pt5-zh/raw/master/docs/dl-keras-ws/img/B15777_01_04.jpg)

图 1.4:一张显示在线购物者购买意图数据集前 20 个实例的图片

加载数据

数据可以以不同的形式存在,并且可以存储在许多不同的位置。对于初学者来说,数据集通常以平面格式提供,也就是说它们是二维的,有行和列。其他常见的数据形式包括图像、JSON对象和文本文件。每种数据格式必须以特定的方式加载。例如,数值数据可以通过NumPy库加载到内存中,NumPy是一个在 Python 中处理矩阵的高效库。

然而,我们无法使用NumPy库将我们的营销数据.csv文件加载到内存中,因为数据集包含字符串值。对于我们的数据集,我们将使用pandas库,因为它能够轻松处理各种数据类型,如字符串、整数、浮动数字和二进制值。事实上,pandas依赖于NumPy来处理数值数据类型。pandas还能够通过 SQL 查询读取 JSON、Excel 文档和数据库,这使得该库在 Python 中加载和处理数据时非常常见。

下面是如何使用NumPy库加载 CSV 文件的示例。我们使用skiprows参数,以防文件中有标题行,标题通常包含列名:

import numpy as np
data = np.loadtxt(filename, delimiter=",", skiprows=1)

下面是使用pandas库加载数据的示例:

import pandas as pd
data = pd.read_csv(filename, delimiter=",")

这里,我们加载一个.CSV文件。默认的分隔符是逗号,所以传递该参数并非必须,但它有助于展示。pandas库还可以处理非数字数据类型,这使得它更加灵活:

import pandas as pd
data = pd.read_json(filename)

pandas库将会展平 JSON 并返回一个DataFrame

该库甚至可以连接到数据库,查询可以直接传入函数,返回的表格将作为pandas DataFrame 加载:

import pandas as pd
data = pd.read_sql(con, "SELECT * FROM table")

我们需要将数据库连接传递给函数才能使其工作。根据数据库类型,这可以通过多种方式实现。

其他常见的数据形式,如图像和文本,也可以加载,并将在本课程后续讨论。

注意

你可以在这里找到 pandas 的所有文档:pandas.pydata.org/pandas-docs/stable/。NumPy 的文档可以在这里找到:docs.scipy.org/doc/

练习 1.01:从 UCI 机器学习库加载数据集

注意

对于本章中的所有练习和活动,你需要在系统上安装 Python 3.7、Jupyter 和 pandas。安装说明请参考 前言 部分。所有的练习和活动都在 Jupyter notebook 中进行。建议为不同的任务保持单独的 notebook。你可以从本书的 GitHub 仓库下载所有的 notebooks,仓库地址为:packt.live/2OL5E9t

在这个练习中,我们将从 UCI 机器学习库加载 online shoppers purchasing intention 数据集。这个练习的目标是加载 CSV 数据,并确定一个目标变量来进行预测,以及用于建模目标变量的特征变量。最后,我们将分离特征列和目标列,并将它们保存为 .CSV 文件,以便在后续活动和练习中使用。

数据集与在线商店客户的在线行为和活动相关,并指示用户是否从网站购买了任何产品。你可以在 GitHub 仓库中找到该数据集:packt.live/39rdA7S

按照以下步骤完成本练习:

  1. 打开一个新的 Jupyter Notebook,并使用 pandas 库的 read_csv 函数将数据加载到内存中。导入 pandas 库并读取 data 文件:

    import pandas as pd
    data = pd.read_csv('../data/online_shoppers_intention.csv')
    

    注意

    上述代码假设你使用的是与 GitHub 仓库中相同的文件夹和文件结构。如果出现找不到文件的错误,请检查你的工作目录是否正确构建。或者,你可以编辑代码中的文件路径,使其指向系统中正确的文件位置,但你需要确保在后续练习中保存和加载文件时保持一致。

  2. 为了验证我们是否已正确将数据加载到内存中,我们可以打印出前几行。让我们打印出变量的前20个值:

    data.head(20)
    

    打印输出应如下所示:

    图 1.5:pandas 数据框的前 20 行和前 8 列

    图 1.5:pandas 数据框的前 20 行和前 8 列

  3. 我们还可以打印出 DataFrameshape

    data.shape
    

    打印输出应如下所示,显示数据框(DataFrame)有12330行和18列:

    (12330, 18)
    

    我们已成功将数据加载到内存中,现在可以对数据进行操作和清理,以便使用这些数据训练模型。请记住,机器学习模型需要数据以数值数据类型表示,才能进行训练。从数据集的前几行中我们可以看到,有些列是字符串类型,因此我们稍后需要将它们转换为数值数据类型。

  4. 我们可以看到数据集中有一个名为Revenue的输出变量,表示用户是否在网站上购买了产品。这看起来是一个合适的预测目标,因为网站的设计和所展示的产品选择可能会基于用户的行为以及他们是否购买了特定的产品。请按照以下方式创建featuretarget数据集,并提供axis=1参数:

    feats = data.drop('Revenue', axis=1)
    target = data['Revenue']
    

    注意

    axis=1参数告诉函数删除列,而不是删除行。

  5. 为了验证数据集的形状是否符合预期,请打印出每个数据集的数量:

    print(f'Features table has {feats.shape[0]} \
    rows and {feats.shape[1]} columns')
    print(f'Target table has {target.shape[0]} rows')
    

    上述代码生成了以下输出:

    Features table has 12330 rows and 17 columns
    Target table has 12330 rows
    

    我们可以在这里看到两个重要的事项,应该在继续之前始终验证:首先,feature DataFrame 和target DataFrame 的行数是相同的。在这里,我们看到两者都有12330行。其次,feature DataFrame 的列数应该比总 DataFrame 少一列,而target DataFrame 恰好有一列。

    关于第二点,我们必须验证目标数据集不包含在特征数据集中;否则,模型会快速发现这是唯一一个需要的列,从而将总误差降到零。目标列不一定必须是单列,但对于二元分类,如我们的案例,它会是。请记住,这些机器学习模型试图最小化某个代价函数,其中target变量会作为该代价函数的一部分,通常是预测值和target变量之间的差异函数。

  6. 最后,将 DataFrame 保存为 CSV 文件,以便以后使用:

    feats.to_csv('../data/OSI_feats.csv', index=False)
    target.to_csv('../data/OSI_target.csv', \
                  header='Revenue', index=False)
    

    注意

    header='Revenue'参数用于提供列名。我们将这样做以减少后续的混淆。index=False参数用于确保索引列不会被保存。

在本节中,我们已经成功演示了如何使用pandas库将数据加载到 Python 中。这将成为加载大多数表格数据到内存中的基础。图像和大型文档是机器学习应用中其他常见的数据形式,必须使用其他方法加载,这些内容将在本书后面讨论。

注意

要访问此特定部分的源代码,请参考packt.live/2YZRAyB

你也可以在线运行这个示例,网址是packt.live/3dVR0pF

数据预处理

为了将模型拟合到数据中,数据必须以数值格式表示,因为所有机器学习算法中使用的数学仅适用于数字矩阵(你无法对图像执行线性代数运算)。这将是本节的一个目标:学习如何将所有特征编码为数值表示。例如,在二进制文本中,包含两个可能值之一的值可以表示为零或一。以下图示展示了这一点。由于只有两个可能的值,值0假设为cat(猫),值1假设为dog(狗)。

我们还可以重新命名列以便解释:

图 1.6:二进制文本值的数值编码

图 1.6:二进制文本值的数值编码

另一个目标是将数据适当地表示为数值格式——这里的“适当”是指我们希望通过数字的分布将相关信息进行数值编码。例如,将年份的月份进行编码的一种方法是使用月份在年份中的数字表示。例如,January(一月)会被编码为1,因为它是第一个月,而December(十二月)会被编码为12。以下是这个过程的实际示例:

图 1.7:月份的数值编码

图 1.7:月份的数值编码

如果未将信息恰当地编码为数值特征,可能会导致机器学习模型学习到不直观的表示,以及feature数据和target变量之间的关系,这些关系对人类的解释毫无帮助。

你对所使用的机器学习算法的理解也有助于将特征适当地编码为数值表示。例如,像人工神经网络(ANN)和逻辑回归这样的分类任务算法对特征间尺度的巨大差异很敏感,这种差异可能会影响它们的模型拟合能力。

举个例子,假设有一个回归问题,试图将房屋属性(例如平方英尺面积和卧室数量)拟合到房价上。面积的范围可能从05000不等,而卧室的数量可能只在06之间变化,因此变量之间的尺度差异很大。

解决特征间尺度差异大的有效方法是对数据进行归一化。归一化数据将适当地缩放数据,使其具有相似的大小。这确保了任何模型的系数或权重可以正确地进行比较。像决策树这样的算法不受数据缩放的影响,因此使用基于树的算法的模型可以省略这一步。

在本节中,我们展示了多种不同的数字编码方式。还有许多其他技术可以在其他地方进行探索。在这里,我们将展示一些简单且流行的方法,用于解决常见的数据格式问题。

练习 1.02:清理数据

我们必须适当清理数据,以便可以用于训练模型。这通常包括将非数值数据类型转换为数值数据类型。这将是本练习的重点——将特征数据集中的所有列转换为数值列。为完成该练习,请执行以下步骤:

  1. 首先,我们将feature数据集加载到内存中:

    %matplotlib inline
    import pandas as pd
    data = pd.read_csv('../data/OSI_feats.csv')
    
  2. 再次查看前20行以检查数据:

    data.head(20)
    

    以下截图显示了前述代码的输出:

    图 1.8:pandas 特征 DataFrame 的前 20 行和 8 列

    图 1.8:pandas 特征 DataFrame 的前 20 行和 8 列

    在这里,我们可以看到有一些列需要转换为数值格式。我们可能不需要修改的数值列是名为>2)的列。这些列是名为MonthVisitorType的列。

  3. 对于数值列,使用describe函数快速查看数值列的范围:

    data.describe()
    

    以下截图显示了前述代码的输出:

    图 1.9:特征 DataFrame 中 describe 函数的输出

    图 1.9:特征 DataFrame 中 describe 函数的输出

  4. 将二进制列1转换为0,如果适当,重命名该列以提高可解释性。

    为了提供上下文,查看每个值的分布是有帮助的。我们可以使用value_counts函数来做到这一点。我们可以在Weekend列上尝试:

    data['Weekend'].value_counts()
    

    我们还可以通过调用结果 DataFrame 的plot方法并传入kind='bar'参数,以柱状图的形式查看这些值:

    data['Weekend'].value_counts().plot(kind='bar')
    

    注意

    kind='bar'参数将数据绘制为柱状图。默认是折线图。在 Jupyter 笔记本中绘图时,为了在笔记本中生成图表,可能需要运行以下命令:%matplotlib inline

    以下图表显示了前述代码的输出:

    图 1.10:默认列值分布的绘图

    图 1.10:默认列值分布的绘图

  5. 在这里,我们可以看到这个分布偏向于 false 值。这个列表示访问网站是否发生在周末,true 表示周末,false 表示工作日。由于工作日比周末多,所以这个偏斜的分布是合理的。通过将 True 值转换为 1False 值转换为 0,将该列转换为数值值。我们还可以将列的名称从默认名称改为 is_weekend,这样可以更清楚地表示列的含义:

    data['is_weekend'] = data['Weekend'].apply(lambda \
                         row: 1 if row == True else 0)
    

    注意

    apply 函数会遍历列中的每个元素,并应用作为参数传递的函数。必须提供一个函数作为参数。这里,提供了一个 lambda 函数。

  6. 看看原始列和转换后的列并排展示。取样最后几行,查看值如何被处理,以便它们成为数值数据类型:

    data[['Weekend','is_weekend']].tail()
    

    注意

    tail 函数与 head 函数相同,不同之处在于它返回的是 DataFrame 底部的 n 个值,而不是顶部的 n 个值。

    下图展示了前面代码的输出结果:

    图 1.11: 原始列与处理后的列

    图 1.11: 原始列与处理后的列

    在这里,我们可以看到 True 被转换为 1False 被转换为 0

  7. 现在我们可以删除 Weekend 列,因为只需要 is_weekend 列:

    data.drop('Weekend', axis=1, inplace=True)
    
  8. 接下来,我们需要处理分类列。我们将以略有不同于二进制文本列的方式来转换分类列为数值,但概念是相同的。将每个分类列转换为一组虚拟列。通过虚拟列,每个分类列将被转换为 n 列,其中 n 是该类别中唯一值的数量。列的值将是 01,具体取决于分类列的值。

    这可以通过 get_dummies 函数实现。如果我们需要任何关于这个函数的帮助,可以使用 help 函数或其他任何函数:

    help(pd.get_dummies)
    

    下图展示了前面代码的输出结果:

    图 1.12: 对 pd.get_dummies 函数应用 help 命令后的输出结果

    图 1.12: 对 pd.get_dummies 函数应用 help 命令后的输出结果

  9. 让我们演示如何操作 age 列的分类列。同样,查看值的分布是很有帮助的,因此可以查看值的计数并绘制图表:

    data['VisitorType'].value_counts()
    data['VisitorType'].value_counts().plot(kind='bar')
    

    下图展示了前面代码的输出结果:

    图 1.13: 年龄列值分布的图示

    图 1.13: 年龄列值分布的图示

  10. VisitorType 列调用 get_dummies 函数,并查看原始列旁边的行:

    colname = 'VisitorType'
    visitor_type_dummies = pd.get_dummies(data[colname], \
                                          prefix=colname)
    pd.concat([data[colname], \
               visitor_type_dummies], axis=1).tail(n=10)
    

    下图展示了前面代码的输出结果:

    图 1.14: 来自 VisitorType 列的虚拟列

    图 1.14:来自 VisitorType 列的虚拟列

    在这里,我们可以看到,在每一行中,只有一个值为 1,它出现在与 VisitorType 列中值对应的列中。

    实际上,使用虚拟列时,有一些冗余信息。因为我们知道有三个值,如果某一行的两个虚拟列的值为 0,那么剩下的列的值一定为 1。去除特征中的任何冗余和相关性非常重要,因为这样会导致难以确定哪个特征对于最小化总误差最为重要。

  11. 为了消除相互依赖性,删除 VisitorType_Other 列,因为它的出现频率最低:

    visitor_type_dummies.drop('VisitorType_Other', \
                              axis=1, inplace=True)
    visitor_type_dummies.head()
    

    注意

    drop 函数中,inplace 参数会就地应用函数,因此不需要声明新变量。

    看一下前几行,我们可以看到原始 VisitorType 列的虚拟列剩余部分:

    图 1.15:来自 VisitorType 列的最终虚拟列

    图 1.15:来自 VisitorType 列的最终虚拟列

  12. 最后,通过按列拼接两个数据框,将这些虚拟列添加到原始特征数据中,并删除原始列:

    data = pd.concat([data, visitor_type_dummies], axis=1)
    data.drop('VisitorType', axis=1, inplace=True) 
    
  13. 对剩下的分类列 Month 重复相同的步骤。首先,检查列值的分布,这是一个可选步骤。其次,创建虚拟列。第三,删除其中一个列以去除冗余。第四,将虚拟列拼接成特征数据集。最后,如果原始列仍然保留在数据集中,则删除它。你可以使用以下代码做到这一点:

    colname = 'Month'
    month_dummies = pd.get_dummies(data[colname], prefix=colname)
    month_dummies.drop(colname+'_Feb', axis=1, inplace=True)
    data = pd.concat([data, month_dummies], axis=1)
    data.drop('Month', axis=1, inplace=True) 
    
  14. 现在,我们应该已经将整个数据集转换为数值列。检查每一列的数据类型以验证这一点:

    data.dtypes
    

    下图展示了前面代码的输出:

    图 1.16:处理后的特征数据集的数据类型

    图 1.16:处理后的特征数据集的数据类型

  15. 现在我们已经验证了数据类型,我们有一个可以用来训练模型的数据集,所以让我们稍后保存它:

    data.to_csv('../data/OSI_feats_e2.csv', index=False)
    
  16. 我们对 target 变量也做同样的操作。首先,加载数据,转换列为数值数据类型,并将该列保存为 CSV 文件:

    target = pd.read_csv('../data/OSI_target.csv')
    target.head(n=10)
    

    下图展示了前面代码的输出:

    图 1.17:目标数据集的前 10 行

    图 1.17:目标数据集的前 10 行

    在这里,我们可以看到这是一个 Boolean 数据类型,并且有两个唯一值。

  17. 将其转换为二进制数值列,就像我们在特征数据集中处理二进制列一样:

    target['Revenue'] = target['Revenue'].apply(lambda row: 1 \
                        if row==True else 0)
    target.head(n=10)
    

    下图展示了前面代码的输出:

    图 1.18:目标数据集的前 10 行在转换为整数时的情况

    图 1.18:目标数据集的前 10 行在转换为整数时的情况

  18. 最后,将目标数据集保存为 CSV 文件:

    target.to_csv('../data/OSI_target_e2.csv', index=False)
    

在本练习中,我们学习了如何适当清洗数据,以便用它来训练模型。我们将非数值数据类型转换为数值数据类型;也就是说,我们将特征数据集中的所有列转换为数值列。最后,我们将目标数据集保存为 CSV 文件,以便在接下来的练习和活动中使用。

注意

要访问此特定部分的源代码,请参考packt.live/2YW1DVi

你也可以在线运行此示例,网址是packt.live/2BpO4EI

数据的适当表示

在我们的在线购物者购买意图数据集中,有些列被定义为数值变量,但仔细观察后发现,它们实际上是已经被赋予数值标签的分类变量。这些列包括OperatingSystemsBrowserTrafficTypeRegion。目前,我们将它们当作数值变量处理,尽管它们应该是分类变量。如果我们希望构建的模型能学习特征和目标之间的关系,这些分类变量应该被编码为特征。

我们这么做是因为我们可能会在特征中编码一些误导性关系。例如,如果OperatingSystems字段的值为2,这是否意味着它是值为1的两倍?可能不是,因为它指的是操作系统。基于这个原因,我们将把该字段转换为分类变量。同样的处理也适用于BrowserTrafficTypeRegion列。

练习 1.03:数据的适当表示

在这个练习中,我们将把OperatingSystemsBrowserTrafficTypeRegion列转换为分类类型,以准确反映信息。为此,我们将像在练习 1.02清洗数据》中那样,从这些列创建虚拟变量。具体步骤如下:

  1. 打开一个 Jupyter Notebook。

  2. 将数据集加载到内存中。我们可以使用练习 1.02清洗数据》中的输出数据集,该数据集包含OperatingSystemsBrowserTrafficTypeRegion列的原始数值版本:

    import pandas as pd
    data = pd.read_csv('../data/OSI_feats_e2.csv')
    
  3. 查看OperatingSystems列中的值分布:

    data['OperatingSystems'].value_counts()
    

    下图显示了前面代码的输出:

    图 1.19:操作系统列中值的分布

    图 1.19:操作系统列中值的分布

  4. OperatingSystem列创建虚拟变量:

    colname = 'OperatingSystems'
    operation_system_dummies = pd.get_dummies(data[colname], \
                               prefix=colname)
    
  5. 删除表示出现频率最低的值的虚拟变量,并与原始数据合并:

    operation_system_dummies.drop(colname+'_5', axis=1, \
                                  inplace=True)
    data = pd.concat([data, operation_system_dummies], axis=1)
    
  6. Browser列重复此操作:

    data['Browser'].value_counts()
    

    下图显示了前面代码的输出:

    图 1.20:浏览器列中值的分布

    图 1.20:浏览器列中值的分布

  7. 创建虚拟变量,删除出现频率最低的虚拟变量,并将其与原始数据连接:

    colname = 'Browser'
    browser_dummies = pd.get_dummies(data[colname], \
                      prefix=colname)
    browser_dummies.drop(colname+'_9', axis=1, inplace=True)
    data = pd.concat([data, browser_dummies], axis=1)
    
  8. TrafficTypeRegion列重复此操作:

    colname = 'TrafficType'
    data[colname].value_counts()
    traffic_dummies = pd.get_dummies(data[colname], prefix=colname)
    # value 17 occurs with lowest frequency
    traffic_dummies.drop(colname+'_17', axis=1, inplace=True)
    data = pd.concat([data, traffic_dummies], axis=1)
    colname = 'Region'
    data[colname].value_counts()
    region_dummies = pd.get_dummies(data[colname], \
                     prefix=colname)
    # value 5 occurs with lowest frequency
    region_dummies.drop(colname+'_5', axis=1, inplace=True)
    data = pd.concat([data, region_dummies], axis=1)
    
  9. 检查列类型,验证它们是否全部为数值型:

    data.dtypes
    

    以下图表显示了前面代码的输出:

    图 1.21:处理后特征数据集的数据类型

    图 1.21:处理后特征数据集的数据类型

  10. 最后,将数据集保存为 CSV 文件以便后续使用:

    data.to_csv('../data/OSI_feats_e3.csv', index=False)
    

现在,我们可以准确地测试浏览器类型、操作系统、流量类型或地区是否会影响目标变量。这个练习展示了如何正确地表示数据,以便在机器学习算法中使用。我们介绍了一些技术,可以将数据转换为数值型数据类型,这些技术覆盖了处理表格数据时可能遇到的多种情况。

注意

要访问此特定部分的源代码,请参考packt.live/3dXOTBy

您还可以在packt.live/3iBvDxw上在线运行此示例。

模型创建的生命周期

在本节中,我们将介绍创建高效机器学习模型的生命周期,从特征工程到拟合模型,再到训练数据,并使用各种评估指标评估模型。以下图表展示了构建机器学习模型的迭代过程。我们通过工程化特征来表示特征与目标之间的潜在关联,之后拟合模型,并对模型进行评估。

根据模型评分标准和评估指标的不同,特征会进一步工程化,整个过程不断重复。许多用于创建模型的步骤在所有机器学习库之间都是高度可转移的。我们将从广泛使用的 scikit-learn 开始,它有一个优点就是互联网上有大量的文档、教程和学习资料:

图 1.22:模型创建的生命周期

图 1.22:模型创建的生命周期

机器学习库

虽然本书是 Keras 深度学习的入门书籍,但正如我们之前提到的,我们将首先使用 scikit-learn。这将帮助我们建立使用 Python 编程语言构建机器学习模型的基础。

与 scikit-learn 类似,Keras 通过易于使用的 API 使得在 Python 编程语言中创建模型变得简单。然而,Keras 的目标是创建和训练神经网络,而不是一般的机器学习模型。人工神经网络(ANNs)代表了一个大型的机器学习算法类别,它们之所以被称为“神经网络”,是因为它们的架构类似于人类大脑中的神经元。Keras 库内置了许多通用功能,如 优化器激活函数层属性,使得用户像在 scikit-learn 中一样,不必从零开始编写这些算法。

scikit-learn

Scikit-learn 最初是由 David Cournapeau 于 2007 年创建的,目的是为了在 Python 编程语言中轻松创建机器学习模型。自从它问世以来,这个库因为易用性、在机器学习社区的广泛采用以及灵活性而迅速获得了巨大的流行。因为提供了大量的 分类回归聚类 算法,并且能够快速获得结果,scikit-learn 通常是使用 Python 的从业者首先实现的机器学习包。

例如,如果你希望快速训练一个简单的回归模型,scikit-learn 的 LinearRegression 类是一个优秀的选择,而如果需要一个更复杂的算法来学习非线性关系,scikit-learn 的 GradientBoostingRegressor 或任何一种 支持向量机 算法都是很好的选择。同样,对于分类或聚类任务,scikit-learn 提供了多种算法供你选择。

以下是使用 scikit-learn 进行机器学习时的一些优缺点:

Scikit-learn 的优点如下:

  • 成熟:Scikit-learn 在社区中已经得到了很好的建立,社区内各个技能层次的成员都在使用该包。该包包括了大部分常见的机器学习算法,适用于分类、回归和聚类任务。

  • 用户友好:Scikit-learn 具有易于使用的 API,使得初学者能够高效地进行原型设计,而无需深入理解或编写每个特定模型的代码。

  • 开源:有一个活跃的开源社区致力于改进这个库,添加文档并发布定期更新,这确保了包的稳定性和最新状态。

Scikit-learn 的缺点如下:

缺乏神经网络支持:具有 ANN 算法的估计器非常少。

注意

你可以在这里找到 scikit-learn 库的所有文档:scikit-learn.org/stable/documentation.html

scikit-learn 中的估计器通常可以分为监督学习和无监督学习技术。监督学习发生在存在目标变量时。目标变量是你试图预测的数据集中的变量,前提是其他变量已知。监督学习要求目标变量已知,并且模型需要训练以正确预测该变量。使用逻辑回归进行的二分类是监督学习技术的一个典型例子。

无监督学习中,训练数据中没有给定目标变量,但模型的目标是分配一个目标变量。k-means 聚类是无监督学习技术的一个例子。该算法根据数据点之间的邻近关系,将数据划分为指定数量的聚类。分配的目标变量可能是聚类编号或聚类中心。

在实践中使用聚类示例的一个例子可能如下所示。假设你是一个夹克制造商,目标是为各种夹克尺码开发尺寸。你无法为每个客户定制夹克,因此,你可以选择通过样本调查顾客群体,收集与合身相关的各种参数,如身高和体重。然后,你可以使用 scikit-learn 的k-means 聚类算法,根据你希望生产的夹克尺码的数量,来将顾客群体划分为相应数量的聚类。由聚类算法创建的聚类中心将成为夹克尺码的参数基础。

如下图所示:

图 1.23:一个无监督学习的示例,将客户参数分组到不同的聚类中

图 1.23:一个无监督学习的示例,将客户参数分组到不同的聚类中

甚至还有半监督学习技术,其中未标记的数据用于机器学习模型的训练。如果只有少量标记数据,而未标记数据量很大,那么可以使用这种技术。在实践中,半监督学习相较于无监督学习,能够显著提高模型性能。

scikit-learn 库非常适合初学者,因为构建机器学习管道的一般概念可以轻松学习。诸如数据预处理(为机器学习模型准备数据)、超参数调优(选择适当的模型参数的过程)、模型评估(模型性能的定量评估)等概念都包含在该库中。即使是经验丰富的用户,也能轻松使用该库快速原型化模型,然后再使用更专业的机器学习库。

事实上,我们讨论过的各种机器学习技术,如监督学习和无监督学习,都可以通过 Keras 使用不同架构的神经网络进行应用,这些内容将在本书中详细讲解。

Keras

Keras 被设计为一个高层次的神经网络 API,构建在 TensorFlow、CNTK 和 Theano 等框架之上。使用 Keras 作为深度学习入门的一个大好处是它非常用户友好;如优化器和层等高级功能已经内置在库中,无需从零开始编写。因此,Keras 不仅在初学者中受欢迎,在资深专家中也同样广泛使用。此外,该库支持神经网络的快速原型设计,支持多种网络架构,并且可以在 CPU 和 GPU 上运行。

注意

你可以在这里找到 Keras 库和所有文档:Keras.io/

Keras 用于创建和训练神经网络,在其他机器学习算法方面提供的功能较少,包括监督学习算法(如支持向量机)和无监督学习算法(如 k-means 聚类)。然而,Keras 提供了一个设计良好的 API,用于创建和训练神经网络,这大大减少了准确应用线性代数和多变量微积分所需的工作量。

本书将全面讲解 Keras 库中可用的特定模块,如 神经层代价函数优化器初始化方案激活函数正则化方案。所有这些模块都有相关的功能,可以用来优化训练神经网络以执行特定任务的性能。

Keras 的优势

以下是使用 Keras 进行机器学习的一些主要优势:

  • 用户友好:与 scikit-learn 类似,Keras 具有易于使用的 API,允许用户专注于模型构建,而非算法的具体细节。

  • 模块化:API 由完全可配置的模块组成,这些模块可以无缝连接并一起工作。

  • 可扩展:向库中添加新模块相对简单。这使得用户可以充分利用库中的许多强大模块,同时为他们提供创建自定义模块的灵活性。

  • 开源:Keras 是一个开源库,并且由于许多合作开发者的共同努力,Keras 的代码库不断改进和增加模块,从而帮助构建一个强大的库供所有人使用。

  • 与 Python 兼容:Keras 模型直接在 Python 中声明,而不是在单独的配置文件中,这使得 Keras 能够充分利用 Python 的优势,比如调试和扩展性。

Keras 的缺点

以下是使用 Keras 进行机器学习的一些主要缺点:

  • 高级自定义:虽然像创建简单的自定义损失函数或神经网络层这样的表面级自定义是容易的,但改变底层架构的工作方式可能会很困难。

  • 缺乏示例:初学者通常依赖示例来启动他们的学习。在 Keras 文档中,高级示例可能缺乏,这可能会阻碍初学者的学习进展。

Keras 为熟悉 Python 编程语言和机器学习的人提供了轻松创建神经网络架构的能力。由于神经网络相当复杂,我们将在使用 Keras 库之前,先通过 scikit-learn 引入许多机器学习概念。

不仅仅是构建模型

尽管像 scikit-learn 和 Keras 这样的机器学习库是为了帮助构建和训练预测模型而创建的,但它们的实用性远不止于此。构建模型的一个常见用途是可以用它们对新数据进行预测。一旦模型训练完成,就可以将新的观察数据输入模型,从而生成预测结果。模型甚至可以作为中间步骤使用。例如,神经网络模型可以用作特征提取器,对图像中的物体进行分类,然后将其输入到后续模型中,如下图所示:

图 1.24:使用深度学习进行物体分类

图 1.24:使用深度学习进行物体分类

另一个常见的模型使用案例是,模型可以通过学习数据的表示来总结数据集。这类模型被称为自编码器,是一种神经网络架构,可以用于学习给定数据集的表示。因此,数据集可以在减少维度的同时,尽量减少信息损失:

图 1.25:使用深度学习进行文本摘要的示例

图 1.25:使用深度学习进行文本摘要的示例

模型训练

在本节中,我们将开始将模型拟合到我们创建的数据集上。在本章中,我们将回顾在使用任何机器学习库(包括 scikit-learn 和 Keras)构建模型时,创建机器学习模型所需的最小步骤。

分类器和回归模型

本书关注的是深度学习的应用。绝大多数深度学习任务都是监督学习,其中有一个给定的目标,我们希望拟合一个模型,以便理解特征与目标之间的关系。

监督学习的一个例子是识别一张图片是否包含。我们想要确定输入(像素值矩阵)与目标变量之间的关系,也就是说,图像是还是

图 1.26:一个简单的监督学习任务,将图像分类为狗和猫

图 1.26:一个简单的监督学习任务,用于将图像分类为狗和猫

当然,我们可能需要更多的图像作为训练数据集,以便更稳健地对新图像进行分类,但在这样的数据集上训练的模型能够识别出区分猫和狗的各种关系,从而用于预测新数据的标签。

监督学习模型通常用于分类或回归任务。

分类任务

分类任务的目标是从具有离散类别的数据中拟合模型,并用于标记无标签数据。例如,这类模型可以用于将图像分类为狗或猫。但分类并不限于二分类;多标签分类也是可能的。另一个可能是分类任务的例子是预测图像中是否存在狗。正向预测表示图像中存在狗,而负向预测表示图像中没有狗。注意,这可以很容易地转化为回归任务,即预测一个连续变量,而不是分类任务估计的离散变量,例如预测图像中狗的数量。

大多数分类任务会为每个独特类别输出一个概率。这个预测由具有最高概率的类别决定,如下图所示:

图 1.27:分类模型标记图像的示意图

图 1.27:分类模型标记图像的示意图

以下是一些最常见的分类算法:

  • 逻辑回归:该算法类似于线性回归,在此过程中学习特征系数,通过特征系数与特征的乘积求和来进行预测。

  • 决策树:该算法遵循树状结构。在每个节点上做出决策,分支代表节点上的可能选项,最终得到预测结果。

  • 人工神经网络(ANNs):人工神经网络模仿生物神经网络的结构和性能,用于执行模式识别任务。ANN 由互联的神经元组成,按照一定的架构排列,神经元之间传递信息,直到得出结果。

回归任务

虽然分类任务的目的是为数据集贴上离散变量的标签,但回归任务的目的是为输入数据提供连续变量,并输出一个数值。例如,如果你有一个股市价格的数据集,分类任务可能预测是否买入、卖出或持有,而回归任务则会预测股市价格。

一种简单但非常流行的回归任务算法是线性回归。它只包含一个独立特征(x),其与依赖特征(y)的关系是线性的。由于其简单性,它常常被忽视,尽管它对于简单的数据问题表现得非常好。

一些最常见的回归算法如下:

  • 线性回归:该算法通过学习特征系数来进行预测,预测值是特征系数与特征乘积之和。

  • 支持向量机(SVM):该算法使用核函数将输入数据映射到多维特征空间,以理解特征与目标之间的关系。

  • 人工神经网络(ANNs):人工神经网络模仿生物神经网络的结构和性能,执行模式识别任务。一个 ANN 由互联的神经元组成,这些神经元按照一定的架构排列,彼此传递信息直到达到结果。

训练数据集和测试数据集

每当我们创建机器学习模型时,我们会将数据分为训练数据集和测试数据集。训练数据是用于训练模型的数据集。通常,它占总数据集的很大一部分——大约80%。测试数据集是从一开始就被分离出来的样本,用于对模型进行无偏评估。测试数据集应尽可能准确地代表真实世界的数据。任何报告的模型评估指标都应该应用于测试数据集,除非明确说明这些指标是在训练数据集上评估的。原因在于,模型通常在它们所训练的数据上表现得更好。

此外,模型可能会出现过拟合训练数据集的情况,这意味着它在训练数据集上表现良好,但在测试数据集上表现差。若模型在评估训练数据集时表现非常好,但在测试数据集上表现差,则说明模型过拟合了数据。相反,模型也可能出现欠拟合的情况。在这种情况下,模型未能学习特征目标之间的关系,这会导致在评估训练测试数据集时都表现不佳。

我们的目标是这两者之间的平衡,既不依赖过多的训练数据集以至于出现过拟合,又能让模型学习特征目标之间的关系,以便模型能很好地泛化到新数据。这一概念在下图中得到了说明:

图 1.28:欠拟合和过拟合数据集的示例

图 1.28:欠拟合和过拟合数据集的示例

有许多通过抽样方法来拆分数据集的方式。一种拆分数据集为训练集的方法是简单地随机抽样,直到你得到所需数量的数据点。这通常是像 scikit-learn 中的train_test_split函数的默认方法。

另一种方法是进行分层抽样。在分层抽样中,每个子群体都被独立抽样。每个子群体是由目标变量决定的。在像二分类这样的例子中,目标变量可能高度偏向某一个值,而随机抽样可能在训练测试数据集中没有两个值的数据点。这种方法在某些情况下是有利的。还有验证数据集,我们将在本章稍后讨论。

模型评估指标

能够有效评估我们的模型非常重要,这不仅仅是从模型的表现角度来看,还需要结合我们试图解决的问题背景。例如,假设我们建立了一个分类任务来预测是否基于历史股市价格购买、出售或持有股票。如果我们的模型每次都预测买入,这将不是一个有用的结果,因为我们可能没有无限的资源来购买股票。或许更好的方法是降低准确度,同时包括一些卖出预测。

常见的分类任务评估指标包括准确率、精确度、召回率和 F1 分数。准确率(Accuracy)定义为正确预测的数量除以总预测数量。准确率非常容易理解并且具有可比性,适用于类别平衡的情况。然而,当类别严重失衡时,准确率可能会误导:

图 1.29:计算准确率的公式

](https://github.com/OpenDocCN/freelearn-dl-pt5-zh/raw/master/docs/dl-keras-ws/img/B15777_01_29.jpg)

图 1.29:计算准确率的公式

精确度(Precision)是另一个有用的指标。它被定义为模型预测的正类结果中,真正正类(True Positive)占的比例:

图 1.30:计算精确度的公式

](https://github.com/OpenDocCN/freelearn-dl-pt5-zh/raw/master/docs/dl-keras-ws/img/B15777_01_30.jpg)

图 1.30:计算精确度的公式

召回率(Recall)定义为正确的正类结果占所有真实正类结果的比例:

图 1.31:计算召回率的公式

](https://github.com/OpenDocCN/freelearn-dl-pt5-zh/raw/master/docs/dl-keras-ws/img/B15777_01_31.jpg)

图 1.31:计算召回率的公式

精确度召回率的得分在之间,但在一个指标上得分高可能意味着在另一个指标上得分较低。例如,一个模型可能精确度很高,但召回率很低,这表明该模型非常准确,但遗漏了大量正类实例。因此,结合召回率和精确度的综合指标是非常有用的。这里的F1 分数可以衡量模型的精确性和鲁棒性:

图 1.32:计算 F1 分数的公式

](https://github.com/OpenDocCN/freelearn-dl-pt5-zh/raw/master/docs/dl-keras-ws/img/B15777_01_32.jpg)

图 1.32:计算 F1 分数的公式

在评估模型时,查看一系列不同的评估指标是非常有帮助的。它们有助于确定最合适的模型,并评估模型在预测中的误分类情况。

例如,考虑一个帮助医生预测患者是否患有罕见疾病的模型。通过对每个实例预测负面结果,模型可能会提供一个高度准确的评估,但这对医生或患者的帮助不大。相反,检查精确度召回率可能会提供更多有用的信息。

高精度模型非常挑剔,可能会确保所有标记为正的预测结果实际上都是正的。高召回率模型则可能召回许多true正例,代价是可能出现许多假阳性。

当你希望确保被标记为true的预测结果具有较高的真实概率时,应该使用高精度模型。在我们的示例中,如果治疗一种罕见疾病的费用很高或治疗并发症的风险很大,那么可能需要这种模型。若希望确保模型能尽可能多地召回true正例,则应使用高召回率模型。在我们的示例中,如果罕见疾病具有高度传染性,我们希望确保所有病例都得到治疗,那么就需要这种模型。

练习 1.04:创建一个简单的模型

在这个练习中,我们将从scikit-learn包中创建一个简单的逻辑回归模型。然后,我们将创建一些模型评估指标,并根据这些评估指标测试预测结果。

我们应该始终将训练任何机器学习模型视为一个迭代过程,从一个简单的模型开始,并使用模型评估指标来评估模型的性能。在这个模型中,我们的目标是将在线购物者的购买意图数据集中的用户分类为在会话中会购买的用户和不会购买的用户。请按照以下步骤完成此练习:

  1. 加载数据:

    import pandas as pd
    feats = pd.read_csv('../data/OSI_feats_e3.csv')
    target = pd.read_csv('../data/OSI_target_e2.csv')
    
  2. 从创建testtraining数据集开始。使用training数据集训练模型,并在test数据集上评估模型的性能。

    我们将使用test_size = 0.2,意味着20%的数据将用于测试,并为random_state参数设置一个值:

    from sklearn.model_selection import train_test_split
    test_size = 0.2
    random_state = 42
    X_train, X_test, \
    y_train, y_test = train_test_split(feats, target, \
                                       test_size=test_size, \
                                       random_state=random_state)
    
  3. 打印每个数据框的shape以验证维度是否正确:

    print(f'Shape of X_train: {X_train.shape}')
    print(f'Shape of y_train: {y_train.shape}')
    print(f'Shape of X_test: {X_test.shape}')
    print(f'Shape of y_test: {y_test.shape}')
    

    上述代码产生如下输出:

    Shape of X_train: (9864, 68)
    Shape of y_train: (9864, 1)
    Shape of X_test: (2466, 68)
    Shape of y_test: (2466, 1)
    

    这些维度看起来正确;每个target数据集只有一列,训练特征和target数据框的行数相同,test特征和target数据框也是如此,且测试数据框占整个数据集的20%

  4. 接下来,实例化模型:

    from sklearn.linear_model import LogisticRegression
    model = LogisticRegression(random_state=42)
    

    虽然我们可以向 scikit-learn 的逻辑回归模型添加许多参数(例如正则化参数的类型和值、求解器的类型,以及模型的最大迭代次数),但我们只会传递random_state

  5. 然后,fit模型以适应训练数据:

    model.fit(X_train, y_train['Revenue'])
    
  6. 为了测试模型的性能,将模型的预测结果与真实值进行比较:

    y_pred = model.predict(X_test)
    

    我们可以使用多种类型的模型评估指标。我们先从accuracy开始,accuracy定义为预测值等于真实值的比例:

    from sklearn import metrics
    accuracy = metrics.accuracy_score(y_pred=y_pred, \
                                      y_true=y_test)
    print(f'Accuracy of the model is {accuracy*100:.4f}%')
    

    前面的代码生成了以下输出:

    Accuracy of the model is 87.0641%
    
  7. 分类模型的其他常见评估指标包括precisionrecallfscore。使用 scikit-learn 的precison_recall_fscore_support函数,可以计算这三者:

    precision, recall, fscore, _ = \
    metrics.precision_recall_fscore_support(y_pred=y_pred, \
                                            y_true=y_test, \
                                            average='binary')
    print(f'Precision: {precision:.4f}\nRecall: \
    {recall:.4f}\nfscore: {fscore:.4f}')
    Precision: 0.7347
    Recall: 0.3504
    fscore: 0.4745
    

    由于这些度量值的评分范围是01recallfscore的表现不如accuracy,尽管将这些度量值一起考虑可以帮助我们找出模型表现良好的地方,以及通过检查模型在哪些观察值上做出了错误预测,找出可以改进的地方。

  8. 查看模型输出的系数,以观察哪些特征对预测结果的整体影响更大:

    coef_list = [f'{feature}: {coef}' for coef, \
                 feature in sorted(zip(model.coef_[0], \
                 X_train.columns.values.tolist()))]
    for item in coef_list:
        print(item)
    

    下图显示了前面代码的输出结果:

    图 1.33:模型的排序重要特征及其相应的系数

    ](https://github.com/OpenDocCN/freelearn-dl-pt5-zh/raw/master/docs/dl-keras-ws/img/B15777_01_33.jpg)

图 1.33:模型的排序重要特征及其相应的系数

这个练习教会了我们如何创建和训练一个预测模型,在给定feature变量的情况下预测target变量。我们将featuretarget数据集划分为trainingtest数据集。然后,我们在training数据集上训练模型,并在test数据集上评估模型。最后,我们观察了该模型的训练系数。

注意

要访问该部分的源代码,请参阅packt.live/2Aq3ZCc

您还可以在packt.live/2VIRSaL上在线运行此示例。

模型调优

在本节中,我们将进一步深入评估模型性能,并检查可以用来通过regularization对模型进行泛化到新数据的技术。提供模型性能的上下文非常重要。我们的目标是确定我们的模型是否比简单或显而易见的方法表现更好。我们通过创建一个基准模型,将其与训练的机器学习模型进行比较,从而实现这一目标。需要强调的是,所有模型评估指标都是通过test数据集进行评估和报告的,因为这将帮助我们了解模型在新数据上的表现。

基准模型

基线模型应该是一个简单且容易理解的过程,该模型的性能应该是我们构建的任何模型所能接受的最低性能。对于分类模型,一个有用且简单的基线模型是计算模型结果值。例如,如果有 60%false 值,我们的基线模型就是预测每个值为 false,这样我们就能得到 60%accuracy。对于 regression models,可以使用 meanmedian 作为基线。

练习 1.05:确定基线模型

在本练习中,我们将把模型的表现放在一个背景下。我们从模型中获得的准确率看起来不错,但我们需要一些对比的标准。由于机器学习模型的表现是相对的,因此建立一个强健的基线模型用于比较是非常重要的。再次提醒,我们使用的是在线购物者购买意图数据集,而我们的 target 变量是每个用户是否会在他们的会话中购买产品。按照以下步骤完成这个练习:

  1. 导入 pandas 库并加载 target 数据集:

    import pandas as pd
    target = pd.read_csv('../data/OSI_target_e2.csv')
    
  2. 接下来,计算 target 变量每个值的相对比例:

    target['Revenue'].value_counts()/target.shape[0]*100
    

    以下图显示了前面代码的输出:

    图 1.34:每个值的相对比例

    图 1.34:每个值的相对比例

  3. 在这里,我们可以看到 0 出现的比例为 84.525547%——即用户没有购买,这是我们的 baseline 准确率。现在,来看其他模型评估指标:

    from sklearn import metrics
    y_baseline = pd.Series(data=[0]*target.shape[0])
    precision, recall, \
    fscore, _ = metrics.precision_recall_fscore_support\
                (y_pred=y_baseline, \
                 y_true=target['Revenue'], average='macro')
    

    在这里,我们设置基线模型为预测 0,并重复该值,使其与 test 数据集中的行数相同。

    注意

    precision_recall_fscore_support 函数中的平均参数必须设置为 macro,因为当它设置为 binary 时(如之前所设置),该函数会查找 true 值,而我们的 baseline 模型仅由 false 值组成。

  4. 打印出最终的精确度、召回率和 fscore 输出:

    print(f'Precision: {precision:.4f}\nRecall:\
    {recall:.4f}\nfscore: {fscore:.4f}')
    

    前面的代码会产生以下输出:

    Precision: 0.9226
    Recall: 0.5000
    Fscore: 0.4581
    

现在,我们有了一个基线模型,可以与之前的模型以及任何后续模型进行比较。通过这样做,我们可以看出,尽管之前模型的准确度看起来较高,但它并没有比这个 baseline 模型好多少。

注意

要访问此特定部分的源代码,请参考 packt.live/31MD1jH

你也可以在网上运行这个例子,访问 packt.live/2VFFSXO

正则化

在本章的早些时候,我们了解了过度拟合及其表现。 过度拟合的特征是,模型在训练数据上表现非常好,但在测试数据上表现糟糕。 这可能的一个原因是模型可能过于依赖某些在训练数据集中表现良好但不能很好泛化到新数据或测试数据集的特征。

为了避免这种情况,可以使用一种称为正则化的技术。 正则化会限制系数向零的值,从而抑制复杂模型的生成。 有许多不同类型的正则化技术。 例如,在线性逻辑回归中,最常见的是套索正则化。 在基于树的模型中,通过限制树的最大深度来进行正则化。

有两种不同类型的正则化,即L1L2。 这个术语可以是权重的L2范数(平方和)或权重的L1范数(绝对值之和)。 由于L1正则化参数作为特征选择器,它能够将特征的系数减少到零。 我们可以使用此模型的输出来观察哪些特征对性能贡献不大,并在需要时完全删除它们。 L2正则化参数不会将特征的系数减少到零,因此我们将观察到它们所有具有非零值。

以下代码显示了如何使用这些正则化技术实例化模型:

model_l1 = LogisticRegressionCV(Cs=Cs, penalty='l1', \
                                cv=10, solver='liblinear', \
                                random_state=42)
model_l2 = LogisticRegressionCV(Cs=Cs, penalty='l2', \
                                cv=10, random_state=42)

以下代码显示了如何拟合这些模型:

model_l1.fit(X_train, y_train['Revenue'])
model_l2.fit(X_train, y_train['Revenue'])

套索和岭正则化中的相同概念可以应用于 ANN。 但是,惩罚发生在权重矩阵而不是系数上。 Dropout 是防止 ANN 中过度拟合的另一种形式的正则化。 在每次迭代中,Dropout 会随机选择节点并移除它们及其连接,如下图所示:

图 1.35:ANN 中的 Dropout 正则化

图 1.35:ANN 中的 Dropout 正则化

交叉验证

交叉验证通常与正则化一起使用以帮助调整超参数。 例如,在岭和套索回归中使用的惩罚参数,或者在 ANN 中使用 Dropout 技术时每次迭代中要放弃的节点比例。 您将如何确定使用哪个参数? 一种方法是对每个正则化参数的值运行模型,并在测试集上评估它们; 但是,经常使用测试集可能会引入模型的偏差。

交叉验证的一个常见例子叫做 k 折交叉验证。这个技术使我们能够在保留一个测试集的同时,在未见过的数据上测试我们的模型,并在最后进行测试。使用这种方法,数据被分成k个子集。在每一次的k次迭代中,k-1个子集作为训练数据,剩下的子集作为验证集。这一过程重复k次,直到所有k个子集都作为验证集使用过。

使用这种技术,可以显著减少偏差,因为大部分数据用于拟合。同时,由于大部分数据也用于验证,因此变化性也会减少。通常,折数在510之间,这项技术甚至可以进行分层,这对于类别严重不平衡时非常有用。

以下示例展示了5 折交叉验证,其中20%的数据被保留作为测试集,剩下的80%数据被分为 5 个折叠。四个折叠作为训练数据,剩下的一个折叠作为验证数据。这个过程总共重复五次,直到每个折叠都被用作一次验证集:

图 1.36:演示如何进行 5 折交叉验证的图示

图 1.36:演示如何进行 5 折交叉验证的图示

活动 1.01:为模型添加正则化

在这个活动中,我们将使用来自 scikit-learn 包的相同逻辑回归模型。然而,这次我们将为模型添加正则化,并搜索最优的正则化参数——这个过程通常称为超参数调优。在训练完模型后,我们将测试预测结果,并将模型评估指标与基准模型以及没有正则化的模型进行比较。

我们将采取的步骤如下:

  1. '../data/OSI_feats_e3.csv''../data/OSI_target_e2.csv'加载在线购物者购买意图数据集的特征和目标数据。

  2. 为每个featuretarget数据集创建trainingtest数据集。training数据集将用于训练,模型的评估将使用test数据集。

  3. 实例化一个scikit-learnlinear_model包中LogisticRegressionCV类的模型实例。

  4. 将模型拟合到training数据。

  5. 使用训练后的模型对test数据集进行预测。

  6. 通过比较模型的得分与true值来评估模型的表现,并使用评估指标。

实施这些步骤后,您应该得到以下预期输出:

l1
Precision: 0.7300
Recall: 0.4078
fscore: 0.5233
l2
Precision: 0.7350
Recall: 0.4106
fscore: 0.5269

注意

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

这项活动教会了我们如何将正则化交叉验证结合使用,以适当地对模型进行评分。我们已经学习了如何使用正则化和交叉验证将模型拟合到数据上。正则化是确保模型不对训练数据过拟合的一个重要技术。通过正则化训练的模型在新数据上的表现通常会更好,这也是机器学习模型的一般目标——在给定输入数据的新观测时预测目标。选择最优的正则化参数可能需要遍历多个不同的选择。

交叉验证是一种技术,用于确定哪组正则化参数最适合数据。交叉验证将使用不同的正则化参数值,在数据的不同划分上训练多个模型。这项技术确保选择最优的正则化参数组合,既不引入偏差,又能最小化方差。

总结

在本章中,我们讲解了如何准备数据并构建机器学习模型。我们通过使用 Python 和诸如 pandas、scikit-learn 等库来实现这一点。我们还使用了 scikit-learn 中的算法来构建我们的机器学习模型。

然后,我们学习了如何将数据加载到 Python 中,并且学会了如何操作数据,使得机器学习模型能够在这些数据上进行训练。这包括将所有列转换为数值数据类型。我们还使用 scikit-learn 算法创建了一个基础的逻辑回归分类模型。我们将数据集分为训练集和测试集,并将模型拟合到训练集上。我们使用模型评估指标(即准确率、精确度、召回率和 F-score)来评估模型在测试集上的表现。

最后,我们通过创建两种不同类型的正则化模型,对这个基础模型进行了迭代。我们利用交叉验证来确定正则化参数的最优值。

在下一章,我们将使用相同的概念,通过 Keras 库来创建模型。我们将使用相同的数据集,并尝试预测相同的目标值,以完成相同的分类任务。通过这样做,我们将学习如何在将神经网络拟合到数据时使用正则化交叉验证模型评估指标

第二章:2. 机器学习与深度学习

概览

在本章中,我们将开始使用 Keras 库创建人工神经网络(ANNs)。在利用该库进行建模之前,我们将介绍组成 ANNs 的数学知识——理解线性变换以及如何在 Python 中应用它们。你将牢固掌握构成 ANNs 的数学基础。到本章结束时,我们将通过使用 Keras 构建一个逻辑回归模型来应用这些知识。

介绍

在上一章中,我们讨论了机器学习的一些应用,甚至使用 scikit-learn Python 包构建了模型。上一章讲解了如何预处理现实世界的数据集,以便用于建模。为此,我们将所有变量转换为数值数据类型,并将categorical变量转换为dummy变量。我们使用logistic regression算法,根据online shoppers purchasing intention数据集中的购买意图对网站用户进行分类。通过将regularization添加到数据集中以提高模型的性能,我们进一步提升了模型构建技能。

在本章中,我们将继续学习如何构建机器学习模型,并扩展我们的知识,使我们能够使用 Keras 包构建一个人工神经网络ANN)。(记住,ANNs代表一类大型的机器学习算法,因为它们的结构类似于人类大脑中的神经元。)

Keras是一个专门用于构建神经网络的机器学习库。虽然 scikit-learn 的功能涵盖了更广泛的机器学习算法,但scikit-learn在神经网络方面的功能较为有限。

ANNs可以用于与其他算法相同的机器学习任务,例如将logistic regression用于classification任务,将linear regression用于regression问题,将k-means用于clustering。每当我们开始解决任何机器学习问题时,要确定它属于哪种任务(regressionclassificationclustering),我们需要问以下问题:

  • classification任务)或者你也可以预测值本身(这将是一个regression问题)。每种情况可能导致不同的后续操作或交易策略。

    下图展示了一个candlestick chart。它描述了金融数据中的价格波动,并展示了一只股票的价格。颜色表示股票价格在每个时间段内是上涨(绿色)还是下跌(红色),每根蜡烛图展示了数据的开盘、收盘、最高和最低值——这些都是股票价格的重要信息。

    注意

    你可以在此处找到本章的高质量彩色图片:packt.live/38nenXS

    建模这些数据的一个目标是预测第二天发生的情况。一个分类任务可能会预测股价是上涨还是下跌,由于只有两个可能的值,这将是一个二元分类任务。另一种选择是预测第二天股价的具体数值。由于预测的数值是一个连续变量,这将是一个回归任务:

![图 2.1:一个烛台图,显示了一个月内股票指数的变化]

](https://github.com/OpenDocCN/freelearn-dl-pt5-zh/raw/master/docs/dl-keras-ws/img/B15777_02_01.jpg)

图 2.1:一个烛台图,显示了一个月内股票指数的变化

  • 我们是否有合适的标注数据来训练模型? 对于监督学习任务,我们必须至少拥有一些标注数据才能训练模型。例如,如果我们想建立一个模型来将图像分类为狗的图像和猫的图像,我们需要训练数据、图像本身以及数据的标签,指示它们是狗的图像还是猫的图像。人工神经网络通常需要大量的数据。对于图像分类,这可能需要数百万张图像来开发准确、强大的模型。在决定哪种算法适用于特定任务时,这可能是一个决定性因素。

人工神经网络(ANNs)是一种机器学习算法,可以用于解决任务。它们在某些方面表现优秀,但在其他方面也有缺点,因此在选择该算法之前,应该考虑这些优缺点。深度学习网络与单层人工神经网络的区别在于它们的“深度”——即网络中隐藏层的总数。

因此,深度学习实际上只是机器学习的一个特定子组,依赖于具有多层的人工神经网络。我们经常接触到深度学习的结果,无论是图像分类模型,例如帮助在 Facebook 照片中标记朋友的朋友识别模型,还是推荐算法,例如帮助在 Spotify 上推荐你下一首最喜欢的歌曲。由于多种原因,深度学习模型正在逐渐取代传统的机器学习模型,包括深度学习模型擅长处理的非结构化数据规模的不断扩大和计算成本的降低。

是否选择使用人工神经网络还是传统机器学习算法,如线性回归和决策树,取决于经验以及对算法本身工作原理的理解。因此,使用传统机器学习算法或人工神经网络的好处将在下一节中提到。

人工神经网络相对于传统机器学习算法的优势

  • ImageNet 挑战赛(一个大规模的视觉识别挑战,旨在将图像分类为1000 个类别),人工神经网络在准确性上可以超过人类。

  • 逻辑回归决策树 在性能上存在平台期,而人工神经网络架构能够学习更高层次的特征——即输入特征的非线性组合,这些特征可能对分类或回归任务至关重要。这使得人工神经网络在提供大量数据时表现更好——尤其是那些具有深度架构的人工神经网络。例如,参加 ImageNet 挑战的人工神经网络需要提供 1400 万图像 进行训练。下图展示了深度学习算法和传统机器学习算法在数据量增加时的性能扩展:

图 2.2:深度学习算法和传统机器学习算法在数据量增加时的性能扩展

图 2.2:深度学习算法和传统机器学习算法在数据量增加时的性能扩展

  • 无需特征工程:人工神经网络能够识别出在建模过程中哪些特征是重要的,从而可以直接从原始数据进行建模。例如,在对狗和猫图像进行二分类时,不需要定义像动物的颜色、大小或体重等特征。图像本身就足以让人工神经网络成功地进行分类。在传统的机器学习算法中,这些特征必须通过手动和耗时的迭代过程进行工程化。

  • 16 层深度学习模型ImageNet 用来对 1000 种随机物体 进行分类。模型中学到的权重可以转移到其他物体的分类任务上,且所需时间大大减少。

然而,使用传统机器学习算法相对于人工神经网络(ANN)仍有一些优势,如下节所述。

传统机器学习算法相较于人工神经网络(ANN)的优势

  • VGG-16 拥有超过 138 百万参数,并且需要 1400 万手工标注的图像 来训练并学习所有的参数。

  • 具有成本效益:无论是财务上还是计算上,深度网络训练通常需要大量的计算能力和时间。这需要大量的资源,而这些资源可能并不是每个人都能获得的。此外,这些模型的调优过程非常耗时,并且需要领域专家熟悉模型的内部机制,才能达到最佳性能。

  • 黑盒,虽然它们在分类图像和其他任务时表现成功,但理解预测是如何产生的却是直觉上难以理解的,并且埋藏在层层计算中。因此,解释结果需要更多的努力。

层次化数据表示

人工神经网络之所以能够表现如此出色的原因之一是,网络中的大量层次允许它在许多不同层次上学习数据的表示。以下图示说明了这一点,图中展示了用于识别人脸的 ANN 的表示。在模型的低层,学习到的是简单的特征,如边缘和梯度,可以通过观察初始层中学习到的特征看出。当模型逐步推进时,低层特征的组合激活,形成面部部分,在模型的更高层,学习到的是通用的人脸。这就是特征层次结构,它展示了这种层级表示在模型构建和解释中的强大作用。

深度神经网络在实际应用中的输入示例通常包括图像、视频和自然语言文本。深度神经网络所学习的特征层次结构使得它们能够发现未标注、未结构化数据中的潜在结构,如图像、视频和自然语言文本,这使得它们在处理实际数据时非常有用——这些数据通常是未经处理的原始数据。

以下图示展示了深度学习模型学习到的表示的一个例子——低层特征如边缘梯度会共同激活,形成通用的面部形状,这些可以在更深层的网络中看到:

图 2.3:深度学习模型各部分学习到的表示

图 2.3:深度学习模型各部分学习到的表示

随着深度神经网络变得更加易于使用,许多公司开始利用它们的应用。以下是一些使用人工神经网络(ANNs)的公司示例:

  • Yelp:Yelp 使用深度神经网络更高效地处理、分类和标注其图片。由于照片是 Yelp 评论的一个重要方面,Yelp 公司特别注重对照片的分类和归类。通过深度神经网络,这一过程得以更加高效地实现。

  • Clarifai:这家基于云计算的公司能够利用深度神经网络模型对图片和视频进行分类。

  • Enlitic:这家公司使用深度神经网络来分析医学影像数据,如 X 光或 MRI。使用这种网络技术可以提高诊断准确性,减少诊断时间和成本。

现在我们理解了使用人工神经网络(ANNs)的潜在应用后,我们可以理解它们工作的背后数学原理。尽管它们看起来可能令人生畏且复杂,但它们可以分解为一系列线性和非线性变换,而这些变换本身是容易理解的。一个 ANN 是通过按顺序组合一系列线性和非线性变换创建的。下一部分将讨论线性变换的基本组件和操作,这些变换构成了 ANN 的数学基础。

线性变换

在这一部分,我们将介绍线性变换。线性变换是使用人工神经网络(ANN)进行建模的核心。事实上,所有的 ANN 建模过程都可以被看作是一系列的线性变换。线性变换的工作组件包括标量、向量、矩阵和张量。像加法转置乘法这样的操作会作用于这些组件。

标量、向量、矩阵和张量

标量向量矩阵张量是任何深度学习模型的实际组件。理解如何使用这些组件,并理解可以对它们执行的操作,对于理解 ANN 的工作原理至关重要。标量向量矩阵张量这一通用实体的例子,因此,在本章中可能会使用张量这个术语,但它可以指代任何组件。标量向量矩阵是指具有特定维度数的张量

张量的秩是一个属性,用来确定张量所跨越的维度数。每个概念的定义如下:

  • 张量。例如,某一给定点的温度是一个标量场。

  • 张量。给定物体的速度是一个向量场的例子,因为它会在二维 (x,y)三维 (x,y,z) 空间中具有一个速度。

  • 矩阵是跨越二维的矩形数组,包含单一的数字。它们是二阶张量的例子。矩阵的一个应用实例是用于存储给定物体随时间变化的速度矩阵的一个维度包含给定方向上的速度,而另一个矩阵维度则包含每个给定的时间点。

  • 张量是封装标量向量矩阵的通用实体。通常,该名称用于秩为3或以上的张量。一个应用实例是存储多个物体随时间变化的速度矩阵的一个维度包含给定方向上的速度,另一个矩阵维度包含每个给定的时间点,第三个维度描述各种物体。

以下图示展示了标量向量矩阵三维张量的一些示例:

图 2.4:标量、向量、矩阵和张量的可视化表示

图 2.4:标量、向量、矩阵和张量的可视化表示

张量加法

张量可以相加以创建新的张量。我们将在本章中使用矩阵作为例子,但这一概念可以扩展到任何秩的张量。在特定条件下,矩阵可以与标量向量以及其他矩阵进行相加。

如果两个矩阵形状相同,它们可以相加(或相减)。对于这样的矩阵加法,结果矩阵是通过输入矩阵按元素相加得出的。因此,结果矩阵将与两个输入矩阵具有相同的形状。我们可以定义矩阵 C = [cij] 为矩阵和 C = A + B,其中 cij = aij + bij,并且 C 中的每个元素都是 AB 中相同元素的和。矩阵加法是交换律的,这意味着 A + B = B + A,顺序无关。矩阵加法也是结合律的,这意味着即使加法顺序不同,或者操作应用多次,结果也会相同:A + (B + C) = (A + B) + C

相同的矩阵加法原则适用于 标量向量张量。以下是一个示例:

图 2.5:矩阵与矩阵相加的示例

图 2.5:矩阵与矩阵相加的示例

标量也可以加到矩阵中。在这里,矩阵的每个元素都会单独加上标量,如下图所示:

图 2.6:矩阵与标量相加的示例

图 2.6:矩阵与标量相加的示例

如果两个矩阵的列数相同,可以将向量添加到矩阵中。这就是所谓的广播(broadcasting)。

练习 2.01:对向量、矩阵和张量进行各种操作

注意

本章中的练习和活动需要你在系统上安装 Python 3.7、Jupyter 和 NumPy。所有的练习和活动将主要在 Jupyter Notebook 中进行开发。除非有特别指示,否则建议为不同的任务保持独立的 Notebook。使用以下链接从本书的 GitHub 仓库下载它们:packt.live/2vpc9rO

在这个练习中,我们将演示如何在 Python 中创建并处理向量矩阵张量。我们假设你对标量已有一定了解。所有这些操作都可以通过 NumPy 库中的arraymatrix函数来实现。任何秩的张量都可以通过 NumPy 的array函数创建。

在开始之前,你应在工作目录中设置本章的文件和文件夹,使用类似于上一章的结构和命名约定。你可以通过与上面的 GitHub 仓库进行对比来验证你的文件夹结构。

按照以下步骤进行此练习:

  1. 打开 Jupyter Notebook 实现这个练习。导入必要的依赖项。创建一个一维数组,或向量,如下所示:

    import numpy as np
    vec1 = np.array([1, 2, 3, 4, 5, 6, 7, 8, 9, 10])
    vec1
    

    上面的代码产生了以下输出:

    array([ 1, 2, 3, 4, 5, 6, 7, 8, 9, 10])
    
  2. 使用array函数创建一个二维数组,或矩阵

    mat1 = np.array([[1, 2, 3], [4, 5, 6], [7, 8, 9], [10, 11, 12]])
    mat1
    

    上面的代码产生了以下输出:

    array([[ 1, 2, 3],
           [ 4, 5, 6],
           [ 7, 8, 9],
           [10, 11, 12]])
    
  3. 使用matrix函数创建矩阵,这将显示类似的输出:

    mat2 = np.matrix([[1, 2, 3], [4, 5, 6], \
                      [7, 8, 9], [10, 11, 12]])
    
  4. 使用array函数创建一个三维数组张量

    ten1 = np.array([[[1, 2, 3], [4, 5, 6]], \
                     [[7, 8, 9], [10, 11, 12]]])
    ten1
    

    上述代码产生以下输出:

    array([[[ 1, 2, 3],
            [ 4, 5, 6],
            [[ 7, 8, 9],
            [10, 11, 12]]])
    
  5. 确定给定向量矩阵张量形状非常重要,因为某些操作(如加法乘法)只能应用于特定形状的组件。可以使用shape方法来确定 n 维数组的形状。编写以下代码以确定vec1形状

    vec1.shape
    

    上述代码产生以下输出:

    (10, )
    
  6. 编写以下代码以确定mat1形状

    mat1.shape
    

    上述代码产生以下输出:

    (4, 3)
    
  7. 编写以下代码以确定ten1形状

    ten1.shape
    

    上述代码产生以下输出:

    (2, 2, 3)
    
  8. 创建一个具有四行三列矩阵,可以使用任意数字。打印结果矩阵以验证其形状

    mat1 = np.matrix([[1, 2, 3], [4, 5, 6], [7, 8, 9], [10, 11, 12]])
    mat1
    

    上述代码产生以下输出:

    matrix([[ 1, 2, 3],
            [ 4, 5, 6],
            [ 7, 8, 9],
            [10, 11, 12]])
    
  9. 创建另一个具有四行三列的矩阵,可以使用任意数字。打印结果矩阵以验证其形状

    mat2 = np.matrix([[2, 1, 4], [4, 1, 7], [4, 2, 9], [5, 21, 1]])
    mat2
    

    上述代码产生以下输出:

    matrix([[ 2, 1, 4],
            [ 4, 1, 7],
            [ 4, 2, 9],
            [ 5, 21, 1]])
    
  10. 矩阵 1矩阵 2相加:

    mat3 = mat1 + mat2
    mat3
    

    上述代码产生以下输出:

    matrix([[ 3, 3, 7],
            [ 8, 6, 13],
            [ 11, 10, 18],
            [ 15, 32, 13]])
    
  11. 使用以下代码将标量加到数组中:

    mat1 + 4
    

    上述代码产生以下输出:

    matrix([[ 5, 6, 7],
            [ 8, 9, 10],
            [ 11, 12, 13],
            [ 14, 15, 16]])
    

在这个练习中,我们学习了如何执行向量矩阵张量的各种操作。我们还学会了如何确定矩阵形状

注意

若要访问此特定部分的源代码,请参考packt.live/2NNQ7VA

你还可以在线运行这个示例,访问packt.live/3eUDtQA

重塑

任何大小的张量都可以被重塑,只要总元素数量保持不变。例如,(4x3)矩阵可以被重塑为(6x2)矩阵,因为它们的元素总数都是12(即维度数)也可以在重塑过程中发生变化。例如,(4x3)矩阵可以重塑为(3x2x2)张量,在这里,2变为3(4x3)矩阵还可以重塑为(12x1)向量,此时,2变为1

下图展示了张量重塑的过程——左侧是一个形状(4x1x3)的张量,它可以被重塑为形状(4x3)的张量。在这里,元素的数量(12)保持不变,尽管张量的形状发生了变化:

图 2.7:将(4x1x3)张量重塑为(4x3)张量的可视化表示

图 2.7:将(4x1x3)张量重塑为(4x3)张量的可视化表示

矩阵转置

矩阵的 转置 是一种操作,它使矩阵沿其对角线翻转。当发生这种情况时,行变为列,反之亦然。转置操作通常用矩阵上方的 T 上标表示。任何秩的张量也可以转置:

图 2.8:矩阵转置的可视化表示

图 2.8:矩阵转置的可视化表示

下图显示了矩阵 AB 的转置特性:

图 2.9:矩阵转置特性,其中 A 和 B 是矩阵

图 2.9:矩阵转置特性,其中 A 和 B 是矩阵

一个方阵(即行数和列数相等的矩阵)被称为对称的,如果矩阵的转置等于原矩阵。

练习 2.02:矩阵重塑和转置

在这个练习中,我们将演示如何重塑和转置矩阵。这个过程非常重要,因为某些操作只有在张量的维度匹配时才能应用。例如,张量乘法只有在两个张量的内维度匹配时才能应用。重塑或转置张量是修改张量维度的一种方式,以确保可以应用某些操作。按照以下步骤完成此练习:

  1. 从开始菜单打开一个 Jupyter notebook 来实现这个练习。创建一个 二维数组,具有 四行三列,如下所示:

    import numpy as np
    mat1 = np.array([[1, 2, 3], [4, 5, 6], [7, 8, 9], [10, 11, 12]])
    mat1
    

    这将产生以下输出:

    array([[ 1,  2,  3],
           [ 4,  5,  6],
           [ 7,  8,  9],
           [10, 11, 12]])
    

    我们可以通过查看矩阵的形状来确认它的维度:

    mat1.shape
    

    输出如下所示:

    (4, 3)
    
  2. 将数组重塑为 三行四列,如下所示:

    mat2 = np.reshape(mat1, [3,4])
    mat2
    

    上述代码生成了以下输出:

    array([[ 1, 2, 3, 4],
           [ 5, 6, 7, 8],
           [ 9, 10, 11, 12]])
    
  3. 通过打印数组的 shape 来确认这一点:

    mat2.shape
    

    上述代码生成了以下输出:

    (3, 4)
    
  4. 将矩阵重塑为一个 三维数组,如下所示:

    mat3 = np.reshape(mat1, [3,2,2])
    mat3
    

    上述代码生成了以下输出:

    array([[[ 1, 2],
            [ 3, 4]],
           [[ 5, 6],
            [ 7, 8]],
           [[ 9, 10],
            [ 11, 12]]]) 
    
  5. 打印数组的 shape 来确认其维度:

    mat3.shape
    

    上述代码生成了以下输出:

    (3, 2, 2)
    
  6. 将矩阵重塑为一个 一维数组,如下所示:

    mat4 = np.reshape(mat1, [12])
    mat4
    

    上述代码生成了以下输出:

    array([ 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12])
    
  7. 通过打印数组的 shape 来确认这一点:

    mat4.shape
    

    上述代码生成了以下输出:

    (12, )
    
  8. 对数组进行转置会使其沿对角线翻转。对于一维数组,行向量会转换为列向量,反之亦然。对于二维数组或矩阵,每一行变为一列,反之亦然。使用 T 方法调用数组的转置:

    mat = np.matrix([[1, 2, 3], [4, 5, 6], [7, 8, 9], [10, 11, 12]])
    mat.T
    

    下图显示了上述代码的输出:

    图 2.10:转置函数的可视化演示

    图 2.10:转置函数的可视化演示

  9. 检查矩阵及其转置的 shape,以验证维度是否已改变:

    mat.shape
    

    上述代码生成了以下输出:

    (4, 3)
    
  10. 检查转置矩阵的shape

    mat.T.shape
    

    上面的代码生成了以下输出:

    (3, 4)
    
  11. 验证当矩阵被重塑和转置时,矩阵元素不匹配:

    np.reshape(mat1, [3,4]) == mat1.T
    

    上面的代码生成了以下输出:

    array([[ True, False, False, False],
           [False, False, False, False],
           [False, False, False, True]], dtype = bool)
    

    在这里,我们可以看到只有第一个和最后一个元素匹配。

在这一部分中,我们介绍了线性代数的一些基本组成部分,包括标量、向量、矩阵和张量。我们还介绍了一些基本的线性代数组件操作,如加法、转置和重塑。通过这些操作,我们学习了如何通过使用NumPy库中的函数将这些概念付诸实践。

注意

要访问此特定部分的源代码,请参考packt.live/3gqBlR0

你也可以在网上运行这个例子,访问packt.live/3eYCChD

在下一部分中,我们将通过讲解与人工神经网络(ANN)相关的最重要的变换之一——矩阵乘法,来扩展我们对线性变换的理解。

矩阵乘法

矩阵乘法是神经网络运算的基础。虽然加法的规则简单直观,但矩阵和张量的乘法规则则更为复杂。矩阵乘法不仅仅是简单的逐元素相乘,而是实现了一个更复杂的过程,涉及一个矩阵的整个行和另一个矩阵的整个列。在这一部分中,我们将解释二维张量或矩阵的乘法如何运作;然而,较高阶的张量也可以进行乘法运算。

给定一个矩阵,A = [aij]m x n,另一个矩阵,B = [bij]n x p,两个矩阵的乘积是 C = AB = [Cij]m x p,其中每个元素 cij 按元素定义为 公式。注意,结果矩阵的形状与矩阵乘积的外维度相同,或者说是第一个矩阵的行数和第二个矩阵的列数。为了使乘法成立,矩阵乘积的内维度必须匹配,或者说第一个矩阵的列数和第二个矩阵的列数必须相同。

矩阵乘法的内外维度概念可以从下图中看到:

图 2.11: 矩阵乘法中内外维度的可视化表示

图 2.11: 矩阵乘法中内外维度的可视化表示

与矩阵加法不同,矩阵乘法不是交换律的,这意味着矩阵在乘积中的顺序很重要:

图 2.12: 矩阵乘法不是交换律的

图 2.12: 矩阵乘法不是交换律的

例如,假设我们有以下两个矩阵:

图 2.13: 两个矩阵,A 和 B

图 2.13:两个矩阵 A 和 B

构建乘积的一种方法是先将矩阵AB相乘:

图 2.14:矩阵 A 与 B 相乘的可视化表示

图 2.14:矩阵 A 与 B 相乘的可视化表示

这将得到一个2x2矩阵。构建乘积的另一种方法是先将BA相乘:

图 2.15:矩阵 B 与 A 相乘的可视化表示

图 2.15:矩阵 B 与 A 相乘的可视化表示

在这里,我们可以看到由3x3矩阵的乘积形成的矩阵与由AB乘积形成的矩阵非常不同。

标量矩阵乘法要简单得多,它只是矩阵中每个元素与标量相乘的结果,因此 λA = [λaij]m x n,其中 λ 是标量,A 是矩阵。

在接下来的练习中,我们将通过使用NumPy库在 Python 中执行矩阵乘法来实践我们的理解。

练习 2.03:矩阵乘法

在本练习中,我们将展示如何将矩阵相乘。请按照以下步骤完成此练习:

  1. 从开始菜单打开一个 Jupyter notebook 来实现此练习。

    为了演示矩阵乘法的基本原理,从两个相同形状的矩阵开始:

    import numpy as np
    mat1 = np.array([[1, 2, 3], [4, 5, 6], \
                     [7, 8, 9], [10, 11, 12]])
    mat2 = np.array([[2, 1, 4], [4, 1, 7], \
                     [4, 2, 9], [5, 21, 1]])
    
  2. 由于这两个矩阵形状相同且不是方阵,因此不能直接相乘,否则,乘积的内维度将不匹配。我们可以通过对其中一个矩阵进行转置来解决这个问题;然后,就可以进行乘法运算。取第二个矩阵的转置,这意味着一个(4x3)矩阵与一个(3x4)矩阵相乘。结果将是一个(4x4)矩阵。使用dot方法进行乘法运算:

    mat1.dot(mat2.T)
    

    上述代码产生了以下输出:

    array([[ 16, 27, 35, 50],
           [ 37, 63, 80, 131],
           [ 58, 99, 125, 212],
           [ 79, 135, 170, 293]])
    
  3. 取第一个矩阵的转置,这意味着一个(3x4)矩阵与一个(4x3)矩阵相乘。结果将是一个(3x3)矩阵

    mat1.T.dot(mat2)
    

    上述代码产生了以下输出:

    array([[ 96, 229, 105],
           [ 111, 254, 126],
           [ 126, 279, 147]])
    
  4. 重新调整其中一个数组的形状,以确保矩阵乘法的内维度匹配。例如,我们可以将第一个数组调整为(3x4)矩阵,而不是转置。请注意,结果与转置时的结果不同:

    np.reshape(mat1, [3,4]).dot(mat2)
    

    上述代码产生了以下输出:

    array([[ 42, 93, 49],
           [ 102, 193, 133],
           [ 162, 293, 217]])
    

在本练习中,我们学习了如何将两个矩阵相乘。这个概念不仅适用于二阶张量,还可以应用于所有阶张量。如果不同阶的张量的内维度匹配,它们甚至可以进行乘法运算。

注意

要访问此特定部分的源代码,请参见packt.live/38p0RD7

您也可以在线运行此示例,网址是packt.live/2VYI1xZ

下一道练习展示了如何将三维张量相乘。

练习 2.04:张量乘法

在本练习中,我们将应用矩阵乘法知识到高阶张量。请按照以下步骤完成此练习:

  1. 打开开始菜单中的 Jupyter 笔记本来实现此练习。首先使用 NumPy 库和array函数创建一个三维张量。导入所有必要的依赖:

    import numpy as np
    mat1 = np.array([[[1, 2, 3], [4, 5, 6]], [[1, 2, 3], [4, 5, 6]]])
    mat1
    

    上述代码产生以下输出:

    array([[[ 1, 2, 3],
            [ 4, 5, 6],
            [[ 1, 2, 3],
            [ 4, 5, 6]]])
    
  2. 使用shape方法确认形状:

    mat1.shape
    

    该张量的形状为(2x2x3)。

  3. 创建一个新的三维张量,我们可以用它来与张量相乘。对原始矩阵取转置:

    mat2 = mat1.T
    mat2
    

    上述代码产生以下输出:

    array([[[ 1, 1],
            [ 4, 4]],
           [[ 2, 2],
            [ 5, 5]],
           [[ 3, 3],
            [ 6, 6]]])
    
  4. 使用shape方法确认形状:

    mat2.shape
    

    该张量的形状为(3x2x2)。

  5. 两个矩阵点积,如下所示:

    mat3 = mat2.dot(mat1)
    mat3
    

    上述代码产生以下输出:

    array([[[[ 5, 7, 9],
             [ 5, 7, 9]],
            [[ 20, 28, 36],
             [ 20, 28, 36]]],
           [[[ 10, 14, 18],
             [ 10, 14, 18]],
            [[ 25, 35, 45],
             [ 25, 35, 45]]],
           [[[ 15, 21, 27],
             [ 15, 21, 27]],
            [[ 30, 42, 54],
             [ 30, 42, 54]]]])
    
  6. 查看此结果张量的形状

    mat3.shape
    

    上述代码产生以下输出:

    (3, 2, 2, 3)
    

    现在,我们有了一个四维张量。

在本练习中,我们学习了如何使用 Python 中的 NumPy 库执行矩阵乘法。尽管在使用 Keras 创建 ANN 时,我们不需要直接执行矩阵乘法,但理解其底层数学原理仍然是有用的。

注释

要访问此特定部分的源代码,请参考packt.live/31G1rLn

你也可以在网上运行此示例,网址为packt.live/2AriZjn

Keras 简介

构建 ANN 涉及创建节点的层。每个节点可以被视为在训练过程中学习的权重张量。一旦 ANN 被拟合到数据,预测是通过逐层将输入数据与权重矩阵相乘来进行的,当需要时应用任何其他线性变换,如激活函数,直到到达最终输出层。每个权重张量的大小由输入节点的形状和输出节点的形状决定。例如,在一个单层 ANN 中,我们的单一隐藏层的大小可以如下所示:

图 2.16:解决单层 ANN 的隐藏层维度

图 2.16:解决单层 ANN 的隐藏层维度

如果输入的特征矩阵有n行,或观测值,和m列,或特征,并且我们希望预测的目标有n行(每个观测值一个行)和一列(预测值),那么我们可以通过需要的方式来确定隐藏层的大小,以使矩阵乘法有效。以下是单层人工神经网络(ANN)的表示:

图 2.17:单层 ANN 的表示

图 2.17:单层 ANN 的表示

在这里,我们可以确定权重矩阵的大小将是(mx1),以确保矩阵乘法是有效的。

如果我们在一个 ANN 中有多个隐藏层,那么我们在这些权重矩阵的大小上将有更多自由。事实上,可能性是无穷的,具体取决于层数和每层的节点数。然而,在实践中,某些架构设计比其他设计更有效,正如我们将在本书中学到的那样。

一般来说,Keras 将神经网络构建中的大部分线性代数进行了抽象,使用户能够专注于设计架构。对于大多数网络,只需要输入大小、输出大小和每个隐藏层中的节点数即可在 Keras 中创建网络。

Keras 中最简单的模型结构是Sequential模型,可以从keras.models导入。Sequential类的模型描述了一个由一系列层组成的人工神经网络(ANN)。可以如下实例化Sequential模型:

from keras.models import Sequential
model = Sequential()

可以向该模型实例添加层,以创建模型的结构。

注意

在初始化模型之前,使用 NumPy 的随机库中的seed函数和 TensorFlow 的随机库中的set_seed函数设置种子是很有帮助的。

层类型

层的概念是 Keras 核心 API 的一部分。可以将层看作是节点的组合,在每个节点上发生一系列计算。在 Keras 中,层的所有节点都可以通过初始化该层本身来初始化。通用层节点的单独操作可以通过以下图示看到。在每个节点上,输入数据通过矩阵乘法与一组权重相乘,正如我们在本章之前所学到的那样。权重与输入的乘积之和会被应用,这可能包括或不包括偏置,如下图中输入节点等于1所示。还可以对该矩阵乘法的输出应用进一步的函数,例如激活函数:

图 2.18:层节点的示意图

图 2.18:层节点的示意图

Keras 中一些常见的层类型如下:

  • 全连接层:这是一个完全连接的层,其中层的所有节点都直接连接到所有输入和所有输出。用于分类或回归任务的人工神经网络(ANNs)通常在其架构中有很大一部分层采用这种类型。

  • 卷积层:这种层类型创建一个卷积核,并将其与输入层进行卷积,产生一个输出张量。此卷积可以发生在一个或多个维度上。用于图像分类的 ANNs 通常在其架构中包含一个或多个卷积层。

  • Pooling:这种类型的层用于减少输入层的维度。常见的池化类型包括最大池化,其中给定窗口的最大值被传递到输出,或平均池化,其中窗口的平均值被传递到输出。这些层通常与卷积层结合使用,目的是减少后续层的维度,从而减少训练参数的数量,同时尽可能减少信息损失。

  • Recurrent:递归层从序列中学习模式,因此每个输出依赖于前一步的结果。模拟序列数据(如自然语言或时间序列数据)的人工神经网络(ANNs)通常会包含一个或多个递归层类型。

Keras 中有其他层类型;然而,在使用 Keras 构建模型时,这些是最常见的层类型。

让我们通过实例化一个 Sequential 类的模型并向模型中添加一个 Dense 层来演示如何添加层。可以按我们希望执行计算的顺序,将后续层添加到模型中,这些层可以从 keras.layers 导入。需要指定单元或节点的数量。这个值也会决定该层输出结果的形状。可以通过以下方式将 Dense 层添加到 Sequential 模型中:

from keras.layers import Dense
from keras.models import Sequential
input_shape = 20
units = 1
model.add(Dense(units, input_dim=input_shape))

注意

在第一层之后,不需要指定输入维度,因为它由前一层决定。

激活函数

激活函数通常应用于节点输出,以限制或限定其值。每个节点的值是没有界限的,可能具有任何值,从负无穷大到正无穷大。在神经网络中,这可能会带来问题,因为权重和损失的计算结果可能会趋向无穷大,产生无法使用的结果。激活函数在这方面可以提供帮助,通过限定值的范围。通常,这些激活函数将值推向两个极限。激活函数还用于决定节点是否应“激活”。常见的激活函数如下:

  • Step 函数:如果值超过某个阈值,则为非零值;否则为零。

  • Linear 函数:公式,即输入值的标量乘法。

  • Sigmoid 函数:公式,例如平滑的阶跃函数,具有平滑的梯度。由于其值被限制在 0 到 1 之间,这个激活函数对于分类任务非常有用。

  • x=0

  • ReLU 函数:公式,否则为 0。

现在我们已经了解了一些主要组件,我们可以开始看看如何利用这些组件创建有用的神经网络。事实上,我们可以用本章学到的所有概念来创建一个逻辑回归模型。逻辑回归模型通过计算输入与一组学习到的权重的乘积之和,然后将输出通过一个逻辑函数来操作。可以通过一个具有 Sigmoid 激活函数的单层神经网络来实现这一点。

激活函数可以以与添加层相同的方式添加到模型中。激活函数将应用于模型中前一步的输出。可以按如下方式向Sequential模型添加tanh激活函数:

from keras.layers import Dense, Activation
from keras.models import Sequential
input_shape = 20
units = 1
model.add(Dense(units, input_dim=input_shape))
model.add(Activation('tanh'))

注意

激活函数也可以通过将其作为参数添加到定义层时来添加到模型中。

模型拟合

一旦模型的架构创建完成,必须对模型进行编译。编译过程配置所有学习参数,包括使用的优化器、要最小化的损失函数,以及可选的指标(例如准确度),这些指标将在模型训练的各个阶段进行计算。模型使用compile方法进行编译,如下所示:

model.compile(optimizer='adam', loss='binary_crossentropy', \
              metrics=['accuracy'])

在模型编译后,就可以将其拟合到训练数据上。通过使用fit方法实例化模型来实现这一点。使用fit方法时有以下有用的参数:

  • X:用于拟合数据的训练特征数据的数组。

  • y:训练目标数据的数组。

  • epochs:运行模型的训练周期数。一个训练周期是对整个训练数据集的一次迭代。

  • batch_size:每次梯度更新时使用的训练数据样本数。

  • validation_split:用于验证的训练数据的比例,该数据将在每个训练周期后进行评估。

  • shuffle:指示是否在每个训练周期前打乱训练数据。

fit方法可以按以下方式在模型上使用:

history = model.fit(x=X_train, y=y_train['y'], \
                    epochs=10, batch_size=32, \
                    validation_split=0.2, shuffle=False)

调用模型的fit方法时,保存其输出是有益的,因为它包含了模型在整个训练过程中的性能信息,包括每个周期后的损失值。如果定义了验证分割,损失将在每个周期后对验证集进行评估。同样,如果在训练中定义了任何指标,这些指标也会在每个周期后进行计算。绘制这些损失和评估指标有助于确定模型在各个训练周期上的性能。模型的损失函数随训练周期变化的图形可以如下可视化:

import matplotlib.pyplot as plt
%matplotlib inline
plt.plot(history.history['loss'])
plt.show()

Keras 模型可以通过利用模型实例的evaluate方法进行评估。该方法返回损失值以及传递给模型用于训练的任何指标。当评估一个样本外的测试数据集时,可以按以下方式调用该方法:

test_loss = model.evaluate(X_test, y_test['y'])

这些模型拟合步骤代表了使用 Keras 包构建、训练和评估模型时需要遵循的基本步骤。从这里开始,构建和评估模型的方式有无数种,具体取决于您希望完成的任务。在接下来的活动中,我们将创建一个人工神经网络(ANN)来执行与第一章《使用 Keras 的机器学习入门》相同的任务。事实上,我们将用 ANN 重现逻辑回归算法。因此,我们预计这两个模型之间的性能会相似。

活动 2.01:使用 Keras 创建逻辑回归模型

在本活动中,我们将使用 Keras 库创建一个基本模型。我们将执行与第一章《使用 Keras 的机器学习入门》相同的分类任务。我们将使用相同的在线购物购买意图数据集,并尝试预测相同的变量。

在上一章中,我们使用了逻辑回归模型来预测用户在给定关于在线会话行为和网页属性的各种特征时,是否会从网站购买产品。在本活动中,我们将介绍 Keras 库,尽管我们将继续使用之前介绍的库,如 pandas,以便轻松加载数据,和 sklearn,用于任何数据预处理和模型评估指标。

注意

已为您提供了预处理过的数据集,可以用于本次活动。您可以从 packt.live/2ApIBwT 下载它们。

完成此活动的步骤如下:

  1. 加载处理后的特征数据和目标数据集。

  2. 将训练数据和目标数据分割为训练集和测试集。模型将拟合训练集,测试集将用于评估模型。

  3. keras.models 库中实例化一个 Sequential 类的模型。

  4. 向模型实例中添加一个 keras.layers 包中的 Dense 类单层。节点数应等于特征数据集中的特征数量。

  5. 向模型添加一个 sigmoid 激活函数。

  6. 通过指定要使用的优化器、评估损失指标以及每个 epoch 后要评估的其他指标来编译模型实例。

  7. 将模型拟合到训练数据,指定要运行的 epoch 数量和使用的验证集划分比例。

  8. 绘制损失和其他评估指标与 epoch 之间的关系,这些指标将在训练集和验证集上进行评估。

  9. 在测试数据集上评估损失和其他评估指标。

完成这些步骤后,您应该会得到以下预期输出:

2466/2466 [==============================] - 0s 15us/step
The loss on the test set is 0.3632 and the accuracy is 86.902%

注意

本活动的解决方案可以在第 356 页找到。

在本次活动中,我们探讨了在 Keras 中创建人工神经网络(ANN)的一些基本概念,包括各种层类型和激活函数。我们使用这些组件创建了一个简单的逻辑回归模型,该模型给出的结果与我们在第一章《Keras 与机器学习入门》中使用的逻辑回归模型相似。我们学习了如何使用 Keras 库构建模型,使用实际数据集训练模型,并在测试数据集上评估模型的表现,以便提供对模型表现的公正评估。

总结

在本章中,我们涵盖了与机器学习相关的各种线性代数组件和操作。这些组件包括标量、向量、矩阵和张量。对这些张量应用的操作包括加法、转置和乘法——这些操作都是理解 ANN 底层数学的基础。

我们还学习了 Keras 包的一些基础知识,包括每个节点上发生的数学运算。我们复现了上一章中的模型,在该模型中,我们构建了一个逻辑回归模型来预测来自在线购物购买意图数据集的相同目标。然而,在这一章中,我们使用 Keras 库创建了一个基于人工神经网络(ANN)的模型,而不是使用 scikit-learn 的逻辑回归模型。我们通过使用 ANN 实现了相似的准确性。

本书接下来的章节将继续使用我们在本章中学到的相同概念;然而,我们将继续使用 Keras 包构建更复杂的 ANN。我们将通过创建具有多个隐藏层的模型,将 ANN 扩展到超过单层的结构。通过向我们的 ANN 中添加多个隐藏层,我们将把“深度”带入“深度学习”。我们还将解决欠拟合和过拟合的问题,因为它们与使用 ANN 训练模型相关。

第三章:3. 使用 Keras 的深度学习

概述

在本章中,你将尝试不同的神经网络架构。你将创建 Keras 顺序模型——构建单层和多层模型——并评估训练模型的性能。不同架构的网络将帮助你理解过拟合和欠拟合。到本章结束时,你将探索如何使用早停方法来应对对训练数据的过拟合问题。

引言

在上一章中,你了解了神经网络的数学基础,包括与标量向量矩阵张量相关的线性变换。然后,你使用 Keras 实现了你的第一个神经网络,通过构建一个逻辑回归模型将网站用户分类为会购买和不会购买的人。

在本章中,你将扩展使用 Keras 构建神经网络的知识。本章介绍了深度学习的基础知识,并为你提供了必要的基础,使你能够构建高度复杂的神经网络架构。我们将从扩展逻辑回归模型到一个简单的单层神经网络开始,然后逐步进入具有多个隐藏层的更复杂的神经网络。

在这个过程中,你将了解神经网络的基本概念,包括用于做出预测的前向传播、计算损失、用于计算损失对模型参数导数的反向传播,最后是用于学习模型最佳参数的梯度下降。你还将了解如何根据激活函数损失函数优化器等不同的选择来构建和训练神经网络。

此外,你将学习如何评估模型,同时了解过拟合欠拟合等问题,同时分析这些问题如何影响模型的性能以及如何检测它们。你将了解到在相同的数据集上评估模型的缺陷,以及将一部分可用数据集保留用于评估的替代方法。接着,你将学习如何比较模型在这两个数据集子集上的错误率,从而检测出如高偏差和高方差等问题。最后,你将学习一种叫做早停的技术,用于减少过拟合,该技术通过将模型的错误率与数据集的两个子集进行比较来实现。

构建你的第一个神经网络

在本节中,你将学习深度学习的表示和概念,例如前向传播—通过网络传播数据,乘以每个节点每个连接的输入值与权重的乘积,以及反向传播—计算损失函数相对于矩阵中权重的梯度,和梯度下降—用于寻找损失函数最小值的优化算法。

我们不会深入探讨这些概念,因为本书并不需要。然而,这部分内容将帮助任何想将深度学习应用于问题的人。

接下来,我们将开始使用 Keras 实现神经网络。此外,我们将坚持最简单的情况,即具有单个隐藏层的神经网络。你将学习如何在 Keras 中定义模型,选择超参数—在训练模型之前设置的模型参数,然后训练你的模型。在本节的最后,你将有机会通过在 Keras 中实现神经网络进行实践,以便你能够在数据集上执行分类并观察神经网络如何优于逻辑回归等简单模型。

逻辑回归到深度神经网络

第一章使用 Keras 的机器学习简介中,你学习了逻辑回归模型,然后在第二章机器学习与深度学习中学习了如何使用 Keras 将其实现为顺序模型。从技术上讲,逻辑回归涉及一个非常简单的神经网络,只有一个隐藏层,且其隐藏层只有一个节点。

具有二维输入的逻辑回归模型概览可以在以下图片中看到。你在这张图片中看到的被称为一个节点单元,在深度学习领域中由绿色圆圈表示。正如你可能已经注意到的那样,逻辑回归术语和深度学习术语之间存在一些差异。在逻辑回归中,我们将模型的参数称为系数截距。在深度学习模型中,参数被称为权重(w)和偏置(b):

图 3.1:具有二维输入的逻辑回归模型概览

图 3.1:具有二维输入的逻辑回归模型概览

在每个节点/单元处,输入会与一些权重相乘,然后将一个偏置项加到这些加权输入的和上。这可以在前一张图中节点上方的计算中看到。inputsX1X2weightsW1W2biasb。接下来,非线性函数(例如,在逻辑回归模型中是 sigmoid 函数)会应用于加权输入和偏置项的和,以计算节点的最终输出。在前一张图中的计算中,这是σ。在深度学习中,非线性函数被称为激活函数,节点的输出被称为该节点的激活值

可以通过将逻辑回归节点/单元垂直堆叠在同一层上来构建单层神经网络,如下图所示。每个输入层的值,X1X2,都传递给隐藏层的三个节点:

图 3.2:具有二维输入的单层神经网络概览且隐藏层的大小为 3

图 3.2:具有二维输入和大小为 3 的隐藏层的单层神经网络概览

还可以通过将多个处理节点层叠在一起构建多层神经网络,如下图所示。下图展示了一个具有二维输入的双层神经网络:

图 3.3:具有二维输入的双层神经网络概览

图 3.3:具有二维输入的双层神经网络概览

前两张图展示了神经网络最常见的表示方式。每个神经网络由输入层输出层和一个或多个隐藏层组成。如果只有一个隐藏层,则该网络被称为浅层神经网络。另一方面,具有多个隐藏层的神经网络被称为深层神经网络,其训练过程被称为深度学习

图 3.2展示了一个只有一个隐藏层的神经网络,因此这是一个浅层神经网络,而图 3.3中的神经网络有两个隐藏层,因此它是一个深层神经网络。输入层通常位于左侧。在图 3.3的情况下,这些是特征X1X2,它们被输入到第一个隐藏层,该层有三个节点。箭头表示应用于输入的权重值。在第二个隐藏层处,第一个隐藏层的结果成为第二个隐藏层的输入。第一个和第二个隐藏层之间的箭头表示权重。输出通常是最右边的层,在图 3.3中,用Y标记的层表示输出。

注意

在某些资源中,您可能会看到一个网络,如前面图示所示,被称为四层网络。这是因为输入层和输出层与隐藏层一起计算。然而,更常见的惯例是只计算隐藏层,因此我们之前提到的网络将被称为二层网络。

在深度学习中,输入层的节点数等于输入数据的特征数,输出层的节点数等于输出数据的维度。然而,您需要选择隐藏层的节点数或隐藏层的大小。如果选择较大的层,模型会变得更加灵活,并能够建模更复杂的函数。这种灵活性的增加需要更多的训练数据和更多的计算来训练模型。开发者需要选择的参数称为超参数,包括层数和每层节点数等参数。常见的超参数包括训练的轮次和使用的损失函数。

在下一部分,我们将介绍在每个隐藏层之后应用的激活函数

激活函数

除了层的大小,您还需要为每个添加到模型中的隐藏层选择一个激活函数,输出层也需要进行相同的选择。我们在逻辑回归模型中了解过 Sigmoid 激活函数。然而,在 Keras 中构建神经网络时,您可以选择更多的激活函数。例如,Sigmoid 激活函数是二分类任务中作为输出层激活函数的一个不错选择,因为 Sigmoid 函数的结果限制在 0 到 1 之间。一些常用的深度学习激活函数包括Sigmoid/Logistictanh双曲正切)和Rectified Linear UnitReLU)。

以下图显示了sigmoid激活函数:

图 3.4:Sigmoid 激活函数

图 3.4:Sigmoid 激活函数

以下图显示了tanh激活函数:

图 3.5:tanh 激活函数

图 3.5:tanh 激活函数

以下图显示了ReLU激活函数:

图 3.6:ReLU 激活函数

图 3.6:ReLU 激活函数

正如你在 图 3.43.5 中看到的,sigmoid 函数的输出始终在01之间,而 tanh 的输出始终在-11之间。这使得tanh在隐藏层中成为更好的选择,因为它保持了每层输出的平均值接近零。事实上,sigmoid仅在构建二元分类器的输出层的激活函数时是一个好选择,因为其输出可以解释为给定输入属于某一类的概率。

因此,tanhReLU是隐藏层激活函数的最常见选择。事实证明,在使用ReLU激活函数时,学习过程更快,因为对于大于0的输入,它具有固定的导数(或斜率),而在其他地方斜率为0

注意

您可以在这里阅读更多关于 Keras 中所有可用激活函数的选择:keras.io/activations/

用于进行预测的前向传播

神经网络通过执行前向传播来对输出进行预测。前向传播包括在神经网络的每一层上对输入执行的计算,直到达到输出层。通过一个例子来理解前向传播是最好的。

让我们逐一通过一个两层神经网络的前向传播方程进行详细说明(如下图所示),其中输入数据是二维的,输出数据是一维的二进制类标签。第 1 层和第 2 层的激活函数将是 tanh,输出层的激活函数是 sigmoid。

下图显示了每一层的权重和偏置作为矩阵和向量,并带有适当的索引。对于每一层,权重矩阵的行数等于上一层的节点数,列数等于该层的节点数。

例如,W1有两行三列,因为第一层的输入是输入层X,它有两列,第一层有三个节点。同样,W2有三行三列,因为第二层的输入是第一层,它有两个节点,第二层有五个节点。然而,偏置始终是一个大小等于该层节点数的向量。深度学习模型中的参数总数等于所有权重矩阵和偏置向量中的元素总数:

图 3.7:一个两层神经网络

图 3.7:一个两层神经网络

根据前面图像中所述的神经网络进行前向传播的所有步骤的示例如下。

执行前向传播的步骤

  1. X是上图中网络的输入,因此它是第一个隐藏层的输入。首先,输入矩阵X是矩阵与层 1 的权重矩阵W1相乘,然后加上偏置b1

    z1 = XW1 + b1*

  2. 接下来,层 1 的输出是通过对z1(前一步的输出)应用激活函数计算得出的:

    a1 = tanh(z1)

  3. a1是层 1 的输出,称为层 1 的激活。层 1 的输出实际上是层 2 的输入。接下来,层 1 的激活是矩阵与层 2 的权重矩阵W2相乘,然后加上偏置b2

    z2 = a1 * W2 + b2

  4. 层 2 的输出/激活是通过对z2应用激活函数计算得出的:

    a2 = tanh(z2)

  5. 层 2 的输出实际上是下一个层(这里是网络输出层)的输入。接着,层 2 的激活是矩阵与输出层的权重矩阵W3相乘,再加上偏置b3

    z3 = a2 * W3 + b3

  6. 最后,网络输出 Y 是通过对z3应用 sigmoid 激活函数计算得出的:

    Y = sigmoid(z3)

该模型的总参数数量等于W1W2W3b1b2b3中元素的总和。因此,可以通过对每个参数的权重矩阵和偏置中的参数求和来计算参数数量,结果为 6 + 15 + 5 + 3 + 5 + 1 = 35。这些参数是深度学习过程中需要学习的。

现在我们已经了解了前向传播步骤,接下来我们需要评估我们的模型并将其与真实的目标值进行比较。为此,我们将使用损失函数,接下来我们将讨论这一部分。在这里,我们将学习一些常见的损失函数,适用于分类和回归任务。

损失函数

在学习模型的最优参数(权重和偏置)时,我们需要定义一个函数来衡量误差。这个函数叫做损失函数,它为我们提供了一个度量,表示网络预测的输出与数据集中的真实输出之间的差异。

损失函数可以根据问题和目标以不同的方式定义。例如,在分类问题中,定义损失的一种常见方法是计算数据集中误分类输入的比例,并将其作为模型出错的概率。另一方面,在回归问题中,损失函数通常通过计算预测输出和其对应真实输出之间的距离来定义,然后对数据集中的所有示例取平均值。

以下是 Keras 中一些常用损失函数的简要描述:

  • (真实输出 – 预测输出)² 对数据集中的每个示例进行计算,然后返回它们的平均值。

  • abs (实际输出 - 预测输出) 对数据集中的每个示例进行计算,然后返回它们的平均值。

  • abs [(实际输出 - 预测输出) / 实际输出] 对数据集中的每个示例进行计算,然后返回它们的平均值,再乘以 100%。

  • 01

  • categorical_crossentropy 是用于多类别(超过两个类别)分类问题的损失函数。

    注意

    你可以在这里阅读更多关于 Keras 中可用的损失函数选项:keras.io/losses/

在训练过程中,我们不断地改变模型参数,直到模型预测输出与实际输出之间的最小差异被达到。这被称为优化过程,我们将在后面的章节中深入了解它是如何工作的。对于神经网络,我们使用反向传播来计算损失函数关于权重的导数。

反向传播用于计算损失函数的导数

反向传播是通过对神经网络从输出层到输入层应用微积分中的链式法则,来计算每一层损失函数关于模型参数的导数。函数的导数就是该函数的斜率。我们关心损失函数的斜率,因为它为我们提供了模型参数需要改变的方向,从而最小化损失值。

微积分中的链式法则表明,如果,例如,zy 的函数,而 yx 的函数,那么 z 关于 x 的导数可以通过将 z 关于 y 的导数与 y 关于 x 的导数相乘来得到。可以将其写作如下:

dz/dx = dz/dy * dy/dx

在深度神经网络中,损失函数是预测输出的函数。我们可以通过下面给出的方程来展示这一点:

loss = L(y_predicted)

另一方面,根据前向传播方程,模型预测的输出是模型参数的函数——即每一层的权重和偏置。因此,根据微积分的链式法则,损失函数关于模型参数的导数可以通过将损失函数关于预测输出的导数与预测输出关于模型参数的导数相乘来计算。

在下一节中,我们将学习在给定损失函数关于权重的导数时,如何修改最优的权重参数。

梯度下降法用于学习参数

在本节中,我们将学习深度学习模型如何学习其最优参数。我们的目标是更新权重参数,以便最小化损失函数。这将是一个迭代过程,我们将不断更新权重参数,直到损失函数达到最小值。这个过程称为学习参数,通过使用优化算法来完成。一个在机器学习中用于学习参数的非常常见的优化算法是梯度下降。让我们来看看梯度下降是如何工作的。

如果我们绘制数据集中所有示例在所有可能的模型参数值下的平均损失,它通常是一个凸形状(如下图所示)。在梯度下降中,我们的目标是找到图中的最小点(Pt)。算法首先通过初始化模型参数为一些随机值(P1)开始。然后,它计算该点的损失和相对于参数的损失导数。如前所述,函数的导数实际上是该函数的斜率。计算出初始点的斜率后,我们就知道了需要更新参数的方向。

超参数,称为P2(如下图所示)。如图所示,P2更接近目标点,如果我们继续朝该方向移动,我们最终会到达目标点Pt。算法再次计算P2处的函数斜率,并迈出下一步。

该过程会重复进行,直到斜率为零,因此不再提供进一步移动的方向:

图 3.8:梯度下降算法寻找最小化损失的参数集的示意图最小化损失的参数集

图 3.8:梯度下降算法寻找最小化损失的参数集的示意图

下面是梯度下降算法的伪代码:

Initialize all the weights (w) and biases (b) arbitrarily
Repeat Until converge {
Compute loss given w and b
Compute derivatives of loss with respect to w (dw), and with respect to b (db) using backpropagation
Update w to w – alpha * dw
Update b to b – alpha * db
}

总结来说,在训练深度神经网络时(在将参数初始化为一些随机值之后),以下步骤会被反复执行:

  1. 使用前向传播和当前的参数预测整个数据集的输出。

  2. 使用预测的输出计算所有示例的损失。

  3. 使用反向传播计算每一层权重和偏置相对于损失的导数。

  4. 使用导数值和学习率更新权重和偏置。

我们在这里讨论的是标准的梯度下降算法,它使用整个数据集计算损失和导数,以更新参数。还有一种叫做随机梯度下降SGD)的梯度下降版本,它每次只使用数据集的一个子集或一个批次来计算损失和导数;因此,它的学习过程比标准的梯度下降更快。

注意

另一种常见的选择是名为 SGD 的优化算法,它用于训练深度学习模型。如我们已经学习过,SGD 只使用一个超参数(称为学习率)来更新参数。然而,Adam 改进了这一过程,通过使用学习率、加权平均梯度和加权平均平方梯度,在每次迭代时更新参数。

通常,在构建神经网络时,您需要选择两个超参数(其中batch_size参数决定了每次优化算法迭代时包含的数据样本数量。batch_size=None相当于标准版本的梯度下降方法,它在每次迭代时使用整个数据集。epochs参数决定优化算法在停止之前需要遍历整个训练数据集多少次。

例如,假设我们有一个大小为 n=400 的数据集,我们选择 batch_size=5epochs=20。在这种情况下,优化器在遍历整个数据集时将有 400/5 = 80 次迭代。由于它需要遍历整个数据集 20 次,因此总共将进行 80 * 20 次迭代。

注意

在 Keras 中构建模型时,您需要选择训练模型时使用的优化器类型。除了 SGD 和 Adam,Keras 还提供了其他一些选择。您可以在此链接中阅读关于 Keras 中所有可能的优化器选项:keras.io/optimizers/

注意

本章中的所有活动和练习将使用 Jupyter notebook 进行开发。请从 packt.live/39pOUMT 下载本书的 GitHub 仓库以及所有准备好的模板。

练习 3.01:使用 Keras 实现神经网络

在本练习中,您将学习如何使用 Keras 步骤化地实现神经网络。我们模拟的数据集代表了森林中不同树木的各种测量值,如树高、树枝数量、树干基部的周长等。我们的目标是根据给定的测量值将记录分类为落叶树或针叶树两类。首先,执行以下代码块来加载一个包含 10000 条记录的模拟数据集,其中有两个类别,代表两种树种,每条数据包含 10 个特征值:

import numpy as np
import pandas as pd
X = pd.read_csv('../data/tree_class_feats.csv')
y = pd.read_csv('../data/tree_class_target.csv')
# 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]) 
print("Possible Output Classes = ", np.unique(y))

预期输出

Number of Examples in the Dataset = 10000
Number of Features for each example = 10
Possible Output Classes = [0 1]

由于该数据集中的每个数据示例只能属于两个类别中的一个,因此这是一个二分类问题。二分类问题在现实生活中非常重要且常见。例如,假设该数据集中的示例代表了10000棵树的测量结果。这些树来自一片森林。目标是使用该数据集构建一个模型,以预测每棵被测量的树的物种是落叶树还是针叶树。树木的10个特征可能包括身高、树枝数量和基部的树干周长等预测因子。

输出类别0表示树是针叶树物种,而输出类别1表示树是落叶树物种。

现在,让我们逐步了解如何构建和训练一个 Keras 模型以执行分类任务:

  1. numpytensorflow中设置种子,并将模型定义为 Keras 的顺序模型。Sequential模型实际上是各层的堆叠。定义模型后,我们可以根据需要向其中添加任意数量的层:

    from keras.models import Sequential
    from tensorflow import random
    np.random.seed(42)
    random.set_seed(42)
    model = Sequential()
    
  2. 向模型中添加一个大小为10的隐藏层,激活函数类型为tanh(记住输入维度为10)。Keras 中有多种类型的层。现在,我们只使用最简单的层类型——Dense层。Dense 层相当于我们之前见过的全连接层

    from keras.layers import Dense, Activation
    model.add(Dense(10, activation='tanh', input_dim=10))
    
  3. 向模型中添加另一个隐藏层,这次层的大小为5,激活函数为tanh。请注意,输入维度参数仅在第一层提供,因为后续层的输入维度是已知的:

    model.add(Dense(5, activation='tanh'))
    
  4. 添加带有sigmoid激活函数的输出层。请注意,输出层的单元数量等于输出维度:

    model.add(Dense(1, activation='sigmoid'))
    
  5. 使用compile()方法确保损失函数为二元交叉熵,并将优化器设置为SGD以训练模型,并打印出模型的摘要以查看其架构:

    model.compile(optimizer='sgd', loss='binary_crossentropy', \
                  metrics=['accuracy'])
    model.summary()
    

    下图显示了前面代码的输出:

    图 3.9:创建的模型概述

    图 3.9:创建的模型概述

  6. 训练模型100个周期,并将batch_size设置为 5,validation_split设置为0.2,然后使用fit()方法将shuffle设置为false。记住,你需要将输入数据X及其对应的输出y传递给fit()方法以训练模型。此外,请记住,训练一个网络可能需要很长时间,这取决于数据集的大小、网络的大小、周期的数量以及可用的 CPU 或 GPU 的数量。将结果保存到名为history的变量中:

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

    verbose 参数可以取以下三个值:012。选择 verbose=0 时,训练过程中不会打印任何信息。verbose=1 会在每次迭代时打印完整的进度条,而 verbose=2 只会打印周期号:

    图 3.10:最后 5 个周期(共 400 个周期)的损失详情

    图 3.10:最后 5 个周期(共 400 个周期)的损失详情

  7. 打印模型在训练和验证数据上的准确率和损失,作为周期的函数:

    import matplotlib.pyplot as plt
    %matplotlib inline
    # Plot training & validation accuracy values
    plt.plot(history.history['accuracy'])
    plt.plot(history.history['val_accuracy'])
    plt.title('Model accuracy')
    plt.ylabel('Accuracy')
    plt.xlabel('Epoch')
    plt.legend(['Train', 'Validation'], loc='upper left')
    plt.show()
    # Plot training & validation loss values
    plt.plot(history.history['loss'])
    plt.plot(history.history['val_loss'])
    plt.title('Model loss')
    plt.ylabel('Loss')
    plt.xlabel('Epoch')
    plt.legend(['Train', 'Validation'], loc='upper left')
    plt.show()
    

    以下图像展示了前面代码的输出:

    图 3.11:模型的准确率和损失作为训练周期的函数    训练过程

    图 3.11:模型的准确率和损失作为训练过程中的周期函数

  8. 使用你训练好的模型预测前 10 个输入数据示例的输出类别(X.iloc[0:10,:]):

    y_predicted = model.predict(X.iloc[0:10,:])
    

    你可以使用以下代码块打印预测的类别:

     # print the predicted classes
    print("Predicted probability for each of the "\
          "examples belonging to class 1: "),
    print(y_predicted)
    print("Predicted class label for each of the examples: "), 
    print(np.round(y_predicted))
    

    预期输出

    Predicted probability for each of the examples belonging to class 1:
    [[0.00354007]
     [0.8302744 ]
     [0.00316998]
     [0.95335543]
     [0.99479216]
     [0.00334176]
     [0.43222323]
     [0.00391936]
     [0.00332899]
     [0.99759173]
    Predicted class label for each of the examples:
    [[0.]
     [1.]
     [0.]
     [1.]
     [1.]
     [0.]
     [0.]
     [0.]
     [0.]
     [1.]]
    

    这里,我们使用训练好的模型预测数据集中前 10 个树种的输出。如你所见,模型预测第二、第四、第五和第十棵树为类别 1 的树种,即落叶树

    注意

    要访问此特定部分的源代码,请参考 packt.live/2YX3fxX

    你也可以在线运行这个示例,网址是 packt.live/38pztVR

请注意,你可以通过向网络添加更多的隐藏层来扩展这些步骤。实际上,在添加输出层之前,你可以根据需要向模型添加任意数量的层。然而,输入维度参数仅提供给第一层,因为后续层的输入维度是已知的。现在,你已经学会了如何在 Keras 中实现神经网络,你可以通过在以下活动中实现一个能够执行分类的神经网络来进一步实践。

活动 3.01:构建单层神经网络进行二分类

在本活动中,我们将使用 Keras 顺序模型构建一个二分类器。提供的模拟数据集表示飞机螺旋桨生产的测试结果。我们的目标变量将是螺旋桨的人工检验结果,标记为“通过”(表示为值 1)或“失败”(表示为值 0)。

我们的目标是将测试结果分类为“通过”或“失败”类别,以匹配人工检验结果。我们将使用不同架构的模型,并观察不同模型性能的可视化效果。这将帮助你更好地理解从一个处理单元到一层处理单元的变化如何影响模型的灵活性和性能。

假设该数据集包含两个特征,代表检查超过3000个螺旋桨的两项不同测试的结果(这两个特征已归一化,使其均值为零)。输出是螺旋桨通过测试的可能性,1 表示通过,0 表示未通过。公司希望减少对耗时且容易出错的人工检查的依赖,将资源转移到开发自动化测试上,以便更快地评估螺旋桨。因此,目标是建立一个模型,当给定两项测试的结果时,能够预测飞机螺旋桨是否能够通过人工检查。在这个活动中,你将首先建立一个逻辑回归模型,然后建立一个具有三单位的单层神经网络,最后建立一个具有六单位的单层神经网络来执行分类任务。请按照以下步骤完成此活动:

  1. 导入所需的包:

    # import required packages from Keras
    from keras.models import Sequential 
    from keras.layers import Dense, Activation 
    import numpy as np
    import pandas as pd
    from tensorflow import random
    from sklearn.model_selection import train_test_split
    # import required packages for plotting
    import matplotlib.pyplot as plt 
    import matplotlib
    %matplotlib inline 
    import matplotlib.patches as mpatches
    # import the function for plotting decision boundary
    from utils import plot_decision_boundary
    

    注意

    你需要从 GitHub 仓库下载utils.py文件并将其保存在你的活动文件夹中,才能使 utils 导入语句正常工作。你可以在这里找到该文件:packt.live/31EumPY

  2. 为随机数生成器设置种子,以便结果是可重复的:

    """
    define a seed for random number generator so the result will be reproducible
    """
    seed = 1
    

    注意

    上述代码片段中的三引号(""")用于表示多行代码注释的开始和结束。注释被添加到代码中,以帮助解释特定的逻辑部分。

  3. 使用pandas库中的read_csv函数加载数据集。使用feats.shapetarget.shapefeats.shape[0]打印XY的大小,以及训练数据集中示例的数量:

    feats = pd.read_csv('outlier_feats.csv')
    target = pd.read_csv('outlier_target.csv')
    print("X size = ", feats.shape)
    print("Y size = ", target.shape)
    print("Number of examples = ", feats.shape[0])
    
  4. 使用以下代码绘制数据集:

    plt.scatter(feats[:,0], feats[:,1], \
                s=40, c=Y, cmap=plt.cm.Spectral)
    
  5. 在 Keras 中实现一个逻辑回归模型,作为一个顺序模型。记住,二分类的激活函数需要使用sigmoid

  6. 使用optimizer='sgd'loss='binary_crossentropy'batch_size = 5epochs = 100shuffle=False训练模型。通过设置verbose=1validation_split=0.2,观察每次迭代的损失值。

  7. 使用以下代码绘制训练模型的决策边界:

    plot_decision_boundary(lambda x: model.predict(x), \
                           X_train, y_train)
    
  8. 实现一个具有三节点隐藏层的单层神经网络,并使用ReLU 激活函数进行200个 epoch 的训练。重要的是要记住,输出层的激活函数仍然需要是 sigmoid,因为这是一个二分类问题。如果选择ReLU或者没有输出层激活函数,将无法得到可以解释为类别标签的输出。使用verbose=1训练模型,并观察每次迭代的损失。在模型训练完成后,绘制决策边界,并在测试数据集上评估损失和准确度。

  9. 重复步骤 8,使用大小为 6的隐藏层和400个 epoch,并比较最终的损失值和决策边界图。

  10. 重复步骤 8步骤 9,使用tanh激活函数为隐藏层进行训练,并将结果与使用relu激活函数的模型进行比较。你认为哪种激活函数更适合这个问题?

    注意

    该活动的解决方案可以在第 362 页找到。

在本活动中,你观察到在一个层中堆叠多个处理单元,能够创建一个比单个处理单元强大的模型。这是神经网络如此强大的基本原因。你还观察到,增加层中单元的数量可以提高模型的灵活性,这意味着可以更精确地估计非线性分隔决策边界。

然而,拥有更多处理单元的模型需要更长的时间来学习模式,需要更多的训练周期,并且可能会过拟合训练数据。因此,神经网络是计算开销较大的模型。你还观察到,与使用ReLU 激活函数相比,使用 tanh 激活函数的训练过程较慢。

在本节中,我们创建了多种模型,并在我们的数据上对其进行了训练。我们通过在训练数据上评估它们,观察到一些模型比其他模型表现更好。在下一节中,我们将了解一些可以用来评估模型的替代方法,这些方法提供了无偏的评估。

模型评估

在本节中,我们将进入多层或深度神经网络的学习,同时了解评估模型性能的技术。正如你可能已经意识到的,构建深度神经网络时需要做出许多超参数选择。

应用深度学习的一些挑战包括如何找到隐藏层数量、每个隐藏层中的单元数量、每层使用的激活函数类型,以及用于训练网络的优化器和损失函数的类型。做出这些决策时需要进行模型评估。通过执行模型评估,你可以判断特定的深度架构或一组特定的超参数在某个数据集上是表现不佳还是表现良好,从而决定是否进行更改。

此外,你将学习关于过拟合欠拟合的知识。这是构建和训练深度神经网络时可能出现的两个非常重要的问题。理解过拟合和欠拟合的概念,并判断它们在实际中是否发生,对于找到适合特定问题的深度神经网络并尽可能提高其性能至关重要。

使用 Keras 评估已训练的模型

在之前的活动中,我们通过预测每个可能输入值的输出,绘制了模型的决策边界。之所以能进行这种模型性能可视化,是因为我们处理的是二维输入数据。输入空间中的特征或度量通常远多于两个,因此不能通过二维绘图进行可视化。评估模型在特定数据集上的表现的一种方法是,在多个示例上计算总体损失。这可以通过在 Keras 中使用evaluate()方法实现,该方法接收一组输入(X)及其对应的输出(y),然后计算并返回模型在输入数据X上的整体损失。

例如,假设我们要构建一个具有两个隐藏层(大小分别为84)的神经网络,以进行二分类任务。可用的数据点及其对应的类别标签存储在Xy数组中。我们可以按如下方式构建并训练上述模型:

model = Sequential()
model.add(Dense(8, activation='tanh', input_dim=2))
model.add(Dense(4, activation='tanh'))
model.add(Dense(1, activation='sigmoid'))
model.compile(optimizer='sgd', loss='binary_crossentropy')
model.fit(X, y, epochs=epochs, batch_size=batch_size)

现在,我们可以通过计算整个数据集上的损失来评估模型的总体性能,而不是使用model.predict()预测给定输入集的输出,方法如下所示:

model.evaluate(X, y, batch_size=None, verbose=0)

如果在定义模型的compile()方法时加入了其他度量指标,如准确率,那么调用evaluate()方法时将返回这些度量指标和损失。例如,如果我们在compile()参数中加入度量指标,如以下代码所示,那么调用evaluate()方法时将返回训练模型在整个数据集上的总体损失和总体准确率:

model.compile(optimizer='sgd', loss='binary_crossentropy', \
              metrics=['accuracy'])
model.evaluate(X, y, batch_size=None, verbose=0)

注意

你可以在 Keras 中查看metrics参数的所有可能选项,网址:keras.io/metrics/

在下一部分中,我们将学习如何将数据集划分为训练集和测试集。就像在第一章《Keras 机器学习简介》中所做的那样,使用独立数据进行训练和评估可以提供模型性能的无偏估计。

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

通常情况下,在用于训练模型的同一数据集上评估模型是一个方法上的错误。由于模型已针对该数据集进行训练并减少了误差,在该数据集上进行评估会导致模型性能的偏估计。换句话说,在用于训练的数据集上的误差率总是低估了新数据集上的误差率。

另一方面,在构建机器学习模型时,目标不仅仅是在训练数据上取得良好表现,更是要在模型未曾见过的未来数据上取得良好表现。这就是为什么我们希望使用未曾用于训练的测试数据集来评估模型性能。

实现这一目标的一种方式是将可用的数据集拆分为两个集合:训练集和测试集。训练集用于训练模型,而测试集用于性能评估。更准确地说,训练集的作用是为模型提供足够的示例,使其能够学习数据中的关系和模式,而测试集的作用是为我们提供对模型在新未见数据上的表现的无偏估计。机器学习中的常见做法是进行 70%-30%80%-20% 的训练集与测试集拆分。对于相对较小的数据集,通常是这种情况。当处理一个包含数百万示例的数据集,且目标是训练一个大型深度神经网络时,训练集与测试集的拆分比例可以使用 98%-2%99%-1%

下图展示了数据集被拆分为训练集和测试集。请注意,训练 集和 测试 集之间没有重叠:

图 3.12:将数据集拆分为训练集和测试集的示意图

图 3.12:将数据集拆分为训练集和测试集的示意图

您可以使用 scikit-learn 的 train_test_split 函数轻松地对数据集进行拆分。例如,以下代码将对数据集进行 70%-30% 的训练集与测试集拆分:

from sklearn.model_selection import train_test_split
X_train, X_test, \
y_train, y_test = train_test_split(X, y, test_size=0.3, \
                                   random_state=None)

test_size 参数表示要保留在测试集中的数据集比例,因此它应该在 01 之间。通过为 random_state 参数分配一个整数,您可以选择用于生成训练集和测试集之间随机拆分的种子。

在将数据拆分为训练集和测试集后,我们可以通过只将训练集作为参数传递给 fit() 来更改前一节的代码:

model = Sequential()
model.add(Dense(8, activation='tanh', input_dim=2))
model.add(Dense(4, activation='tanh'))
model.add(Dense(1, activation='sigmoid'))
model.compile(optimizer='sgd', \
              loss='binary_crossentropy')
model.fit(X_train, y_train, epochs=epochs, \
          batch_size=batch_size)

现在,我们可以分别计算模型在训练集和测试集上的误差率:

model.evaluate(X_train, y_train, batch_size=None, \
               verbose=0)
model.evaluate(X_test, y_test, batch_size=None, \
               verbose=0)

另一种拆分方法是在 Keras 的 fit() 方法中包含 validation_split 参数。例如,通过仅将前一节代码中的 model.fit(X, y) 行更改为 model.fit(X, y, validation_split=0.3),模型将把数据集的最后 30% 保留在单独的测试集上。它只会在其余 70% 的样本上训练模型,并且在每个周期结束时会在训练集和测试集上评估模型。这样,您可以观察到训练误差率和测试误差率在训练过程中是如何变化的。

我们希望对模型进行无偏评估的原因是,这样我们可以看到哪里有改进的空间。由于神经网络有许多参数需要学习,并且能够学习复杂的函数,它们经常会过拟合训练数据,学习到训练数据中的噪声,这可能导致模型在新的、未见过的数据上表现不佳。下一节将详细探讨这些概念。

欠拟合与过拟合

在本节中,您将学习构建需要适配数据集的机器学习模型时可能面临的两个问题。这些问题被称为过拟合欠拟合,与模型的偏差方差概念类似。

一般来说,如果一个模型不够灵活,无法学习数据集中的关系和模式,就会出现较高的训练误差。我们可以称这种模型为具有高偏差的模型。另一方面,如果一个模型对给定数据集过于灵活,它不仅会学习训练数据中的噪声,还会学习数据中的关系和模式。这样,系统的测试误差将大大高于训练误差。我们之前提到过,测试误差通常会略高于训练误差。

然而,测试误差和训练误差之间有较大差距通常是系统具有高方差的一个指示。在数据分析中,这两种情况(高偏差高方差)都不是理想的。事实上,目标是找到一个具有最低偏差和方差的模型。

例如,假设我们有一个数据集,表示两种蝴蝶物种的观测位置归一化后的数据,如下图所示。目标是找到一个模型,当给定蝴蝶观测的位置时,可以将这两种蝴蝶物种分开。显然,这两类之间的分隔线不是线性的。因此,如果我们选择一个简单的模型,如逻辑回归(一个具有一个隐藏层且大小为一的神经网络),来对这个数据集进行分类,我们将得到一条线性分隔线/决策边界,而这条线无法捕捉数据集中的真实模式:

图 3.13:两类不同数据点的二维分布

图 3.13:两类不同数据点的二维分布

以下图展示了通过这种模型实现的决策边界。通过评估这个模型,我们会观察到训练误差率较高,并且测试误差率略高于训练误差。较高的训练误差率表明该模型具有高偏差,而训练误差和测试误差之间的微小差异则代表了一个低方差模型。这是一个典型的欠拟合案例;该模型未能拟合出两类之间的真实分隔线:

图 3.14:欠拟合

图 3.14:欠拟合

如果我们通过增加神经网络的层数,并增加每一层的单元数来提高其灵活性,我们可以训练出更好的模型,并成功捕捉决策边界中的非线性。下图展示了这样的模型。这是一个低训练误差率和低测试误差率的模型(同样,测试误差率略高于训练误差率)。训练误差率低且测试误差率与训练误差率之间的差距较小,表明这是一个低偏差和低方差的模型。低偏差和低方差的模型代表了对给定数据集的适当拟合:

图 3.15:正确拟合

图 3.15:正确拟合

那么,如果我们进一步增加神经网络的灵活性会发生什么呢?通过向模型添加过多的灵活性,它不仅会学习训练数据中的模式和关系,还会学习其中的噪声。换句话说,模型会对每个单独的训练示例进行拟合,而不是仅对数据中的整体趋势和关系进行拟合。下图展示了这样的系统。评估该模型时,会看到非常低的训练误差率和很高的测试误差率(训练误差率与测试误差率之间存在较大差距)。这是一个低偏差和高方差的模型,这种情况称为过拟合:

图 3.16:过拟合

图 3.16:过拟合

在训练集和测试集上评估模型并比较其误差率,可以提供有关当前模型是否适用于给定数据集的宝贵信息。此外,在当前模型没有正确拟合数据集的情况下,还可以确定它是否过拟合或欠拟合数据,并相应地更改模型以找到正确的模型。例如,如果模型出现欠拟合,您可以增大网络。另一方面,如果模型过拟合,您可以通过缩小网络或提供更多训练数据来减少过拟合。实际中有许多方法可以防止欠拟合或过拟合,其中一种方法将在下一节中探讨。

提前停止

有时候,模型的灵活性可能适用于数据集,但仍然会出现过拟合或欠拟合的情况。这是因为我们训练模型的迭代次数太多或太少。当使用像梯度下降这样的迭代优化器时,优化器会在每次迭代中尽可能地拟合训练数据。因此,如果在数据模式已经学到之后继续更新参数,它将开始对个别数据样本进行拟合。

通过观察每次迭代中的训练误差率和测试误差率,可以确定网络何时开始过拟合训练数据,并在这种情况发生之前停止训练过程。下图标记了与欠拟合过拟合相关的区域。可以从测试误差率最低值的区域确定训练模型的正确迭代次数。我们将该区域标记为“合适的拟合”区域,可以看到在该区域内,训练误差率测试误差率都很低:

图 3.17:训练模型时训练误差率和测试误差率的图表

](https://github.com/OpenDocCN/freelearn-dl-pt5-zh/raw/master/docs/dl-keras-ws/img/B15777_03_17.jpg)

图 3.17:训练模型时训练误差率和测试误差率的图表

在使用 Keras 训练时,你可以很容易地在每个 epoch 存储训练损失和测试损失的值。为此,你需要在定义模型的fit()方法时,提供测试集作为validation_data参数,并将其存储在一个history字典中:

history = model.fit(X_train, y_train, validation_data=(X_test, y_test))

你可以稍后绘制存储在history中的值,来找到训练模型所需的正确迭代次数:

import matplotlib.pyplot as plt 
import matplotlib
# plot training loss
plt.plot(history.history['loss'])
# plot test loss
plt.plot(history.history['val_loss'])

一般来说,由于深度神经网络是高度灵活的模型,因此过拟合的可能性很高。为了减少机器学习模型,特别是深度神经网络中的过拟合,已经开发了一整套称为正则化的技术。你将在第五章,《提高模型准确性》中学到更多关于这些技术的内容。在下一个活动中,我们将把我们的理解付诸实践,尝试找出训练的最佳 epoch 数量,以避免过拟合。

活动 3.02:使用神经网络进行晚期纤维化诊断

在这个活动中,你将使用一个真实的数据集,根据年龄、性别和 BMI 等测量数据预测患者是否患有晚期纤维化。该数据集包含 1,385 名接受过丙型肝炎治疗剂量的患者的信息。每个患者有28个不同的属性,并且有一个类别标签,只有两个值:1,表示晚期纤维化,和0,表示没有晚期纤维化的迹象。这是一个二分类问题,输入维度为28

在这个活动中,你将实现不同的深度神经网络架构来执行这个分类任务。绘制训练误差率和测试误差率的趋势图,并确定最终分类器需要训练的轮数:

注意

本活动使用的数据集可以在这里找到:packt.live/39pOUMT

图 3.18:糖尿病诊断的二分类器示意图

](https://github.com/OpenDocCN/freelearn-dl-pt5-zh/raw/master/docs/dl-keras-ws/img/B15777_03_18.jpg)

图 3.18:糖尿病诊断的二分类器示意图

按照以下步骤完成这个活动:

  1. 导入所有必要的依赖项。从 GitHub 的Chapter03文件夹的data子文件夹中加载数据集:

    X = pd.read_csv('../data/HCV_feats.csv')
    y = pd.read_csv('../data/HCV_target.csv')
    
  2. 打印数据集中的示例数量、可用特征的数量以及类标签的可能值。

  3. 使用sklearn.preprocessing中的StandardScalar函数对数据进行缩放,并按80:20比例将数据集拆分为训练集和测试集。然后,在拆分后打印出每个集中的示例数量。

  4. 实现一个具有一个隐藏层(大小为 3)并使用tanh激活函数的浅层神经网络来执行分类任务。使用以下超参数值来编译模型:optimizer = 'sgd', loss = 'binary_crossentropy', metrics = ['accuracy']

  5. 使用以下超参数拟合模型,并在训练过程中存储训练错误率和测试错误率的值:batch_size = 20epochs = 100validation_split=0.1shuffle=False

  6. 绘制每个训练轮次的训练错误率和测试错误率。使用图表确定网络开始对数据集发生过拟合的轮次。同时,打印出在训练集和测试集上达到的最佳准确度值,以及在测试数据集上评估得到的损失和准确度。

  7. 对具有两个隐藏层的深层神经网络(第一个隐藏层大小为 4,第二个隐藏层大小为 3)和'tanh'激活函数(两个层均使用)的步骤 4步骤 5进行重复操作,以执行分类任务。

    注意

    本活动的解决方案可以在第 374 页找到。

请注意,两个模型在训练集或验证集上的准确度都比在测试集上要好,并且在经过大量训练轮次后,训练错误率持续下降。然而,验证错误率在训练过程中下降到某个值后开始上升,这表明模型出现了对训练数据的过拟合。最大验证准确度对应于图中验证损失最低的点,是真正代表模型在未来独立样本上表现的指标。

从结果中可以看出,具有一个隐藏层的模型能够比两个隐藏层的模型达到更低的验证和测试错误率。由此我们可以得出结论,该模型最适合该特定问题。具有一个隐藏层的模型表现出较大的偏差,这体现在训练错误率和验证错误率之间的较大差距,且两者仍在下降,这表明该模型可以进行更多轮次的训练。最后,从图表中可以判断,应该在验证错误率开始上升的区域停止训练,以防止模型对数据点发生过拟合

总结

在本章中,你扩展了对深度学习的理解,从理解常见的表示法和术语,到通过练习和活动将其付诸实践。你学习了神经网络中前向传播的工作原理以及如何用于预测输出,了解了损失函数如何作为模型性能的衡量标准,并学习了如何利用反向传播计算损失函数相对于模型参数的导数。

你还学习了梯度下降法,它利用反向传播计算出的梯度来逐步更新模型参数。除了基础的理论和概念,你还使用 Keras 实现并训练了浅层和深层神经网络,并利用它们对给定输入的输出进行预测。

为了适当地评估你的模型,你将数据集分割为训练集和测试集,这是一种改进网络评估的替代方法,并学习了为什么仅在训练示例上评估模型可能会产生误导性结果。这有助于进一步理解在训练模型时可能出现的过拟合和欠拟合问题。最后,你利用训练误差率和测试误差率来检测网络中的过拟合和欠拟合,并实现了早停技术以减少网络中的过拟合。

在下一章,你将学习如何使用scikit-learn中的 Keras 封装器,以及如何通过使用如交叉验证等重采样方法进一步改进模型评估。通过这样做,你将学习如何为深度神经网络找到最佳的超参数设置。

第四章:4. 使用 Keras 包装器进行交叉验证评估模型

概述

本章将介绍如何使用 scikit-learn 构建 Keras 包装器。你将学习如何应用交叉验证来评估深度学习模型,并创建用户定义的函数来实现深度学习模型及其交叉验证。到本章结束时,你将能够构建出在新数据和训练数据上表现一致、且稳健的模型。

引言

在上一章中,我们实验了不同的神经网络架构。通过观察训练过程中损失值和准确率的变化,我们能够评估不同模型的表现。这帮助我们判断模型是否存在欠拟合或过拟合问题,并学习如何使用如提前停止等技术来防止过拟合。

在本章中,你将学习交叉验证。这是一种重采样技术,相较于我们在前几章讨论的模型评估方法,它能提供更加准确和稳健的模型性能估计。

本章从深入讨论为什么需要使用交叉验证进行模型评估开始,讲解交叉验证的基本原理、变种以及它们之间的比较。接下来,我们将对 Keras 深度学习模型进行交叉验证的实现。我们还将结合 scikit-learn 使用 Keras 包装器,使得 Keras 模型能够作为估计器在 scikit-learn 的工作流中进行处理。然后,你将学习如何在 scikit-learn 中实现交叉验证,最终将所有内容结合起来,在 scikit-learn 中对 Keras 深度学习模型进行交叉验证。

最后,你将学习如何利用交叉验证不仅仅进行模型评估,还可以利用交叉验证对模型性能进行估计,从而比较不同模型,并选择在特定数据集上表现最好的模型。你还将使用交叉验证来提高给定模型的表现,通过寻找最佳超参数集合。我们将在三项活动中实现本章所学的概念,每项活动都涉及一个真实数据集。

交叉验证

重采样技术是统计数据分析中的一组重要技术。它们涉及反复从数据集中抽取样本来创建训练集和测试集。在每次重复中,使用从数据集中抽取的样本来训练和评估模型。

使用这些技术可以为我们提供模型的信息,这是通过仅使用一个训练集和一个测试集来拟合和评估模型无法获得的。由于重采样方法涉及多次对训练数据拟合模型,因此计算开销较大。因此,在深度学习中,我们仅在数据集和网络相对较小,并且可用计算能力允许时,才会实现这些方法。

在本节中,你将学习一种非常重要的重采样方法——交叉验证。交叉验证是最重要和最常用的重采样方法之一。它计算了在给定有限数据集的情况下,模型在新未见过的样本上的最佳性能估计。我们还将探讨交叉验证的基础知识,它的两种变体以及它们之间的比较。

仅进行一次数据集划分的缺点

在上一章中,我们提到过,在用于训练模型的同一数据集上评估模型是一个方法上的错误。由于模型已经训练以减少这个特定数据集上的误差,因此它在该数据集上的表现具有很强的偏倚性。这就是为什么训练数据上的误差率总是低估新样本上的误差率的原因。我们了解到,解决这个问题的一种方法是随机地从数据中留出一部分作为测试集进行评估,并在其余数据上拟合模型,这部分数据被称为训练集。以下图展示了这种方法的示例:

图 4.1:训练集/测试集划分概述

图 4.1:训练集/测试集划分概述

正如我们之前提到的,将数据分配到训练集或测试集完全是随机的。这意味着如果我们重复此过程,每次分配给测试集和训练集的数据会有所不同。通过这种方法报告的测试误差率可能会有所不同,这取决于哪些样本被分配到测试集,哪些样本被分配到训练集。

示例

让我们来看一个例子。在这里,我们为你在活动 3.02使用神经网络进行高级纤维化诊断中的第三章使用 Keras 的深度学习部分看到的丙型肝炎数据集构建了一个单层神经网络。我们使用了训练集/测试集的方法来计算与此模型相关的测试误差。与一次性划分和训练不同,如果我们将数据划分为五个独立的数据集,并重复此过程五次,我们可能会得到五个不同的测试误差率图。以下图表展示了这五次实验的测试误差率:

图 4.2:在一个示例数据集上,使用五种不同的训练集/测试集划分绘制的测试误差率图

图 4.2:在一个示例数据集上,使用五种不同的训练集/测试集划分绘制的测试误差率图

如你所见,每次实验中的测试误差率差异很大。模型评估结果的这种变化表明,仅仅将数据集分为训练集和测试集一次的简单策略可能无法提供对模型性能的稳健且准确的估计。

总结来说,我们在上一章学习的训练集/测试集方法有一个明显的优点,那就是简单、易于实现且计算成本低。然而,它也有一些缺点,具体如下:

  • 第一个缺点是,它对模型误差率的估计在很大程度上依赖于到底是哪些数据被分配到测试集,哪些数据被分配到训练集。

  • 第二个缺点是,在这种方法中,我们只在数据的一个子集上训练模型。当使用较少数据进行训练时,机器学习模型的表现通常会更差。

由于模型的性能可以通过在整个数据集上训练来提高,我们始终在寻找将所有可用数据点纳入训练的方法。此外,我们还希望通过将所有可用数据点纳入评估来获得对模型性能的稳健估计。这些目标可以通过使用交叉验证技术来实现。以下是两种交叉验证方法:

  • K 折交叉验证

  • 留一法交叉验证

K 折交叉验证

k折交叉验证中,我们不是将数据集划分为两个子集,而是将数据集划分为k个大小相近的子集或折叠。在方法的第一次迭代中,第一个折叠被作为测试集。模型在剩余的k-1个折叠上进行训练,然后在第一个折叠上进行评估(第一个折叠用于估算测试误差率)。

这个过程会重复k次,每次迭代使用不同的折叠作为测试集,而其余的折叠作为训练集。最终,这种方法会得到k个不同的测试误差率。模型误差率的最终k折交叉验证估计是通过平均这k个测试误差率计算得出的。

以下图示说明了k折交叉验证方法中的数据集划分过程:

图 4.3:k 折交叉验证方法中的数据集划分概述

图 4.3:k 折交叉验证方法中的数据集划分概述

在实践中,我们通常执行k-fold 交叉验证,其中k=5k=10,如果你在选择适合你数据集的值时遇到困难,这些是推荐的值。决定使用多少折叠取决于数据集中的示例数量和可用的计算能力。如果k=5,模型将被训练和评估五次,而如果k=10,这个过程将重复 10 次。折叠数越高,执行 k 折交叉验证所需的时间就越长。

在 k 折交叉验证中,示例分配到每个折叠是完全随机的。然而,通过查看前面的示意图,你会发现,最终每个数据点都会同时用于训练和评估。这就是为什么如果你在相同的数据集和相同的模型上重复多次 k 折交叉验证,最终报告的测试误差率几乎是相同的。因此,与训练集/测试集方法相比,k 折交叉验证的结果不会受到高方差的影响。现在,我们来看一下交叉验证的第二种形式:留一法验证。

留一法交叉验证

留一法LOO)是交叉验证技术的一种变体,在这种方法中,数据集不会被划分为两个大小相当的子集用于训练集和测试集,而是仅使用一个数据点进行评估。如果整个数据集中有n个数据示例,在每次LOO 交叉验证迭代中,模型会在n-1个示例上进行训练,而剩下的单个示例会用于计算测试误差率。

仅使用一个示例来估计测试误差率会导致对模型性能的无偏但高方差的估计;它是无偏的,因为这个示例没有参与训练模型,它具有高方差,因为它仅基于一个数据示例来计算,并且会根据使用的具体数据示例有所变化。这个过程会重复n次,在每次迭代中,使用不同的数据示例进行评估。最终,方法会得到n个不同的测试误差率,最终的LOO 交叉验证测试误差估计是通过平均这n个误差率来计算的。

LOO 交叉验证方法中数据集划分过程的示意图如下所示:

图 4.4:LOO 交叉验证方法中数据集划分的概述

图 4.4:LOO 交叉验证方法中数据集划分的概述

在每次留一交叉验证的迭代中,几乎所有的数据示例都用于训练模型。另一方面,在训练集/测试集方法中,数据的一个相对较大的子集用于评估,而不参与训练。因此,留一交叉验证对模型性能的估计更接近于在整个数据集上训练的模型的性能,这也是留一交叉验证相对于训练集/测试集方法的主要优势。

此外,由于在每次留一交叉验证的迭代中,仅使用一个唯一的数据示例进行评估,并且每个数据示例也都用于训练,因此这种方法没有随机性。因此,如果你在相同的数据集和相同的模型上重复进行多次留一交叉验证,最终报告的测试误差率每次都会完全相同。

留一交叉验证的缺点是计算开销大。其原因是模型需要训练n次,在n较大和/或网络较大的情况下,完成训练所需的时间会很长。留一交叉验证k 折交叉验证各有其优缺点,接下来我们将进行详细比较。

比较 K 折交叉验证和留一交叉验证方法

通过比较前两个图表,可以明显看出留一交叉验证实际上是k 折交叉验证的一个特殊情况,其中k=n。然而,正如之前提到的,选择k=n在计算上非常昂贵,相比之下,选择k=5k=10更为高效。

因此,k 折交叉验证相对于留一交叉验证的第一个优势是计算开销较小。下表比较了低 k 折交叉验证高 k 折交叉验证留一交叉验证,以及无交叉验证偏差方差方面的差异。表格显示,最大的偏差出现在简单的训练集-测试集划分方法中,最大的方差出现在留一交叉验证中。k 折交叉验证位于两者之间。这也是为什么k 折交叉验证通常是大多数机器学习任务中最合适的选择:

图 4.5:比较训练集-测试集划分、k 折交叉验证和留一交叉验证

交叉验证方法

](https://github.com/OpenDocCN/freelearn-dl-pt5-zh/raw/master/docs/dl-keras-ws/img/B15777_04_06.jpg)

图 4.5:比较训练集-测试集划分、k 折交叉验证和留一交叉验证方法

以下图表比较了训练集/测试集方法、k 折交叉验证留一交叉验证偏差方差方面的差异:

图 4.6:比较训练集/测试集方法、k 折交叉验证和留一交叉验证在偏差和方差方面的差异

](https://github.com/OpenDocCN/freelearn-dl-pt5-zh/raw/master/docs/dl-keras-ws/img/B15777_04_05.jpg)

图 4.6:比较训练集/测试集方法、k 折交叉验证和留一交叉验证在偏差和方差方面的差异

一般来说,在机器学习和数据分析中,最理想的模型是具有最低偏差最低方差的模型。如前面的图所示,图中间标记的区域,其中偏差方差都较低,是我们关注的重点。事实证明,这个区域等同于 k-fold 交叉验证,其中 k 的值介于 510 之间。在下一节中,我们将探索如何在实践中实现不同的交叉验证方法。

深度学习模型的交叉验证

在本节中,你将学习如何使用 Keras 封装器与 scikit-learn 配合使用,这是一种有用的工具,可以让我们将 Keras 模型作为 scikit-learn 工作流的一部分。因此,像交叉验证这样的 scikit-learn 方法和函数可以轻松地应用到 Keras 模型中。

你将一步步学习如何在本节中使用 scikit-learn 实现你在上一节中学到的交叉验证方法。此外,你还将学习如何使用交叉验证评估 Keras 深度学习模型,并使用 Keras 封装器与 scikit-learn 配合使用。最后,你将通过解决一个实际数据集中的问题来实践所学的内容。

Keras 封装器与 scikit-learn

在一般的机器学习和数据分析中,scikit-learn 库比 Keras 更加丰富且易于使用。这就是为什么能够在 Keras 模型上使用 scikit-learn 方法会非常有价值的原因。

幸运的是,Keras 提供了一个有用的封装器 keras.wrappers.scikit_learn,它允许我们为深度学习模型构建 scikit-learn 接口,这些模型可以作为分类或回归估计器在 scikit-learn 中使用。封装器有两种类型:一种用于分类估计器,另一种用于回归估计器。以下代码用于定义这些 scikit-learn 接口:

keras.wrappers.scikit_learn.KerasClassifier(build_fn=None, **sk_params)
# wrappers for classification estimators
keras.wrappers.scikit_learn.KerasRegressor(build_fn=None, **sk_params)
# wrappers for regression estimators

build_fn 参数需要是一个可调用的函数,在其内部定义、编译并返回一个 Keras 顺序模型。

sk_params 参数可以接受用于构建模型的参数(如层的激活函数)和用于拟合模型的参数(如训练轮数和批量大小)。这一点将在接下来的练习中得以应用,我们将在该练习中使用 Keras 封装器解决回归问题。

注意

本章的所有活动将在 Jupyter notebook 中开发。请下载本书的 GitHub 仓库以及所有已准备好的模板,点击以下链接即可找到:

packt.live/3btnjfA

练习 4.01:为回归问题构建 Keras 封装器与 scikit-learn 配合使用

在本次练习中,你将学习如何一步一步构建 Keras 深度学习模型的包装器,使其可以在 scikit-learn 工作流中使用。首先,加载一个包含908个数据点的回归问题数据集,其中每个记录描述了化学物质的六个属性,目标是预测鱼类 Pimephales promelas 的急性毒性,即LC50

注意

注意下面字符串中的斜杠。记住,反斜杠(\)用于将代码分割为多行,而正斜杠(/)是路径的一部分。

# import data
import pandas as pd
colnames = ['CIC0', 'SM1_Dz(Z)', 'GATS1i', \
            'NdsCH', 'NdssC','MLOGP', 'LC50']
data = pd.read_csv('../data/qsar_fish_toxicity.csv', \
                   sep=';', names=colnames)
X = data.drop('LC50', axis=1)
y = data['LC50']
# 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])
# print output range
print("Output Range = [%f, %f]" %(min(y), max(y)))

这是预期的输出:

Number of Examples in the Dataset =  908
Number of Features for each example =  6
Output Range = [0.053000, 9.612000]

由于该数据集的输出是一个数值,因此这是一个回归问题。目标是构建一个模型,预测给定化学物质的其他属性时,鱼类的急性毒性LC50。现在,让我们一步步来看:

  1. 定义一个函数来构建并返回用于回归问题的 Keras 模型。你定义的 Keras 模型必须有一个隐藏层,大小为8,并使用ReLU 激活函数。同时,使用均方误差MSE)损失函数和Adam 优化器来编译模型:

    from keras.models import Sequential
    from keras.layers import Dense, Activation
    # Create the function that returns the keras model
    def build_model():
        # build the Keras model
        model = Sequential()
        model.add(Dense(8, input_dim=X.shape[1], \
                  activation='relu'))
        model.add(Dense(1))
        # Compile the model
        model.compile(loss='mean_squared_error', \
                      optimizer='adam')
        # return the model
        return model  
    
  2. 现在,使用 Keras 包装器和 scikit-learn 创建 scikit-learn 接口来构建你的模型。记住,你需要在这里提供epochsbatch_sizeverbose参数:

    # build the scikit-Learn interface for the keras model
    from keras.wrappers.scikit_learn import KerasRegressor
    YourModel = KerasRegressor(build_fn= build_model, \
                               epochs=100, \
                               batch_size=20, \
                               verbose=1) 
    

    现在,YourModel已经可以作为 scikit-learn 中的回归估计器使用。

在本次练习中,我们学习了如何使用模拟数据集通过 scikit-learn 构建 Keras 包装器来解决回归问题。

注意

若要访问此特定部分的源代码,请参阅packt.live/38nuqVP

你也可以在网上运行这个示例,访问packt.live/31MLgMF

我们将在本章剩余的练习中继续使用此数据集实现交叉验证。

使用 scikit-learn 进行交叉验证

在上一章中,你学会了如何在 scikit-learn 中轻松进行训练集/测试集的划分。假设你的原始数据集存储在Xy数组中。你可以使用以下命令将它们随机划分为训练集和测试集:

from sklearn.model_selection import train_test_split
X_train, X_test, y_train, y_test = train_test_split\
                                   (X, y, test_size=0.3, \
                                    random_state=0)

test_size参数可以设置为介于01之间的任何数值,具体取决于你希望测试集的大小。通过为random_state参数提供一个int类型的数字,你将能够选择随机数生成器的种子。

在 scikit-learn 中执行交叉验证的最简单方法是使用cross_val_score函数。为此,你需要首先定义你的估计器(在我们的案例中,估计器将是一个 Keras 模型)。然后,你将能够使用以下命令对估计器/模型进行交叉验证:

from sklearn.model_selection import cross_val_score
scores = cross_val_score(YourModel, X, y, cv=5)

请注意,我们将 Keras 模型和原始数据集作为参数传递给 cross_val_score 函数,并指定折数(即 cv 参数)。在这里,我们使用了 cv=5,所以 cross_val_score 函数会将数据集随机划分为五个折,并使用五个不同的训练集和测试集对模型进行五次训练和拟合。它将在每次迭代/折叠时计算默认的模型评估指标(或在定义 Keras 模型时提供的指标),并将它们存储在 scores 中。我们可以如下打印最终的交叉验证得分:

print(scores.mean())

之前我们提到过,cross_val_score 函数返回的得分是我们模型的默认指标,或者是我们在定义模型时为其确定的指标。然而,也可以通过在调用 cross_val_score 函数时提供所需的指标作为 scoring 参数,来更改交叉验证的指标。

注意

您可以在这里了解如何在 cross_val_score 函数的 scoring 参数中提供所需的评估指标:scikit-learn.org/stable/modules/model_evaluation.html#scoring-parameter

通过为 cross_val_score 函数的 cv 参数提供一个整数,我们告诉函数在数据集上执行 k-fold 交叉验证。然而,scikit-learn 中还有几种其他的迭代器可以分配给 cv,以执行数据集的其他交叉验证变种。例如,以下代码块将对数据集执行 LOO 交叉验证

from sklearn.model_selection import LeaveOneOut
loo = LeaveOneOut()
scores = cross_val_score(YourModel, X, y, cv=loo)

在下一节中,我们将探索 scikit-learn 中的 k-fold 交叉验证,并看看它如何与 Keras 模型一起使用。

scikit-learn 中的交叉验证迭代器

这里提供了 scikit-learn 中最常用的交叉验证迭代器的列表,并简要描述了它们的功能:

  • KFold(n_splits=?)

    这将把数据集划分为 k 个折或组。n_splits 参数是必需的,用于确定使用多少个折。如果 n_splits=n,它将等同于 LOO 交叉验证

  • RepeatedKFold(n_splits=?, n_repeats=?, random_state=random_state)

    这将重复执行 k-fold 交叉验证 n_repeats 次。

  • LeaveOneOut()

    这将对数据集进行 LOO 交叉验证 的划分。

  • ShuffleSplit(n_splits=?, test_size=?, random_state=random_state)

    这将生成 n_splits 个随机且独立的训练集/测试集数据集划分。可以使用 random_state 参数存储随机数生成器的种子;如果这么做,数据集的划分将是可重现的。

除了常规迭代器(例如这里提到的迭代器),还有 分层 版本。分层抽样在数据集的不同类别的样本数量不平衡时非常有用。例如,假设我们想设计一个分类器来预测某人是否会拖欠信用卡债务,其中数据集中几乎有 95% 的样本属于 负类。分层抽样确保在每个 训练集/测试集 划分中保留类别的相对频率。对于这种情况,建议使用分层版本的迭代器。

通常,在使用训练集训练和评估模型之前,我们会对其进行预处理,以便将样本缩放,使其均值为 0,标准差为 1。在 训练集/测试集 方法中,我们需要缩放训练集并存储该转换。以下代码块将为我们完成这项工作:

from sklearn.preprocessing import StandardScaler
scaler = StandardScaler()
X_train = scaler.fit_transform(X_train)
X_test = scaler.transform(X_test)

这是在我们的 Xy 数据集上执行 k=5分层 K 折交叉验证 的示例:

from sklearn.model_selection import StratifiedKFold
skf = StratifiedKFold(n_splits=5)
scores = cross_val_score(YourModel, X, y, cv=skf)

注意

你可以在这里了解更多关于 scikit-learn 中交叉验证迭代器的内容:

scikit-learn.org/stable/modules/cross_validation.html#cross-validation-iterators

现在我们了解了交叉验证迭代器,可以在练习中将它们付诸实践。

练习 4.02:使用交叉验证评估深度神经网络

在这次练习中,我们将把我们在本主题中学习到的所有交叉验证相关概念和方法结合起来。我们将再次经历所有步骤,从定义 Keras 深度学习模型到将其转移到 scikit-learn 工作流并执行交叉验证以评估其性能。从某种意义上说,这个练习是我们到目前为止所学内容的回顾,涵盖的内容对于 活动 4.01使用交叉验证评估先进的纤维化诊断分类器模型)将非常有帮助:

  1. 第一步始终是加载你想要构建模型的数据集。首先,加载回归问题的 908 个数据点的数据集,每个记录描述了一个化学物质的六个属性,目标是预测对鱼类 Pimephales promelas 的急性毒性,即 LC50

    # import data
    import pandas as pd
    colnames = ['CIC0', 'SM1_Dz(Z)', 'GATS1i', \
                'NdsCH', 'NdssC','MLOGP', 'LC50']
    data = pd.read_csv('../data/qsar_fish_toxicity.csv', \
                       sep=';', names=colnames)
    X = data.drop('LC50', axis=1)
    y = data['LC50']
    # 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])
    # print output range
    print("Output Range = [%f, %f]" %(min(y), max(y)))
    

    输出如下:

    Number of Examples in the Dataset =  908
    Number of Features for each example =  6
    Output Range = [0.053000, 9.612000]
    
  2. 定义一个函数,返回一个具有单个隐藏层(大小为 8,使用 ReLU 激活 函数)的 Keras 模型,使用 均方误差 (MSE) 损失函数和 Adam 优化器

    from keras.models import Sequential
    from keras.layers import Dense, Activation
    # Create the function that returns the keras model
    def build_model():
        # build the Keras model
        model = Sequential()
        model.add(Dense(8, input_dim=X.shape[1], \
                  activation='relu'))
        model.add(Dense(1))
        # Compile the model
        model.compile(loss='mean_squared_error', \
                      optimizer='adam')
        # return the model
        return model
    
  3. 设置seed并使用包装器构建我们在步骤 2中定义的 Keras 模型的 scikit-learn 接口:

    # build the scikit-Learn interface for the keras model
    from keras.wrappers.scikit_learn import KerasRegressor
    import numpy as np
    from tensorflow import random
    seed = 1
    np.random.seed(seed)
    random.set_seed(seed)
    YourModel = KerasRegressor(build_fn= build_model, \
                               epochs=100, batch_size=20, \
                               verbose=1 , shuffle=False)
    
  4. 定义要用于交叉验证的迭代器。我们来进行 5 折交叉验证

    # define the iterator to perform 5-fold cross-validation
    from sklearn.model_selection import KFold
    kf = KFold(n_splits=5)
    
  5. 调用 cross_val_score 函数来执行交叉验证。根据可用的计算能力,这一步可能需要一些时间才能完成:

    # perform cross-validation on X, y
    from sklearn.model_selection import cross_val_score
    results = cross_val_score(YourModel, X, y, cv=kf) 
    
  6. 一旦交叉验证完成,打印最终交叉验证模型性能评估(性能的默认评估指标为测试损失):

    # print the result
    print(f"Final Cross-Validation Loss = {abs(results.mean()):.4f}")
    

    这是一个示例输出:

    Final Cross-Validation Loss = 0.9680
    

交叉验证损失表明,在该数据集上训练的 Keras 模型能够以0.9680的平均损失预测化学物质的LC50值。我们将在下一次练习中进一步研究该模型。

这些就是使用 scikit-learn 中的交叉验证评估 Keras 深度学习模型所需的所有步骤。现在,我们将在活动中将这些步骤付诸实践。

注意

要访问此特定部分的源代码,请参考 packt.live/3eRTlTM

你也可以在网上运行此示例,访问 packt.live/31IdVT0

活动 4.01:使用交叉验证评估高级纤维化诊断分类器的模型

我们在第 3.02 活动中了解了肝炎 C 数据集,第三章Keras 深度学习中的高级纤维化诊断与神经网络。该数据集包含了1385名接受肝炎 C 治疗剂量的患者的信息。每位患者都有28个不同的属性可供参考,如年龄、性别、BMI 等,以及一个类标签,该标签只能取两个值:1,表示高级纤维化;0,表示没有高级纤维化的迹象。这是一个二元/两类分类问题,输入维度为28

第三章Keras 深度学习中,我们构建了 Keras 模型来对该数据集进行分类。我们使用训练集/测试集划分训练并评估模型,并报告了测试误差率。在本活动中,我们将运用本主题中学到的知识,使用k 折交叉验证来训练和评估深度学习模型。我们将使用在前一个活动中得出的最佳测试误差率的模型。目标是将交叉验证误差率与训练集/测试集方法的误差率进行比较:

  1. 导入必要的库。从 GitHub 的Chapter04文件夹中的data子文件夹加载数据集,使用X = pd.read_csv('../data/HCV_feats.csv'), y = pd.read_csv('../data/HCV_target.csv')。打印数据集中的示例数量、可用的特征数量以及类标签的可能值。

  2. 定义一个函数来返回 Keras 模型。该 Keras 模型将是一个深度神经网络,包含两个隐藏层,其中第一个隐藏层大小为 4第二个隐藏层大小为 2,并使用tanh 激活函数进行分类。使用以下超参数值:

    optimizer = 'adam', loss = 'binary_crossentropy', metrics = ['accuracy']

  3. 为 Keras 模型构建 scikit-learn 接口,设置epochs=100batch_size=20,并将shuffle=False。将交叉验证迭代器定义为StratifiedKFold,并设置k=5。对模型进行 k 折交叉验证,并存储分数。

  4. 打印每次迭代/折叠的准确性,以及总体交叉验证准确性和其相关标准差。

  5. 将此结果与第三章《使用 Keras 的深度学习》中的活动 3.02《使用神经网络进行高级纤维化诊断》中的结果进行比较。

完成前述步骤后,期望的输出将如下所示:

Test accuracy at fold 1 = 0.5198556184768677
Test accuracy at fold 2 = 0.4693140685558319
Test accuracy at fold 3 = 0.512635350227356
Test accuracy at fold 4 = 0.5740072131156921
Test accuracy at fold 5 = 0.5523465871810913
Final Cross Validation Test Accuracy: 0.5256317675113678
Standard Deviation of Final Test Accuracy: 0.03584760640500936

注意

本活动的解决方案可以在第 381 页找到。

第三章《使用 Keras 的深度学习》中的活动 3.02《使用神经网络进行高级纤维化诊断》中,我们通过训练集/测试集方法得到的准确率为49.819%,这低于我们在对相同深度学习模型和相同数据集进行5 折交叉验证时所取得的测试准确率,但低于其中一折的准确率。

这种差异的原因在于,通过训练集/测试集方法计算的测试错误率是通过仅将数据点的一个子集纳入模型评估得出的。另一方面,这里计算的测试错误率是通过将所有数据点纳入评估,因此,这种模型性能的估计更为准确且更具鲁棒性,在未见过的测试数据集上表现更好。

在本活动中,我们使用交叉验证对涉及真实数据集的问题进行模型评估。提高模型评估的准确性并不是使用交叉验证的唯一目的,它还可以用来为给定的问题选择最佳模型或参数。

使用交叉验证进行模型选择

交叉验证为我们提供了模型在未见过的示例上的鲁棒性估计。因此,它可以用来在特定问题中决定两个模型之间的优劣,或者决定在特定问题中使用哪一组模型参数(或超参数)。在这些情况下,我们希望找出哪个模型或哪一组模型参数/超参数会导致最低的测试错误率。因此,我们将选择该模型或该组参数/超参数作为我们问题的解决方案。

在本节中,你将练习使用交叉验证来完成这一任务。你将学习如何为深度学习模型定义一组超参数,然后编写用户定义的函数,对模型进行交叉验证,涵盖每种可能的超参数组合。然后,你将观察哪一组超参数组合导致最低的测试错误率,这组超参数将成为你最终模型的选择。

模型评估与模型选择中的交叉验证

在本节中,我们将深入探讨使用交叉验证进行模型评估与模型选择之间的区别。到目前为止,我们已经了解到,在训练集上评估模型会导致对模型在未见样本上的错误率的低估。将数据集分为训练集测试集可以更准确地估计模型的表现,但会面临较高的方差问题。

最后,交叉验证能够更稳健、准确地估计模型在未见样本上的表现。以下图示展示了这三种模型评估方法所产生的错误率估计。

以下图示展示了在训练集/测试集方法的错误率估计稍低于交叉验证估计的情况。然而,重要的是要记住,训练集/测试集的错误率也可能高于交叉验证估计的错误率,具体取决于测试集中包含的数据(因此会存在高方差问题)。另一方面,在训练集上进行评估所得到的错误率始终低于其他两种方法:

图 4.7:展示三种模型评估方法所产生的错误率估计

图 4.7:展示三种模型评估方法所产生的错误率估计

我们已经确定,交叉验证提供了模型在独立数据样本上的最佳表现估计。知道这一点后,我们可以使用交叉验证来决定针对特定问题使用哪个模型。例如,如果我们有四个不同的模型,并希望确定哪个模型最适合某一数据集,我们可以使用交叉验证训练并评估每个模型,选择交叉验证错误率最低的模型作为最终模型。以下图展示了与四个假设模型相关的交叉验证错误率。由此,我们可以得出结论,模型 1最适合该问题,而模型 4是最差的选择。这四个模型可能是深度神经网络,它们具有不同数量的隐藏层和隐藏层中不同数量的单元:

图 4.8:展示与四个假设模型相关的交叉验证错误率假设模型

图 4.8:展示与四个假设模型相关的交叉验证错误率

在确定了哪种模型最适合特定问题后,下一步是为该模型选择最佳的参数集或超参数。之前我们讨论了,在构建深度神经网络时,模型需要选择多个超参数,而且每个超参数都有多个选择。

这些超参数包括激活函数的类型、损失函数和优化器,以及训练的轮次和批次大小。我们可以定义每个超参数的可能选择集合,然后实现模型并结合交叉验证,找出最佳的超参数组合。

以下图展示了与四组不同超参数集相关的交叉验证误差率的插图。从中我们可以得出结论,超参数集 1是该模型的最佳选择,因为与超参数集 1对应的线条在交叉验证误差率上具有最低的值:

图 4.9:展示与四组不同超参数集相关的交叉验证误差率的插图,针对一个假设的深度学习模型

图 4.9:展示与四组不同超参数集相关的交叉验证误差率的插图,针对一个假设的深度学习模型

在下一个练习中,我们将学习如何遍历不同的模型架构和超参数,以找到结果最优的模型集合。

练习 4.03:编写用户定义的函数来实现带交叉验证的深度学习模型

在本次练习中,您将学习如何使用交叉验证进行模型选择。

首先,加载包含908个数据点的回归问题数据集,每条记录描述了一种化学品的六个属性,目标是其对鱼类 Pimephales promelas 的急性毒性,或LC50。目标是构建一个模型,根据化学品属性预测每种化学品的LC50

# import data
import pandas as pd
import numpy as np
from tensorflow import random
colnames = ['CIC0', 'SM1_Dz(Z)', 'GATS1i', 'NdsCH', \
            'NdssC','MLOGP', 'LC50']
data = pd.read_csv('../data/qsar_fish_toxicity.csv', \
                   sep=';', names=colnames)
X = data.drop('LC50', axis=1)
y = data['LC50']

按照以下步骤完成本次练习:

  1. 定义三个函数,返回三个 Keras 模型。第一个模型应该有一个大小为 4的隐藏层,第二个模型应该有一个大小为 8的隐藏层,第三个模型应该有两个隐藏层,第一个层的大小为 4,第二个层的大小为 2。对于所有隐藏层,使用ReLU 激活函数。目标是找出这三种模型中,哪个模型能带来最低的交叉验证误差率:

    # Define the Keras models
    from keras.models import Sequential
    from keras.layers import Dense
    def build_model_1():
        # build the Keras model_1
        model = Sequential()
        model.add(Dense(4, input_dim=X.shape[1], \
                        activation='relu'))
        model.add(Dense(1))
        # Compile the model
        model.compile(loss='mean_squared_error', \
                      optimizer='adam')
        # return the model
        return model
    def build_model_2():
        # build the Keras model_2
        model = Sequential()
        model.add(Dense(8, input_dim=X.shape[1], \
                  activation='relu'))
        model.add(Dense(1))
        # Compile the model
        model.compile(loss='mean_squared_error', \
                      optimizer='adam')
        # return the model
        return model
    def build_model_3():
        # build the Keras model_3
        model = Sequential()
        model.add(Dense(4, input_dim=X.shape[1], \
                        activation='relu'))
        model.add(Dense(2, activation='relu'))
        model.add(Dense(1))
        # Compile the model
        model.compile(loss='mean_squared_error', \
                      optimizer='adam')
        # return the model
        return model
    
  2. 编写一个循环来构建 Keras 包装器,并对三个模型执行3 折交叉验证。存储每个模型的得分:

    """
    define a seed for random number generator so the result will be reproducible
    """
    seed = 1
    np.random.seed(seed)
    random.set_seed(seed)
    # perform cross-validation on each model
    from keras.wrappers.scikit_learn import KerasRegressor
    from sklearn.model_selection import KFold
    from sklearn.model_selection import cross_val_score
    results_1 = []
    models = [build_model_1, build_model_2, build_model_3]
    # loop over three models
    for m in range(len(models)):
        model = KerasRegressor(build_fn=models[m], \
                               epochs=100, batch_size=20, \
                               verbose=0, shuffle=False)
        kf = KFold(n_splits=3)
        result = cross_val_score(model, X, y, cv=kf)
        results_1.append(result)
    
  3. 打印每个模型的最终交叉验证误差率,以找出哪个模型的误差率较低:

    # print the cross-validation scores
    print("Cross-Validation Loss for Model 1 =", \
          abs(results_1[0].mean()))
    print("Cross-Validation Loss for Model 2 =", \
          abs(results_1[1].mean()))
    print("Cross-Validation Loss for Model 3 =", \
          abs(results_1[2].mean()))
    

    下面是一个输出示例:

    Cross-Validation Loss for Model 1 = 0.990475798256843
    Cross-Validation Loss for Model 2 = 0.926532513151634
    Cross-Validation Loss for Model 3 = 0.9735719371528117
    

    模型 2的错误率最低,因此我们将在接下来的步骤中使用它。

  4. 再次使用交叉验证确定导致最低交叉验证误差率的模型的轮次和批次大小。编写代码,对epochsbatch_sizeepochs=[100, 150]batch_size=[20, 15]范围内的所有可能组合执行3 折交叉验证并存储得分:

    """
    define a seed for random number generator so the result will be reproducible
    """
    np.random.seed(seed)
    random.set_seed(seed)
    results_2 = []
    epochs = [100, 150]
    batches = [20, 15]
    # Loop over pairs of epochs and batch_size
    for e in range(len(epochs)):
        for b in range(len(batches)):
            model = KerasRegressor(build_fn= build_model_3, \
                                   epochs= epochs[e], \
                                   batch_size= batches[b], \
                                   verbose=0, \
                                   shuffle=False)
            kf = KFold(n_splits=3)
            result = cross_val_score(model, X, y, cv=kf)
            results_2.append(result)
    

    注意

    上面的代码块使用了两个 for 循环来执行 3 折交叉验证,针对所有可能的 epochsbatch_size 组合。由于每个参数都有两个选择,因此有四种不同的组合,所以交叉验证将进行四次。

  5. 打印每对 epochs/batch_size 的最终交叉验证错误率,以找出哪个组合的错误率最低:

    """
    Print cross-validation score for each possible pair of epochs, batch_size
    """
    c = 0
    for e in range(len(epochs)):
        for b in range(len(batches)):
            print("batch_size =", batches[b],", \
                  epochs =", epochs[e], ", Test Loss =", \
                  abs(results_2[c].mean()))
            c += 1
    

    这是一个示例输出:

    batch_size = 20 , epochs = 100 , Test Loss = 0.9359159401008821
    batch_size = 15 , epochs = 100 , Test Loss = 0.9642481369794683
    batch_size = 20 , epochs = 150 , Test Loss = 0.9561188386646661
    batch_size = 15 , epochs = 150 , Test Loss = 0.9359079093029896
    

    如你所见,epochs=150batch_size=15,以及 epochs=100batch_size=20 的性能几乎相同。因此,我们将在下一步选择 epochs=100batch_size=20 来加快这一过程。

  6. 再次使用交叉验证,以决定隐藏层的激活函数和模型的优化器,选择范围为 activations = ['relu', 'tanh']optimizers = ['sgd', 'adam', 'rmsprop']。记得使用上一步骤中最佳的 batch_sizeepochs 组合:

    # Modify build_model_2 function
    def build_model_2(activation='relu', optimizer='adam'):
        # build the Keras model_2
        model = Sequential()
        model.add(Dense(8, input_dim=X.shape[1], \
                  activation=activation))
        model.add(Dense(1))
        # Compile the model
        model.compile(loss='mean_squared_error', \
                      optimizer=optimizer)
        # return the model
        return model
    results_3 = []
    activations = ['relu', 'tanh']
    optimizers = ['sgd', 'adam', 'rmsprop']
    """
    Define a seed for the random number generator so the result will be reproducible
    """
    np.random.seed(seed)
    random.set_seed(seed)
    # Loop over pairs of activation and optimizer
    for o in range(len(optimizers)):
        for a in range(len(activations)):
            optimizer = optimizers[o]
            activation = activations[a]
            model = KerasRegressor(build_fn= build_model_3, \
                                   epochs=100, batch_size=20, \
                                   verbose=0, shuffle=False)
            kf = KFold(n_splits=3)
            result = cross_val_score(model, X, y, cv=kf)
            results_3.append(result)
    

    注意

    注意,我们必须通过将 activationoptimizer 及其默认值作为函数的参数来修改 build_model_2 函数。

  7. 打印每对 activationoptimizer 的最终交叉验证错误率,以找出哪个组合的错误率最低:

    """
    Print cross-validation score for each possible pair of optimizer, activation
    """
    c = 0
    for o in range(len(optimizers)):
        for a in range(len(activations)):
            print("activation = ", activations[a],", \
                  optimizer = ", optimizers[o], ", \
                  Test Loss = ", abs(results_3[c].mean()))
            c += 1
    

    这是输出:

    activation =  relu , optimizer =  sgd , Test Loss =  1.0123592540516995
    activation =  tanh , optimizer =  sgd , Test Loss =  3.393908379781118
    activation =  relu , optimizer =  adam , Test Loss =  0.9662686089392641
    activation =  tanh , optimizer =  adam , Test Loss =  2.1369285960222144
    activation =  relu , optimizer =  rmsprop , Test Loss =  2.1892826984214984
    activation =  tanh , optimizer =  rmsprop , Test Loss =  2.2029884275363014
    
  8. activation='relu'optimizer='adam' 的组合产生了最低的错误率。同时,activation='relu'optimizer='sgd' 的组合结果几乎一样好。因此,我们可以在最终模型中使用这两种优化器之一来预测这个数据集的水生毒性。

    注意

    要访问此特定部分的源代码,请参考 packt.live/2BYCwbg

    你也可以在网上运行这个示例,网址是 packt.live/3gofLfP

现在,你已经准备好使用交叉验证在另一个数据集上练习模型选择。在活动 4.02使用交叉验证进行模型选择以诊断高级纤维化分类器中,你将通过自己实现这些步骤,在肝炎 C 数据集的分类问题上进一步练习。

注意

练习 4.02使用交叉验证评估深度神经网络,以及 练习 4.03编写用户定义的函数实现带交叉验证的深度学习模型,涉及多次执行 k 折交叉验证,因此步骤可能需要几分钟才能完成。如果它们需要太长时间才能完成,你可以尝试通过减少折数或训练轮数(epochs)或增加批次大小来加速过程。显然,如果这样做,你将获得与预期输出不同的结果,但选择模型和超参数的原则仍然适用。

活动 4.02:使用交叉验证进行模型选择以诊断高级纤维化分类器

在本活动中,我们将通过使用交叉验证来选择模型和超参数,以改进我们的肝炎 C 数据集分类器。请按照以下步骤操作:

  1. 导入所需的包。从 GitHub 的Chapter04文件夹中的data子文件夹加载数据集,使用X = pd.read_csv('../data/HCV_feats.csv'), y = pd.read_csv('../data/HCV_target.csv')

  2. 定义三个函数,每个函数返回一个不同的 Keras 模型。第一个 Keras 模型将是一个深度神经网络,具有三个隐藏层,每个隐藏层的大小为 4,并使用ReLU 激活函数。第二个 Keras 模型将是一个深度神经网络,具有两个隐藏层,第一个隐藏层的大小为 4,第二个隐藏层的大小为 2,并使用ReLU 激活函数。第三个 Keras 模型将是一个深度神经网络,具有两个隐藏层,两个隐藏层的大小为 8,并使用ReLU 激活函数。使用以下超参数值:

    optimizer = 'adam', loss = 'binary_crossentropy', metrics = ['accuracy']

  3. 编写代码,遍历三个模型并对每个模型执行5 折交叉验证(在此步骤中使用epochs=100batch_size=20shuffle=False)。将所有的交叉验证得分存储在一个列表中,并打印结果。哪个模型结果获得了最佳准确率?

    注意

    本活动的步骤 345涉及分别对三个、四个和六个模型执行5 折交叉验证。因此,它们可能需要一些时间来完成。

  4. 编写代码,使用epochs = [100, 200]batches = [10, 20]的值分别作为epochsbatch_size。对每一对可能的组合在 Keras 模型上进行 5 折交叉验证,该模型是在第 3 步中得到的最佳准确率。将所有的交叉验证得分存储在一个列表中,并打印结果。哪个epochsbatch_size的组合结果获得了最佳准确率?

  5. 编写代码,使用optimizers = ['rmsprop', 'adam', 'sgd']activations = ['relu', 'tanh']的值分别作为optimizeractivation。对每一对可能的组合在 Keras 模型上进行 5 折交叉验证,该模型是在第 3 步中得到的最佳准确率。使用在第 4 步中得到的最佳准确率的batch_sizeepochs值。将所有的交叉验证得分存储在一个列表中,并打印结果。哪个optimizeractivation的组合结果获得了最佳准确率?

    注意

    请注意,初始化权重和偏差以及在执行 k 折交叉验证时选择哪些示例纳入每一折中,都存在随机性。因此,如果你运行相同的代码两次,可能会得到完全不同的结果。为此,在构建和训练神经网络时,以及在执行交叉验证时,设置随机种子非常重要。通过这样做,你可以确保每次重新运行代码时,使用的是完全相同的神经网络初始化,以及完全相同的训练集和测试集。

实现这些步骤后,预期输出将如下所示:

activation =  relu , optimizer =  rmsprop , Test accuracy =  0.5234657049179077
activation =  tanh , optimizer =  rmsprop , Test accuracy =  0.49602887630462644
activation =  relu , optimizer =  adam , Test accuracy =  0.5039711117744445
activation =  tanh , optimizer =  adam , Test accuracy =  0.4989169597625732
activation =  relu , optimizer =  sgd , Test accuracy =  0.48953068256378174
activation =  tanh , optimizer =  sgd , Test accuracy =  0.5191335678100586

注意

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

在本次活动中,你学习了如何使用交叉验证评估深度神经网络,以找出使分类问题的错误率最低的模型。你还学习了如何通过使用交叉验证来改进给定的分类模型,从而找到最佳的超参数集。在下一次活动中,我们将重复这个过程,并将任务转换为回归问题。

活动 4.03:使用交叉验证进行交通流量数据集的模型选择

在本次活动中,你将再次通过交叉验证进行模型选择练习。在这里,我们将使用一个模拟数据集,数据集表示一个目标变量,代表城市桥梁上每小时的交通流量,以及与交通数据相关的各种归一化特征,如一天中的时间和前一天的交通流量。我们的目标是构建一个模型,基于这些特征预测城市桥梁上的交通流量。

数据集包含10000条记录,每条记录包括10个属性/特征。目标是构建一个深度神经网络,该网络接收10个特征并预测桥上的交通流量。由于输出是一个数字,这个问题是一个回归问题。让我们开始吧:

  1. 导入所有必需的包。

  2. 打印输入和输出的大小,检查数据集中示例的数量以及每个示例的特征数量。此外,你还可以打印输出的范围(该数据集中的输出代表拥有者自住房屋的中位数,单位为千美元)。

  3. 定义三个函数,每个函数返回一个不同的 Keras 模型。第一个 Keras 模型将是一个浅层神经网络,包含一个隐藏层,隐藏层大小为10,并使用ReLU 激活函数。第二个 Keras 模型将是一个深层神经网络,包含两个隐藏层,每个隐藏层的大小为10,并使用每层的ReLU 激活函数。第三个 Keras 模型将是一个深层神经网络,包含三个隐藏层,每个隐藏层的大小为10,并使用每层的ReLU 激活函数。

    还需使用以下值:

    optimizer = 'adam', loss = 'mean_squared_error'

    注意

    步骤 456 涉及分别执行5 折交叉验证三次、四次和三次。因此,它们可能需要一些时间才能完成。

  4. 编写代码,循环遍历这三个模型,并对每个模型执行5 折交叉验证(在这一步中使用epochs=100batch_size=5,并且shuffle=False)。将所有交叉验证得分存储在一个列表中并打印结果。哪个模型得到了最低的测试错误率?

  5. 编写代码,使用epochs = [80, 100]batches = [5, 10]作为epochsbatch_size的值。对在第 4 步中获得最低测试误差率的 Keras 模型进行 5 折交叉验证。将所有交叉验证的得分存储在列表中并打印结果。哪一对epochsbatch_size值导致最低的测试误差率?

  6. 编写代码,使用optimizers = ['rmsprop', 'sgd', 'adam']并对每个可能的优化器进行5 折交叉验证,以评估在第 4 步中获得最低测试误差率的 Keras 模型。使用在第 5 步中获得最低测试误差率的batch_sizeepochs值。将所有交叉验证的得分存储在列表中并打印结果。哪种优化器导致最低的测试误差率?

实现这些步骤后,预期输出如下:

optimizer= adam  test error rate =  25.391812739372256
optimizer= sgd  test error rate =  25.140230269432067
optimizer= rmsprop  test error rate =  25.217947859764102

注意

本活动的解答可在第 391 页找到。

在这个活动中,你学习了如何使用交叉验证来评估深度神经网络,从而找到产生最低误差率的回归问题模型。此外,你还学会了如何通过交叉验证改进给定的回归模型,以便找到最佳的超参数集。

摘要

在本章中,你学习了交叉验证,这是最重要的重采样方法之一。它能够对模型在独立数据上的表现进行最佳估计。本章介绍了交叉验证的基本知识以及它的两种不同变体,留一法和 k 折交叉验证,并进行了比较。

接下来,我们介绍了带有 scikit-learn 的 Keras 封装器,这是一个非常有用的工具,它允许将执行交叉验证的 scikit-learn 方法和函数轻松应用于 Keras 模型。随后,你将学习如何按照一步步的过程实现交叉验证,以便使用带有 scikit-learn 的 Keras 封装器评估 Keras 深度学习模型。

最后,你了解到,交叉验证对模型性能的估计可以用于在不同模型之间做出选择,或者决定某个模型应使用哪些参数(或超参数)。你通过编写用户自定义函数并执行交叉验证,练习了如何使用交叉验证来选择最终模型的最佳模型或超参数组合,从而使测试误差率最小。

在下一章,你将学习到,实际上我们在这里为模型寻找最佳超参数集的过程,称为超参数调优超参数优化。此外,你将学习如何通过 scikit-learn 中的一种方法网格搜索来进行超参数调优,并且无需编写用户自定义函数来遍历可能的超参数组合。

第五章:5. 提高模型准确性

概述

本章介绍了神经网络的正则化概念。正则化的目的是防止模型在训练过程中对训练数据过拟合,并在模型在新数据上进行测试时提供更准确的结果。你将学会使用不同的正则化技术——L1 和 L2 正则化以及丢弃法正则化——来提高模型的表现。正则化是一个重要的组成部分,因为它可以防止神经网络对训练数据过拟合,帮助我们构建出在新数据上表现良好的健壮、准确的模型。通过本章的学习,你将能够在 scikit-learn 中实现网格搜索和随机搜索,找到最优的超参数。

引言

在上一章中,我们继续通过实验交叉验证,发展了使用神经网络创建准确模型的知识。交叉验证是一种无偏地测试各种超参数表现的方法。我们使用了留一法交叉验证,其中我们将一个记录从训练过程中留下,作为验证使用,并对数据集中的每一条记录重复这一过程。接着,我们介绍了 k 折交叉验证,我们将训练数据集分为k个折叠,在k-1个折叠上训练模型,并使用最后一个折叠进行验证。这些交叉验证方法允许我们使用不同的超参数训练模型,并在无偏数据上测试它们的表现。

深度学习不仅仅是构建神经网络,使用现有的数据集训练它们,并报告模型的准确性。它还涉及理解你的模型和数据集,并且通过改善许多方面,使基本模型超越。 在本章中,你将学习到两大类对提高机器学习模型(尤其是深度学习模型)非常重要的技术。这些技术分别是正则化方法和超参数调优。

本章将进一步介绍正则化方法——特别是为什么我们需要它们,以及它们如何帮助我们。接着,我们将介绍两种最重要和最常用的正则化技术。在这里,你将详细了解参数正则化及其两种变体,L1L2范数正则化。然后,你将了解一种专门为神经网络设计的正则化技术——丢弃法(Dropout Regulation)。你还将通过完成涉及真实数据集的活动,实践在 Keras 模型上实现这些技术。最后,我们将简要介绍一些其他正则化技术,这些技术在你后续的工作中可能会有所帮助。

接下来,我们将讨论超参数调优的重要性,特别是对于深度学习模型,探讨如何调整超参数的值会显著影响模型的准确性,以及在构建深度神经网络时调优多个超参数的挑战。你将学习到两个非常有用的 scikit-learn 方法,这些方法可以用来对 Keras 模型进行超参数调优,并了解每种方法的优缺点,以及如何将它们结合起来,以便从中获得最大的收益。最后,你将通过完成一个实践活动来练习使用 scikit-learn 优化器为 Keras 模型实施超参数调优。

正则化

由于深度神经网络是高度灵活的模型,过拟合是训练过程中经常会遇到的问题。因此,成为深度学习专家的一个非常重要的部分是知道如何检测过拟合,并且随后如何解决模型中的过拟合问题。如果你的模型在训练数据上表现优异,但在新、未见过的数据上表现较差,那么模型的过拟合问题就很明显了。

例如,如果你构建了一个模型来将狗和猫的图像分类到各自的类别,并且你的图像分类器在训练过程中表现出高准确率,但在新数据上表现不佳,这就表明你的模型对训练数据过拟合。正则化技术是一类重要的方法,专门用于减少机器学习模型的过拟合问题。

彻底理解正则化技术,并能够将其应用到你的深度神经网络中,是构建深度神经网络以解决现实问题的重要一步。在本节中,你将了解正则化的基本概念,这将为后续的章节奠定基础,在这些章节中,你将学习如何使用 Keras 实现各种类型的正则化方法。

正则化的必要性

机器学习的主要目标是构建在训练数据上表现良好,并且能在新的、未包含在训练中的示例上同样表现良好的模型。一个好的机器学习模型应该能找到产生训练数据的真实底层过程/函数的形式和参数,而不是捕捉到个别训练数据的噪声。这样的模型能够很好地泛化到后续由相同过程生成的新数据上。

我们之前讨论的几种方法——比如将数据集分为训练集和测试集,以及交叉验证——都是为了估计训练模型的泛化能力。事实上,用于描述测试集误差和交叉验证误差的术语是“泛化误差”。这意味着在未用于训练的样本上的误差率。再次强调,机器学习的主要目标是构建具有低泛化误差率的模型。

第三章《使用 Keras 进行深度学习》中,我们讨论了机器学习模型的两个非常重要的问题:过拟合和欠拟合。我们指出,欠拟合是指估计的模型不够灵活或复杂,无法捕捉到与真实过程相关的所有关系和模式。这是一个高偏差的模型,并且在训练误差较高时会被发现。另一方面,过拟合是指用于估计真实过程的模型过于灵活或复杂。这是一个高方差的模型,并且当训练误差和泛化误差之间存在较大差距时被诊断出来。以下图像概述了二分类问题中的这些情境:

图 5.1:欠拟合

图 5.1:欠拟合

如上所示,欠拟合比过拟合是一个较少有问题的情况。事实上,欠拟合可以通过让模型变得更加灵活/复杂来轻松修复。在深度神经网络中,这意味着改变网络的架构,通过添加更多层或增加层中的单元数来使网络变大。

现在让我们来看下面的过拟合图像:

图 5.2:过拟合

图 5.2:过拟合

类似地,解决过拟合有一些简单的方案,比如让模型变得不那么灵活/复杂(同样是通过改变网络的架构)或者提供更多的训练样本。然而,让网络变得不那么复杂有时会导致偏差或训练误差率的剧烈增加。原因在于,大多数情况下,过拟合的原因不是模型的灵活性,而是训练样本太少。另一方面,为了减少过拟合,提供更多的数据样本并非总是可能的。因此,找到在保持模型复杂度和训练样本数量不变的情况下减少泛化误差的方法,既重要又具有挑战性。

现在让我们来看下面的右侧拟合图像:

图 5.3:右侧拟合

图 5.3:右侧拟合

这就是为什么在构建高度灵活的机器学习模型(如深度神经网络)时,我们需要正则化技术,以抑制模型的灵活性,避免其对单个样本进行过拟合。在接下来的章节中,我们将描述正则化方法如何减少模型在训练数据上的过拟合,从而降低模型的方差。

通过正则化减少过拟合

正则化方法试图通过修改学习算法,减少模型的方差。通过减少方差,正则化技术旨在降低泛化误差,同时不会显著增加训练误差(或者至少不会大幅增加训练误差)。

正则化方法提供了一种限制,有助于模型的稳定性。实现这一点有几种方式。对深度神经网络进行正则化的最常见方法之一是对权重施加某种惩罚项,以保持权重较小。

保持权重较小使得网络对个别数据样本中的噪声不那么敏感。在神经网络中,权重实际上是决定每个处理单元对网络最终输出影响大小的系数。如果单元的权重大,这意味着每个单元对输出的影响都会很大。将所有处理单元产生的巨大影响结合起来,最终输出将会有很多波动。

另一方面,保持权重较小减少了每个单元对最终输出的影响。实际上,通过将权重保持接近零,某些单元将几乎对输出没有影响。训练一个大型神经网络,其中每个单元对输出的影响微乎其微,相当于训练一个更简单的网络,从而减少了方差和过拟合。下图展示了正则化如何使大网络中某些单元的影响为零的示意图:

图 5.4:正则化如何使大网络中某些单元的影响为零的示意图

图 5.4:正则化如何使大网络中某些单元的影响为零的示意图

上面的图是正则化过程的示意图。顶部的网络展示了没有正则化的网络,而底部的网络则展示了一个应用了正则化的网络示例,其中白色单元代表那些由于正则化过程的惩罚几乎对输出没有影响的单元。

到目前为止,我们已经学习了正则化背后的概念。在接下来的章节中,我们将了解深度学习模型最常见的正则化方法——L1L2 和 dropout 正则化,以及如何在 Keras 中实现它们。

L1 和 L2 正则化

深度学习模型中最常见的正则化类型是保持网络权重较小的正则化。这种正则化被称为权重正则化,并且有两种不同的变体:L2 正则化L1 正则化。在本节中,您将详细了解这些正则化方法,并学习如何在 Keras 中实现它们。此外,您还将练习将它们应用于现实问题,并观察它们如何提高模型的性能。

L1 和 L2 正则化公式

在权重正则化中,一个惩罚项被添加到损失函数中。这个项通常是L1 正则化。如果使用 L2 范数,则称之为L2 正则化。在每种情况下,这个和将乘以一个超参数,称为正则化参数lambda)。

因此,对于L1 正则化,公式如下:

损失函数 = 旧损失函数 + lambda * 权重的绝对值和

对于L2 正则化,公式如下:

损失函数 = 旧损失函数 + lambda * 权重的平方和

Lambda可以取任意值,范围从01,其中lambda=0意味着没有惩罚(相当于一个没有正则化的网络),lambda=1意味着完全惩罚。

和其他超参数一样,lambda的正确值可以通过尝试不同的值并观察哪个值提供较低的泛化误差来选择。实际上,最好从没有正则化的网络开始,并观察结果。然后,您应该使用逐渐增大的lambda值进行正则化,如0.0010.010.10.5,并观察每种情况下的结果,以找出对特定问题来说,惩罚权重值的合适程度。

在每次带有正则化的优化算法迭代中,权重(w)会变得越来越小。因此,权重正则化通常被称为权重衰减

到目前为止,我们仅讨论了在深度神经网络中正则化权重。然而,您需要记住,同样的过程也可以应用于偏置。更准确地说,我们可以通过向损失函数中添加一个惩罚偏置的项来更新损失函数,从而在神经网络训练过程中保持偏置的值较小。

注意

如果通过向损失函数添加两个项(一个惩罚权重,一个惩罚偏置)来执行正则化,那么我们称之为参数正则化,而不是权重正则化。

然而,在深度学习中,正则化偏置值并不常见。原因在于,权重是神经网络中更为重要的参数。事实上,通常,添加另一个项来正则化偏置与仅正则化权重值相比,不会显著改变结果。

L2 正则化是一般机器学习中最常见的正则化技术。与L1 正则化的不同之处在于,L1会导致更稀疏的权重矩阵,意味着有更多的权重等于零,因此有更多的节点完全从网络中移除。另一方面,L2 正则化则更为微妙。它会显著减少权重,但同时让你留下更少的权重等于零。同时进行L1L2 正则化也是可能的。

现在你已经了解了L1L2 正则化的工作原理,可以继续在 Keras 中实现深度神经网络上的L1L2 正则化了。

Keras 中的 L1 和 L2 正则化实现

Keras 提供了一个正则化 API,可以用来在每一层的深度神经网络中向损失函数添加惩罚项,以对权重或偏置进行正则化。要定义惩罚项或正则化器,你需要在keras.regularizers下定义所需的正则化方法。

例如,要定义一个L1 正则化器,使用lambda=0.01,你可以这样写:

from keras.regularizers import l1
keras.regularizers.l1(0.01)

同样地,要定义一个L2 正则化器,使用lambda=0.01,你可以这样写:

from keras.regularizers import l2
keras.regularizers.l2(0.01)

最后,要同时定义L1L2 正则化器,使用lambda=0.01,你可以这样写:

from keras.regularizers import l1_l2
keras.regularizers.l1_l2(l1=0.01, l2=0.01)

每个正则化器可以应用于层中的权重和/或偏置。例如,如果我们想在具有八个节点的密集层上同时应用L2 正则化(使用lambda=0.01)到权重和偏置上,我们可以这样写:

from keras.layers import Dense
from keras.regularizers import l2
model.add(Dense(8, kernel_regularizer=l2(0.01), \
          bias_regularizer=l2(0.01)))

我们将在活动 5.01中进一步实践实现L1L2 正则化阿维拉模式分类器的权重正则化,你将为糖尿病数据集的深度学习模型应用正则化,并观察结果与先前活动的比较。

注意

本章中的所有活动将在 Jupyter 笔记本中开发。请从packt.live/2OOBjqq下载本书的 GitHub 存储库以及所有准备好的模板。

活动 5.01:阿维拉模式分类器的权重正则化

阿维拉数据集是从阿维拉圣经的 800 幅图像中提取的,这是 12 世纪的拉丁文圣经的巨大复制品。该数据集包含有关文本图像的各种特征,例如列间距和文本的边距。数据集还包含一个类标签,指示图像模式是否属于最频繁出现的类别。在这个活动中,你将构建一个 Keras 模型,根据给定的网络架构和超参数值对该数据集进行分类。目标是在模型上应用不同类型的权重正则化,并观察每种类型如何改变结果。

在此活动中,我们将使用训练集/测试集方法进行评估,原因有两个。首先,由于我们将尝试几种不同的正则化器,执行交叉验证将需要很长时间。其次,我们希望绘制训练误差和测试误差的趋势图,以便通过视觉方式理解正则化如何防止模型对数据样本过拟合。

按照以下步骤完成此活动:

  1. 从 GitHub 的Chapter05文件夹中的data子文件夹加载数据集,使用X = pd.read_csv('../data/avila-tr_feats.csv')y = pd.read_csv('../data/avila-tr_target.csv')。使用sklearn.model_selection.train_test_split方法将数据集拆分为训练集和测试集。保留20%的数据样本作为测试集。

  2. 定义一个 Keras 模型,包含三个隐藏层,第一个隐藏层的大小为 10,第二个隐藏层的大小为 6,第三个隐藏层的大小为 4,用于执行分类任务。使用以下超参数:activation='relu'loss='binary_crossentropy'optimizer='sgd'metrics=['accuracy']batch_size=20epochs=100shuffle=False

  3. 在训练集上训练模型,并使用测试集进行评估。在每次迭代中存储训练损失和测试损失。训练完成后,绘制训练误差测试误差的趋势图(将纵轴的限制调整为(0, 1),这样可以更好地观察损失的变化)。在测试集上的最小误差率是多少?

  4. 向模型的隐藏层添加L2 正则化器,其中lambda=0.01,并重复训练。训练完成后,绘制训练误差和测试误差的趋势图。在测试集上的最小误差率是多少?

  5. lambda=0.1lambda=0.005重复前面的步骤,针对每个lambda值训练模型,并报告结果。哪个lambda值对于在该深度学习模型和数据集上执行L2 正则化更为合适?

  6. 重复前面的步骤,这次使用L1 正则化器,针对lambda=0.01lambda=0.005训练模型,并报告结果。哪个lambda值对于在该深度学习模型和数据集上执行L1 正则化更为合适?

  7. 向模型的隐藏层添加L1_L2 正则化器,其中L1 lambda=0.005L2 lambda=0.005,并重复训练。训练完成后,绘制训练误差和测试误差的趋势图。在测试集上的最小误差率是多少?

完成这些步骤后,你应该得到以下预期输出:

![图 5.5:模型在训练过程中,L1 lambda 为 0.005,L2 lambda 为 0.005 时的训练误差和验证误差的趋势图]

](https://github.com/OpenDocCN/freelearn-dl-pt5-zh/raw/master/docs/dl-keras-ws/img/B15777_05_05.jpg)

图 5.5:模型在训练过程中,L1 lambda 为 0.005,L2 lambda 为 0.005 时的训练误差和验证误差的趋势图

注释

该活动的解决方案可以在第 398 页找到。

在这个活动中,你实践了为实际问题实现L1L2权重正则化,并将正则化模型的结果与没有任何正则化的模型进行比较。在下一节中,我们将探讨另一种技术的正则化方法,称为 dropout 正则化。

Dropout 正则化

在本节中,你将学习 dropout 正则化的工作原理,它如何帮助减少过拟合,以及如何使用 Keras 实现它。最后,你将通过完成一个涉及实际数据集的活动,来实践你学到的关于 dropout 的知识。

Dropout 正则化的原理

Dropout 正则化通过在训练过程中随机移除神经网络中的节点来工作。更准确地说,dropout 在每个节点上设置一个概率。这个概率表示在每次学习算法的迭代中,该节点被包含在训练中的可能性。假设我们有一个大型神经网络,其中每个节点的 dropout 概率为0.5。在这种情况下,在每次迭代中,学习算法会为每个节点掷一次硬币,决定是否将该节点从网络中移除。以下图示展示了这一过程:

图 5.6:使用 dropout 正则化从深度神经网络中移除节点的示意图

图 5.6:使用 dropout 正则化从深度神经网络中移除节点的示意图

这个过程在每次迭代时都会重复;这意味着,在每次迭代中,随机选择的节点会从网络中移除,这也意味着参数更新过程将在一个不同的较小网络上进行。例如,前图底部显示的网络只会用于一次训练迭代。对于下一次迭代,另一些随机选中的节点会从顶部网络中被删除,因此从这些节点中移除后的网络将与图中的底部网络不同。

当某些节点在学习算法的某次迭代中被选择移除/忽略时,这意味着它们在该次迭代中的参数更新过程中完全不参与。更准确地说,前向传播以预测输出、损失计算和反向传播以计算导数的所有操作都将在移除一些节点的较小网络上进行。因此,参数更新将仅在该次迭代中存在于网络中的节点上进行;被移除节点的权重和偏置将不会被更新。

然而,需要牢记的是,在测试集或保留集上评估模型的性能时,始终使用原始的完整网络。如果我们用随机删除节点的网络进行评估,结果会引入噪声,这样是不可取的。

dropout 正则化中,训练始终在通过随机移除原始网络中的部分节点所得到的网络上进行。评估则始终使用原始网络进行。在接下来的部分,我们将了解为什么 dropout 正则化有助于防止过拟合。

使用 Dropout 减少过拟合

在本节中,我们将讨论 dropout 作为正则化方法背后的概念。正如我们之前讨论的,正则化技术的目标是防止模型对数据过拟合。因此,我们将研究如何通过随机移除神经网络中的一部分节点来帮助减少方差和过拟合。

删除网络中的随机节点防止过拟合的最明显解释是,通过从网络中移除节点,我们是在对比原始网络训练一个更小的网络。如你之前所学,一个更小的神经网络提供的灵活性较低,因此网络对数据过拟合的可能性较小。

还有一个原因,解释了为什么 dropout 正则化如此有效地减少过拟合。通过在深度神经网络的每一层随机移除输入,整个网络变得对单一输入不那么敏感。我们知道,在训练神经网络时,权重会以某种方式更新,使得最终的模型能够适应训练样本。通过随机移除一些权重参与训练过程,dropout 强制其他权重参与学习与训练样本相关的模式,因此最终的权重值会更好地分散。

换句话说,不是某些权重为了拟合某些输入值而过度更新,而是所有权重都参与学习这些输入值,从而导致过拟合减少。这就是为什么执行 dropout 比仅仅使用更小的网络更能产生一个更强大的模型——在新数据上表现更好。实际上,dropout 正则化在更大的网络上效果更佳。

现在你已经了解了 dropout 的基本过程及其有效性背后的原因,我们可以继续在 Keras 中实现dropout 正则化

练习 5.01:在 Keras 中实现 Dropout

Dropout 正则化作为 Keras 中的核心层提供。因此,你可以像添加其他层到网络一样,将 dropout 添加到模型中。在 Keras 中定义 dropout 层时,你需要提供 rate 超参数作为参数。rate 可以是一个介于 01 之间的值,决定了要移除或忽略的输入单元的比例。在这个练习中,你将学习如何一步步实现带有 dropout 层的 Keras 深度学习模型。

我们的模拟数据集表示了树木的各种测量数据,如树高、树枝数和树干底部的胸围。我们的目标是根据给定的测量值将记录分类为落叶树(类值为1)或针叶树(类值为0)。数据集包含10000条记录,代表两种树种的两个类别,每个数据实例有10个特征值。请按照以下步骤完成此练习:

  1. 首先,执行以下代码块以加载数据集,并将数据集拆分为训练集测试集

    # Load the data
    import pandas as pd
    X = pd.read_csv('../data/tree_class_feats.csv')
    y = pd.read_csv('../data/tree_class_target.csv')
    """
    Split the dataset into training set and test set with a 80-20 ratio
    """
    from sklearn.model_selection import train_test_split
    seed = 1
    X_train, X_test, \
    y_train, y_test = train_test_split(X, y, \
                                       test_size=0.2, \
                                       random_state=seed)
    
  2. 导入所有必要的依赖项。构建一个四层的 Keras 顺序模型,且不使用dropout 正则化。构建该网络时,第一隐藏层有 16 个单元,第二隐藏层有12个单元,第三隐藏层有8个单元,第四隐藏层有4个单元,所有层都使用ReLU 激活函数。添加一个具有sigmoid 激活函数的输出层:

    #Define your model
    from keras.models import Sequential
    from keras.layers import Dense, Activation
    import numpy as np
    from tensorflow import random
    np.random.seed(seed)
    random.set_seed(seed)
    model_1 = Sequential()
    model_1.add(Dense(16, activation='relu', input_dim=10))
    model_1.add(Dense(12, activation='relu'))
    model_1.add(Dense(8, activation='relu'))
    model_1.add(Dense(4, activation='relu'))
    model_1.add(Dense(1, activation='sigmoid'))
    
  3. 使用binary cross-entropy作为loss函数,sgd作为优化器,编译模型,并在训练集上以batch_size=50进行300个 epochs 的训练。然后,在测试集上评估训练好的模型:

    model_1.compile(optimizer='sgd', loss='binary_crossentropy')
    # train the model
    model_1.fit(X_train, y_train, epochs=300, batch_size=50, \
                verbose=0, shuffle=False)
    # evaluate on test set
    print("Test Loss =", model_1.evaluate(X_test, y_test))
    

    这是预期的输出:

    2000/2000 [==============================] - 0s 23us/step
    Test Loss = 0.1697693831920624
    

    因此,经过300个 epochs 训练后的树种预测测试误差率为16.98%

  4. 使用与之前模型相同的层数和每层相同大小重新定义模型。然而,在模型的第一隐藏层添加rate=0.1dropout 正则化,然后重复编译、训练和在测试数据上评估模型的步骤:

    """
    define the keras model with dropout in the first hidden layer
    """
    from keras.layers import Dropout
    np.random.seed(seed)
    random.set_seed(seed)
    model_2 = Sequential()
    model_2.add(Dense(16, activation='relu', input_dim=10))
    model_2.add(Dropout(0.1))
    model_2.add(Dense(12, activation='relu'))
    model_2.add(Dense(8, activation='relu'))
    model_2.add(Dense(4, activation='relu'))
    model_2.add(Dense(1, activation='sigmoid'))
    model_2.compile(optimizer='sgd', loss='binary_crossentropy')
    # train the model
    model_2.fit(X_train, y_train, \
                epochs=300, batch_size=50, \
                verbose=0, shuffle=False)
    # evaluate on test set
    print("Test Loss =", model_2.evaluate(X_test, y_test))
    

    这是预期的输出:

    2000/2000 [==============================] - 0s 29us/step
    Test Loss = 0.16891103076934816
    

    在网络的第一层添加rate=0.1的 dropout 正则化后,测试误差率从16.98%降低到了16.89%

  5. 使用与之前模型相同的层数和每层相同大小重新定义模型。然而,在第一隐藏层添加rate=0.2的 dropout 正则化,并在模型的其余层添加rate=0.1的 dropout 正则化,然后重复编译、训练和在测试数据上评估模型的步骤:

    # define the keras model with dropout in all hidden layers
    np.random.seed(seed)
    random.set_seed(seed)
    model_3 = Sequential()
    model_3.add(Dense(16, activation='relu', input_dim=10))
    model_3.add(Dropout(0.2))
    model_3.add(Dense(12, activation='relu'))
    model_3.add(Dropout(0.1))
    model_3.add(Dense(8, activation='relu'))
    model_3.add(Dropout(0.1))
    model_3.add(Dense(4, activation='relu'))
    model_3.add(Dropout(0.1))
    model_3.add(Dense(1, activation='sigmoid'))
    model_3.compile(optimizer='sgd', loss='binary_crossentropy')
    # train the model
    model_3.fit(X_train, y_train, epochs=300, \
                batch_size=50, verbose=0, shuffle=False)
    # evaluate on test set
    print("Test Loss =", model_3.evaluate(X_test, y_test))
    

    这是预期的输出:

    2000/2000 [==============================] - 0s 40us/step
    Test Loss = 0.19390961921215058
    

在第一层保持rate=0.2的 dropout 正则化的同时,添加rate=0.1的 dropout 正则化到后续层,测试误差率从16.89%上升到19.39%。像L1L2 正则化一样,添加过多的 dropout 会阻止模型学习与训练数据相关的潜在函数,从而导致比没有 dropout 正则化时更高的偏差。

如你在本次练习中看到的,你还可以根据认为在各个层上可能发生的过拟合情况,对不同层使用不同的 dropout 比例。通常,我们不建议在输入层和输出层使用 dropout。对于隐藏层,我们需要调整 rate 值,并观察结果,以便确定哪个值最适合特定问题。

注意

要查看此特定部分的源代码,请参考packt.live/3iugM7K

你也可以在线运行这个示例,网址是packt.live/31HlSYo

在接下来的活动中,你将练习在 Keras 中实现深度学习模型,并结合 Dropout 正则化来处理交通流量数据集。

活动 5.02:在交通流量数据集上应用 Dropout 正则化

活动 4.03在交通流量数据集上使用交叉验证进行模型选择第四章使用 Keras 包装器评估模型并进行交叉验证 中,你使用交通流量数据集构建了一个模型,预测给定一系列与交通数据相关的标准化特征(如一天中的时间和前一天的交通量等)时城市桥梁上的交通流量。该数据集包含 10000 条记录,每条记录包含 10 个特征。

在本次活动中,你将从 活动 4.03在交通流量数据集上使用交叉验证进行模型选择第四章使用 Keras 包装器评估模型并进行交叉验证 开始。你将使用训练集/测试集方法来训练和评估模型,绘制训练误差和泛化误差的趋势,并观察模型的过拟合情况。接下来,你将尝试通过使用 dropout 正则化来解决过拟合问题,从而提升模型性能。具体来说,你将尝试找出应该在哪些层添加 dropout 正则化,并找到最佳的 rate 值来最大程度地改进该模型。完成此活动的步骤如下:

  1. 使用 pandas 的 read_csv 函数加载数据集。数据集也存储在 Chapter05 GitHub 仓库的 data 子文件夹中。将数据集按 80-20 比例分割为训练集和测试集。

  2. 定义一个 Keras 模型,包含两个隐藏层,每个隐藏层的size10,用于预测交通流量。使用以下超参数:activation='relu'loss='mean_squared_error'optimizer='rmsprop'batch_size=50epochs=200shuffle=False

  3. 在训练集上训练模型,并在测试集上评估。在每次迭代时存储训练损失和测试损失。

  4. 训练完成后,绘制训练误差和测试误差的趋势。训练集和测试集的最低误差率是多少?

  5. 向你的模型的第一个隐藏层添加rate=0.1的 dropout 正则化,并重复训练过程(由于训练时使用了 dropout,训练时间较长,请训练200个 epoch)。训练完成后,绘制训练误差和测试误差的趋势。训练集和测试集上的最低误差率分别是多少?

  6. 重复之前的步骤,这次向你的模型的两个隐藏层添加rate=0.1的 dropout 正则化,并训练模型并报告结果。

  7. 重复之前的步骤,这次在第一个隐藏层使用rate=0.2,在第二个隐藏层使用0.1,训练模型并报告结果。

  8. 到目前为止,哪种 dropout 正则化方法在这个深度学习模型和数据集上取得了最佳性能?

实施这些步骤后,你应该得到以下预期的输出:

图 5.7:训练过程中使用 dropout 正则化的训练误差和验证误差图,第一个隐藏层的 rate=0.2,第二个隐藏层的 rate=0.1

图 5.7:训练过程中使用 dropout 正则化的训练误差和验证误差图,第一个隐藏层的 rate=0.2,第二个隐藏层的 rate=0.1

注意

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

在这个活动中,你学习了如何在 Keras 中实现 dropout 正则化,并在涉及交通流量数据集的问题中进行了实践。Dropout 正则化专门用于减少神经网络中的过拟合,其原理是通过在训练过程中随机去除神经网络中的节点。这个过程导致神经网络的权重值分布更加均匀,从而减少了单个数据样本的过拟合。接下来的章节中,我们将讨论其他可以应用于防止模型在训练数据上过拟合的正则化方法。

其他正则化方法

在这一节中,你将简要了解一些常用的正则化技术,这些技术在深度学习中被证明是有效的。需要牢记的是,正则化是机器学习中一个广泛且活跃的研究领域。因此,在一章中涵盖所有可用的正则化方法是不可能的(而且大多数情况下并不必要,尤其是在一本关于应用深度学习的书中)。因此,在这一节中,我们将简要介绍另外三种正则化方法,分别是提前停止数据增强添加噪声。你将了解它们的基本原理,并获得一些如何使用它们的技巧和建议。

提前停止

本章早些时候我们讨论了机器学习的主要假设是存在一个真实的函数或过程来生成训练样本。然而,这个过程是未知的,且没有明确的方法可以找到它。不仅找不到确切的底层过程,而且选择一个具有适当灵活性或复杂度的模型来估计这个过程也很具挑战性。因此,一种好的做法是选择一个高灵活度的模型,比如深度神经网络,来建模这个过程,并仔细监控训练过程。

通过监控训练过程,我们可以在模型捕捉到过程的形式时及时停止训练,避免在模型开始对单个数据样本过拟合时继续训练。这就是早停背后的基本思想。我们在第三章使用 Keras 的深度学习模型评估部分简要讨论了早停的概念。我们提到,通过监控和观察训练过程中 训练误差测试误差 的变化,我们可以判断训练量过少或过多的界限。

下图展示了在训练高度灵活的模型时,训练误差和测试误差的变化情况。正如我们所见,训练需要在标记为合适拟合的区域停止,以避免过拟合:

图 5.8:训练模型时训练误差和测试误差的变化图

图 5.8:训练模型时训练误差和测试误差的变化图

第三章使用 Keras 的深度学习中,我们练习了存储和绘制训练误差和测试误差的变化,以识别过拟合。你学到,在训练 Keras 模型时,你可以提供验证集或测试集,并通过以下代码在每个训练周期中存储它们的指标值:

history=model.fit(X_train, y_train, validation_data=(X_test, y_test), \
                  epochs=epochs)

在本节中,你将学习如何在 Keras 中实现早停。这意味着在 Keras 模型训练时,当某个期望的指标——例如,测试误差率——不再改善时,强制停止训练。为此,你需要定义一个 EarlyStopping() 回调函数,并将其作为参数提供给 model.fit()

在定义 EarlyStopping() 回调函数时,你需要为其提供正确的参数。第一个参数是 monitor,它决定了在训练过程中监控哪个指标来执行早停。通常,monitor='val_loss' 是一个不错的选择,这意味着我们希望监控测试误差率。

此外,根据你为 monitor 选择的参数,你需要将 mode 参数设置为 'min''max'。如果指标是误差/损失,我们希望将其最小化。例如,以下代码块定义了一个 EarlyStopping() 回调函数,用于在训练过程中监控测试误差,并检测其是否不再减少:

from keras.callbacks import EarlyStopping
es_callback = EarlyStopping(monitor='val_loss', mode='min')

如果误差率波动很大或噪声较多,那么在损失开始增加时就立即停止训练可能并不是一个好主意。因此,我们可以将patience参数设置为一定的 epoch 数量,给早停方法一些时间,在停止训练过程之前,能够监控目标度量值更长时间:

es_callback = EarlyStopping(monitor='val_loss', \
                            mode='min', patience=20)

我们还可以修改EarlyStopping()回调函数,如果在过去的epoch内,monitor度量没有发生最小的改进,或者monitor度量已达到基准水平时,停止训练过程:

es_callback = EarlyStopping(monitor='val_loss', \
                            mode='min', min_delta=1)
es_callback = EarlyStopping(monitor='val_loss', \
                            mode='min', baseline=0.2)

在定义了EarlyStopping()回调函数后,可以将其作为callbacks参数传递给model.fit()并训练模型。训练将根据EarlyStopping()回调函数自动停止:

history=model.fit(X_train, y_train, validation_data=(X_test, y_test), \
                  epochs=epochs, callbacks=[es_callback])

我们将在下一个练习中探索如何在实际中实现早停。

练习 5.02:在 Keras 中实现早停

在这个练习中,你将学习如何在 Keras 深度学习模型中实现早停。我们将使用的数据集是一个模拟数据集,包含表示树木不同测量值的数据,例如树高、树枝数量和树干基部的周长。我们的目标是根据给定的测量值将记录分类为落叶树或针叶树。

首先,执行以下代码块以加载包含10000条记录的模拟数据集,这些记录包括两类,表示两种树种,其中落叶树的类值为1,针叶树的类值为0。每条记录有10个特征值。

目标是构建一个模型,以便在给定树木的测量值时预测树的种类。现在,让我们按照以下步骤操作:

  1. 使用 pandas 的read_csv函数加载数据集,并使用train_test_split函数将数据集按80-20比例拆分:

    # Load the data
    import pandas as pd
    X = pd.read_csv('../data/tree_class_feats.csv')
    y = pd.read_csv('../data/tree_class_target.csv')
    """
    Split the dataset into training set and test set with an 80-20 ratio
    """
    from sklearn.model_selection import train_test_split
    seed=1
    X_train, X_test, \
    y_train, y_test = train_test_split(X, y, test_size=0.2, \
                                       random_state=seed)
    
  2. 导入所有必要的依赖项。构建一个没有早停的三层 Keras 顺序模型。第一层将有16个单元,第二层有8个单元,第三层有4个单元,所有层均使用ReLU 激活函数。添加输出层并使用sigmoid 激活函数

    # Define your model
    from keras.models import Sequential
    from keras.layers import Dense, Activation
    import numpy as np
    from tensorflow import random
    np.random.seed(seed)
    random.set_seed(seed)
    model_1 = Sequential()
    model_1.add(Dense(16, activation='relu', \
                      input_dim=X_train.shape[1]))
    model_1.add(Dense(8, activation='relu'))
    model_1.add(Dense(4, activation='relu'))
    model_1.add(Dense(1, activation='sigmoid'))
    
  3. 使用二进制交叉熵作为loss函数,并将优化器设为SGD来编译模型。训练模型300个 epoch,batch_size=50,同时在每次迭代时记录训练误差测试误差

    model_1.compile(optimizer='sgd', loss='binary_crossentropy')
    # train the model
    history = model_1.fit(X_train, y_train, \
                          validation_data=(X_test, y_test), \
                          epochs=300, batch_size=50, \
                          verbose=0, shuffle=False)
    
  4. 导入绘图所需的包:

    import matplotlib.pyplot as plt 
    import matplotlib
    %matplotlib inline
    
  5. 绘制在拟合过程中存储的训练误差测试误差

    matplotlib.rcParams['figure.figsize'] = (10.0, 8.0) 
    plt.plot(history.history['loss'])
    plt.plot(history.history['val_loss'])
    plt.ylim(0,1)
    plt.ylabel('loss')
    plt.xlabel('epoch')
    plt.legend(['train loss', 'validation loss'], \
               loc='upper right')
    

    这是预期的输出:

    图 5.9:训练模型时,未使用早停的训练误差和验证误差图

    图 5.9:训练模型时,未使用早停的训练误差和验证误差图

    从前面的图中可以看出,训练模型 300 个 epoch 后,训练误差验证误差之间的差距不断扩大,这表明过拟合已经开始发生。

  6. 通过创建具有相同层数和每层相同单位数的模型来重新定义模型。这确保了模型以相同的方式初始化。向训练过程中添加回调es_callback = EarlyStopping(monitor='val_loss', mode='min')。重复步骤 4以绘制训练误差和验证误差:

    #Define your model with early stopping on test error
    from keras.callbacks import EarlyStopping
    np.random.seed(seed)
    random.set_seed(seed)
    model_2 = Sequential()
    model_2.add(Dense(16, activation='relu', \
                      input_dim=X_train.shape[1]))
    model_2.add(Dense(8, activation='relu'))
    model_2.add(Dense(4, activation='relu'))
    model_2.add(Dense(1, activation='sigmoid'))
    """
    Choose the loss function to be binary cross entropy and the optimizer to be SGD for training the model
    """
    model_2.compile(optimizer='sgd', loss='binary_crossentropy')
    # define the early stopping callback
    es_callback = EarlyStopping(monitor='val_loss', \
                                mode='min')
    # train the model
    history=model_2.fit(X_train, y_train, \
                        validation_data=(X_test, y_test), \
                        epochs=300, batch_size=50, \
                        callbacks=[es_callback], verbose=0, \
                        shuffle=False)
    
  7. 现在绘制损失值:

    # plot training error and test error
    matplotlib.rcParams['figure.figsize'] = (10.0, 8.0) 
    plt.plot(history.history['loss'])
    plt.plot(history.history['val_loss'])
    plt.ylim(0,1)
    plt.ylabel('loss')
    plt.xlabel('epoch')
    plt.legend(['train loss', 'validation loss'], \
               loc='upper right')
    

    这是预期的输出:

    图 5.10:使用提前停止(patience=0)训练模型时的训练误差和验证误差图

    图 5.10:使用提前停止(patience=0)训练模型时的训练误差和验证误差图

    通过将patience=0的提前停止回调添加到模型中,训练过程将在大约39个 epoch 后自动停止。

  8. 重复步骤 5,同时将patience=10添加到你的提前停止回调中。重复步骤 3以绘制训练误差验证误差

    """
    Define your model with early stopping on test error with patience=10
    """
    from keras.callbacks import EarlyStopping
    np.random.seed(seed)
    random.set_seed(seed)
    model_3 = Sequential()
    model_3.add(Dense(16, activation='relu', \
                      input_dim=X_train.shape[1]))
    model_3.add(Dense(8, activation='relu'))
    model_3.add(Dense(4, activation='relu'))
    model_3.add(Dense(1, activation='sigmoid'))
    """
    Choose the loss function to be binary cross entropy and the optimizer to be SGD for training the model
    """
    model_3.compile(optimizer='sgd', loss='binary_crossentropy')
    # define the early stopping callback
    es_callback = EarlyStopping(monitor='val_loss', \
                                mode='min', patience=10)
    # train the model
    history=model_3.fit(X_train, y_train, \
                        validation_data=(X_test, y_test), \
                        epochs=300, batch_size=50, \
                        callbacks=[es_callback], verbose=0, \
                        shuffle=False)
    
  9. 然后再次绘制损失图:

    # plot training error and test error
    matplotlib.rcParams['figure.figsize'] = (10.0, 8.0) 
    plt.plot(history.history['loss'])
    plt.plot(history.history['val_loss'])
    plt.ylim(0,1)
    plt.ylabel('loss')
    plt.xlabel('epoch')
    plt.legend(['train loss', 'validation loss'], \
               loc='upper right')
    

    这是预期的输出:

图 5.11:使用提前停止(patience=10)训练模型时的训练误差和验证误差图

图 5.11:使用提前停止(patience=10)训练模型时的训练误差和验证误差图

通过将patience=10的提前停止回调添加到模型中,训练过程将在大约150个 epoch 后自动停止。

在这个练习中,你学会了如何停止模型训练,以防止你的 Keras 模型在训练数据上过拟合。为此,你使用了EarlyStopping回调并在训练时应用了它。我们使用这个回调在验证损失增加时停止模型,并添加了一个patience参数,它会在停止之前等待给定的 epoch 数量。我们在一个涉及交通流量数据集的问题上练习了使用这个回调来训练 Keras 模型。

注意

要访问此特定部分的源代码,请参阅packt.live/3iuM4eL

你也可以在packt.live/38AbweB上在线运行这个示例。

在下一部分中,我们将讨论其他可以应用的正则化方法,以防止过拟合。

数据增强

数据增强是一种正则化技术,试图通过以廉价的方式在更多的训练样本上训练模型来解决过拟合问题。在数据增强中,现有数据会以不同的方式进行转换,并作为新的训练数据输入到模型中。这种类型的正则化已被证明是有效的,特别是在一些特定的应用中,例如计算机视觉中的目标检测/识别和语音处理。

例如,在计算机视觉应用中,您可以通过将每个图像的镜像版本和旋转版本添加到数据集中,轻松地将训练数据集的大小加倍或加三倍。通过这些变换生成的新训练示例显然不如原始训练示例好。然而,它们已被证明能改善模型的过拟合问题。

执行数据增强时的一个挑战是选择在数据上执行的正确变换。根据数据集的类型和应用,变换需要谨慎选择。

添加噪声

通过向数据中添加噪声来对模型进行正则化的基本思想与数据增强正则化相同。在小数据集上训练深度神经网络会增加网络记住单一数据示例的概率,而不是捕捉输入与输出之间的关系。

这样会导致在新数据上的表现不佳,表明模型对训练数据进行了过拟合。相反,在大数据集上训练模型可以增加模型捕捉真实底层过程的概率,而不是记住单个数据点,从而减少过拟合的机会。

扩展训练数据并减少过拟合的一种方法是通过向现有数据中注入噪声来生成新的数据示例。这种正则化方式已被证明能够减少过拟合,其效果与权重正则化技术相当。

通过将单个示例的不同版本添加到训练数据中(每个版本通过在原始示例中加入少量噪声创建),我们可以确保模型不会过拟合数据中的噪声。此外,通过包含这些修改后的示例来增加训练数据集的大小,可以为模型提供更好的底层数据生成过程的表示,并增加模型学习真实过程的机会。

在深度学习应用中,您可以通过向隐藏层的权重或激活值、网络的梯度,甚至输出层添加噪声,或向训练示例(输入层)添加噪声来提高模型性能。决定在深度神经网络中添加噪声的位置是另一个需要通过尝试不同网络并观察结果来解决的挑战。

在 Keras 中,您可以轻松地将噪声定义为一个层并将其添加到模型中。例如,要向模型添加高斯噪声,标准差为0.1(均值为0),可以编写如下代码:

from keras.layers import GaussianNoise
model.add(GaussianNoise(0.1))

以下代码将向模型的第一个隐藏层的输出/激活值添加高斯噪声

model = Sequential()
model.add(Dense(4, input_dim=30, activation='relu'))
model.add(GaussianNoise(0.01))
model.add(Dense(4, activation='relu'))
model.add(Dense(4, activation='relu'))
model.add(Dense(1, activation='sigmoid')) 

在本节中,你学习了三种正则化方法:early stoppingdata augmentationadding noise。除了它们的基本概念和流程外,你还了解了它们如何减少过拟合,并且提供了一些使用它们的技巧和建议。在下一节中,你将学习如何使用 scikit-learn 提供的函数来调优超参数。通过这样做,我们可以将 Keras 模型整合到 scikit-learn 的工作流中。

使用 scikit-learn 进行超参数调优

超参数调优是提高深度学习模型性能的一个非常重要的技术。在第四章使用 Keras 包装器进行交叉验证评估模型中,你学习了如何使用 scikit-learn 的 Keras 包装器,这使得 Keras 模型能够在 scikit-learn 的工作流中使用。因此,scikit-learn 中可用的不同通用机器学习和数据分析工具与方法可以应用于 Keras 深度学习模型。其中包括 scikit-learn 的超参数优化器。

在上一章中,你学习了如何通过编写用户定义的函数,遍历每个超参数的可能值来进行超参数调优。在本节中,你将学习如何通过使用 scikit-learn 中可用的各种超参数优化方法,以更简单的方式进行调优。你还将通过完成涉及实际数据集的活动来实践应用这些方法。

使用 scikit-learn 进行网格搜索

到目前为止,我们已经确认,构建深度神经网络涉及对多个超参数做出决策。超参数的列表包括隐藏层的数量、每个隐藏层中单元的数量、每层的激活函数、网络的损失函数、优化器的类型及其参数、正则化器的类型及其参数、批次大小、训练的轮次等。我们还观察到,不同的超参数值会显著影响模型的性能。

因此,找到最佳超参数值是成为深度学习专家过程中最重要也是最具挑战性的部分之一。由于没有适用于每个数据集和每个问题的超参数绝对规则,因此确定超参数的值需要通过试验和错误来针对每个特定问题进行调整。这个过程——用不同的超参数训练和评估模型,并根据模型表现决定最终的超参数——被称为超参数调优超参数优化

对于我们希望调整的每个超参数,设置一组可能的取值范围可以创建一个网格,如下图所示。因此,超参数调整可以看作是一个网格搜索问题;我们希望尝试网格中的每一个单元格(每一个可能的超参数组合),并找到那个能为模型带来最佳性能的单元格:

图 5.12:通过优化器、批处理大小和 epoch 的一些值创建的超参数网格

图 5.12:通过优化器、批处理大小和 epoch 的一些值创建的超参数网格

Scikit-learn 提供了一个名为 GridSearchCV() 的参数优化器,用于执行这种穷举的网格搜索。GridSearchCV() 接收模型作为 estimator 参数,并接收包含所有可能的超参数值的字典作为 param_grid 参数。然后,它会遍历网格中的每个点,使用该点的超参数值对模型进行交叉验证,并返回最佳的交叉验证得分,以及导致该得分的超参数值。

在上一章中,你学习了为了在 scikit-learn 中使用 Keras 模型,你需要定义一个返回 Keras 模型的函数。例如,下面的代码块定义了一个 Keras 模型,我们希望在之后对其进行超参数调整:

from keras.models import Sequential
from keras.layers import Dense
def build_model():
    model = Sequential(optimizer)
    model.add(Dense(10, input_dim=X_train.shape[1], \
                    activation='relu'))
    model.add(Dense(10, activation='relu'))
    model.add(Dense(1))
    model.compile(loss='mean_squared_error', \
                  optimizer= optimizer)
    return model

下一步是定义超参数网格。例如,假设我们想要调整 optimizer=['rmsprop', 'adam', 'sgd', 'adagrad']epochs = [100, 150]batch_size = [1, 5, 10]。为了做到这一点,我们将编写如下代码:

optimizer = ['rmsprop', 'adam', 'sgd', 'adagrad']
epochs = [100, 150]
batch_size = [1, 5, 10]
param_grid = dict(optimizer=optimizer, epochs=epochs, \
                  batch_size= batch_size)

现在超参数网格已经创建完毕,我们可以创建封装器,以便构建 Keras 模型的接口,并将其用作估计器来执行网格搜索:

from keras.wrappers.scikit_learn import KerasRegressor
model = KerasRegressor(build_fn=build_model, \
                       verbose=0, shuffle=False)
from sklearn.model_selection import GridSearchCV
grid_search = GridSearchCV(estimator=model, \
                           param_grid=param_grid, cv=10)
results = grid_search.fit(X, y)

上述代码会穷举地遍历网格中的每个单元格,并使用每个单元格中的超参数值进行 10 折交叉验证(这里,它会执行 10 折交叉验证 423=24 次)。然后,它返回每个这些 24 个单元格的交叉验证得分,并返回获得最佳得分的那个。

注意

对多个可能的超参数组合执行 k 折交叉验证确实需要很长时间。因此,你可以通过将 n_jobs=-1 参数传递给 GridSearchCV() 来并行化这个过程,这样会使用所有可用的处理器来执行网格搜索。该参数的默认值是 n_jobs=1,意味着不进行并行化。

创建超参数网格只是通过超参数迭代找到最优选择的一种方式。另一种方法是简单地随机选择超参数,这将在下一个主题中介绍。

使用 scikit-learn 进行随机化搜索

正如你可能已经意识到的那样,穷举网格搜索可能不是调优深度学习模型超参数的最佳选择,因为它效率不高。深度学习中有许多超参数,尤其是当你想为每个超参数尝试一个大范围的值时,穷举网格搜索将需要花费过长的时间才能完成。进行超参数优化的另一种方式是,在网格上进行随机采样,并对一些随机选择的单元格进行 k 折交叉验证。Scikit-learn 提供了一个名为RandomizedSearchCV()的优化器,用于执行超参数优化的随机搜索。

例如,我们可以将前一节的代码从穷举网格搜索更改为随机搜索,如下所示:

from keras.wrappers.scikit_learn import KerasRegressor
model = KerasRegressor(build_fn=build_model, verbose=0)
from sklearn.model_selection import RandomizedSearchCV
grid_search = RandomizedSearchCV(estimator=model, \
                                 param_distributions=param_grid, \
                                 cv=10, n_iter=12)
results = grid_search.fit(X, y)

请注意,RandomizedSearchCV()需要额外的n_iter参数,它决定了必须选择多少个随机单元格。这决定了 k 折交叉验证将执行多少次。因此,通过选择较小的值,将考虑较少的超参数组合,方法完成的时间也会更短。另外,请注意,这里param_grid参数已更改为param_distributionsparam_distributions参数可以接受一个字典,字典的键是参数名称,值可以是参数的列表或每个键的分布。

可以说,RandomizedSearchCV()不如GridSearchCV()好,因为它没有考虑所有可能的超参数值及其组合,这一点是合理的。因此,一种进行深度学习模型超参数调优的聪明方法是,首先对许多超参数使用RandomizedSearchCV(),或对较少的超参数使用GridSearchCV(),并且这些超参数之间的间隔较大。

通过从许多超参数的随机搜索开始,我们可以确定哪些超参数对模型性能的影响最大。这也有助于缩小重要超参数的范围。然后,你可以通过对较少的超参数及其较小的范围执行GridSearchCV()来完成超参数调优。这种方法称为粗到精的超参数调优方法。

现在,你准备好实践使用 scikit-learn 优化器进行超参数调优了。在接下来的活动中,你将通过调优超参数来改进你的糖尿病数据集模型。

活动 5.03:在 Avila 模式分类器上进行超参数调优

Avila 数据集是从800张 Avila 圣经图像中提取的,Avila 圣经是 12 世纪的拉丁文巨型圣经副本。该数据集包含有关文本图像的各种特征,如行间距和文本边距。数据集还包含一个类别标签,指示图像的模式是否属于最常见的类别。在本活动中,您将构建一个与前几个活动类似的 Keras 模型,但这次您将为模型添加正则化方法。然后,您将使用 scikit-learn 优化器来调整模型的超参数,包括正则化器的超参数。以下是您在本活动中需要完成的步骤:

  1. 使用X = pd.read_csv('../data/avila-tr_feats.csv')y = pd.read_csv('../data/avila-tr_target.csv')从 GitHub 上的Chapter05文件夹中的data子文件夹加载数据集。

  2. 定义一个返回 Keras 模型的函数,该模型包含三个隐藏层,第一层的size 10,第二层的size 6,第三层的size 4,且均带有L2 权重正则化。使用以下值作为模型的超参数:activation='relu'loss='binary_crossentropy'optimizer='sgd'metrics=['accuracy']。另外,确保将L2 lambda超参数作为参数传递给函数,以便我们以后进行调整。

  3. 为您的 Keras 模型创建包装器,并使用cv=5对其执行GridSearchCV()。然后,在参数网格中添加以下值:lambda_parameter = [0.01, 0.5, 1]epochs = [50, 100],以及batch_size = [20]。这可能需要一些时间来处理。参数搜索完成后,打印最佳交叉验证分数的准确率和超参数。您还可以打印每个其他交叉验证分数,以及导致该分数的超参数。

  4. 重复前一步,这次使用GridSearchCV()在更窄的范围内进行搜索,参数为lambda_parameter = [0.001, 0.01, 0.05, 0.1]epochs = [400],以及batch_size = [10]。这可能需要一些时间来处理。

  5. 重复前一步,但从 Keras 模型中移除L2 正则化器,而是使用rate参数在每个隐藏层中添加 dropout 正则化。使用以下值在参数网格中执行GridSearchCV()并打印结果:rate = [0, 0.2, 0.4]epochs = [350, 400],以及batch_size = [10]

  6. 重复前一步,使用rate = [0.0, 0.05, 0.1]epochs=[400]

完成这些步骤后,您应该看到以下预期输出:

Best cross-validation score= 0.7862895488739013
Parameters for Best cross-validation score= {'batch_size': 20, 'epochs': 100, 'rate': 0.0}
Accuracy 0.786290 (std 0.013557) for params {'batch_size': 20, 'epochs': 100, 'rate': 0.0}
Accuracy 0.786098 (std 0.005184) for params {'batch_size': 20, 'epochs': 100, 'rate': 0.05}
Accuracy 0.772004 (std 0.013733) for params {'batch_size': 20, 'epochs': 100, 'rate': 0.1}

注意

本活动的解决方案可以在第 422 页找到。

在这个活动中,我们学习了如何在 Keras 模型上实现超参数调优,并结合正则化器使用真实数据集进行分类。我们学习了如何使用 scikit-learn 优化器对模型超参数进行调优,包括正则化器的超参数。在这一部分,我们通过创建一个超参数网格并遍历它们来实现超参数调优。这使我们能够使用 scikit-learn 工作流找到最优的超参数组合。

总结

在本章中,你学习了两组非常重要的技术,用于提高深度学习模型的准确性:正则化和超参数调优。你学习了正则化如何通过多种不同的方法解决过拟合问题,包括 L1 和 L2 范数正则化以及 dropout 正则化——这些是常用的正则化技术。你发现了超参数调优对于机器学习模型的重要性,特别是对于深度学习模型的挑战。你甚至练习了使用 scikit-learn 优化器,在 Keras 模型上执行超参数调优。

在下一章中,你将探索评估模型性能时准确性指标的局限性,以及其他指标(如 precisionsensitivityspecificityAUC-ROC score),并了解如何使用它们更好地评估模型的性能。

第六章:6. 模型评估

概述

本章将深入讨论模型评估。我们将讨论在标准技术不可行时,如何使用其他方法来评估模型的性能,特别是在类别不平衡的情况下。最后,我们将利用混淆矩阵、敏感性、特异性、精确度、假阳性率(FPR)、ROC 曲线和 AUC 分数来评估分类器的性能。在本章结束时,你将深入理解准确度和零准确度,并能够理解并应对不平衡数据集的挑战。

介绍

在上一章中,我们讨论了神经网络的 正则化 技术。正则化 是一种重要的技术,用于防止模型对训练数据过拟合,并帮助模型在新的、未见过的数据样本上表现良好。我们讨论的正则化技术之一是 L1L2 权重正则化,在其中对权重进行惩罚。我们还学习了另一种正则化技术——丢弃正则化,在每次迭代中,随机移除模型中的某些层单元,防止它们过度影响模型拟合过程。这两种正则化技术的设计目的是防止单个权重或单元被过度影响,从而使模型能够更好地泛化。

在本章中,我们将学习一些与 准确度 不同的评估技术。对于任何数据科学家来说,构建模型后的第一步是评估模型,而评估模型的最简单方法就是通过其准确度。然而,在现实世界的场景中,特别是在分类任务中,当类别高度不平衡时,比如预测飓风的发生、预测罕见疾病的出现或预测某人是否会违约时,使用准确度分数来评估模型并不是最好的评估方法。

本章探讨了核心概念,如不平衡数据集以及如何使用不同的评估技术来处理这些不平衡数据集。本章首先介绍准确度及其局限性。然后,我们将探索 零准确度不平衡数据集敏感性特异性精确度假阳性ROC 曲线AUC 分数 等概念。

准确度

为了正确理解准确度,让我们探索模型评估。模型评估是模型开发过程中的一个关键部分。一旦你构建并执行了模型,下一步就是评估你的模型。

模型是基于 训练数据集 构建的,在同一个训练数据集上评估模型的表现在数据科学中是不推荐的。一旦模型在训练数据集上训练完成,它应该在与训练数据集完全不同的数据集上进行评估。这个数据集被称为 测试数据集。目标始终是构建一个具有泛化能力的模型,也就是说,模型应该在任何数据集上都能产生类似(但不完全相同)的结果,或者是相对相似的结果。只有在使用模型未知的数据进行评估时,才能实现这一目标。

模型评估过程需要一个能够量化模型表现的指标。模型评估中最简单的指标就是准确率。准确率 是我们的模型预测正确的比例。计算 准确率 的公式如下:

准确率 = (正确预测数量)/(总预测数量)

例如,如果我们有 10 条记录,其中 7 条预测正确,那么我们可以说模型的准确率是 70%。计算方法为 7/10 = 0.770%

Null accuracy 是通过预测最频繁类别可以达到的准确率。如果我们不运行算法,而只是基于最频繁的结果预测准确率,那么基于此预测计算得到的准确率就是 null accuracy

Null accuracy = (最频繁类别的实例总数)/(实例总数)

看一下这个例子:

10 个实际结果: [1,0,0,0,0,0,0,0,1,0]。

预测: [0,0,0,0,0,0,0,0,0,0]

Null accuracy = 8/10 = 0.8 或 80%

因此,我们的 null accuracy80%,意味着我们在 80% 的情况下预测是正确的。这意味着我们在没有运行任何算法的情况下就达到了 80% 的准确率。请记住,当 null accuracy 较高时,表示响应变量的分布偏向于频繁出现的类别。

我们来做一个练习,计算数据集的 null accuracy。可以通过使用 pandas 库中的 value_count 函数来找到数据集的 null accuracy。value_count 函数返回一个包含唯一值计数的序列。

注意

本章中所有的 Jupyter Notebook 练习和活动可在 GitHub 上找到:packt.live/37jHNUR

练习 6.01:计算太平洋飓风数据集的 Null Accuracy

我们有一个数据集,记录了是否在太平洋上观察到 飓风,该数据集有两列,日期飓风日期 列表示观察的日期,而 飓风 列表示该日期是否有飓风发生。飓风 列的值为 1 表示发生了飓风,值为 0 表示没有飓风发生。通过以下步骤可以计算该数据集的 null accuracy

  1. 打开一个 Jupyter 笔记本。导入所有必要的库,并从本书的 GitHub 仓库中将pacific_hurricanes.csv文件加载到data文件夹中:

    # Import the data
    import pandas as pd
    df = pd.read_csv("../data/pacific_hurricanes.csv")
    df.head() 
    

    以下是前面代码的输出:

    图 6.1:太平洋飓风数据集的探索

    图 6.1:太平洋飓风数据集的探索

  2. 使用pandas库内置的value_count函数来获取hurricane列数据的分布。value_count函数显示了唯一值的总实例:

    df['hurricane'].value_counts()
    

    上述代码产生的输出如下:

    0 22435
    1 1842
    Name: hurricane, dtype: int64
    
  3. 使用value_count函数并将normalize参数设置为True。为了找到空值准确度,你需要索引为0pandas序列,来获取与某一天没有发生飓风相关的值的比例:

    df['hurricane'].value_counts(normalize=True).loc[0]
    

    上述代码产生的输出如下:

    0.9241257156979857
    

    计算出的数据集空值准确度92.4126%

在这里,我们可以看到数据集的空值准确度非常高,达到了92.4126%。因此,如果我们仅仅创建一个傻瓜模型,对所有结果预测多数类,那么我们的模型将会有92.4126%的准确度。

注意

要访问此特定部分的源代码,请参考packt.live/31FtQBm

你也可以在packt.live/2ArNwNT在线运行这个示例。

在本章后面,在活动 6.01中,在我们更改训练/测试拆分时计算神经网络的准确度和空值准确度,我们将看到空值准确度如何随着test/train拆分的变化而变化。

准确度的优缺点

准确度的优点如下:

  • 易于使用:准确度的计算非常简单,容易理解,因为它只是一个简单的分数公式。

  • 与其他技术相比更流行:由于它是最容易计算的度量,它也是最受欢迎的,并且被普遍接受作为评估模型的第一步。大多数数据科学入门书籍都会将准确度作为评估指标来讲解。

  • 适用于比较不同的模型:假设你正在尝试使用不同的模型解决问题。你总是可以信任那个给出最高准确度的模型。

准确度的局限性如下:

  • response/dependent变量。如果我们模型的准确度为80%,我们并不知道响应变量是如何分布的,以及数据集的空值准确度是多少。如果我们数据集的空值准确度超过70%,那么一个80%准确的模型就毫无意义。

  • 准确率同样无法提供关于模型的类型 1类型 2错误的任何信息。类型 1错误是当某一类为类时,我们将其预测为类,而类型 2错误则是当某一类为正类时,我们将其预测为负类。我们将在本章稍后研究这两种错误。在下一节中,我们将讨论不平衡数据集。对于分类不平衡数据集的模型来说,准确率分数可能会特别具有误导性,这也是为什么其他评估指标对模型评估很有用的原因。

不平衡数据集

不平衡数据集是分类问题中的一种特殊情况,其中类分布在各个类别之间存在差异。在这种数据集中,某一类别占据主导地位。换句话说,不平衡数据集的空准确率非常高。

以信用卡欺诈为例。如果我们有一个信用卡交易的数据集,那么我们会发现,在所有交易中,欺诈交易的数量非常少,而大多数交易都是正常交易。如果1代表欺诈交易,0代表正常交易,那么数据中会有许多0,几乎没有1。该数据集的空准确率可能会超过99%。这意味着多数类(在此案例中是0)远大于少数类(在此案例中是1)。这就是不平衡数据集的特点。请看下面的图表,它展示了一个一般的不平衡数据集的散点图

图 6.2:一个一般的、不平衡数据集的散点图

图 6.2:一个一般的、不平衡数据集的散点图

上述图表展示了一个不平衡数据集的广义散点图,其中星星代表少数类,圆圈代表多数类。正如我们所见,圆圈的数量远多于星星;这可能使得机器学习模型很难区分这两类。在下一节中,我们将讨论一些处理不平衡数据集的方法。

处理不平衡数据集

在机器学习中,有两种方法可以克服不平衡数据集的缺点,具体如下:

  • 90%,然后采样技术很难正确地表现数据中多数类和少数类的比例,这可能导致我们的模型过拟合。因此,最好的方法是修改我们的评估技术。

  • 修改模型评估技术:当处理高度不平衡的数据集时,最好修改模型评估技术。这是获得良好结果的最稳健的方法,意味着使用这些方法很可能会在新数据上获得好结果。除了准确率外,还有许多其他评估指标可以修改来评估模型。要了解所有这些技术,首先需要理解混淆矩阵的概念。

混淆矩阵

混淆矩阵描述了分类模型的性能。换句话说,混淆矩阵是总结分类器性能的一种方式。下表展示了混淆矩阵的基本表示,并表示模型预测结果与实际值的比较:

图 6.3:混淆矩阵的基本表示

图 6.3:混淆矩阵的基本表示

让我们回顾一下前表中使用的缩写的含义:

  • TN真负例):这是指原本为负的结果被预测为负的数量。

  • FP假正例):这是指原本为负的结果被预测为正的数量。这种错误也叫做第一类错误

  • FN假负例):这是指原本为正的结果被预测为负的数量。这种错误也叫做第二类错误

  • TP真正例):这是指原本为正的结果被预测为正的数量。

目标是最大化前表中TNTP框中的值,即真正负例和真正正例,同时最小化FNFP框中的值,即假负例和假正例。

以下代码是混淆矩阵的一个示例:

from sklearn.metrics import confusion_matrix
cm = confusion_matrix(y_test,y_pred_class)
print(cm)

前面的代码会生成如下输出:

array([[89, 2],
       [13, 4]], dtype=int64)

所有机器学习和深度学习算法的目标都是最大化 TN 和 TP,最小化 FN 和 FP。以下示例代码计算了 TN、FP、FN 和 TP:

# True Negative
TN = cm[0,0]
# False Negative
FN = cm[1,0]
# False Positives
FP = cm[0,1]
# True Positives
TP = cm[1,1]

注意

准确率无法帮助我们理解第一类错误和第二类错误。

从混淆矩阵计算的指标

混淆矩阵中可以推导出以下指标:灵敏度特异性精度假阳性率ROCAUC

  • 1,除以实际为1的患者总数:

    灵敏度 = TP / (TP+FN)

    灵敏度是指当实际值为正时,预测正确的频率。在构建如预测患者再入院的模型等情况下,我们需要模型具有很高的灵敏度。我们需要将1预测为1。如果0被预测为1,是可以接受的,但如果1被预测为0,意味着本应再入院的患者被预测为没有再入院,这会对医院造成严重的惩罚。

  • 0,除以实际为0的患者总数。特异性也称为真负例率:

    特异性 = TN / (TN+FP)

    特异性指的是当实际值为负时,预测正确的频率。比如在垃圾邮件检测中,我们希望算法更具特异性。当一封邮件是垃圾邮件时,模型预测为1;当不是垃圾邮件时,预测为0。我们希望模型总是将非垃圾邮件预测为0,因为如果一个非垃圾邮件被误分类为垃圾邮件,重要邮件可能会进入垃圾邮件文件夹。这里灵敏度可能会有所妥协,因为一些垃圾邮件可能会进入收件箱,但非垃圾邮件绝对不应该进入垃圾邮件文件夹。

    注意

    正如我们之前讨论的,模型应该偏向灵敏度还是特异性完全取决于业务问题。

  • 精确度:这是正确预测的正样本数量除以总的正样本预测数量。精确度指的是当预测值为正时,我们预测正确的频率:

    精确度 = TP / (TP+FP)

  • FPR的计算方法是将假阳性事件的数量与实际负样本总数相除。FPR指的是当实际值为负时,我们预测错误的频率。FPR也等于1 - 特异性:

    假阳性率 = FP / (FP+TN)

  • ROC 曲线ROC 曲线是一个图形,表示真实正例率(灵敏度)和FPR1 - 特异性)之间的关系。以下图展示了一个ROC 曲线的示例:图 6.4:ROC 曲线的示例

图 6.4:ROC 曲线的示例

为了决定在多个曲线中哪个ROC 曲线最好,我们需要查看曲线左上方的空白区域——空白区域越小,结果越好。以下图展示了多个ROC 曲线的示例:

图 6.5:多个 ROC 曲线的示例

图 6.5:多个 ROC 曲线的示例

注意

红色曲线比蓝色曲线更好,因为它在左上角留下的空白更少。

模型的ROC 曲线告诉我们灵敏度特异性之间的关系。

  • ROC 曲线。有时,AUC也写作AUROC,表示ROC 曲线下的面积。基本上,AUC是一个数值,表示ROC 曲线下的面积。ROC下的面积越大越好,AUC 分数越大越好。前面的图展示了一个AUC的示例。

    在前面的图中,红色曲线的AUC大于蓝色曲线的AUC,这意味着红色曲线的AUC优于蓝色曲线的AUCAUC 分数没有标准规则,但以下是一些普遍可接受的值及其与模型质量的关系:

图 6.6:一般可接受的 AUC 分数

图 6.6:一般可接受的 AUC 分数

现在我们理解了各种度量背后的理论,让我们通过一些活动和练习来实施所学的内容。

练习 6.02:使用 Scania 卡车数据计算准确率和空值准确率

我们将在本练习中使用的数据集来自于重型斯堪尼亚卡车在日常使用中的故障数据。关注的系统是 空气压力系统APS),该系统生成的加压空气用于卡车的各种功能,如刹车和换挡。数据集中的正类表示 APS 中某个特定部件的故障,而负类表示与 APS 无关的部件的故障。

本练习的目标是预测哪些卡车由于 APS 系统发生故障,以便修理和维护人员能够在检查卡车故障原因时获得有用的信息,并了解卡车需要检查的具体部件。

注意

本练习的数据集可以从本书的 GitHub 仓库下载,地址为 packt.live/2SGEEsH

在整个练习过程中,由于内部数学运算的随机性,你可能会获得略微不同的结果。

数据预处理和探索性数据分析

  1. 导入所需的库。使用 pandas 的 read_csv 函数加载数据集,并查看数据集的前 five 行:

    #import the libraries
    import numpy as np
    import pandas as pd
    # Load the Data
    X = pd.read_csv("../data/aps_failure_training_feats.csv")
    y = pd.read_csv("../data/aps_failure_training_target.csv")
    # use the head function view the first 5 rows of the data
    X.head()
    

    下表展示了前述代码的输出:

    图 6.7:患者重新入院数据集的前五行

    图 6.7:患者重新入院数据集的前五行

  2. 使用 describe 方法描述数据集中的特征值:

    # Summary of Numerical Data
    X.describe()
    

    下表展示了前述代码的输出:

    图 6.8:患者重新入院数据集的数值元数据

    图 6.8:患者重新入院数据集的数值元数据

    注意

    自变量也被称为解释变量,而因变量则被称为 响应变量。另外,请记住,Python 中的索引是从 0 开始的。

  3. 使用 head 函数查看 y 的内容:

    y.head()
    

    下表展示了前述代码的输出:

    图 6.9:患者重新入院数据集  变量的前五行

    图 6.9:患者重新入院数据集 y 变量的前五行

  4. 使用 scikit-learn 库中的 train_test_split 函数将数据拆分为测试集和训练集。为了确保我们获得相同的结果,设置 random_state 参数为 42。数据按 80:20 比例拆分,即 80%训练数据,剩余的 20%测试数据

    from sklearn.model_selection import train_test_split
    seed = 42
    X_train, X_test, \
    y_train, y_test= train_test_split(X, y, test_size=0.20, \
                                      random_state=seed)
    
  5. 使用 StandardScaler 函数对训练数据进行缩放,并使用该缩放器对 test data 进行缩放:

    # Initialize StandardScaler
    from sklearn.preprocessing import StandardScaler
    sc = StandardScaler()
    # Transform the training data
    X_train = sc.fit_transform(X_train)
    X_train = pd.DataFrame(X_train,columns=X_test.columns)
    # Transform the testing data
    X_test = sc.transform(X_test)
    X_test = pd.DataFrame(X_test,columns=X_train.columns)
    

    注意

    sc.fit_transform() 函数对数据进行转换,数据会被转换成 NumPy 数组。我们可能需要在数据框对象中进行进一步分析,因此 pd.DataFrame() 函数将数据重新转换为数据框。

    这完成了本次练习的数据预处理部分。现在,我们需要构建神经网络并计算准确率

  6. 导入创建神经网络架构所需的库:

    # Import the relevant Keras libraries
    from keras.models import Sequential
    from keras.layers import Dense
    from keras.layers import Dropout
    from tensorflow import random
    
  7. 初始化Sequential类:

    # Initiate the Model with Sequential Class
    np.random.seed(seed)
    random.set_seed(seed)
    model = Sequential()
    
  8. 添加Dense类的隐藏层,并在每个隐藏层后面加上Dropout。构建第一个隐藏层,大小为64,丢弃率为0.5。第二个隐藏层的大小为32,丢弃率为0.4。第三个隐藏层的大小为16,丢弃率为0.3。第四个隐藏层的大小为8,丢弃率为0.2。最后一个隐藏层的大小为4,丢弃率为0.1。每个隐藏层使用ReLU 激活函数,并且权重初始化器设置为uniform

    # Add the hidden dense layers and with dropout Layer
    model.add(Dense(units=64, activation='relu', \
                    kernel_initializer='uniform', \
                    input_dim=X_train.shape[1]))
    model.add(Dropout(rate=0.5))
    model.add(Dense(units=32, activation='relu', \
                    kernel_initializer='uniform'))
    model.add(Dropout(rate=0.4))
    model.add(Dense(units=16, activation='relu', \
                    kernel_initializer='uniform'))
    model.add(Dropout(rate=0.3))
    model.add(Dense(units=8, activation='relu', \
                    kernel_initializer='uniform'))
    model.add(Dropout(rate=0.2))
    model.add(Dense(units=4, activation='relu', \
                    kernel_initializer='uniform'))
    model.add(Dropout(rate=0.1))
    
  9. 添加一个输出层,使用sigmoid激活函数:

    # Add Output Dense Layer
    model.add(Dense(units=1, activation='sigmoid', \
                    kernel_initializer='uniform'))
    

    注意

    由于输出是二分类,我们使用sigmoid函数。如果输出是多分类(即超过两个类别),则应该使用softmax函数。

  10. 编译网络并拟合模型。在训练过程中,通过设置metrics=['accuracy']来计算准确率:

    # Compile the model
    model.compile(optimizer='adam', \
                  loss='binary_crossentropy', \
                  metrics=['accuracy'])
    
  11. 使用100个训练周期(epochs),批处理大小为20,验证集比例为20%来训练模型:

    #Fit the Model
    model.fit(X_train, y_train, epochs=100, \
              batch_size=20, verbose=1, \
              validation_split=0.2, shuffle=False)
    
  12. test数据集上评估模型:

    test_loss, test_acc = model.evaluate(X_test, y_test)
    print(f'The loss on the test set is {test_loss:.4f} \
    and the accuracy is {test_acc*100:.4f}%')
    

    上述代码生成了以下输出:

    12000/12000 [==============================] - 0s 20us/step
    The loss on the test set is 0.0802 and the accuracy is 98.9917%
    

    模型返回的准确率为98.9917%。但是,这个结果够好吗?我们只能通过与空准确率进行比较来回答这个问题。

    计算空准确率:

  13. 空准确率可以通过使用pandas库的value_count函数来计算,我们在本章的练习 6.01中使用了它,在太平洋飓风数据集上计算空准确率

    """
    Use the value_count function to calculate distinct class values
    """
    y_test['class'].value_counts()
    

    上述代码生成了以下输出:

    0    11788
    1      212
    Name: class, dtype: int64
    
  14. 计算准确率:

    # Calculate the null accuracy 
    y_test['class'].value_counts(normalize=True).loc[0]
    

    上述代码生成了以下输出:

    0.9823333333333333
    

    在这里,我们得到了模型的空准确率。随着我们结束这个练习,以下几点必须注意:我们模型的准确率大约是98.9917%。在理想情况下,98.9917%的准确率是非常的准确率,但这里,空准确率非常高,这有助于我们将模型的表现放到一个合理的视角下。我们模型的空准确率98.2333%。由于模型的空准确率如此之高,98.9917%的准确率并不具有显著意义,但肯定是值得尊敬的,而在这种情况下,准确率并不是评估算法的正确指标。

    注意

    要访问该部分的源代码,请参阅packt.live/31FUb2d

    你也可以在线运行这个示例:packt.live/3goL0ax

现在,让我们通过活动来计算神经网络模型在我们改变训练/测试数据集划分时的准确率和空准确率。

活动 6.01:计算神经网络在我们改变训练/测试数据集划分时的准确率和空准确率

训练/测试划分是一种随机采样技术。在此活动中,我们将看到,通过改变train/test划分,我们的零准确度和准确度会受到影响。要实现这一点,必须更改定义train/test划分的代码部分。我们将使用在练习 6.02中使用的相同数据集,计算斯堪尼亚卡车数据的准确度和零准确度。按照以下步骤完成此活动:

  1. 导入所有必要的依赖项并加载数据集。

  2. test_sizerandom_state分别从0.20更改为0.30,从42更改为13

  3. 使用StandardScaler函数对数据进行缩放。

  4. 导入构建神经网络架构所需的库,并初始化Sequential类。

  5. 添加带有DropoutDense层。设置第一个隐藏层,使其大小为64,丢弃率为0.5,第二个隐藏层设置为32,丢弃率为0.4,第三个隐藏层设置为16,丢弃率为0.3,第四个隐藏层设置为8,丢弃率为0.2,最后一个隐藏层设置为4,丢弃率为0.1。将所有激活函数设置为ReLU

  6. 添加一个输出Dense层,并使用sigmoid激活函数。

  7. 编译网络并使用准确度拟合模型。用 100 个 epoch 和 20 的批量大小拟合模型。

  8. 将模型拟合到训练数据,并保存拟合过程中的结果。

  9. 在测试数据集上评估模型。

  10. 统计测试目标数据集中每个类别的值的数量。

  11. 使用 pandas 的value_count函数计算零准确度。

    注意

    在此活动中,由于内部数学操作的随机性质,您可能会得到略有不同的结果。

在这里,我们可以看到,随着train/test划分的变化,准确度和零准确度也会发生变化。由于我们的数据集极度不平衡,并且采样技术不会带来有效的结果,本章不涉及任何采样技术。

注意

本活动的解决方案可以在第 430 页找到。

让我们继续进行下一个练习,并计算基于混淆矩阵得出的指标。

练习 6.03:基于混淆矩阵推导并计算指标

我们在本次练习中使用的数据集包含来自重型斯堪尼亚卡车的日常使用数据,这些卡车在某些方面发生了故障。焦点系统是空气压力系统APS),它生成的加压空气被用于卡车的各种功能,如刹车和换档。数据集中的正类表示APS中某个特定组件的故障,而负类表示与APS无关的组件故障。

本练习的目标是预测哪些卡车由于 APS 发生故障,就像我们在前一个练习中所做的那样。我们将推导出神经网络模型的灵敏度、特异性、精确度和假阳性率,以评估其性能。最后,我们将调整阈值并重新计算灵敏度和特异性。按照以下步骤完成此练习:

注意

本练习的数据集可以从本书的 GitHub 仓库下载:packt.live/2SGEEsH

由于内部数学运算的随机性,您可能会得到略微不同的结果。

  1. 导入必要的库并使用 pandas 的read_csv函数加载数据:

    # Import the libraries
    import numpy as np
    import pandas as pd
    # Load the Data
    X = pd.read_csv("../data/aps_failure_training_feats.csv")
    y = pd.read_csv("../data/aps_failure_training_target.csv")
    
  2. 接下来,使用train_test_split函数将数据分割为训练集和测试集:

    from sklearn.model_selection import train_test_split
    seed = 42
    X_train, X_test, \
    y_train, y_test = train_test_split(X, y, \
                      test_size=0.20, random_state=seed)
    
  3. 接下来,使用StandardScaler函数对特征数据进行缩放,使其具有0均值1标准差。将缩放器拟合到训练数据并将其应用于测试数据

    from sklearn.preprocessing import StandardScaler
    sc = StandardScaler()
    # Transform the training data
    X_train = sc.fit_transform(X_train)
    X_train = pd.DataFrame(X_train,columns=X_test.columns)
    # Transform the testing data
    X_test = sc.transform(X_test)
    X_test = pd.DataFrame(X_test,columns=X_train.columns)
    
  4. 接下来,导入创建模型所需的Keras库。实例化一个KerasSequential类模型,并向模型中添加五个隐藏层,每个层都包括 dropout。第一个隐藏层的大小应为64,dropout 率为0.5。第二个隐藏层的大小应为32,dropout 率为0.4。第三个隐藏层的大小应为16,dropout 率为0.3。第四个隐藏层的大小应为8,dropout 率为0.2。最后一个隐藏层的大小应为4,dropout 率为0.1。所有隐藏层都应使用ReLU 激活函数,并且kernel_initializer = 'uniform'。为模型添加一个最终的输出层,并使用sigmoid 激活函数。通过在训练过程中计算准确性指标来编译模型:

    # Import the relevant Keras libraries
    from keras.models import Sequential
    from keras.layers import Dense
    from keras.layers import Dropout
    from tensorflow import random
    np.random.seed(seed)
    random.set_seed(seed)
    model = Sequential()
    # Add the hidden dense layers and with dropout Layer
    model.add(Dense(units=64, activation='relu', \
                    kernel_initializer='uniform', \
                    input_dim=X_train.shape[1]))
    model.add(Dropout(rate=0.5))
    model.add(Dense(units=32, activation='relu', \
                    kernel_initializer='uniform'))
    model.add(Dropout(rate=0.4))
    model.add(Dense(units=16, activation='relu', \
                    kernel_initializer='uniform'))
    model.add(Dropout(rate=0.3))
    model.add(Dense(units=8, activation='relu', \
                    kernel_initializer='uniform'))
    model.add(Dropout(rate=0.2))
    model.add(Dense(units=4, activation='relu', \
                    kernel_initializer='uniform'))
    model.add(Dropout(rate=0.1))
    # Add Output Dense Layer
    model.add(Dense(units=1, activation='sigmoid', \
                    kernel_initializer='uniform'))
    # Compile the Model
    model.compile(optimizer='adam', \
                  loss='binary_crossentropy', \
                  metrics=['accuracy'])
    
  5. 接下来,通过训练100个周期,batch_size=20,并设置validation_split=0.2来拟合模型:

    model.fit(X_train, y_train, epochs=100, \
              batch_size=20, verbose=1, \
              validation_split=0.2, shuffle=False)
    
  6. 一旦模型完成对训练数据的拟合,创建一个变量,该变量是使用模型的predictpredict_proba方法对测试数据进行预测的结果:

    y_pred = model.predict(X_test)
    y_pred_prob = model.predict_proba(X_test)
    
  7. 接下来,通过设置测试集上预测值大于0.5时的预测值为1,小于0.5时为0来计算预测类别。使用scikit-learnconfusion_matrix函数计算混淆矩阵

    from sklearn.metrics import confusion_matrix
    y_pred_class1 = y_pred > 0.5
    cm = confusion_matrix(y_test, y_pred_class1)
    print(cm)
    

    上述代码会产生以下输出:

    [[11730  58]
     [   69 143]]
    

    始终将y_test作为第一个参数,将y_pred_class1作为第二个参数,以确保始终获得正确的结果。

  8. 计算真正例(TN)、假负例(FN)、假阳性(FP)和真正阳性(TP):

    # True Negative
    TN = cm[0,0]
    # False Negative
    FN = cm[1,0]
    # False Positives
    FP = cm[0,1]
    # True Positives
    TP = cm[1,1]
    

    注意

    按照y_testy_pred_class1的顺序使用是必要的,因为如果顺序颠倒,矩阵仍然会被计算,但结果将不正确。

  9. 计算灵敏度

    # Calculating Sensitivity
    Sensitivity = TP / (TP + FN)
    print(f'Sensitivity: {Sensitivity:.4f}')
    

    上述代码生成了以下输出:

    Sensitivity: 0.6745
    
  10. 计算特异性

    # Calculating Specificity
    Specificity = TN / (TN + FP)
    print(f'Specificity: {Specificity:.4f}')
    

    上述代码生成了以下输出:

    Specificity: 0.9951
    
  11. 计算精确度

    # Precision
    Precision = TP / (TP + FP)
    print(f'Precision: {Precision:.4f}')
    

    上述代码生成了以下输出:

    Precision: 0.7114
    
  12. 计算假阳性率

    # Calculate False positive rate
    False_Positive_rate = FP / (FP + TN)
    print(f'False positive rate: \
          {False_Positive_rate:.4f}')
    

    上述代码生成了以下输出:

    False positive rate: 0.0049
    

    以下图显示了值的输出:

    图 6.10:指标汇总

    ](https://github.com/OpenDocCN/freelearn-dl-pt5-zh/raw/master/docs/dl-keras-ws/img/B15777_06_10.jpg)

    图 6.10:指标汇总

    注意

    灵敏度与特异性成反比。

    正如我们之前讨论的那样,我们的模型应该更敏感,但它看起来更具特异性,且灵敏度较低。那么,我们该如何解决这个问题呢?答案在于阈值概率。通过调整分类依赖变量为10的阈值,可以提高模型的灵敏度。回想一下,最初我们将y_pred_class1的值设置为大于0.5。让我们将阈值更改为0.3,并重新运行代码来检查结果。

  13. 转到步骤 7,将阈值从0.5改为0.3,并重新运行代码:

    y_pred_class2 = y_pred > 0.3
    
  14. 现在,创建一个混淆矩阵并计算特异性灵敏度

    from sklearn.metrics import confusion_matrix
    cm = confusion_matrix(y_test,y_pred_class2)
    print(cm)
    

    上述代码生成了以下输出:

    [[11700  88]
     [   58 154]]
    

    为了对比,以下是阈值为0.5的之前的混淆矩阵

    [[11730  58]
     [   69 143]]
    

    注意

    始终记得,y_test的原始值应该作为第一个参数传递,而y_pred作为第二个参数。

  15. 计算混淆矩阵的各个组件:

    # True Negative
    TN = cm[0,0]
    # False Negative
    FN = cm[1,0]
    # False Positives
    FP = cm[0,1]
    # True Positives
    TP = cm[1,1]
    
  16. 计算新的灵敏度

    # Calculating Sensitivity
    Sensitivity = TP / (TP + FN)
    print(f'Sensitivity: {Sensitivity:.4f}')
    

    上述代码生成了以下输出:

    Sensitivity: 0.7264
    
  17. 计算特异性

    # Calculating Specificity
    Specificity = TN / (TN + FP)
    print(f'Specificity: {Specificity:.4f}')
    

    上述代码生成了以下输出:

    Specificity: 0.9925
    

    降低阈值后,灵敏度特异性明显增加:

    图 6.11:灵敏度与特异性对比

    ](https://github.com/OpenDocCN/freelearn-dl-pt5-zh/raw/master/docs/dl-keras-ws/img/B15777_06_11.jpg)

    图 6.11:灵敏度与特异性对比

    所以,显然,降低阈值会增加灵敏度。

  18. 可视化数据分布。为了理解为什么降低阈值会增加灵敏度,我们需要查看预测概率的直方图。回想一下,我们创建了y_pred_prob变量来预测分类器的概率:

    import matplotlib.pyplot as plt
    %matplotlib inline
    # histogram of class distribution
    plt.hist(y_pred_prob, bins=100)
    plt.title("Histogram of Predicted Probabilities")
    plt.xlabel("Predicted Probabilities of APS failure")
    plt.ylabel("Frequency")
    plt.show()
    

    以下图显示了上述代码的输出:

图 6.12:数据集中患者再次入院的概率直方图

](https://github.com/OpenDocCN/freelearn-dl-pt5-zh/raw/master/docs/dl-keras-ws/img/B15777_06_12.jpg)

图 6.12:数据集中患者再次入院的概率直方图

该直方图清楚地显示,大多数预测分类器的概率位于0.00.1的范围内,确实非常低。除非我们将阈值设置得非常低,否则无法提高模型的灵敏度。还要注意,灵敏度与特异性成反比,因此当灵敏度增加时,特异性会减少,反之亦然。

注意

要访问此特定部分的源代码,请参考packt.live/31E6v32

你还可以在packt.live/3gquh6y在线运行这个例子。

阈值没有一个普遍适用的值,尽管0.5的值通常作为默认值。选择阈值的一种方法是绘制直方图,然后手动选择阈值。在我们的案例中,任何在0.10.7之间的阈值都可以使用,因为在这些值之间的预测较少,正如在前一个练习结束时生成的直方图所示。

选择阈值的另一种方法是绘制ROC 曲线,该曲线将真正例率与假正例率进行比较。根据你对每个值的容忍度,可以选择合适的阈值。如果我们希望评估模型的性能,绘制ROC 曲线也是一个很好的方法,因为ROC 曲线下的面积是评估模型性能的直接指标。在接下来的活动中,我们将使用ROC 曲线AUC 分数来探索我们模型的性能。

活动 6.02:计算 ROC 曲线和 AUC 分数

ROC 曲线AUC 分数是有效且简便的二分类器性能评估方法。在本次活动中,我们将绘制ROC 曲线并计算模型的AUC 分数。我们将使用与练习 6.03中相同的数据集和模型,该练习是基于混淆矩阵推导与计算指标。使用 APS 故障数据,计算ROC 曲线AUC 分数。按照以下步骤完成此活动:

  1. 导入所有必要的依赖项并加载数据集。

  2. 使用train_test_split函数将数据集分割为训练集和测试集。

  3. 使用StandardScaler函数对训练数据和测试数据进行缩放。

  4. 导入构建神经网络架构所需的库,并初始化Sequential类。添加五个Dense层和Dropout层。设置第一个隐藏层的大小为64,并且丢弃率为0.5;第二个隐藏层的大小为32,丢弃率为0.4;第三个隐藏层的大小为16,丢弃率为0.3;第四个隐藏层的大小为8,丢弃率为0.2;最后一个隐藏层的大小为4,丢弃率为0.1。将所有激活函数设置为ReLU

  5. 添加一个输出Dense层,使用sigmoid激活函数。编译网络并使用准确度来拟合模型。用100个 epochs 和20的批次大小来训练模型。

  6. 将模型拟合到训练数据,并保存拟合过程中的结果。

  7. 创建一个变量,表示测试数据集的预测类别。

  8. 使用 sklearn.metrics 中的 roc_curve 函数计算假阳性率和真阳性率。假阳性率和真阳性率是三个返回变量中的第一和第二个。将真实值和预测值传递给函数。

  9. 绘制 ROC 曲线,这是真正阳性率作为假阳性率的函数。

  10. 使用 sklearn.metrics 中的 roc_auc_score 计算 AUC 分数,同时传递模型的真实值和预测值。

实施这些步骤后,您应该获得以下输出:

0.944787151628455

注意

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

在此活动中,我们学习了如何使用 APS 失效数据集计算 ROCAUC 分数。我们还学习了特异性和敏感性如何随不同阈值变化。

总结

在本章中,我们深入讨论了模型评估和准确性。当数据集不平衡时,我们学到了准确性不是评估的最合适技术。我们还学习了如何使用 scikit-learn 计算混淆矩阵,以及如何推导其他指标,如敏感性、特异性、精确度和假阳性率。

最后,我们了解了如何使用阈值值来调整指标,以及 ROC 曲线AUC 分数 如何帮助我们评估模型。在现实生活中处理不平衡数据集非常常见。信用卡欺诈检测、疾病预测和垃圾邮件检测等问题都有不同比例的不平衡数据。

在下一章中,我们将学习一种不同类型的神经网络架构(卷积神经网络),它在图像分类任务上表现良好。我们将通过将图像分类为两类并尝试不同的架构和激活函数来测试性能。

第七章:7. 使用卷积神经网络的计算机视觉

概览

本章涵盖了计算机视觉以及如何通过神经网络实现这一目标。你将学习如何构建图像处理应用程序,并使用卷积神经网络进行模型分类。你还将研究卷积神经网络的架构,并学习如何利用最大池化、展平、特征映射和特征检测等技术。在本章结束时,你不仅能构建自己的图像分类器,还能有效地评估它们,以便应用到自己的实际项目中。

引言

在上一章中,我们详细探讨了模型评估。我们介绍了准确率,以及为什么在某些数据集上,尤其是类别高度不平衡的分类任务中,准确率可能会产生误导。像预测太平洋地区飓风或预测某人是否会违约信用卡贷款这样的数据集,正类实例相较于负类实例较为稀有,因此准确率分数会产生误导,因为空准确率如此之高。

为了应对类别不平衡问题,我们学习了可以用来适当评估模型的技术,包括计算模型评估指标,如灵敏度、特异性、假阳性率和AUC 分数,以及绘制ROC 曲线。在本章中,我们将学习如何分类另一种类型的数据集——即图像。图像分类非常有用,并且有很多现实世界的应用,我们将会发现。

计算机视觉是机器学习和人工智能中最重要的概念之一。随着智能手机在拍摄、分享和上传图片方面的广泛使用,通过图像生成的数据量正在呈指数级增长。因此,专注于计算机视觉领域的专家需求达到了历史最高水平。像医疗保健行业这样的行业,由于医学影像领域的进展,正处于一场革命的边缘。

本章将介绍计算机视觉及其应用的各个行业。你还将学习ANNs(人工神经网络),它使用向量作为输入,而 CNN 则使用图像作为输入。在本章中,我们将更详细地研究CNNs(卷积神经网络),以及与之相关的概念,如最大池化展平特征图特征选择。我们将使用 Keras 作为工具,运行实际图像上的图像处理算法。

计算机视觉

为了理解计算机视觉,我们先来讨论一下人类视觉。人类视觉是指人眼和大脑看见并识别物体的能力。计算机视觉是给机器提供一种类似的,甚至是更好的理解,能够看到和识别现实世界中的物体。

对人眼来说,准确识别一个动物是老虎还是狮子相对简单,但对于计算机系统来说,要区分这些物体需要大量的训练。计算机视觉也可以定义为构建能够模仿人眼和大脑功能的数学模型。基本上,就是训练计算机理解和处理图像与视频。

计算机视觉是许多前沿机器人技术领域的重要组成部分:医疗健康(X 光片、MRI 扫描、CT 扫描等)、无人机、自动驾驶汽车、体育和娱乐等。几乎所有的企业都需要计算机视觉才能成功运营。

想象一下,由全球的 CCTV 视频、我们智能手机每天捕捉的照片、每天在 YouTube 等互联网网站上传播的视频,以及我们在 Facebook 和 Instagram 等社交网站上分享的图片,所生成的大量数据。所有这些都会产生巨量的图像数据。为了处理和分析这些数据,使计算机在处理上变得更智能,这些数据需要高水平的计算机视觉专家来处理。计算机视觉是机器学习中一个高回报的领域。以下章节将描述如何通过神经网络——特别是卷积神经网络——来实现计算机视觉任务,而这些神经网络在计算机视觉任务中表现出色。

卷积神经网络

当我们谈论计算机视觉时,我们几乎总是提到 CNN。CNN 是一类深度神经网络,主要用于计算机视觉和图像领域。CNN 用于识别图像,将它们按相似性进行聚类,并在场景中实现物体识别。CNN 有不同的层次——即输入层、输出层和多个隐藏层。这些隐藏层包括全连接层、卷积层、作为激活函数ReLU 层归一化层和池化层。在一个非常简单的层面上,CNN 帮助我们识别图像并进行适当的标注;例如,一张老虎图像会被识别为老虎:

图 7.1:一个通用的 CNN

图 7.1:一个通用的 CNN

以下是一个 CNN 分类老虎的例子:

图 7.2:一个 CNN 将一张老虎图像分类为“老虎”类别

图 7.2:一个 CNN 将一张老虎图像分类为“老虎”类别

CNN 的架构

CNN 架构的主要组成部分如下:

  • 输入图像

  • 卷积层

  • 池化层

  • 扁平化

输入图像

输入图像是 CNN 架构的第一个组成部分。图像可以是任何类型的:人类、动物、风景、医学 X 光图像等等。每张图像都会被转换成一个由零和一组成的数学矩阵。以下图解释了计算机如何看待字母T的图像。

所有值为 1 的块表示数据,而值为 0 的块表示空白区域:

图 7.3:字母‘T’的矩阵

图 7.3:字母‘T’的矩阵

卷积层

卷积层是图像处理开始的地方。卷积层由两个部分组成:

  • 特征检测器滤波器

  • 特征图

特征检测器滤波器:这是一个矩阵或模式,将其应用到图像上,可以将其转化为特征图:

图 7.4:特征检测器

图 7.4:特征检测器

如我们所见,这个特征检测器被(叠加)放置在原始图像上,并对相应的元素进行计算。计算是通过乘以相应的元素完成的,如下图所示。这个过程会对所有单元进行重复。最终得到一个新的处理过的图像—— (0x0+0x0+0x1) + (0x1+1x0+0x0) + (0x0+0x1+0x1) = 0

图 7.5:特征检测器在图像中的掩膜

图 7.5:特征检测器在图像中的掩膜

特征图:这是通过将图像特征检测器卷积得到的缩小图像。我们必须将特征检测器放在原始图像的所有可能位置,并从中得出一个更小的图像;这个生成的图像就是输入图像的特征图:

图 7.6:特征图

图 7.6:特征图

注意

在这里,特征检测器是滤波器,特征图是缩小后的图像。在图像缩小时,一些信息会丢失。

在实际的卷积神经网络(CNN)中,会使用多个特征检测器生成多个特征图,如下图所示:

图 7.7:多个特征检测器和特征图

图 7.7:多个特征检测器和特征图

池化层

池化层帮助我们忽略图像中不太重要的数据,并进一步缩小图像的大小,同时保留其重要特征。考虑以下三张包含四只猫的图像:

图 7.8:猫图像示例

图 7.8:猫图像示例

为了识别图像中是否包含猫,神经网络分析图片。它可能会查看耳朵形状、眼睛形状等。同时,图像中包含许多与猫无关的特征。前两张图中的树和叶子在猫的识别中是无用的。池化机制帮助算法理解图像中哪些部分是相关的,哪些部分是无关的。

从卷积层得到的特征图会通过池化层进一步减少图像的尺寸,同时保留图像中最相关的部分。池化层由最大池化、最小池化和平均池化等功能组成。这意味着我们选择一个矩阵大小,比如2x2,然后扫描特征图,选择适合该块的2x2矩阵中的最大数值。下图清晰地展示了最大池化是如何工作的。请参考颜色;从特征图中,每个彩色框中的最大值会被选中并放入池化后的特征图:

图 7.9:池化

图 7.9:池化

考虑一个包含数字4的框。假设数字4表示一只猫的耳朵,而耳朵周围的空白部分是01。因此,我们忽略该块中的01,只选择4。以下是我们用来添加池化层的示例代码;这里使用Maxpool2D进行最大池化,它有助于识别最重要的特征:

classifier.add(MaxPool2D(2,2))

展平

Flattening是 CNN 的一部分,其中图像被处理为 ANN 的输入。顾名思义,经过池化的图像被展平,并转换成一个单一的列。每一行被转化为一列,并一个接一个地堆叠在一起。在这里,我们将一个3x3矩阵转换成了一个1xn矩阵,其中n在我们的例子中为9

图 7.10:展平

图 7.10:展平

在实时处理中,我们会得到多个池化后的特征图,并将它们展平成一个单一的列。这个单一的列会作为 ANN 的输入。下图展示了多个池化层被展平为单一列:

图 7.11:池化与展平

图 7.11:池化与展平

以下是我们用来添加展平层的示例代码;这里使用Flatten来展平 CNN:

classifier.add(Flatten())

现在,让我们来看一下卷积神经网络(CNN)的整体结构:

图 7.12:CNN 架构

图 7.12:CNN 架构

以下是我们用来为 CNN 添加第一层的示例代码:

classifier.add(Conv2D(32,3,3,input_shape=(64,64,3),activation='relu'))

32,3,3表示有323x3大小的特征检测器。作为一个好的实践,建议从32开始,之后可以添加64128

Input_shape:由于所有图像的形状和大小都不同,这个input_image将所有图像转换成统一的形状和大小。(64,64)是转换后的图像的尺寸。它可以设置为128256,但如果你在笔记本电脑的 CPU 上工作,建议使用64x64。最后一个参数3是因为图像是彩色图像(使用红、绿、蓝编码,或 RGB)。如果图像是黑白的,那么可以将该参数设置为 1。使用的激活函数是 ReLU。

注意

本书中,我们使用 Keras 并以 TensorFlow 作为后端。如果后端是 Theano,那么input_image将被编码为(3,64,64)。

最后一步是拟合已经创建的数据。这里是我们用来执行此操作的代码:

classifier.fit_generator(training_set,steps_per_epoch = 5000,\
                         epochs = 25,validation_data = test_set,\
                         validation_steps = 1000)

注意

steps_per_epoch 是训练图像的数量。validation_steps 是测试图像的数量。

图像增强

增强这个词意味着使某物变大或增多的动作或过程。图像数据增强以类似的方式工作。图像/数据增强创建了许多我们图像的批次。然后,它对批次中的随机图像应用随机转换。数据转换可以是旋转图像、平移图像、翻转图像等。通过应用这种转换,我们在批次中获得了更多样化的图像,并且我们也拥有了比原来更多的数据。

一个圆柱体可以从不同角度旋转并呈现出不同的视角。在以下图像中,单个圆柱体可以从五个不同的角度看到。因此,我们实际上从一张图像中创建了五张不同的图像:

图 7.13:圆柱体的图像增强

图 7.13:圆柱体的图像增强

以下是我们将用于图像增强的一些示例代码;在这里,ImageDataGenerator 类用于处理。shear_rangezoom_rangehorizontal_flip 都用于转换图像:

from keras.preprocessing.image import ImageDataGenerator
train_datagen = ImageDataGenerator(rescale = 1./255.0,\
                                   shear_range = 0.3,\
                                   zoom_range = 0.3,\
                                   horizontal_flip = False)
test_datagen = ImageDataGenerator(rescale = 1./255.0)

图像增强的优势

图像增强是图像处理中的一个重要部分:

  • 减少过拟合:通过创建相同图像的多个版本,并将其旋转一个给定角度,从而有助于减少过拟合。

  • 增加图像数量:一张图像作为多张图像处理。因此,本质上,数据集中的图像较少,但每张图像都可以通过图像增强转换为多张图像。图像增强将增加图像的数量,并且每张图像将被算法以不同的方式处理。

  • 容易预测新图像:想象一张足球的图像从不同角度拍摄,每个角度都被认为是一张不同的图像。这意味着算法在预测新图像时将更加准确:

图 7.14:足球图像的图像增强

图 7.14:足球图像的图像增强

现在我们已经学习了计算机视觉与卷积神经网络(CNN)的概念和理论,让我们做一些实际的例子。

首先,我们将从一个练习开始,构建一个简单的 CNN。在接下来的练习和活动中,我们将通过排列组合以下内容来调整我们的 CNN:

  • 添加更多 CNN 层

  • 添加更多 ANN 层

  • 改变优化器函数

  • 改变激活函数

让我们开始创建我们的第一个 CNN,以便将汽车和花卉图像分类到各自的类别中。

练习 7.01:构建 CNN 并识别汽车和花卉的图像

本次练习中,我们有汽车和花朵的图像,这些图像已经分为训练集和测试集,我们需要构建一个 CNN 模型,用于识别图像是汽车还是花朵。

注意

本章中的所有练习和活动将在 Jupyter notebooks 中进行。请从packt.live/39tID2C下载本书的 GitHub 仓库,以及所有准备好的模板。

在开始之前,确保已从本书的 GitHub 仓库下载了图像数据集到自己的工作目录。您将需要一个training_set文件夹来训练模型,一个test_set文件夹来测试模型。这些文件夹中都将包含一个cars文件夹,里面是汽车图像,以及一个flowers文件夹,里面是花朵图像。

完成此练习的步骤如下:

  1. 导入numpy库和所需的 Keras 库及类:

    # Import the Libraries
    from keras.models import Sequential
    from keras.layers import Conv2D, MaxPool2D, Flatten, Dense
    import numpy as np
    from tensorflow import random
    
  2. 现在,设置种子并使用Sequential类初始化模型:

    # Initiate the classifier
    seed = 1
    np.random.seed(seed)
    random.set_seed(seed)
    classifier = Sequential()
    
  3. 添加CNN的第一层,将输入形状设置为(64, 64, 3),即每个图像的维度,并设置激活函数为ReLU

    classifier.add(Conv2D(32,3,3, input_shape=(64,64,3), \
                   activation='relu'))
    

    32,3,3表示有323x3大小的特征探测器。

  4. 现在,添加池化层,图像大小为2x2

    classifier.add(MaxPool2D(2,2))
    
  5. 通过添加展平层到CNN模型中,展平池化层的输出:

    classifier.add(Flatten())
    
  6. 添加 ANN 的第一层Dense。这里,128是节点的输出数量。作为一个好的实践,128是一个不错的起点。activationrelu,作为一个好的实践,最好选择 2 的幂次方:

    classifier.add(Dense(128, activation='relu')) 
    
  7. 添加 ANN 的输出层。由于这是一个二分类问题,输出大小为1,激活函数为sigmoid

    classifier.add(Dense(1, activation='sigmoid')) 
    
  8. 使用adam优化器编译网络,并在训练过程中计算准确率:

    #Compile the network
    classifier.compile(optimizer='adam', loss='binary_crossentropy', \
                       metrics=['accuracy'])
    
  9. 创建训练和测试数据生成器。将训练和测试图像按1/255进行重缩放,使所有值都在01之间。仅为训练数据生成器设置以下参数——shear_range=0.2zoom_range=0.2horizontal_flip=True

    from keras.preprocessing.image import ImageDataGenerator
    train_datagen = ImageDataGenerator(rescale = 1./255,\
                                       shear_range = 0.2,\
                                       zoom_range = 0.2,\
                                       horizontal_flip = True)
    test_datagen = ImageDataGenerator(rescale = 1./255)
    
  10. training set文件夹创建训练集。'../dataset/training_set'是我们存放数据的文件夹。我们的 CNN 模型的图像大小为64x64,因此这里也应传入相同的大小。batch_size是每个批次中的图像数量,设为32Class_mode设置为binary,因为我们正在处理二分类问题:

    training_set = train_datagen.flow_from_directory(\
                   '../dataset/training_set',\
                   target_size = (64, 64),\
                   batch_size = 32,\
                   class_mode = 'binary')
    
  11. 对测试集重复第 10 步,同时将文件夹设置为测试图像所在的位置,即'../dataset/test_set'

    test_set = test_datagen.flow_from_directory(\
               '../dataset/test_set',\
               target_size = (64, 64),\
               batch_size = 32,\
               class_mode = 'binary')
    
  12. 最后,拟合数据。将steps_per_epoch设置为10000validation_steps设置为2500。以下步骤可能需要一些时间来执行:

    classifier.fit_generator(training_set,steps_per_epoch = 10000,\
                             epochs = 2,validation_data = test_set,\
                             validation_steps = 2500,shuffle=False)
    

    上述代码将产生以下输出:

    Epoch 1/2
    10000/10000 [==============================] - 1994s 199ms/step - loss: 0.2474 - accuracy: 0.8957 - val_loss: 1.1562 - val_accuracy: 0.8400
    Epoch 2/2
    10000/10000 [==============================] - 1695s 169ms/step - loss: 0.0867 - accuracy: 0.9689 - val_loss: 1.4379 - val_accuracy: 0.8422
    

    验证集上的准确率为84.22%

    注意

    为了获得更准确的结果,尝试将 epochs 的数量增加到大约25。这将增加处理数据所需的时间,总时间取决于机器的配置。

    要访问此特定部分的源代码,请参考packt.live/38njqHU

    你还可以在packt.live/3iqFpSN上在线运行这个示例。

这完成了关于处理图像和识别图像内容的练习。这里需要记住的一件重要事是,这段代码对于任何二分类问题都是健壮的。这意味着即使图像数据发生变化,代码也保持不变。我们将在下一个活动中通过修改模型的一些参数并评估模型的性能来测试我们对此的理解。

活动 7.01:用多层和使用 softmax 修改我们的模型

由于我们已经成功运行了CNN 模型,下一步是尝试提高我们算法的性能。有很多方法可以提高其性能,其中一种最直接的方法是向模型中添加多个 ANN 层,我们将在本活动中学习这个方法。我们还将激活函数从 sigmoid 改为 softmax。通过这样做,我们可以将结果与上一个练习的结果进行比较。请按照以下步骤完成此活动:

  1. 要构建 CNN 导入库,设置种子并创建Sequential类,导入Conv2DMaxPool2DFlattenDenseConv2D用于构建卷积层。由于我们的图片是二维的,所以这里使用了二维卷积。类似地,MaxPool2D用于最大池化,Flatten用于将 CNN 展开,Dense用于向 ANN 添加全连接层。

  2. 使用前面的库开始构建 CNN 架构。在添加第一层之后,向 CNN 中添加两层额外的层。

  3. 向其中添加一个池化和展平层,它将作为 ANN 的输入。

  4. 构建一个全连接的 ANN,其输入将是 CNN 的输出。在添加 ANN 的第一层后,再添加三层。对于 ANN 的输出层,使用 softmax 激活函数。编译模型。

  5. 执行图像增强以处理和转换数据。ImageDataGenerator类用于处理。shear_rangezoom_rangehorizontal_flip都用于图像的转换。

  6. 创建训练集和测试集数据。

  7. 最后,拟合已创建的数据。

在实现这些步骤之后,你应该得到以下预期输出:

Epoch 1/2
10000/10000 [==============================] - 2452s 245ms/step - loss: 8.1783 - accuracy: 0.4667 - val_loss: 11.4999 - val_accuracy: 0.4695
Epoch 2/2
10000/10000 [==============================] - 2496s 250ms/step - loss: 8.1726 - accuracy: 0.4671 - val_loss: 10.5416 - val_accuracy: 0.4691

注意

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

在此活动中,我们修改了 CNN 模型,尝试提高图像分类器的准确性。我们添加了额外的卷积层和额外的 ANN 全连接层,并更改了输出层的激活函数。这样做后,我们的准确性反而下降了。在下一个练习中,我们将激活函数更改回 sigmoid。我们将通过观察在验证数据集上评估的准确性来评估性能。

练习 7.02:通过恢复为 Sigmoid 激活函数来修改我们的模型

在这个练习中,我们将重建模型,但将激活函数从 softmax 恢复为 sigmoid。通过这样做,我们可以与之前的模型进行准确度比较。按照以下步骤完成此练习:

  1. 导入numpy库和必要的 Keras 库及类:

    # Import the Libraries 
    from keras.models import Sequential
    from keras.layers import Conv2D, MaxPool2D, Flatten, Dense
    import numpy as np
    from tensorflow import random
    
  2. 现在,设置随机种子并使用Sequential类初始化模型:

    # Initiate the classifier
    seed = 43
    np.random.seed(seed)
    random.set_seed(seed)
    classifier = Sequential()
    
  3. 添加 CNN 的第一层,设置输入形状为(64, 64, 3),即每张图像的维度,并将激活函数设置为 ReLU。然后,添加32个大小为(3, 3)的特征检测器。再添加两层具有32(3, 3)大小特征检测器的卷积层,且同样使用 ReLU 激活函数:

    classifier.add(Conv2D(32,3,3,input_shape=(64,64,3),\
                          activation='relu'))
    classifier.add(Conv2D(32, (3, 3), activation = 'relu'))
    classifier.add(Conv2D(32, (3, 3), activation = 'relu'))
    
  4. 现在,添加池化层,图像大小为2x2

    classifier.add(MaxPool2D(2,2))
    
  5. 再添加一个Conv2D层,参数与步骤 3中的相同,再加一个池化层,用于补充步骤 4中使用的相同参数:

    classifier.add(Conv2D(32, (3, 3), activation = 'relu'))
    classifier.add(MaxPool2D(pool_size = (2, 2)))
    
  6. 通过向CNN model中添加一个 flatten 层,扁平化池化层的输出:

    classifier.add(Flatten())
    
  7. 添加 ANN 的第一个Dense层。这里,128是节点的输出数量。作为一个好的实践,128是一个很好的起点。activationrelu。作为一个好的实践,最好选择二的幂次。再添加三个具有相同参数的额外层:

    classifier.add(Dense(128,activation='relu'))
    classifier.add(Dense(128,activation='relu'))
    classifier.add(Dense(128,activation='relu'))
    classifier.add(Dense(128,activation='relu'))
    
  8. 添加 ANN 的输出层。这是一个二分类问题,因此输出为1,激活函数为sigmoid

    classifier.add(Dense(1,activation='sigmoid')) 
    
  9. 使用 Adam 优化器编译网络,并在训练过程中计算准确度:

    classifier.compile(optimizer='adam', loss='binary_crossentropy', \
                       metrics=['accuracy'])
    
  10. 创建训练和测试数据生成器。将训练和测试图像按1/255缩放,以使所有值位于01之间。只为训练数据生成器设置以下参数——shear_range=0.2zoom_range=0.2,以及horizontal_flip=True

    from keras.preprocessing.image import ImageDataGenerator
    train_datagen = ImageDataGenerator(rescale = 1./255,
                                       shear_range = 0.2,
                                       zoom_range = 0.2,
                                       horizontal_flip = True)
    test_datagen = ImageDataGenerator(rescale = 1./255)
    
  11. training set文件夹创建训练集。../dataset/training_set是我们存放数据的文件夹。我们的 CNN 模型图像大小为 64x64,因此这里也应该传入相同的大小。batch_size是单个批次中的图像数量,为32class_mode为二进制,因为我们正在处理二分类问题:

    training_set = \
    train_datagen.flow_from_directory('../dataset/training_set',\
                                      target_size = (64, 64),\
                                      batch_size = 32,\
                                      class_mode = 'binary')
    
  12. 对测试集重复步骤 11,通过设置文件夹为测试图像所在位置,即'../dataset/test_set'

    test_set = \
    test_datagen.flow_from_directory('../dataset/test_set',\
                                     target_size = (64, 64),\
                                     batch_size = 32,\
                                     class_mode = 'binary')
    
  13. 最后,拟合数据。将steps_per_epoch设置为10000validation_steps设置为2500。以下步骤可能需要一些时间来执行:

    classifier.fit_generator(training_set,steps_per_epoch = 10000,\
                             epochs = 2,validation_data = test_set,\
                             validation_steps = 2500,shuffle=False)
    

    上述代码产生以下输出:

    Epoch 1/2
    10000/10000 [==============================] - 2241s 224ms/step - loss: 0.2339 - accuracy: 0.9005 - val_loss: 0.8059 - val_accuracy: 0.8737
    Epoch 2/2
    10000/10000 [==============================] - 2394s 239ms/step - loss: 0.0810 - accuracy: 0.9699 - val_loss: 0.6783 - val_accuracy: 0.8675
    

模型的准确率为86.75%,显然高于我们在上一个练习中构建的模型的准确率。这表明激活函数的重要性。仅仅将输出激活函数从 softmax 改为 sigmoid,就将准确率从46.91%提高到了86.75%

注意

要访问此特定部分的源代码,请参阅 packt.live/2ZD9nKM

您也可以在网上运行此示例:packt.live/3dPZiiQ

在下一个练习中,我们将尝试不同的优化器,并观察它如何影响模型的性能。

注意

在二分类问题中(在我们的案例中是汽车与花朵),通常更好的做法是将 sigmoid 作为输出的激活函数。

练习 7.03:将优化器从 Adam 更改为 SGD

在这个练习中,我们将再次修改模型,将优化器更改为SGD。通过这样做,我们可以比较与之前模型的准确度。请按照以下步骤完成本练习:

  1. 导入numpy库以及所需的 Keras 库和类:

    # Import the Libraries 
    from keras.models import Sequential
    from keras.layers import Conv2D, MaxPool2D, Flatten, Dense
    import numpy as np
    from tensorflow import random
    
  2. 现在,使用Sequential类初始化模型:

    # Initiate the classifier
    seed = 42
    np.random.seed(seed)
    random.set_seed(seed)
    classifier = Sequential()
    
  3. 添加CNN的第一层,将输入形状设置为(64, 64, 3),即每个图像的维度,并将激活函数设置为ReLU。然后,添加32个大小为(3, 3)的特征检测器。再添加两个相同特征检测器和相同大小的卷积层:

    classifier.add(Conv2D(32,(3,3),input_shape=(64,64,3),\
                   activation='relu'))
    classifier.add(Conv2D(32,(3,3),activation='relu'))
    classifier.add(Conv2D(32,(3,3),activation='relu'))
    
  4. 现在,添加池化层,图像大小为2x2

    classifier.add(MaxPool2D(pool_size=(2, 2)))
    
  5. 添加另一个Conv2D,参数与步骤 3中的相同,并添加一个池化层来补充它,使用与步骤 4中相同的参数:

    classifier.add(Conv2D(32, (3, 3), input_shape = (64, 64, 3), \
                   activation = 'relu'))
    classifier.add(MaxPool2D(pool_size=(2, 2)))
    
  6. 添加一个Flatten层以完成 CNN 架构:

    classifier.add(Flatten())
    
  7. 添加 ANN 的第一层Dense,大小为128。再向网络添加三个相同参数的Dense层:

    classifier.add(Dense(128,activation='relu')) 
    classifier.add(Dense(128,activation='relu'))
    classifier.add(Dense(128,activation='relu'))
    classifier.add(Dense(128,activation='relu'))
    
  8. 添加 ANN 的输出层。这是一个二分类问题,所以输出为1,激活函数为sigmoid

    classifier.add(Dense(1,activation='sigmoid')) 
    
  9. 使用SGD 优化器编译网络,并在训练过程中计算准确度:

    classifier.compile(optimizer='SGD', loss='binary_crossentropy', \
                       metrics=['accuracy'])
    
  10. 创建训练和测试数据生成器。通过1/255对训练和测试图像进行重新缩放,使得所有值都在01之间。仅为训练数据生成器设置以下参数:shear_range=0.2zoom_range=0.2,以及horizontal_flip=True

    from keras.preprocessing.image import ImageDataGenerator
    train_datagen = ImageDataGenerator(rescale = 1./255,\
                                       shear_range = 0.2,\
                                       zoom_range = 0.2,\
                                       horizontal_flip = True)
    test_datagen = ImageDataGenerator(rescale = 1./255)
    
  11. training set文件夹创建一个训练集。../dataset/training_set是我们存放数据的文件夹。我们的 CNN 模型图像大小为64x64,因此这里也应该传递相同的大小。batch_size是每批次的图像数量,即32class_mode是二分类模式,因为我们正在创建一个二分类器:

    training_set = \
    train_datagen.flow_from_directory('../dataset/training_set',\
                                      target_size = (64, 64),\
                                      batch_size = 32,\
                                      class_mode = 'binary')
    
  12. 对测试集重复步骤 11,将文件夹设置为测试图像所在的位置,即'../dataset/test_set'

    test_set = \
    test_datagen.flow_from_directory('../dataset/test_set',\
                                     target_size = (64, 64),\
                                     batch_size = 32,\
                                     class_mode = 'binary')
    
  13. 最后,拟合数据。将steps_per_epoch设置为10000validation_steps设置为2500。以下步骤可能需要一些时间来执行:

    classifier.fit_generator(training_set,steps_per_epoch = 10000,\
                             epochs = 2,validation_data = test_set,\
                             validation_steps = 2500,shuffle=False)
    

    上述代码会产生以下输出:

    Epoch 1/2
    10000/10000 [==============================] - 4376s 438ms/step - loss: 0.3920 - accuracy: 0.8201 - val_loss: 0.3937 - val_accuracy: 0.8531
    Epoch 2/2
    10000/10000 [==============================] - 5146s 515ms/step - loss: 0.2395 - accuracy: 0.8995 - val_loss: 0.4694 - val_accuracy: 0.8454
    

    准确率为84.54%,因为我们使用了多个ANNsSGD作为优化器。

    注意

    要访问此特定部分的源代码,请参阅packt.live/31Hu9vm

    您还可以在packt.live/3gqE9x8上在线运行这个示例。

到目前为止,我们已经尝试了模型的多种不同排列和组合。看起来,针对该数据集,获得最佳准确率的做法是:

  • 添加多个 CNN 层。

  • 添加多个 ANN 层。

  • 使用 sigmoid 激活函数。

  • 使用 Adam 作为优化器。

  • 将 epoch 大小增加到大约25(这需要大量的计算时间——确保您有 GPU 进行处理)。这将提高预测的准确性。

最后,我们将继续预测一个新的未知图像,将其传递给算法,并验证该图像是否被正确分类。在下一个练习中,我们将演示如何使用模型对新图像进行分类。

练习 7.04:分类新图像

在本练习中,我们将尝试对新图像进行分类。该图像尚未接触过算法,因此我们将使用此练习来测试我们的算法。您可以运行本章中的任何算法(尽管推荐使用准确率最高的那个),然后使用模型对图像进行分类。

注意

本练习中使用的图像可以在本书的 GitHub 仓库中找到,地址为packt.live/39tID2C

在我们开始之前,请确保您已经从本书的 GitHub 仓库下载了test_image_1并将其保存到您的工作目录中。这个练习是基于之前的练习,所以请确保您已经在工作区中准备好本章中的算法并可以运行。

完成本练习的步骤如下:

  1. 加载图像。'test_image_1.jpg'是测试图像的路径。请将路径更改为您在系统中保存数据集的位置。查看图像以验证它是什么:

    from keras.preprocessing import image
    new_image = image.load_img('../test_image_1.jpg', \
                               target_size = (64, 64))
    new_image
    
  2. 打印训练集的class_indices属性中的类别标签:

    training_set.class_indices
    
  3. 处理图像:

    new_image = image.img_to_array(new_image)
    new_image = np.expand_dims(new_image, axis = 0)
    
  4. 预测新图像:

    result = classifier.predict(new_image)
    
  5. prediction 方法将输出图像的10。为了将10映射到flower(花)或car(车),可以使用class_indices方法和if…else语句,示例如下:

    if result[0][0] == 1:
        prediction = 'It is a flower'
    else:
        prediction = 'It is a car'
    print(prediction)
    

    上述代码会产生以下输出:

    It is a car
    

    test_image_1 是一张汽车的图像(您可以通过查看图像确认),并且模型正确预测它是汽车。

在这个练习中,我们训练了我们的模型,然后给模型提供了一张汽车的图像。通过这样做,我们发现算法正确地对图像进行了分类。你可以使用相同的过程训练模型来处理任何类型的图像。例如,如果你使用肺部感染和健康肺部的扫描训练模型,那么模型将能够分类新扫描是否代表感染的肺部或健康的肺部。

注意

要访问此特定部分的源代码,请参阅 packt.live/31I6B9F

你也可以在 packt.live/2BzmEMx 上在线运行此示例。

在下一个活动中,我们将把我们的知识付诸实践,使用我们在 Exercise 7.04Classifying a New Image 中训练的模型。

活动 7.02:分类新图像

在这个活动中,你将尝试像我们在前一个练习中所做的那样分类另一张新图像。由于图像没有暴露给算法,因此我们将利用此活动来测试我们的算法。你可以运行本章中的任何一个算法(尽管最高准确率的算法更可取),然后使用模型来对你的图像进行分类。实施此活动的步骤如下:

  1. 运行本章中的任何一个算法。

  2. 从你的目录加载图像(test_image_2)。

  3. 使用算法处理图像。

  4. 预测新图像的主题。你可以查看图像本身以检查预测是否正确。

    注意

    这个活动中使用的图像可以在这本书的 GitHub 仓库中找到,链接地址为 packt.live/39tID2C

在开始之前,请确保已经将 test_image_2 从这本书的 GitHub 仓库下载到你自己的工作目录中。此活动直接跟进前面的练习,因此请确保已经准备好本章中的一个算法以在你的工作空间中运行。

实施这些步骤后,你应该得到以下预期输出:

It is a flower

注意

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

在此活动中,我们根据在验证数据集上的准确性修改了各种参数,包括优化器和输出层中的激活函数,从而训练了本章中最高性能的模型。我们对一个测试图像进行了分类,发现其分类是正确的。

摘要

在本章中,我们研究了为什么需要计算机视觉以及其工作原理。我们了解到为什么计算机视觉是机器学习中最热门的领域之一。然后,我们使用卷积神经网络进行了工作,了解了它们的架构,并探讨了如何在现实应用中构建 CNN。我们还通过增加更多的 ANN 和 CNN 层以及更改激活和优化器函数来尝试改进我们的算法。最后,我们尝试了不同的激活函数和损失函数。

最终,我们成功地通过该算法对新图像(如汽车和花卉)进行了分类。请记住,汽车和花卉的图像可以替换为任何其他图像,例如老虎和鹿,或带有和不带有肿瘤的大脑 MRI 扫描图像。任何二分类的计算机图像问题都可以采用相同的方法来解决。

在下一章,我们将学习一种更高效的计算机视觉技术,它不仅节省时间,而且更容易实现。接下来的章节将教我们如何微调预训练模型,以便应用于我们的实际需求,从而帮助创建更加准确、训练速度更快的模型。将要使用的模型是 VGG-16 和 ResNet50,它们是常用的预训练模型,广泛用于图像分类。

第八章:8. 迁移学习与预训练模型

概述

本章介绍了预训练模型的概念及其在不同应用中的使用,这种应用不同于它们最初训练时的目标,被称为迁移学习。到本章结束时,你将能够将特征提取应用于预训练模型,利用预训练模型进行图像分类,并通过微调预训练模型,将花卉和汽车图像分类到各自的类别中。我们将看到,这样做能够完成与上一章相同的任务,但准确度更高且训练时间更短。

介绍

在上一章中,我们学习了如何使用 Keras 从头创建卷积神经网络CNN)。我们通过添加更多卷积层和全连接层、修改激活函数,来实验不同的架构。我们通过将汽车和花卉的图像分类到各自类别,并比较它们的准确率,来评估每个模型的性能。

然而,在实际项目中,你几乎从不从头开始编码一个卷积神经网络。你总是根据需求对其进行调整和训练。本章将向你介绍迁移学习预训练网络(也称为预训练模型)这两个在行业中使用的重要概念。

我们将使用图像,并且不是从头开始构建 CNN,而是将这些图像与预训练模型匹配,尝试对其进行分类。我们还将调整模型,使其更加灵活。本章将使用的模型是VGG16ResNet50,我们将在本章后续部分讨论它们。在开始使用预训练模型之前,我们需要了解迁移学习。

预训练集与迁移学习

人类通过经验学习。我们将从一个情境中获得的知识应用到未来面对的类似情境中。假设你想学习如何驾驶 SUV,你从未驾驶过 SUV,你所知道的仅仅是如何驾驶一辆小型掀背车。

SUV 的尺寸比掀背车大得多,因此在交通中驾驶 SUV 肯定是一个挑战。不过,一些基本系统(如离合器、油门和刹车)与掀背车相似。所以,学习驾驶 SUV 时,掌握了如何驾驶掀背车的技能肯定会对你大有帮助。在学习驾驶大型 SUV 时,所有你在驾驶掀背车时获得的知识都可以派上用场。

这正是迁移学习的概念。迁移学习在机器学习中是指在学习一个类似的活动时,我们存储并使用在另一个活动中获得的知识。从掀背车到 SUV 的学习模式完全符合这一定义。

假设我们想知道一张照片是狗还是猫;在这种情况下,我们可以有两种方法。其一是从头开始构建一个深度学习模型,然后将新的图片传递给网络。另一种选择是使用一个已经通过猫狗图片构建好的预训练深度学习神经网络模型,而不是从零开始创建神经网络。

使用预训练模型可以节省计算时间和资源。使用预训练网络可能会带来一些意想不到的好处。例如,几乎所有的狗和猫的照片中都会包含一些其他物体,如树木、天空和家具。我们甚至可以使用这个预训练网络来识别树木、天空和家具等物体。

因此,预训练网络是一个保存的网络(在深度学习的情况下是神经网络),它是在一个非常大的数据集上训练的,主要用于图像分类问题。要使用预训练网络,我们需要理解特征提取和微调的概念。

特征提取

要理解特征提取,我们需要回顾卷积神经网络的架构。

你可能还记得,CNN的完整架构在高层次上由以下几个部分组成:

  • 一个卷积层

  • 一个池化和扁平化层

  • 一个人工神经网络ANN

下图展示了完整的 CNN 架构:

图 8.1: CNN 架构

图 8.1: CNN 架构

现在,让我们将这个架构分为两个部分。第一部分包含除ANN之外的所有内容,而第二部分仅包含ANN。下图展示了一个拆分的CNN架构:

图 8.2: CNN 拆分架构 - 卷积基础层和分类器

图 8.2: CNN 拆分架构 - 卷积基础层和分类器

第一部分称为卷积基础层,而第二部分称为分类器

在特征提取中,我们不断重用卷积基础层,而分类器则被替换。所以,我们保留了卷积层的学习成果,并可以将不同的分类器添加到卷积层之上。分类器可以是狗与猫、摩托车与汽车,甚至是医学 X 光图像来分类肿瘤、感染等。下图展示了一些用于不同分类器的卷积基础层:

图 8.3: 可重用的卷积基础层

图 8.3: 可重用的卷积基础层

显而易见的下一个问题是,难道我们不能像基础层那样重用分类器吗?一般来说,答案是否定的。原因在于,从卷积基础部分学习可能更为通用,因此更具可重用性。然而,分类器的学习通常是特定于模型训练时所用的类别。因此,建议仅重用卷积基础层,而不是分类器。

从卷积基础层获得的泛化学习量取决于该层的深度。例如,在猫的情况下,模型的初始层学习一般的特征,如边缘和背景,而更高层可能学习更具体的细节,如眼睛、耳朵或鼻子的形状。因此,如果你的新数据集与原始数据集非常不同——例如,如果你想识别水果而不是猫——那么最好只使用卷积基础层的一些初始层,而不是使用整个层。

CNN),网络顶部的许多全连接层是随机初始化的,并且可能由于反向传播的原因,网络的初始层学习会被完全破坏。

为了避免信息衰减,我们冻结了一些层。这是通过将这些层设为不可训练来实现的。冻结一些层并训练其他层的过程称为微调网络。

微调预训练网络

微调意味着调整我们的神经网络,使其更适应当前的任务。我们可以冻结网络的一些初始层,这样就不会丢失存储在这些层中的信息。这些信息是通用且有用的。然而,如果我们可以在分类器学习的同时冻结这些层,然后再解冻它们,我们可以稍微调整它们,使其更好地适应当前的问题。假设我们有一个预训练网络,可以识别动物。如果我们想要识别特定的动物,比如狗和猫,我们可以稍微调整这些层,让它们学习狗和猫的外观。这就像是使用整个预训练网络,然后在其上添加一个新层,其中包含狗和猫的图像。我们将通过使用一个预构建的网络并在其上添加一个分类器来做类似的活动,分类器将基于狗和猫的图片进行训练。

有一个三步法则来进行微调:

  1. 在预训练系统的顶部添加一个分类器(ANN)。

  2. 冻结 卷积基础 并训练网络。

  3. 联合训练已添加的分类器和未冻结的卷积基础部分。

ImageNet 数据集

在实际工作经验中,你几乎永远不需要自己构建基础卷积模型。你总是会使用预训练模型。但数据从哪里来呢?对于视觉计算,答案是 ImageNet。ImageNet 数据集是一个大型视觉数据库,用于视觉对象识别。它包含超过 1400 万张标注图像及其对象名称。ImageNet 包含超过 20,000 个类别。

Keras 中的一些预训练网络

以下预训练网络可以被视为基础卷积层。你可以使用这些网络并为其拟合一个分类器(ANN):

  • VGG16

  • Inception V3

  • Xception

  • ResNet50

  • MobileNet

不同的供应商创建了前面的预训练网络。例如,ResNet50是由Microsoft创建的,而Inception V3MobileNet是由Google创建的。在本章中,我们将使用VGG16ResNet50模型。

VGG16是一个具有 16 层的卷积神经网络模型,由牛津大学的 K. Simonyan 和 A. Zisserman 提出。该模型于 2014 年提交到ImageNet 大规模视觉识别挑战赛ILSVRC)——这是一个用于测试最新技术的模型的挑战,模型使用了ImageNet数据集。ResNet50是另一种在ImageNet数据集上训练的卷积神经网络,具有 50 层,并在 2015 年的ILSVRC中获得了第一名。

现在我们了解了这些网络的原理,我们将练习利用这些预训练的神经网络,使用VGG16模型来分类一张披萨片的图像。

注意

本章中的所有练习和活动都将在 Jupyter 笔记本中开发。请从packt.live/2uI63CC下载本书的 GitHub 仓库以及所有准备好的模板。

练习 8.01:使用 VGG16 网络识别图像

我们有一张披萨片的图片。我们将使用VGG16网络来处理并识别这张图像。在完成以下步骤之前,请确保从 GitHub 下载了pizza图像并将其保存到你的工作目录中:

  1. 导入库:

    import numpy as np
    from keras.applications.vgg16 import VGG16
    from keras.preprocessing import image
    from keras.applications.vgg16 import preprocess_input
    
  2. 初始化模型(这可能需要一些时间):

    classifier = VGG16()
    

    注意

    预测的最后一层(Dense)有 1000 个值。这意味着VGG16总共有 1000 个标签,我们的图像将是这 1000 个标签中的一个。

  3. 加载图像。'../Data/Prediction/pizza.jpg.jpg'是我们系统中图像的路径;在你的系统中可能会有所不同:

    new_image= image.load_img('../Data/Prediction/pizza.jpg', \
                              target_size=(224, 224))
    new_image
    

    下图显示了前面代码的输出:

    图 8.4:披萨片的图像

    图 8.4:披萨片的图像

    目标大小应该是224x224,因为VGG16仅接受(224,224)大小。

  4. 使用img_to_array函数将图像转换为数组:

    transformed_image = image.img_to_array(new_image)
    transformed_image.shape
    

    前面的代码产生了以下输出:

    (224, 224, 3)
    
  5. 图像必须是四维形式,以便VGG16能够进行进一步处理。扩展图像的维度,如下所示:

    transformed_image = np.expand_dims(transformed_image, axis=0)
    transformed_image.shape
    

    前面的代码产生了以下输出:

    (1, 224, 224, 3)
    
  6. 使用preprocess_input函数对图像进行预处理:

    transformed_image = preprocess_input(transformed_image)
    transformed_image
    

    下图显示了前面代码的输出:

    图 8.5:图像预处理的屏幕截图

    图 8.5:图像预处理的屏幕截图

  7. 创建predictor变量:

    y_pred = classifier.predict(transformed_image)
    y_pred
    
  8. 检查图像的形状。它应该是(1,1000)。1000是因为ImageNet数据库有1000个类别的图像。预测变量显示了我们图像属于这些图像之一的概率:

    y_pred.shape
    

    前面的代码产生了以下输出:

    (1, 1000)
    
  9. 使用decode_predictions函数打印出我们的图像是什么的前五个概率,并传递预测变量y_pred的函数和预测数量及相应的标签输出:

    from keras.applications.vgg16 import decode_predictions
    decode_predictions(y_pred,top=5)
    

    前述代码生成以下输出:

    [[('n07873807', 'pizza', 0.97680503),
      ('n07871810', 'meat_loaf', 0.012848727),
      ('n07880968', 'burrito', 0.0019428912),
      ('n04270147', 'spatula', 0.0019108421),
      ('n03887697', 'paper_towel', 0.0009799759)]]
    

    数组的第一列是内部代码编号。第二列是可能的标签,第三列是图片为该标签的概率。

  10. 将预测结果以人类可读的形式呈现。从decode_predictions函数的输出中打印出最有可能的标签:

    label = decode_predictions(y_pred)
    """
    Most likely result is retrieved, for example, the highest probability
    """
    decoded_label = label[0][0]
    # The classification is printed 
    print('%s (%.2f%%)' % (decoded_label[1], \
          decoded_label[2]*100 ))
    

    前述代码生成以下输出:

    pizza (97.68%)
    

在这个练习中,我们预测了一个图片,显示(以97.68%的概率)这张图片是披萨。显然,更高的准确率意味着我们的图片在 ImageNet 数据库中存在一个相对相似的对象,并且我们的算法成功识别了这张图片。

注意

要访问本节的源代码,请参阅packt.live/3dXqdsQ

您还可以在packt.live/3dZMZAq上在线运行这个示例。

在接下来的活动中,我们将通过使用VGG16网络来对不在 ImageNet 数据库中的图片进行分类,将我们的知识付诸实践。

活动 8.01:使用 VGG16 网络训练深度学习网络以识别图像

你有一张摩托车的图片。使用VGG16网络来预测这张图片。在开始之前,请确保已将图片(test_image_1)下载到你的工作目录中。要完成这个活动,请按照以下步骤操作:

  1. 导入必要的库,包括VGG16网络。

  2. 初始化预训练的VGG16模型。

  3. 加载将要分类的图片。

  4. 通过应用转换来预处理图片。

  5. 创建一个预测变量来预测图像。

  6. 给图片贴上标签并进行分类。

    注意

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

通过这些步骤,我们完成了这个活动。与第七章使用卷积神经网络进行计算机视觉不同,我们没有从头开始构建CNN。相反,我们使用了一个预训练的模型。我们刚刚上传了一张需要分类的图片。从中可以看出,以84.33%的准确率预测它是一辆踏板车。在下一个练习中,我们将使用 ImageNet 数据库中没有匹配图片的图片。

练习 8.02:对不在 ImageNet 数据库中的图片进行分类

现在,让我们处理一张不在VGG16网络的1000个标签中的图像。在这个练习中,我们将处理一张竹节虫的图片,而我们的预训练网络中没有竹节虫的标签。让我们看看我们得到什么结果:

  1. 导入numpy库和必要的Keras库:

    import numpy as np
    from keras.applications.vgg16 import VGG16
    from keras.preprocessing import image
    from keras.applications.vgg16 import preprocess_input
    
  2. 初始化模型并打印模型的摘要:

    classifier = VGG16()
    classifier.summary()
    

    classifier.summary()显示了网络的架构。以下是需要注意的点 - 它具有四维输入形状(None, 224, 224, 3),并且具有三个卷积层。以下图显示了输出的最后四层:

    图 8.6:使用 VGG16 分类器对图像的摘要

    图 8.6:使用 VGG16 分类器对图像的摘要

    注意

    预测的最后一层(Dense)有1000个值。这意味着VGG161000个标签,而我们的图像将是这些标签中的一个。

  3. 加载图像。'../Data/Prediction/stick_insect.jpg'是我们系统上的图像路径。在您的系统上可能会有所不同:

    new_image = \
    image.load_img('../Data/Prediction/stick_insect.jpg', \
                   target_size=(224, 224))
    new_image
    

    以下图显示了前面代码的输出:

    图 8.7:用于预测的示例竹节虫图像

    图 8.7:用于预测的示例竹节虫图像

    目标大小应为224x224,因为VGG16仅接受(224,224)。

  4. 使用img_to_array函数将图像转换为数组:

    transformed_image = image.img_to_array(new_image)
    transformed_image.shape
    
  5. 图像必须以四维形式存在,以便VGG16允许进一步处理。使用expand_dims函数沿着第 0 轴扩展图像的维度:

    transformed_image = np.expand_dims(transformed_image, axis=0)
    transformed_image.shape
    
  6. 使用preprocess_input函数预处理图像:

    transformed_image = preprocess_input(transformed_image)
    transformed_image
    

    以下图显示了前面代码的输出:

    图 8.8:显示图像预处理的几个实例

    图 8.8:显示图像预处理的几个实例

  7. 创建predictor变量:

    y_pred = classifier.predict(transformed_image)
    y_pred
    

    以下图显示了前面代码的输出:

    图 8.9:创建预测变量

    图 8.9:创建预测变量

  8. 检查图像的形状。它应该是(1,1000)。这是因为,正如我们之前提到的,ImageNet 数据库有1000个图像类别。预测变量显示了我们的图像属于这些图像之一的概率:

    y_pred.shape
    

    前面的代码产生了以下代码:

    (1, 1000)
    
  9. 选择出VGG16网络具有的1000个标签中,我们图像标签的前五个最高概率:

    from keras.applications.vgg16 import decode_predictions
    decode_predictions(y_pred, top=5)
    

    前面的代码产生了以下代码:

    [[('n02231487', 'walking_stick', 0.30524516),
      ('n01775062', 'wolf_spider', 0.26035702),
      ('n03804744', 'nail', 0.14323168),
      ('n01770081', 'harvestman', 0.066652186),
      ('n01773549', 'barn_spider', 0.03670299)]]
    

    数组的第一列是内部代码编号。第二列是标签,而第三列是图像成为该标签的概率。

  10. 将预测结果以人类可读的格式呈现出来。从decode_predictions函数的输出中打印出最可能的标签:

    label = decode_predictions(y_pred)
    """
    Most likely result is retrieved, for example, the highest probability
    """
    decoded_label = label[0][0]
    # The classification is printed
    print('%s (%.2f%%)' % (decoded_label[1], decoded_label[2]*100 ))
    

    前面的代码产生了以下代码:

    walking_stick (30.52%)
    

    在这里,您可以看到网络预测我们的图像是竹节虫,准确率为30.52%。显然,这张图像不是竹节虫,而是一只竹节虫;在VGG16网络包含的所有标签中,竹节虫是最接近的事物。以下图片展示了一只竹节虫:

    图 8.10:竹节虫

图 8.10:竹节虫

为了避免这种输出,我们可以冻结VGG16现有的层,并添加我们自己的层。我们还可以添加一个包含拐杖和竹节虫图像的层,以便我们能够获得更好的输出。

如果你有大量的拐杖和竹节虫的图像,你可以进行类似的操作来提升模型在分类这些图像时的能力。然后你可以通过重新运行之前的练习来测试它。

注意

要访问此特定部分的源代码,请参见 packt.live/31I7bnR

你也可以通过在线运行这个例子,访问 packt.live/31Hv1QE

为了更详细地理解这一点,我们来看一个不同的例子,在这个例子中,我们冻结网络的最后一层,并添加自己的层,包含汽车和花卉的图像。这将帮助网络提高分类汽车和花卉图像的准确性。

练习 8.03:微调 VGG16 模型

让我们来进行VGG16模型的微调。在这个练习中,我们将冻结网络并移除VGG16的最后一层,该层包含1000个标签。移除最后一层后,我们将建立一个新的花卉-汽车分类器ANN,就像我们在第七章《卷积神经网络与计算机视觉》中做的那样,并将这个ANN连接到VGG16,而不是原始的具有1000个标签的模型。基本上,我们所做的就是用用户自定义的层替换VGG16的最后一层。

在开始之前,请确保你已经从本书的 GitHub 仓库下载了图像数据集到自己的工作目录。你将需要一个training_set文件夹和一个test_set文件夹来测试你的模型。每个文件夹里都会包含一个cars文件夹,里面是汽车图像,还有一个flowers文件夹,里面是花卉图像。

完成此练习的步骤如下:

注意

与原始的新模型(具有1000个标签,涵盖100个不同的物体类别)不同,这个新的微调模型仅包含花卉或汽车的图像。因此,不管你向模型输入什么图像,它都会根据预测概率将其分类为花卉或汽车。

  1. 导入numpy库、TensorFlow 的random库以及所需的Keras库:

    import numpy as np
    import keras
    from keras.layers import Dense
    from tensorflow import random
    
  2. 启动VGG16模型:

    vgg_model = keras.applications.vgg16.VGG16()
    
  3. 检查模型summary

    vgg_model.summary()
    

    下图展示了前述代码的输出:

    图 8.11:启动模型后的模型总结

    图 8.11:启动模型后的模型总结

  4. 从模型总结中移除最后一层,即前述图像中的labeled predictions。创建一个新的 Keras 顺序模型,并遍历 VGG 模型的所有层。将所有层添加到新模型中,除了最后一层:

    last_layer = str(vgg_model.layers[-1])
    np.random.seed(42)
    random.set_seed(42)
    classifier= keras.Sequential()
    for layer in vgg_model.layers:
        if str(layer) != last_layer:
            classifier.add(layer)
    

    在这里,我们创建了一个新的模型分类器名称,而不是vgg_model。所有层,除了最后一层,即vgg_model,都已经包含在分类器中。

  5. 打印新创建模型的summary

    classifier.summary()
    

    以下图显示了上述代码的输出:

    图 8.12:移除最后一层后重新检查摘要

    图 8.12:移除最后一层后重新检查摘要

    最后一层的预测层(Dense)已被删除。

  6. 通过遍历各层并将trainable参数设置为False来冻结这些层:

    for layer in classifier.layers:
        layer.trainable=False
    
  7. 添加一个大小为1的新输出层,使用sigmoid激活函数并打印模型摘要:

    classifier.add(Dense(1, activation='sigmoid'))
    classifier.summary()
    

    以下函数显示了上述代码的输出:

    图 8.13:添加新层后重新检查摘要

    图 8.13:添加新层后重新检查摘要

    现在,最后一层是新创建的用户定义层。

  8. 使用adam优化器和二元交叉熵损失函数来编译网络,并在训练过程中计算accuracy

    classifier.compile(optimizer='adam', loss='binary_crossentropy', \
                       metrics=['accuracy'])
    

    创建一些训练和测试数据生成器,就像我们在第七章使用卷积神经网络进行计算机视觉中所做的那样。将训练和测试图像重新缩放为1/255,确保所有值都在01之间。仅为训练数据生成器设置以下参数:shear_range=0.2zoom_range=0.2horizontal_flip=True

  9. 接下来,从training set文件夹中创建训练集。../Data/dataset/training_set是我们存放数据的文件夹。我们的 CNN 模型的图像大小为224x224,所以这里也应该传入相同的大小。batch_size是每个批次中的图像数量,即32class_mode是二进制的,因为我们正在创建一个二分类器。

    from keras.preprocessing.image import ImageDataGenerator
    generate_train_data = \
    ImageDataGenerator(rescale = 1./255,\
                       shear_range = 0.2,\
                       zoom_range = 0.2,\
                       horizontal_flip = True)
    generate_test_data = ImageDataGenerator(rescale =1./255)
    training_dataset = \
    generate_train_data.flow_from_directory(\
        '../Data/Dataset/training_set',\
        target_size = (224, 224),\
        batch_size = 32,\
        class_mode = 'binary')
    test_datasetset = \
    generate_test_data.flow_from_directory(\
        '../Data/Dataset/test_set',\
        target_size = (224, 224),\
        batch_size = 32,\
        class_mode = 'binary')
    classifier.fit_generator(training_dataset,\
                             steps_per_epoch = 100,\
                             epochs = 10,\
                             validation_data = test_datasetset,\
                             validation_steps = 30,\
                             shuffle=False)
    

    这里有 100 张训练图像,所以设置steps_per_epoch = 100,设置validation_steps=30,并设置shuffle=False

    100/100 [==============================] - 2083s 21s/step - loss: 0.5513 - acc: 0.7112 - val_loss: 0.3352 - val_acc: 0.8539
    
  10. 预测新图像(代码与第七章使用卷积神经网络进行计算机视觉中的相同)。首先,从'../Data/Prediction/test_image_2.jpg'加载图像,并将目标大小设置为(224, 224),因为VGG16模型接受该大小的图像。

    from keras.preprocessing import image
    new_image = \
    image.load_img('../Data/Prediction/test_image_2.jpg', \
                   target_size = (224, 224))
    new_image
    

    此时,你可以通过执行代码new_image来查看图像,通过运行training_dataset.class_indices来查看类标签。

    接下来,先通过img_to_array函数将图像转换为数组,再使用expand_dims函数沿着第 0 轴添加一个新的维度。最后,使用分类器的predict方法进行预测,并以人类可读格式打印输出:

    new_image = image.img_to_array(new_image)
    new_image = np.expand_dims(new_image, axis = 0)
    result = classifier.predict(new_image)
    if result[0][0] == 1:
        prediction = 'It is a flower'
    else:
        prediction = 'It is a car'
    print(prediction)
    

    上述代码生成了以下输出:

    It is a car
    
  11. 最后一步,你可以通过运行classifier.save('car-flower-classifier.h5')来保存分类器。

在这里,我们可以看到算法通过识别汽车图像完成了正确的图像分类。我们仅使用了一个预先构建的VGG16模型,通过调整其层并根据我们的需求进行定制,完成了图像分类。这是一种非常强大的图像分类技术。

注意

若要访问此特定部分的源代码,请参考packt.live/2ZxCqzA

本节目前没有在线交互示例,需要在本地运行。

在下一个练习中,我们将使用另一个预训练模型 ResNet50,并展示如何使用该模型进行图像分类。

练习 8.04:使用 ResNet 进行图像分类

最后,在结束本章之前,让我们来做一个关于 ResNet50 网络的练习。我们将使用一张纳斯卡赛车手的图像并尝试通过网络预测。按照以下步骤完成该练习:

  1. 导入必要的库:

    import numpy as np
    from keras.applications.resnet50 import ResNet50, preprocess_input
    from keras.preprocessing import image 
    
  2. 启动 ResNet50 模型并打印该模型的 summary

    classifier = ResNet50()
    classifier.summary()
    

    以下图表显示了前面代码的输出:

    图 8.14:模型的总结

    图 8.14:模型的总结

  3. 加载图像。'../Data/Prediction/test_image_3.jpg' 是我们系统中图像的路径。您系统中的路径会有所不同:

    new_image = \
    image.load_img('../Data/Prediction/test_image_3.jpg', \
                   target_size=(224, 224))
    new_image
    

    以下图表显示了前面代码的输出:

    图 8.15:用于预测的纳斯卡赛车手图像示例

    图 8.15:用于预测的纳斯卡赛车手图像示例

    请注意,目标大小应为 224x224,因为 ResNet50 仅接受 (224,224) 的输入。

  4. 使用 img_to_array 函数将图像转换为数组:

    transformed_image = image.img_to_array(new_image)
    transformed_image.shape
    
  5. 为了让 ResNet50 进行进一步处理,图像必须是四维形式。使用 expand_dims 函数沿着第 0 轴扩展维度:

    transformed_image = np.expand_dims(transformed_image, axis=0)
    transformed_image.shape
    
  6. 使用 preprocess_input 函数预处理图像:

    transformed_image = preprocess_input(transformed_image)
    transformed_image
    
  7. 使用分类器的 predict 方法创建预测变量,通过该方法预测图像:

    y_pred = classifier.predict(transformed_image)
    y_pred
    
  8. 检查图像的形状。它应该是 (1,1000):

    y_pred.shape
    

    前面的代码会产生以下输出:

    (1, 1000)
    
  9. 使用 decode_predictions 函数选择图像的前五个概率,并通过传递预测变量 y_pred 作为参数,得到前几个预测和相应的标签:

    from keras.applications.resnet50 import decode_predictions
    decode_predictions(y_pred, top=5)
    

    前面的代码会产生以下输出:

    [[('n04037443', 'racer', 0.8013074),
      ('n04285008', 'sports_car', 0.06431753),
      ('n02974003', 'car_wheel', 0.024077434),
      ('n02504013', 'Indian_elephant', 0.019822922),
      ('n04461696', 'tow_truck', 0.007778575)]]
    

    数组的第一列是内部代码编号,第二列是标签,第三列是图像为该标签的概率。

  10. 将预测结果以人类可读的格式输出。从 decode_predictions 函数的结果中打印最可能的标签:

    label = decode_predictions(y_pred)
    """
    Most likely result is retrieved, for example, the highest probability
    """
    decoded_label = label[0][0]
    # The classification is printed
    print('%s (%.2f%%)' % (decoded_label[1], \
          decoded_label[2]*100 ))
    

    前面的代码会产生以下输出:

    racer (80.13%)
    

在这里,模型明确显示(概率为80.13%)图片是赛车手的照片。这就是预训练模型的强大之处,Keras 使我们可以灵活地使用和调整这些模型。

注意

若要访问此特定部分的源代码,请参考packt.live/2BzvTMK

您也可以在packt.live/3eWelJh 上在线运行此示例。

在下一个活动中,我们将使用预训练的 ResNet50 模型分类另一张图像。

活动 8.02:使用 ResNet 进行图像分类

现在,让我们进行一个使用另一个预训练网络(ResNet)的活动。我们有一张电视图像,位于../Data/Prediction/test_image_4。我们将使用ResNet50网络来预测这张图像。要实现该活动,请按以下步骤操作:

  1. 导入所需的库。

  2. 启动ResNet模型。

  3. 加载需要分类的图像。

  4. 通过应用适当的转换来预处理图像。

  5. 创建一个预测变量来预测图像。

  6. 给图像贴上标签并进行分类。

    注意

    本活动的解决方案可以在第 448 页找到。

因此,网络以接近100%的准确率判断这张图像是电视的图像。这次,我们使用了ResNet50预训练模型来对电视图像进行分类,获得了与使用VGG16模型预测披萨切片图像时相似的结果。

总结

在本章中,我们讲解了迁移学习的概念以及它与预训练网络的关系。我们通过使用预训练的深度学习网络VGG16ResNet50来预测各种图像,应用了这些知识。我们练习了如何利用预训练网络,通过特征提取和微调等技术,加速模型训练并提高准确性。最后,我们学习了通过调整现有模型并使其根据我们的数据集工作这一强大技术。构建自己的ANN基于现有的CNN,是业界最强大的技术之一。

在下一章,我们将通过研究一些 Google Assistant 的真实案例来学习顺序建模和顺序记忆。此外,我们还将学习顺序建模与循环神经网络RNN)的关系。我们将详细了解消失梯度问题,并学习如何使用LSTM比简单的RNN更好地克服消失梯度问题。我们将把所学应用于时间序列问题,通过预测股票趋势,得出相当准确的结果。

第九章:9. 使用循环神经网络进行序列建模

概述

本章将介绍序列建模——创建模型来预测序列中的下一个值或一系列值。在本章结束时,你将能够构建序列模型,解释RNNLSTM架构,并预测 Alphabet 和 Amazon 未来股价的值。

介绍

在上一章,我们学习了预训练网络以及如何通过迁移学习将它们应用于我们自己的应用。我们实验了VGG16ResNet50,这两种用于图像分类的预训练网络,并利用它们对新图像进行分类,并为我们的应用进行微调。通过利用预训练网络,我们能够比在之前的章节中训练的卷积神经网络更快地训练出更准确的模型。

在传统神经网络(以及前几章中介绍的所有神经网络架构)中,数据从输入层开始,顺序通过网络,经过隐藏层(如果有的话),直到输出层。信息只通过一次网络,输出被认为是相互独立的,只依赖于模型的输入。然而,在某些情况下,特定的输出依赖于系统之前的输出。

以公司的股价为例:某一天结束时的输出与前一天的输出相关。类似地,在自然语言处理NLP)中,为了使句子语法正确,句子中的最后几个词依赖于前面出现的词。如果要使句子合乎语法,最终的词语往往与前面的词语高度相关。NLP 是序列建模的一个特殊应用,在该应用中,处理和分析的数据集是自然语言数据。为了解决这些类型的问题,使用了一种特殊类型的神经网络——循环神经网络RNN)。RNN 能够记住之前的输出,并用于处理这类问题。

本章介绍并探讨了 RNN 的概念和应用。还解释了 RNN 如何与标准的前馈神经网络不同。你还将理解梯度消失问题以及长短期记忆LSTM)网络。本章还将介绍序列数据及其处理方式。我们将通过使用股市数据进行股价预测来学习这些概念。

序列记忆与序列建模

如果我们分析Alphabet过去 6 个月的股价,如下图所示,我们可以看到一个趋势。为了预测或预报未来的股价,我们需要理解这个趋势,然后在进行数学计算时牢记这一趋势:

图 9.1:Alphabet 过去 6 个月的股价

图 9.1:Alphabet 过去 6 个月的股价

这一趋势与顺序记忆和顺序建模密切相关。如果你有一个可以记住之前输出并基于这些输出预测下一个输出的模型,我们说这个模型具有顺序记忆。

处理这种顺序记忆的建模过程被称为 顺序建模。这不仅适用于股市数据,也适用于自然语言处理(NLP)应用;我们将在下一节研究 RNNs 时看到一个这样的例子。

循环神经网络(RNNs)

RNNs 是一种基于顺序记忆概念构建的神经网络。与传统神经网络不同,RNN 预测顺序数据中的结果。目前,RNN 是处理顺序数据的最强大技术。

如果你有一部能使用 Google Assistant 的智能手机,试着打开它并问:“联合国是什么时候成立的?” 答案将在以下截图中显示:

图 9.2:Google Assistant 的输出

图 9.2:Google Assistant 的输出

现在,问第二个问题:“为什么它被成立?”,如下:

图 9.3:Google Assistant 的上下文输出

图 9.3:Google Assistant 的上下文输出

现在,问第三个问题:“它的总部在哪里?”,你应该得到如下答案:

图 9.4:Google Assistant 的输出

图 9.4:Google Assistant 的输出

这里有一个有趣的事情值得注意,我们在第一个问题中只提到了“联合国”。在第二和第三个问题中,我们分别问了助手 为什么它被成立总部在哪里。Google Assistant 理解到,由于前一个问题是关于联合国的,接下来的问题也与联合国相关。这对于机器来说并不是一件简单的事情。

机器能够显示预期结果,因为它已经以序列的形式处理了数据。机器明白当前的问题与前一个问题有关,因此本质上,它记住了之前的问题。

让我们考虑另一个简单的例子。假设我们想要预测以下序列中的下一个数字:789?。我们希望下一个输出是 9 + 1。另外,如果我们提供序列 369?,我们希望得到 9 + 3 作为输出。虽然在这两种情况下,最后一个数字都是 9,但是预测结果应该是不同的(也就是说,当我们考虑到前一个值的上下文信息,而不仅仅是最后一个值时)。这里的关键是记住从前一个值中获取的上下文信息。

在高层次上,这些能够记住先前状态的网络被称为递归网络。为了完全理解RNN,我们先回顾一下传统的神经网络,也就是前馈神经网络。这是一种神经网络,其中神经网络的连接不形成循环;也就是说,数据仅沿一个方向流动,如下图所示:

图 9.5:一个前馈神经网络

图 9.5:一个前馈神经网络

在一个前馈神经网络中,如上图所示,输入层(左侧的绿色圆圈)接收数据并将其传递给隐藏层(带权重的蓝色圆圈)。然后,隐藏层的数据传递给输出层(右侧的红色圆圈)。根据阈值,数据会进行反向传播,但在隐藏层中没有数据的循环流动。

RNN中,网络的隐藏层允许数据和信息的循环。如下图所示,结构类似于前馈神经网络;然而,在这里,数据和信息也在循环流动:

图 9.6:一个 RNN

图 9.6:一个 RNN

在这里,RNN的定义特性是隐藏层不仅给出输出,而且还将输出的信息反馈到自身。在深入了解 RNN 之前,我们先讨论一下为什么我们需要RNN,以及为什么卷积神经网络CNN)或普通的人工神经网络ANN)在处理序列数据时存在不足。假设我们正在使用CNN来识别图像;首先,我们输入一张狗的图片,CNN会将该图像标记为“狗”。然后,我们输入一张芒果的图片,CNN会将该图像标记为“芒果”。我们假设在时间t输入狗的图片,如下所示:

图 9.7:一个使用 CNN 的狗的图像

图 9.7:一个使用 CNN 的狗的图像

现在,我们输入时间t + 1的芒果图像,如下所示:

图 9.8:一个使用 CNN 的芒果图像

图 9.8:一个使用 CNN 的芒果图像

在这里,你可以清楚地看到,时间t的狗图像输出和时间t + 1的芒果图像输出是完全独立的。因此,我们不需要我们的算法记住先前的输出实例。然而,正如我们在 Google 助手的例子中提到的,当我们问“联合国何时成立”和“为什么成立”时,算法必须记住先前实例的输出,才能处理序列数据。CNNANN无法做到这一点,因此我们需要使用RNN

RNN中,我们可以在多个时间点上得到多个输出。下面的图示表示一个RNN的示意图。它展示了网络从时间t – 1到时间t + n的状态:

图 9.9:在不同时间戳下展开的 RNN

图 9.9:在不同时间戳下展开的 RNN

在训练RNN时,你可能会遇到一些与RNN独特架构相关的问题。这些问题涉及梯度的值,因为随着RNN深度的增加,梯度可能会消失或爆炸,正如我们将在下一节中学习的那样。

消失梯度问题

如果有人问你:“你昨晚吃了什么?”你可能很容易记住并正确回答他们。现在,如果有人问你:“过去 30 天你吃了什么?”你可能能记住过去 3 到 4 天的菜单,但之前的菜单就很难记得清楚了。能够回忆过去的信息是消失梯度问题的基础,我们将在本节中研究这个问题。简而言之,消失梯度问题指的是在一段时间内丢失或衰减的信息。

下面的图示表示RNN在不同时间点t的状态。顶部的点(红色)表示输出层,中间的点(蓝色)表示隐藏层,底部的点(绿色)表示输入层:

图 9.10:信息随时间衰减

图 9.10:信息随时间衰减

如果你处在t + 10时刻,你很难记得 10 天前(即时间t)的晚餐菜单。此外,如果你处在t + 100时刻,你很可能根本记不起 100 天前的晚餐菜单,假设你做的晚餐没有规律可循。在机器学习的上下文中,消失梯度问题是在使用基于梯度的学习方法和反向传播训练 ANN 时遇到的一个困难。让我们回顾一下神经网络是如何工作的,如下所示:

  1. 首先,我们用随机的权重和偏置值初始化网络。

  2. 我们得到一个预测输出;这个输出与实际输出进行比较,二者的差异被称为代价。

  3. 训练过程利用梯度,它衡量的是代价相对于权重或偏置变化的速率。

  4. 然后,我们通过在训练过程中反复调整权重和偏置,尽可能降低代价,直到获得最低的可能值。

例如,如果你把一个球放在陡坡上,球会迅速滚动;然而,如果你把球放在平坦的表面上,它会滚得很慢,甚至不滚动。类似地,在深度神经网络中,当梯度较大时,模型学习得很快。然而,如果梯度较小,模型的学习速度就会变得非常慢。记住,在任何时刻,梯度是到该点为止所有梯度的乘积(即遵循微积分链式法则)。

此外,梯度通常是一个介于 01 之间的小数,而两个介于 01 之间的数字相乘会得到一个更小的数字。你的网络越深,网络初始层的梯度就越小。在某些情况下,它会变得非常小,以至于网络根本无法进行训练;这就是梯度消失问题。下图展示了遵循微积分链式法则的梯度:

图 9.11:带有成本 C 和微积分链式法则的梯度消失

图 9.11:带有成本 C 和微积分链式法则的梯度消失

参见图 9.10,假设我们处于 t + 10 时刻,我们得到一个输出,这个输出将反向传播到 t,即 10 步之遥。现在,当权重被更新时,会有 10 个梯度(它们本身就非常小),当它们相乘时,结果变得非常小,以至于几乎可以忽略不计。这就是梯度消失问题。

梯度爆炸问题的简要解释

如果权重不是很小,而是大于 1,那么随后的乘法将使梯度指数级增长;这就是梯度爆炸问题。梯度爆炸实际上是梯度消失的对立面,梯度消失时值变得太小,而梯度爆炸时值变得非常大。结果,网络会受到严重影响,无法做出任何预测。虽然梯度爆炸问题比梯度消失问题少见,但了解梯度爆炸是什么还是有必要的。

有一些方法可以帮助我们克服在面临梯度消失或梯度爆炸问题时遇到的挑战。我们将在这里学习的一种方法是长短时记忆(LSTM),它通过记住长期的信息来克服梯度问题。

长短时记忆(LSTM)

LSTMRNN 的一种,主要目标是克服梯度消失和梯度爆炸问题的缺点。它的架构设计使得它能够长时间记住数据和信息。

LSTM 旨在克服消失梯度和爆炸梯度问题的限制。LSTM 网络是一种特殊的 RNN,能够学习长期依赖关系。它们被设计来避免长期依赖问题;能够记住长时间间隔的信息就是它们的设计方式。下图展示了一个标准的递归网络,其中重复模块具有 tanh 激活 函数。这是一个简单的 RNN。在这种架构中,我们通常需要面对消失梯度问题:

图 9.12:一个简单的 RNN 模型

图 9.12:一个简单的 RNN 模型

LSTM 架构与简单的 RNN 相似,但它们的重复模块包含不同的组件,如下图所示:

图 9.13:LSTM 模型架构

图 9.13:LSTM 模型架构

除了简单的 RNNLSTM 还包括以下内容:

  • Sigmoid 激活 函数(σ

  • 数学计算功能(带有 + 和 x 的黑色圆圈)

  • 有门控单元(或称为门):

图 9.14:LSTM 详细解析

图 9.14:LSTM 详细解析

简单的 RNNLSTM 之间的主要区别是是否存在门控单元。你可以将门视为计算机内存,在内存中可以进行信息的写入、读取或存储。前面的图展示了 LSTM 的详细图像。门中的单元(由黑色圆圈表示)决定了存储什么信息以及何时允许读取或写入值。这些门接受从 01 的任何信息;也就是说,如果是 0,那么信息会被阻止;如果是 1,则所有信息都会流通。如果输入值介于 01 之间,则仅部分信息会流通。

除了这些输入门,网络的梯度还依赖于两个因素:权重和激活函数。门决定了哪些信息需要在 LSTM 单元中保持,哪些需要被遗忘或删除。这样,门就像水阀一样;也就是说,网络可以选择哪个阀门允许水流通,哪个阀门不允许水流通。

这些阀门的调整方式使得输出值永远不会导致梯度(消失或爆炸)问题。例如,如果值变得过大,那么会有一个遗忘门将忘记该值,并不再将其考虑用于计算。遗忘门的作用本质上是将信息乘以 01。如果信息需要进一步处理,遗忘门将信息乘以 1,如果需要忘记,则将信息乘以 0。每个门都由一个 sigmoid 函数辅助,该函数将信息压缩到 01 之间。为了更好地理解这一点,我们来看一些活动和练习。

注意

本章中的所有活动和练习将在 Jupyter notebook 中进行。您可以在packt.live/2vtdA8o下载本书的 GitHub 仓库,并获取所有准备好的模板。

练习 9.01:使用 50 个单元(神经元)的 LSTM 预测 Alphabet 股票价格的趋势

在本练习中,我们将研究 Alphabet 股票在 5 年期间的价格——即从 2014 年 1 月 1 日到 2018 年 12 月 31 日。在此过程中,我们将尝试使用RNNs预测并预测公司 2019 年 1 月的未来趋势。我们拥有 2019 年 1 月的实际值,因此稍后我们将能够将我们的预测与实际值进行比较。按照以下步骤完成本练习:

  1. 导入所需的库:

    import numpy as np
    import matplotlib.pyplot as plt
    import pandas as pd
    from tensorflow import random
    
  2. 使用 pandas 的 read_csv 函数导入数据集,并使用 head 方法查看数据集的前五行:

    dataset_training = pd.read_csv('../GOOG_train.csv')
    dataset_training.head()
    

    下图展示了前述代码的输出:

    图 9.15:GOOG_Training 数据集的前五行

    图 9.15:GOOG_Training 数据集的前五行

  3. 我们将使用 Open 股票价格进行预测;因此,从数据集中选择 Open 股票价格列并打印其值:

    training_data = dataset_training[['Open']].values
    training_data
    

    前述代码生成了以下输出:

    array([[ 555.647278],
           [ 555.418152],
           [ 554.42688 ],
           ...,
           [1017.150024],
           [1049.619995],
           [1050.959961]])
    
  4. 然后,通过使用 MinMaxScaler 进行数据标准化来执行特征缩放,并设置特征范围,使它们的最小值为 0,最大值为 1。在训练数据上使用缩放器的 fit_transform 方法:

    from sklearn.preprocessing import MinMaxScaler
    sc = MinMaxScaler(feature_range = (0, 1))
    training_data_scaled = sc.fit_transform(training_data)
    training_data_scaled
    

    前述代码生成了以下输出:

    array([[0.08017394],
           [0.07987932],
           [0.07860471],
           ...,
           [0.67359064],
           [0.71534169],
           [0.71706467]])
    
  5. 创建数据以获取当前实例的 60 个时间戳。我们在这里选择了60,因为这样可以提供足够数量的前实例,帮助我们理解趋势;从技术上讲,这个值可以是任何数字,但60是最优值。此外,这里的上限值是1258,即训练集中的行数(或记录数)的索引或计数:

    X_train = []
    y_train = []
    for i in range(60, 1258):
        X_train.append(training_data_scaled[i-60:i, 0])
        y_train.append(training_data_scaled[i, 0])
    X_train, y_train = np.array(X_train), \
                       np.array(y_train)
    
  6. 接下来,使用 NumPy 的 reshape 函数调整数据形状,为 X_train 的末尾添加一个额外的维度:

    X_train = np.reshape(X_train, (X_train.shape[0], \
                                   X_train.shape[1], 1))
    X_train
    

    下图展示了前述代码的输出:

    图 9.16:当前实例中几个时间戳的数据

    图 9.16:当前实例中几个时间戳的数据

  7. 导入以下 Keras 库以构建 RNN

    from keras.models import Sequential
    from keras.layers import Dense, LSTM, Dropout
    
  8. 设置随机种子并初始化顺序模型,如下所示:

    seed = 1
    np.random.seed(seed)
    random.set_seed(seed)
    model = Sequential()
    
  9. 向网络中添加一个包含 50 个单元的 LSTM 层,设置 return_sequences 参数为 True,并将 input_shape 参数设置为 (X_train.shape[1], 1)。再添加三个包含 50 个单元的 LSTM 层,并为前两个设置 return_sequences 参数为 True,如下所示:

    model.add(LSTM(units = 50, return_sequences = True, \
                   input_shape = (X_train.shape[1], 1)))
    # Adding a second LSTM layer
    model.add(LSTM(units = 50, return_sequences = True))
    # Adding a third LSTM layer
    model.add(LSTM(units = 50, return_sequences = True))
    # Adding a fourth LSTM layer
    model.add(LSTM(units = 50))
    # Adding the output layer
    model.add(Dense(units = 1))
    
  10. 使用 adam 优化器编译网络,并使用 均方误差(Mean Squared Error)作为损失函数。将模型拟合到训练数据上,训练 100 个周期,每批次大小为 32

    # Compiling the RNN
    model.compile(optimizer = 'adam', loss = 'mean_squared_error')
    # Fitting the RNN to the Training set
    model.fit(X_train, y_train, epochs = 100, batch_size = 32)
    
  11. 加载并处理test数据(这里将其视为实际数据),并选择表示Open股票数据的列:

    dataset_testing = pd.read_csv("../GOOG_test.csv")
    actual_stock_price = dataset_testing[['Open']].values
    actual_stock_price
    

    以下图展示了前面代码的输出:

    图 9.17:实际处理过的数据

    图 9.17:实际处理过的数据

  12. 拼接数据;我们需要60个前期实例来获得每一天的股票价格。因此,我们将需要训练和测试数据:

    total_data = pd.concat((dataset_training['Open'], \
                            dataset_testing['Open']), axis = 0)
    
  13. 重新调整和缩放输入,以准备测试数据。请注意,我们预测的是 1 月的月度趋势,它有21个交易日,因此为了准备测试集,我们将下限值设为 60,上限值设为 81。这确保了21天的差异得以保持:

    inputs = total_data[len(total_data) \
             - len(dataset_testing) - 60:].values
    inputs = inputs.reshape(-1,1)
    inputs = sc.transform(inputs)
    X_test = []
    for i in range(60, 81):
        X_test.append(inputs[i-60:i, 0])
    X_test = np.array(X_test)
    X_test = np.reshape(X_test, (X_test.shape[0], \
                        X_test.shape[1], 1))
    predicted_stock_price = model.predict(X_test)
    predicted_stock_price = sc.inverse_transform(\
                            predicted_stock_price)
    
  14. 通过绘制实际股票价格和预测股票价格来可视化结果:

    # Visualizing the results
    plt.plot(actual_stock_price, color = 'green', \
             label = 'Real Alphabet Stock Price',\
             ls='--')
    plt.plot(predicted_stock_price, color = 'red', \
             label = 'Predicted Alphabet Stock Price',\
             ls='-')
    plt.title('Predicted Stock Price')
    plt.xlabel('Time in days')
    plt.ylabel('Real Stock Price')
    plt.legend()
    plt.show()
    

    请注意,您的结果可能与实际的 Alphabet 股票价格略有不同。

    期望的输出

    图 9.18:实际股票价格与预测股票价格的对比

图 9.18:实际股票价格与预测股票价格的对比

这就是练习 9.01使用具有 50 个单元(神经元)的 LSTM 预测 Alphabet 股票价格的趋势的总结,在此我们通过LSTM预测了 Alphabet 的股票趋势。如前述图所示,趋势已被较好地捕捉。

注意

若要访问该特定部分的源代码,请参考packt.live/2ZwdAzW

您还可以在packt.live/2YV3PvX在线运行此示例。

在下一个活动中,我们将测试我们的知识并练习通过预测亚马逊过去 5 年股票价格的趋势来构建RNNsLSTM层。

活动 9.01:使用具有 50 个单元(神经元)的 LSTM 预测亚马逊股票价格的趋势

在本活动中,我们将考察亚马逊过去 5 年的股票价格——即从 2014 年 1 月 1 日到 2018 年 12 月 31 日。通过这种方式,我们将尝试使用 RNN 和 LSTM 预测并预测该公司 2019 年 1 月的未来趋势。我们已经有了 2019 年 1 月的实际值,因此稍后可以将我们的预测与实际值进行比较。按照以下步骤完成此活动:

  1. 导入所需的库。

  2. 从完整的数据集中提取Open列,因为预测将基于开盘股票值。可以从本书的 GitHub 库下载数据集。数据集可以在packt.live/2vtdA8o找到。

  3. 将数据规范化到 0 和 1 之间。

  4. 然后,创建时间戳。2019 年 1 月每一天的股票价格将由前 60 天的值来预测;因此,如果 1 月 1 日的预测是使用第n天到 12 月 31 日的值,那么 1 月 2 日将使用第n + 1 天和 1 月 1 日的值来预测,以此类推。

  5. 将数据重新调整为三维,因为网络需要三维数据。

  6. Keras中构建一个包含50个单元的RNN模型(这里,单元指的是神经元),并使用四个LSTM层。第一步应该提供输入形状。请注意,最后一个LSTM层总是会添加return_sequences=True,因此不必显式定义。

  7. 处理并准备测试数据,即 2019 年 1 月的实际数据。

  8. 合并并处理训练数据和测试数据。

  9. 可视化结果。

在实现这些步骤后,您应该看到以下预期输出:

图 9.19:实际股票价格与预测股票价格的对比

图 9.19:实际股票价格与预测股票价格的对比

注意

该活动的解决方案可以在第 452 页找到。

现在,让我们尝试通过调整LSTM来提升性能。关于如何构建LSTM没有绝对标准;然而,以下的排列组合可以尝试,以改进性能:

  • 构建一个具有适中单元数的LSTM,例如50

  • 构建一个包含超过100个单元的LSTM模型

  • 使用更多数据;也就是说,除了5年的数据外,获取10年的数据

  • 使用100个单元应用正则化

  • 使用50个单元应用正则化

  • 使用更多数据和50个单元应用正则化

这个列表可以有多种组合;任何能够提供最佳结果的组合都可以视为该数据集的好算法。在下一个练习中,我们将通过增加LSTM层的单元数来探索其中的一个选项,并观察性能表现。

练习 9.02:使用 100 个单元的 LSTM 预测字母表公司股票价格的趋势

在本练习中,我们将研究字母表公司在过去 5 年的股票价格,从 2014 年 1 月 1 日到 2018 年 12 月 31 日。在此过程中,我们将尝试使用 RNN 预测和预测该公司在 2019 年 1 月的未来趋势。我们有 2019 年 1 月的实际数据,因此稍后我们将把预测值与实际值进行比较。这与第一个练习相同,但这次我们使用了 100 个单元。确保与练习 9.01使用 50 个单元(神经元)的 LSTM 预测字母表公司股票价格的趋势进行比较。按照以下步骤完成此练习:

  1. 导入所需的库:

    import numpy as np
    import matplotlib.pyplot as plt
    import pandas as pd
    from tensorflow import random
    
  2. 使用 pandas 的read_csv函数导入数据集,并使用head方法查看数据集的前五行:

    dataset_training = pd.read_csv('../GOOG_train.csv')
    dataset_training.head()
    
  3. 我们将使用Open股票价格进行预测;因此,从数据集中选择Open股票价格列并打印值:

    training_data = dataset_training[['Open']].values
    training_data
    
  4. 然后,通过使用MinMaxScaler对数据进行标准化,执行特征缩放,并设置特征的范围,使其最小值为零,最大值为一。对训练数据使用标准化器的fit_transform方法:

    from sklearn.preprocessing import MinMaxScaler
    sc = MinMaxScaler(feature_range = (0, 1))
    training_data_scaled = sc.fit_transform(training_data)
    training_data_scaled
    
  5. 创建数据以从当前实例中获取60个时间戳。我们在这里选择60,因为它将提供足够多的前期实例以便理解趋势;从技术角度讲,这可以是任何数字,但60是最佳值。此外,这里的上限值为1258,它表示training集中的行数或记录数:

    X_train = []
    y_train = []
    for i in range(60, 1258):
        X_train.append(training_data_scaled[i-60:i, 0])
        y_train.append(training_data_scaled[i, 0])
    X_train, y_train = np.array(X_train), np.array(y_train)
    
  6. 使用 NumPy 的reshape函数调整数据的形状,在X_train的末尾添加一个额外的维度:

    X_train = np.reshape(X_train, (X_train.shape[0], \
                                   X_train.shape[1], 1))
    
  7. 导入以下Keras库以构建RNN

    from keras.models import Sequential
    from keras.layers import Dense, LSTM, Dropout
    
  8. 设置种子并初始化序列模型,如下所示:

    seed = 1
    np.random.seed(seed)
    random.set_seed(seed)
    model = Sequential()
    
  9. 向网络中添加一个具有50个单元的LSTM层,将return_sequences参数设置为True,并将input_shape参数设置为(X_train.shape[1], 1)。再添加三个具有50个单元的LSTM层,并将前两个LSTM层的return_sequences参数设置为True。最后添加一个大小为1的输出层:

    model.add(LSTM(units = 100, return_sequences = True, \
                   input_shape = (X_train.shape[1], 1)))
    # Adding a second LSTM
    model.add(LSTM(units = 100, return_sequences = True))
    # Adding a third LSTM layer
    model.add(LSTM(units = 100, return_sequences = True))
    # Adding a fourth LSTM layer
    model.add(LSTM(units = 100))
    # Adding the output layer
    model.add(Dense(units = 1))
    
  10. 使用adam优化器编译网络,并使用均方误差作为损失函数。用32的批量大小在100个 epochs 上拟合训练数据:

    # Compiling the RNN
    model.compile(optimizer = 'adam', loss = 'mean_squared_error')
    # Fitting the RNN to the Training set
    model.fit(X_train, y_train, epochs = 100, batch_size = 32)
    
  11. 加载并处理测试数据(在此视为实际数据),并选择代表Open股价数据的列:

    dataset_testing = pd.read_csv("../GOOG_test.csv")
    actual_stock_price = dataset_testing[['Open']].values
    actual_stock_price
    
  12. 将数据连接起来,因为我们需要60个前期实例来预测每一天的股价。因此,我们将需要训练数据和测试数据:

    total_data = pd.concat((dataset_training['Open'], \
                            dataset_testing['Open']), axis = 0)
    
  13. 调整和缩放输入以准备测试数据。请注意,我们正在预测 1 月的月度趋势,1 月有21个交易日,因此为了准备测试集,我们将下限值设为60,上限值设为81。这样可以确保21的差值保持不变:

    inputs = total_data[len(total_data) \
                        - len(dataset_testing) - 60:].values
    inputs = inputs.reshape(-1,1)
    inputs = sc.transform(inputs)
    X_test = []
    for i in range(60, 81):
        X_test.append(inputs[i-60:i, 0])
    X_test = np.array(X_test)
    X_test = np.reshape(X_test, (X_test.shape[0], \
                        X_test.shape[1], 1))
    predicted_stock_price = model.predict(X_test)
    predicted_stock_price = sc.inverse_transform(\
                            predicted_stock_price)
    
  14. 通过绘制实际股价和预测股价来可视化结果:

    # Visualizing the results
    plt.plot(actual_stock_price, color = 'green', \
             label = 'Real Alphabet Stock Price',ls='--')
    plt.plot(predicted_stock_price, color = 'red', \
             label = 'Predicted Alphabet Stock Price',ls='-')
    plt.title('Predicted Stock Price')
    plt.xlabel('Time in days')
    plt.ylabel('Real Stock Price')
    plt.legend()
    plt.show()
    

    预期输出

    图 9.20:实际与预测的股价

图 9.20:实际与预测的股价

注意

要访问此特定部分的源代码,请参见 packt.live/2ZDggf4

你也可以在网上运行这个例子,访问 packt.live/2O4ZoJ7

现在,如果我们将练习 9.01中的LSTM,即使用 50 个单元(神经元)的 LSTM 预测字母股价的趋势,它有50个神经元(单元),与这个使用100个单元的LSTM进行比较,我们可以看到,与亚马逊股价的情况不同,使用100个单元的LSTM能够更好地捕捉到字母股价的趋势:

图 9.21:与练习 9.01 的 LSTM 输出比较

图 9.21:与练习 9.01 的 LSTM 输出比较

因此,我们可以清楚地看到,100个单元的LSTM预测的趋势比50个单元的LSTM更准确。请记住,100个单元的LSTM虽然需要更多的计算时间,但在这种情况下提供了更好的结果。除了通过增加单元数来修改我们的模型,我们还可以添加正则化。接下来的活动将测试添加正则化是否能使我们的亚马逊模型更加准确。

活动 9.02:使用添加正则化的 LSTM 预测亚马逊股票价格

在这个活动中,我们将研究亚马逊股票在过去 5 年的价格,从 2014 年 1 月 1 日到 2018 年 12 月 31 日。在此过程中,我们将尝试使用 RNN 和 LSTM 预测并预报公司 2019 年 1 月的未来趋势。我们已经有 2019 年 1 月的实际值,因此稍后可以将我们的预测与实际值进行比较。最初,我们使用 50 个单元(或神经元)的 LSTM 预测了亚马逊股票的趋势。在这里,我们还将添加 dropout 正则化,并将结果与活动 9.01使用 50 个单元(神经元)的 LSTM 预测亚马逊股票价格的趋势进行比较。请按照以下步骤完成此活动:

  1. 导入所需的库。

  2. 从完整的数据集中提取Open列,因为预测将基于开盘股票值进行。你可以从本书的 GitHub 仓库下载数据集,链接为packt.live/2vtdA8o

  3. 将数据规范化到 0 和 1 之间。

  4. 然后,创建时间戳。2019 年 1 月的每一天的值将通过前60天的数据进行预测。所以,如果 1 月 1 日的预测是通过第n天到 12 月 31 日的数据进行的,那么 1 月 2 日将通过第n+1天和 1 月 1 日的数据进行预测,依此类推。

  5. 将数据重新塑形为三维,因为网络需要三维数据。

  6. 使用 Keras 构建一个包含四个 LSTM 层的 RNN,每个 LSTM 层有50个单元(此处,单元指神经元),每个 LSTM 层后有 20%的 dropout。第一步应提供输入形状。请注意,最后一个 LSTM 层总是设置return_sequences=True

  7. 处理并准备测试数据,即 2019 年 1 月的实际数据。

  8. 合并并处理训练数据和测试数据。

  9. 最后,可视化结果。

在实现这些步骤后,你应该得到以下预期的输出:

图 9.22:实际股票价格与预测股票价格

图 9.22:实际股票价格与预测股票价格

注意

本活动的解决方案可以在第 457 页找到。

在下一个活动中,我们将尝试构建每个LSTM层有100个单元的RNN,并将其与只有50个单元的RNN表现进行比较。

活动 9.03:使用逐渐增加 LSTM 神经元数量(100 个单元)预测亚马逊股票价格的趋势

在这个活动中,我们将研究亚马逊过去 5 年的股票价格,从 2014 年 1 月 1 日到 2018 年 12 月 31 日。在此过程中,我们将尝试使用 RNN 预测和预测该公司 2019 年 1 月的未来趋势。我们拥有 2019 年 1 月的实际值,因此稍后可以将我们的预测与实际值进行比较。你还可以将输出的差异与活动 9.01,“使用 50 个单元(神经元)的 LSTM 预测亚马逊股价趋势”进行比较。按照以下步骤完成此活动:

  1. 导入所需的库。

  2. 从完整的数据集中提取Open列,因为预测将基于Open股价进行。

  3. 将数据归一化到 0 到 1 之间。

  4. 然后,创建时间戳。2019 年 1 月每一天的值将由前60天的数据预测;因此,如果 1 月 1 日是根据第 n 天到 12 月 31 日的数据预测的,那么 1 月 2 日将根据第n + 1天和 1 月 1 日的数据来预测,依此类推。

  5. 将数据重塑为三维,因为网络需要三维数据。

  6. 在 Keras 中构建一个包含 100 个单元的 LSTM(此处的单元指的是神经元)。第一步应该提供输入形状。请注意,最终的LSTM层始终需要设置return_sequences=True。编译并将模型拟合到训练数据中。

  7. 处理并准备测试数据,即 2019 年 1 月的实际数据。

  8. 合并和处理训练数据与测试数据。

  9. 可视化结果。

完成这些步骤后,你应该得到以下预期输出:

图 9.23:实际股价与预测股价

图 9.23:实际股价与预测股价

注意

本活动的解决方案可以在第 462 页找到。

在这个活动中,我们创建了一个包含四个LSTM层的RNN,每层有100个单元。我们将其与活动 9.02,“使用添加正则化的 LSTM 预测亚马逊股价”中的结果进行了比较,在该活动中每层有50个单元。两个模型之间的差异很小,因此,具有较少单元的模型更为优选,因为它减少了计算时间,并且更不容易出现过拟合训练数据的情况。

概述

在本章中,我们通过研究一些 Google Assistant 的实际案例,学习了序列建模和序列记忆。然后,我们学习了序列建模如何与RNN相关,并且RNN与传统前馈网络的不同之处。我们详细了解了梯度消失问题,以及为什么使用LSTM比简单的RNN更好地克服梯度消失问题。我们将所学应用于时间序列问题,通过预测股价趋势来实践。

在这个工作坊中,我们学习了机器学习和 Python 的基础知识,同时深入了解了如何应用 Keras 开发高效的深度学习解决方案。我们探索了机器学习和深度学习之间的差异。我们通过首先使用 scikit-learn,然后使用 Keras 来构建逻辑回归模型,开始了本次工作坊。

然后,我们通过创建各种实际场景的预测模型进一步探索了 Keras 及其不同的模型,例如将在线购物者分类为有购买意图和没有购买意图的群体。我们学习了如何评估、优化和改进模型,以实现最大的信息量,创建在新数据上表现良好的强大模型。

我们还通过构建带有 scikit-learn 包装器的 Keras 模型来实现交叉验证,帮助那些熟悉 scikit-learn 工作流程的人轻松使用 Keras 模型。然后,我们学习了如何应用 L1L2dropout regularization 技术,以提高模型的准确性并防止模型过拟合训练数据。

接下来,我们通过应用诸如空准确率作为基准比较技术,以及评估指标如精确度、AUC-ROC 得分等,进一步探索了模型评估,以理解我们的模型如何对分类任务进行评分。最终,这些高级评估技术帮助我们了解了模型在什么情况下表现良好,哪里有改进的空间。

我们通过使用 Keras 创建一些高级模型来结束了工作坊。我们通过构建带有各种参数的 CNN 模型来探索计算机视觉,以分类图像。然后,我们使用预训练模型对新图像进行分类,并对这些预训练模型进行了微调,以便我们可以将它们应用于自己的应用程序。最后,我们介绍了序列建模,这用于建模诸如股票价格和自然语言处理等序列。我们通过创建带有 LSTM 层的 RNN 网络,测试了使用真实股票数据预测股价的知识,并实验了每层中不同单元数的影响以及 dropout 正则化对模型性能的影响。

总体来说,我们全面了解了如何使用 Keras 解决各种实际问题。我们涵盖了在线购物者的分类任务、丙型肝炎数据和 Scania 卡车的故障数据,还包括回归任务,如在给定各种化学属性时预测各种化学物质的水生毒性。我们还进行了图像分类任务,构建了 CNN 模型来预测图像是花朵还是汽车,并构建了回归任务来预测未来的股价,使用的是 RNNs。通过在这个工作坊中使用真实世界的数据集构建模型,你已准备好将所学知识应用到自己的问题解决中,并创建自己的应用程序。

附录

1. 使用 Keras 进行机器学习简介

活动 1.01:向模型添加正则化

在这个活动中,我们将使用来自 scikit-learn 包的相同逻辑回归模型。然而,这一次,我们将向模型中添加正则化,并搜索最佳正则化参数——这个过程通常称为 超参数调优。在训练模型后,我们将测试预测结果,并将模型评估指标与基准模型和未加正则化的模型的评估指标进行比较。

  1. 练习 1.03数据的适当表示 加载特征数据,从 练习 1.02数据清理 加载目标数据:

    import pandas as pd
    feats = pd.read_csv('../data/OSI_feats_e3.csv')
    target = pd.read_csv('../data/OSI_target_e2.csv')
    
  2. 创建 testtrain 数据集。使用训练数据集训练数据。然而,这一次,请使用部分 training 数据集进行验证,以选择最合适的超参数。

    再次使用 test_size = 0.2,这意味着将 20% 的数据保留用于测试。我们的验证集的大小将由验证折数决定。如果我们进行 10 折交叉验证,则相当于将 10%training 数据集保留用于验证模型。每一折将使用不同的 10% 训练数据集,而所有折的平均误差将用于比较具有不同超参数的模型。为 random_state 变量分配一个随机值:

    from sklearn.model_selection import train_test_split
    test_size = 0.2
    random_state = 13
    X_train, X_test, y_train, y_test = \
    train_test_split(feats, target, test_size=test_size, \
                     random_state=random_state)
    
  3. 检查数据框的维度:

    print(f'Shape of X_train: {X_train.shape}')
    print(f'Shape of y_train: {y_train.shape}')
    print(f'Shape of X_test: {X_test.shape}')
    print(f'Shape of y_test: {y_test.shape}')
    

    上述代码产生以下输出:

    Shape of X_train: (9864, 68)
    Shape of y_train: (9864, 1)
    Shape of X_test: (2466, 68)
    Shape of y_test: (2466, 1)
    
  4. 接下来,实例化模型。尝试两种正则化参数,l1l2,并使用 10 倍交叉验证。将我们的正则化参数从 1x10-2 到 1x106 在对数空间中均匀遍历,以观察这些参数如何影响结果:

    import numpy as np
    from sklearn.linear_model import LogisticRegressionCV
    Cs = np.logspace(-2, 6, 9)
    model_l1 = LogisticRegressionCV(Cs=Cs, penalty='l1', \
                                    cv=10, solver='liblinear', \
                                    random_state=42, max_iter=10000)
    model_l2 = LogisticRegressionCV(Cs=Cs, penalty='l2', cv=10, \
                                    random_state=42, max_iter=10000)
    

    注意

    对于具有 l1 正则化参数的逻辑回归模型,只能使用 liblinear 求解器。

  5. 接下来,将模型拟合到训练数据:

    model_l1.fit(X_train, y_train['Revenue'])
    model_l2.fit(X_train, y_train['Revenue'])
    

    下图显示了上述代码的输出:

    图 1.37:fit 命令的输出,显示所有模型训练参数

    ](https://github.com/OpenDocCN/freelearn-dl-pt5-zh/raw/master/docs/dl-keras-ws/img/B15777_01_37.jpg)

    图 1.37:fit 命令的输出,显示所有模型训练参数

  6. 在这里,我们可以看到两种不同模型的正则化参数值。正则化参数是根据哪个模型产生了最低误差来选择的:

    print(f'Best hyperparameter for l1 regularization model: \
    {model_l1.C_[0]}')
    print(f'Best hyperparameter for l2 regularization model: \
    {model_l2.C_[0]}')
    

    上述代码产生以下输出:

    Best hyperparameter for l1 regularization model: 1000000.0
    Best hyperparameter for l2 regularization model: 1.0
    

    注意

    C_ 属性只有在模型训练完成后才能使用,因为它是在交叉验证过程确定最佳参数后设置的。

  7. 为了评估模型的性能,请对 test 集合进行预测,并将其与 true 值进行比较:

    y_pred_l1 = model_l1.predict(X_test)
    y_pred_l2 = model_l2.predict(X_test)
    
  8. 为了比较这些模型,计算评估指标。首先,查看模型的准确度:

    from sklearn import metrics
    accuracy_l1 = metrics.accuracy_score(y_pred=y_pred_l1, \
                                         y_true=y_test)
    accuracy_l2 = metrics.accuracy_score(y_pred=y_pred_l2, \
                                         y_true=y_test)
    print(f'Accuracy of the model with l1 regularization is \
    {accuracy_l1*100:.4f}%')
    print(f'Accuracy of the model with l2 regularization is \
    {accuracy_l2*100:.4f}%')
    

    上述代码产生以下输出:

    Accuracy of the model with l1 regularization is 89.2133%
    Accuracy of the model with l2 regularization is 89.2944%
    
  9. 另外,还请查看其他评估指标:

    precision_l1, recall_l1, fscore_l1, _ = \
    metrics.precision_recall_fscore_support(y_pred=y_pred_l1, \
                                            y_true=y_test, \
                                            average='binary')
    precision_l2, recall_l2, fscore_l2, _ = \
    metrics.precision_recall_fscore_support(y_pred=y_pred_l2, \
                                            y_true=y_test, \
                                            average='binary')
    print(f'l1\nPrecision: {precision_l1:.4f}\nRecall: \
    {recall_l1:.4f}\nfscore: {fscore_l1:.4f}\n\n')
    print(f'l2\nPrecision: {precision_l2:.4f}\nRecall: \
    {recall_l2:.4f}\nfscore: {fscore_l2:.4f}')
    

    前面的代码会产生以下输出:

    l1
    Precision: 0.7300
    Recall: 0.4078
    fscore: 0.5233
    l2
    Precision: 0.7350
    Recall: 0.4106
    fscore: 0.5269
    
  10. 观察模型训练完成后系数的值:

    coef_list = [f'{feature}: {coef}' for coef, \
                 feature in sorted(zip(model_l1.coef_[0], \
                                   X_train.columns.values.tolist()))]
    for item in coef_list:
        print(item)
    

    注意

    coef_属性仅在模型训练完成后可用,因为它是在交叉验证过程中确定最佳参数后设置的。

    以下图显示了前面代码的输出:

    图 1.38:特征列名称及其相应系数的值    对于具有 l1 正则化的模型

    图 1.38:具有 l1 正则化的模型的特征列名称及其相应系数的值

  11. 对具有l2正则化参数类型的模型执行相同操作:

    coef_list = [f'{feature}: {coef}' for coef, \
                 feature in sorted(zip(model_l2.coef_[0], \
                                       X_train.columns.values.tolist()))]
    for item in coef_list:
        print(item)
    

    以下图显示了前面代码的输出:

    图 1.39:特征列名称及其相应系数的值    对于具有 l2 正则化的模型

图 1.39:具有 l2 正则化的模型的特征列名称及其相应系数的值

注意

要访问该特定部分的源代码,请参考packt.live/2VIoe5M

本节目前没有在线交互式示例,需要在本地运行。

2. 机器学习与深度学习

活动 2.01:使用 Keras 创建逻辑回归模型

在这个活动中,我们将使用 Keras 库创建一个基本模型。我们将构建的模型将把网站用户分为两类:一类是会从网站购买产品的用户,另一类则不会。为了实现这一目标,我们将使用之前相同的在线购物购买意图数据集,并尝试预测我们在第一章中预测的相同变量,即使用 Keras 进行机器学习入门

执行以下步骤完成此活动:

  1. 打开开始菜单中的 Jupyter 笔记本以实现此活动。加载在线购物购买意图数据集,你可以从 GitHub 仓库下载。我们将使用pandas库进行数据加载,因此请先导入pandas库。确保你已经将 csv 文件保存到本章适当的数据文件夹中,或者可以更改代码中使用的文件路径。

    import pandas as pd
    feats = pd.read_csv('../data/OSI_feats.csv')
    target = pd.read_csv('../data/OSI_target.csv')
    
  2. 对于本次活动,我们不进行进一步的预处理。和上一章一样,我们将数据集拆分为训练集和测试集,并将测试推迟到最后,在评估模型时进行。我们将保留20%的数据用于测试,通过设置test_size=0.2参数,并创建一个random_state参数,以便重现结果:

    from sklearn.model_selection import train_test_split
    test_size = 0.2
    random_state = 42
    X_train, X_test, y_train, y_test = \
    train_test_split(feats, target, test_size=test_size, \
                     random_state=random_state)
    
  3. numpytensorflow中设置随机种子以保证可复现性。通过初始化Sequential类的模型开始创建模型:

    from keras.models import Sequential
    import numpy as np
    from tensorflow import random
    np.random.seed(random_state)
    random.set_seed(random_state)
    model = Sequential()
    
  4. 要向模型中添加一个全连接层,请添加一个Dense类的层。在这里,我们需要包括该层中的节点数。在我们的例子中,由于我们正在执行二分类,且期望输出为zeroone,所以该值将为 1。此外,还需要指定输入维度,这只需要在模型的第一层中指定。它的作用是表示输入数据的格式。传入特征的数量:

    from keras.layers import Dense
    model.add(Dense(1, input_dim=X_train.shape[1]))
    
  5. 在前一层的输出上添加一个 sigmoid 激活函数,以复制logistic regression算法:

    from keras.layers import Activation
    model.add(Activation('sigmoid'))
    
  6. 一旦我们将所有模型组件按正确顺序排列,我们必须编译模型,以便所有的学习过程都能被配置。使用adam优化器,binary_crossentropy作为损失函数,并通过将参数传入metrics参数来跟踪模型的准确率:

    model.compile(optimizer='adam', loss='binary_crossentropy', \
                  metrics=['accuracy'])
    
  7. 打印模型摘要,以验证模型是否符合我们的预期:

    print(model.summary())
    

    下图展示了前面代码的输出:

    图 2.19:模型摘要

    图 2.19:模型摘要

  8. 接下来,使用model类的fit方法拟合模型。提供训练数据,以及训练周期数和每个周期后用于验证的数据量:

    history = model.fit(X_train, y_train['Revenue'], epochs=10, \
                        validation_split=0.2, shuffle=False)
    

    下图展示了前面代码的输出:

    图 2.20:在模型上使用 fit 方法

    图 2.20:在模型上使用 fit 方法

  9. 损失和准确率的值已存储在history变量中。使用每个训练周期后我们跟踪的损失和准确率,绘制每个值的图表:

    import matplotlib.pyplot as plt
    %matplotlib inline
    # Plot training and validation accuracy values
    plt.plot(history.history['accuracy'])
    plt.plot(history.history['val_accuracy'])
    plt.title('Model accuracy')
    plt.ylabel('Accuracy')
    plt.xlabel('Epoch')
    plt.legend(['Train', 'Validation'], loc='upper left')
    plt.show()
    # Plot training and validation loss values
    plt.plot(history.history['loss'])
    plt.plot(history.history['val_loss'])
    plt.title('Model loss')
    plt.ylabel('Loss')
    plt.xlabel('Epoch')
    plt.legend(['Train', 'Validation'], loc='upper left')
    plt.show()
    

    以下图表展示了前面代码的输出:

    图 2.21:拟合模型时的损失和准确率

    图 2.21:拟合模型时的损失和准确率

  10. 最后,在我们从一开始就保留的测试数据上评估模型,这将为模型的性能提供客观评价:

    test_loss, test_acc = model.evaluate(X_test, y_test['Revenue'])
    print(f'The loss on the test set is {test_loss:.4f} \
    and the accuracy is {test_acc*100:.3f}%')
    

    前面代码的输出如下所示。在这里,模型预测了测试数据集中用户的购买意图,并通过将其与y_test中的真实值进行比较来评估性能。在测试数据集上评估模型将产生损失和准确率值,我们可以打印出来:

    2466/2466 [==============================] - 0s 15us/step
    The loss on the test set is 0.3632 and the accuracy is 86.902%
    

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

    packt.live/3dVTQLe

    你还可以在packt.live/2ZxEhV4在线运行此示例。

3. 使用 Keras 进行深度学习

活动 3.01:构建一个单层神经网络进行二分类

在本活动中,我们将比较逻辑回归模型和不同节点大小以及不同激活函数的单层神经网络的结果。我们将使用的数据集表示飞机螺旋桨检查的标准化测试结果,而类别表示它们是否通过了人工视觉检查。我们将创建模型来预测给定自动化测试结果时的人工检查结果。请按照以下步骤完成此活动:

  1. 加载所有必要的包:

    # import required packages from Keras
    from keras.models import Sequential 
    from keras.layers import Dense, Activation 
    import numpy as np
    import pandas as pd
    from tensorflow import random
    from sklearn.model_selection import train_test_split
    # import required packages for plotting
    import matplotlib.pyplot as plt 
    import matplotlib
    %matplotlib inline 
    import matplotlib.patches as mpatches
    # import the function for plotting decision boundary
    from utils import plot_decision_boundary
    
  2. 设置一个seed

    """
    define a seed for random number generator so the result will be reproducible
    """
    seed = 1
    
  3. 加载模拟数据集并打印XY的大小以及示例的数量:

    """
    load the dataset, print the shapes of input and output and the number of examples
    """
    feats = pd.read_csv('../data/outlier_feats.csv')
    target = pd.read_csv('../data/outlier_target.csv')
    print("X size = ", feats.shape)
    print("Y size = ", target.shape)
    print("Number of examples = ", feats.shape[0])
    

    预期输出

    X size = (3359, 2)
    Y size = (3359, 1)
    Number of examples = 3359
    
  4. 绘制数据集。每个点的 x 和 y 坐标将是两个输入特征。每条记录的颜色代表通过/失败结果:

    class_1=plt.scatter(feats.loc[target['Class']==0,'feature1'], \
                        feats.loc[target['Class']==0,'feature2'], \
                        c="red", s=40, edgecolor='k')
    class_2=plt.scatter(feats.loc[target['Class']==1,'feature1'], \
                        feats.loc[target['Class']==1,'feature2'], \
                        c="blue", s=40, edgecolor='k')
    plt.legend((class_1, class_2),('Fail','Pass'))
    plt.xlabel('Feature 1')
    plt.ylabel('Feature 2')
    

    以下图像显示了前述代码的输出:

    图 3.19:模拟训练数据点

    图 3.19:模拟训练数据点

  5. 构建逻辑回归模型,这是一个没有隐藏层的单节点顺序模型,使用sigmoid 激活函数:

    np.random.seed(seed)
    random.set_seed(seed)
    model = Sequential()
    model.add(Dense(1, activation='sigmoid', input_dim=2))
    model.compile(optimizer='sgd', loss='binary_crossentropy')
    
  6. 将模型拟合到训练数据:

    model.fit(feats, target, batch_size=5, epochs=100, verbose=1, \
              validation_split=0.2, shuffle=False)
    

    100轮 = 0.3537

    图 3.20:100 轮中的最后 5 轮的损失详情

    图 3.20:100 轮中的最后 5 轮的损失详情

  7. 在训练数据上绘制决策边界:

    matplotlib.rcParams['figure.figsize'] = (10.0, 8.0)
    plot_decision_boundary(lambda x: model.predict(x), feats, target)
    plt.title("Logistic Regression")
    

    以下图像显示了前述代码的输出:

    图 3.21:逻辑回归模型的决策边界

    图 3.21:逻辑回归模型的决策边界

    逻辑回归模型的线性决策边界显然无法捕捉到两类之间的圆形决策边界,并将所有结果预测为通过结果。

  8. 创建一个包含三个节点的单隐藏层神经网络,并使用relu 激活函数,输出层为一个节点,并使用sigmoid 激活函数。最后,编译模型:

    np.random.seed(seed)
    random.set_seed(seed)
    model = Sequential() 
    model.add(Dense(3, activation='relu', input_dim=2))
    model.add(Dense(1, activation='sigmoid'))
    model.compile(optimizer='sgd', loss='binary_crossentropy')
    
  9. 将模型拟合到训练数据:

    model.fit(feats, target, batch_size=5, epochs=200, verbose=1, \
              validation_split=0.2, shuffle=False)
    

    200轮 = 0.0260

    图 3.22:200 轮中的最后 5 轮的损失详情

    图 3.22:200 轮中的最后 5 轮的损失详情

  10. 绘制所创建的决策边界:

    matplotlib.rcParams['figure.figsize'] = (10.0, 8.0)
    plot_decision_boundary(lambda x: model.predict(x), feats, target)
    plt.title("Decision Boundary for Neural Network with "\
              "hidden layer size 3")
    

    以下图像显示了前述代码的输出:

    图 3.23:带有隐藏层的神经网络的决策边界    3 个节点的大小和 ReLU 激活函数

    图 3.23:带有 3 个节点的隐藏层和 ReLU 激活函数的神经网络决策边界

    使用三个处理单元代替一个显著提高了模型捕捉两类之间非线性边界的能力。注意,与上一步相比,损失值显著下降。

  11. 创建一个神经网络,包含一个具有六个节点的隐藏层和一个relu 激活函数,输出层有一个节点,并使用sigmoid 激活函数。最后,编译模型:

    np.random.seed(seed)
    random.set_seed(seed)
    model = Sequential() 
    model.add(Dense(6, activation='relu', input_dim=2))
    model.add(Dense(1, activation='sigmoid'))
    model.compile(optimizer='sgd', loss='binary_crossentropy')
    
  12. 将模型拟合到训练数据:

    model.fit(feats, target, batch_size=5, epochs=400, verbose=1, \
              validation_split=0.2, shuffle=False)
    

    400 轮次 = 0.0231

    图 3.24:最后 5 个 epoch(共 400 个)的损失详情

    图 3.24:最后 5 个 epoch(共 400 个)的损失详情

  13. 绘制决策边界:

    matplotlib.rcParams['figure.figsize'] = (10.0, 8.0)
    plot_decision_boundary(lambda x: model.predict(x), feats, target)
    plt.title("Decision Boundary for Neural Network with "\
              "hidden layer size 6")
    

    以下图像显示了前述代码的输出:

    图 3.25:具有隐藏层的神经网络的决策边界    隐藏层大小为 6 且使用 ReLU 激活函数

    图 3.25:具有隐藏层大小为 6 且使用 ReLU 激活函数的神经网络的决策边界

    通过将隐藏层中的单元数加倍,模型的决策边界更加接近真实的圆形,而且与前一步相比,损失值进一步减少。

  14. 创建一个神经网络,包含一个具有三个节点的隐藏层和一个tanh 激活函数,输出层有一个节点,并使用sigmoid 激活函数。最后,编译模型:

    np.random.seed(seed)
    random.set_seed(seed)
    model = Sequential() 
    model.add(Dense(3, activation='tanh', input_dim=2))
    model.add(Dense(1, activation='sigmoid'))
    model.compile(optimizer='sgd', loss='binary_crossentropy')
    
  15. 将模型拟合到训练数据:

    model.fit(feats, target, batch_size=5, epochs=200, verbose=1, \
              validation_split=0.2, shuffle=False)
    

    200 轮次 = 0.0426

    图 3.26:最后 5 个 epoch(共 200 个)的损失详情

    图 3.26:最后 5 个 epoch(共 200 个)的损失详情

  16. 绘制决策边界:

    plot_decision_boundary(lambda x: model.predict(x), feats, target) 
    plt.title("Decision Boundary for Neural Network with "\
              "hidden layer size 3")
    

    以下图像显示了前述代码的输出:

    图 3.27:具有隐藏层的神经网络的决策边界    隐藏层大小为 3 且使用 tanh 激活函数

    图 3.27:具有隐藏层大小为 3 且使用 tanh 激活函数的神经网络的决策边界

    使用tanh激活函数消除了决策边界中的尖锐边缘。换句话说,它使得决策边界变得更加平滑。然而,由于我们看到损失值的增加,模型并没有表现得更好。尽管之前提到过tanh的学习速度比relu慢,但在对测试数据集进行评估时,我们得到了相似的损失和准确度评分。

  17. 创建一个神经网络,包含一个具有六个节点的隐藏层和一个tanh 激活函数,输出层有一个节点,并使用sigmoid 激活函数。最后,编译模型:

    np.random.seed(seed)
    random.set_seed(seed)
    model = Sequential() 
    model.add(Dense(6, activation='tanh', input_dim=2))
    model.add(Dense(1, activation='sigmoid'))
    model.compile(optimizer='sgd', loss='binary_crossentropy')
    
  18. 将模型拟合到训练数据:

    model.fit(feats, target, batch_size=5, epochs=400, verbose=1, \
              validation_split=0.2, shuffle=False)
    

    400 轮次 = 0.0215

    图 3.28:最后 5 个 epoch(共 400 个)的损失详情

    图 3.28:最后 5 个 epoch(共 400 个)的损失详情

  19. 绘制决策边界:

    matplotlib.rcParams['figure.figsize'] = (10.0, 8.0)
    plot_decision_boundary(lambda x: model.predict(x), feats, target)
    plt.title("Decision Boundary for Neural Network with "\
              "hidden layer size 6")
    

    以下图像显示了前述代码的输出:

    图 3.29:具有隐藏层大小为 6 且使用 ReLU 激活函数的神经网络的决策边界    具有 6 个节点和 tanh 激活函数

图 3.29:具有隐藏层大小为 6 且使用 tanh 激活函数的神经网络的决策边界

再次使用tanh激活函数代替relu,并将更多的节点添加到隐藏层中,使决策边界的曲线更加平滑,训练数据的拟合效果更好,依据训练数据的准确性来判断。我们应当小心,不要向隐藏层中添加过多的节点,否则可能会导致过拟合数据。这可以通过评估测试集来观察,在拥有六个节点的神经网络上,相比于具有三个节点的神经网络,准确度有所下降。

注:

要访问此特定部分的源代码,请参阅packt.live/3iv0wn1

您也可以在packt.live/2BqumZt上在线运行此示例。

活动 3.02:使用神经网络进行高级纤维化诊断

在本活动中,您将使用一个真实数据集来预测患者是否存在高级纤维化,基于的测量指标包括年龄、性别和 BMI。该数据集包含 1,385 名接受过丙型肝炎治疗剂量的患者信息。每个患者都有28个不同的属性,并且有一个类别标签,该标签只能取两个值:1表示高级纤维化,0表示没有高级纤维化迹象。这是一个二分类问题,输入维度为 28。

在本活动中,您将实现不同的深度神经网络架构来执行此分类任务,绘制训练误差率和测试误差率的趋势,并确定最终分类器需要训练多少个 epoch。请按照以下步骤完成此活动:

  1. 导入所有必要的库,并使用 pandas 的read_csv函数加载数据集:

    import pandas as pd
    import numpy as np
    from tensorflow import random
    from sklearn.model_selection import train_test_split
    from sklearn.preprocessing import StandardScaler
    from keras.models import Sequential
    from keras.layers import Dense
    import matplotlib.pyplot as plt 
    import matplotlib
    %matplotlib inline
    X = pd.read_csv('../data/HCV_feats.csv')
    y = pd.read_csv('../data/HCV_target.csv')
    
  2. 打印recordsfeaturesfeature数据集中的数量,以及target数据集中唯一类别的数量:

    print("Number of Examples in the Dataset = ", X.shape[0])
    print("Number of Features for each example = ", X.shape[1]) 
    print("Possible Output Classes = ", \
          y['AdvancedFibrosis'].unique())
    

    预期输出

    Number of Examples in the Dataset = 1385
    Number of Features for each example = 28
    Possible Output Classes = [0 1]
    
  3. 对数据进行归一化并进行缩放。然后,将数据集拆分为训练集和测试集:

    seed = 1
    np.random.seed(seed)
    sc = StandardScaler()
    X = pd.DataFrame(sc.fit_transform(X), columns=X.columns)
    X_train, X_test, y_train, y_test = \
    train_test_split(X, y, test_size=0.2, random_state=seed)
    # Print the information regarding dataset sizes
    print(X_train.shape)
    print(y_train.shape)
    print(X_test.shape)
    print(y_test.shape)
    print ("Number of examples in training set = ", X_train.shape[0])
    print ("Number of examples in test set = ", X_test.shape[0])
    

    预期输出

    (1108, 28)
    (1108, 1)
    (277, 28)
    (277, 1)
    Number of examples in training set = 1108
    Number of examples in test set = 277
    
  4. 实现一个具有一个隐藏层,隐藏层大小为3,激活函数为tanh,输出层为一个节点,并使用sigmoid激活函数的深度神经网络。最后,编译模型并打印出模型的摘要:

    np.random.seed(seed)
    random.set_seed(seed)
    # define the keras model
    classifier = Sequential()
    classifier.add(Dense(units = 3, activation = 'tanh', \
                         input_dim=X_train.shape[1]))
    classifier.add(Dense(units = 1, activation = 'sigmoid'))
    classifier.compile(optimizer = 'sgd', loss = 'binary_crossentropy', \
                       metrics = ['accuracy'])
    classifier.summary()
    

    以下图像显示了前述代码的输出:

    图 3.30:神经网络的架构

    图 3.30:神经网络的架构

  5. 将模型拟合到训练数据中:

    history=classifier.fit(X_train, y_train, batch_size = 20, \
                           epochs = 100, validation_split=0.1, \
                           shuffle=False)
    
  6. 绘制每个 epoch 的训练误差率测试误差率

    plt.plot(history.history['loss'])
    plt.plot(history.history['val_loss'])
    plt.ylabel('loss')
    plt.xlabel('epoch')
    plt.legend(['train loss', 'validation loss'], loc='upper right')
    

    预期输出

    图 3.31:训练模型时训练误差率和测试误差率的变化图

    图 3.31:训练模型时训练误差率和测试误差率的变化图

  7. 打印出在训练集和测试集上达到的最佳准确率值,以及在test数据集上评估的lossaccuracy值。

    print(f"Best Accuracy on training set = \
    {max(history.history['accuracy'])*100:.3f}%")
    print(f"Best Accuracy on validation set = \
    {max(history.history['val_accuracy'])*100:.3f}%") 
    test_loss, test_acc = \
    classifier.evaluate(X_test, y_test['AdvancedFibrosis'])
    print(f'The loss on the test set is {test_loss:.4f} and \
    the accuracy is {test_acc*100:.3f}%')
    

    下图展示了前面代码的输出结果:

    Best Accuracy on training set = 52.959%
    Best Accuracy on validation set = 58.559%
    277/277 [==============================] - 0s 25us/step
    The loss on the test set is 0.6885 and the accuracy is 55.235%
    
  8. 实现一个具有两个隐藏层的深度神经网络,第一个隐藏层的大小为4,第二个隐藏层的大小为2,使用tanh 激活函数,输出层有一个节点,使用sigmoid 激活函数。最后,编译模型并打印出模型的总结:

    np.random.seed(seed)
    random.set_seed(seed)
    # define the keras model
    classifier = Sequential()
    classifier.add(Dense(units = 4, activation = 'tanh', \
                         input_dim = X_train.shape[1]))
    classifier.add(Dense(units = 2, activation = 'tanh'))
    classifier.add(Dense(units = 1, activation = 'sigmoid'))
    classifier.compile(optimizer = 'sgd', loss = 'binary_crossentropy', \
                       metrics = ['accuracy'])
    classifier.summary()
    

    图 3.32:神经网络架构

    图 3.32:神经网络架构

  9. 将模型拟合到训练数据:

    history=classifier.fit(X_train, y_train, batch_size = 20, \
                           epochs = 100, validation_split=0.1, \
                           shuffle=False)
    
  10. 绘制具有两个隐藏层(大小分别为 4 和 2)的训练和测试误差图。打印在训练集和测试集上达到的最佳准确率:

    # plot training error and test error plots 
    plt.plot(history.history['loss'])
    plt.plot(history.history['val_loss'])
    plt.ylabel('loss')
    plt.xlabel('epoch')
    plt.legend(['train loss', 'validation loss'], loc='upper right')
    

    预期输出

    图 3.33:训练误差和测试误差率在训练模型时的变化图

    图 3.33:训练误差和测试误差率在训练模型时的变化图

  11. 打印在训练集和测试集上达到的最佳准确率,以及在测试数据集上评估的损失准确率值。

    print(f"Best Accuracy on training set = \
    {max(history.history['accuracy'])*100:.3f}%")
    print(f"Best Accuracy on validation set = \
    {max(history.history['val_accuracy'])*100:.3f}%") 
    test_loss, test_acc = \
    classifier.evaluate(X_test, y_test['AdvancedFibrosis'])
    print(f'The loss on the test set is {test_loss:.4f} and \
    the accuracy is {test_acc*100:.3f}%')
    

    以下展示了前面代码的输出结果:

    Best Accuracy on training set = 57.272%
    Best Accuracy on test set = 54.054%
    277/277 [==============================] - 0s 41us/step
    The loss on the test set is 0.7016 and the accuracy is 49.819%
    

    注意

    若要访问该部分的源代码,请参考 packt.live/2BrIRMF

    你也可以在线运行这个例子,访问 packt.live/2NUl22A

4. 使用 Keras 封装器进行交叉验证评估模型

活动 4.01:使用交叉验证评估高级肝纤维化诊断分类器模型

在这个活动中,我们将使用本主题中学到的内容,使用k 折交叉验证训练和评估深度学习模型。我们将使用前一个活动中得到的最佳测试误差率的模型,目标是将交叉验证的误差率与训练集/测试集方法的误差率进行比较。我们将使用的数据库是丙型肝炎 C 数据集,在该数据集中,我们将构建一个分类模型,预测哪些患者会患上晚期肝纤维化。按照以下步骤完成此活动:

  1. 加载数据集并打印数据集中的记录数和特征数,以及目标数据集中可能的类别数:

    # Load the dataset
    import pandas as pd
    X = pd.read_csv('../data/HCV_feats.csv')
    y = pd.read_csv('../data/HCV_target.csv')
    # 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]) 
    print("Possible Output Classes = ", \
          y['AdvancedFibrosis'].unique())
    

    这是预期的输出:

    Number of Examples in the Dataset = 1385
    Number of Features for each example = 28
    Possible Output Classes = [0 1]
    
  2. 定义一个返回 Keras 模型的函数。首先,导入 Keras 所需的库。在函数内部,实例化顺序模型并添加两个全连接层,第一个层的大小为4,第二个层的大小为2,两者均使用tanh 激活函数。添加输出层并使用sigmoid 激活函数。编译模型并返回模型:

    from keras.models import Sequential
    from keras.layers import Dense
    # Create the function that returns the keras model
    def build_model():
        model = Sequential()
        model.add(Dense(4, input_dim=X.shape[1], activation='tanh'))
        model.add(Dense(2, activation='tanh'))
        model.add(Dense(1, activation='sigmoid'))
        model.compile(loss='binary_crossentropy', optimizer='adam', \
                      metrics=['accuracy'])
        return model
    
  3. 使用StandardScaler函数对训练数据进行缩放。设置种子,以便模型可复现。定义超参数n_foldsepochsbatch_size。然后,使用 scikit-learn 构建 Keras 封装器,定义cross-validation迭代器,执行k 折交叉验证并存储得分:

    # import required packages
    import numpy as np
    from tensorflow import random
    from keras.wrappers.scikit_learn import KerasClassifier
    from sklearn.model_selection import StratifiedKFold
    from sklearn.model_selection import cross_val_score
    from sklearn.preprocessing import StandardScaler
    sc = StandardScaler()
    X = pd.DataFrame(sc.fit_transform(X), columns=X.columns)
    """
    define a seed for random number generator so the result will be reproducible
    """
    seed = 1
    np.random.seed(seed)
    random.set_seed(seed)
    """
    determine the number of folds for k-fold cross-validation, number of epochs and batch size
    """
    n_folds = 5
    epochs = 100
    batch_size = 20
    # build the scikit-learn interface for the keras model
    classifier = KerasClassifier(build_fn=build_model, \
                                 epochs=epochs, \
                                 batch_size=batch_size, \
                                 verbose=1, shuffle=False)
    # define the cross-validation iterator
    kfold = StratifiedKFold(n_splits=n_folds, shuffle=True, \
                            random_state=seed)
    """
    perform the k-fold cross-validation and store the scores in results
    """
    results = cross_val_score(classifier, X, y, cv=kfold)
    
  4. 对于每一折,打印存储在results参数中的准确率:

    # print accuracy for each fold
    for f in range(n_folds):
        print("Test accuracy at fold ", f+1, " = ", results[f])
    print("\n")
    """
    print overall cross-validation accuracy plus the standard deviation of the accuracies
    """
    print("Final Cross-validation Test Accuracy:", results.mean())
    print("Standard Deviation of Final Test Accuracy:", results.std())
    

    以下是预期的输出:

    Test accuracy at fold 1 = 0.5198556184768677
    Test accuracy at fold 2 = 0.4693140685558319
    Test accuracy at fold 3 = 0.512635350227356
    Test accuracy at fold 4 = 0.5740072131156921
    Test accuracy at fold 5 = 0.5523465871810913
    Final Cross-Validation Test Accuracy: 0.5256317675113678
    Standard Deviation of Final Test Accuracy: 0.03584760640500936
    

    注意

    要访问此特定部分的源代码,请参考packt.live/3eWgR2b

    你也可以在线运行这个示例,网址为:packt.live/3iBYtOi

活动 4.02:使用交叉验证为高级纤维化诊断分类器选择模型

在此活动中,我们将通过使用交叉验证来选择模型和超参数,从而改进针对肝炎 C 数据集的分类器。按照以下步骤完成此活动:

  1. 导入所有所需的包并加载数据集。使用StandardScaler函数对数据集进行标准化:

    # import the required packages
    from keras.models import Sequential
    from keras.layers import Dense
    from keras.wrappers.scikit_learn import KerasClassifier
    from sklearn.model_selection import StratifiedKFold
    from sklearn.model_selection import cross_val_score
    import numpy as np
    import pandas as pd
    from sklearn.preprocessing import StandardScaler
    from tensorflow import random
    # Load the dataset
    X = pd.read_csv('../data/HCV_feats.csv')
    y = pd.read_csv('../data/HCV_target.csv')
    sc = StandardScaler()
    X = pd.DataFrame(sc.fit_transform(X), columns=X.columns)
    
  2. 定义三个函数,每个函数返回一个不同的 Keras 模型。第一个模型应具有三个隐藏层,每层大小为 4,第二个模型应具有两个隐藏层,第一个隐藏层大小为 4,第二个隐藏层大小为 2,第三个模型应具有两个隐藏层,大小为 8。使用函数参数来传递激活函数和优化器,以便它们可以传递给模型。目标是找出这三个模型中哪一个导致了最低的交叉验证误差率:

    # Create the function that returns the keras model 1
    def build_model_1(activation='relu', optimizer='adam'):
        # create model 1
        model = Sequential()
        model.add(Dense(4, input_dim=X.shape[1], \
                        activation=activation))
        model.add(Dense(4, activation=activation))
        model.add(Dense(4, activation=activation))
        model.add(Dense(1, activation='sigmoid'))
        # Compile model
        model.compile(loss='binary_crossentropy', \
                      optimizer=optimizer, metrics=['accuracy'])
        return model
    # Create the function that returns the keras model 2
    def build_model_2(activation='relu', optimizer='adam'):
        # create model 2
        model = Sequential()
        model.add(Dense(4, input_dim=X.shape[1], \
                        activation=activation))
        model.add(Dense(2, activation=activation))
        model.add(Dense(1, activation='sigmoid'))
        # Compile model
        model.compile(loss='binary_crossentropy', \
                      optimizer=optimizer, metrics=['accuracy'])
        return model
    # Create the function that returns the keras model 3
    def build_model_3(activation='relu', optimizer='adam'):
        # create model 3
        model = Sequential()
        model.add(Dense(8, input_dim=X.shape[1], \
                        activation=activation))
        model.add(Dense(8, activation=activation))
        model.add(Dense(1, activation='sigmoid'))
        # Compile model
        model.compile(loss='binary_crossentropy', \
                      optimizer=optimizer, metrics=['accuracy'])
        return model
    

    编写代码,循环遍历三个模型并执行5 折交叉验证。设置随机种子以确保模型可重复,并定义n_foldsbatch_sizeepochs超参数。在训练模型时,存储应用cross_val_score函数的结果:

    """
    define a seed for random number generator so the result will be reproducible
    """
    seed = 2
    np.random.seed(seed)
    random.set_seed(seed)
    """
    determine the number of folds for k-fold cross-validation, number of epochs and batch size
    """
    n_folds = 5
    batch_size=20
    epochs=100
    # define the list to store cross-validation scores
    results_1 = []
    # define the possible options for the model
    models = [build_model_1, build_model_2, build_model_3]
    # loop over models
    for m in range(len(models)):
        # build the scikit-learn interface for the keras model
        classifier = KerasClassifier(build_fn=models[m], \
                                     epochs=epochs, \
                                     batch_size=batch_size, \
                                     verbose=0, shuffle=False)
        # define the cross-validation iterator
        kfold = StratifiedKFold(n_splits=n_folds, shuffle=True, \
                                random_state=seed)
        """
        perform the k-fold cross-validation and store the scores 
        in result
        """
        result = cross_val_score(classifier, X, y, cv=kfold)
        # add the scores to the results list 
        results_1.append(result)
    # Print cross-validation score for each model
    for m in range(len(models)):
        print("Model", m+1,"Test Accuracy =", results_1[m].mean())
    

    这是一个示例输出。在此实例中,模型 2具有最佳的交叉验证测试准确率,具体如下所示:

    Model 1 Test Accuracy = 0.4996389865875244
    Model 2 Test Accuracy = 0.5148014307022095
    Model 3 Test Accuracy = 0.5097472846508027
    
  3. 选择具有最高准确率得分的模型,并通过遍历epochs = [100, 200]batches = [10, 20]的值并执行5 折交叉验证来重复步骤 2

    """
    define a seed for random number generator so the result will be reproducible
    """
    np.random.seed(seed)
    random.set_seed(seed)
    # determine the number of folds for k-fold cross-validation
    n_folds = 5
    # define possible options for epochs and batch_size
    epochs = [100, 200]
    batches = [10, 20]
    # define the list to store cross-validation scores
    results_2 = []
    # loop over all possible pairs of epochs, batch_size
    for e in range(len(epochs)):
        for b in range(len(batches)):
            # build the scikit-learn interface for the keras model
            classifier = KerasClassifier(build_fn=build_model_2, \
                                         epochs=epochs[e], \
                                         batch_size=batches[b], \
                                         verbose=0)
            # define the cross-validation iterator
            kfold = StratifiedKFold(n_splits=n_folds, shuffle=True, \
                                    random_state=seed)
            # perform the k-fold cross-validation. 
            # store the scores in result
            result = cross_val_score(classifier, X, y, cv=kfold)
            # add the scores to the results list 
            results_2.append(result)
    """
    Print cross-validation score for each possible pair of epochs, batch_size
    """
    c = 0
    for e in range(len(epochs)):
        for b in range(len(batches)):
            print("batch_size =", batches[b],", epochs =", epochs[e], \
                  ", Test Accuracy =", results_2[c].mean())
            c += 1
    

    这是一个示例输出:

    batch_size = 10 , epochs = 100 , Test Accuracy = 0.5010830342769623
    batch_size = 20 , epochs = 100 , Test Accuracy = 0.5126353740692139
    batch_size = 10 , epochs = 200 , Test Accuracy = 0.5176895320416497
    batch_size = 20 , epochs = 200 , Test Accuracy = 0.5075812220573426
    

    在此案例中,batch_size= 10epochs=200的组合具有最佳的交叉验证测试准确率。

  4. 选择具有最高准确率得分的批处理大小和训练轮数,并通过遍历optimizers = ['rmsprop', 'adam', 'sgd']activations = ['relu', 'tanh']的值并执行5 折交叉验证来重复步骤 3

    """
    define a seed for random number generator so the result will be reproducible
    """
    np.random.seed(seed)
    random.set_seed(seed)
    """
    determine the number of folds for k-fold cross-validation, number of epochs and batch size
    """
    n_folds = 5
    batch_size = 10
    epochs = 200
    # define the list to store cross-validation scores
    results_3 = []
    # define possible options for optimizer and activation
    optimizers = ['rmsprop', 'adam','sgd']
    activations = ['relu', 'tanh']
    # loop over all possible pairs of optimizer, activation
    for o in range(len(optimizers)):
        for a in range(len(activations)):
            optimizer = optimizers[o]
            activation = activations[a]
            # build the scikit-learn interface for the keras model
            classifier = KerasClassifier(build_fn=build_model_2, \
                                         epochs=epochs, \
                                         batch_size=batch_size, \
                                         verbose=0, shuffle=False)
            # define the cross-validation iterator
            kfold = StratifiedKFold(n_splits=n_folds, shuffle=True, \
                                    random_state=seed)
            # perform the k-fold cross-validation. 
            # store the scores in result
            result = cross_val_score(classifier, X, y, cv=kfold)
            # add the scores to the results list 
            results_3.append(result)
    """
    Print cross-validation score for each possible pair of optimizer, activation
    """
    c = 0
    for o in range(len(optimizers)):
        for a in range(len(activations)):
            print("activation = ", activations[a],", optimizer = ", \
                  optimizers[o], ", Test accuracy = ", \
                  results_3[c].mean())
            c += 1
    

    以下是预期的输出:

    activation =  relu , optimizer =  rmsprop , 
    Test accuracy =  0.5234657049179077
    activation =  tanh , optimizer =  rmsprop , 
    Test accuracy =  0.49602887630462644
    activation =  relu , optimizer =  adam , 
    Test accuracy =  0.5039711117744445
    activation =  tanh , optimizer =  adam , 
    Test accuracy =  0.4989169597625732
    activation =  relu , optimizer =  sgd , 
    Test accuracy =  0.48953068256378174
    activation =  tanh , optimizer =  sgd , 
    Test accuracy =  0.5191335678100586
    

    在这里,activation='relu'optimizer='rmsprop'的组合具有最佳的交叉验证测试准确率。此外,activation='tanh'optimizer='sgd'的组合则取得了第二好的性能。

    注意

    要访问此特定部分的源代码,请参考packt.live/2D3AIhD

    你也可以在线运行这个示例,网址为:packt.live/2NUpiiC

活动 4.03:使用交叉验证选择交通量数据集的模型

在这个活动中,你将再次练习使用交叉验证进行模型选择。在这里,我们将使用一个模拟数据集,表示一个目标变量,表示城市桥梁上每小时的交通量,以及与交通数据相关的各种归一化特征,如一天中的时间和前一天的交通量。我们的目标是建立一个模型,根据这些特征预测城市桥梁上的交通量。按照以下步骤完成此活动:

  1. 导入所有必需的包并加载数据集:

    # import the required packages
    from keras.models import Sequential
    from keras.layers import Dense
    from keras.wrappers.scikit_learn import KerasRegressor
    from sklearn.model_selection import KFold
    from sklearn.model_selection import cross_val_score
    from sklearn.preprocessing import StandardScaler
    from sklearn.pipeline import make_pipeline
    import numpy as np
    import pandas as pd
    from tensorflow import random
    
  2. 加载数据集,打印特征数据集的输入和输出大小,并打印目标数据集中的可能类别。同时,打印输出的范围:

    # Load the dataset
    # Load the dataset
    X = pd.read_csv('../data/traffic_volume_feats.csv')
    y = pd.read_csv('../data/traffic_volume_target.csv') 
    # Print the sizes of input data and output data
    print("Input data size = ", X.shape)
    print("Output size = ", y.shape)
    # Print the range for output
    print(f"Output Range = ({y['Volume'].min()}, \
    { y['Volume'].max()})")
    

    这是预期的输出:

    Input data size =  (10000, 10)
    Output size =  (10000, 1)
    Output Range = (0.000000, 584.000000)
    
  3. 定义三个函数,每个函数返回一个不同的 Keras 模型。第一个模型应有一个大小为 10的隐藏层,第二个模型应有两个大小为 10的隐藏层,第三个模型应有三个大小为 10的隐藏层。使用函数参数来传递优化器,以便它们可以传递给模型。目标是找出这三种模型中哪个能带来最低的交叉验证错误率:

    # Create the function that returns the keras model 1
    def build_model_1(optimizer='adam'):
        # create model 1
        model = Sequential()
        model.add(Dense(10, input_dim=X.shape[1], activation='relu'))
        model.add(Dense(1))
        # Compile model
        model.compile(loss='mean_squared_error', optimizer=optimizer)
        return model
    # Create the function that returns the keras model 2
    def build_model_2(optimizer='adam'):
        # create model 2
        model = Sequential()
        model.add(Dense(10, input_dim=X.shape[1], activation='relu'))
        model.add(Dense(10, activation='relu'))
        model.add(Dense(1))
        # Compile model
        model.compile(loss='mean_squared_error', optimizer=optimizer)
        return model
    # Create the function that returns the keras model 3
    def build_model_3(optimizer='adam'):
        # create model 3
        model = Sequential()
        model.add(Dense(10, input_dim=X.shape[1], activation='relu'))
        model.add(Dense(10, activation='relu'))
        model.add(Dense(10, activation='relu'))
        model.add(Dense(1))
        # Compile model
        model.compile(loss='mean_squared_error', optimizer=optimizer)
        return model
    
  4. 编写代码,循环遍历三个模型并执行5 折交叉验证。设置随机种子以确保模型可复现,并定义n_folds超参数。存储在训练模型时应用cross_val_score函数的结果:

    """
    define a seed for random number generator so the result will be reproducible
    """
    seed = 1
    np.random.seed(seed)
    random.set_seed(seed)
    # determine the number of folds for k-fold cross-validation
    n_folds = 5
    # define the list to store cross-validation scores
    results_1 = []
    # define the possible options for the model
    models = [build_model_1, build_model_2, build_model_3]
    # loop over models
    for i in range(len(models)):
        # build the scikit-learn interface for the keras model
        regressor = KerasRegressor(build_fn=models[i], epochs=100, \
                                   batch_size=50, verbose=0, \
                                   shuffle=False)
        """
        build the pipeline of transformations so for each fold training 
        set will be scaled and test set will be scaled accordingly.
        """
        model = make_pipeline(StandardScaler(), regressor)
        # define the cross-validation iterator
        kfold = KFold(n_splits=n_folds, shuffle=True, \
                      random_state=seed)
        # perform the k-fold cross-validation. 
        # store the scores in result
        result = cross_val_score(model, X, y, cv=kfold)
        # add the scores to the results list 
        results_1.append(result)
    # Print cross-validation score for each model
    for i in range(len(models)):
        print("Model ", i+1," test error rate = ", \
              abs(results_1[i].mean()))
    

    以下是预期的输出:

    Model  1  test error rate =  25.48777518749237
    Model  2  test error rate =  25.30460816860199
    Model  3  test error rate =  25.390239462852474
    

    模型 2(一个两层神经网络)具有最低的测试错误率。

  5. 选择具有最低测试错误率的模型,并在迭代epochs = [80, 100]batches = [50, 25]时重复步骤 4,同时执行5 折交叉验证

    """
    define a seed for random number generator so the result will be reproducible
    """
    np.random.seed(seed)
    random.set_seed(seed)
    # determine the number of folds for k-fold cross-validation
    n_folds = 5
    # define the list to store cross-validation scores
    results_2 = []
    # define possible options for epochs and batch_size
    epochs = [80, 100]
    batches = [50, 25]
    # loop over all possible pairs of epochs, batch_size
    for i in range(len(epochs)):
        for j in range(len(batches)):
            # build the scikit-learn interface for the keras model
            regressor = KerasRegressor(build_fn=build_model_2, \
                                       epochs=epochs[i], \
                                       batch_size=batches[j], \
                                       verbose=0, shuffle=False)
            """
            build the pipeline of transformations so for each fold 
            training set will be scaled and test set will be scaled 
            accordingly.
            """
            model = make_pipeline(StandardScaler(), regressor)
            # define the cross-validation iterator
            kfold = KFold(n_splits=n_folds, shuffle=True, \
                          random_state=seed)
            # perform the k-fold cross-validation. 
            # store the scores in result
            result = cross_val_score(model, X, y, cv=kfold)
            # add the scores to the results list 
            results_2.append(result)
    """
    Print cross-validation score for each possible pair of epochs, batch_size
    """
    c = 0
    for i in range(len(epochs)):
        for j in range(len(batches)):
            print("batch_size = ", batches[j],\
                  ", epochs = ", epochs[i], \
                  ", Test error rate = ", abs(results_2[c].mean()))
            c += 1
    

    这是预期的输出:

    batch_size = 50 , epochs = 80 , Test error rate = 25.270704221725463
    batch_size = 25 , epochs = 80 , Test error rate = 25.309741401672362
    batch_size = 50 , epochs = 100 , Test error rate = 25.095393986701964
    batch_size = 25 , epochs = 100 , Test error rate = 25.24592453837395
    

    batch_size=5epochs=100的组合具有最低的测试错误率。

  6. 选择具有最高准确度的模型,并重复步骤 2,通过迭代optimizers = ['rmsprop', 'sgd', 'adam']并执行5 折交叉验证

    """
    define a seed for random number generator so the result will be reproducible
    """
    np.random.seed(seed)
    random.set_seed(seed)
    # determine the number of folds for k-fold cross-validation
    n_folds = 5
    # define the list to store cross-validation scores
    results_3 = []
    # define the possible options for the optimizer
    optimizers = ['adam', 'sgd', 'rmsprop']
    # loop over optimizers
    for i in range(len(optimizers)):
        optimizer=optimizers[i]
        # build the scikit-learn interface for the keras model
        regressor = KerasRegressor(build_fn=build_model_2, \
                                   epochs=100, batch_size=50, \
                                   verbose=0, shuffle=False)
        """
        build the pipeline of transformations so for each fold training 
        set will be scaled and test set will be scaled accordingly.
        """
        model = make_pipeline(StandardScaler(), regressor)
        # define the cross-validation iterator
        kfold = KFold(n_splits=n_folds, shuffle=True, \
                      random_state=seed)
        # perform the k-fold cross-validation. 
        # store the scores in result
        result = cross_val_score(model, X, y, cv=kfold)
        # add the scores to the results list 
        results_3.append(result)
    # Print cross-validation score for each optimizer
    for i in range(len(optimizers)):
        print("optimizer=", optimizers[i]," test error rate = ", \
              abs(results_3[i].mean()))
    

    这是预期的输出:

    optimizer= adam  test error rate =  25.391812739372256
    optimizer= sgd  test error rate =  25.140230269432067
    optimizer= rmsprop  test error rate =  25.217947859764102
    

    optimizer='sgd'具有最低的测试错误率,因此我们应继续使用这个模型。

    注意

    要访问此特定部分的源代码,请参考packt.live/31TcYaD

    你也可以在packt.live/3iq6iqb在线运行这个示例。

5. 改进模型准确性

活动 5.01:在 Avila 模式分类器上应用权重正则化

在此活动中,你将构建一个 Keras 模型,根据给定的网络架构和超参数值对 Avila 模式数据集进行分类。目标是对模型应用不同类型的权重正则化,即L1L2,并观察每种类型如何改变结果。按照以下步骤完成此活动:

  1. 加载数据集,并将数据集拆分为训练集测试集

    # Load the dataset
    import pandas as pd
    X = pd.read_csv('../data/avila-tr_feats.csv')
    y = pd.read_csv('../data/avila-tr_target.csv')
    """
    Split the dataset into training set and test set with a 0.8-0.2 ratio
    """
    from sklearn.model_selection import train_test_split
    seed = 1
    X_train, X_test, y_train, y_test = \
    train_test_split(X, y, test_size=0.2, random_state=seed)
    
  2. 定义一个包含三个隐藏层的 Keras 顺序模型,第一个隐藏层为 size 10,第二个隐藏层为 size 6,第三个隐藏层为 size 4。最后,编译模型:

    """
    define a seed for random number generator so the result will be reproducible
    """
    import numpy as np
    from tensorflow import random
    np.random.seed(seed)
    random.set_seed(seed)
    # define the keras model
    from keras.models import Sequential
    from keras.layers import Dense
    model_1 = Sequential()
    model_1.add(Dense(10, input_dim=X_train.shape[1], \
                      activation='relu'))
    model_1.add(Dense(6, activation='relu'))
    model_1.add(Dense(4, activation='relu'))
    model_1.add(Dense(1, activation='sigmoid'))
    model_1.compile(loss='binary_crossentropy', optimizer='sgd', \
                    metrics=['accuracy'])
    
  3. 将模型拟合到训练数据上以执行分类,并保存训练过程的结果:

    history=model_1.fit(X_train, y_train, batch_size = 20, epochs = 100, \
                        validation_data=(X_test, y_test), \
                        verbose=0, shuffle=False)
    
  4. 通过导入必要的库来绘制训练误差和测试误差的趋势,绘制损失和验证损失,并将它们保存在模型拟合训练过程时创建的变量中。打印出最大验证准确度:

    import matplotlib.pyplot as plt 
    import matplotlib
    %matplotlib inline 
    # plot training error and test error
    matplotlib.rcParams['figure.figsize'] = (10.0, 8.0) 
    plt.plot(history.history['loss'])
    plt.plot(history.history['val_loss'])
    plt.ylim(0,1)
    plt.ylabel('loss')
    plt.xlabel('epoch')
    plt.legend(['train loss', 'validation loss'], loc='upper right')
    # print the best accuracy reached on the test set
    print("Best Accuracy on Validation Set =", \
          max(history.history['val_accuracy']))
    

    以下是预期输出:

    图 5.13:训练过程中模型训练误差和验证误差的图    对于没有正则化的模型

    图 5.13:训练过程中模型在没有正则化情况下的训练误差和验证误差图

    验证损失随训练损失不断减少。尽管没有使用正则化,这仍然是一个相当不错的训练过程示例,因为偏差和方差都比较低。

  5. 重新定义模型,为每个隐藏层添加 L2 正则化器lambda=0.01。重复 步骤 3步骤 4 来训练模型并绘制 训练误差验证误差

    """
    set up a seed for random number generator so the result will be reproducible
    """
    np.random.seed(seed)
    random.set_seed(seed)
    # define the keras model with l2 regularization with lambda = 0.01
    from keras.regularizers import l2
    l2_param = 0.01
    model_2 = Sequential()
    model_2.add(Dense(10, input_dim=X_train.shape[1], \
                      activation='relu', \
                      kernel_regularizer=l2(l2_param)))
    model_2.add(Dense(6, activation='relu', \
                      kernel_regularizer=l2(l2_param)))
    model_2.add(Dense(4, activation='relu', \
                      kernel_regularizer=l2(l2_param)))
    model_2.add(Dense(1, activation='sigmoid'))
    model_2.compile(loss='binary_crossentropy', optimizer='sgd', \
                    metrics=['accuracy'])
    # train the model using training set while evaluating on test set
    history=model_2.fit(X_train, y_train, batch_size = 20, epochs = 100, \
                        validation_data=(X_test, y_test), \
                        verbose=0, shuffle=False)
    plt.plot(history.history['loss'])
    plt.plot(history.history['val_loss'])
    plt.ylim(0,1)
    plt.ylabel('loss')
    plt.xlabel('epoch')
    plt.legend(['train loss', 'validation loss'], loc='upper right')
    # print the best accuracy reached on the test set
    print("Best Accuracy on Validation Set =", \
          max(history.history['val_accuracy']))
    

    以下是预期输出:

    图 5.14:训练过程中模型训练误差和验证误差的图    对于具有 L2 权重正则化(lambda=0.01)的模型

    图 5.14:训练过程中具有 L2 权重正则化(lambda=0.01)模型的训练误差和验证误差图

    从前面的图中可以看出,测试误差在降到一定程度后几乎趋于平稳。训练过程结束时训练误差和验证误差之间的差距(偏差)稍微缩小,这表明模型对训练样本的过拟合有所减少。

  6. 使用 lambda=0.1L2 参数 重复前一步骤——使用新的 lambda 参数重新定义模型,拟合模型到训练数据,并重复 步骤 4 绘制训练误差和验证误差:

    """
    set up a seed for random number generator so the result will be reproducible
    """
    np.random.seed(seed)
    random.set_seed(seed)
    from keras.regularizers import l2
    l2_param = 0.1
    model_3 = Sequential()
    model_3.add(Dense(10, input_dim=X_train.shape[1], \
                      activation='relu', \
                      kernel_regularizer=l2(l2_param)))
    model_3.add(Dense(6, activation='relu', \
                      kernel_regularizer=l2(l2_param)))
    model_3.add(Dense(4, activation='relu', \
                      kernel_regularizer=l2(l2_param)))
    model_3.add(Dense(1, activation='sigmoid'))
    model_3.compile(loss='binary_crossentropy', optimizer='sgd', \
                    metrics=['accuracy'])
    # train the model using training set while evaluating on test set
    history=model_3.fit(X_train, y_train, batch_size = 20, \
                        epochs = 100, validation_data=(X_test, y_test), \
                        verbose=0, shuffle=False)
    # plot training error and test error
    matplotlib.rcParams['figure.figsize'] = (10.0, 8.0)
    plt.plot(history.history['loss'])
    plt.plot(history.history['val_loss'])
    plt.ylim(0,1)
    plt.ylabel('loss')
    plt.xlabel('epoch')
    plt.legend(['train loss', 'validation loss'], loc='upper right')
    # print the best accuracy reached on the test set
    print("Best Accuracy on Validation Set =", \
          max(history.history['val_accuracy']))
    

    以下是预期输出:

    图 5.15:训练过程中模型训练误差和验证误差的图    对于具有 L2 权重正则化(lambda=0.1)的模型

    图 5.15:训练过程中具有 L2 权重正则化(lambda=0.1)模型的训练误差和验证误差图

    训练和验证误差迅速达到平稳状态,且远高于我们使用较低 L2 参数 创建的模型,这表明我们对模型的惩罚过多,导致它没有足够的灵活性去学习训练数据的潜在函数。接下来,我们将减少正则化参数的值,以防止它对模型造成过多惩罚。

  7. 重复前一步骤,这次使用 lambda=0.005。重复 步骤 4 绘制训练误差和验证误差:

    """
    set up a seed for random number generator so the result will be reproducible
    """
    np.random.seed(seed)
    random.set_seed(seed)
    # define the keras model with l2 regularization with lambda = 0.05
    from keras.regularizers import l2
    l2_param = 0.005
    model_4 = Sequential()
    model_4.add(Dense(10, input_dim=X_train.shape[1], \
                      activation='relu', \
                      kernel_regularizer=l2(l2_param)))
    model_4.add(Dense(6, activation='relu', \
                      kernel_regularizer=l2(l2_param)))
    model_4.add(Dense(4, activation='relu', \
                      kernel_regularizer=l2(l2_param)))
    model_4.add(Dense(1, activation='sigmoid'))
    model_4.compile(loss='binary_crossentropy', optimizer='sgd', \
                    metrics=['accuracy'])
    # train the model using training set while evaluating on test set
    history=model_4.fit(X_train, y_train, batch_size = 20, \
                        epochs = 100, validation_data=(X_test, y_test), \
                        verbose=0, shuffle=False)
    # plot training error and test error
    matplotlib.rcParams['figure.figsize'] = (10.0, 8.0)
    plt.plot(history.history['loss'])
    plt.plot(history.history['val_loss'])
    plt.ylim(0,1)
    plt.ylabel('loss')
    plt.xlabel('epoch')
    plt.legend(['train loss', 'validation loss'], loc='upper right')
    # print the best accuracy reached on the test set
    print("Best Accuracy on Validation Set =", \
          max(history.history['val_accuracy'])) 
    

    以下是预期的输出:

    图 5.16:带有 L2 权重正则化(lambda=0.005)的模型在训练过程中训练误差和验证误差的图示

    图 5.16:带有 L2 权重正则化(lambda=0.005)的模型在训练过程中训练误差和验证误差的图示

    L2 权重 正则化的值在所有使用 L2 正则化 的模型中,在验证数据上评估时,获得了最高的准确度,但稍微低于没有正则化时的准确度。同样,在被减少到某个值之后,测试误差并没有显著增加,这表明模型没有过拟合训练样本。看起来 lambda=0.005L2 权重正则化 获得了最低的验证误差,同时防止了模型的过拟合。

  8. 向模型的隐藏层添加 L1 正则化器,其中 lambda=0.01。重新定义模型,使用新的 lambda 参数,拟合模型到训练数据,并重复 步骤 4 来绘制训练误差和验证误差:

    """
    set up a seed for random number generator so the result will be reproducible
    """
    np.random.seed(seed)
    random.set_seed(seed)
    # define the keras model with l1 regularization with lambda = 0.01
    from keras.regularizers import l1
    l1_param = 0.01
    model_5 = Sequential()
    model_5.add(Dense(10, input_dim=X_train.shape[1], \
                      activation='relu', \
                      kernel_regularizer=l1(l1_param)))
    model_5.add(Dense(6, activation='relu', \
                      kernel_regularizer=l1(l1_param)))
    model_5.add(Dense(4, activation='relu', \
                      kernel_regularizer=l1(l1_param)))
    model_5.add(Dense(1, activation='sigmoid'))
    model_5.compile(loss='binary_crossentropy', optimizer='sgd', \
                    metrics=['accuracy'])
    # train the model using training set while evaluating on test set
    history=model_5.fit(X_train, y_train, batch_size = 20, \
                        epochs = 100, validation_data=(X_test, y_test), \
                        verbose=0, shuffle=True)
    # plot training error and test error
    matplotlib.rcParams['figure.figsize'] = (10.0, 8.0)
    plt.plot(history.history['loss'])
    plt.plot(history.history['val_loss'])
    plt.ylim(0,1)
    plt.ylabel('loss')
    plt.xlabel('epoch')
    plt.legend(['train loss', 'validation loss'], loc='upper right')
    # print the best accuracy reached on the test set
    print("Best Accuracy on Validation Set =", \
          max(history.history['val_accuracy']))
    

    以下是预期的输出:

    图 5.17:训练过程中训练误差和验证误差的图示    对于带有 L1 权重正则化(lambda=0.01)的模型

    图 5.17:带有 L1 权重正则化(lambda=0.01)的模型在训练过程中训练误差和验证误差的图示

  9. 重复之前的步骤,将 lambda=0.005 应用于 L1 参数—使用新的 lambda 参数重新定义模型,拟合模型到训练数据,并重复 步骤 4 来绘制 训练误差验证误差

    """
    set up a seed for random number generator so the result will be reproducible
    """
    np.random.seed(seed)
    random.set_seed(seed)
    # define the keras model with l1 regularization with lambda = 0.1
    from keras.regularizers import l1
    l1_param = 0.005
    model_6 = Sequential()
    model_6.add(Dense(10, input_dim=X_train.shape[1], \
                      activation='relu', \
                      kernel_regularizer=l1(l1_param)))
    model_6.add(Dense(6, activation='relu', \
                      kernel_regularizer=l1(l1_param)))
    model_6.add(Dense(4, activation='relu', \
                      kernel_regularizer=l1(l1_param)))
    model_6.add(Dense(1, activation='sigmoid'))
    model_6.compile(loss='binary_crossentropy', optimizer='sgd', \
                    metrics=['accuracy'])
    # train the model using training set while evaluating on test set
    history=model_6.fit(X_train, y_train, batch_size = 20, \
                        epochs = 100, validation_data=(X_test, y_test), \
                        verbose=0, shuffle=False)
    # plot training error and test error
    matplotlib.rcParams['figure.figsize'] = (10.0, 8.0) 
    plt.plot(history.history['loss'])
    plt.plot(history.history['val_loss'])
    plt.ylim(0,1)
    plt.ylabel('loss')
    plt.xlabel('epoch')
    plt.legend(['train loss', 'validation loss'], loc='upper right')
    # print the best accuracy reached on the test set
    print("Best Accuracy on Validation Set =", \
           max(history.history['val_accuracy']))
    

    以下是预期的输出:

    图 5.18:带有 L1 权重正则化(lambda=0.005)的模型在训练过程中训练误差和验证误差的图示

    图 5.18:带有 L1 权重正则化(lambda=0.005)的模型在训练过程中训练误差和验证误差的图示

    看起来 lambda=0.005L1 权重正则化 在防止模型过拟合的同时,获得了更好的测试误差,因为 lambda=0.01 的值过于严格,导致模型无法学习训练数据的潜在函数。

  10. 向模型的隐藏层添加 L1L2 正则化器,其中 L1lambda=0.005L2lambda = 0.005。然后,重复 步骤 4 来绘制训练误差和验证误差:

    """
    set up a seed for random number generator so the result will be reproducible
    """
    np.random.seed(seed)
    random.set_seed(seed)
    """
    define the keras model with l1_l2 regularization with l1_lambda = 0.005 and l2_lambda = 0.005
    """
    from keras.regularizers import l1_l2
    l1_param = 0.005
    l2_param = 0.005
    model_7 = Sequential()
    model_7.add(Dense(10, input_dim=X_train.shape[1], \
                activation='relu', \
                kernel_regularizer=l1_l2(l1=l1_param, l2=l2_param)))
    model_7.add(Dense(6, activation='relu', \
                      kernel_regularizer=l1_l2(l1=l1_param, \
                                               l2=l2_param)))
    model_7.add(Dense(4, activation='relu', \
                      kernel_regularizer=l1_l2(l1=l1_param, \
                                               l2=l2_param)))
    model_7.add(Dense(1, activation='sigmoid'))
    model_7.compile(loss='binary_crossentropy', optimizer='sgd', \
                    metrics=['accuracy'])
    # train the model using training set while evaluating on test set
    history=model_7.fit(X_train, y_train, batch_size = 20, \
                        epochs = 100, validation_data=(X_test, y_test), \
                        verbose=0, shuffle=True)
    
    # plot training error and test error
    matplotlib.rcParams['figure.figsize'] = (10.0, 8.0)
    plt.plot(history.history['loss'])
    plt.plot(history.history['val_loss'])
    plt.ylim(0,1)
    plt.ylabel('loss')
    plt.xlabel('epoch')
    plt.legend(['train loss', 'validation loss'], loc='upper right')
    # print the best accuracy reached on the test set
    print("Best Accuracy on Validation Set =", \
           max(history.history['val_accuracy']))
    

    以下是预期的输出:

    图 5.19:带有 L1 lambda 等于 0.005 和 L2 lambda 等于 0.005 的模型在训练过程中训练误差和验证误差的图示

图 5.19:带有 L1 lambda 等于 0.005 和 L2 lambda 等于 0.005 的模型在训练过程中训练误差和验证误差的图示

尽管L1L2 正则化成功防止了模型的过拟合,但模型的方差非常低。然而,验证数据上的准确率并不像没有正则化训练的模型,或者使用L2 正则化 lambda=0.005L1 正则化 lambda=0.005参数单独训练的模型那样高。

注意

要访问此特定部分的源代码,请参考packt.live/31BUf34

你也可以在packt.live/38n291s上在线运行这个示例。

活动 5.02:在交通量数据集上使用 dropout 正则化

在这个活动中,你将从活动 4.03使用交叉验证对交通量数据集进行模型选择第四章使用 Keras 包装器进行交叉验证评估模型开始。你将使用训练集/测试集方法来训练和评估模型,绘制训练误差和泛化误差的趋势,并观察模型对数据示例的过拟合情况。然后,你将尝试通过使用 dropout 正则化来解决过拟合问题,从而提高模型的性能。特别地,你将尝试找出应该在模型的哪些层添加 dropout 正则化,以及什么rate值能够最大程度地提高该特定模型的性能。按照以下步骤完成这个练习:

  1. 使用 pandas 的read_csv函数加载数据集,使用train_test_split将数据集按80-20比例划分为训练集和测试集,并使用StandardScaler对输入数据进行标准化:

    # Load the dataset
    import pandas as pd
    X = pd.read_csv('../data/traffic_volume_feats.csv')
    y = pd.read_csv('../data/traffic_volume_target.csv')
    """
    Split the dataset into training set and test set with an 80-20 ratio
    """
    from sklearn.model_selection import train_test_split
    seed=1
    X_train, X_test, y_train, y_test = \
    train_test_split(X, y, test_size=0.2, random_state=seed)
    
  2. 设置一个随机种子,以便模型可以复现。接下来,定义一个包含两个size 10的隐藏层的 Keras 顺序模型,每个隐藏层都使用ReLU 激活函数。添加一个没有激活函数的输出层,并使用给定的超参数编译模型:

    """
    define a seed for random number generator so the result will be reproducible
    """
    import numpy as np
    from tensorflow import random
    np.random.seed(seed)
    random.set_seed(seed)
    from keras.models import Sequential
    from keras.layers import Dense
    # create model
    model_1 = Sequential()
    model_1.add(Dense(10, input_dim=X_train.shape[1], \
                      activation='relu'))
    model_1.add(Dense(10, activation='relu'))
    model_1.add(Dense(1))
    # Compile model
    model_1.compile(loss='mean_squared_error', optimizer='rmsprop')
    
  3. 使用给定的超参数训练模型:

    # train the model using training set while evaluating on test set
    history=model_1.fit(X_train, y_train, batch_size = 50, \
                        epochs = 200, validation_data=(X_test, y_test), \
                        verbose=0) 
    
  4. 绘制训练误差测试误差的趋势。打印在训练集和验证集上达到的最佳准确率:

    import matplotlib.pyplot as plt 
    import matplotlib
    %matplotlib inline 
    matplotlib.rcParams['figure.figsize'] = (10.0, 8.0) 
    # plot training error and test error plots 
    plt.plot(history.history['loss'])
    plt.plot(history.history['val_loss'])
    plt.ylim((0, 25000))
    plt.ylabel('loss')
    plt.xlabel('epoch')
    plt.legend(['train loss', 'validation loss'], loc='upper right')
    # print the best accuracy reached on the test set
    print("Lowest error on training set = ", \
          min(history.history['loss']))
    print("Lowest error on validation set = ", \
          min(history.history['val_loss']))
    

    以下是预期的输出:

    Lowest error on training set =  24.673954981565476
    Lowest error on validation set =  25.11553382873535
    

    图 5.20:训练过程中训练误差和验证误差的图表    对于没有正则化的模型

    图 5.20:没有正则化的模型在训练过程中训练误差和验证误差的图表

    在训练误差和验证误差值中,训练误差和验证误差之间的差距非常小,这表明模型的方差很低,这是一个好兆头。

  5. 重新定义模型,创建相同的模型架构。然而,这一次,在模型的第一个隐藏层添加rate=0.1的 dropout 正则化。重复步骤 3,使用训练数据训练模型,并重复步骤 4绘制训练误差和验证误差的趋势。然后,打印在验证集上达到的最佳准确率:

    """
    define a seed for random number generator so the result will be reproducible
    """
    np.random.seed(seed)
    random.set_seed(seed)
    from keras.layers import Dropout
    # create model
    model_2 = Sequential()
    model_2.add(Dense(10, input_dim=X_train.shape[1], \
                      activation='relu'))
    model_2.add(Dropout(0.1))
    model_2.add(Dense(10, activation='relu'))
    model_2.add(Dense(1))
    # Compile model
    model_2.compile(loss='mean_squared_error', \
                    optimizer='rmsprop')
    # train the model using training set while evaluating on test set
    history=model_2.fit(X_train, y_train, batch_size = 50, \
                        epochs = 200, validation_data=(X_test, y_test), \
                        verbose=0, shuffle=False)
    matplotlib.rcParams['figure.figsize'] = (10.0, 8.0)
    plt.plot(history.history['loss'])
    plt.plot(history.history['val_loss'])
    plt.ylim((0, 25000))
    plt.ylabel('loss')
    plt.xlabel('epoch')
    plt.legend(['train loss', 'validation loss'], loc='upper right')
    # print the best accuracy reached on the test set
    print("Lowest error on training set = ", \
          min(history.history['loss']))
    print("Lowest error on validation set = ", \
          min(history.history['val_loss']))
    

    以下是预期输出:

    Lowest error on training set =  407.8203821182251
    Lowest error on validation set =  54.58488750457764
    

    ![图 5.21:在使用 dropout 正则化(第一层 rate=0.1)训练模型时,训练误差和验证误差的曲线图]

    ](https://github.com/OpenDocCN/freelearn-dl-pt5-zh/raw/master/docs/dl-keras-ws/img/B15777_05_21.jpg)

    图 5.21:在使用 dropout 正则化(第一层 rate=0.1)训练模型时,训练误差和验证误差的曲线图

    训练误差和验证误差之间存在小的差距;然而,验证误差低于训练误差,表明模型没有对训练数据发生过拟合。

  6. 重复上一步,这次为模型的两个隐藏层添加rate=0.1的 dropout 正则化。重复步骤 3,在训练数据上训练模型,并重复步骤 4,绘制训练误差和验证误差的趋势。然后,打印在验证集上达到的最佳准确率:

    """
    define a seed for random number generator so the result will be reproducible
    """
    np.random.seed(seed)
    random.set_seed(seed)
    # create model
    model_3 = Sequential()
    model_3.add(Dense(10, input_dim=X_train.shape[1], \
                      activation='relu'))
    model_3.add(Dropout(0.1))
    model_3.add(Dense(10, activation='relu'))
    model_3.add(Dropout(0.1))
    model_3.add(Dense(1))
    # Compile model
    model_3.compile(loss='mean_squared_error', \
                    optimizer='rmsprop')
    # train the model using training set while evaluating on test set
    history=model_3.fit(X_train, y_train, batch_size = 50, \
                        epochs = 200, validation_data=(X_test, y_test), \
                        verbose=0, shuffle=False)
    matplotlib.rcParams['figure.figsize'] = (10.0, 8.0)
    plt.plot(history.history['loss'])
    plt.plot(history.history['val_loss'])
    plt.ylim((0, 25000))
    plt.ylabel('loss')
    plt.xlabel('epoch')
    plt.legend(['train loss', 'validation loss'], loc='upper right')
    # print the best accuracy reached on the test set
    print("Lowest error on training set = ", \
          min(history.history['loss']))
    print("Lowest error on validation set = ", \
          min(history.history['val_loss']))
    

    以下是预期输出:

    Lowest error on training set =  475.9299939632416
    Lowest error on validation set =  61.646054649353026
    

    ![图 5.22:在使用 dropout 正则化(rate=0.1)训练模型时,训练误差和验证误差的曲线图]

    使用 dropout 正则化(rate=0.1)在两个层上的模型

    ](https://github.com/OpenDocCN/freelearn-dl-pt5-zh/raw/master/docs/dl-keras-ws/img/B15777_05_22.jpg)

    图 5.22:在使用 dropout 正则化(rate=0.1)训练模型时,训练误差和验证误差的曲线图

    这里训练误差和验证误差之间的差距略有增大,主要是由于在模型第二个隐藏层上增加了正则化,导致训练误差的增加。

  7. 重复上一步,这次为模型的第一层添加rate=0.2的 dropout 正则化,为第二层添加rate=0.1的 dropout 正则化。重复步骤 3,在训练数据上训练模型,并重复步骤 4,绘制训练误差和验证误差的趋势。然后,打印在验证集上达到的最佳准确率:

    """
    define a seed for random number generator so the result will be reproducible
    """
    np.random.seed(seed)
    random.set_seed(seed)
    # create model
    model_4 = Sequential()
    model_4.add(Dense(10, input_dim=X_train.shape[1], \
                      activation='relu'))
    model_4.add(Dropout(0.2))
    model_4.add(Dense(10, activation='relu'))
    model_4.add(Dropout(0.1))
    model_4.add(Dense(1))
    # Compile model
    model_4.compile(loss='mean_squared_error', optimizer='rmsprop')
    # train the model using training set while evaluating on test set
    history=model_4.fit(X_train, y_train, batch_size = 50, epochs = 200, \
                        validation_data=(X_test, y_test), verbose=0)
    matplotlib.rcParams['figure.figsize'] = (10.0, 8.0)
    plt.plot(history.history['loss'])
    plt.plot(history.history['val_loss'])
    plt.ylim((0, 25000))
    plt.ylabel('loss')
    plt.xlabel('epoch')
    plt.legend(['train loss', 'validation loss'], loc='upper right')
    # print the best accuracy reached on the test set
    print("Lowest error on training set = ", \
          min(history.history['loss']))
    print("Lowest error on validation set = ", \
          min(history.history['val_loss']))
    

    以下是预期输出:

    Lowest error on training set =  935.1562484741211
    Lowest error on validation set =  132.39965686798095
    

    ![图 5.23:在使用 dropout 正则化(第一层 rate=0.2,第二层 rate=0.1)训练模型时,训练误差和验证误差的曲线图]

    ](https://github.com/OpenDocCN/freelearn-dl-pt5-zh/raw/master/docs/dl-keras-ws/img/B15777_05_23.jpg)

图 5.23:在使用 dropout 正则化(第一层 rate=0.2,第二层 rate=0.1)训练模型时,训练误差和验证误差的曲线图

由于正则化的增加,训练误差和验证误差之间的差距略有增大。在这种情况下,原始模型没有发生过拟合。因此,正则化增加了训练和验证数据集上的误差率。

注意

若要查看此特定部分的源代码,请参考packt.live/38mtDo7

你也可以在packt.live/31Isdmu上在线运行这个示例。

活动 5.03:Avila 模式分类器的超参数调整

在本次活动中,你将构建一个类似于前几次活动中的 Keras 模型,但这次你将向模型中添加正则化方法。然后,你将使用 scikit-learn 优化器对模型的超参数进行调优,包括正则化器的超参数。按照以下步骤完成本次活动:

  1. 加载数据集并导入库:

    # Load The dataset
    import pandas as pd
    X = pd.read_csv('../data/avila-tr_feats.csv')
    y = pd.read_csv('../data/avila-tr_target.csv')
    
  2. 定义一个函数,返回一个 Keras 模型,该模型具有三层隐藏层,第一层大小为10,第二层大小为6,第三层大小为4,并在每个隐藏层上应用L2 权重正则化ReLU 激活函数。使用给定的参数编译模型并返回模型:

    # Create the function that returns the keras model
    from keras.models import Sequential
    from keras.layers import Dense
    from keras.regularizers import l2
    def build_model(lambda_parameter):
        model = Sequential()
        model.add(Dense(10, input_dim=X.shape[1], \
                        activation='relu', \
                        kernel_regularizer=l2(lambda_parameter)))
        model.add(Dense(6, activation='relu', \
                        kernel_regularizer=l2(lambda_parameter)))
        model.add(Dense(4, activation='relu', \
                        kernel_regularizer=l2(lambda_parameter)))
        model.add(Dense(1, activation='sigmoid'))
        model.compile(loss='binary_crossentropy', \
                      optimizer='sgd', metrics=['accuracy'])
        return model
    
  3. 设置随机种子,使用 scikit-learn 封装器对我们在上一步中创建的模型进行封装,并定义要扫描的超参数。最后,使用超参数网格对模型执行GridSearchCV()并拟合模型:

    from keras.wrappers.scikit_learn import KerasClassifier
    from sklearn.model_selection import GridSearchCV
    """
    define a seed for random number generator so the result will be reproducible
    """
    import numpy as np
    from tensorflow import random
    seed = 1
    np.random.seed(seed)
    random.set_seed(seed)
    # create the Keras wrapper with scikit learn
    model = KerasClassifier(build_fn=build_model, verbose=0, \
                            shuffle=False)
    # define all the possible values for each hyperparameter
    lambda_parameter = [0.01, 0.5, 1]
    epochs = [50, 100]
    batch_size = [20]
    """
    create the dictionary containing all possible values of hyperparameters
    """
    param_grid = dict(lambda_parameter=lambda_parameter, \
                      epochs=epochs, batch_size=batch_size)
    # perform 5-fold cross-validation for ??????? store the results
    grid_seach = GridSearchCV(estimator=model, \
                              param_grid=param_grid, cv=5)
    results_1 = grid_seach.fit(X, y)
    
  4. 打印在拟合过程中我们创建的变量中存储的最佳交叉验证分数的结果。遍历所有参数并打印各折的准确率均值、准确率标准差以及参数本身:

    print("Best cross-validation score =", results_1.best_score_)
    print("Parameters for Best cross-validation score=", \
          results_1.best_params_)
    # print the results for all evaluated hyperparameter combinations
    accuracy_means = results_1.cv_results_['mean_test_score']
    accuracy_stds = results_1.cv_results_['std_test_score']
    parameters = results_1.cv_results_['params']
    for p in range(len(parameters)):
        print("Accuracy %f (std %f) for params %r" % \
              (accuracy_means[p], accuracy_stds[p], parameters[p]))
    

    以下是预期的输出:

    Best cross-validation score = 0.7673058390617371
    Parameters for Best cross-validation score= {'batch_size': 20, 
    'epochs': 100, 'lambda_parameter': 0.01}
    Accuracy 0.764621 (std 0.004330) for params {'batch_size': 20, 
    'epochs': 50, 'lambda_parameter': 0.01}
    Accuracy 0.589070 (std 0.008244) for params {'batch_size': 20, 
    'epochs': 50, 'lambda_parameter': 0.5}
    Accuracy 0.589070 (std 0.008244) for params {'batch_size': 20, 
    'epochs': 50, 'lambda_parameter': 1}
    Accuracy 0.767306 (std 0.015872) for params {'batch_size': 20, 
    'epochs': 100, 'lambda_parameter': 0.01}
    Accuracy 0.589070 (std 0.008244) for params {'batch_size': 20, 
    'epochs': 100, 'lambda_parameter': 0.5}
    Accuracy 0.589070 (std 0.008244) for params {'batch_size': 20, 
    'epochs': 100, 'lambda_parameter': 1}
    
  5. 重复步骤 3,使用GridSearchCV()lambda_parameter = [0.001, 0.01, 0.05, 0.1]batch_size = [20]epochs = [100]。使用5 折交叉验证对模型进行拟合,并打印整个网格的结果:

    """
    define a seed for random number generator so the result will be reproducible
    """
    np.random.seed(seed)
    random.set_seed(seed)
    # create the Keras wrapper with scikit learn
    model = KerasClassifier(build_fn=build_model, verbose=0, shuffle=False)
    # define all the possible values for each hyperparameter
    lambda_parameter = [0.001, 0.01, 0.05, 0.1]
    epochs = [100]
    batch_size = [20]
    """
    create the dictionary containing all possible values of hyperparameters
    """
    param_grid = dict(lambda_parameter=lambda_parameter, \
                      epochs=epochs, batch_size=batch_size)
    """
    search the grid, perform 5-fold cross-validation for each possible combination, store the results
    """
    grid_seach = GridSearchCV(estimator=model, \
                              param_grid=param_grid, cv=5)
    results_2 = grid_seach.fit(X, y)
    # print the results for best cross-validation score
    print("Best cross-validation score =", results_2.best_score_)
    print("Parameters for Best cross-validation score =", \
          results_2.best_params_)
    # print the results for the entire grid
    accuracy_means = results_2.cv_results_['mean_test_score']
    accuracy_stds = results_2.cv_results_['std_test_score']
    parameters = results_2.cv_results_['params']
    for p in range(len(parameters)):
        print("Accuracy %f (std %f) for params %r" % \
              (accuracy_means[p], accuracy_stds[p], parameters[p]))
    

    以下是预期的输出:

    Best cross-validation score = 0.786385428905487
    Parameters for Best cross-validation score = {'batch_size': 20, 
    'epochs': 100, 'lambda_parameter': 0.001}
    Accuracy 0.786385 (std 0.010177) for params {'batch_size': 20, 
    'epochs': 100, 'lambda_parameter': 0.001}
    Accuracy 0.693960 (std 0.084994) for params {'batch_size': 20, 
    'epochs': 100, 'lambda_parameter': 0.01}
    Accuracy 0.589070 (std 0.008244) for params {'batch_size': 20, 
    'epochs': 100, 'lambda_parameter': 0.05}
    Accuracy 0.589070 (std 0.008244) for params {'batch_size': 20, 
    'epochs': 100, 'lambda_parameter': 0.1}
    
  6. 重新定义一个函数,返回一个 Keras 模型,该模型具有三层隐藏层,第一层大小为10,第二层大小为6,第三层大小为4,并在每个隐藏层上应用dropout 正则化ReLU 激活函数。使用给定的参数编译模型并从函数中返回:

    # Create the function that returns the keras model
    from keras.layers import Dropout
    def build_model(rate):
        model = Sequential()
        model.add(Dense(10, input_dim=X.shape[1], activation='relu'))
        model.add(Dropout(rate))
        model.add(Dense(6, activation='relu'))
        model.add(Dropout(rate))
        model.add(Dense(4, activation='relu'))
        model.add(Dropout(rate))
        model.add(Dense(1, activation='sigmoid'))
        model.compile(loss='binary_crossentropy', \
                      optimizer='sgd', metrics=['accuracy'])
        return model
    
  7. 使用rate = [0, 0.1, 0.2]epochs = [50, 100],对模型进行GridSearchCV()调优。使用5 折交叉验证对模型进行拟合,并打印整个网格的结果:

    """
    define a seed for random number generator so the result will be reproducible
    """
    np.random.seed(seed)
    random.set_seed(seed)
    # create the Keras wrapper with scikit learn
    model = KerasClassifier(build_fn=build_model, verbose=0,shuffle=False)
    # define all the possible values for each hyperparameter
    rate = [0, 0.1, 0.2]
    epochs = [50, 100]
    batch_size = [20]
    """
    create the dictionary containing all possible values of hyperparameters
    """
    param_grid = dict(rate=rate, epochs=epochs, batch_size=batch_size)
    """
    perform 5-fold cross-validation for 10 randomly selected combinations, store the results
    """
    grid_seach = GridSearchCV(estimator=model, \
                              param_grid=param_grid, cv=5)
    results_3 = grid_seach.fit(X, y)
    # print the results for best cross-validation score
    print("Best cross-validation score =", results_3.best_score_)
    print("Parameters for Best cross-validation score =", \
          results_3.best_params_)
    # print the results for the entire grid
    accuracy_means = results_3.cv_results_['mean_test_score']
    accuracy_stds = results_3.cv_results_['std_test_score']
    parameters = results_3.cv_results_['params']
    for p in range(len(parameters)):
        print("Accuracy %f (std %f) for params %r" % \
              (accuracy_means[p], accuracy_stds[p], parameters[p]))
    

    以下是预期的输出:

    Best cross-validation score= 0.7918504476547241
    Parameters for Best cross-validation score= {'batch_size': 20, 
    'epochs': 100, 'rate': 0}
    Accuracy 0.786769 (std 0.008255) for params {'batch_size': 20, 
    'epochs': 50, 'rate': 0}
    Accuracy 0.764717 (std 0.007691) for params {'batch_size': 20, 
    'epochs': 50, 'rate': 0.1}
    Accuracy 0.752637 (std 0.013546) for params {'batch_size': 20, 
    'epochs': 50, 'rate': 0.2}
    Accuracy 0.791850 (std 0.008519) for params {'batch_size': 20, 
    'epochs': 100, 'rate': 0}
    Accuracy 0.779291 (std 0.009504) for params {'batch_size': 20, 
    'epochs': 100, 'rate': 0.1}
    Accuracy 0.767306 (std 0.005773) for params {'batch_size': 20, 
    'epochs': 100, 'rate': 0.2}
    
  8. 重复步骤 5,使用rate = [0.0, 0.05, 0.1]epochs = [100]。使用5 折交叉验证对模型进行拟合,并打印整个网格的结果:

    """
    define a seed for random number generator so the result will be reproducible
    """
    np.random.seed(seed)
    random.set_seed(seed)
    # create the Keras wrapper with scikit learn
    model = KerasClassifier(build_fn=build_model, verbose=0, shuffle=False)
    # define all the possible values for each hyperparameter
    rate = [0.0, 0.05, 0.1]
    epochs = [100]
    batch_size = [20]
    """
    create the dictionary containing all possible values of hyperparameters
    """
    param_grid = dict(rate=rate, epochs=epochs, batch_size=batch_size)
    """
    perform 5-fold cross-validation for 10 randomly selected combinations, store the results
    """
    grid_seach = GridSearchCV(estimator=model, \
                              param_grid=param_grid, cv=5)
    results_4 = grid_seach.fit(X, y)
    # print the results for best cross-validation score
    print("Best cross-validation score =", results_4.best_score_)
    print("Parameters for Best cross-validation score =", \
          results_4.best_params_)
    # print the results for the entire grid
    accuracy_means = results_4.cv_results_['mean_test_score']
    accuracy_stds = results_4.cv_results_['std_test_score']
    parameters = results_4.cv_results_['params']
    for p in range(len(parameters)):
        print("Accuracy %f (std %f) for params %r" % \
              (accuracy_means[p], accuracy_stds[p], parameters[p]))
    

    以下是预期的输出:

    Best cross-validation score= 0.7862895488739013
    Parameters for Best cross-validation score= {'batch_size': 20, 
    'epochs': 100, 'rate': 0.0}
    Accuracy 0.786290 (std 0.013557) for params {'batch_size': 20, 
    'epochs': 100, 'rate': 0.0}
    Accuracy 0.786098 (std 0.005184) for params {'batch_size': 20, 
    'epochs': 100, 'rate': 0.05}
    Accuracy 0.772004 (std 0.013733) for params {'batch_size': 20, 
    'epochs': 100, 'rate': 0.1}
    

    注意

    要访问此特定部分的源代码,请参考packt.live/2D7HN0L

    本节目前没有在线互动示例,需要在本地运行。

6. 模型评估

活动 6.01:当我们改变训练/测试数据集的拆分时,计算神经网络的准确率和空准确率

在这个活动中,我们将看到null accuracyaccuracy会受到train/test划分的影响。为实现这一点,需要修改定义训练/测试划分的代码部分。我们将使用在练习 6.02中使用的相同数据集,即使用 Scania 卡车数据计算准确率和零准确率。按照以下步骤完成此活动:

  1. 导入所需的库。使用 pandas 的read_csv函数加载数据集,并查看数据集的前行:

    # Import the libraries
    import numpy as np
    import pandas as pd
    # Load the Data
    X = pd.read_csv("../data/aps_failure_training_feats.csv")
    y = pd.read_csv("../data/aps_failure_training_target.csv")
    # Use the head function to get a glimpse data
    X.head()
    

    下表显示了前面代码的输出:

    图 6.13:数据集的初始五行

    图 6.13:数据集的初始五行

  2. test_sizerandom_state0.2042分别更改为0.313

    # Split the data into training and testing sets
    from sklearn.model_selection import train_test_split
    seed = 13
    X_train, X_test, y_train, y_test = \
    train_test_split(X, y, test_size=0.3, random_state=seed)
    

    注意

    如果使用不同的random_state,可能会得到不同的train/test划分,这可能会导致稍微不同的最终结果。

  3. 使用StandardScaler函数对数据进行缩放,并使用缩放器对测试数据进行缩放。将两者转换为 pandas DataFrame:

    # Initialize StandardScaler
    from sklearn.preprocessing import StandardScaler
    sc = StandardScaler()
    # Transform the training data
    X_train = sc.fit_transform(X_train)
    X_train = pd.DataFrame(X_train, columns=X_test.columns)
    # Transform the testing data
    X_test = sc.transform(X_test)
    X_test = pd.DataFrame(X_test, columns = X_train.columns)
    

    注意

    sc.fit_transform()函数转换数据,同时数据也被转换为NumPy数组。我们可能稍后需要将数据作为 DataFrame 对象进行分析,因此使用pd.DataFrame()函数将数据重新转换为 DataFrame。

  4. 导入构建神经网络架构所需的库:

    # Import the relevant Keras libraries
    from keras.models import Sequential
    from keras.layers import Dense
    from keras.layers import Dropout
    from tensorflow import random
    
  5. 启动Sequential类:

    # Initiate the Model with Sequential Class
    np.random.seed(seed)
    random.set_seed(seed)
    model = Sequential()
    
  6. 向网络中添加五个Dense层,并使用Dropout。设置第一个隐藏层的大小为64,丢弃率为0.5;第二个隐藏层的大小为32,丢弃率为0.4;第三个隐藏层的大小为16,丢弃率为0.3;第四个隐藏层的大小为8,丢弃率为0.2;最后一个隐藏层的大小为4,丢弃率为0.1。将所有激活函数设置为ReLU

    # Add the hidden dense layers and with dropout Layer
    model.add(Dense(units=64, activation='relu', \
                    kernel_initializer='uniform', \
                    input_dim=X_train.shape[1]))
    model.add(Dropout(rate=0.5))
    model.add(Dense(units=32, activation='relu', \
                    kernel_initializer='uniform', \
                    input_dim=X_train.shape[1]))
    model.add(Dropout(rate=0.4))
    model.add(Dense(units=16, activation='relu', \
                    kernel_initializer='uniform', \
                    input_dim=X_train.shape[1]))
    model.add(Dropout(rate=0.3))
    model.add(Dense(units=8, activation='relu', \
                    kernel_initializer='uniform', \
                    input_dim=X_train.shape[1]))
    model.add(Dropout(rate=0.2))
    model.add(Dense(units=4, activation='relu', \
                    kernel_initializer='uniform'))
    model.add(Dropout(rate=0.1))
    
  7. 添加一个输出Dense层,并使用sigmoid激活函数:

    # Add Output Dense Layer
    model.add(Dense(units=1, activation='sigmoid', \
                    kernel_initializer='uniform'))
    

    注意

    由于输出是二分类的,我们使用sigmoid函数。如果输出是多类的(即超过两个类别),则应使用softmax函数。

  8. 编译网络并拟合模型。这里使用的度量标准是accuracy

    # Compile the Model
    model.compile(optimizer='adam', loss='binary_crossentropy', \
                  metrics=['accuracy'])
    

    注意

    在我们这例中,度量标准是accuracy,已在前面的代码中定义。

  9. 使用100轮训练、批量大小为20、验证集划分比例为0.2来训练模型:

    # Fit the Model
    model.fit(X_train, y_train, epochs=100, batch_size=20, \
              verbose=1, validation_split=0.2, shuffle=False)
    
  10. 在测试数据集上评估模型,并打印出lossaccuracy的值:

    test_loss, test_acc = model.evaluate(X_test, y_test)
    print(f'The loss on the test set is {test_loss:.4f} and \
    the accuracy is {test_acc*100:.4f}%')
    

    前面的代码产生以下输出:

    18000/18000 [==============================] - 0s 19us/step
    The loss on the test set is 0.0766 and the accuracy is 98.9833%
    

    模型返回的准确率为98.9833%。但这足够好吗?我们只能通过与零准确率进行比较来回答这个问题。

  11. 现在,计算空准确率。空准确率可以使用pandas库的value_count函数计算,之前在本章的练习 6.01中,计算太平洋飓风数据集上的空准确率时,我们已经用过这个函数:

    # Use the value_count function to calculate distinct class values
    y_test['class'].value_counts()
    

    上述代码将产生以下输出:

    0    17700
    1      300
    Name: class, dtype: int64
    
  12. 计算空准确率

    # Calculate the null accuracy
    y_test['class'].value_counts(normalize=True).loc[0]
    

    上述代码将产生以下输出:

    0.9833333333333333
    

    注意

    要访问此特定部分的源代码,请参阅packt.live/3eY7y1E

    你也可以在packt.live/2BzBO4n在线运行此示例。

活动 6.02:计算 ROC 曲线和 AUC 得分

ROC 曲线AUC 得分是一种有效的方式,能够轻松评估二分类器的性能。在这个活动中,我们将绘制ROC 曲线并计算模型的AUC 得分。我们将使用相同的数据集并训练与练习 6.03中相同的模型,基于混淆矩阵推导和计算指标。继续使用相同的 APS 故障数据,绘制ROC 曲线并计算模型的AUC 得分。按照以下步骤完成这个活动:

  1. 导入必要的库,并使用 pandas 的read_csv函数加载数据:

    # Import the libraries
    import numpy as np
    import pandas as pd
    # Load the Data
    X = pd.read_csv("../data/aps_failure_training_feats.csv")
    y = pd.read_csv("../data/aps_failure_training_target.csv")
    
  2. 使用train_test_split函数将数据集分割为训练集和测试集:

    from sklearn.model_selection import train_test_split
    seed = 42
    X_train, X_test, y_train, y_test = \
    train_test_split(X, y, test_size=0.20, random_state=seed)
    
  3. 使用StandardScaler函数对特征数据进行缩放,使其具有0均值1标准差。对训练数据进行拟合,并将其应用于测试数据

    from sklearn.preprocessing import StandardScaler
    sc = StandardScaler()
    # Transform the training data
    X_train = sc.fit_transform(X_train)
    X_train = pd.DataFrame(X_train,columns=X_test.columns)
    # Transform the testing data
    X_test = sc.transform(X_test)
    X_test = pd.DataFrame(X_test,columns=X_train.columns)
    
  4. 导入创建模型所需的 Keras 库。实例化一个Sequential类的 Keras 模型,并向模型中添加五个隐藏层,包括每层的丢弃层。第一个隐藏层应具有64的大小和0.5的丢弃率。第二个隐藏层应具有32的大小和0.4的丢弃率。第三个隐藏层应具有16的大小和0.3的丢弃率。第四个隐藏层应具有8的大小和0.2的丢弃率。最后一个隐藏层应具有4的大小和0.1的丢弃率。所有隐藏层应具有ReLU 激活函数,并将kernel_initializer = 'uniform'。在模型中添加一个最终的输出层,并使用 sigmoid 激活函数。通过计算训练过程中准确率指标来编译模型:

    # Import the relevant Keras libraries
    from keras.models import Sequential
    from keras.layers import Dense
    from keras.layers import Dropout
    from tensorflow import random
    np.random.seed(seed)
    random.set_seed(seed)
    model = Sequential()
    # Add the hidden dense layers with dropout Layer
    model.add(Dense(units=64, activation='relu', \
                    kernel_initializer='uniform', \
                    input_dim=X_train.shape[1]))
    model.add(Dropout(rate=0.5))
    model.add(Dense(units=32, activation='relu', \
                    kernel_initializer='uniform'))
    model.add(Dropout(rate=0.4))
    model.add(Dense(units=16, activation='relu', \
                    kernel_initializer='uniform'))
    model.add(Dropout(rate=0.3))
    model.add(Dense(units=8, activation='relu', \
              kernel_initializer='uniform'))
    model.add(Dropout(rate=0.2))
    model.add(Dense(units=4, activation='relu', \
                    kernel_initializer='uniform'))
    model.add(Dropout(rate=0.1))
    # Add Output Dense Layer
    model.add(Dense(units=1, activation='sigmoid', \
                    kernel_initializer='uniform'))
    # Compile the Model
    model.compile(optimizer='adam', loss='binary_crossentropy', \
                  metrics=['accuracy'])
    
  5. 使用100个训练周期、batch_size=20validation_split=0.2将模型拟合到训练数据:

    model.fit(X_train, y_train, epochs=100, batch_size=20, \
              verbose=1, validation_split=0.2, shuffle=False)
    
  6. 一旦模型完成了对训练数据的拟合,创建一个变量,该变量是模型对测试数据的预测结果,使用模型的predict_proba方法:

    y_pred_prob = model.predict_proba(X_test)
    
  7. 从 scikit-learn 导入roc_curve并运行以下代码:

    from sklearn.metrics import roc_curve
    fpr, tpr, thresholds = roc_curve(y_test, y_pred_prob)
    

    fpr = 假阳性率(1 - 特异性)

    tpr = 真阳性率(灵敏度)

    thresholds = y_pred_prob的阈值

  8. 运行以下代码使用matplotlib.pyplot绘制ROC 曲线

    import matplotlib.pyplot as plt
    plt.plot(fpr, tpr)
    plt.title("ROC Curve for APS Failure")
    plt.xlabel("False Positive rate (1-Specificity)")
    plt.ylabel("True Positive rate (Sensitivity)")
    plt.grid(True)
    plt.show()
    

    以下图表显示了前述代码的输出:

    图 6.14:APS 失败数据集的 ROC 曲线

    ](https://github.com/OpenDocCN/freelearn-dl-pt5-zh/raw/master/docs/dl-keras-ws/img/B15777_06_14.jpg)

    图 6.14:APS 失败数据集的 ROC 曲线

  9. 使用roc_auc_score函数计算 AUC 分数:

    from sklearn.metrics import roc_auc_score
    roc_auc_score(y_test,y_pred_prob)
    

    以下是前述代码的输出:

    0.944787151628455
    

    94.4479%的 AUC 分数表明我们的模型表现优秀,符合上面列出的普遍可接受的AUC 分数标准。

    注意

    若要访问此特定部分的源代码,请参考packt.live/2NUOgyh

    你也可以在packt.live/2As33NH在线运行此示例。

7. 卷积神经网络的计算机视觉

活动 7.01:通过多个层和使用 softmax 来修改我们的模型

让我们尝试提高图像分类算法的性能。有很多方法可以提升性能,其中最直接的一种方式就是向模型中添加多个 ANN 层,这将在本活动中讲解。我们还将把激活函数从 sigmoid 更改为 softmax。然后,我们可以将结果与之前练习的结果进行比较。按照以下步骤完成此活动:

  1. 导入numpy库以及必要的 Keras 库和类:

    # Import the Libraries 
    from keras.models import Sequential
    from keras.layers import Conv2D, MaxPool2D, Flatten, Dense
    import numpy as np
    from tensorflow import random
    
  2. 现在,使用Sequential类初始化模型:

    # Initiate the classifier
    seed = 1
    np.random.seed(seed)
    random.set_seed(seed)
    classifier=Sequential()
    
  3. 添加 CNN 的第一层,设置输入形状为(64, 64, 3),即每个图像的维度,并设置激活函数为 ReLU。然后,添加32个大小为(3, 3)的特征检测器。再添加两层卷积层,每层有32个大小为(3, 3)的特征检测器,且都使用ReLU 激活函数:

    classifier.add(Conv2D(32,(3,3),input_shape=(64,64,3),\
                   activation='relu'))
    classifier.add(Conv2D(32,(3,3),activation = 'relu'))
    classifier.add(Conv2D(32,(3,3),activation = 'relu'))
    

    32, (3, 3)表示有32个大小为3x3的特征检测器。作为良好的实践,始终从32开始;你可以稍后添加64128

  4. 现在,添加池化层,图像大小为2x2

    classifier.add(MaxPool2D(pool_size=(2,2)))
    
  5. 通过向CNN 模型添加 flatten 层,来展平池化层的输出:

    classifier.add(Flatten())
    
  6. 添加 ANN 的第一层密集层。此处,128是节点数量的输出。作为一个良好的实践,128是一个不错的起点。activationrelu。作为一个良好的实践,建议使用 2 的幂:

    classifier.add(Dense(units=128,activation='relu')) 
    
  7. 向 ANN 中添加三层相同大小为128的层,并配以ReLU 激活函数:

    classifier.add(Dense(128,activation='relu'))
    classifier.add(Dense(128,activation='relu'))
    classifier.add(Dense(128,activation='relu'))
    
  8. 添加 ANN 的输出层。将 sigmoid 函数替换为softmax

    classifier.add(Dense(units=1,activation='softmax')) 
    
  9. 使用Adam 优化器编译网络,并在训练过程中计算准确率:

    # Compile The network
    classifier.compile(optimizer='adam', loss='binary_crossentropy', \
                       metrics=['accuracy'])
    
  10. 创建训练和测试数据生成器。通过1/255重新缩放训练和测试图像,使所有值都介于01之间。仅为训练数据生成器设置以下参数:shear_range=0.2zoom_range=0.2horizontal_flip=True

    from keras.preprocessing.image import ImageDataGenerator
    train_datagen = ImageDataGenerator(rescale = 1./255, \
                                       shear_range = 0.2, \
                                       zoom_range = 0.2, \
                                       horizontal_flip = True)
    test_datagen = ImageDataGenerator(rescale = 1./255)
    
  11. training set文件夹创建训练集。'../dataset/training_set'是我们存放数据的文件夹。我们的 CNN 模型的图像大小为64x64,因此这里也应该传递相同的大小。batch_size是每个批次中的图像数量,设置为32class_mode设置为binary,因为我们正在处理二分类器:

    training_set = \
    train_datagen.flow_from_directory('../dataset/training_set', \
                                      target_size = (64, 64), \
                                      batch_size = 32, \
                                      class_mode = 'binary')
    
  12. 对测试数据重复步骤 6,将文件夹设置为测试图像的所在位置,即'../dataset/test_set'

    test_set = \
    test_datagen.flow_from_directory('../dataset/test_set', \
                                     target_size = (64, 64), \
                                     batch_size = 32, \
                                     class_mode = 'binary')
    
  13. 最后,拟合数据。将steps_per_epoch设置为10000,将validation_steps设置为2500。以下步骤可能需要一些时间来执行:

    classifier.fit_generator(training_set, steps_per_epoch = 10000, \
                             epochs = 2, validation_data = test_set, \
                             validation_steps = 2500, shuffle=False)
    

    上述代码生成以下输出:

    Epoch 1/2
    10000/10000 [==============================] - 2452s 245ms/step - loss: 8.1783 - accuracy: 0.4667 - val_loss: 11.4999 - val_accuracy: 0.4695
    Epoch 2/2
    10000/10000 [==============================] - 2496s 250ms/step - loss: 8.1726 - accuracy: 0.4671 - val_loss: 10.5416 - val_accuracy: 0.4691
    

    请注意,由于新的 softmax 激活函数,准确率已降至46.91%

    注意

    要访问此特定部分的源代码,请参考packt.live/3gj0TiA

    您也可以在线运行此示例:packt.live/2VIDj7e

活动 7.02:分类新图像

在本次活动中,您将尝试分类另一张新图像,就像我们在前面的练习中所做的那样。该图像尚未暴露于算法,因此我们将使用此活动来测试我们的算法。您可以运行本章中的任何算法(虽然推荐使用获得最高准确率的算法),然后使用该模型对您的图像进行分类。按照以下步骤完成此活动:

  1. 运行本章中的一个算法。

  2. 加载图像并处理它。'test_image_2.jpg'是测试图像的路径。请在代码中更改为您保存数据集的路径:

    from keras.preprocessing import image
    new_image = \
    image.load_img('../test_image_2.jpg', target_size = (64, 64))
    new_image
    
  3. 您可以使用以下代码查看类标签:

    training_set.class_indices
    
  4. 通过使用img_to_array函数将图像转换为numpy数组来处理图像。然后,使用numpyexpand_dims函数沿第 0 轴添加一个额外的维度:

    new_image = image.img_to_array(new_image)
    new_image = np.expand_dims(new_image, axis = 0)
    
  5. 通过调用分类器的predict方法来预测新图像:

    result = classifier.predict(new_image)
    
  6. 使用class_indices方法结合if…else语句,将预测的 0 或 1 输出映射到一个类标签:

    if result[0][0] == 1:
        prediction = 'It is a flower'
    else:
        prediction = 'It is a car'
    print(prediction)
    

    上述代码生成以下输出:

    It is a flower
    

    test_image_2是一张花卉图像,预测结果为花卉。

    注意

    要访问此特定部分的源代码,请参考packt.live/38ny95E

    您也可以在线运行此示例:packt.live/2VIM4Ow

8. 转移学习与预训练模型

活动 8.01:使用 VGG16 网络训练深度学习网络以识别图像

使用VGG16网络预测给定的图像(test_image_1)。在开始之前,请确保您已将图像(test_image_1)下载到工作目录中。按照以下步骤完成此活动:

  1. 导入numpy库和必要的Keras库:

    import numpy as np
    from keras.applications.vgg16 import VGG16, preprocess_input
    from keras.preprocessing import image 
    
  2. 初始化模型(注意,此时您还可以查看网络架构,如以下代码所示):

    classifier = VGG16()
    classifier.summary()
    

    classifier.summary() 显示了网络的架构。需要注意以下几点:它具有四维输入形状(None, 224, 224, 3),并且有三层卷积层。

    输出的最后四层如下:

    图 8.16:网络的架构

    图 8.16:网络的架构

  3. 加载图像。 '../Data/Prediction/test_image_1.jpg' 是我们系统中图像的路径,在您的系统上会有所不同:

    new_image = \
    image.load_img('../Data/Prediction/test_image_1.jpg', \
                   target_size=(224, 224))
    new_image
    

    以下图显示了前面代码的输出:

    图 8.17:示例摩托车图像

    图 8.17:示例摩托车图像

    目标大小应该是 224x 224,因为 VGG16 仅接受(224, 224)。

  4. 使用 img_to_array 函数将图像转换为数组:

    transformed_image = image.img_to_array(new_image)
    transformed_image.shape
    

    前面的代码提供了以下输出:

    (224, 224, 3)
    
  5. 图像应该是四维形式的,以便 VGG16 允许进一步处理。按如下方式扩展图像的维度:

    transformed_image = np.expand_dims(transformed_image, axis=0)
    transformed_image.shape
    

    前面的代码提供了以下输出:

    (1, 224, 224, 3)
    
  6. 预处理图像:

    transformed_image = preprocess_input(transformed_image)
    transformed_image
    

    以下图显示了前面代码的输出:

    图 8.18:图像预处理

    图 8.18:图像预处理

  7. 创建 predictor 变量:

    y_pred = classifier.predict(transformed_image)
    y_pred
    

    以下图显示了前面代码的输出:

    图 8.19:创建预测变量

    图 8.19:创建预测变量

  8. 检查图像的形状。它应该是(1,1000)。之所以是 1000,是因为如前所述,ImageNet 数据库有 1000 个图像类别。预测变量显示了我们图像属于这些图像类别之一的概率:

    y_pred.shape
    

    前面的代码提供了以下输出:

    (1, 1000)
    
  9. 使用 decode_predictions 函数打印我们图像的前五个概率,并传递预测变量 y_pred 的函数,以及预测数量和对应标签以输出:

    from keras.applications.vgg16 import decode_predictions
    decode_predictions(y_pred, top=5)
    

    前面的代码提供了以下输出:

    [[('n03785016', 'moped', 0.8433369),
      ('n03791053', 'motor_scooter', 0.14188054),
      ('n03127747', 'crash_helmet', 0.007004856),
      ('n03208938', 'disk_brake', 0.0022349996),
      ('n04482393', 'tricycle', 0.0007717237)]]
    

    数组的第一列是内部编码号,第二列是标签,第三列是图像属于该标签的概率。

  10. 将预测转换为人类可读的格式。我们需要从输出中提取最可能的标签,如下所示:

    label = decode_predictions(y_pred)
    """
    Most likely result is retrieved, for example, the highest probability
    """
    decoded_label = label[0][0]
    # The classification is printed
    print('%s (%.2f%%)' % (decoded_label[1], decoded_label[2]*100 ))
    

    前面的代码提供了以下输出:

    moped (84.33%)
    

    在这里,我们可以看到该图片有 84.33% 的概率是摩托车,这与摩托车足够接近,可能表示在 ImageNet 数据集中摩托车被标记为踏板车。

    注意

    若要访问此特定部分的源代码,请参考 packt.live/2C4nqRo

    您还可以在 packt.live/31JMPL4 在线运行此示例。

活动 8.02:使用 ResNet 进行图像分类

在本活动中,我们将使用另一个预训练网络,称为 ResNet。我们有一张位于 ../Data/Prediction/test_image_4 的电视图像。我们将使用 ResNet50 网络来预测这张图像。请按照以下步骤完成此活动:

  1. 导入 numpy 库和必要的 Keras 库:

    import numpy as np
    from keras.applications.resnet50 import ResNet50, preprocess_input
    from keras.preprocessing import image 
    
  2. 初始化 ResNet50 模型并打印模型的总结:

    classifier = ResNet50()
    classifier.summary()
    

    classifier.summary() 显示了网络的架构,以下几点需要注意:

    图 8.20:输出的最后四层

    图 8.20:输出的最后四层

    注意

    最后一层预测(Dense)有 1000 个值。这意味着 VGG16 总共有 1000 个标签,我们的图像将属于这 1000 个标签中的一个。

  3. 加载图像。'../Data/Prediction/test_image_4.jpg' 是我们系统上图像的路径,在你的系统中会有所不同:

    new_image = \
    image.load_img('../Data/Prediction/test_image_4.jpg', \
                   target_size=(224, 224))
    new_image
    

    以下是上述代码的输出:

    图 8.21:一张电视的示例图像

    图 8.21:一张电视的示例图像

    目标大小应该是 224x224,因为 ResNet50 只接受 (224,224)。

  4. 使用 img_to_array 函数将图像转换为数组:

    transformed_image = image.img_to_array(new_image)
    transformed_image.shape
    
  5. 为了使 ResNet50 允许进一步处理,图像必须为四维形式。使用 expand_dims 函数沿第 0 维扩展图像的维度:

    transformed_image = np.expand_dims(transformed_image, axis=0)
    transformed_image.shape
    
  6. 使用 preprocess_input 函数对图像进行预处理:

    transformed_image = preprocess_input(transformed_image)
    transformed_image
    
  7. 使用分类器的 predict 方法,通过创建预测变量来预测图像:

    y_pred = classifier.predict(transformed_image)
    y_pred
    
  8. 检查图像的形状。它应该是(1,1000):

    y_pred.shape
    

    上述代码提供了以下输出:

    (1, 1000)
    
  9. 使用 decode_predictions 函数,传递预测变量 y_pred 作为参数,选择最顶端的五个概率及其对应的标签:

    from keras.applications.resnet50 import decode_predictions
    decode_predictions(y_pred, top=5)
    

    上述代码提供了以下输出:

    [[('n04404412', 'television', 0.99673873),
      ('n04372370', 'switch', 0.0009829825),
      ('n04152593', 'screen', 0.00095111143),
      ('n03782006', 'monitor', 0.0006477369),
      ('n04069434', 'reflex_camera', 8.5398955e-05)]]
    

    数组的第一列是内部代码编号,第二列是标签,第三列是图像与标签匹配的概率。

  10. 将预测结果转化为人类可读的格式。从 decode_predictions 函数的输出中打印最可能的标签:

    label = decode_predictions(y_pred)
    """
    Most likely result is retrieved, for example, 
    the highest probability
    """
    decoded_label = label[0][0]
    # The classification is printed 
    print('%s (%.2f%%)' % (decoded_label[1], decoded_label[2]*100 ))
    

    上述代码产生了以下输出:

    television (99.67%)
    

    注意

    要访问此特定部分的源代码,请参考 packt.live/38rEe0M

    你也可以在网上运行这个示例,网址是 packt.live/2YV5xxo

9. 使用递归神经网络进行序列建模

活动 9.01:使用 50 个单元(神经元)的 LSTM 预测亚马逊股价趋势

在这个活动中,我们将研究亚马逊过去 5 年的股票价格——从 2014 年 1 月 1 日到 2018 年 12 月 31 日。通过这一过程,我们将尝试使用RNNLSTM预测 2019 年 1 月该公司的未来趋势。我们拥有 2019 年 1 月的实际数据,因此可以稍后将预测结果与实际值进行比较。按照以下步骤完成这个活动:

  1. 导入所需的库:

    import numpy as np
    import matplotlib.pyplot as plt
    import pandas as pd
    from tensorflow import random
    
  2. 使用 pandas 的read_csv函数导入数据集,并使用head方法查看数据集的前五行:

    dataset_training = pd.read_csv('../AMZN_train.csv')
    dataset_training.head()
    

    以下图示显示了上述代码的输出:

    图 9.24:数据集的前五行

    ](https://github.com/OpenDocCN/freelearn-dl-pt5-zh/raw/master/docs/dl-keras-ws/img/B15777_09_24.jpg)

    图 9.24:数据集的前五行

  3. 我们将使用Open股票价格进行预测;因此,从数据集中选择Open股票价格列并打印其值:

    training_data = dataset_training[['Open']].values 
    training_data
    

    上述代码生成了以下输出:

    array([[ 398.799988],
           [ 398.290009],
           [ 395.850006],
           ...,
           [1454.199951],
           [1473.349976],
           [1510.800049]])
    
  4. 然后,通过使用MinMaxScaler进行特征缩放来规范化数据,设定特征的范围,使它们的最小值为 0,最大值为 1。使用缩放器的fit_transform方法对训练数据进行处理:

    from sklearn.preprocessing import MinMaxScaler
    sc = MinMaxScaler(feature_range = (0, 1))
    training_data_scaled = sc.fit_transform(training_data)
    training_data_scaled
    

    上述代码生成了以下输出:

    array([[0.06523313],
           [0.06494233],
           [0.06355099],
           ...,
           [0.66704299],
           [0.67796271],
           [0.69931748]])
    
  5. 创建数据以从当前实例获取60个时间戳。我们选择60是因为它能为我们提供足够的前置实例来理解趋势;从技术上讲,这个数字可以是任何值,但60是最优值。此外,这里的上界值是1258,它是训练集中的行数(或记录数)索引:

    X_train = []
    y_train = []
    for i in range(60, 1258):
        X_train.append(training_data_scaled[i-60:i, 0])
        y_train.append(training_data_scaled[i, 0])
    X_train, y_train = np.array(X_train), np.array(y_train)
    
  6. 使用 NumPy 的reshape函数重塑数据,为X_train的末尾添加一个额外的维度:

    X_train = np.reshape(X_train, (X_train.shape[0], \
                         X_train.shape[1], 1))
    
  7. 导入以下库来构建 RNN:

    from keras.models import Sequential
    from keras.layers import Dense, LSTM, Dropout
    
  8. 设置随机种子并初始化顺序模型,如下所示:

    seed = 1
    np.random.seed(seed)
    random.set_seed(seed)
    model = Sequential()
    
  9. 向网络中添加一个LSTM层,设定50个单元,将return_sequences参数设置为True,并将input_shape参数设置为(X_train.shape[1], 1)。添加三个额外的LSTM层,每个层有50个单元,并为前两个层将return_sequences参数设置为True。最后,添加一个大小为 1 的输出层:

    model.add(LSTM(units = 50, return_sequences = True, \
              input_shape = (X_train.shape[1], 1)))
    # Adding a second LSTM layer
    model.add(LSTM(units = 50, return_sequences = True))
    # Adding a third LSTM layer
    model.add(LSTM(units = 50, return_sequences = True))
    # Adding a fourth LSTM layer
    model.add(LSTM(units = 50))
    # Adding the output layer
    model.add(Dense(units = 1))
    
  10. 使用adam优化器并使用均方误差作为损失函数编译网络。将模型拟合到训练数据,进行100个周期的训练,批量大小为32

    # Compiling the RNN
    model.compile(optimizer = 'adam', loss = 'mean_squared_error')
    # Fitting the RNN to the Training set
    model.fit(X_train, y_train, epochs = 100, batch_size = 32)
    
  11. 加载并处理测试数据(在这里视为实际数据),并选择表示Open股票数据的列:

    dataset_testing = pd.read_csv('../AMZN_test.csv')
    actual_stock_price = dataset_testing[['Open']].values
    actual_stock_price
    
  12. 连接数据,因为我们需要60个前一个实例来获得每天的股票价格。因此,我们将需要训练数据和测试数据:

    total_data = pd.concat((dataset_training['Open'], \
                            dataset_testing['Open']), axis = 0)
    
  13. 重塑并缩放输入数据以准备测试数据。请注意,我们正在预测 1 月的月度趋势,这一月份有21个金融工作日,因此为了准备测试集,我们将下界值设为60,上界值设为81。这样可以确保21的差值得以保持:

    inputs = total_data[len(total_data) \
             - len(dataset_testing) - 60:].values
    inputs = inputs.reshape(-1,1)
    inputs = sc.transform(inputs)
    X_test = []
    for i in range(60, 81):
        X_test.append(inputs[i-60:i, 0])
    X_test = np.array(X_test)
    X_test = np.reshape(X_test, (X_test.shape[0], \
                                 X_test.shape[1], 1))
    predicted_stock_price = model.predict(X_test)
    predicted_stock_price = \
    sc.inverse_transform(predicted_stock_price)
    
  14. 通过绘制实际股价和预测股价来可视化结果:

    # Visualizing the results
    plt.plot(actual_stock_price, color = 'green', \
             label = 'Real Amazon Stock Price',ls='--')
    plt.plot(predicted_stock_price, color = 'red', \
             label = 'Predicted Amazon Stock Price',ls='-')
    plt.title('Predicted Stock Price')
    plt.xlabel('Time in days')
    plt.ylabel('Real Stock Price')
    plt.legend()
    plt.show()
    

    请注意,您的结果可能会与亚马逊的实际股价略有不同。

    预期输出

    图 9.25:实际股价与预测股价

图 9.25:实际股价与预测股价

如前面的图所示,预测股价和实际股价的趋势几乎相同;两条线的波峰和波谷一致。这是因为 LSTM 能够记住序列数据。传统的前馈神经网络无法预测出这一结果。这正是LSTMRNNs的真正强大之处。

注意

要访问此特定部分的源代码,请参阅packt.live/3goQO3I

你也可以在packt.live/2VIMq7O上在线运行此示例。

活动 9.02:使用正则化预测亚马逊的股价

在这个活动中,我们将研究亚马逊过去 5 年的股价,从 2014 年 1 月 1 日到 2018 年 12 月 31 日。在此过程中,我们将尝试使用 RNN 和 LSTM 预测并预测 2019 年 1 月亚马逊股价的未来趋势。我们已拥有 2019 年 1 月的实际值,因此稍后我们将能够将我们的预测与实际值进行比较。最初,我们使用 50 个单元(或神经元)的 LSTM 预测了亚马逊股价的趋势。在本次活动中,我们还将添加 Dropout 正则化,并将结果与活动 9.01使用 50 个单元(神经元)的 LSTM 预测亚马逊股价趋势进行比较。按照以下步骤完成此活动:

  1. 导入所需的库:

    import numpy as np
    import matplotlib.pyplot as plt
    import pandas as pd
    from tensorflow import random
    
  2. 使用 pandas 的read_csv函数导入数据集,并使用head方法查看数据集的前五行:

    dataset_training = pd.read_csv('../AMZN_train.csv')
    dataset_training.head()
    
  3. 我们将使用Open股票价格来进行预测;因此,从数据集中选择Open股票价格列并打印其值:

    training_data = dataset_training[['Open']].values
    training_data
    

    上述代码产生了以下输出:

    array([[ 398.799988],
           [ 398.290009],
           [ 395.850006],
           ...,
           [1454.199951],
           [1473.349976],
           [1510.800049]])
    
  4. 然后,通过使用MinMaxScaler对数据进行特征缩放,并设置特征的范围,使其最小值为0,最大值为 1。使用缩放器的fit_transform方法对训练数据进行处理:

    from sklearn.preprocessing import MinMaxScaler
    sc = MinMaxScaler(feature_range = (0, 1))
    training_data_scaled = sc.fit_transform(training_data)
    training_data_scaled
    

    上述代码产生了以下输出:

    array([[0.06523313],
           [0.06494233],
           [0.06355099],
           ...,
           [0.66704299],
           [0.67796271],
           [0.69931748]])
    
  5. 创建数据以获取来自当前实例的60个时间戳。我们选择60,因为它将为我们提供足够的先前实例,以便理解趋势;技术上讲,这可以是任何数字,但60是最优值。此外,这里的上限值是1258,这是训练集中的索引或行数(或记录数):

    X_train = []
    y_train = []
    for i in range(60, 1258):
        X_train.append(training_data_scaled[i-60:i, 0])
        y_train.append(training_data_scaled[i, 0])
    X_train, y_train = np.array(X_train), np.array(y_train)
    
  6. 使用 NumPy 的reshape函数将数据重塑,以便在X_train的末尾添加一个额外的维度:

    X_train = np.reshape(X_train, (X_train.shape[0], \
                                   X_train.shape[1], 1))
    
  7. 导入以下 Keras 库以构建 RNN:

    from keras.models import Sequential
    from keras.layers import Dense, LSTM, Dropout
    
  8. 设置种子并初始化顺序模型,如下所示:

    seed = 1
    np.random.seed(seed)
    random.set_seed(seed)
    model = Sequential()
    
  9. 在网络中添加一个 LSTM 层,设置 50 个单元,将return_sequences参数设置为True,并将input_shape参数设置为(X_train.shape[1], 1)。为模型添加丢弃层,rate=0.2。再添加三个 LSTM 层,每个 LSTM 层有50个单元,前两个 LSTM 层的return_sequences参数设置为True。在每个LSTM层后,添加丢弃层,rate=0.2。最后添加一个大小为1的输出层:

    model.add(LSTM(units = 50, return_sequences = True, \
                   input_shape = (X_train.shape[1], 1)))
    model.add(Dropout(0.2))
    # Adding a second LSTM layer and some Dropout regularization
    model.add(LSTM(units = 50, return_sequences = True))
    model.add(Dropout(0.2))
    # Adding a third LSTM layer and some Dropout regularization
    model.add(LSTM(units = 50, return_sequences = True))
    model.add(Dropout(0.2))
    # Adding a fourth LSTM layer and some Dropout regularization
    model.add(LSTM(units = 50))
    model.add(Dropout(0.2))
    # Adding the output layer
    model.add(Dense(units = 1))
    
  10. 使用adam优化器编译网络,并使用均方误差作为损失函数。将模型拟合到训练数据,训练100个周期,批量大小为32

    # Compiling the RNN
    model.compile(optimizer = 'adam', loss = 'mean_squared_error')
    # Fitting the RNN to the Training set
    model.fit(X_train, y_train, epochs = 100, batch_size = 32)
    
  11. 加载并处理测试数据(这里将其视为实际数据),并选择表示Open股票数据的列:

    dataset_testing = pd.read_csv('../AMZN_test.csv')
    actual_stock_price = dataset_testing[['Open']].values
    actual_stock_price 
    
  12. 将数据连接起来,因为我们需要60个前序实例来获取每天的股票价格。因此,我们将需要训练数据和测试数据:

    total_data = pd.concat((dataset_training['Open'], \
                            dataset_testing['Open']), axis = 0)
    
  13. 对输入数据进行重塑和缩放,以准备测试数据。请注意,我们正在预测 1 月的月度趋势,1 月有21个交易日,因此为了准备测试集,我们将下界值设为60,上界值设为81。这确保了21的差异得以保持:

    inputs = total_data[len(total_data) \
             - len(dataset_testing) - 60:].values
    inputs = inputs.reshape(-1,1)
    inputs = sc.transform(inputs)
    X_test = []
    for i in range(60, 81):
        X_test.append(inputs[i-60:i, 0])
    X_test = np.array(X_test)
    X_test = np.reshape(X_test, (X_test.shape[0], \
                                 X_test.shape[1], 1))
    predicted_stock_price = model.predict(X_test)
    predicted_stock_price = \
    sc.inverse_transform(predicted_stock_price)
    
  14. 通过绘制实际股票价格与预测股票价格的图表来可视化结果:

    # Visualizing the results
    plt.plot(actual_stock_price, color = 'green', \
             label = 'Real Amazon Stock Price',ls='--')
    plt.plot(predicted_stock_price, color = 'red', \
             label = 'Predicted Amazon Stock Price',ls='-')
    plt.title('Predicted Stock Price')
    plt.xlabel('Time in days')
    plt.ylabel('Real Stock Price')
    plt.legend()
    plt.show()
    

请注意,您的结果可能与实际股票价格略有不同。

预期输出

图 9.26:实际股票价格与预测股票价格的对比

](https://github.com/OpenDocCN/freelearn-dl-pt5-zh/raw/master/docs/dl-keras-ws/img/B15777_09_26.jpg)

图 9.26:实际股票价格与预测股票价格的对比

在下图中,第一个图展示了来自活动 9.02 的带正则化的模型预测输出,第二个图展示了来自活动 9.01 的没有正则化的模型预测输出。正如你所见,加入丢弃正则化并没有更精确地拟合数据。因此,在这种情况下,最好不要使用正则化,或者使用丢弃正则化并设置较低的丢弃率:

图 9.27:比较活动 9.01 与活动 9.02 的结果

](https://github.com/OpenDocCN/freelearn-dl-pt5-zh/raw/master/docs/dl-keras-ws/img/B15777_09_27.jpg)

图 9.27:比较活动 9.01 与活动 9.02 的结果

注意

要访问该特定部分的源代码,请参考packt.live/2YTpxR7

你也可以在线运行这个示例,网址是packt.live/3dY5Bku

活动 9.03:使用增加数量的 LSTM 神经元(100 个单元)预测亚马逊股票价格的趋势

在此活动中,我们将分析亚马逊过去 5 年(2014 年 1 月 1 日到 2018 年 12 月 31 日)的股价。我们将使用四个 LSTM 层的 RNN,每个层有 100 个单元,尝试预测并预测 2019 年 1 月的公司未来趋势。我们已知 2019 年 1 月的实际股价,因此之后可以将我们的预测与实际值进行对比。你也可以将输出差异与 活动 9.01使用 50 个单元(神经元)的 LSTM 预测亚马逊股价趋势 进行比较。按照以下步骤完成此活动:

  1. 导入所需的库:

    import numpy as np
    import matplotlib.pyplot as plt
    import pandas as pd
    from tensorflow import random
    
  2. 使用 pandas 的 read_csv 函数导入数据集,并使用 head 方法查看数据集的前五行:

    dataset_training = pd.read_csv('../AMZN_train.csv')
    dataset_training.head()
    
  3. 我们将使用 Open 股票价格进行预测;因此,从数据集中选择 Open 股票价格列并打印值:

    training_data = dataset_training[['Open']].values
    training_data
    
  4. 然后,使用 MinMaxScaler 对数据进行特征缩放,并设置特征的范围,使其最小值为零,最大值为一。对训练数据使用 fit_transform 方法:

    from sklearn.preprocessing import MinMaxScaler
    sc = MinMaxScaler(feature_range = (0, 1))
    training_data_scaled = sc.fit_transform(training_data)
    training_data_scaled
    
  5. 创建数据,从当前实例获取 60 个时间戳。我们选择 60,因为它能为我们提供足够的前期数据,以帮助理解趋势;技术上讲,这个数字可以是任何值,但 60 是最佳值。此外,这里的上界值为 1258,即训练集中行(或记录)的索引或数量:

    X_train = []
    y_train = []
    for i in range(60, 1258):
        X_train.append(training_data_scaled[i-60:i, 0])
        y_train.append(training_data_scaled[i, 0])
    X_train, y_train = np.array(X_train), np.array(y_train)
    
  6. 重塑数据,通过使用 NumPy 的 reshape 函数,在 X_train 末尾添加一个额外的维度:

    X_train = np.reshape(X_train, (X_train.shape[0], \
                                   X_train.shape[1], 1))
    
  7. 导入以下 Keras 库以构建 RNN:

    from keras.models import Sequential
    from keras.layers import Dense, LSTM, Dropout
    
  8. 设置种子并初始化顺序模型:

    seed = 1
    np.random.seed(seed)
    random.set_seed(seed)
    model = Sequential()
    
  9. 向网络中添加一个 100 单元的 LSTM 层,将 return_sequences 参数设置为 True,并将 input_shape 参数设置为 (X_train.shape[1], 1)。再添加三个 LSTM 层,每个层有 100 个单元,前两个层的 return_sequences 参数设置为 True。最后添加一个大小为 1 的输出层:

    model.add(LSTM(units = 100, return_sequences = True, \
                   input_shape = (X_train.shape[1], 1)))
    # Adding a second LSTM layer
    model.add(LSTM(units = 100, return_sequences = True))
    # Adding a third LSTM layer
    model.add(LSTM(units = 100, return_sequences = True))
    # Adding a fourth LSTM layer
    model.add(LSTM(units = 100))
    # Adding the output layer
    model.add(Dense(units = 1))
    
  10. 使用 adam 优化器编译网络,并使用 均方误差(Mean Squared Error) 作为损失函数。将模型拟合到训练数据上,训练 100 个周期,批量大小为 32

    # Compiling the RNN
    model.compile(optimizer = 'adam', loss = 'mean_squared_error')
    # Fitting the RNN to the Training set
    model.fit(X_train, y_train, epochs = 100, batch_size = 32)
    
  11. 加载并处理测试数据(这里视为实际数据),并选择表示开盘股价数据的列:

    dataset_testing = pd.read_csv('../AMZN_test.csv')
    actual_stock_price = dataset_testing[['Open']].values
    actual_stock_price
    
  12. 合并数据,因为我们需要 60 个前期数据来获取每天的股价。因此,我们将需要训练数据和测试数据:

    total_data = pd.concat((dataset_training['Open'], \
                            dataset_testing['Open']), axis = 0)
    
  13. 重塑并缩放输入数据以准备测试数据。请注意,我们预测的是一月的月度趋势,21 个交易日,因此,为了准备测试集,我们将下界值设置为 60,上界值设置为 81。这确保了 21 的差值得以保持:

    inputs = total_data[len(total_data) \
             - len(dataset_testing) - 60:].values
    inputs = inputs.reshape(-1,1)
    inputs = sc.transform(inputs)
    X_test = []
    for i in range(60, 81):
        X_test.append(inputs[i-60:i, 0])
    X_test = np.array(X_test)
    X_test = np.reshape(X_test, (X_test.shape[0], \
                                 X_test.shape[1], 1))
    predicted_stock_price = model.predict(X_test)
    predicted_stock_price = \
    sc.inverse_transform(predicted_stock_price)
    
  14. 通过绘制实际股价和预测股价来可视化结果:

    plt.plot(actual_stock_price, color = 'green', \
             label = 'Actual Amazon Stock Price',ls='--')
    plt.plot(predicted_stock_price, color = 'red', \
             label = 'Predicted Amazon Stock Price',ls='-')
    plt.title('Predicted Stock Price')
    plt.xlabel('Time in days')
    plt.ylabel('Real Stock Price')
    plt.legend()
    plt.show()
    

    请注意,你的结果可能会与实际股价略有不同。

    预期输出

    图 9.28:实际股票价格与预测股票价格

    ](https://github.com/OpenDocCN/freelearn-dl-pt5-zh/raw/master/docs/dl-keras-ws/img/B15777_09_28.jpg)

图 9.28:实际股票价格与预测股票价格

因此,如果我们将本节中的LSTM(50 个单元,来自活动 9.01使用 50 个单元(神经元)的 LSTM 预测亚马逊股票价格趋势)与LSTM(100 个单元)进行比较,我们会得到 100 个单元的趋势。另外,请注意,当我们运行LSTM(100 个单元)时,它比运行LSTM(50 个单元)需要更多的计算时间。在这种情况下需要考虑权衡:

图 9.29:比较 50 个和 100 个单元的实际股票价格与预测股票价格

](https://github.com/OpenDocCN/freelearn-dl-pt5-zh/raw/master/docs/dl-keras-ws/img/B15777_09_29.jpg)

图 9.29:比较 50 个和 100 个单元的实际股票价格与预测股票价格

注意

要访问此特定部分的源代码,请参阅 packt.live/31NQkQy

您还可以在网上运行此示例,访问 packt.live/2ZCZ4GR

posted @ 2025-07-12 11:41  绝不原创的飞龙  阅读(26)  评论(0)    收藏  举报