TensorFlow-研讨会-全-
TensorFlow 研讨会(全)
原文:
annas-archive.org/md5/92a4018e7ab653973d73d2eb5915e580
译者:飞龙
前言
关于本书
如果你希望学习如何在 TensorFlow 中构建深度学习模型以解决实际问题,那么这本书非常适合你。
本书从介绍 TensorFlow 开始,带领你了解张量的基本数学运算,以及数据预处理方法,用以构建模型并通过 TensorFlow 资源节省开发时间。你将构建回归和分类模型,使用正则化防止模型对训练数据过拟合,并创建卷积神经网络以解决图像数据集上的分类任务。最后,你将学习实现预训练、递归和生成模型,并创建自己定制的 TensorFlow 组件,在模型中使用。
在阅读完本书后,你将具备使用 TensorFlow 框架构建、训练和评估深度学习模型的实际技能。
关于作者
Matthew Moocarme 是一位经验丰富的数据科学家,拥有超过八年的创建和使用机器学习模型的经验。他有物理科学背景,持有 CUNY 研究生中心的物理学博士学位。目前,他带领一个数据科学家和工程师团队,在媒体和广告领域构建和整合机器学习模型,应用于各种场景。在闲暇时,Matthew 通过发布作品、参加会议演讲和举办工作坊与数据科学社区分享自己的知识。
Anthony So 是数据科学领域的知名领袖。他在利用高级分析和人工智能解决不同行业复杂商业问题方面拥有丰富经验,涉及金融服务、媒体和电信等行业。他目前是一个最具创新力的金融科技初创公司首席数据官。他也是多本畅销书的作者,涵盖数据科学、机器学习和深度学习等领域。他曾在多个黑客松比赛中获奖,如 Unearthed、GovHack 和 Pepper Money 等。Anthony 拥有两个硕士学位,一个是计算机科学,另一个是数据科学与创新。
Anthony Maddalone 是 TieSet 的研究工程师,TieSet 是位于硅谷的分布式人工智能和联邦学习领域的领导者。他曾是一个成功初创公司的创始人兼 CEO。Anthony 和妻子及两个孩子一起生活在科罗拉多州,享受户外活动的时光。他还是乔治亚理工学院工业工程方向的分析学硕士候选人。
本书适合人群
本书适合任何希望加深对深度学习理解,并开始使用 TensorFlow 构建神经网络的人。如果你具备 Python 编程及其库的基本知识,以及对数据科学和机器学习基础的通用理解,将帮助你更轻松地掌握本书所涉及的内容。
关于章节
第一章,TensorFlow 机器学习简介,介绍了支撑 TensorFlow 和机器学习模型开发的数学概念,包括张量和线性代数。
第二章,加载和处理数据,教你如何加载和处理各种数据类型,包括表格数据、图像、音频和文本,以便将它们输入到机器学习模型中。
第三章,TensorFlow 开发,介绍了 TensorFlow 提供的各种开发工具,帮助你构建模型,包括 TensorBoard、TensorFlow Hub 和 Google Colab。这些工具可以加快开发速度,并帮助你理解模型的架构和性能。
第四章,回归与分类模型,引导你通过使用 TensorFlow 构建回归和分类任务的模型。你将学习如何构建简单的模型、使用哪些层以及为每个任务选择合适的损失函数。
第五章,分类模型,展示了如何使用 TensorFlow 构建分类模型。你将学习如何定制神经网络的架构,以适应二分类、多类分类或多标签分类。
第六章,正则化与超参数调优,讨论了帮助防止模型过拟合的不同方法,例如正则化、丢弃法(dropout)或提前停止(early stopping)。你还将学习如何进行自动超参数调优。
第七章,卷积神经网络,展示了如何构建包含卷积层的神经网络。这些网络因其在处理图像时的优良表现而广受欢迎,因为它们包含卷积层。
第八章,预训练网络,教你如何利用预训练模型来实现更好的性能,而无需从头开始训练模型。
第九章,递归神经网络,介绍了另一种类型的深度学习架构——递归神经网络,这种架构最适合处理时序数据,例如时间序列或文本。
第十章,自定义 TensorFlow 组件,通过教你如何构建自己的自定义 TensorFlow 组件,如损失函数和神经网络层,扩展你的技能。
第十一章,生成模型,展示了如何通过训练模型在数据集上发现潜在的模式和表示,从而生成新的和创新的数据。训练好的模型将能够生成完全新的、令人信服的真实例子。
规范
文本中的代码词、数据库表名、文件夹名称、文件名、文件扩展名、路径名、虚拟 URL 和用户输入按如下方式显示:
"TensorFlow 可以通过导入特定的库在 Python 中使用。你可以使用 import
语句在 Python 中导入库。"
屏幕上看到的单词,例如在菜单或对话框中显示的,也会以相同的格式呈现。
代码块的设置如下:
int_variable = tf.Variable(4113, tf.int16)
int_variable
新的重点词汇如下所示:“反向传播是确定损失函数对模型参数的导数的过程。”
代码片段中的关键部分被加粗如下:
df = pd.read_csv('Bias_correction_ucl.csv')
代码展示
跨越多行的代码通过反斜杠 (\
) 来分隔。当代码执行时,Python 会忽略反斜杠,并将下一行的代码视为当前行的直接延续。
例如,
year_dummies = pd.get_dummies(df['Date'].dt.year, \
prefix='year')
year_dummies
代码中添加了注释,以帮助解释特定的逻辑部分。单行注释使用 #
符号表示,如下所示:
# Importing the matplotlib library
import matplotlib.pyplot as plt
最低硬件要求
为了获得最佳体验,我们推荐以下硬件配置:
-
处理器:双核或更高
-
内存:4 GB RAM
-
存储:10 GB 可用空间
下载代码包
从 GitHub 下载代码文件,地址为 packt.link/Z7pcq
。请参考这些代码文件以获取完整的代码包。这里的文件包含每一章的练习、活动以及一些中间代码。当您遇到困难时,这些文件可以作为有用的参考。
在 GitHub 仓库页面,您可以点击绿色的 Code
按钮,然后点击 Download ZIP
选项,将完整代码作为 ZIP 文件下载到您的磁盘(参见 图 0.1)。然后,您可以将这些代码文件解压到您选择的文件夹中,例如 C:\Code
。
图 0.1:下载 ZIP 选项
在您的系统上,解压后的 ZIP 文件应该包含 GitHub 仓库中的所有文件:
图 0.2:GitHub 代码目录结构
设置您的环境
在详细探索本书之前,您需要设置特定的软件和工具。在接下来的部分,您将看到如何进行设置。
在您的系统上安装 Anaconda
本书中所有练习和活动的代码都可以通过 Jupyter Notebook 执行。您需要先安装 Anaconda Navigator,它是一个可以访问 Jupyter 笔记本的界面。Anaconda Navigator 会作为 Anaconda Individual Edition 的一部分安装,Anaconda 是一个适用于 Windows、macOS 和 Linux 的开源 Python 分发平台。安装 Anaconda 时也会安装 Python。请前往 www.anaconda.com/distribution/
:
-
在打开的页面中,点击
Download
按钮(由 1 标注)。确保下载的是Individual Edition
。图 0.3:Anaconda 主页
-
安装程序应立即开始下载。网站会默认根据您的系统配置选择合适的安装程序。如果您更喜欢为其他操作系统(Windows、macOS 或 Linux)以及系统配置(32 位或 64 位)下载 Anaconda,可以点击框底部的
获取附加安装程序
链接(参考图 0.3)。页面应该会滚动到一个部分(参考图 0.4),允许您根据所需的操作系统和配置选择不同的选项。本书建议您使用最新版本的 Python(3.8 或更高版本)。图 0.4: 根据操作系统下载 Anaconda
-
请按照屏幕上显示的安装步骤进行操作。
图 0.5: Anaconda 设置
-
在 Windows 系统中,如果您之前从未在系统中安装过 Python,可以勾选提示将 Anaconda 添加到您的
PATH
的复选框。这样,您就可以在默认命令提示符中运行 Anaconda 特有的命令(如conda
)。如果您已经安装了 Python,或者之前安装过 Anaconda 的旧版本,建议您不要勾选此选项(您可以通过 Anaconda Prompt 应用来运行 Anaconda 命令)。根据您的系统配置,安装可能需要一些时间。图 0.6: Anaconda 安装步骤
若要获取更详细的安装说明,您可以点击以下链接查阅官方文档:Linux 版请点击此链接(
docs.anaconda.com/anaconda/install/linux/
);macOS 版请点击此链接(docs.anaconda.com/anaconda/install/mac-os/
);Windows 版请点击此链接(docs.anaconda.com/anaconda/install/windows/
)。 -
要检查 Anaconda Navigator 是否正确安装,请在您的应用程序中查找
Anaconda Navigator
。查找一个具有以下图标的应用程序。根据您的操作系统,图标的外观可能会有所不同。图 0.7: Anaconda Navigator 图标
您还可以使用操作系统的搜索功能来搜索该应用程序。例如,在 Windows 10 上,您可以使用Windows 键 + S组合键,然后输入Anaconda Navigator。在 macOS 上,您可以使用 Spotlight 搜索。在 Linux 上,您可以打开终端,输入
anaconda-navigator
命令并按Return键。图 0.8: 在 Windows 10 上搜索 Anaconda Navigator
有关如何验证是否已安装 Anaconda Navigator 的详细步骤,请参考以下链接:
docs.anaconda.com/anaconda/install/verify-install/
。 -
点击图标打开 Anaconda Navigator。首次加载可能需要一些时间,但安装成功后,你应该看到类似的界面:
图 0.9:Anaconda Navigator 界面
如果你对安装过程有更多问题,可以参考 Anaconda 文档中的常见问题列表:docs.anaconda.com/anaconda/user-guide/faq/
。
启动 Jupyter Notebook
打开 Anaconda Navigator 后,你可以从这个界面启动 Jupyter Notebook。以下步骤将指导你如何操作:
-
打开 Anaconda Navigator,你应该能看到以下界面:
图 0.10:Anaconda Navigator 界面
-
现在,点击
Launch
在Jupyter Notebook
面板下启动本地系统上的 Jupyter Notebook 界面:图 0.11:Jupyter Notebook 启动选项
-
点击
Launch
按钮后,你会注意到,尽管前面截图显示的窗口没有变化,但在默认浏览器中会打开一个新标签页。这就是所谓的Notebook Dashboard。它会默认打开到你的根目录。对于 Windows 用户,这个路径类似于C:\Users\<用户名>
。在 macOS 和 Linux 上,它会是/home/<用户名>/
。图 0.12:Notebook Dashboard
请注意,你也可以通过在终端或命令提示符中运行
jupyter notebook
命令来直接打开 Jupyter Notebook,或者像在图 0.8中那样,在应用程序中搜索Jupyter Notebook
。 -
你可以使用这个仪表板作为文件浏览器,导航到你下载或存储书中代码文件的目录(关于如何从 GitHub 下载文件,请参考下载代码包章节)。一旦你导航到目标目录,就可以开始创建新的笔记本。或者,如果你已经从我们的代码库下载了代码,也可以打开现有的笔记本(笔记本文件扩展名为
.inpyb
)。这里的菜单非常简便易用:图 0.13:Jupyter Notebook 导航菜单选项介绍
如果你通过操作系统的文件资源管理器更改了目录,但在 Jupyter Notebook 导航器中没有显示更改后的文件,可以点击
刷新笔记本列表
按钮(标注为1)。要退出,点击退出按钮
(标注为2)。要创建一个新文件(一个新的 Jupyter 笔记本),你可以点击新建
按钮(标注为3)。 -
点击
New
按钮将会弹出如下的下拉菜单:
图 0.14:创建一个新的 Jupyter 笔记本
你可以通过选择Python 3
来开始并创建你的第一个笔记本;然而,建议你同时安装我们提供的虚拟环境,这样可以帮助你安装本书所需的所有软件包。接下来的章节将教你如何安装它。
注意
你可以在这里找到关于 Jupyter 笔记本界面和快捷键的详细教程:jupyter-notebook.readthedocs.io/en/stable/notebook.html#the-jupyter-notebook
。
安装 tensorflow 虚拟环境
当你运行习题和活动的代码时,你会发现即使安装了 Anaconda,仍然有一些库需要在书中的不同阶段单独安装。也许你已经安装了这些库,但它们的版本可能与我们使用的版本不同,这可能会导致不同的结果。正因如此,我们在本书中提供了一个environment.yml
文件,它将:
-
一次性安装本书所需的所有软件包和库。
-
确保你的库版本与我们为本书编写代码时使用的版本匹配。
-
确保你根据本课程编写的代码与任何其他编程环境分开。
你可以通过点击以下链接下载environment.yml
文件:packt.link/Z7pcq
。
保存此文件,最好将其保存在你运行本书代码的同一文件夹中。如果你已经按照《下载代码包》章节的说明从 GitHub 下载了代码,那么该文件应该已经存在于父目录中,你无需单独下载它。
要设置环境,请按照以下步骤操作:
-
在 macOS 上,从 Launchpad 打开终端(你可以在此处找到关于终端的更多信息:
support.apple.com/en-in/guide/terminal/apd5265185d-f365-44cb-8b09-71a064a42125/mac
)。在 Linux 上,打开你所在发行版的终端应用程序。在 Windows 上,你可以直接搜索应用程序并打开 Anaconda Prompt。你可以通过打开开始
菜单并搜索Anaconda Prompt
来做到这一点。图 0.15:在 Windows 中搜索 Anaconda Prompt
应该会打开一个新的终端,默认情况下,它将在你的主目录中启动:
](https://github.com/OpenDocCN/freelearn-dl-pt3-zh/raw/master/docs/tf-ws/img/B16341_0_16.jpg)
图 0.16:Anaconda 终端提示符
在 Linux 系统中,它将显示如下:
](https://github.com/OpenDocCN/freelearn-dl-pt3-zh/raw/master/docs/tf-ws/img/B16341_0_17.jpg)
图 0.17:Linux 中的终端
-
在终端中,使用
cd
命令导航到保存environment.yml
文件的目录。例如,如果你把文件保存在Documents\The-TensorFlow-Workshop
中,那么在命令提示符下输入以下命令并按Enter键:cd Documents\The-TensorFlow-Workshop
请注意,命令可能会根据你的目录结构和操作系统有所不同。
-
现在你已导航到正确的文件夹,在终端中键入或粘贴以下命令来创建新的
conda
环境。按Enter键运行该命令:conda env create -f environment.yml
这将安装
tensorflow
虚拟环境及运行本书代码所需的库。如果安装过程中有提示要求你确认,输入y
并按Enter键继续创建环境。根据你的系统配置,过程可能需要一些时间才能完成。注意
若要查看完整的
conda
命令列表,请访问以下链接:conda.io/projects/conda/en/latest/index.html
。若要查看有关如何管理
conda
环境的详细指南,请访问以下链接:conda.io/projects/conda/en/latest/user-guide/tasks/manage-environments.html
。 -
完成后,在终端中键入或粘贴以下命令来激活新安装的环境——
tensorflow
:conda activate tensorflow
如果安装成功,你将看到环境名称的括号内由
base
更改为tensorflow
:](https://github.com/OpenDocCN/freelearn-dl-pt3-zh/raw/master/docs/tf-ws/img/B16341_0_18.jpg)
图 0.18:环境名称出现在终端
-
在新激活的
conda
环境中运行以下命令来安装ipykernel
:pip3 instead of pip.
-
在同一环境中,运行以下命令以将
ipykernel
添加为 Jupyter 内核:python -m ipykernel install --user --name=tensorflow
-
仅限 Windows:如果你使用的是 Windows 系统,请键入或粘贴以下命令。否则,你可以跳过此步骤并退出终端:
conda install pywin32
-
启动 Jupyter 笔记本时,选择创建的
tensorflow
内核。](https://github.com/OpenDocCN/freelearn-dl-pt3-zh/raw/master/docs/tf-ws/img/B16341_0_19.jpg)
图 0.19:选择 tensorflow 内核
一个新标签页将会打开,里面是一个新的、未命名的 Jupyter 笔记本,你可以开始编写代码:
](https://github.com/OpenDocCN/freelearn-dl-pt3-zh/raw/master/docs/tf-ws/img/B16341_0_20.jpg)
图 0.20:新的 Jupyter 笔记本
联系我们
我们欢迎读者的反馈。
customercare@packtpub.com
。
勘误:尽管我们已尽一切努力确保内容的准确性,但错误难免会发生。如果你在本书中发现错误,我们将不胜感激,感谢你能向我们报告。请访问 www.packtpub.com/support/errata 并填写表格。
copyright@packt.com
并附上材料的链接。
如果你有兴趣成为一名作者:如果你在某个领域拥有专业知识,并且有兴趣写作或为书籍做贡献,请访问 authors.packtpub.com。
请留下评论
通过在亚马逊上留下详细、公正的评论,让我们知道你的想法。我们感谢所有的反馈——这帮助我们继续制作优秀的产品,并帮助有志的开发者提升技能。请花几分钟时间给出你的想法——这对我们意义重大。你可以通过点击以下链接留下评论:packt.link/r/1800205252
。
第一章:1. 使用 TensorFlow 进行机器学习介绍
概述
在本章中,你将学习如何创建、利用和应用线性变换到 TensorFlow 编程的基本构建块——张量。然后,你将利用张量来理解与神经网络相关的复杂概念,包括张量重塑、转置和乘法。
介绍
机器学习(ML)已经渗透到许多人未曾察觉的日常生活的各个方面。从日常社交媒体推荐到在线搜索结果,它们都由机器学习算法驱动。这些算法最初在研究环境中解决特定问题,但随着其可访问性不断扩展,它们的应用场景也变得越来越广泛。各类研究人员和企业都认识到,使用模型来优化各自运营的每个环节具有重要价值。医生可以使用机器学习来决定诊断和治疗方案,零售商可以利用 ML 在合适的时间将合适的产品送到店铺,而娱乐公司可以使用 ML 向客户提供个性化的推荐。
在数据时代,机器学习模型已被证明是任何数据驱动型公司的宝贵资产。海量数据使得可以创建强大且准确的模型来完成各种任务,从回归到分类,从推荐到时间序列分析,甚至是生成艺术,这些内容将在本次研讨会中介绍。所有这些任务都可以通过 TensorFlow 构建、训练和部署。
TensorFlow API 拥有大量功能,使其在所有构建机器学习模型或处理张量(多维数值数组)的机器学习从业者中广受欢迎。对于研究人员来说,TensorFlow 是创建新机器学习应用的合适选择,因为它提供了高度的定制和灵活性。对于开发者而言,TensorFlow 是一个优秀的机器学习库选择,因为它在模型从开发到生产环境的部署方面非常方便。综合来看,TensorFlow 的灵活性和易于部署使得它成为许多从业者的智能选择,他们希望使用各种数据源构建高效的机器学习模型,并在生产环境中复制这些学习成果。
本章提供了 TensorFlow API 的实用介绍。你将学习如何执行与机器学习相关的数学操作,这些操作将为你使用 TensorFlow 构建高效 ML 模型奠定坚实的基础。你将首先学习基本操作,例如如何使用 API 创建变量。接下来,你将学习如何执行线性变换,如加法,然后再深入到更高级的任务,包括张量乘法。
在 TensorFlow 中实现人工神经网络
TensorFlow 提供的高级灵活性非常适合构建人工神经网络(ANNs)。ANNs 是受到大脑神经元连接启发的算法,旨在复制人类学习的过程。它们由多个层组成,信息通过这些层从输入传播到输出。
图 1.1 展示了一个人工神经网络(ANN)的可视化表示。输入层位于左侧,在这个示例中,输入层有两个特征(X
1 和 X
2)。输入层与第一个隐藏层连接,后者有三个单元。来自前一层的所有数据会传递给第一个隐藏层的每个单元。然后,数据被传递到第二个隐藏层,第二个隐藏层同样有三个单元。再次地,来自上一层每个单元的信息会传递给第二个隐藏层的每个单元。最后,所有来自第二个隐藏层的信息都会传递到输出层,输出层有一个单元,表示每组输入特征的单个数字。
图 1.1:一个具有两个隐藏层的人工神经网络(ANN)的可视化表示
ANNs 已被证明能够成功学习具有复杂和非线性关系的大规模非结构化数据集,如音频、图像和文本数据。尽管结果可能令人印象深刻,但 ANNs 的配置存在很大的可变性。例如,层数、每层的大小以及应该使用哪种非线性函数是决定 ANNs 配置的一些因素。TensorFlow 提供的类和函数不仅非常适合构建和训练 ANNs,此外,该库还提供了一套工具,帮助在训练过程中可视化和调试 ANNs。
与传统的机器学习算法(如线性回归和逻辑回归)相比,人工神经网络(ANNs)在数据量较大的情况下能够超越这些算法。ANNs 的优势在于它们能够处理非结构化数据,并且不一定需要特征工程。数据预处理可能是一个耗时的过程。因此,如果数据量较大,许多实践者更倾向于选择 ANNs。
来自各行各业的许多公司都利用 TensorFlow 构建人工神经网络(ANN)用于他们的应用。由于 TensorFlow 得到了 Google 的支持,该公司将该库应用于大部分机器学习应用的研究、开发和生产。然而,许多其他公司也使用该库。像 Airbnb、可口可乐、Uber 和 GE Healthcare 等公司,都在进行各种任务时使用该库。人工神经网络的使用尤其具有吸引力,因为只要提供足够的数据并进行适当训练,它们就能实现显著的准确性。例如,GE Healthcare 使用 TensorFlow 构建 ANN,从磁共振图像中识别特定的解剖结构,不论其朝向如何,以提高速度和准确性。通过使用 ANN,他们能够在几秒钟内识别出解剖结构,准确率超过 99%,无论头部如何旋转,而这在没有 ANN 的情况下需要经过训练的专业人员花费更多时间才能完成。
尽管许多公司都在使用人工神经网络,但 ANN 可能不是解决所有业务问题的最佳选择。在这种环境下,你需要回答以下问题,以确定 ANN 是否是最合适的选择:
-
问题是否有数值解决方案? 包括人工神经网络(ANN)在内的机器学习算法,基于输入数据生成预测的数值结果。例如,机器学习算法可能会预测一个给定的数字,如根据城市的位置和先前的天气条件预测温度,或者根据先前的股票价格预测股价,或者将图像分类为某个给定的类别。在这些例子中,基于提供的数据生成数值输出,并且只要有足够的标注数据,模型就能表现得很好。然而,当期望的结果更为抽象,或需要创意时,比如创作一首新歌,那么机器学习算法可能不是最合适的选择,因为可能没有明确的数值解决方案可供参考。
-
是否有足够的合适标注数据来训练模型? 对于监督学习任务,你必须至少拥有一些标注数据来训练模型。例如,如果你想构建一个模型来预测某个公司的金融股票数据,你首先需要历史训练数据。如果该公司尚未上市很长时间,可能就没有足够的训练数据。人工神经网络通常需要大量的数据。当处理图像时,ANN 通常需要数百万个训练样本来开发出准确且稳健的模型。这可能是决定哪个算法适合给定任务的一个关键因素。
既然你已经了解了什么是 TensorFlow,那么请考虑以下 TensorFlow 的优缺点。
TensorFlow 的优点
以下是许多从业者在决定是否使用 TensorFlow 进行机器学习时所考虑的几个主要优点:
-
库管理:TensorFlow 库有一个庞大的开发者社区,他们不断更新库,发布新版本以修复 bug,增加新的函数和类,以反映当前领域的进展,并支持多种编程语言。
-
流水线:TensorFlow 支持端到端的模型生产,从在支持 GPU 处理的高并行环境中开发模型,到一整套模型部署工具。此外,TensorFlow 还有轻量级的库,用于将训练好的 TensorFlow 模型部署到移动设备和嵌入式设备上,例如物联网(IoT)设备。
-
社区支持:使用并支持该库的开发者社区庞大,他们互相支持,正因如此,初学者能轻松地获得他们想要的结果。
-
开源:TensorFlow 是一个开源库,其代码库对任何人开放,允许用户根据自己的需求使用和修改。
-
支持多种语言:虽然该库本身是为 Python 设计的,但现在也可以在 JavaScript 中训练和部署模型。
TensorFlow 的缺点
以下是使用 TensorFlow 的一些缺点:
-
计算速度:由于 TensorFlow 的主要编程语言是 Python,因此它的计算速度不如其他语言(如 C++)的原生实现。
-
陡峭的学习曲线:与其他机器学习库(如 Keras)相比,TensorFlow 的学习曲线较为陡峭,这可能会使得新手在没有现成示例代码的情况下,创建自己的模型变得具有挑战性。
现在你已经了解了 TensorFlow 是什么,接下来的部分将演示如何使用 Python 来操作 TensorFlow 库。
Python 中的 TensorFlow 库
你可以通过导入特定的库在 Python 中使用 TensorFlow。你可以使用import
语句在 Python 中导入库:
import tensorflow as tf
在之前的命令中,你已导入了 TensorFlow 库并使用了简写tf
。
在接下来的练习中,你将学习如何导入 TensorFlow 库并检查其版本,以便你能够利用库提供的类和函数,这是使用该库时非常重要和必要的第一步。
练习 1.01:验证你的 TensorFlow 版本
在这个练习中,你将加载 TensorFlow 并检查系统中安装的版本。
执行以下步骤:
-
打开一个 Jupyter 笔记本来实现这个练习,在终端中输入
jupyter notebook
。 -
通过在 Jupyter 单元格中输入以下代码来导入 TensorFlow 库:
import tensorflow as tf
-
使用以下命令验证 TensorFlow 的版本:
tf.__version__
这将产生以下输出:
'2.6.0'
从之前的输出中可以看到,TensorFlow 的版本是
2.6.0
。注意
如果你没有按照前言中的步骤设置环境,版本可能会在你的系统上有所不同。
在这个练习中,你成功导入了 TensorFlow。你还检查了系统上安装的 TensorFlow 版本。
这个任务可以在 Python 中对任何导入的库执行,并且对于调试和参考文档非常有用。
使用 TensorFlow 的潜在应用非常广泛,它已经取得了令人印象深刻的成果,像 Airbnb 这样的公司利用 TensorFlow 对平台上的图像进行分类,GE Healthcare 则使用 TensorFlow 在大脑的 MRI 图像中识别解剖结构。要学习如何为自己的应用创建强大的模型,你首先需要学习构成 TensorFlow 中可实现的机器学习模型的基本数学原理和运算。数学运算可能会让新用户感到畏惧,但对它们的全面理解是构建高效模型的关键。
张量简介
张量可以被看作是人工神经网络(ANNs)的核心组件——输入数据、输出预测以及在训练过程中学习到的权重都是张量。信息通过一系列线性和非线性变换传播,将输入数据转化为预测结果。本节展示了如何对张量应用线性变换,如加法、转置和乘法。其他线性变换,如旋转、反射和剪切,也存在。然而,它们在人工神经网络中的应用较为少见。
标量、向量、矩阵和张量
张量可以表示为多维数组。张量所跨越的维度数量称为张量的阶数。阶数为0
、1
和2
的张量常常使用,并且各自有独立的名称,分别是标量、向量和矩阵,尽管术语张量可以用来描述它们中的任何一个。图 1.2展示了不同阶张量的一些示例。从左到右分别是标量、向量、矩阵和一个三维张量,每个元素表示一个不同的数字,下标表示该元素在张量中的位置:
图 1.2:标量、向量、矩阵和张量的可视化表示
标量、向量、矩阵和张量的正式定义如下:
-
标量:标量由一个单一的数字组成,因此它是一个零维数组,是零阶张量的例子。标量没有任何轴。例如,物体的宽度就是一个标量。
-
向量:向量是一维数组,是一阶张量的例子。它们可以被看作是值的列表。向量有一个轴。给定物体的宽度、高度和深度的大小是一个向量场的例子。
-
矩阵:矩阵是具有两个轴的二维数组,是二阶张量的一个例子。矩阵可能用于存储多个对象的大小。矩阵的每个维度包含每个对象的大小(宽度、高度、深度),另一个矩阵维度则用于区分对象。
-
3
或更多。张量可以用来存储多个对象的大小及其随时间变化的位置。矩阵的第一维包含每个对象的大小(宽度、高度、深度),第二维用于区分不同的对象,第三维描述这些对象随时间变化的位置。
张量可以使用 TensorFlow 库中的Variable
类来创建,并传入一个表示张量的值。标量可以传入浮动值或整数,向量可以传入浮动值或整数的列表,矩阵可以传入浮动值或整数的嵌套列表,依此类推。以下命令演示了如何使用Variable
类,其中传入了张量的预期值列表以及任何需要显式定义的其他属性:
tensor1 = tf.Variable([1,2,3], dtype=tf.int32, \
name='my_tensor', trainable=True)
结果Variable
对象具有几个常用的属性,具体如下:
-
dtype
:Variable
对象的数据类型(对于上面定义的张量,数据类型是tf.int32
)。该属性的默认值由传入的值决定。 -
shape
:Variable
对象的维度数和每个维度的长度(对于上面定义的张量,形状是[3]
)。该属性的默认值也由传入的值决定。 -
name
:Variable
对象的名称(对于上面定义的张量,张量的名称定义为'my_tensor'
)。该属性的默认值为Variable
。 -
trainable
:该属性指示Variable
对象是否可以在模型训练过程中更新(对于上面定义的张量,trainable
参数设置为true
)。该属性的默认值是true
。注意
你可以在这里阅读更多关于
Variable
对象属性的内容:www.tensorflow.org/api_docs/python/tf/Variable
。
Variable
对象的shape
属性可以按如下方式调用:
tensor1.shape
shape
属性给出了张量的形状,即它是标量、向量、矩阵等。前面命令的输出将是[3]
,因为该张量只有一个维度,且该维度上有三个值。
可以使用 TensorFlow 中的rank
函数来确定张量的阶数。通过将张量作为唯一的参数传递给该函数,结果将是一个整数值:
tf.rank(tensor1)
以下命令的输出将是一个零维整数张量,表示输入张量的阶数。在此情况下,tensor1
的阶数为1
,因为该张量只有一个维度。
在以下练习中,您将学习如何使用 TensorFlow 的 Variable
类创建各种秩的张量。
练习 1.02:在 TensorFlow 中创建标量、向量、矩阵和张量
三个不同政党在 A 和 B 两个选区的不同候选人所获得的选票如下:
在 A 和 B 两个选区的各个政党的候选人选票分布](https://github.com/OpenDocCN/freelearn-dl-pt3-zh/raw/master/docs/tf-ws/img/B16341_01_03.jpg)
图 1.3:三种不同政党在 A、B 两个选区的不同候选人获得的选票数
您需要执行以下操作:
-
创建一个标量来存储选区 A 中
X
政党候选人 1
的选票数,即4113
,并检查其形状和秩。 -
创建一个向量来表示
X
政党在 A 选区中三名候选人所获得选票的比例,并检查其形状和秩。 -
创建一个矩阵来表示
X
和Y
政党在三个不同候选人中的选票,并检查其形状和秩。 -
创建一个张量来表示三名候选人在两个不同选区、三种不同政党中的选票,并检查其形状和秩。
执行以下步骤以完成本练习:
-
导入 TensorFlow 库:
import tensorflow as tf
-
使用 TensorFlow 的
Variable
类创建一个整数变量,并传入4113
来表示某个候选人获得的选票数。同时,传入tf.int16
作为第二个参数,以确保输入的数字是整数数据类型。打印结果:int_variable = tf.Variable(4113, tf.int16) int_variable
这将产生以下输出:
<tf.Variable 'Variable:0' shape=() dtype=int32, numpy=4113>
在这里,您可以看到所创建变量的属性,包括名称
Variable:0
、形状、数据类型以及张量的 NumPy 表示。 -
使用 TensorFlow 的
rank
函数打印创建的变量的秩:tf.rank(int_variable)
这将产生以下输出:
<tf.Tensor: shape=(), dtype=int32, numpy=0>
从 NumPy 对张量的表示中可以看到,创建的整数变量的秩是
0
。 -
通过调用
numpy
属性访问秩的整数变量:tf.rank(int_variable).numpy()
这将产生以下输出:
0
标量的秩是
0
。注意
rank
函数的所有属性都可以被调用,包括shape
和dtype
属性。 -
调用整数的
shape
属性以查看张量的形状:int_variable.shape
这将产生以下输出:
TensorShape([])
上面的输出表明张量的形状没有大小,代表它是一个标量。
-
打印标量变量的
shape
,以 Python 列表的形式表示:int_variable.shape.as_list()
这将产生以下输出:
[]
-
使用 TensorFlow 的
Variable
类创建一个vector
变量。传入一个列表来表示三名候选人所获得选票的比例,并传入第二个参数tf.float32
以确保它是一个float
数据类型。打印结果:vector_variable = tf.Variable([0.23, 0.42, 0.35], \ tf.float32) vector_variable
这将产生以下输出:
<tf.Variable 'Variable:0' shape(3,) dtype=float32, numpy=array([0.23, 0.42, 0.35], dtype=float32)>
您可以看到,形状和 NumPy 属性与之前创建的标量变量不同。现在的形状是
(3,)
,表示张量是一个一维的,沿该维度有三个元素。 -
使用 TensorFlow 的
rank
函数打印vector
变量的秩,作为 NumPy 变量:tf.rank(vector_variable).numpy()
这将产生以下输出:
1
在这里,您可以看到,向量变量的秩为
1
,确认该变量是一维的。 -
打印
vector
变量的形状作为 Python 列表:vector_variable.shape.as_list()
这将产生以下输出:
[3]
-
使用 TensorFlow 的
Variable
类创建一个矩阵变量。传入一个整数的列表列表,表示在两个不同选区中,三个不同候选人的投票结果。该矩阵将有三列,表示候选人,和两行,表示选区。传入第二个参数,数据类型为tf.int32
,以确保其为整数数据类型。打印结果:matrix_variable = tf.Variable([[4113, 7511, 6259], \ [3870, 6725, 6962]], \ tf.int32) matrix_variable
这将产生以下输出:
图 1.4:TensorFlow 变量的输出
-
打印矩阵变量的秩作为 NumPy 变量:
tf.rank(matrix_variable).numpy()
这将产生以下输出:
2
在这里,您可以看到矩阵变量的秩为
2
,确认该变量是二维的。 -
打印矩阵变量的形状作为 Python 列表:
matrix_variable.shape.as_list()
这将产生以下输出:
[2, 3]
-
使用 TensorFlow 的
Variable
类创建一个张量变量。传入一个三重嵌套的整数列表,表示三个不同候选人在两个不同选区、三个政党中的投票结果。打印结果:tensor_variable = tf.Variable([[[4113, 7511, 6259], \ [3870, 6725, 6962]], \ [[5102, 7038, 6591], \ [3661, 5901, 6235]], \ [[951, 1208, 1098], \ [870, 645, 948]]]) tensor_variable
这将产生以下输出:
图 1.5:TensorFlow 变量的输出
-
打印张量变量的秩作为 NumPy 变量:
tf.rank(tensor_variable).numpy()
这将产生以下输出:
3
在这里,您可以看到,张量变量的秩为
3
,确认该变量是三维的。 -
打印张量变量的形状作为 Python 列表:
tensor_variable.shape.as_list()
这将产生以下输出:
[3, 2, 3]
结果显示,生成的张量的形状是一个列表对象。
在这个练习中,您已经成功地使用 TensorFlow 的 Variable
类从政治投票数据中创建了各种秩的张量。首先,您创建了标量,这是秩为 0
的张量。接下来,您创建了向量,这是秩为 1
的张量。然后,创建了矩阵,这是秩为 2
的张量。最后,创建了秩为 3
或更高的张量。您通过使用 TensorFlow 的 rank
函数确认了所创建的张量的秩,并通过调用张量的 shape
属性验证了它们的形状。
在下一部分,您将结合张量,通过张量加法创建新张量。
张量加法
张量可以相加以创建新的张量。在本章中,你将使用矩阵的例子,但这一概念可以扩展到任何秩的张量。矩阵在特定条件下可以与标量、向量和其他矩阵相加,这个过程被称为广播。广播是指对不同形状的张量进行数组算术运算的过程。
如果两个矩阵形状相同,则可以将它们相加(或相减)。对于这种矩阵-矩阵加法,结果矩阵由输入矩阵逐元素相加得到。因此,结果矩阵的形状与两个输入矩阵相同。你可以定义矩阵Z = [Z_ij]
为矩阵和Z = X + Y
,其中z_ij = x_ij + y_ij
,并且Z
中的每个元素是X
和Y
中相同位置元素的和。
矩阵加法是交换律的,这意味着X
和Y
的顺序无关,即X + Y = Y + X
。矩阵加法也是结合律的,这意味着即使加法顺序不同,或者即使运算应用多次,结果也相同,即X + (Y + Z) = (X + Y) + Z
。
相同的矩阵加法原则适用于标量、向量和张量。以下图为例:
图 1.6:矩阵-矩阵加法的视觉示例
标量也可以与矩阵相加。在这里,矩阵的每个元素都与标量逐个相加,如图 1.7所示:
图 1.7:矩阵-标量加法的视觉示例
加法是一个重要的变换,因其在张量中应用非常频繁。例如,在开发人工神经网络(ANN)时,一个常见的变换是为层添加偏置。这是将与 ANN 层大小相同的常量张量数组加到该层上。因此,了解如何以及何时应用这个看似简单的变换是很重要的。
张量加法可以通过使用add
函数并传递张量作为参数来在 TensorFlow 中执行,或者简单地使用+
运算符,如下所示:
tensor1 = tf.Variable([1,2,3])
tensor2 = tf.Variable([4,5,6])
tensor_add1 = tf.add(tensor1, tensor2)
tensor_add2 = tensor1 + tensor2
在以下练习中,你将执行 TensorFlow 中标量、向量和矩阵的张量加法。
练习 1.03:在 TensorFlow 中执行张量加法
A 区和 B 区三种不同政治党派不同候选人投票数如下:
图 1.8:A 区和 B 区三种不同政治党派不同候选人投票数
你的任务如下:
-
存储 A 区政治党派 X 获得的总票数。
-
存储 A 区每个政党获得的总票数。
-
存储两个选区中每个政党所投的总票数。
执行以下步骤完成本次练习:
-
导入 TensorFlow 库:
import tensorflow as tf
-
使用 TensorFlow 的
Variable
类创建三个标量变量,表示在选区 A 中政党 X 的三位候选人所投的票数:int1 = tf.Variable(4113, tf.int32) int2 = tf.Variable(7511, tf.int32) int3 = tf.Variable(6529, tf.int32)
-
创建一个新变量,用来存储政党 X 在选区 A 中的总票数:
int_sum = int1+int2+int3
-
打印两个变量之和作为 NumPy 变量的结果:
int_sum.numpy()
这将产生以下输出:
18153
-
创建三个向量,表示在选区 A 中不同政党的投票数量,每个向量有一行三列:
vec1 = tf.Variable([4113, 3870, 5102], tf.int32) vec2 = tf.Variable([7511, 6725, 7038], tf.int32) vec3 = tf.Variable([6529, 6962, 6591], tf.int32)
-
创建一个新变量,用来存储每个政党在选区 A 中的总票数:
vec_sum = vec1 + vec2 + vec3
-
打印两个变量之和作为 NumPy 数组的结果:
vec_sum.numpy()
这将产生以下输出:
array([18153, 17557, 18731])
-
通过执行对向量中每个元素的加法,验证向量加法是否符合预期:
print((vec1[0] + vec2[0] + vec3[0]).numpy()) print((vec1[1] + vec2[1] + vec3[1]).numpy()) print((vec1[2] + vec2[2] + vec3[2]).numpy())
这将产生以下输出:
18153 17557 18731
你可以看到,三个向量上的
+
操作仅仅是向量的逐元素加法。 -
创建三个矩阵,用来存储每个选区中各政党的候选人所投的票数:
matrix1 = tf.Variable([[4113, 3870, 5102], \ [3611, 951, 870]], tf.int32) matrix2 = tf.Variable([[7511, 6725, 7038], \ [5901, 1208, 645]], tf.int32) matrix3 = tf.Variable([[6529, 6962, 6591], \ [6235, 1098, 948]], tf.int32)
-
验证这三个张量是否具有相同的形状:
matrix1.shape == matrix2.shape == matrix3.shape
这将产生以下输出:
True
-
创建一个新变量,用来存储两个选区中每个政党所投的总票数:
matrix_sum = matrix1 + matrix2 + matrix3
-
打印两个变量之和作为 NumPy 数组的结果:
matrix_sum.numpy()
这将产生以下输出,表示每个选区中每个候选人和每个政党的总票数:
图 1.9:作为 NumPy 变量的矩阵求和输出
-
通过执行对向量中每个元素的加法,验证张量加法是否符合预期:
print((matrix1[0][0] + matrix2[0][0] + matrix3[0][0]).numpy()) print((matrix1[0][1] + matrix2[0][1] + matrix3[0][1]).numpy()) print((matrix1[0][2] + matrix2[0][2] + matrix3[0][2]).numpy()) print((matrix1[1][0] + matrix2[1][0] + matrix3[1][0]).numpy()) print((matrix1[1][1] + matrix2[1][1] + matrix3[1][1]).numpy()) print((matrix1[1][2] + matrix2[1][2] + matrix3[1][2]).numpy())
这将产生以下输出:
18153 17557 18731 15747 3257 2463
你可以看到,
+
操作等同于对创建的三个矩阵进行逐元素加法。
在本次练习中,你成功地对代表政治候选人选票的数据进行了张量加法。这个转换可以通过使用+
操作来实现。你还验证了加法是逐元素进行的,并且确保转换有效的一种方式是确保张量具有相同的秩和形状。
在接下来的活动中,你将进一步练习在 TensorFlow 中进行张量加法。
活动 1.01:在 TensorFlow 中进行张量加法
你在一家公司工作,该公司有三个地点,每个地点有两位销售人员,并且每个地点销售三种产品。你需要对张量进行求和,以表示各地点每个产品的总收入。
图 1.10:每个销售人员在不同地点销售的不同产品数量
你将采取的步骤如下:
-
导入 TensorFlow 库。
-
使用 TensorFlow 的
Variable
类,创建两个标量来表示Location X
上所有销售人员的Product A
总收入。第一个变量的值为2706
,第二个变量的值为2386
。 -
创建一个新变量作为标量的和并打印结果。
你应该得到以下输出:
5092
-
使用 TensorFlow 的
Variable
类创建一个值为[2706, 2799, 5102]
的向量和一个值为95
的标量。 -
创建一个新变量,将标量与向量相加,表示
Salesperson 1
在Location X
的销售目标并打印结果。你应该得到以下输出:
图 1.11:整数向量求和的输出,作为 NumPy 变量
-
使用 TensorFlow 的
Variable
类创建三个秩为 2 的张量,表示每个销售人员、产品和位置的收入。第一个张量的值为[[2706, 2799, 5102], [2386, 4089, 5932]]
,第二个张量的值为[[5901, 1208, 645], [6235, 1098, 948]]
,第三个张量的值为[[3908, 2339, 5520], [4544, 1978, 4729]]
。 -
创建一个新变量作为矩阵的和并打印结果:
图 1.12:矩阵求和的输出,作为 NumPy 变量
注意
本活动的解答可以通过此链接找到。
在接下来的章节中,你将学习如何改变张量的形状和秩。
形状重塑
一些操作,如加法,只有在满足特定条件时才能应用于张量。重塑是修改张量形状的一种方法,使得这些操作可以执行。重塑将张量的元素重新排列成一个不同大小的张量。只要总元素的数量保持不变,任何大小的张量都可以进行重塑。
例如,一个 (4x3)
矩阵可以被重塑为一个 (6x2)
矩阵,因为它们都有 12
个元素。秩,即维度的数量,也可以在重塑过程中发生变化。例如,一个秩为 2
的 (4x3)
矩阵可以被重塑为一个秩为 3
的 (3x2x2)
张量。该 (4x3)
矩阵也可以被重塑为一个 (12x1)
向量,其秩从 2
变为 1
。
图 1.13 说明了张量的重塑。左侧是一个形状为 (3x2)
的张量,它可以被重塑为形状为 (2x3)
、(6)
或 (6x1)
的张量。在这里,元素的数量,即六,保持不变,尽管张量的形状和秩发生了变化:
图 1.13:将 (3x2) 张量重塑为不同形状张量的可视化表示
张量的形状重塑可以通过 TensorFlow 的 reshape
函数来实现,并将张量和新张量的目标形状作为参数传入:
tensor1 = tf.Variable([1,2,3,4,5,6])
tensor_reshape = tf.reshape(tensor1, shape=[3,2])
在这里,创建了一个新的张量,其元素与原张量相同;然而,形状是[3,2]
,而不是[6]
。
下一部分介绍了张量转置,这是另一种修改张量形状的方法。
张量转置
当一个张量被转置时,张量中的元素会按特定顺序重新排列。转置操作通常用张量上的 T
上标表示。张量中每个元素的新位置可以通过 (x12…k
)T =
xk…21来确定。对于秩为
2`的矩阵或张量,行变为列,反之亦然。矩阵转置的示例如图 1.14所示。任何秩的张量都可以进行转置,且通常会导致形状的变化:
图 1.14:在 (3x2) 矩阵上的张量转置的可视化表示
下图展示了矩阵A
和B
的转置特性:
图 1.15:张量转置特性,其中 X 和 Y 是张量
如果一个张量的转置等于原张量,则称该张量为对称的。
张量转置可以通过 TensorFlow 的 transpose
函数来实现,并将张量作为唯一参数传入:
tensor1 = tf.Variable([1,2,3,4,5,6])
tensor_transpose = tf.transpose(tensor1)
在进行张量转置时,只有一个可能的结果;然而,改变张量形状则有多个可能的结果,这取决于输出的目标形状。
在以下练习中,使用 TensorFlow 演示张量的形状重塑和转置操作。
练习 1.04:在 TensorFlow 中执行张量重塑和转置
在本练习中,你将学习如何使用 TensorFlow 库进行张量形状重塑和转置。
执行以下步骤:
-
导入 TensorFlow 库,并使用 TensorFlow 的
Variable
类创建一个具有两行四列的矩阵:import tensorflow as tf matrix1 = tf.Variable([[1,2,3,4], [5,6,7,8]])
-
通过调用矩阵的
shape
属性,将矩阵作为 Python 列表来验证矩阵的形状:matrix1.shape.as_list()
这将产生以下输出:
[2, 4]
你会看到矩阵的形状是
[2,4]
。 -
使用 TensorFlow 的
reshape
函数,通过传入矩阵和所需的新形状,将矩阵更改为具有四行两列的矩阵:reshape1 = tf.reshape(matrix1, shape=[4, 2]) reshape1
你应该得到以下输出:
图 1.16:重塑后的矩阵
-
通过调用
shape
属性并将其作为 Python 列表来验证重塑后的矩阵的形状:reshape1.shape.as_list()
这将产生以下输出:
[4, 2]
在这里,你可以看到矩阵的形状已经改变为你期望的形状
[4,2]
。 -
使用 TensorFlow 的
reshape
函数将矩阵转换为一行八列的矩阵。将矩阵和期望的新形状作为参数传递给reshape
函数:reshape2 = tf.reshape(matrix1, shape=[1, 8]) reshape2
你应该会得到以下输出:
<tf.Tensor: shape=(1, 8), dtype=int32, numpy=array([[1, 2, 3, 4, 5, 6, 7, 8]])>
-
通过调用
shape
属性作为 Python 列表来验证重新塑形后的矩阵形状:reshape2.shape.as_list()
这将产生以下输出:
[1, 8]
前面的输出确认了重新塑形矩阵的形状为
[1, 8]
。 -
使用 TensorFlow 的
reshape
函数将矩阵转换为八行一列的矩阵,将矩阵和期望的新形状作为参数传递给reshape
函数:reshape3 = tf.reshape(matrix1, shape=[8, 1]) reshape3
你应该会得到以下输出:
图 1.17:形状为 (8, 1) 的重新塑形矩阵
-
通过调用
shape
属性作为 Python 列表来验证重新塑形后的矩阵形状:reshape3.shape.as_list()
这将产生以下输出:
[8, 1]
前面的输出确认了重新塑形矩阵的形状为
[8, 1]
。 -
使用 TensorFlow 的
reshape
函数将矩阵转换为大小为2x2x2
的张量。将矩阵和期望的新形状作为参数传递给 reshape 函数:reshape4 = tf.reshape(matrix1, shape=[2, 2, 2]) reshape4
你应该会得到以下输出:
图 1.18:形状为 (2, 2, 2) 的重新塑形矩阵
-
通过调用
shape
属性作为 Python 列表来验证重新塑形后的矩阵形状:reshape4.shape.as_list()
这将产生以下输出:
[2, 2, 2]
前面的输出确认了重新塑形矩阵的形状为
[2, 2, 2]
。 -
使用 TensorFlow 的
rank
函数验证秩是否已更改,并将结果作为 NumPy 变量打印:tf.rank(reshape4).numpy()
这将产生以下输出:
3
-
使用 TensorFlow 的
transpose
函数将大小为2X4
的矩阵转换为大小为4x2
的矩阵:transpose1 = tf.transpose(matrix1) transpose1
你应该会得到以下输出:
图 1.19:转置矩阵
-
验证
reshape
函数和transpose
函数在应用于给定矩阵时,是否会产生不同的结果:transpose1 == reshape1
图 1.20:验证转置和重新塑形产生不同结果
-
使用 TensorFlow 的
transpose
函数转置 步骤 9 中的重新塑形矩阵:transpose2 = tf.transpose(reshape4) transpose2
这将产生以下输出:
图 1.21:转置后的重新塑形张量的输出
这个结果展示了在重新塑形和转置张量后,得到的张量的形状。
在本次练习中,你已成功通过重新塑形或转置操作修改了张量的形状。你研究了在重新塑形和转置操作后,张量的形状和秩如何变化。
在接下来的活动中,你将测试如何使用 TensorFlow 重新塑形和转置张量的知识。
活动 1.02:在 TensorFlow 中执行张量的重塑和转置
在此活动中,你需要模拟将 24 名学校儿童分组进行课堂项目。每个重新塑形或转置后的张量的维度将表示每个小组的大小。
执行以下步骤:
-
导入 TensorFlow 库。
-
使用
Variable
类创建一个包含 24 个单调递增元素的单维张量,用来表示学校儿童的 ID。验证矩阵的形状。你应该得到以下输出:
[24]
-
使用 TensorFlow 的
reshape
函数将矩阵重塑为 12 行 2 列,表示 12 对学校儿童。验证新矩阵的形状。你应该得到以下输出:
[12, 2]
-
使用 TensorFlow 的
reshape
函数将原始矩阵重塑为3x4x2
的形状,表示 3 组由 4 对学校儿童组成的小组。验证新张量的形状。你应该得到以下输出:
[3, 4, 2]
-
验证新张量的秩为
3
。 -
将第 3 步中创建的张量进行转置,使用 TensorFlow 的
transpose
函数表示 12 名学生的 2 组。验证新张量的形状。你应该得到以下输出:
[2, 12]
注意
本活动的解决方案可以通过此链接找到。
在本节中,你被介绍了一些人工神经网络(ANNs)的基本组件——张量。你还学习了张量的一些基本操作,如加法、转置和重新塑形。你通过使用 TensorFlow 库中的函数实现了这些概念。
在下一主题中,你将通过学习与人工神经网络(ANNs)相关的另一个重要变换——张量乘法,来扩展你对线性变换的理解。
张量乘法
张量乘法是另一个在构建和训练人工神经网络(ANNs)过程中常用的基本操作,因为信息通过网络从输入传递到结果,过程中涉及一系列加法和乘法。加法的规则简单且直观,但张量的规则则更加复杂。张量乘法不仅仅是元素之间的逐元素相乘,而是通过计算每个张量的整行/整列的点积来计算结果张量的每个元素。这一部分将解释二维张量或矩阵的乘法是如何工作的。但更高阶的张量也可以进行乘法操作。
给定矩阵 X = [x
ij]
m x n,和另一个矩阵
Y = [yij
]n x p
,这两个矩阵的乘积是 Z = XY = [z
ij]
m x p,每个元素
zij
都按元素定义如 。结果矩阵的形状与矩阵乘积的外部维度相同,即第一个矩阵的行数和第二个矩阵的列数。为了使乘法有效,矩阵乘积的内部维度必须匹配,即第一个矩阵的列数和第二个矩阵的行数必须相对应。
矩阵乘法的内部和外部维度的概念显示在下图中,其中 X
表示第一个矩阵,Y
表示第二个矩阵:
Figure 1.22: 矩阵乘法中内部和外部维度的可视化表示
与矩阵加法不同,矩阵乘法不是交换的,这意味着矩阵在乘积中的顺序很重要:
Figure 1.23: 矩阵乘法是非交换的
例如,假设你有以下两个矩阵:
Figure 1.24: 两个矩阵 X 和 Y
构建乘积的一种方式是首先有矩阵 X
,然后乘以 Y
:
Figure 1.25: 矩阵 X 乘以 Y 的可视化表示,X•Y
这导致一个 2x2
的矩阵。构建乘积的另一种方式是首先有 Y
,然后乘以 X
:
Figure 1.26: 矩阵 Y 乘以 X 的可视化表示,Y•X
在这里你可以看到从乘积 YX
形成的矩阵是一个 3x3
的矩阵,与从乘积 XY
形成的矩阵非常不同。
在 TensorFlow 中,可以通过使用 matmul
函数执行张量乘法,并按照它们需要被乘的顺序将张量作为参数传递进去:
tensor1 = tf.Variable([[1,2,3]])
tensor2 = tf.Variable([[1],[2],[3]])
tensor_mult = tf.matmul(tensor1, tensor2)
张量乘法也可以通过使用 @
运算符来实现,如下所示:
tensor_mult = tensor1 @ tensor2
标量-张量乘法要简单得多,只是张量中每个元素乘以标量,因此 λX = [λx
ij…k]
,其中 λ
是标量,X
是张量。
在 TensorFlow 中,可以通过使用 matmul
函数或使用 *
运算符来实现标量乘法:
tensor1 = tf.Variable([[1,2,3]])
scalar_mult = 5 * tensor1
在下面的练习中,您将使用 TensorFlow 库执行张量乘法。
练习 1.05:在 TensorFlow 中执行张量乘法
在本练习中,你将使用 TensorFlow 的 matmul
函数和 @
运算符执行张量乘法。本练习将以三明治零售商的数据为例,表示不同三明治的配料及配料的成本。你将使用矩阵乘法来确定每个三明治的成本。
三明治食谱:
图 1.27:三明治食谱
配料详情:
图 1.28:配料详情
销售预测:
图 1.29:销售预测
执行以下步骤:
-
导入 TensorFlow 库:
import tensorflow as tf
-
创建一个矩阵表示不同的三明治食谱,行代表三种不同的三明治种类,列代表五种不同配料的组合和数量,使用
Variable
类:matrix1 = tf.Variable([[1.0,0.0,3.0,1.0,2.0], \ [0.0,1.0,1.0,1.0,1.0], \ [2.0,1.0,0.0,2.0,0.0]], \ tf.float32) matrix1
你应该得到以下输出:
图 1.30:表示制作三明治所需配料数量的矩阵
-
通过调用矩阵的
shape
属性作为 Python 列表,验证矩阵的形状:matrix1.shape.as_list()
这将产生以下输出:
[3, 5]
-
创建一个第二个矩阵,表示每种配料的成本和重量,其中行代表五种配料,列代表配料的成本和重量(单位:克):
matrix2 = tf.Variable([[0.49, 103], \ [0.18, 38], \ [0.24, 69], \ [1.02, 75], \ [0.68, 78]]) matrix2
你应该得到以下结果:
图 1.31:表示每种配料的成本和重量的矩阵
-
使用 TensorFlow 的
matmul
函数执行matrix1
和matrix2
的矩阵乘法:matmul1 = tf.matmul(matrix1, matrix2) matmul1
这将产生以下输出:
图 1.32:矩阵乘法的输出
-
创建一个矩阵来表示五家不同商店对三种三明治的销售预测:
matrix3 = tf.Variable([[120.0, 100.0, 90.0], \ [30.0, 15.0, 20.0], \ [220.0, 240.0, 185.0], \ [145.0, 160.0, 155.0], \ [330.0, 295.0, 290.0]])
-
将
matrix3
与matrix1
和matrix2
相乘的结果相乘,以给出每家五家商店的预期成本和重量:matmul3 = matrix3 @ matmul1 matmul3
这将产生以下输出:
图 1.33:矩阵乘法的输出
乘法得到的张量显示了每家商店三明治的预期成本以及总配料的预期重量。
在本练习中,你已经成功学会了如何使用几个操作符在 TensorFlow 中进行矩阵乘法。你使用了 TensorFlow 的matmul
函数以及简写符号@
操作符。它们都能执行乘法操作;不过,matmul
函数有多个不同的参数,可以传入,使其更加灵活。
注意
你可以在这里阅读更多关于matmul
函数的内容:www.tensorflow.org/api_docs/python/tf/linalg/matmul
。
在下一节中,你将探索一些与 ANN 相关的其他数学概念。你将探索前向传播和反向传播,以及激活函数。
优化
在本节中,你将学习一些对于训练机器学习模型至关重要的优化方法。优化是通过更新 ANN 各层的权重,使得 ANN 预测值与训练数据的真实值之间的误差最小化的过程。
前向传播
前向传播是信息在人工神经网络(ANN)中传播的过程。在网络的每一层都会发生一系列张量乘法和加法操作,直到最终输出。前向传播在图 1.37中进行了说明,展示了一个单隐层的 ANN。输入数据有两个特征,而输出层对每个输入记录只有一个值。
隐藏层和输出层的权重和偏差显示为具有适当索引的矩阵和向量。对于隐藏层,权重矩阵的行数等于输入特征的数量,列数等于隐藏层单元的数量。因此,W1
有两行三列,因为输入X
有两个特征。同样,W2
有三行一列,隐藏层有三个单元,输出层的大小为一。然而,偏差始终是一个大小等于该层节点数量的向量,并与输入和权重矩阵的乘积相加。
图 1.34:单层人工神经网络
执行前向传播的步骤如下:
-
X
是网络的输入,也是隐藏层的输入。首先,输入矩阵X
与隐藏层的权重矩阵W1
相乘,然后加上偏差b1
:z1 = X*W1 + b1
下面是操作后结果张量的形状示例。如果输入的大小为
nX2
,其中n
是输入示例的数量,W1
的大小为2X3
,b1
的大小为1X3
,则结果矩阵z1
的大小将为nX3
。 -
z1
是隐藏层的输出,它是W2
与偏差b2
相加后的结果:Y = z1 * W2 + b2
为了理解最终张量的形状,考虑以下示例。如果输出层的输入
z1
大小为nX3
,W2
的大小为3X1
,b1
的大小为1X1
,则结果矩阵Y
的大小将为nX1
,表示每个训练样本的一个结果。
该模型中的总参数数量等于W1
、W2
、b1
和b2
中元素的总和。因此,可以通过求和每个权重矩阵和偏置参数中的元素来计算参数的数量,计算结果为6 + 3 + 3 + 1 = 13
。这些是需要在训练人工神经网络(ANN)过程中学习的参数。
在前向传播步骤之后,你必须评估你的模型,并将其与实际目标值进行比较。这是通过使用损失函数来实现的。均方误差(Mean Squared Error,MSE),即真实值和预测值之间的平方差的均值,是回归任务中损失函数的一个例子。一旦计算出损失,就需要更新权重以减少损失,并且通过反向传播找出更新权重的量和方向。
反向传播
loss
函数对预测输出的影响如下:
loss = L(y_predicted)
损失函数对模型参数的导数可以告诉你,增加或减少模型参数是否会导致损失增加或减少。反向传播的过程通过应用微积分的链式法则,从神经网络的输出层到输入层,在每一层计算损失函数对模型参数的导数来实现。
微积分的链式法则是一种通过中间函数计算复合函数导数的技术。该函数的广义版本可以写作如下:
dz/dx = dz/dy * dy/dx
这里,dz/dx
是复合函数,y
是中间函数。在人工神经网络中,复合函数是损失函数与模型参数的关系,中间函数则表示隐藏层。因此,损失函数对模型参数的导数可以通过将损失对预测输出的导数与预测输出对模型参数的导数相乘来计算。
在下一节中,你将学习如何根据损失函数对每个权重的导数来更新权重参数,以最小化损失。
学习最优参数
在本节中,您将看到如何迭代选择最优权重。您知道前向传播通过一系列张量加法和乘法在网络中传输信息,而反向传播是理解每个模型权重变化对损失的过程。下一步是使用反向传播的结果更新权重,以便根据损失函数减少误差。这个过程称为学习参数,使用优化算法实现。一个常用的优化算法通常称为梯度下降。
在学习最优参数时,应用优化算法直到损失函数达到最小值。通常在给定步数之后停止或在损失函数变化微不足道时停止。如果将损失作为每个模型参数的函数绘制,损失函数的形状类似于一个凸形,只有一个最小值,优化函数的目标是找到这个最小值。
下图显示了特定特征的损失函数:
图 1.35:梯度下降算法找到的最优参数以最小化损失
首先,通过随机设置每个权重的参数(在图中表示为 p
1)。然后计算该模型参数的损失,记为 l
1. 后向传播步骤确定了损失相对于模型参数的导数,并确定了模型应更新的方向。下一个模型参数 p
2 等于当前模型参数减去学习率(α
)乘以导数值。学习率是在模型训练过程之前设置的超参数。通过导数值的乘积,当参数远离导数绝对值较大的最小值时,会采取更大的步长。然后计算损失 l
2,并继续该过程,直到达到最小损失 l
m,并找到最优参数 p
m。
总结一下,这些是优化算法执行以找到最优参数的迭代步骤:
-
使用前向传播和当前参数预测整个数据集的输出。
-
应用损失函数计算预测输出中所有示例的损失。
-
使用反向传播计算每层权重和偏差对损失的导数。
-
使用导数值和学习率更新权重和偏差。
TensorFlow 中的优化器
TensorFlow 中有几种不同的优化器,每个优化器都基于不同的优化算法,旨在找到损失函数的全局最小值。它们都基于梯度下降算法,尽管在实现上有所不同。TensorFlow 中可用的优化器包括以下几种:
-
随机梯度下降(SGD):SGD 算法将梯度下降应用于小批次的训练数据。在使用 TensorFlow 中的优化器时,还可以使用一个动量参数,它通过对计算出的梯度进行指数平滑,从而加速训练过程。
-
Adam:这是一种基于连续自适应估计一阶和二阶矩的 SGD 方法。
-
均方根传播(RMSProp):这是一种未公开的自适应学习率优化器。RMSProp 在每步找到损失最小值时,将学习率除以梯度平方的平均值,这样得到的学习率呈指数衰减。
-
Adagrad:该优化器具有参数特定的学习率,学习率会根据参数在训练过程中更新的频率进行调整。当参数收到更多更新时,每次更新的幅度会变小。
优化器的选择会影响训练时间和模型性能。每个优化器都有超参数,比如初始学习率,这些超参数必须在训练前进行选择,而调优这些超参数也会影响训练时间和模型性能。虽然 TensorFlow 中还有其他优化器未在此明确列出(可以在此处找到:www.tensorflow.org/api_docs/python/tf/keras/optimizers
),但上述优化器在训练时间和模型性能上表现良好,是选择优化器时的一个安全的首选。TensorFlow 中可用的优化器位于tf.optimizers
模块中;例如,一个学习率为0.001
的 Adam 优化器可以如下初始化:
optimizer = tf.optimizer.adam(learning_rate=0.001)
在本节中,你已经看到了实现梯度下降的步骤,用以计算模型训练的最优参数。在梯度下降中,每一个训练样本都会用于学习这些参数。然而,在处理大规模数据集时,比如图像和音频数据,通常会采用批量训练,并在每次学习完一个批次后进行更新。当使用梯度下降进行批量数据训练时,这个算法被称为 SGD。TensorFlow 中提供了 SGD 优化器以及一系列其他高效的优化器,包括 Adam、RMSProp、Adagrad 优化器等。
在下一节中,你将探索不同的激活函数,这些函数通常应用于每一层的输出。
激活函数
激活函数是应用于 ANN 层输出的数学函数,通常用于限制或约束层的值。值可能需要被约束的原因是,如果没有激活函数,值和相应的梯度可能会爆炸或消失,从而导致结果不可用。这是因为最终值是每个后续层值的累积乘积。随着层数的增加,值和梯度爆炸到无穷大或消失到零的可能性增加。这个概念被称为梯度爆炸和消失问题。决定一个层中的节点是否应被激活是激活函数的另一个用途,这也就是它们的名字的由来。常见的激活函数及其在图 1.36中的可视化表示如下:
-
阶跃函数:如果值超过某一阈值,则值为非零,否则为零。此内容如图 1.36a所示。
-
线性函数:
,它是输入值的标量乘积。此内容如图 1.36b所示。
-
Sigmoid函数:
,类似于一个平滑的阶跃函数,具有平滑的梯度。此激活函数对于分类非常有用,因为其值被限制在 0 到 1 之间。此内容如图 1.36c所示。
-
x=0
。此内容如图 1.36d所示。 -
0
。此内容如图 1.36e所示。 -
ELU(指数线性单元)函数:
,否则
,其中
是常数。
-
SELU(缩放指数线性单元)函数:
,否则
,其中
是常数。此内容如图 1.36f所示。
-
Swish函数:
。此内容如图 1.36g所示:
图 1.36:常见激活函数的可视化表示
可以通过利用tf.keras.activations
模块中的激活函数,将激活函数应用于任何张量。例如,sigmoid 激活函数可以应用于一个张量,如下所示:
y=tf.keras.activations.sigmoid(x)
现在,让我们通过以下活动测试你到目前为止所获得的知识。
活动 1.03:应用激活函数
在此活动中,你将回顾本章中使用的许多概念,并将激活函数应用于张量。你将使用汽车销售数据示例,应用这些概念,展示各个销售人员的销售记录,并突出显示那些有净正销售的记录。
销售记录:
图 1.37:销售记录
车辆 MSRP:
图 1.38:车辆 MSRP
固定成本:
图 1.39:固定成本
执行以下步骤:
-
导入 TensorFlow 库。
-
创建一个
3x4
的张量作为输入,值为[[-0.013, 0.024, 0.06, 0.022], [0.001, -0.047, 0.039, 0.016], [0.018, 0.030, -0.021, -0.028]]
。这个张量的行表示不同销售代表的销售情况,列表示经销商提供的各种车辆,而值则表示与建议零售价(MSRP)的平均百分比差异。根据销售员是否能以高于或低于 MSRP 的价格售出车辆,数值为正或负。 -
创建一个
4x1
的权重张量,形状为4x1
,值为[[19995.95], [24995.50], [36745.50], [29995.95]]
,表示汽车的 MSRP。 -
创建一个大小为
3x1
的偏置张量,值为[[-2500.0], [-2500.0], [-2500.0]]
,表示每个销售员的固定成本。 -
对输入与权重进行矩阵乘法,以展示所有车辆的 MSRP 平均偏差,并加上偏置,减去销售员的固定成本。打印结果。
您应该得到以下结果:
图 1.40:矩阵乘法的输出
-
应用 ReLU 激活函数以突出净销售为正的销售员,并打印结果。
您应该得到以下结果:
图 1.41:应用激活函数后的输出
注意
本活动的解决方案可以通过此链接查看。
在后续的章节中,您将学习如何为您的 ANN 添加激活函数,激活函数可以插入到层与层之间,或在定义层后直接应用。您将学习如何选择最适合的激活函数,这通常是通过超参数优化技术来实现的。激活函数是超参数的一个例子,超参数是学习过程开始前设定的参数,它可以进行调整以找到模型性能的最佳值。
总结
在本章中,您已了解了 TensorFlow 库。您学习了如何在 Python 编程语言中使用它。您创建了具有不同秩和形状的 ANN 构建块(张量),并使用 TensorFlow 对张量进行线性变换,实施了张量的加法、重塑、转置和乘法——这些都是理解 ANN 基础数学原理的基础。
在下一章中,你将提升对张量的理解,并学习如何加载各种类型的数据并进行预处理,以使其适合在 TensorFlow 中训练人工神经网络(ANN)。你将处理表格数据、视觉数据和文本数据,所有这些数据必须以不同的方式进行预处理。通过处理视觉数据(即图像),你还将学习如何使用那些无法全部加载到内存中的训练数据。
第二章:2. 加载和处理数据
概述
在本章中,你将学习如何加载和处理多种数据类型,以便在 TensorFlow 中建模。你将实现将数据输入到 TensorFlow 模型中的方法,从而优化模型训练。
在本章结束时,你将学会如何输入表格数据、图像、文本和音频数据,并进行预处理,使其适合用于训练 TensorFlow 模型。
介绍
在上一章中,你学习了如何使用 TensorFlow 创建、利用和应用线性变换到张量。该章首先介绍了张量的定义以及如何使用 TensorFlow 库中的 Variable
类创建张量。然后,你创建了不同阶数的张量,并学习了如何使用该库进行张量加法、重塑、转置和乘法操作。这些都是线性变换的例子。你通过讲解优化方法和激活函数并如何在 TensorFlow 库中访问它们,结束了这一章的内容。
在 TensorFlow 中训练机器学习模型时,必须向模型提供训练数据。可用的原始数据可能有多种格式——例如,表格型 CSV 文件、图像、音频或文本文件。不同的数据源以不同的方式加载和预处理,以便为 TensorFlow 模型提供数值张量。例如,虚拟助手使用语音查询作为输入交互,然后应用机器学习模型来解码输入的语音并执行特定的输出操作。为了创建这一任务的模型,必须将语音输入的音频数据加载到内存中。还需要一个预处理步骤,将音频输入转换为文本。之后,文本将被转换为数值张量以进行模型训练。这是一个例子,展示了从非表格型、非数值型数据(如音频数据)创建模型的复杂性。
本章将探讨一些常用的数据类型,这些数据类型用于构建机器学习模型。你将以高效的方式将原始数据加载到内存中,然后执行一些预处理步骤,将原始数据转换为适合训练机器学习模型的数值张量。幸运的是,机器学习库已经取得了显著的进展,这意味着使用图像、文本和音频等数据类型训练模型对于实践者来说变得非常可行。
探索数据类型
根据数据来源的不同,原始数据可以有不同的形式。常见的数据形式包括表格数据、图像、视频、音频和文本。例如,温度记录仪(用于记录给定地点随时间变化的温度)输出的数据是表格数据。表格数据是按行和列结构化的,在温度记录仪的例子中,每一列可能代表每条记录的某个特征,比如时间、地点和温度,而每一行则代表每条记录的数值。下表展示了数值型表格数据的示例:
图 2.1:一个由数值组成的表格数据的示例,包含 10 行数据
图像数据代表另一种常见的原始数据形式,在构建机器学习模型时非常受欢迎。由于数据量巨大,这些模型也非常受欢迎。随着智能手机和监控摄像头记录下生活中的每一个瞬间,它们已经生成了大量可以用于训练模型的数据。
用于训练的图像数据的维度与表格数据不同。每张图像都有高度和宽度维度,并且颜色通道增加了第三个维度,而图像数量则增加了第四个维度。因此,图像数据模型的输入张量是四维的,而表格数据的输入张量是二维的。下图展示了从Open Images
数据集(storage.googleapis.com/openimages/web/index.html
)中提取的带标签的船只和飞机的训练示例;这些图像已经过预处理,使得它们的高度和宽度相同。这个数据可以用来训练一个二分类模型,将图像分类为船只或飞机:
图 2.2:可以用于训练机器学习模型的图像数据样本
其他类型的原始数据,如文本和音频,也可以用来构建机器学习模型。像图像一样,它们在机器学习社区中的受欢迎程度来自于大量可用的数据。音频和文本都有大小不确定的挑战。你将在本章后面探索如何克服这一挑战。下图展示了一个采样率为 44.1 kHz 的音频样本,这意味着音频数据每秒被采样 44,100 次。这是输入虚拟助手的原始数据类型的一个示例,虚拟助手通过这些数据解析请求并做出相应的动作:
图 2.3:音频数据的可视化表示
现在你已经了解了在构建机器学习模型时可能遇到的一些数据类型,接下来的部分将揭示如何预处理不同类型的数据。
数据预处理
数据预处理是指将原始数据转换为适合机器学习模型使用的形式的过程。不同的数据类型将需要不同的预处理步骤,最基本的要求是,结果张量仅包含数值元素,如整数或小数。需要数值张量,因为模型依赖于线性变换,如加法和乘法,而这些变换只能在数值张量上执行。
虽然许多数据集仅包含数值字段,但也有很多数据集不止如此。它们可能包含字符串、布尔值、类别型或日期类型的字段,这些字段都必须转换为数值字段。有些转换可能很简单;例如,布尔值字段可以映射为 true
对应 1
,false
对应 0
。因此,将布尔字段映射为数值字段是简单的,并且所有必要的信息都得以保留。然而,当转换其他数据类型(如日期字段)时,除非另有明确说明,否则转换为数值字段时可能会丢失信息。
一个可能的信息丢失示例是将日期字段通过 Unix 时间转换为数值字段。Unix 时间表示自 Unix 纪元以来经过的秒数;即 1970 年 1 月 1 日 00:00:00 UTC,忽略闰秒。使用 Unix 时间移除了月份、星期几、小时等的显式指示,而这些信息在训练模型时可能作为重要特征。
在将字段转换为数值数据类型时,重要的是尽可能保留信息上下文,因为这将有助于训练模型理解特征与目标之间的关系。以下图示展示了如何将日期字段转换为一系列数值字段:
图 2.4:日期列的数值编码
如前图所示,左侧是日期字段,表示一个特定日期,而右侧则提供了一种方法,将其转化为数值信息:
-
从日期中提取年份,作为整数。
-
月份使用独热编码(one-hot encoding)。每个月份都有一个列,并且月份被二进制编码,如果日期的月份与该列的名称相符。
-
创建了一个列,指示日期是否为周末。
这里仅是对date
列进行编码的方法;并不是所有前述的方法都是必须的,且还有许多其他方法可以使用。将所有字段适当地编码为数值字段对于创建高效的机器学习模型至关重要,这样模型才能学习特征与目标之间的关系。
数据归一化是另一种预处理技术,用于加速训练过程。归一化过程会重新缩放字段,使它们都具有相同的尺度。这也有助于确保模型的权重具有相同的尺度。
在前面的图示中,year
列的数量级为10³
,而其他列的数量级为10⁰
。这意味着列之间存在三个数量级的差异。字段的数值范围差异很大的话,可能会导致模型不够精确,因为在最小化误差函数时,可能无法找到最优的权重。这可能是由于在训练前定义的容忍限度或学习率,并不适用于这两个数量级的权重更新。在前面的例子中,重新缩放year
列,使其与其他列具有相同的数量级,可能是有益的。
在本章中,你将探索多种方法来预处理表格数据、图像数据、文本数据和音频数据,以便将其用于训练机器学习模型。
处理表格数据
在本节中,你将学习如何将表格数据加载到 Python 开发环境中,以便可以用于 TensorFlow 建模。你将使用 pandas 和 scikit-learn 来利用有助于处理数据的类和函数,并探索可用于预处理数据的方法。
可以通过使用 pandas 的read_csv
函数并传入数据集路径,将表格数据加载到内存中。这个函数非常适合且易于使用来加载表格数据,使用方法如下:
df = pd.read_csv('path/to/dataset')
为了对数据进行归一化,你可以使用在 scikit-learn 中可用的缩放器。可以应用多种缩放器;StandardScaler
会对数据进行归一化,使数据集的字段均值为0
,标准差为1
。另一个常用的缩放器是MinMaxScaler
,它会重新缩放数据集,使字段的最小值为0
,最大值为1
。
要使用缩放器,必须先初始化并将其拟合到数据集。通过这样做,数据集就可以通过缩放器进行转换。实际上,拟合和转换过程可以通过使用fit_transform
方法一步完成,如下所示:
scaler = StandardScaler()
transformed_df = scaler.fit_transform(df)
在第一个练习中,你将学习如何使用 pandas 和 scikit-learn 加载数据集并对其进行预处理,使其适合建模。
练习 2.01:加载表格数据并重新缩放数值字段
数据集 Bias_correction_ucl.csv
包含了针对韩国首尔的次日最高和最低气温预报的偏差修正信息。各字段代表给定日期的温度测量值、测量数据的天气站、与天气相关的模型预报指标(如湿度),以及次日的温度预报。你需要对数据进行预处理,使所有列符合正态分布,均值为 0
,标准差为 1
。你将以 Present_Tmax
列为例,展示其效果,该列表示给定日期和天气站的最高温度。
注意
数据集可以在这里找到:packt.link/l83pR
。
执行以下步骤以完成此练习:
-
打开一个新的 Jupyter notebook 来实现这个练习。将文件保存为
Exercise2-01.ipnyb
。 -
在新的 Jupyter Notebook 单元格中,导入 pandas 库,如下所示:
import pandas as pd
注意
你可以在以下链接找到 pandas 的文档:
pandas.pydata.org/docs/
。 -
创建一个新的 pandas DataFrame,命名为
df
,并将Bias_correction_ucl.csv
文件读取到其中。通过打印结果 DataFrame 来检查数据是否正确加载:df = pd.read_csv('Bias_correction_ucl.csv') df
注意
确保根据 CSV 文件在系统中的位置更改路径(高亮部分)。如果你从存储 CSV 文件的同一目录运行 Jupyter notebook,则可以直接运行上述代码而无需修改。
输出结果如下:
](https://github.com/OpenDocCN/freelearn-dl-pt3-zh/raw/master/docs/tf-ws/img/B16341_02_06.jpg)
图 2.5:打印 DataFrame 后的输出结果
-
使用 DataFrame 的
drop
方法删除date
列,并传入列名。删除date
列是因为它是非数值型字段,存在非数值型字段时无法进行缩放。由于要删除列,因此需要同时传入axis=1
和inplace=True
参数:df.drop('Date', inplace=True, axis=1)
-
绘制表示数据集中各日期和天气站的最大温度的
Present_Tmax
列的直方图:ax = df['Present_Tmax'].hist(color='gray') ax.set_xlabel("Temperature") ax.set_ylabel("Frequency")
输出结果如下:
](https://github.com/OpenDocCN/freelearn-dl-pt3-zh/raw/master/docs/tf-ws/img/B16341_02_05.jpg)
图 2.6:表示
Present_Tmax
列的温度与频率的直方图结果直方图显示了
Present_Tmax
列的值的分布。可以看到,温度值的范围从 20 到 38 摄氏度。绘制特征值的直方图是查看值的分布并判断是否需要缩放的好方法。 -
从 scikit-learn 的预处理包中导入
StandardScaler
类。初始化缩放器,拟合缩放器并使用缩放器的fit_transform
方法对 DataFrame 进行变换。由于fit_transform
方法的结果是一个 NumPy 数组,因此使用变换后的 DataFrame 创建一个新的 DataFramedf2
。标准缩放器将对数值字段进行变换,使字段的均值为0
,标准差为1
:from sklearn.preprocessing import StandardScaler scaler = StandardScaler() df2 = scaler.fit_transform(df) df2 = pd.DataFrame(df2, columns=df.columns)
注意
结果数据的均值和标准差可以输入到缩放器中。
-
绘制变换后的
Present_Tmax
列的直方图:ax = df2['Present_Tmax'].hist(color='gray') ax.set_xlabel("Normalized Temperature") ax.set_ylabel("Frequency")
输出将如下所示:
图 2.7:重新缩放后的 Present_Tmax 列的直方图
结果的直方图显示,温度值的范围大约从 -3
到 3
摄氏度,可以从直方图的 x 轴范围中看出。使用标准缩放器后,数值将始终具有 0
的均值和 1
的标准差。规范化特征可以加速模型训练过程。
在这个练习中,你成功地使用 pandas 库导入了表格数据,并使用 scikit-learn 库进行了一些预处理。数据的预处理包括删除 date
列和对数值字段进行缩放,使它们的均值为 0
,标准差为 1
。
在以下活动中,你将使用 pandas 库加载表格数据,并使用 scikit-learn 中的 MinMax
缩放器对数据进行缩放。你将使用与之前练习中相同的数据集,该数据集描述了韩国首尔空气温度预报的偏差修正。
活动 2.01:加载表格数据并使用 MinMax 缩放器重新缩放数值字段
在此活动中,你需要加载表格数据并使用 MinMax
缩放器对数据进行重新缩放。数据集 Bias_correction_ucl.csv
包含了韩国首尔次日最大和最小空气温度预报的偏差修正信息。字段表示给定日期的温度测量值、测量指标的气象站、与天气相关的指标(如湿度)模型预测值以及次日的温度预测值。你需要对列进行缩放,使得每列的最小值为 0
,最大值为 1
。
完成此活动的步骤如下:
-
打开一个新的 Jupyter notebook 来实现这个活动。
-
导入 pandas 和
Bias_correction_ucl.csv
数据集。 -
使用 pandas 的
read_csv
函数读取数据集。 -
删除 DataFrame 的
date
列。 -
绘制
Present_Tmax
列的直方图。 -
导入
MinMaxScaler
并将其拟合到并变换特征 DataFrame。 -
绘制变换后的
Present_Tmax
列的直方图。你应该得到类似以下的输出:
图 2.8:活动 2.01 的预期输出
注意
这个活动的解决方案可以通过这个链接找到。
转换非数值字段(如分类字段或日期字段)的一种方法是进行独热编码。0
表示除了与正确列对应的那一列外的所有列。新创建的虚拟列的列头对应于唯一值。独热编码可以通过使用 pandas 库的 get_dummies
函数来实现,并传入需要编码的列。一个可选的参数是提供前缀功能,为列头添加前缀,这对于引用列很有用:
dummies = pd.get_dummies(df['feature1'], prefix='feature1')
注意
使用 get_dummies
函数时,NaN
值会被转换为全 0。
在接下来的练习中,你将学习如何预处理非数值字段。你将使用与之前的练习和活动相同的数据集,该数据集描述了韩国首尔的气温预报偏差修正。
练习 2.02:预处理非数值数据
在这个练习中,你将通过使用 get_dummies
函数对 date
列的年份和月份进行独热编码来预处理 date
列。你将把独热编码后的列与原始 DataFrame 合并,确保结果 DataFrame 中的所有字段都是数值类型。
完成这个练习的步骤如下:
-
打开一个新的 Jupyter notebook 来实现这个练习。将文件保存为
Exercise2-02.ipnyb
。 -
在一个新的 Jupyter Notebook 单元格中,导入 pandas 库,如下所示:
import pandas as pd
-
创建一个新的 pandas DataFrame,命名为
df
,并将Bias_correction_ucl.csv
文件读取到其中。通过打印结果 DataFrame 检查数据是否正确加载:df = pd.read_csv('Bias_correction_ucl.csv')
注意
确保根据文件在你系统上的位置更改路径(高亮部分)。如果你从存储 CSV 文件的目录运行 Jupyter notebook,你可以直接运行上述代码,而无需修改。
-
使用 pandas 的
to_datetime
函数将date
列的数据类型更改为Date
:df['Date'] = pd.to_datetime(df['Date'])
-
使用 pandas 的
get_dummies
函数为year
创建虚拟列。将date
列的年份作为第一个参数传入,并为结果 DataFrame 的列添加前缀。打印出结果 DataFrame:year_dummies = pd.get_dummies(df['Date'].dt.year, \ prefix='year') year_dummies
输出将如下所示:
图 2.9:应用
get_dummies
函数处理日期列的年份后的输出结果 DataFrame 只包含 0 和 1。
1
对应于原始date
列中存在的值。空值将在新创建的 DataFrame 中对所有列显示为 0。 -
通过从
date
列的月份创建虚拟列来重复此操作。打印出生成的 DataFrame:month_dummies = pd.get_dummies(df['Date'].dt.month, \ prefix='month') month_dummies
输出将如下所示:
图 2.10:应用于日期列月份的 get_dummies 函数的输出
结果 DataFrame 现在仅包含
date
列中月份的 0 和 1。 -
将原始 DataFrame 与您在步骤 5和6中创建的虚拟 DataFrame 连接起来:
df = pd.concat([df, month_dummies, year_dummies], \ axis=1)
-
删除原始
date
列,因为它现在是多余的:df.drop('Date', axis=1, inplace=True)
-
验证所有列现在都是数值数据类型:
df.dtypes
输出如下所示:
图 2.11:结果 DataFrame 的 dtypes 属性的输出
在这里,您可以看到结果 DataFrame 的所有数据类型都是数值的。这意味着它们现在可以传递到 ANN 中进行建模。
在此练习中,您成功导入了表格数据,并使用 pandas 和 scikit-learn 库预处理了date
列。您使用了get_dummies
函数将分类数据转换为数值数据类型。
注意
另一种从日期数据类型获取数值数据类型的方法是使用pandas.Series.dt
访问器对象。有关可用选项的更多信息,请参阅:pandas.pydata.org/docs/reference/api/pandas.Series.dt.html
。
处理非数值数据是创建高性能模型的重要步骤。如果可能,应将任何领域知识传授给训练数据特征。例如,使用日期预测温度,就像在本章前面的练习和活动中使用的数据集一样,对月份进行编码将是有帮助的,因为温度很可能与年份的月份高度相关。然而,编码星期几可能没有用,因为星期几与温度可能没有相关性。使用这些领域知识可以帮助模型学习特征与目标之间的潜在关系。
在下一节中,您将学习如何处理图像数据,以便将其输入到机器学习模型中。
处理图像数据
各种组织每天生成大量图像,这些图像可用于创建预测模型,例如对象检测、图像分类和对象分割。在处理图像数据和其他一些原始数据类型时,通常需要预处理数据。使用 ANN 进行建模的最大优势之一是从原始数据创建模型时的最小预处理步骤。特征工程通常涉及使用领域知识从原始数据中创建特征,这是耗时的,并不能保证模型性能的提高。利用无需特征工程的 ANN 简化了训练过程,并且不需要领域知识。
例如,在医学图像中定位肿瘤需要经过多年训练的专家知识,但对于人工神经网络(ANNs)而言,只需要足够的标注数据进行训练。通常需要对这些图像进行少量的预处理步骤。这些步骤是可选的,但对于标准化训练过程和创建高性能模型是有帮助的。
一项预处理步骤是重新缩放。由于图像的颜色值是整数,范围从0
到255
,它们被缩放为介于0
和1
之间的值,类似于活动 2.01,加载表格数据并使用 MinMax Scaler 重新缩放数值字段。另一种常见的预处理步骤是图像增强,稍后在本节中你将进一步探讨,它本质上是通过增强图像来添加更多的训练样本,从而构建一个更强大的模型。
本节还介绍了批处理。批处理一次加载一批训练数据。这可能导致比一次性加载数据更慢的训练时间;然而,这允许你在非常大体量的数据集上训练模型。图像或音频训练是需要大量数据来获得良好性能的典型例子。
例如,一个典型的图像可能是 100 KB 大小。对于 100 万张图像的训练数据集,你将需要 100 GB 的内存,这对大多数人来说可能是不可实现的。如果模型以 32 张图像为一批进行训练,那么内存需求将小很多。批量训练使你能够增强训练数据,稍后在本节中你将进一步了解这一点。
图像可以通过一个名为ImageDataGenerator
的类加载到内存中,该类可以从 Keras 的预处理包中导入。这个类最初来自 Keras,现在也可以在 TensorFlow 中使用。在加载图像时,你可以对它们进行重新缩放。通常的做法是将图像按 1/255 像素的值进行重新缩放。这意味着原本在 0 到 255 范围内的图像值现在将位于 0 到 1 之间。
ImageDataGenerator
可以通过以下方式初始化并进行重新缩放:
datagenerator = ImageDataGenerator(rescale = 1./255)
一旦初始化了 ImageDataGenerator
类,你可以使用 flow_from_directory
方法并传入图像所在的目录。该目录应包括按类标签标记的子目录,并且这些子目录应包含相应类别的图像。另一个需要传入的参数是所需的图像大小、批量大小以及类模式。类模式决定了生成的标签数组的类型。以下是使用 flow_from_directory
方法进行二分类的示例,批量大小为 25,图像大小为 64x64:
dataset = datagenerator.flow_from_directory\
('path/to/data',\
target_size = (64, 64),\
batch_size = 25,\
class_mode = 'binary')
在接下来的练习中,你将利用 ImageDataGenerator
类将图像加载到内存中。
注意
提供的图像数据来自 Open Image 数据集,完整描述可以在这里找到:storage.googleapis.com/openimages/web/index.html
。
可以使用 Matplotlib 绘制图像来查看图像。这是验证图像是否与其相应标签匹配的一个有用练习。
练习 2.03:加载用于批量处理的图像数据
在本练习中,你将学习如何加载用于批量处理的图像数据。image_data
文件夹包含一组船只和飞机的图像。你将加载船只和飞机的图像进行批量处理,并对它们进行重新缩放,使得图像值在0
和1
之间。然后,你的任务是打印出来自数据生成器的批次图像及其标签。
注意
你可以在此找到image_data
:packt.link/jZ2oc
。
执行以下步骤来完成此练习:
-
打开一个新的 Jupyter Notebook 来实现本练习。将文件保存为
Exercise2-03.ipnyb
。 -
在新的 Jupyter Notebook 单元格中,从
tensorflow.keras.preprocessing.image
导入ImageDataGenerator
类:from tensorflow.keras.preprocessing.image \ import ImageDataGenerator
-
实例化
ImageDataGenerator
类,并传递rescale
参数,值为1./255
,以将图像值转换为介于0
和1
之间:train_datagen = ImageDataGenerator(rescale = 1./255)
-
使用数据生成器的
flow_from_directory
方法,指示数据生成器加载图像数据。传入目标尺寸、批次大小和类别模式的参数:training_set = train_datagen.flow_from_directory\ ('image_data',\ target_size = (64, 64),\ batch_size = 25,\ class_mode = 'binary')
-
创建一个函数来显示批量图像。该函数将以 5x5 数组的形式绘制前 25 张图像及其对应的标签:
import matplotlib.pyplot as plt def show_batch(image_batch, label_batch):\ lookup = {v: k for k, v in \ training_set.class_indices.items()} label_batch = [lookup[label] for label in \ label_batch] plt.figure(figsize=(10,10)) for n in range(25): ax = plt.subplot(5,5,n+1) plt.imshow(image_batch[n]) plt.title(label_batch[n].title()) plt.axis('off')
-
从数据生成器中获取一个批次并将其传递给函数以显示图像及其标签:
image_batch, label_batch = next(training_set) show_batch(image_batch, label_batch)
输出将如下所示:
图 2.12:来自一批的图像
在这里,你可以看到一批船只和飞机的图像输出,这些图像可以输入到模型中。请注意,所有图像的大小相同,这是通过修改图像的长宽比实现的。这样可以确保图像在传入人工神经网络(ANN)时的一致性。
在本练习中,你将学习如何以批量的方式导入图像,以便它们可以用于训练人工神经网络(ANN)。图像是一次加载一个批次的,通过限制每个批次的训练图像数量,你可以确保不会超出计算机的 RAM。
在接下来的部分中,你将看到如何在图像加载时进行增强。
图像增强
图像增强是通过修改图像来增加可用训练样本数量的过程。该过程可以包括放大图像、旋转图像或水平/垂直翻转图像。如果增强过程不会改变图像的上下文,则可以进行增强。例如,当香蕉图像水平翻转时,它仍然可以被识别为香蕉,并且新生成的香蕉图像很可能是任意方向的。在这种情况下,训练过程中提供模型的两种方向将有助于构建一个更强大的模型。
然而,如果您有一张船只的图像,垂直翻转可能不合适,因为这并不代表船只通常在图像中的存在方式——船只不会倒立。图像增强的最终目标是增加训练图像的数量,这些图像应与物体在日常生活中的出现方式相似,并保留其上下文。这有助于训练出的模型在面对新的、未见过的图像时表现良好。图像增强的一个例子可以在下图中看到,图中的香蕉图像经过了三次增强;左侧图像为原始图像,右侧图像为增强后的图像。
右上角的图像是原始图像水平翻转后的结果,右中间的图像是原始图像放大了 15%,右下角的图像是原始图像旋转了 10 度。经过这个增强过程后,您会得到四张香蕉图像,每张图像的香蕉处于不同的位置和方向:
图 2.13:图像增强示例
图像增强可以通过 TensorFlow 的 ImageDataGenerator
类在每批图像加载时进行。与图像重缩放类似,可以应用多种图像增强过程。常见增强过程的参数包括:
-
horizontal_flip
:水平翻转图像。 -
vertical_flip
:垂直翻转图像。 -
rotation_range
:旋转图像至指定的度数。 -
width_shift_range
:沿图像的宽度轴移动图像,最多移动指定的比例或像素量。 -
height_shift_range
:沿图像的高度轴移动图像,最多移动指定的比例或像素量。 -
brightness_range
:修改图像的亮度,最大可调整到指定的值。 -
shear_range
:按指定量剪切图像。 -
zoom_range
:按指定的比例放大图像。
图像增强可以在实例化 ImageDataGenerator
类时应用,示例如下:
datagenerator = ImageDataGenerator(rescale = 1./255,\
shear_range = 0.2,\
rotation_range= 180,\
zoom_range = 0.2,\
horizontal_flip = True)
在以下活动中,您将使用 TensorFlow 的 ImageDataGenerator
类进行图像增强。这个过程非常简单,只需要传入参数即可。您将使用与练习 2.03、批处理图像数据加载中相同的数据集,该数据集包含了船只和飞机的图像。
活动 2.02:批处理图像数据加载
在这个活动中,你将加载图像数据进行批处理,并在此过程中增强图像。image_data
文件夹包含一组船只和飞机的图像。你需要加载图像数据进行批处理,并通过随机扰动(如旋转、水平翻转图像和为图像添加剪切)调整输入数据。这将从现有的图像数据中创建额外的训练数据,并通过增加不同的训练样本数来提高机器学习模型的准确性和鲁棒性,即使只有少量样本可用。接着,你的任务是打印出从数据生成器中提取的一批带标签的图像。
这个活动的步骤如下:
-
打开一个新的 Jupyter notebook 来实现这个活动。
-
从
tensorflow.keras.preprocessing.image
导入ImageDataGenerator
类。 -
实例化
ImageDataGenerator
并设置rescale=1./255
、shear_range=0.2
、rotation_range=180
、zoom_range=0.2
和horizontal_flip=True
参数。 -
使用
flow_from_directory
方法,将数据生成器指向图像,同时传入目标尺寸64x64
、批量大小25
,并将类别模式设置为binary
。 -
创建一个函数,将前 25 个图像按 5x5 的数组显示,并附上相关标签。
-
从数据生成器中取一个批次,并将其传递给函数以显示图像及其标签。
注意
这个活动的解决方案可以通过这个链接找到。
在这个活动中,你批量增强了图像,使其可以用于训练人工神经网络(ANNs)。你已经看到,当图像作为输入时,它们可以被增强以生成更多有效的训练样本。
你学会了如何批量加载图像,这使得你可以在巨大的数据量上进行训练,而这些数据可能无法一次性全部加载到机器的内存中。你还学会了如何使用ImageDataGenerator
类增强图像,它本质上是从训练集中的图像生成新的训练样本。
在下一节中,你将学习如何加载和预处理文本数据。
文本处理
文本数据代表了一大类易于获取的原始数据。例如,文本数据可以来自网页,如维基百科、转录的语音或社交媒体对话——这些数据量在大规模增加,且在用于训练机器学习模型之前必须进行处理。
处理文本数据可能会面临多种挑战,包括以下几种:
-
存在成千上万种不同的单词。
-
不同的语言带来了不同的挑战。
-
文本数据通常在大小上有所不同。
有许多方法可以将文本数据转换为数值表示。一种方法是对单词进行独热编码,这就像你在练习 2.02中处理日期字段时做的那样,预处理非数值数据。然而,这在训练模型时会带来问题,因为如果数据集包含大量独特的单词,就会导致数据稀疏,从而可能导致训练速度缓慢并且模型可能不准确。此外,如果遇到一个新单词,而这个单词不在训练数据中,模型就无法使用该单词。
一种常用的方法是将整个文本转换为嵌入向量。已有预训练模型将原始文本转换为向量。这些模型通常在大量文本数据上训练。使用预训练模型的单词嵌入向量具有一些明显的优势:
-
结果向量的大小是固定的。
-
向量保持上下文信息,因此它们能受益于迁移学习。
-
不需要对数据进行进一步的预处理,嵌入的结果可以直接输入到人工神经网络(ANN)中。
虽然 TensorFlow Hub 将在下一章中更深入地讲解,但下面是如何将预训练模型用作预处理步骤的示例。要加载预训练模型,您需要导入tensorflow_hub
库。通过这样做,可以加载模型的 URL。然后,可以通过调用KerasLayer
类将模型加载到环境中,KerasLayer
类会将模型封装,使其可以像其他 TensorFlow 模型一样使用。可以如下创建:
import tensorflow_hub as hub
model_url = "url_of_model"
hub_layer = hub.KerasLayer(model_url, \
input_shape=[], dtype=tf.string, \
trainable=True)
输入数据的数据类型,由dtype
参数指示,应作为输入传递给KerasLayer
类,同时还需要一个布尔参数,指示权重是否可训练。一旦使用tensorflow_hub
库加载了模型,就可以在文本数据上调用它,如下所示:
hub_layer(data)
这将通过预训练模型运行数据。输出将基于预训练模型的架构和权重。
在下面的练习中,您将学习如何加载包含文本字段的数据,批量处理数据集,并将预训练模型应用于文本字段,将该字段转换为嵌入向量。
注意
预训练模型可以在这里找到:tfhub.dev/google/tf2-preview/gnews-swivel-20dim/1
。
数据集可以在这里找到:archive.ics.uci.edu/ml/datasets/Drug+Review+Dataset+%28Drugs.com%29
。
练习 2.04:为 TensorFlow 模型加载文本数据
数据集drugsComTrain_raw.tsv
包含与患者对特定药物的评价相关的信息,包括患者对药物的满意度评分以及与之相关的病情。在本练习中,您将加载文本数据进行批处理。您将应用一个来自 TensorFlow Hub 的预训练模型,对患者评论进行词嵌入。您需要只处理review
字段,因为该字段包含文本数据。
执行以下步骤:
-
打开一个新的 Jupyter notebook 来实现这个练习。将文件保存为
Exercise2-04.ipnyb
。 -
在新的 Jupyter Notebook 单元格中,导入 TensorFlow 库:
import tensorflow as tf
-
使用库的
make_csv_dataset
函数创建一个 TensorFlow 数据集对象。将batch_size
参数设置为1
,并将field_delim
参数设置为\t
,因为数据集是制表符分隔的:df = tf.data.experimental.make_csv_dataset\ ('../Datasets/drugsComTest_raw.tsv', \ batch_size=1, field_delim='\t')
-
创建一个函数,将数据集对象作为输入,并对数据集进行洗牌、重复和批处理:
def prep_ds(ds, shuffle_buffer_size=1024, \ batch_size=32): # Shuffle the dataset ds = ds.shuffle(buffer_size=shuffle_buffer_size) # Repeat the dataset ds = ds.repeat() # Batch the dataset ds = ds.batch(batch_size) return ds
-
对您在步骤 3中创建的数据集对象应用该函数,设置
batch_size
等于5
:ds = prep_ds(df, batch_size=5)
-
获取第一批数据并打印出来:
for x in ds.take(1):\ print(x)
您应该会看到类似以下的输出:
图 2.14:来自数据集对象的一批数据
输出表示输入数据的张量格式。
-
从 TensorFlow Hub 导入预训练的词嵌入模型,并创建一个 Keras 层:
import tensorflow_hub as hub embedding = "https://tfhub.dev/google/tf2-preview"\ "/gnews-swivel-20dim/1" hub_layer = hub.KerasLayer(embedding, input_shape=[], \ dtype=tf.string, \ trainable=True)
-
从数据集中取出一批数据,展平对应
review
字段的张量,应用预训练层并打印输出:for x in ds.take(1):\ print(hub_layer(tf.reshape(x['review'],[-1])))
这将显示以下输出:
图 2.15:应用预训练模型后,评论列的一批数据
上述输出表示了第一批药物评论的嵌入向量。具体的值乍一看可能没有太大意义,但嵌入中编码了基于数据集的信息,这些数据集是嵌入模型训练的基础。批处理大小为5
,嵌入向量的大小为20
,这意味着在应用预训练层后,得到的大小为5x20
。
在本练习中,您学习了如何导入可能包含各种数据类型的表格数据。您处理了review
字段,并应用了预训练的词嵌入模型,将文本转换为数值张量。最终,您对文本数据进行了预处理和批处理,使其适合大规模训练。这是一种表示文本的方式,使其能够输入到 TensorFlow 的机器学习模型中。实际上,还可以使用其他预训练的词嵌入模型,这些模型可以在 TensorFlow Hub 上找到。在下一章中,您将进一步学习如何使用 TensorFlow Hub。
在本节中,你了解了如何为机器学习模型预处理文本数据的一种方法。实际上,你可以使用许多不同的方法来生成数值张量,例如,可以对单词进行独热编码、去除停用词、进行词干提取和词形还原,或者甚至做一些简单的操作,如统计每个评论中的单词数量。本节展示的方法具有优势,因为它简单易实施。此外,词嵌入方法能够结合文本中的上下文信息,而这些信息在其他方法中(如独热编码)难以编码。
最终,是否将任何领域知识应用于预处理步骤以保留尽可能多的上下文信息,取决于实践者自身。这将使任何后续模型能够学习特征和目标变量之间的潜在函数。
在接下来的章节中,你将学习如何加载和处理音频数据,以便这些数据可以用于 TensorFlow 模型。
音频处理
本节将演示如何批量加载音频数据,以及如何处理它以便用于训练机器学习模型。音频文件的预处理涉及一些高级信号处理步骤。这些步骤有些是可选的,但为了提供全面的音频数据处理方法,我们将呈现这些步骤。由于每个音频文件可能有数百 KB,因此你将利用批处理,就像处理图像数据时一样。批处理可以通过创建数据集对象来实现。创建数据集对象的一种通用方法是使用 TensorFlow 的 from_tensor_slice
函数。此函数通过沿张量的第一维切片来生成数据集对象,使用方式如下:
dataset = tf.data.Dataset\
.from_tensor_slices([1, 2, 3, 4, 5])
可以通过 TensorFlow 将音频数据加载到 Python 环境中,方法是使用 read_file
函数将文件读取到内存中,然后使用 decode_wav
函数解码文件。在使用 decode_wav
函数时,必须传入样本率(即表示 1 秒钟数据点的数量)以及所需的通道。例如,如果传入 -1
作为所需通道的值,则将解码所有音频通道。导入音频文件的方式如下:
sample_rate = 44100
audio_data = tf.io.read_file('path/to/file')
audio, sample_rate = tf.audio.decode_wav\
(audio_data,\
desired_channels=-1,\
desired_samples=sample_rate)
与文本数据一样,你必须对数据进行预处理,以便生成的数值张量与数据的大小相匹配。这是通过在将数据转换到频域后对音频文件进行采样来实现的。音频采样可以看作是将音频文件切割成始终相同大小的片段。例如,一个 30 秒的音频文件可以切分成 30 个 1 秒钟的不重叠音频样本,同样,一个 15 秒钟的音频文件可以切分成 15 个 1 秒钟的不重叠样本。这样,最终你会得到 45 个大小相同的音频样本。
另一个常见的音频数据预处理步骤是将音频样本从时域转换到频域。在时域解释数据对于理解音频的强度或音量很有用,而频域则能帮助你发现哪些频率是存在的。这对于声音分类非常有用,因为不同的物体具有不同的特征声音,这些特征声音会出现在频域中。可以使用 stft
函数将音频数据从时域转换到频域。
该函数对输入数据进行短时傅里叶变换。函数的参数包括帧长度,这是一个整数值,表示窗口的长度(单位为样本);帧步长,这是一个整数值,描述了每次步进的样本数;以及 快速傅里叶变换 (FFT) 长度,这是一个整数值,表示要应用的 FFT 长度。频谱图是短时傅里叶变换的绝对值,因为它对视觉解释非常有用。可以按以下方式创建短时傅里叶变换和频谱图:
stfts = tf.signal.stft(audio, frame_length=1024,\
frame_step=256,\
fft_length=1024)
spectrograms = tf.abs(stfts)
另一个可选的预处理步骤是生成 梅尔频率倒谱系数 (MFCCs)。顾名思义,MFCC 是梅尔频率倒谱的系数。倒谱是音频信号短时功率谱的表示。MFCC 常用于语音识别和音乐信息检索等应用。因此,理解每一步 MFCC 的生成过程可能并不重要,但理解它们可以作为预处理步骤来增加音频数据管道的信息密度是非常有益的。
MFCC 是通过创建一个矩阵,将线性尺度映射到梅尔尺度来生成的。这个矩阵可以通过使用 linear_to_mel_weight_matrix
来创建,并传入生成的梅尔频谱中的带数、源频谱图中的频带数、采样率,以及要包含在梅尔频谱中的最低和最高频率。一旦线性到梅尔的权重矩阵创建完成,就可以使用 tensordot
函数在第一个轴上对频谱图进行张量收缩。
接下来,应用对值的对数转换以生成对数梅尔频谱图。最后,可以应用 mfccs_from_log_mel_spectrograms
函数,传入对数梅尔频谱图来生成 MFCC。以下是这些步骤的应用方法:
lower_edge_hertz, upper_edge_hertz, num_mel_bins \
= 80.0, 7600.0, 80
linear_to_mel_weight_matrix \
= tf.signal.linear_to_mel_weight_matrix\
(num_mel_bins, num_spectrogram_bins, sample_rate, \
lower_edge_hertz, upper_edge_hertz)
mel_spectrograms = tf.tensordot\
(spectrograms, \
linear_to_mel_weight_matrix, 1)
mel_spectrograms.set_shape\
(spectrograms.shape[:-1].concatenate\
(linear_to_mel_weight_matrix.shape[-1:]))
log_mel_spectrograms = tf.math.log(mel_spectrograms + 1e-6)
mfccs = tf.signal.mfccs_from_log_mel_spectrograms\
(log_mel_spectrograms)[..., :num_mfccs]
在接下来的练习中,你将了解如何处理音频数据。与之前的练习 2.03,加载图像数据以进行批量处理,以及练习 2.04,为 TensorFlow 模型加载文本数据类似,你将批量加载数据以提高训练效率和可扩展性。你将使用 TensorFlow 的通用 read_file
函数加载音频文件,然后使用 TensorFlow 的 decode_wav
函数解码音频数据。接着,你将创建一个函数,从每个音频样本中生成 MFCC(梅尔频率倒谱系数)。最后,将生成一个数据集对象,传递给 TensorFlow 模型进行训练。你将使用的数据集是 Google 的语音命令数据集,包含时长为 1 秒的单词发音。
注意
数据集可以在这里找到:packt.link/Byurf
。
练习 2.05:为 TensorFlow 模型加载音频数据
在本练习中,你将学习如何为批量处理加载音频数据。数据集 data_speech_commands_v0.02
包含人们发音单词 zero
的语音样本,时长恰好为 1 秒,采样率为 44.1 kHz,意味着每秒有 44,100 个数据点。你将应用一些常见的音频预处理技术,包括将数据转换到傅里叶域、对数据进行采样以确保数据与模型大小一致,并为每个音频样本生成 MFCC。这样将生成一个预处理的数据集对象,可以输入到 TensorFlow 模型进行训练。
执行以下步骤:
-
打开一个新的 Jupyter Notebook 来实现这个练习。将文件保存为
Exercise2-05.ipnyb
。 -
在一个新的 Jupyter Notebook 单元中,导入
tensorflow
和os
库:import tensorflow as tf import os
-
创建一个函数,分别使用 TensorFlow 的
read_file
和decode_wav
函数加载音频文件,并返回结果张量的转置:def load_audio(file_path, sample_rate=44100): # Load audio at 44.1kHz sample-rate audio = tf.io.read_file(file_path) audio, sample_rate = tf.audio.decode_wav\ (audio,\ desired_channels=-1,\ desired_samples=sample_rate) return tf.transpose(audio)
-
使用
os.list_dir
将音频数据的路径作为列表加载:prefix = " ../Datasets/data_speech_commands_v0.02"\ "/zero/" paths = [os.path.join(prefix, path) for path in \ os.listdir(prefix)]
-
测试函数,通过加载列表中的第一个音频文件并绘制它来验证:
import matplotlib.pyplot as plt audio = load_audio(paths[0]) plt.plot(audio.numpy().T) plt.xlabel('Sample') plt.ylabel('Value')
输出将如下所示:
图 2.16:音频文件的可视化表示
图示显示了语音样本的波形。在某一时刻的振幅对应声音的音量;高振幅表示高音量。
-
创建一个函数,从音频数据中生成 MFCC。首先,应用短时傅里叶变换,将音频信号作为第一个参数,帧长设置为
1024
作为第二个参数,帧步长设置为256
作为第三个参数,FFT 长度作为第四个参数。然后,取结果的绝对值来计算频谱图。频谱图的 bin 数由短时傅里叶变换的最后一个维度长度给出。接下来,定义 mel 权重矩阵的上下限分别为80
和7600
,mel 的 bin 数为80
。然后,使用 TensorFlow 的信号包中的linear_to_mel_weight_matrix
计算 mel 权重矩阵。接着,通过张量收缩使用 TensorFlow 的tensordot
函数,沿频谱图的第 1 轴与 mel 权重矩阵进行计算,得到 mel 频谱图。然后,对 mel 频谱图取对数,最后使用 TensorFlow 的mfccs_from_log_mel_spectrograms
函数计算 MFCC。最后,从函数中返回 MFCC。def apply_mfccs(audio, sample_rate=44100, num_mfccs=13): stfts = tf.signal.stft(audio, frame_length=1024, \ frame_step=256, \ fft_length=1024) spectrograms = tf.abs(stfts) num_spectrogram_bins = stfts.shape[-1]#.value lower_edge_hertz, upper_edge_hertz, \ num_mel_bins = 80.0, 7600.0, 80 linear_to_mel_weight_matrix = \ tf.signal.linear_to_mel_weight_matrix\ (num_mel_bins, num_spectrogram_bins, \ sample_rate, lower_edge_hertz, upper_edge_hertz) mel_spectrograms = tf.tensordot\ (spectrograms, \ linear_to_mel_weight_matrix, 1) mel_spectrograms.set_shape\ (spectrograms.shape[:-1].concatenate\ (linear_to_mel_weight_matrix.shape[-1:])) log_mel_spectrograms = tf.math.log\ (mel_spectrograms + 1e-6) #Compute MFCCs from log_mel_spectrograms mfccs = tf.signal.mfccs_from_log_mel_spectrograms\ (log_mel_spectrograms)[..., :num_mfccs] return mfccs
-
应用该函数为你在步骤 5中加载的音频数据生成 MFCC:
mfcc = apply_mfccs(audio) plt.pcolor(mfcc.numpy()[0]) plt.xlabel('MFCC log coefficient') plt.ylabel('Sample Value')
输出如下所示:
图 2.17:音频文件 MFCC 的可视化表示
上面的图表显示了 MFCC 值在x轴上的分布,以及音频样本在y轴上的不同点。MFCC 是原始音频信号的另一种表示方式,显示在步骤 5中,已被证明在语音识别相关应用中非常有用。
-
加载
AUTOTUNE
,以便可以使用 CPU 的所有可用线程。创建一个函数,接受数据集对象,打乱数据集,使用你在步骤 3中创建的函数加载音频数据,使用你在步骤 6中创建的函数生成 MFCC,重复数据集对象,进行批处理,并进行预取操作。使用AUTOTUNE
来根据可用的 CPU 预取数据,并设置缓冲区大小:AUTOTUNE = tf.data.experimental.AUTOTUNE def prep_ds(ds, shuffle_buffer_size=1024, \ batch_size=64): # Randomly shuffle (file_path, label) dataset ds = ds.shuffle(buffer_size=shuffle_buffer_size) # Load and decode audio from file paths ds = ds.map(load_audio, num_parallel_calls=AUTOTUNE) # generate MFCCs from the audio data ds = ds.map(apply_mfccs) # Repeat dataset forever ds = ds.repeat() # Prepare batches ds = ds.batch(batch_size) # Prefetch ds = ds.prefetch(buffer_size=AUTOTUNE) return ds
-
使用你在步骤 8中创建的函数生成训练数据集。为此,使用 TensorFlow 的
from_tensor_slices
函数创建数据集对象,并传入音频文件的路径。之后,你可以使用你在步骤 8中创建的函数:ds = tf.data.Dataset.from_tensor_slices(paths) train_ds = prep_ds(ds)
-
获取数据集的第一批并打印出来:
for x in train_ds.take(1):\ print(x)
输出如下所示:
图 2.18:生成 MFCC 后的一批音频数据
输出显示了第一批 MFCC 频谱值的张量形式。
在这个练习中,你导入了音频数据。你处理了数据集并对数据集进行了批处理,以便它适合大规模训练。这个方法是一个综合性的方案,其中数据被加载并转换到频域,生成了频谱图,然后最终生成了 MFCC。
在下一个活动中,你将加载音频数据并取输入的绝对值,随后对值进行对数缩放。这将确保数据集中没有负值。你将使用与练习 2.05中使用的相同音频数据集,即 Google 的语音命令数据集。该数据集包含 1 秒钟长的单词发音。
活动 2.03:为批处理加载音频数据
在此活动中,你将加载音频数据进行批处理。所执行的音频预处理技术包括取绝对值并使用 1 加上该值的对数。这样可以确保结果值为非负且以对数形式缩放。最终结果将是一个可以输入到 TensorFlow 模型中进行训练的预处理数据集对象。
该活动的步骤如下:
-
打开一个新的 Jupyter 笔记本以实现此活动。
-
导入 TensorFlow 和
os
库。 -
创建一个函数,使用 TensorFlow 的
read_file
函数加载音频文件,然后使用decode_wav
函数解码。返回函数的结果张量的转置。 -
使用
os.list_dir
将文件路径加载到音频数据列表中。 -
创建一个函数,该函数接受一个数据集对象,对其进行打乱,使用你在步骤 2中创建的函数加载音频,并对数据集应用绝对值和
log1p
函数。此函数将数据集中的每个值加上1
,然后对结果应用对数。接下来,重复数据集对象,对其进行批处理,并使用等于批处理大小的缓冲区大小进行预取。 -
使用 TensorFlow 的
from_tensor_slices
函数创建一个数据集对象,并传入音频文件的路径。然后,将你在第 4 步中创建的函数应用到第 5 步中创建的数据集上。 -
获取数据集的第一批数据并打印出来。
-
绘制批次中的第一个音频文件。
输出结果如下:
图 2.19:活动 2.03 的预期输出
注意
本活动的解决方案可以通过此链接找到。
在此活动中,你学习了如何批量加载和预处理音频数据。你使用了在练习 2.05中使用的大部分函数,加载数据并解码原始数据。练习 2.05与活动 2.03之间的区别在于预处理步骤;练习 2.05涉及为音频数据生成 MFCC,而活动 2.03则涉及对数据进行对数缩放。两者展示了可以用于所有音频数据建模应用的常见预处理技术。
在这一部分,你探索了如何将音频数据批量加载以用于 TensorFlow 模型。这个全面的方法展示了许多高级信号处理技术,这些技术为希望将音频数据应用于自己项目的从业者提供了良好的起点。
摘要
在本章中,你学习了如何加载不同形式的数据并为各种数据类型执行一些预处理步骤。你首先处理的是以 CSV 文件形式呈现的表格数据。由于数据集仅由一个 CSV 文件组成,你使用了 pandas 库将文件加载到内存中。
然后,你继续对数据进行预处理,通过缩放字段并将所有字段转换为数值数据类型。这一点很重要,因为 TensorFlow 模型只能对数值数据进行训练,如果所有字段的尺度相同,训练过程的速度和准确性都会得到提升。
然后,你探索了如何加载图像数据。你将数据批处理,这样就不需要一次性加载整个数据集,这也让你能够对图像进行数据增强。图像增强是有用的,因为它增加了有效的训练示例数量,并且有助于让模型更加稳健。
接下来,你学习了如何加载文本数据并利用预训练模型。这帮助你将文本嵌入到向量中,这些向量保留了关于文本的上下文信息。这样,你就可以将文本数据输入到 TensorFlow 模型中,因为它们需要数值张量作为输入。
最后,最后一部分讲解了如何加载和处理音频数据,并展示了一些高级信号处理技术,包括生成 MFCC(梅尔频率倒谱系数),它们可以用于生成信息密集的数值张量,并可以输入到 TensorFlow 模型中。
加载和预处理数据,以便将其输入到机器学习模型中,是训练任何机器学习模型的一个重要且必要的第一步。在下一章中,你将探索 TensorFlow 提供的许多资源,这些资源有助于模型构建的开发。
第三章:3. TensorFlow 开发
概述
TensorFlow 提供了许多资源,以创建高效的工作流程,帮助开发数据科学和机器学习应用程序。在本章中,你将学习如何使用 TensorBoard 来可视化 TensorFlow 图和操作,如何使用 TensorFlow Hub 来访问一个用户社区(这是一个非常好的预训练模型资源),以及如何使用 Google Colab,这是一个用于与他人共同开发代码的协作环境。你将使用这些工具通过最大化计算资源、转移预训练模型中的知识以及可视化模型构建过程的各个方面来加速开发。
介绍
在上一章中,你学习了如何加载和处理各种数据类型,以便在 TensorFlow 建模中使用。这包括来自 CSV 文件的表格数据、图像数据、文本数据和音频文件。到本章结束时,你应该能够处理所有这些数据类型,并从中生成数值张量,这些张量可以作为模型训练的输入。
在本章中,你将学习到有助于模型构建的 TensorFlow 资源,并帮助你创建高效的机器学习算法。你将探索实践者可以利用的实用资源,包括 TensorBoard、TensorFlow Hub 和 Google Colab。TensorBoard 是一个互动平台,提供了在 TensorFlow 开发过程中生成的计算图和数据的可视化表示。该平台解决了机器学习中常见的可视化各种数据类型的问题。该可视化工具包可以在模型构建过程中绘制模型评估指标,展示图像,播放音频数据,并执行许多其他任务,这些任务通常需要编写自定义函数。TensorBoard 提供了简单的日志写入函数,日志可以在浏览器窗口中进行可视化。
TensorFlow Hub 是一个开源库,包含了预训练的机器学习模型,代码库对所有人开放,供用户用于自己的应用程序并进行修改。用户可以通过专门的库将模型直接导入代码,并可以在tfhub.dev/
上查看。TensorFlow Hub 允许用户使用由领域专家创建的最先进的模型,并能显著缩短将预训练模型作为用户模型一部分时的训练时间。
例如,该平台包含了 ResNet-50 模型,这是一个 50 层的人工神经网络(ANN),在 ILSVRC 2015 分类任务中获得了第一名,该任务是将图像分类为 1000 个不同的类别。该网络拥有超过 2300 万个可训练参数,并在超过 1400 万张图像上进行了训练。从头开始在一台普通笔记本电脑上训练该模型,并达到接近 TensorFlow Hub 上预训练模型的准确性,可能需要几天时间。正因如此,利用 TensorFlow Hub 模型可以加速开发进程。
本章中你将了解的最后一个资源是 Google Colab,它是一个在线开发环境,用于在 Google 服务器上执行 Python 代码并创建机器学习算法。该环境甚至可以访问包含图形处理单元(GPUs)和张量处理单元(TPUs)的硬件,可以免费加速模型训练。Google Colab 可通过 colab.research.google.com/
访问。
Google Colab 解决了设置开发环境的问题,可以创建可以与他人共享的机器学习模型。例如,多名机器学习实践者可以开发相同的模型,并在同一硬件实例上训练该模型,而不需要各自使用自己的资源来运行该实例。顾名思义,该平台促进了机器学习实践者之间的协作。
现在,让我们来探索 TensorBoard,这是一个帮助实践者理解和调试机器学习工作流的工具。
TensorBoard
TensorBoard 是一个用于辅助机器学习实验的可视化工具包。该平台具有仪表板功能,可以可视化数据科学或机器学习实践者可能需要的多种常见数据类型,例如标量值、图像批次和音频文件。虽然这些可视化可以通过其他绘图库创建,例如 matplotlib
或 ggplot
,但 TensorBoard 将多种可视化集成在一个易于使用的环境中。此外,创建这些可视化所需的唯一操作是在构建、拟合和评估步骤中记录跟踪信息。TensorBoard 有助于以下任务:
- 可视化模型图以查看和理解模型的架构:
图 3.1:TensorBoard 中模型图和函数的可视化表示
-
查看变量的直方图和分布,并跟踪它们随时间的变化。
-
显示图像、文本和音频数据。例如,以下图显示了来自 Fashion MNIST 数据集的图像(
www.tensorflow.org/datasets/catalog/fashion_mnist
):
图 3.2:在 TensorBoard 中查看图像
- 在模型训练过程中绘制模型评估指标随 epoch 变化的图:
图 3.3:在 TensorBoard 中绘制模型评估指标
- 用于可视化嵌入向量的降维:
图 3.4:在 TensorBoard 中可视化嵌入向量
TensorBoard 从开发过程中写入的日志中创建可视化图像。为了创建日志以可视化图形,需要在开发代码中初始化文件写入对象,并将日志的位置作为参数提供。文件写入对象通常在 Jupyter notebook 或等效开发环境的开始阶段创建,在写入任何日志之前。具体如下:
logdir = 'logs/'
writer = tf.summary.create_file_writer(logdir)
在前面的代码中,设置了写入日志的目录,如果该目录尚不存在,则在运行代码后,工作目录中会自动创建一个新目录。文件写入对象在日志导出时将文件写入日志目录。要开始追踪,必须执行以下代码:
tf.summary.trace_on(graph=True, profiler=True)
前面的命令启动了追踪功能,用于记录从执行命令时开始的计算图。如果没有开启追踪,则不会记录任何内容,因此也无法在 TensorBoard 中可视化。计算图的追踪完成后,可以使用文件写入对象将日志写入日志目录,具体如下:
with writer.as_default():
tf.summary.trace_export(name="my_func_trace",\
step=0, profiler_outdir=logdir)
在写入日志时,需要使用以下参数:
-
name
:该参数描述了摘要的名称。 -
step
:该参数描述了摘要的单调步长值,如果对象随时间变化不大,可以将其设置为0
。 -
profiler_outdir
:该参数描述了写入日志的位置,如果在定义文件写入对象时没有提供该参数,则此参数是必需的。
日志写入目录设置完成后,可以通过命令行启动 TensorBoard,使用以下命令传入日志目录作为 logdir
参数:
tensorboard --logdir=./logs
某些版本的 Jupyter Notebooks 允许在笔记本中直接运行 TensorBoard。然而,库依赖关系和冲突常常会阻止 TensorBoard 在笔记本环境中运行,在这种情况下,可以通过命令行在单独的进程中启动 TensorBoard。在本书中,你将使用 TensorFlow 版本 2.6 和 TensorBoard 版本 2.1,并且始终通过命令行启动 TensorBoard。
在第一个练习中,你将学习如何使用 TensorBoard 可视化图计算过程。你将创建一个执行张量乘法的函数,并在 TensorBoard 中可视化计算图。
练习 3.01:使用 TensorBoard 可视化矩阵乘法
在本练习中,你将进行 7x7
矩阵的矩阵乘法运算,使用随机值,并追踪计算图和性能信息。之后,你将使用 TensorBoard 查看计算图。此练习将在 Jupyter Notebook 中进行。启动 TensorBoard 需要在命令行中运行命令,如最后一步所示。
请按照以下步骤操作:
-
打开一个新的 Jupyter Notebook,导入 TensorFlow 库,并设置随机种子以保证结果的可重复性。由于你正在生成随机值,设置种子可以确保每次运行代码时生成的值相同:
import tensorflow as tf tf.random.set_seed(42)
-
创建一个
file_writer
对象,并设置日志存储目录:logdir = 'logs/' writer = tf.summary.create_file_writer(logdir)
-
创建一个 TensorFlow 函数来进行两个矩阵相乘:
@tf.function def my_matmult_func(x, y): result = tf.matmul(x, y) return result
-
创建两个形状为
7x7
的张量,数据为随机变量:x = tf.random.uniform((7, 7)) y = tf.random.uniform((7, 7))
-
使用 TensorFlow 的
summary
类开启图形追踪:tf.summary.trace_on(graph=True, profiler=True)
-
将第3 步中创建的函数应用于第4 步中创建的示例张量。接下来,将追踪数据导出到
log
目录,并为图形设置name
参数以便参考,同时将log
目录设置为profiler_outdir
参数。step
参数表示摘要的单调步长值;如果被追踪的值有变化,该值应非零,并且可以通过该参数指定的步长进行可视化。对于静态对象,例如此处的图形追踪,步长应设置为零:z = my_matmult_func(x, y) with writer.as_default(): tf.summary.trace_export(name="my_func_trace",\ step=0,\ profiler_outdir=logdir)
-
最后,使用命令行在当前工作目录下启动 TensorBoard,查看图形的可视化表示。启动 TensorBoard 后,可以通过访问提供的网址在网页浏览器中查看:
tensorboard --logdir=./logs
对于在 Windows 上运行的用户,请在 Anaconda 提示符下运行以下命令:
tensorboard --logdir=logs
通过运行上述代码,你将能够可视化以下模型图:
图 3.5:在 TensorBoard 中矩阵乘法的可视化表示
在 TensorBoard 中,你可以查看一个张量如何将两个矩阵相乘以生成另一个矩阵。通过选择不同的元素,你可以查看计算图中每个对象的相关信息,具体取决于对象的类型。在这里,你创建了两个张量,命名为 x
和 y
,它们由底部的节点表示。通过选择其中一个节点,你可以查看有关张量的属性,包括它的数据类型(float
)、用户指定的名称(x
或 y
),以及输出节点的名称(MatMul
)。这些表示输入张量的节点然后被输入到另一个节点,该节点表示张量乘法过程,标记为 MatMul
,它是在 TensorFlow 函数之后的节点。选择该节点后,你可以查看该函数的属性,包括输入参数、输入节点(x
和 y
)和输出节点(Identity
)。最后两个节点,标记为 Identity
和 identity_RetVal
,表示输出张量的创建。
在本次练习中,你使用 TensorBoard 可视化计算图。你创建了一个简单的函数,将两个张量相乘,并通过追踪图并记录结果来记录过程。在记录了计算图之后,你可以通过启动 TensorBoard 并将工具指向日志的存储位置来可视化它。
在第一个活动中,你将练习使用 TensorBoard 可视化一个更复杂的张量转换。实际上,任何张量过程和转换都可以在 TensorBoard 中可视化,前一个练习中演示的过程是创建和编写日志的好指南。
活动 3.01:使用 TensorBoard 可视化张量转换
给定两个形状为5x5x5
的张量。你需要创建 TensorFlow 函数来执行张量转换,并查看转换的可视化表示。
你将采取的步骤如下:
-
导入 TensorFlow 库并将随机种子设置为
42
。 -
设置日志目录并初始化文件写入对象以写入跟踪。
-
创建一个 TensorFlow 函数,将两个张量相乘,并使用
ones_like
函数向结果张量的所有元素加上一个值1
,以创建一个与矩阵乘法结果相同形状的张量。然后,对张量的每个值应用sigmoid
函数。 -
创建两个形状为
5x5x5
的张量。 -
打开图形追踪。
-
将函数应用于这两个张量,并将追踪记录导出到日志目录。
-
在命令行中启动 TensorBoard,并在网页浏览器中查看图形:
图 3.6:TensorBoard 中张量转换的可视化表示
注意
本活动的解决方案可以通过这个链接找到。
然而,TensorBoard 不仅仅用于可视化计算图。通过使用合适的 TensorFlow summary
方法,将图片、标量变量、直方图和分布写入日志目录后,均可在 TensorBoard 中查看。例如,图片可以按照以下方式写入日志:
with file_writer.as_default():
tf.summary.image("Training data", training_images, step=0)
这样做的结果将是在日志目录中添加一个名为 Training data
的文件,其中包含文件写入器写入的图片。可以通过选择标签为 IMAGES
的选项卡在 TensorBoard 中查看这些图片。
同样地,标量变量也可以以如下方式写入日志,以便在 TensorBoard 中查看:
with file_writer.as_default():
tf.summary.scalar('scalar variable', variable, step=0)
音频文件也可以按照以下方式写入日志,以便在 TensorBoard 中播放:
with file_writer.as_default():
tf.summary.audio('audio file', data, sample_rate=44100, \
step=0)
可以通过传入数据如下方式记录直方图:
with file_writer.as_default():
tf.summary.histogram('histogram', data, step=0)
在所有这些写入日志的数据示例中,step
参数被设置为零,因为这是一个必需的参数,不能为 null。将参数设置为零表示该值是静态的,不会随时间变化。每种数据类型将在 TensorBoard 中的不同标签下显示。
在下一个练习中,你将把图片写入 TensorBoard,以便可以直接在平台内查看。使用 TensorBoard,这个过程变得非常简便,而不需要编写自定义代码来查看图片。你可能希望可视化图片批次,以验证标签、检查数据增强过程,或验证图片的整体质量。
练习 3.02:使用 TensorBoard 可视化图片批次
在这个练习中,你将使用 TensorBoard 查看图片批次。你将创建一个文件写入器和一个数据生成器,并将一批图片写入日志文件。最后,你将会在 TensorBoard 中查看这些图片。
注意
你可以在 image_data
文件夹中找到图片:packt.link/1ue46
。
按照以下步骤操作:
-
导入 TensorFlow 库和
ImageDataGenerator
类:import tensorflow as tf from tensorflow.keras.preprocessing.image import \ ImageDataGenerator
-
创建一个
file_writer
对象,并设置日志存储的目录:logdir = 'logs/' writer = tf.summary.create_file_writer(logdir)
-
初始化一个
ImageDataGenerator
对象:train_datagen = ImageDataGenerator(rescale = 1./255)
-
使用数据生成器的
flow_from_directory
方法创建一个批次图片加载器:batch_size = 25 training_set = train_datagen.flow_from_directory\ ('image_data',\ target_size = (224, 224),\ batch_size = batch_size,\ class_mode = 'binary')
注意
确保将路径(高亮部分)更改为系统上目录的位置。如果你是在存储数据集的同一目录下运行 Jupyter notebook,可以直接运行上述代码而无需修改。
-
从第一批图片中取出并使用文件写入器将其写入日志:
with file_writer.as_default(): tf.summary.image("Training data", \ next(training_set)[0], \ max_outputs=batch_size, \ step=0)
-
在命令行中启动 TensorBoard 以查看图形的可视化表示。启动 TensorBoard 后,可以通过访问提供的 URL 在 Web 浏览器中查看 TensorBoard。默认的 URL 是
http://localhost:6006/
:tensorboard --logdir=./logs
对于在 Windows 上运行的用户,请在 Anaconda 提示符下运行以下命令:
tensorboard --logdir=logs
目录中的图片将在 TensorBoard 中显示如下:
图 3.7:在 TensorBoard 中查看图片批次
注意
系统中的图像可能会有所不同。
TensorBoard 中的结果是来自第一批次的图像。你可以看到它们是船和飞机的图像。TensorBoard 还提供了调整图像亮度和对比度的功能;然而,这仅会影响 TensorBoard 中的图像,而不会影响底层的图像数据。
在这个练习中,你通过 TensorBoard 查看了一个图像数据生成器的图像批次。这是验证训练数据质量的一个极好方法。虽然不必验证每一张图像的质量,但可以通过 TensorBoard 轻松查看样本批次。
本节介绍了 TensorFlow 提供的一项资源,帮助数据科学和机器学习从业者理解和可视化数据和算法:TensorBoard。你已使用该资源可视化计算图和图像批次。在下一节中,你将探索 TensorFlow Hub,它是一个可以轻松访问并集成到自定义应用中的机器学习模块库。这些模型由该领域的专家创建,你将学习如何将它们应用于自己的项目。
TensorFlow Hub
TensorFlow Hub 是一个机器学习模块的在线库。该模块包含了使用任何模型所需的资产及其相关权重(例如,用于预测或迁移学习),在迁移学习中,训练一个模型所获得的知识被用于解决不同但相关的问题。这些模块可以直接用于创建它们所训练的应用,或者可以作为构建新应用的起点。可以通过以下网址访问该平台:tfhub.dev/
。当你访问该网站时,会看到以下页面:
图 3.8:TensorFlow Hub 首页
在这里,你可以浏览各个领域的模型。最受欢迎的领域包括图像、文本和视频;这些领域中有许多模型:
图 3.9:TensorFlow Hub 上可用的模型领域
TensorFlow Hub 上有许多模型可供使用,这些模型以图像作为输入数据。通常,这些模型用于图像分类、分割、嵌入、生成、增强和风格迁移等任务。为文本数据创建的模型通常用于文本嵌入,而用于视频数据的模型则用于视频分类。还有用于音频数据的模型,涉及命令检测和音高提取等任务。TensorFlow Hub 不断更新最新的先进模型,这些模型可用于各种应用。
选择模型后,你将进入以下页面,该页面会告诉你关于该模型的信息,如模型大小、架构、训练使用的数据集以及参考的 URL:
图 3.10:TensorFlow Hub 模型的页面
在引用自己的应用程序所需的模型时,你需要获取模型页面的 URL 以加载它。
可以通过使用 tensorflow_hub
库在笔记本环境中访问 TensorFlow Hub 上的模型。可以按照以下方式导入该库:
import tensorflow_hub as hub
可以通过使用库的 load
函数并传入模型的参考 URL 来加载模型:
module = hub.load("https://tfhub.dev/google/imagenet"\
"/inception_resnet_v2/classification/4")
可以通过访问 signatures
属性来查看模型模块的资源,例如其架构。每个模型的 signatures
属性中可能有不同的键;然而,相关信息通常会包含在 default
键中:
model = module.signatures['default']
该模型还可以通过将整个模型视为一个单独的 Keras 层,并使用 KerasLayer
方法直接用于训练:
layer = hub.KerasLayer("https://tfhub.dev/google/imagenet"\
"/inception_resnet_v2/classification/4")
将模型作为层用于自己的应用程序的过程称为迁移学习,将在后续章节中更深入地探讨。
可以通过将模型图写入日志来查看 TensorFlow Hub 中的模型,方法是使用文件写入器,如下所示:
from tensorflow.python.client import session
from tensorflow.python.summary import summary
from tensorflow.python.framework import ops
with session.Session(graph=ops.Graph()) as sess:
file_writer = summary.FileWriter(logdir)
file_writer.add_graph(model.graph)
在接下来的练习中,你将从 TensorFlow Hub 下载一个模型。加载模型后,你将使用 TensorBoard 查看模型的架构。
练习 3.03:从 TensorFlow Hub 下载模型
在本练习中,你将从 TensorFlow Hub 下载一个模型,然后在 TensorBoard 中查看该模型的架构。将要下载的模型是 InceptionV3
模型。该模型是在 TensorFlow 1 中创建的,因此在我们使用 TensorFlow 2 时,需要一些额外的步骤来显示模型的细节。该模型包含两部分:一部分包括卷积层,用于从图像中提取特征,另一部分是具有全连接层的分类部分。
由于原作者已恰当地命名了这些层,因此它们将在 TensorBoard 中可见。
注意
你可以在这里获取 InceptionV3
模型:tfhub.dev/google/imagenet/inception_v3/classification/5
。
按照以下步骤完成此练习:
-
从 TensorFlow 导入以下库:
import tensorflow as tf import tensorflow_hub as hub from tensorflow.python.client import session from tensorflow.python.summary import summary from tensorflow.python.framework import ops
导入和构建模型需要 TensorFlow 和 TensorFlow Hub 库,此外,还需要使用 TensorFlow 2 视图功能来可视化使用 TensorFlow 1 创建的模型,这也是你在本书中使用的版本。
-
为存储日志创建一个变量:
logdir = 'logs/'
-
通过使用
tensorflow_hub
库中的load
方法并传入模型的 URL,可以加载模型模块:module = hub.load('https://tfhub.dev/google/imagenet'\ '/inception_v3/classification/5')
-
从模块的
signatures
属性加载模型:model = module.signatures['default']
-
使用文件写入器将模型图写入 TensorBoard:
with session.Session(graph=ops.Graph()) as sess: file_writer = summary.FileWriter(logdir) file_writer.add_graph(model.graph)
-
在命令行中启动 TensorBoard,以查看图形的可视化表示。启动 TensorBoard 后,您可以通过访问提供的 URL 在网页浏览器中查看 TensorBoard:
tensorboard --logdir=./logs
对于运行 Windows 的用户,在 Anaconda 提示符中运行以下命令:
tensorboard --logdir=logs
您应该能看到类似以下的图像:
](https://github.com/OpenDocCN/freelearn-dl-pt3-zh/raw/master/docs/tf-ws/img/B16341_03_11.jpg)
图 3.11:在 TensorBoard 中查看的 InceptionV3 模型架构
TensorBoard 中的结果是 InceptionV3
模型的架构。在这里,您可以查看模型每一层的所有细节,包括输入、输出和激活函数。
在本次练习中,您成功地使用 TensorFlow Hub 库将模型下载到 Jupyter 笔记本环境中。一旦模型被加载到环境中,您就使用 TensorBoard 可视化了模型的架构。这是一种帮助您在调试过程中可视化模型架构的有效方式。
在本节中,您已探讨了如何使用 TensorFlow Hub 作为一种方式,利用机器学习领域专家创建的众多优秀模型。正如您将在后续章节中发现的,这些模型可以用于解决与它们最初开发用途稍有不同的应用问题;这就是所谓的迁移学习。在下一节中,您将学习如何使用 Google Colab,这是一种类似于 Jupyter Notebooks 的环境,可以在 Google 服务器上在线协作开发 Python 应用程序。
Google Colab
Google Colab 使用户能够在 Google 服务器上执行代码,专门为数据科学从业人员设计,用于在协作环境中开发机器学习代码。该平台可以在 colab.research.google.com/
上使用,提供在网页浏览器中直接开发 Python 编程语言的机会,无需在本地计算机上执行任何代码。该环境预加载了最新的数据科学和机器学习库,提供了一个方便的替代方案,而无需使用 Jupyter Notebooks 设置开发环境。此外,该平台还提供免费的使用层级,包括访问 GPU 和 TPU,无需配置,并且在协作者之间共享笔记本非常简便。
Google Colab 提供了与 Jupyter Notebooks 非常相似的开发体验,使用 Google Colab 相较于 Jupyter Notebooks 也有一些优点和缺点。
Google Colab 的优点
以下是使用 Google Colab 的一些主要优点:
-
协作:许多用户可以访问同一个笔记本并共同协作工作。
-
托管环境:Google Colab 运行在 Google 服务器上,如果本地计算资源有限,这将非常有帮助。无需设置开发环境,因为许多软件包已经预装。
-
易于访问:Google Colab 直接保存到 Google Drive,提供无缝集成。由于笔记本保存在云端,因此无论何时只要能访问 Google Drive,都可以访问它们。
-
加速训练时间:提供 GPU 和 TPU 服务器,这可以加速训练机器学习模型的时间,尤其是有许多隐藏层的人工神经网络(ANN)。
-
交互式小部件:可以向笔记本添加小部件,以交互方式轻松地变化输入参数和变量。
Google Colab 的缺点
以下是使用 Google Colab 的一些缺点:
-
受限的运行时:Google Colab 上仅提供两个版本的 TensorFlow,1.X 和 2.X,且它们会更新,因此某些功能可能会随时间变化,从而导致代码损坏。此外,TensorFlow 的版本可能与其他软件包不兼容。
-
依赖互联网:由于 Python 代码是在 Google 服务器上执行的,Google Colab 只能在有互联网连接的情况下访问。
-
无自动保存:笔记本必须手动保存,这与 Jupyter Notebooks 的自动保存功能不同。
-
会话超时:在虚拟机上运行的笔记本有最大 12 小时的使用时间,长时间未使用的环境将被断开连接。
-
geoplotlib
可能无法显示交互元素,因为与预装的库存在不兼容问题。
在 Google Colab 上开发
由于 Google Colab 使用笔记本,开发环境与 Jupyter Notebooks 非常相似。事实上,IPython 笔记本可以加载到 Google Colab 中。可以通过直接上传、Google Drive 或 GitHub 仓库加载它们。或者,该平台提供了示例笔记本,供用户入门。当你访问该平台时,colab.research.google.com/
,你将看到以下界面,其中提供了可打开的笔记本或选择新笔记本开始开发的选项:
图 3.12:Google Colab 的主页
如果选择了一个新笔记本,你将看到以下界面,这可能让你想起在 Jupyter Notebooks 中开发的场景,且有许多相同的功能。你可以以完全相同的方式创建代码或文本片段,许多从业者发现从 Jupyter 过渡到 Colab 非常顺畅:
图 3.13:Google Colab 中的空白笔记本
在接下来的练习中,您将使用 Google Colab 导入并操作数据。在 Google Colab 与 Jupyter Notebooks 工作的主要区别之一是,在 Google Colab 中工作时,您是在远程服务器上开发。这意味着任何用于分析或训练模型的数据必须加载到 Google Drive 中或直接在线可用。在以下练习中,您将直接从 GitHub 仓库导入本书的 CSV 数据。
练习 3.04:使用 Google Colab 可视化数据
在本练习中,您将从 GitHub 仓库加载一个数据集,该数据集包含针对韩国首尔次日最高和最低气温预报的偏差校正数据。
注意
您可以在此处找到 Bias_correction_ucl.csv
文件:packt.link/8kP3j
。
为了执行此练习,您需要导航到 colab.research.google.com/
并创建一个新的笔记本进行操作。您需要连接到一个启用了 GPU 的环境,以加速 TensorFlow 操作,如张量乘法。一旦数据加载到开发环境中,您将查看前五行。接下来,您将删除 Date
字段,因为矩阵乘法需要数值字段。然后,您将对数据集进行张量乘法运算,使用一个张量或均匀随机变量。
按照以下步骤完成此练习:
-
导入 TensorFlow 并检查库的版本:
import tensorflow as tf print('TF version:', tf.__version__)
您应当得到 TensorFlow 库的版本:
图 3.14:Google Colab 中可用的 TensorFlow 版本的输出
-
导航到
编辑
标签,进入笔记本设置
,然后从硬件加速
下拉菜单中选择GPU
。通过显示 GPU 设备名称来验证 GPU 是否已启用:tf.test.gpu_device_name()
您应当得到 GPU 设备的名称:
图 3.15:GPU 设备名称
-
导入
pandas
库并直接从 GitHub 仓库加载数据集:import pandas as pd df = pd.read_csv('https://raw.githubusercontent.com'\ '/PacktWorkshops/The-TensorFlow-Workshop'\ '/master/Chapter03/Datasets'\ '/Bias_correction_ucl.csv')
-
使用
head
方法查看数据集的前五行:df.head()
您应当得到以下输出:
图 3.16:DataFrame 前五行的输出
-
删除
Date
字段,因为您将进行矩阵乘法,而矩阵乘法需要数值字段:df.drop('Date', axis=1, inplace=True)
-
导入 NumPy,将 DataFrame 转换为 NumPy 数组,然后创建一个包含均匀随机变量的 TensorFlow 张量。张量的第一个轴的值将等于数据集字段的数量,第二个轴将等于
1
:import numpy as np df = np.asarray(df).astype(np.float32) random_tensor = tf.random.normal((df.shape[1],1))
-
使用 TensorFlow 的
matmul
函数对数据集和随机张量进行张量乘法,并打印结果:tf.matmul(df, random_tensor)
您应当得到如下输出:
图 3.17:张量乘法的输出
执行乘法运算的结果是一个新的张量,形状为7752x1
。
在本练习中,你学习了如何使用 Google Colab。你观察到 Google Colab 提供了一个方便的环境来构建机器学习模型,并且预先加载了许多可能需要的机器学习库。你还可以看到使用的是这些库的最新版本。不幸的是,TensorFlow 的版本不能修改,因此在生产环境中使用 Google Colab 可能不是最合适的应用。但它非常适合开发环境。
在接下来的活动中,你将进一步练习如何在开发环境中使用 Google Colab。你将以与 Jupyter Notebooks 中相同的方式使用 TensorFlow Hub。本活动将类似于练习 2.04(加载文本数据到 TensorFlow 模型),其中通过使用预训练的词嵌入模型处理文本数据。利用预训练模型将在未来章节中介绍,但本活动将展示如何轻松利用来自 TensorFlow Hub 的预训练模型。
活动 3.02:从 TensorFlow Hub 中进行预训练模型的词嵌入
在本活动中,你将练习在 Google Colab 环境中工作。你将从 TensorFlow Hub 下载一个通用句子编码器,URL 为:tfhub.dev/google/universal-sentence-encoder/4
。模型加载到内存后,你将使用它对一些示例文本进行编码。
按照以下步骤操作:
-
导入 TensorFlow 和 TensorFlow Hub,并打印库的版本。
-
将模块的句柄设置为通用句子编码器的 URL。
-
使用 TensorFlow Hub 的
KerasLayer
类创建一个 hub 层,并传入以下参数:module_handle
、input_shape
和dtype
。 -
创建一个包含字符串
The TensorFlow Workshop
的列表,用该编码器进行编码。 -
将
hub_layer
应用于文本,将句子嵌入为一个向量。你的最终输出应该如下所示:
图 3.18:活动 3.02 的预期输出
注意
本活动的解决方案可以通过此链接找到。
本节介绍了 Google Colab,这是一种在线开发环境,用于在 Google 服务器上运行 Python 代码。这可以使任何有互联网连接的从业人员开始构建机器学习模型。此外,你可以浏览预训练模型的选择,使用本章中学习到的另一个资源——TensorFlow Hub,开始为自己的应用创建模型。Google Colab 为从业者提供了零配置、最新的环境,甚至可以访问 GPU 和 TPU 以加速模型训练。
概要
在本章中,您使用了多种 TensorFlow 资源,包括 TensorBoard、TensorFlow Hub 和 Google Colab。 TensorBoard 为用户提供了一种可视化计算模型图、指标和任何实验结果的方法。 TensorFlow Hub 允许用户利用领域专家构建的预训练模型加速他们的机器学习开发。 Google Colab 提供了一个协作环境,让您在 Google 服务器上开发机器学习模型。 开发高效的机器学习模型是一个试验和错误的迭代过程,能够可视化每个步骤可以帮助从业者调试和改进他们的模型。 此外,理解领域专家如何构建他们的模型,并能够利用网络中的预训练权重,可以显著减少训练时间。 所有这些资源都用于提供一个有效的工作流程,以开发和调试机器学习算法。
在下一章中,您将开始在 TensorFlow 中创建自己的机器学习模型,从回归模型开始。 回归模型旨在从输入数据中预测连续变量。 您将利用 Keras 层来制作您的回归模型,这对于构建人工神经网络非常有用。
第四章:4. 回归与分类模型
概述
在本章中,你将学习如何使用 TensorFlow 构建回归和分类模型。你将利用 TensorFlow 中的 Keras 层来构建模型,Keras 是一种简单的建模方法,提供了一个高级 API 来构建和训练模型。你将创建模型来解决回归和分类任务,包括分类不同分子结合特性的任务。你还将使用 TensorBoard 来可视化 TensorFlow 模型的架构并查看训练过程。
介绍
在上一章中,你学习了如何使用一些 TensorFlow 资源来帮助开发。这些资源包括 TensorBoard(用于可视化计算图)、TensorFlow Hub(一个机器学习模块的在线仓库)和 Google Colab(一个在线 Python 开发环境,用于在 Google 服务器上运行代码)。所有这些资源都帮助机器学习从业者高效地开发模型。
在本章中,你将探索如何使用 TensorFlow 创建人工神经网络(ANNs)。你将构建具有不同架构的 ANNs 来解决回归和分类任务。回归任务旨在从输入的训练数据中预测连续变量,而分类任务则旨在将输入数据分类到两个或更多类别中。例如,预测某一天是否会下雨的模型是一个分类任务,因为模型的结果会有两个类别——下雨或不下雨。然而,预测某一天降水量的模型则是一个回归任务,因为模型的输出是一个连续变量——降水量。
用于解决这些任务的模型代表了机器学习模型中的一大类,许多机器学习问题都属于这两大类。 本章将演示如何在 TensorFlow 中创建、训练和评估回归和分类模型。你将运用前几章所学的内容(包括使用 TensorBoard 来监控模型训练过程),了解如何构建高效的模型。
本章介绍了构建人工神经网络(ANNs)时使用的各种参数(称为超参数),这些参数包括激活函数、损失函数和优化器。模型拟合过程中的其他超参数包括训练轮次(epochs)和批量大小(batch size),它们分别决定了整个数据集用于更新权重的次数和每次更新时使用的数据点数量。你还将学习如何在模型拟合过程中记录变量,以便在 TensorBoard 中进行可视化。这样可以帮助你判断模型是欠拟合还是过拟合训练数据。最后,在构建模型后,你将学习如何在数据集上评估模型,看看它的表现如何。
序列模型
顺序模型用于构建回归和分类模型。在顺序模型中,信息从开始的输入层传播到结束的输出层。模型中的层是按顺序堆叠的,每一层都有输入和输出。
存在其他类型的人工神经网络(ANN)模型,例如递归神经网络(其输出反馈到输入中),这些将在后续章节中介绍。顺序神经网络与递归神经网络的区别如图 4.01所示。在这两种模型中,信息从输入层通过隐藏层流向输出层,箭头的方向表示这一流向。然而,在递归结构中,隐藏层的输出会反馈到隐藏层的输入:
](https://github.com/OpenDocCN/freelearn-dl-pt3-zh/raw/master/docs/tf-ws/img/B16341_04_01.jpg)
图 4.1:顺序和递归人工神经网络的架构
在接下来的章节中,您将学习如何在 TensorFlow 中创建顺序模型,这些模型构成了回归和分类模型的基础。您将使用 Keras API,该 API 现在作为 TensorFlow 库的一部分,专门用于顺序模型,因为这个高级 API 提供了一个简单的接口来创建这些模型。使用该 API,您会发现向模型中添加更多层非常容易,非常适合新手学习这个领域。
可以按如下方式初始化一个顺序模型:
model = tf.keras.Sequential()
一旦模型初始化完成,就可以向模型添加层。在本节中,您还将探讨如何向模型中添加 Keras 层。
Keras 层
Keras 层包含在 TensorFlow 包中。Keras 层是常用层的集合,可以轻松地添加到您的顺序模型中。
注意
您可以在这里查看 Keras 层的所有可能选项:www.tensorflow.org/api_docs/python/tf/keras/layers
。
要向 Sequential
类的模型添加层,可以使用模型的 add
方法。可以选择在顺序模型的开头添加一个输入层,作为网络的入口点。输入层可以接受以下常见的输入参数:
-
input_shape
(必需):输入张量的形状,不包括批处理轴 -
batch_size
:一个可选参数,表示输入的批处理大小 -
name
:输入层的可选名称
可以按如下方式向模型添加输入层。以下代码片段用于添加一个层,假设输入具有八个特征:
model.add(tf.keras.layers.InputLayer(input_shape=(8,), \
name='Input_layer'))
通过提供 name
参数,您可以给层命名,这在 TensorBoard 中可视化模型时会非常有用。另一种常用的层是提供为参数的 input_shape
。以下是 Dense
类层的常见输入参数:
-
units
(必需):这是一个正整数,表示该层中的单元数量。 -
input_shape
: 这是输入张量的形状,除非它是模型的第一层,否则不需要。 -
activation
: 这是一个可选参数,指示要应用于该层输出的激活函数。 -
use_bias
: 这是一个布尔值参数,指示是否在层中使用偏置。默认值设置为True
。 -
name
: 这是该层的名称。如果未提供该参数,将会自动生成一个名称。 -
kernel_initializer
: 这是用于初始化核权重的初始化器。默认使用Glorot 均匀初始化器,它的分布以零为中心,标准差依赖于该层单元的数量。 -
bias_initializer
: 这是偏置的初始化器。此参数的默认值将偏置值设置为零。 -
kernel_regularizer
: 这是用于核权重的正则化器,默认情况下没有应用任何正则化器。 -
bias_regularizer
: 这是用于偏置的正则化器,默认情况下没有应用任何正则化器。
以下是将一个 12
单元的密集层(dense layer)添加到模型的示例,并在该层输出处添加 sigmoid
激活函数,同时将该层命名为 Dense_layer_1
:
model.add(tf.keras.layers.Dense(units=12, name='Dense_layer_1', \
activation='sigmoid'))
现在你已经理解了如何初始化序列型模型并向其中添加层,你将在第一个练习中使用 TensorFlow 创建一个 Keras 序列模型。你将初始化一个模型,向模型添加层,向模型的输出添加激活函数,并将数据通过模型来模拟创建一个预测。
练习 4.01:使用 TensorFlow 创建人工神经网络(ANN)
在本练习中,你将创建第一个 TensorFlow 序列型人工神经网络(ANN)。你将有一个输入层、一个具有四个单元和 ReLU 激活函数的隐藏层,以及一个具有一个单元的输出层。然后,你将通过生成随机数来创建一些模拟数据,并将其通过模型,使用模型的 predict
方法模拟对每个数据示例的预测。
执行以下步骤以完成本次练习:
-
打开 Jupyter Notebook 并导入 TensorFlow 库:
import tensorflow as tf
-
初始化一个 Keras 序列模型:
model = tf.keras.Sequential()
-
使用模型的
add
方法向模型添加一个输入层,并添加input_shape
参数,大小为(8,)
,表示具有八个特征的输入数据:model.add(tf.keras.layers.InputLayer(input_shape=(8,), \ name='Input_layer'))
-
向模型添加两个
Dense
类的层,第一个表示具有四个单元和 ReLU 激活函数的隐藏层,第二个表示具有一个单元的输出层:model.add(tf.keras.layers.Dense(4, activation='relu', \ name='First_hidden_layer')) model.add(tf.keras.layers.Dense(1, name='Output_layer'))
-
通过调用模型的
variables
属性查看权重:model.variables
你应该会看到以下输出:
图 4.2:人工神经网络的变量
该输出显示了组成模型的所有变量;它们包括每一层中所有权重和偏置的值。
-
创建一个大小为
32x8
的张量,这表示一个包含 32 条记录和 8 个特征的张量:data = tf.random.normal((32,8))
-
调用模型的
predict
方法并传入样本数据:model.predict(data) prediction
你应该得到以下结果:
图 4.3:应用随机输入后的人工神经网络(ANN)输出
在样本数据上调用predict()
方法将数据传递通过网络。在每一层中,数据会与权重进行矩阵乘法,偏置将被加到数据中,然后数据作为输入传递到下一层。这个过程会持续,直到最终的输出层。
在这个练习中,你创建了一个包含多层的顺序模型。你初始化了一个模型,添加了一个输入层以接受具有八个特征的数据,添加了一个具有四个单元的隐藏层,并添加了一个具有一个单元的输出层。在将模型拟合到训练数据之前,必须首先通过优化器编译模型,并选择一个损失函数来最小化通过更新训练过程中的权重计算的值。
在接下来的章节中,你将探索如何编译模型,然后将其拟合到训练数据中。
模型拟合
一旦模型被初始化并且层已经添加到人工神经网络(ANN)中,就必须通过编译过程使用优化器、损失函数和任何评估指标来配置模型。模型可以使用模型的compile
方法进行编译,如下所示:
model.compile(optimizer='adam', loss='binary_crossentropy', \
metrics=['accuracy'])
可以通过简单地命名优化器作为参数来选择优化器。以下是 Keras 模型的默认可用优化器:
-
随机梯度下降法(SGD):此方法更新数据集中每个示例的权重。你可以在这里找到有关 SGD 的更多信息:
keras.io/api/optimizers/sgd/
。 -
RMSprop:这是一种自适应优化器,在训练过程中通过使用梯度的衰减平均值来改变权重。你可以在这里找到有关 RMSprop 的更多信息:
keras.io/api/optimizers/rmsprop/
。 -
Adam:这也是一个自适应优化器,实施了 Adam 算法,根据一阶和二阶梯度更新学习率。你可以在这里找到有关 Adam 的更多信息:
keras.io/api/optimizers/adam/
。 -
Adagrad:这种自适应梯度优化器在每次更新权重时调整学习率。每个特征的学习率会根据先前的梯度和观测值进行调整。你可以在这里找到有关 Adagrad 的更多信息:
keras.io/api/optimizers/adagrad/
。 -
Adadelta:这是 Adagrad 的一个更强大的版本,通过使用梯度更新的滑动窗口来调整学习率。你可以在这里找到有关 Adadelta 的更多信息:
keras.io/api/optimizers/adadelta/
。 -
Adamax:这是一种自适应优化器,是 Adam 优化器的变种。 您可以在这里找到有关 Adamax 的更多信息:
keras.io/api/optimizers/adamax/
。 -
Nadam:这是 Adam 优化器的另一种自适应变种,具有 Nesterov 动量。 您可以在这里找到有关 Nadam 的更多信息:
keras.io/api/optimizers/Nadam/
。 -
Ftrl:这是实施 FTRL 算法的优化器。 您可以在这里找到有关 Ftrl 的更多信息:
keras.io/api/optimizers/ftrl/
。
如果提供的优化器不相关,则还可以将自定义优化器添加到 Keras 模型中。 选择最合适的优化器通常是尝试每个优化器并确定哪个优化器通过优化过程产生最低误差的问题。 这个过程称为 超参数调优,将在后续章节中讨论。 在下一节中,您将了解在编译模型时的另一个选项:损失函数。 训练模型的目标是通过最小化损失函数计算的值来最小化损失函数。
损失函数
损失函数是预测结果与真实结果之间误差的度量。 您在训练过程中使用损失函数来确定通过优化过程中任何权重和偏差的变化是否能创建更好的模型,通过最小化优化过程中损失函数的值来实现这一目标。
可以使用许多不同类型的损失函数,具体选择将取决于问题和目标。 一般来说,回归和分类任务将使用不同的损失函数。 由于回归模型预测连续变量,回归模型的损失函数通常旨在总结预测与真实值之间的平均距离。 对于分类模型,损失函数旨在确定预测类的真阳性、真阴性、假阳性和假阴性分类数量与真实类的差异程度。
真阳性 是分类器正确预测为阳性的标签; 同样地,真阴性 是正确预测为阴性的标签。假阳性 是被预测为阳性但实际为阴性的预测,而 假阴性 是被预测为阴性但实际为阳性的预测。Keras 顺序模型中用于回归的直接可用损失函数包括以下内容:
-
(真实值 – 预测值)²
,并返回整个数据集的平均值。 此损失函数主要用于回归问题,两个值之间的平方差确保损失函数结果为正数。 -
|真实值 – 预测值|
,并返回整个数据集的平均值。 此方法还确保结果为正值。 -
|(真实值 - 预测值) / 真实值|
,并返回数据集上的平均值,作为百分比表示。
对于分类问题,可用的损失函数包括以下内容:
-
0
和1
,值越接近1
表示真实正例分类的数量越多。 -
0
和1
。
在编译模型时,其他指标也可以作为参数传递给方法。它们将在每个 epoch 后进行计算,并在训练过程中保存。可用于 Keras 模型计算的指标包括以下内容:
-
准确率:这是正确结果占总结果的比例。
-
精确度:这是预测为正例的真实正例的比例。
-
召回率:这是实际正例中真实正例的比例。
-
AUC:这个指标表示 ROC 曲线下的面积。
这些指标在理解模型在训练过程中的表现时非常有价值。所有指标的值都在 0
和 1
之间,值越高表示表现越好。一旦模型编译完成,就可以将其拟合到训练数据。这可以通过调用 fit
方法并传入以下参数来实现:
-
x
:这是特征数据,可以是一个 TensorFlow 张量或 NumPy 数组。 -
y
:这是目标数据,可以是一个 TensorFlow 张量或 NumPy 数组。 -
epochs
:这是模型训练的 epoch 数量。一个 epoch 是对整个训练数据集的一个迭代。 -
batch_size
:这是每次梯度更新时使用的训练数据样本数量。 -
validation_split
:这是用于验证的数据占训练数据的比例,在每个 epoch 后进行评估。此部分数据不会用于权重更新过程。 -
shuffle
:这表示是否在每个 epoch 前对训练数据进行洗牌。
要将模型拟合到训练数据,可以通过以下方式将 fit
方法应用于模型:
model.fit(x=features, y=target, epochs=10, batch_size=32, \
validation_split=0.2, shuffle=False)
一旦调用了 fit
方法,模型将开始拟合训练数据。每个 epoch 后,会返回训练的损失。如果定义了验证集,则验证集的损失也会被评估。
模型评估
一旦模型训练完成,可以通过使用模型的 evaluate
方法来评估模型。evaluate
方法根据用于训练模型的损失函数以及传递给模型的任何指标来评估模型的性能。该方法最适合用来确定模型在新数据上的表现,可以通过传入未在训练过程中使用的特征和目标数据集,或者是超出样本的数据集来实现。该方法可以按如下方式调用:
eval_metrics = model.evaluate(features, target)
该方法的结果首先是对输入数据计算的损失,然后,如果在模型编译过程中传递了任何指标,它们也将在执行 evaluate
方法时计算。模型评估是确定模型表现如何的关键步骤。由于存在大量的超参数(例如隐藏层的数量、每层的单元数量以及激活函数的选择等),模型评估是确定哪些超参数组合最优的必要步骤。有效的模型评估有助于提供一个无偏的视角,帮助确定哪种模型架构在整体上表现最佳。
在以下练习中,您将进行创建 ANN、编译模型、将模型拟合到训练数据以及最终在训练数据上评估模型的过程。您将使用 ANN 重现线性回归算法,这可以被理解为一个只有一层和一个单元的 ANN。此外,您将通过 TensorBoard 查看模型的架构和模型训练过程。
练习 4.02:使用 TensorFlow 创建一个线性回归模型作为 ANN
在本练习中,您将使用 TensorFlow 创建一个线性回归模型作为 ANN。数据集 Bias_correction_ucl.csv
描述了韩国首尔空气温度预报的偏差修正。该数据字段表示给定日期的温度测量、测量数据的气象站、与天气相关的模型预报(如湿度)以及次日温度的预测。您需要根据先前时间点的测量值和气象站的属性来预测接下来的最大和最小温度。
注意
Bias_correction_ucl.csv
文件可以在这里找到:packt.link/khfeF
。
执行以下步骤以完成本次练习:
-
打开一个新的 Jupyter notebook 来实现本次练习。
-
在新的 Jupyter Notebook 单元格中,导入 TensorFlow 和 pandas 库:
import tensorflow as tf import pandas as pd
-
使用 pandas 的
read_csv
函数加载数据集:df = pd.read_csv('Bias_correction_ucl.csv')
注意
确保您根据系统中 CSV 文件的位置更改路径(高亮显示的部分)。如果您从与 CSV 文件存储在同一目录的 Jupyter notebook 中运行代码,您可以在不做任何修改的情况下运行之前的代码。
-
删除
date
列,并删除所有包含空值的行,因为您的模型只需要数值数据:df.drop('Date', inplace=True, axis=1) df.dropna(inplace=True)
-
创建目标和特征数据集。目标数据集将包含名为
Next_Tmax
和Next_Tmin
的列,而特征数据集将包含除Next_Tmax
和Next_Tmin
外的所有列:target = df[['Next_Tmax', 'Next_Tmin']] features = df.drop(['Next_Tmax', 'Next_Tmin'], axis=1)
-
对特征数据集进行重缩放:
from sklearn.preprocessing import MinMaxScaler scaler = MinMaxScaler() feature_array = scaler.fit_transform(features) features = pd.DataFrame(feature_array, columns=features.columns)
-
初始化一个 Keras 的
Sequential
类模型:model = tf.keras.Sequential()
-
使用模型的
add
方法为模型添加输入层,并将input_shape
设置为特征数据集中的列数:model.add(tf.keras.layers.InputLayer\ (input_shape=(features.shape[1],), \ name='Input_layer'))
-
将
Dense
类的输出层添加到模型中,大小为2
,表示两个目标变量:model.add(tf.keras.layers.Dense(2, name='Output_layer'))
-
使用 RMSprop 优化器和均方误差损失函数编译模型:
model.compile(tf.optimizers.RMSprop(0.001), loss='mse')
-
为 TensorBoard 添加回调:
tensorboard_callback = tf.keras.callbacks\ .TensorBoard(log_dir="./logs")
-
将模型拟合到训练数据:
model.fit(x=features.to_numpy(), y=target.to_numpy(),\ epochs=50, callbacks=[tensorboard_callback])
你应该会得到以下输出:
图 4.4:拟合过程的输出,显示每个 epoch 的训练时间和损失
-
在训练数据上评估模型:
loss = model.evaluate(features.to_numpy(), target.to_numpy()) print('loss:', loss)
这将产生以下输出:
loss: 3.5468221449764012
-
通过在命令行中调用以下命令,在 TensorBoard 上查看模型架构和拟合过程:
tensorboard –-logdir=logs/
你可以通过访问启动 TensorBoard 后提供的 URL,在网页浏览器中查看其执行情况。默认提供的 URL 是
http://localhost:6006/
:
图 4.5:TensorBoard 中模型架构的可视化表示
损失函数可以通过以下图形进行可视化:
图 4.6:TensorBoard 中表示损失与每个 epoch 的关系的可视化图
你可以在 GRAPHS
标签页中看到模型的架构。架构显示了模型的输入层和输出层,以及计算出的损失。在模型拟合过程中,损失在每个 epoch 后都会被计算,并显示在 TensorBoard 的 SCALARS
标签页中。损失是在编译过程中定义的;在这个例子中,损失是均方误差。从 TensorBoard 中,你可以看到均方误差在每个 epoch 后减少,这表明模型正在从训练数据中学习,并且正在更新权重以减少总损失。
在本次练习中,你学习了如何使用 Keras 层通过 TensorFlow 创建、训练和评估一个 ANN。你通过创建一个包含输入层和输出层的 ANN 重建了线性回归算法,每个输出都有一个单元。在这里,有两个输出,分别表示温度的最大值和最小值;因此,输出层有两个单元。
在 练习 4.01,使用 TensorFlow 创建人工神经网络(ANN) 中,你创建了一个仅包含一个层(带有权重)和输出层的 ANN。这是一个 浅层神经网络 的例子。具有多个隐藏层且包含权重的 ANN 被称为 深度神经网络,而训练它们的过程称为 深度学习。通过增加层数并使 ANN 更深,模型变得更加灵活,能够建模更复杂的函数。然而,为了获得这种灵活性,你需要更多的训练数据和更强的计算能力来训练模型。
在下一个练习中,你将创建并训练具有多个隐藏层的 ANN。
练习 4.03:使用 TensorFlow 创建多层 ANN
在这个练习中,你将使用 TensorFlow 创建一个多层人工神经网络(ANN)。这个模型将有四个隐藏层。你将向模型中添加多个层,并为每一层的输出添加激活函数。第一个隐藏层将有16
个单元,第二个隐藏层将有8
个单元,第三个隐藏层将有4
个单元。输出层将有2
个单元。你将使用与练习 4.02相同的数据集,使用 TensorFlow 创建线性回归模型作为 ANN,该数据集描述了韩国首尔的气温预报偏差修正。该练习旨在根据先前时间点的测量值和气象站的属性,预测下一个最大和最小气温。
执行以下步骤以完成此练习:
-
打开一个新的 Jupyter 笔记本以实现此练习。
-
在新的 Jupyter Notebook 单元格中,导入 TensorFlow 和 pandas 库:
import tensorflow as tf import pandas as pd
-
使用 pandas 的
read_csv
函数加载数据集:df = pd.read_csv('Bias_correction_ucl.csv')
注意
确保你根据文件在系统上的位置更改路径(高亮显示的部分)。如果你是从与 CSV 文件相同的目录运行 Jupyter 笔记本,你可以不做任何修改直接运行前面的代码。
-
删除
Date
列并删除任何包含空值的行:df.drop('Date', inplace=True, axis=1) df.dropna(inplace=True)
-
创建目标和特征数据集:
target = df[['Next_Tmax', 'Next_Tmin']] features = df.drop(['Next_Tmax', 'Next_Tmin'], axis=1)
-
重新缩放特征数据集:
from sklearn.preprocessing import MinMaxScaler scaler = MinMaxScaler() feature_array = scaler.fit_transform(features) features = pd.DataFrame(feature_array, columns=features.columns)
-
初始化一个
Sequential
类的 Keras 模型:model = tf.keras.Sequential()
-
使用模型的
add
方法为模型添加一个输入层,并将input_shape
设置为特征数据集中的列数:model.add(tf.keras.layers.InputLayer\ (input_shape=(features.shape[1],), \ name='Input_layer'))
-
向模型中添加三个隐藏层和一个
Dense
类的输出层。第一个隐藏层将有16
个单元,第二个隐藏层将有8
个单元,第三个隐藏层将有4
个单元。为各个层适当命名。输出层将有两个单元,以匹配具有两列的目标变量:model.add(tf.keras.layers.Dense(16, name='Dense_layer_1')) model.add(tf.keras.layers.Dense(8, name='Dense_layer_2')) model.add(tf.keras.layers.Dense(4, name='Dense_layer_3')) model.add(tf.keras.layers.Dense(2, name='Output_layer'))
-
使用 RMSprop 优化器和均方误差损失编译模型:
model.compile(tf.optimizers.RMSprop(0.001), loss='mse')
-
为 TensorBoard 添加一个回调:
tensorboard_callback = tf.keras.callbacks\ .TensorBoard(log_dir="./logs")
-
将模型拟合到训练数据,训练
50
个周期,并添加一个验证拆分,比例为 20%:model.fit(x=features.to_numpy(), y=target.to_numpy(),\ epochs=50, callbacks=[tensorboard_callback] , \ validation_split=0.2)
你应该得到以下输出:
图 4.7:拟合过程的输出,显示每个周期、每个样本的训练时间以及每个周期后的损失
-
在训练数据上评估模型:
loss = model.evaluate(features.to_numpy(), target.to_numpy()) print('loss:', loss)
这将显示以下结果:
loss: 1.664448248190068
-
在 TensorBoard 中查看模型架构和模型拟合过程:
tensorboard --logdir=logs/
你应该得到如下所示的结果:
图 4.8:TensorBoard 中模型架构的可视化表示
你可以可视化损失函数,如下截图所示:
图 4.9:在训练和验证集划分上,损失随 epoch 变化的可视化表示
网络架构显示了模型的输入层和四个隐藏层,以及最终计算的损失。在模型拟合过程中,每个 epoch 后都会计算损失,并显示在 TensorBoard 的SCALARS
选项卡中。在这里,损失是均方误差。从 TensorBoard 中,你可以看到每个 epoch 后,训练集(橙色线)和验证集(蓝色线)的均方误差在逐渐减少,表明模型正在有效地从训练数据中学习。
在本次练习中,你已经创建了一个具有多个隐藏层的 ANN。你获得的损失比使用线性回归时的损失要低,这展示了 ANN 的强大能力。通过调整超参数(例如,改变层数、每层的单元数、添加激活函数以及改变损失和优化器),损失可能会更低。在下一次活动中,你将把你的模型构建技能应用到一个新的数据集上。
活动 4.01:使用 TensorFlow 创建多层感知器(ANN)
特征数据集superconductivity.csv
包含超导体的属性,包括材料的原子质量和密度。重要的是,数据集还包含材料的临界温度,即材料表现出超导特性的温度。在本次活动中,你的任务是找出材料的临界温度,或者说材料获得超导特性的温度。
注意
superconductivity.csv
文件可以在这里找到:packt.link/sOCPh
。
执行以下步骤完成本次活动:
-
打开一个新的 Jupyter 笔记本来实现本次活动。
-
导入 TensorFlow 和 pandas 库。
-
加载
superconductivity.csv
数据集。 -
删除包含空值的行。
-
将目标设置为
critical_temp
列,将特征数据集设置为其余列。 -
使用标准缩放器对特征数据集进行缩放。
-
初始化一个 Keras
Sequential
类的模型。 -
向模型添加一个输入层,四个隐藏层,大小分别为
64
、32
、16
和8
,以及一个大小为1
的输出层。为第一个隐藏层添加 ReLU 激活函数。 -
使用 RMSprop 优化器编译模型,学习率为
0.001
,并使用均方误差作为损失函数。 -
添加回调函数,将日志写入 TensorBoard。
-
将模型拟合到训练数据,训练
100
个 epochs,批量大小为32
,验证集划分比例为 20%。 -
在训练数据上评估模型。
-
在 TensorBoard 中查看模型架构。
你应该得到类似以下的输出:
图 4.10:在 TensorBoard 中,模型架构的可视化表示
-
在 TensorBoard 中可视化模型拟合过程。你应该会得到如下输出:
图 4.11:在 TensorBoard 中,损失随训练和验证拆分的轮次变化的可视化表示
注意
本活动的解决方案可以通过此链接找到。
在下一部分,你将探索分类模型,这些模型尝试将数据分类为不同的类别。你将从二分类模型开始,它将数据分类为仅两个类别。这是最简单的分类模型形式。一旦掌握了二分类器,就可以挑战更复杂的模型,如多标签分类和多类分类。
分类模型
分类模型的目标是将数据分类到不同的类别中。例如,垃圾邮件过滤器就是一个分类模型,它旨在将电子邮件分类为“垃圾邮件”(指未经请求的、不需要的邮件)或“正常邮件”(合法邮件)。垃圾邮件过滤器是一个二分类模型的例子,因为它有两个类别。输入到过滤器的内容可能包括电子邮件的内容、发件人的电子邮件地址和主题行等特征,输出将是预测的类别,垃圾邮件
或正常邮件
。分类模型可以将数据分类为两个以上的类别(称为多类分类),或者对数据进行多个正标签的分类(称为多标签分类)。
有几种不同的算法可以用于分类任务。一些流行的算法包括逻辑回归、决策树和人工神经网络(ANN)。ANN 是分类模型的一个不错选择,因为它们能够学习特征与目标之间的复杂关系,并且通过在 ANN 输出层应用适当的激活函数可以获得结果。
常用的分类模型激活函数是 sigmoid 函数,它与逻辑回归中使用的函数相同。事实上,可以通过构建一个具有单层、一个单元和 sigmoid 激活函数的 ANN 来创建一个逻辑回归模型。Sigmoid 函数是一种转换,其中输入是任意实数值,输出是一个严格介于 0
和 1
之间的数值。以下图示为例:
Sigmoid 转换的输出可以被解释为一个值属于正类的概率;接近 1
的值表示属于正类的概率较高:
图 4.12:Sigmoid 函数的可视化表示
在应用了 sigmoid 函数后,会应用一个阈值,高于该阈值的数据将被归类为正类,低于该阈值的数据将被归类为负类。sigmoid 函数的默认阈值为 0.5
,意味着任何值大于或等于 0.5
的数据将被归类为正类。
在下一个练习中,你将使用 TensorFlow 创建一个逻辑回归模型。你将通过创建一个单层人工神经网络(ANN)来实现这一点,这一过程类似于 练习 4.02 中的线性回归模型,使用 TensorFlow 创建线性回归模型作为 ANN。不同之处在于,你将在 ANN 的输出端添加一个 sigmoid 激活函数。另一个将这两个练习区分开的差异是你将使用不同的损失函数来计算损失。
练习 4.04:使用 TensorFlow 创建逻辑回归模型作为 ANN
在本练习中,你将使用 TensorFlow 将逻辑回归模型创建为 ANN。数据集 qsar_androgen_receptor.csv
用于开发分类模型,以区分具有不同分子属性的结合/非结合分子。在这里,分子属性代表数据集的特征,其结合属性代表目标变量,其中正值代表结合分子,负值代表非结合分子。你将创建一个逻辑回归模型,根据数据集中提供的分子属性预测分子的结合特性。
注意
qsar_androgen_receptor.csv
文件可以在这里找到:packt.link/hWvjc
。
执行以下步骤来完成本练习:
-
打开一个新的 Jupyter notebook 来实现本练习。
-
导入 TensorFlow 和 pandas 库:
import tensorflow as tf import pandas as pd
-
使用 pandas 的
read_csv
函数加载数据集:df = pd.read_csv('qsar_androgen_receptor.csv', \ sep=';')
注意
确保根据 CSV 文件在系统上的位置更改路径(已突出显示)。如果你从存储 CSV 文件的同一目录运行 Jupyter notebook,则无需修改代码即可运行上述代码。
-
丢弃任何包含空值的行:
df.dropna(inplace=True)
-
创建目标和特征数据集:
target = df['positive'].apply(lambda x: 1 if x=='positive' else 0) features = df.drop('positive', axis=1)
-
初始化一个 Keras
Sequential
类的模型:model = tf.keras.Sequential()
-
使用模型的
add
方法为模型添加输入层,并将input_shape
设置为特征数据集中的列数:model.add(tf.keras.layers.InputLayer\ (input_shape=(features.shape[1],), \ name='Input_layer'))
-
将
Dense
类的输出层添加到模型中,大小为1
,表示目标变量:model.add(tf.keras.layers.Dense(1, name='Output_layer', \ activation='sigmoid'))
-
使用 RMSprop 优化器和二元交叉熵损失函数编译模型,并计算准确率:
model.compile(tf.optimizers.RMSprop(0.0001), \ loss='binary_crossentropy', metrics=['accuracy'])
-
创建一个 TensorBoard 回调:
tensorboard_callback = tf.keras.callbacks.TensorBoard\ (log_dir="./logs")
-
通过训练数据训练模型
50
个 epoch,并添加 TensorBoard 回调,验证集比例为 20%:model.fit(x=features.to_numpy(), y=target.to_numpy(), \ epochs=50, callbacks=[tensorboard_callback] , \ validation_split=0.2)
你的输出应该类似于下图:
图 4.13:拟合过程的输出,显示每个 epoch、每个样本的训练时间,以及每个 epoch 后的损失
-
在训练数据上评估模型:
loss, accuracy = model.evaluate(features.to_numpy(), \ target.to_numpy()) print(f'loss: {loss}, accuracy: {accuracy}')
你应该得到类似如下的输出:
loss: 0.2781583094794838, accuracy: 0.9110320210456848
-
通过在命令行中调用以下命令,在 TensorBoard 中可视化模型拟合过程:
tensorboard --logdir=logs/
你应该在浏览器中看到类似如下的界面:
图 4.14:在 TensorBoard 中的模型架构的可视化表示
损失函数可以表示为如下形式:
图 4.15:在 TensorBoard 中评估的训练和验证分割上,以一个 epoch 为函数的损失和准确度的可视化表示
从 TensorBoard 中可以看到,通过在模型编译过程中添加 metrics
参数,模型架构中有一个额外的节点用于计算准确度指标。此外,在 SCALARS
标签页中,还显示了训练和验证分割的准确度指标,作为一个 epoch 的函数。
从图表中可以看出,对于训练集,准确度随着时间的推移增加,损失减少,这表明模型正在学习。然而,在验证集上,准确度开始下降,损失开始增加,这表明模型可能正在过拟合训练数据。
在这个练习中,你学会了如何构建一个分类模型,用于区分不同分子在其其他分子属性基础上的结合特性。该分类模型等同于逻辑回归模型,因为它只有一层,并且之前有一个 sigmoid 激活函数。由于只有一层,每个输入特征都有一个权重,并且偏置只有一个值。sigmoid 激活函数将该层的输出转换为介于 0
和 1
之间的值,然后通过四舍五入表示你的两类。0.5
及以上代表一类,具有结合特性的分子,而低于 0.5
代表另一类,即没有结合特性的分子。
接下来的活动将通过结合你在练习 4.03(使用 TensorFlow 创建多层感知机)、活动 4.01(使用 TensorFlow 创建多层感知机)中学到的创建多层人工神经网络(ANN)的知识,以及你在练习 4.04(使用 TensorFlow 创建逻辑回归模型作为 ANN)中学到的创建分类模型的知识,来总结你在本章的学习。你将使用与前一个活动相同的数据集,但会改变目标变量,使其更适合分类任务。
活动 4.02:使用 TensorFlow 创建多层分类 ANN
特征数据集superconductivity.csv
包含超导体的属性,包括材料的原子质量和密度。重要的是,数据集还包含材料的临界温度,即材料展现超导性特性的温度。你需要确定哪些超导体在氮气的沸点(77.36 K)以上展现超导性,从而使得可以使用易得的液氮进行超导性实验。目标变量将在临界温度高于 77.36 K 时为true
,低于该温度时为false
,表示材料是否在氮气的沸点以上展现超导性。
注意事项
superconductivity.csv
文件可以在此找到:packt.link/sOCPh
。
执行以下步骤来完成此活动:
-
打开一个 Jupyter Notebook 来完成活动。
-
导入 TensorFlow 和 pandas 库。
-
加载
superconductivity.csv
数据集。 -
删除所有包含空值的行。
-
当
critical_temp
列的值高于77.36
时,将目标值设置为true
,低于时设置为false
。特征数据集是数据集中的其余列。 -
使用标准化缩放器对特征数据集进行重新缩放。
-
初始化一个 Keras
Sequential
类的模型。 -
向模型中添加一个输入层,三个隐藏层,大小分别为
32
、16
、8
,以及一个输出层,使用sigmoid
激活函数,大小为1
。 -
使用 RMSprop 优化器编译模型,学习率为
0.0001
,损失函数为二元交叉熵,并计算准确率指标。 -
添加一个回调函数,将日志写入 TensorBoard。
-
将模型拟合到训练数据中,训练
50
个周期,验证集分割为 0%。 -
在训练数据上评估模型。
-
在 TensorBoard 中查看模型架构和模型拟合过程。
注意事项
本活动的解决方案可以通过此链接找到。
在本节中,你已经开始了使用 TensorFlow 构建、训练和评估分类模型的探索。你已经看到它们与回归任务的 ANN 构建方式非常相似,主要的区别在于输出层的激活函数。
总结
在本章中,你开始了在 TensorFlow 中创建人工神经网络(ANNs)的旅程。你了解了利用 Keras 层创建回归和分类模型是多么简单。Keras 层是一个独立的类库,在后台使用 TensorFlow。由于其流行和易用性,它们现在已经包含在 TensorFlow 中,并可以像调用任何其他 TensorFlow 类一样进行调用。
你创建了具有全连接层的人工神经网络(ANN),并且通过不同的层级进行变动,首先是一个类似于线性回归算法的 ANN,这相当于一个单层 ANN。然后,你向 ANN 中添加了更多层,并在各层的输出上添加了激活函数。激活函数可以用来判断某个单元是否激活,或者用来绑定某个单元输出的值。回归模型的目标是根据提供的数据预测一个连续变量。在本章的练习和活动中,你尝试预测了首尔的温度,给定来自气象站的数据,并预测了超导材料的临界温度,基于不同的材料特性。
最后,你探讨了分类模型,这些模型旨在将数据分类到不同的类别中。这些模型与回归模型在设置方式上相似;然而,在最终输出上使用激活函数,将输出值限制在两个数字之间,这两个数字表示数据点是否被分类到某个类别中。你从二分类模型开始,这些模型旨在将数据分为两个类别,并通过一个练习演示了二分类的概念,在该练习中,你根据分子其他属性的特征将分子分类到代表其结合特性的类别中。
在下一章,你将更深入地探讨分类模型。你将学习一些分类模型的细节和功能,包括如何对具有多个类别的数据进行分类(即多类分类),以及数据点是否可以有多个正标签(即多标签分类)。你将探讨如何构建架构来创建这些模型,训练时使用的适当损失函数,以及如何计算相关的度量指标来评估模型的表现。
第五章:5. 分类模型
概述
在本章中,您将探索不同类型的分类模型。您将获得使用 TensorFlow 构建二元分类、多类别分类和多标签分类器的实际经验。最后,您将学习模型评估的概念,并了解如何使用不同的指标来评估模型的表现。
到本章结束时,您将对分类模型及使用 TensorFlow 编程有一个良好的理解。
介绍
在上一章中,您学习了回归问题,其中目标变量是连续的。连续变量可以取最小值和最大值之间的任何值。您已经学会了如何使用 TensorFlow 训练这样的模型。
在本章中,您将研究另一种监督学习问题,称为分类问题,其中目标变量是离散的——意味着它只能取有限数量的值。在行业中,您最有可能遇到这样的项目,其中变量被聚合为不同的组,如产品层次、用户类别、客户类别或薪资范围。分类器的目标是从数据中学习模式,并预测观察值的正确类别。
例如,在贷款提供商的情况下,分类模型将尝试根据客户的资料和财务状况预测他们在来年违约的可能性。该结果只能取两个可能的值(是
或否
),这就是二元分类。另一个分类器模型可能会根据用户之前的评分和这部新电影的信息,预测用户对该电影的评分,评分范围为 1 到 5。当结果可以取两个以上的值时,您正在处理多类别分类。最后,还有第三种类型的分类器,称为多标签分类,其中模型将预测多个类别。例如,模型将分析一张输入图像,并预测图像中是否有猫、狗或老鼠。在这种情况下,模型将预测三个不同的二元输出(或标签)。
本章将介绍每种分类器,详细阐述其特点,并探讨如何衡量这些模型的表现。
二元分类
如前所述,二元分类指的是一种监督学习类型,其中目标变量只能取两个可能的值(或类别),例如真/假或是/否。例如,在医疗行业中,您可能想根据患者的个人信息(如年龄、身高、体重和/或医疗测量值)来预测他们是否更有可能患病。同样,在营销领域,广告商可能利用类似的信息来优化电子邮件活动。
诸如随机森林分类器、支持向量分类器或逻辑回归等机器学习算法在分类任务上表现良好。神经网络也可以为二元分类实现良好的结果。将回归模型转换为二元分类器非常简单,只需要两个关键的变化:最后一层的激活函数和损失函数。
逻辑回归
0
和1
。值0
通常对应于false
(或no
),而值1
则指true
(或yes
)。
换句话说,逻辑回归的输出将是其为真的概率。例如,如果输出为0.3
,则可以说结果为真(或 yes)的概率为 30%。但由于只有两个可能的值,这也意味着有 70%的概率(100% - 30%)结果为假(或 no):
图 5.1: 逻辑回归的输出
现在您已经了解了逻辑回归的输出是什么,您只需找到一个可以将连续输入值转换为0
和1
之间值的函数。幸运的是,这样的数学函数确实存在,称为sigmoid 函数。该函数的公式如下:
图 5.2: Sigmoid 函数的公式
对应于应用于
x
的指数函数。指数函数的取值范围从0
到正无穷。因此,如果x
的值接近正无穷,sigmoid 的值将趋向于1
。另一方面,如果x
非常接近负无穷,则 sigmoid 的值将趋向于0
:
图 5.3: Sigmoid 函数的曲线
因此,将线性回归模型的输出应用于 sigmoid 函数将其转换为逻辑回归。对于神经网络也是同样的逻辑:如果您将 sigmoid 函数应用于感知器模型(线性回归),则将获得二元分类器。要实现这一点,只需在感知器模型的最后一个全连接层中指定 sigmoid 作为激活函数。在 TensorFlow 中,您可以将activation
参数指定为:
from tensorflow.keras.layers import Dense
Dense(1, activation='sigmoid')
前面的代码片段展示了如何定义一个具有单个单元的全连接层,该单元可以输出任何值,并对其应用 sigmoid 激活函数。结果将在0
和1
之间。现在,您已经了解如何修改神经网络的回归模型以将其转变为二元分类器,需要指定相关的损失函数。
二元交叉熵
在上一节中,你学习了如何将一个线性回归模型转换为二分类器。使用神经网络,只需在最后的全连接层上添加 sigmoid 激活函数即可。但还有一个影响该模型训练的因素:损失函数的选择。
对于线性回归,最常用的损失函数是均方误差和平均绝对误差,如在第四章《回归与分类模型》中所示。这些函数会计算预测值与实际值之间的差异,神经网络模型会在反向传播过程中相应地更新所有权重。对于二分类问题,典型的损失函数是二元交叉熵(也叫做对数损失)。该函数的公式如下:
图 5.4:二元交叉熵公式
代表观测
i
的实际值。
代表观测
i
的预测概率。
N
代表观测的总数。
这个公式看起来相当复杂,但其逻辑非常简单。考虑以下单个观测的例子:实际值为 1
,预测概率为 0.8
。如果应用上述公式,结果将如下:
请注意,方程式右侧的值大约为零:
因此,当预测值与实际值非常接近时,损失值会非常小。
现在考虑另一个例子,其中实际值为 0
,预测概率为 0.99
。结果如下:
在这种情况下,损失会很高,因为预测值与实际值差异很大。
BinaryCrossentropy
用于计算这个损失:
from tensorflow.keras.losses import BinaryCrossentropy
bce = BinaryCrossentropy()
二分类架构
二分类器的架构与线性回归非常相似,如在第四章《回归与分类模型》中所示。它由一个输入层组成,该层读取输入数据集的每个观测值,一个输出层负责预测响应变量,以及一些隐藏层,学习导致正确预测的模式。以下图示展示了这种架构的一个例子:
图 5.5:二分类器的架构
与线性回归的唯一区别是输出,它是一个介于0
和1
之间的概率值。这个概率值表示其中一个可能值发生的概率。如前所述,这通过使用 sigmoid 激活函数和二元交叉熵进行反向传播来实现。
现在你已经看到了构建二分类器的所有元素,你可以通过一个练习将它付诸实践。
练习 5.01:构建逻辑回归模型
在这个练习中,你将使用 TensorFlow 构建并训练一个逻辑回归模型,预测 Dota 2 比赛中哪个队伍获胜,使用的输入信息包括游戏的模式和类型等。
你将使用 Dota 2 数据集进行工作。Dota 2 是一款流行的电脑游戏。该数据集包含与游戏相关的信息,目标变量指示哪个队伍获胜。
注意
训练数据集可以在这里访问:packt.link/Tdvdj
。
测试数据集可以在这里访问:packt.link/4PsPN
。
原始数据集可以在这里找到:archive.ics.uci.edu/ml/datasets/Dota2+Games+Results
。
-
打开一个新的 Jupyter 笔记本。
-
导入 pandas 库并使用
pd
作为别名:import pandas as pd
-
创建一个名为
train_url
的变量,包含训练集的 URL:train_url = 'https://raw.githubusercontent.com/PacktWorkshops'\ '/The-TensorFlow-Workshop/master/Chapter05'\ '/dataset/dota2Train.csv'
-
使用
read_csv()
方法将训练数据集加载到一个名为X_train
的DataFrame()
函数中,提供 CSV 文件的 URL,并将header=None
设置为数据集没有列名。使用head()
方法打印 DataFrame 的前五行数据:X_train = pd.read_csv(train_url, header=None) X_train.head()
期望的输出如下所示:
图 5.6:Dota 2 训练集的前五行数据
你可以看到数据集包含 117 列,且它们都是数值型的。还需要注意的是,目标变量(第
0
列)包含两个不同的值:-1
和1
。由于你将训练一个逻辑回归模型,可能的值应该是0
和1
。你需要将-1
的值替换为0
。 -
使用
pop()
方法提取目标变量(第 0 列),并将其保存在名为y_train
的变量中:y_train = X_train.pop(0)
-
使用
replace()
方法将目标变量中的所有-1
值替换为0
,并使用head()
方法打印前五行数据:y_train = y_train.replace(-1,0) y_train.head()
期望的输出如下所示:
图 5.7:Dota 2 训练集中目标变量的前五行数据
现在,训练集的目标变量中的所有值都是
0
或1
。 -
创建一个名为
test_url
的变量,包含测试集的 URL:test_url = 'https://raw.githubusercontent.com/PacktWorkshops'\ '/The-TensorFlow-Workshop/master/Chapter05/dataset'\ '/dota2Test.csv'
-
使用
read_csv()
方法加载测试数据集到名为X_test
的DataFrame()
函数中,提供 CSV 文件的 URL,并设置header=None
,因为数据集没有提供列名。使用head()
方法打印前五行:X_test = pd.read_csv(test_url, header=None) X_test.head()
预期的输出如下:
图 5.8:Dota 2 测试集的前五行
测试集与训练集非常相似,你需要对其进行相同的转换。
-
使用
pop()
方法提取目标变量(第 0 列),并将其保存为名为y_test
的变量:y_test = X_test.pop(0)
-
使用
replace()
方法将目标变量中的所有-1
值替换为0
,并使用head()
方法打印前五行:y_test = y_test.replace(-1,0) y_test.head()
预期的输出如下:
图 5.9:Dota 2 测试集中的目标变量前五行
-
导入 TensorFlow 库并使用
tf
作为别名:import tensorflow as tf
-
使用
tf.random.set_seed()
设置 TensorFlow 的种子为8
,以获得可重复的结果:tf.random.set_seed(8)
-
使用
tf.keras.Sequential()
实例化一个顺序模型,并将其保存为名为model
的变量:model = tf.keras.Sequential()
-
从
tensorflow.keras.layers
导入Dense()
类:from tensorflow.keras.layers import Dense
-
使用
Dense()
创建一个包含512
个单元的全连接层,指定 ReLu 作为激活函数,并将输入形状设置为(116,)
,这对应于数据集中的特征数量。将其保存为名为fc1
的变量:fc1 = Dense(512, input_shape=(116,), activation='relu')
-
使用
Dense()
创建一个包含512
个单元的全连接层,并指定 ReLu 作为激活函数。将其保存为名为fc2
的变量:fc2 = Dense(512, activation='relu')
-
使用
Dense()
创建一个包含128
个单元的全连接层,并指定 ReLu 作为激活函数。将其保存为名为fc3
的变量:fc3 = Dense(128, activation='relu')
-
使用
Dense()
创建一个包含128
个单元的全连接层,并指定 ReLu 作为激活函数。将其保存为名为fc4
的变量:fc4 = Dense(128, activation='relu')
-
使用
Dense()
创建一个包含128
个单元的全连接层,并指定 sigmoid 作为激活函数。将其保存为名为fc5
的变量:fc5 = Dense(1, activation='sigmoid')
-
使用
add()
方法顺序添加所有五个全连接层到模型中:model.add(fc1) model.add(fc2) model.add(fc3) model.add(fc4) model.add(fc5)
-
使用
summary()
方法打印模型的摘要:model.summary()
预期的输出如下:
图 5.10:模型架构摘要
上述输出显示了模型中有五个层(如预期)并展示了每层的参数数量。例如,第一层包含 59,904 个参数,该模型的总参数数量为 404,855。所有这些参数将在训练模型时进行训练。
-
从
tf.keras.losses
实例化一个BinaryCrossentropy()
函数,并将其保存为名为loss
的变量:loss = tf.keras.losses.BinaryCrossentropy()
-
从
tf.keras.optimizers
实例化Adam()
,学习率设置为0.001
,并将其保存为名为optimizer
的变量:optimizer = tf.keras.optimizers.Adam(0.001)
-
使用
compile()
函数编译模型,并指定在前面步骤中创建的优化器和损失函数:model.compile(optimizer=optimizer, loss=loss)
-
使用
fit()
方法在训练集上进行五个 epoch 的模型训练:model.fit(X_train, y_train, epochs=5)
预期输出如下:
图 5.11:训练过程的日志
上述输出显示了每个 epoch 训练过程中记录的日志。请注意,处理单个 epoch 大约需要 15 秒,且损失值从
0.6923
(第一 epoch)降到0.6650
(第五 epoch),这表明模型通过减少二元交叉熵损失正在缓慢改善其性能。 -
使用
predict()
方法预测测试集的结果。将结果保存到名为preds
的变量中,并显示其前五个值:preds = model.predict(X_test) preds[:5]
预期输出如下:
图 5.12:测试集前五行的预测结果
上述输出显示了每个预测的概率。小于
0.5
的值会被分类为0
(该输出中的第一和最后一个观察结果),而大于或等于0.5
的值会被分类为1
(第二到第四个观察结果)。 -
显示测试集前五个真实标签:
y_test[:5]
预期输出如下:
图 5.13:测试集前五行的真实标签
将该输出与模型在测试集前五行上的预测结果进行比较,发现存在一些错误值:第三个预测(索引为2
)应为0
,最后一个预测应为0
。因此,在这五个观察值中,二分类器犯了两个错误。
在接下来的部分中,你将看到如何使用不同的指标正确评估模型的性能。
分类器的评估指标
在前一部分中,你学习了如何训练一个二分类器来预测正确的输出:0
或1
。在练习 5.01中,构建逻辑回归模型,你查看了几个样本以评估所构建模型的性能。通常,你会使用像准确率或 F1 分数这样的性能指标对模型进行评估,而不仅仅是对一个小子集进行评估,而是对整个数据集进行评估。
准确率和空白准确率
其中一个最广泛使用的分类问题评估指标是准确率。它的公式非常简单:
图 5.14:准确率指标的公式
准确率的最大值为1
,表示模型正确预测了 100%的案例。其最小值为0
,表示模型无法正确预测任何案例。
对于二分类器,正确预测的数量是指那些值为0
或1
的观察结果,且这些值为正确预测的值:
图 5.15:二分类器的准确度指标公式
假设你正在评估两个不同的二分类器在测试集上对 10,000 个观察结果的预测表现。第一个模型正确预测了 5,000 个值为 0
的实例和 3,000 个值为 1
的实例。其准确度得分将如下:
图 5.16:模型 1 准确度公式
第二个模型正确预测了 500 个值为 0
的案例和 1,500 个值为 1
的案例。其准确度得分将如下:
图 5.17:模型 2 准确度公式
第一个模型在 80% 的情况下预测正确,而第二个模型仅在 20% 的情况下预测正确。在这种情况下,你可以说模型 1 比模型 2 更好。
尽管0.8
通常是一个相对较好的分数,但这并不一定意味着你的模型表现良好。例如,假设你的数据集中包含 9,000 个值为 0
的案例和 1,000 个值为 1
的案例。一个非常简单的模型,如果总是预测值为 0
,将会获得 0.9 的准确度。在这种情况下,第一个模型的表现甚至比这个极其简单的模型差。这种总是预测数据集中最频繁值的模型特性被称为 0.9
,因为简单模型预测 0
,它在 90% 的情况下是正确的。
注意
准确度和空准确度指标不仅适用于二分类,还可以应用于其他类型的分类。
TensorFlow 提供了一个类 tf.keras.metrics.Accuracy
,可以从张量中计算准确度得分。该类有一个名为 update_state()
的方法,它接受两个张量作为输入参数,并计算它们之间的准确度得分。你可以通过调用 result()
方法来访问此得分。输出结果将是一个张量。你可以使用 numpy()
方法将其转换为 NumPy 数组。以下是如何计算准确度得分的示例:
from tensorflow.keras.metrics import Accuracy
preds = [1, 1, 1, 1, 0, 0]
target = [1, 0, 1, 0, 1, 0]
acc = Accuracy()
acc.update_state(preds, target)
acc.result().numpy()
这将导致以下准确度得分:
0.5
注意
TensorFlow 并没有为空准确度指标提供一个类,但你可以通过使用 Accuracy()
,并提供一个仅包含 1
(或 0
)的预测张量,轻松计算它。
精确度、召回率和 F1 分数
在上一节中,你学习了如何使用准确度指标来评估模型的表现,并将其与称为“空准确度”的基准进行比较。准确度得分被广泛使用,因为它对于非技术性听众来说是熟知的,但它也有一些局限性。考虑以下示例。
图 5.18:模型预测与实际值的示例
该模型的准确度得分为 0.981!,这个值相当高。但如果这个模型用于预测一个人是否患有某种疾病,它只会在一个案例中预测正确。在其他九个案例中,它错误地预测这些人没有得病,而实际上他们有该疾病。同时,它错误地预测了 10 个实际上健康的人为患病。由此可见,这个模型的表现显然不令人满意。不幸的是,准确率得分只是一个整体得分,它并不能告诉你模型表现不好的地方。
幸运的是,其他指标可以更好地评估模型,比如精确度、召回率或 F1 分数。这三项指标的取值范围与准确率得分相同:1
表示完美得分,所有观察值都预测正确,0
是最差得分,意味着没有任何正确的预测。
但在查看它们之前,你需要了解以下定义:
-
真阳性(TP):所有实际值和相应预测都为真(即正类)的观察值
-
真负(TN):所有实际值和相应预测都为假(即负类)的观察值
-
假阳性(FP):所有预测为真,但实际值为假的观察值
-
假阴性(FN):所有预测为假,但实际值为真的观察值
以 图 5.18 为例,你将得到以下结果:
-
TP = 1
-
TN = 980
-
FP = 10
-
FN = 9
这在下表中可以看到:
图 5.19:TP、TN、FP 和 FN 示例
精确度得分是一个评估模型是否预测了大量假阳性的指标。其公式如下:
图 5.20:精确度公式
在前面的例子中,精确度得分为 。你可以看到该模型犯了很多错误,并且预测了大量的假阳性(FP),而实际的真阳性(TP)则相对较少。
召回率用于评估假阴性(FN)与真阳性(TP)之比。其公式如下:
图 5.21:召回率公式
在前面的例子中,召回率得分为 。通过这个指标,你可以看到模型表现不好,预测了大量的假阴性(FN)。
最后,F1 分数是一个结合了精确度和召回率的指标(它是精确度和召回率的调和平均数)。其公式如下:
图 5.22:F1 分数的公式
以与前述相同的例子,F1 分数将为
模型实现了0.095
的 F1 分数,这与其0.981
的准确度分数非常不同。因此,当你希望强调不正确的预测时,F1 分数是一个很好的性能指标——该分数考虑了 FN 和 FP 的数量,以及 TP 和 TN。
注意
与准确度、精确度和召回率性能指标一样,F1 分数也可应用于其他类型的分类。
你可以通过使用Precision()
和Recall()
的相应类来轻松计算 TensorFlow 中的精确度和召回率:
from tensorflow.keras.metrics import Precision, Recall
preds = [1, 1, 1, 1, 0, 0]
target = [1, 0, 1, 0, 1, 0]
prec = Precision()
prec.update_state(preds, target)
print(f"Precision: {prec.result().numpy()}")
rec = Recall()
rec.update_state(preds, target)
print(f"Recall: {rec.result().numpy()}")
这导致以下输出:
图 5.23:提供示例的精确度和召回率分数
注意
TensorFlow 并未提供用于计算 F1 分数的类,但可以通过创建自定义指标来轻松实现这一功能。这将在练习 5.02,分类评估指标中介绍。
混淆矩阵
混淆矩阵本身并不是一个性能指标,而更多是用于可视化模型预测与实际值之间关系的图形工具。在前一节中,你已经看到了图 5.18 的一个示例。
混淆矩阵将显示预测值(例如,水平轴)和实际值(例如,垂直轴)的所有可能值。在每个预测和实际值组合的交点处,你将记录属于此情况的观察数。
对于二元分类,混淆矩阵如下所示:
图 5.24:二元分类的混淆矩阵
理想情况是所有值都位于该矩阵的对角线上。这意味着你的模型正确预测了所有可能的值。在对角线之外的所有值是模型犯错的地方。
注意
混淆矩阵也可用于多类分类,不仅限于二元分类。
运行下面的代码查看混淆矩阵:
from tensorflow.math import confusion_matrix
preds = [1, 1, 1, 1, 0, 0]
target = [1, 0, 1, 0, 1, 0]
print(confusion_matrix(target, preds))
这将显示以下输出:
图 5.25:TensorFlow 混淆矩阵
前述输出显示了混淆矩阵。从中可以看出,模型预测了以下结果:两个 TP、一个 TN、两个 FP 和一个 FN。
在下一练习中,你将应用这些性能指标到与练习 5.01,构建逻辑回归模型相同的逻辑回归模型。
练习 5.02:分类评估指标
在本练习中,你将重复使用与练习 5.01,构建逻辑回归模型相同的逻辑回归模型,并通过查看不同的性能指标来评估其性能:准确度、精确度、召回率和 F1 分数。
原始数据集由悉尼大学的 Stephen Tridgell 分享。
注意
训练数据集可以通过以下链接访问:packt.link/QJGpA
。
测试数据集可以通过以下链接访问:packt.link/ix5rW
。
练习 5.01中的模型,构建逻辑回归模型,可以通过以下链接找到:packt.link/sSRQL
。
现在,运行以下指令:
-
打开一个新的 Jupyter 笔记本。
-
导入 pandas 库,并使用
pd
作为别名:import pandas as pd
-
创建一个名为
train_url
的变量,包含训练集的 URL:train_url = 'https://raw.githubusercontent.com/PacktWorkshops'\ '/The-TensorFlow-Workshop/master/Chapter05/dataset'\ '/dota2PreparedTrain.csv'
-
使用
read_csv()
方法将训练数据集加载到名为X_train
的DataFrame()
函数中,提供 CSV 文件的 URL,并设置header=None
,因为数据集没有提供列名:X_train = pd.read_csv(train_url, header=None)
-
使用
pop()
方法提取目标变量(第0
列),并将其保存在名为y_train
的变量中:y_train = X_train.pop(0)
-
创建一个名为
test_url
的变量,包含测试集的 URL:test_url = 'https://raw.githubusercontent.com/PacktWorkshops'\ '/The-TensorFlow-Workshop/master/Chapter05/dataset'\ '/dota2PreparedTest.csv'
-
使用
read_csv()
方法将测试数据集加载到名为X_test
的DataFrame()
函数中,提供 CSV 文件的 URL,并设置header=None
,因为数据集没有提供列名:X_test = pd.read_csv(test_url, header=None)
-
使用
pop()
方法提取目标变量(第0
列),并将其保存在名为y_test
的变量中:y_test = X_test.pop(0)
-
使用
tf
作为别名导入tensorflow
库,并从tensorflow.keras.utils
导入get_file()
方法:import tensorflow as tf from tensorflow.keras.utils import get_file
-
创建一个名为
model_url
的变量,包含模型的 URL:model_url = 'https://github.com/PacktWorkshops'\ '/The-TensorFlow-Workshop/blob/master/Chapter05'\ 'model/exercise5_01_model.h5?raw=true'
-
使用
get_file()
方法,通过提供文件名(exercise5_01_model.h5
)及其 URL 将模型下载到本地。将输出保存到一个名为model_path
的变量中:model_path = get_file('exercise5_01_model.h5', model_url)
-
使用
tf.keras.models.load_model()
加载模型,并指定模型的本地路径:model = tf.keras.models.load_model(model_path)
-
使用
summary()
方法打印模型摘要:model.summary()
预期的输出如下:
图 5.26:模型总结
上述输出展示了与练习 5.01中相同的架构,即构建逻辑回归模型。
-
使用
predict()
方法预测测试集的结果。将其保存在一个名为preds_proba
的变量中,并显示其前五个值:preds_proba = model.predict(X_test) preds_proba[:5]
预期的输出如下:
图 5.27:测试集的预测概率
输出为每个观察值为
1
(或为真)的预测概率。你需要将这些概率转换为0
和1
。为此,你需要将所有概率大于或等于0.5
的情况视为1
(或为真),而对于概率小于0.5
的记录则视为0
(或为假)。 -
当预测概率大于或等于
0.5
时,将其转换为1
,当小于0.5
时转换为0
。将结果保存在名为preds
的变量中,并打印其前五行:preds = preds_proba >= 0.5 preds[:5]
预期的输出如下:
图 5.28:测试集的预测结果
现在,预测结果已转换为二值:真(等于
1
)和假(等于0
)。 -
从
tensorflow.keras.metrics
导入Accuracy
、Precision
和Recall
:from tensorflow.keras.metrics import Accuracy, Precision, Recall
-
实例化
Accuracy
、Precision
和Recall
对象,并将其保存在名为acc
、pres
和rec
的变量中:acc = Accuracy() prec = Precision() rec = Recall()
-
使用
update_state()
、result()
和numpy()
方法计算测试集上的准确度分数。将结果保存在名为acc_results
的变量中并打印其内容:acc.update_state(preds, y_test) acc_results = acc.result().numpy() acc_results
预期的输出如下:
0.59650314
该模型的准确率为
0.597
。 -
使用
update_state()
、result()
和numpy()
方法计算测试集上的精确度分数。将结果保存在名为prec_results
的变量中并打印其内容:prec.update_state(preds, y_test) prec_results = prec.result().numpy() prec_results
预期的输出如下:
0.59578335
该模型的精确度为
0.596
。 -
使用
update_state()
、result()
和numpy()
方法计算测试集上的召回率分数。将结果保存在名为rec_results
的变量中并打印其内容:rec.update_state(preds, y_test) rec_results = rec.result().numpy() rec_results
预期的输出如下:
0.6294163
该模型的召回率为
0.629
。 -
使用上一节中显示的公式计算 F1 分数。将结果保存在名为
f1
的变量中并打印其内容:f1 = 2*(prec_results * rec_results) / (prec_results + rec_results) f1
预期的输出如下:
0.6121381493171637
总体而言,模型在准确率、精确度、召回率和 F1 分数这四个不同指标上都取得了接近
0.6
的较低分数。因此,该模型的正确预测和错误预测几乎一样多。你可以尝试自己构建另一个模型,看看是否能提高其表现。
在接下来的部分,你将学习如何通过多类分类将分类扩展到超过两个可能值。
多类分类
使用二分类时,你只能处理目标变量,它只能取两个可能的值:0
和1
(假或真)。多类分类可以看作是二分类的扩展,它允许目标变量拥有超过两个值(或者可以说,二分类是多类分类的一个子集)。例如,预测患者疾病严重程度的模型,或者根据用户过去的购物行为将其分类到不同组的模型,都属于多类分类器。
在下一节中,你将深入了解 Softmax 函数,它用于多类分类。
Softmax 函数
二分类器需要为神经网络的最后一个全连接层使用特定的激活函数,即 sigmoid。多类分类器的激活函数不同,它是 softmax。其公式如下:
图 5.29:Softmax 函数公式
对应于类别
i
的预测值。
对应于类别
j
的预测值。
此公式将应用于目标变量的每个可能值。如果有 10 个可能的值,那么该激活函数将计算 10 个不同的 softmax 值。
注意,softmax 会对分子和分母上的预测值进行指数运算。其背后的原因是,指数函数放大了预测值之间的微小变化,并使得概率更接近0
或1
,以便更好地解释最终的输出。例如,exp(2) = 7.39
,而exp(2.2) = 9.03
。因此,如果两个类别的预测值非常接近,它们的指数化值之间的差异将变得更大,从而更容易选择较大的一个。
softmax 函数的结果介于0
和1
之间,因为该方法将一个类别的值除以所有类别值的总和。因此,softmax 函数的实际输出是相关类别为最终预测结果的概率:
图 5.30:Softmax 转换示例
在前面的示例中,目标变量有五个不同的值,softmax 函数将它们转换为概率。第一个类别(0
)是具有最高概率的类别,这将是最终的预测结果。
类别交叉熵
多类分类还需要一个特定的损失函数,这与二分类器使用的二元交叉熵不同。对于多类分类,损失函数是类别交叉熵。其公式如下:
图 5.31:类别交叉熵的公式
表示观察值
i
属于类别j
的实际值的概率。
表示观察值
i
属于类别j
的预测概率。
TensorFlow 提供了两个不同的类别来计算这个损失函数:CategoricalCrossentropy()
和 SparseCategoricalCrossentropy()
:
from tensorflow.keras.losses import CategoricalCrossentropy,
SparseCategoricalCrossentropy
cce = CategoricalCrossentropy()
scce = SparseCategoricalCrossentropy()
它们之间的区别在于目标变量的格式。如果实际值以独热编码存储,表示实际类别,则需要使用 CategoricalCrossentropy()
。另一方面,如果响应变量以整数形式存储,表示实际类别,则需要使用 SparseCategoricalCrossentropy()
:
图 5.32:根据目标变量的格式使用的损失函数
多类模型的输出将是一个包含每个类别概率的向量,如下所示:
import numpy as np
preds_proba = np.array([0.54, 0.16, 0.09, 0.15, 0.06])
第一个值(0.54
)对应于索引 0 类别的概率,0.016
是索引 1 类别的概率,而 0.09
对应于索引 2 类别的概率,依此类推。
为了得到最终的预测(即具有最高概率的类别),你需要使用 argmax()
函数,它会查看向量中的所有值,找到最大值并返回与其相关的索引:
preds_proba.argmax()
这将显示以下输出:
0
在前面的例子中,最终的预测是 类别 0
,这对应于具有最高概率(0.54
)的向量索引。
多类分类架构
多类分类器的架构与逻辑回归非常相似,区别在于最后一层将包含更多的单元。每个单元对应于目标变量的一个类。例如,如果你正在构建一个输入向量大小为 6 并使用单一隐藏层预测具有三种不同值的响应的模型,则其架构如下所示:
图 5.33:多类分类器的架构
最后一层的 softmax 激活函数为每个可能类别提供了发生的概率:A
、B
和 C
。这些概率是相互依赖的,因为最终应该只预测一个类别。如果类别 A
更有可能成为预测(如前面的例子所示),那么其余类别(B
和 C
)的概率应该较低。请注意,所有类别的概率总和为 1
,因此它们确实是相互依赖的。
现在你已经了解了所有的构建模块,可以在接下来的练习中构建一个多类分类器。
练习 5.03:构建一个多类模型
在本练习中,你将使用 TensorFlow 构建并训练一个多类分类器,该分类器将使用数据集中提供的九个不同的数值特征,从八个不同的值预测航天飞机的散热器位置。
目标变量(最后一列)包含七个不同的级别:Rad.Flow
、Fpv.Close
、Fpv.Open
、High
、Bypass
、Bpv.Close
和 Bpv.Open
。你的目标是使用数据集中提供的九个特征准确预测这七个级别中的一个。
注意
训练数据集可以通过以下链接访问:packt.link/46iMY
。
测试数据集可以通过以下链接访问:packt.link/dcNPt
。
原始数据集可以通过以下链接找到:archive.ics.uci.edu/ml/datasets/Statlog+%28Shuttle%29
。
执行以下步骤以完成练习:
-
打开一个新的 Jupyter 笔记本。
-
导入 pandas 库并使用
pd
作为别名:import pandas as pd
-
创建一个名为
train_url
的变量,包含训练集的 URL:train_url = 'https://raw.githubusercontent.com/PacktWorkshops'\ '/The-TensorFlow-Workshop/master/Chapter05'\ '/dataset/shuttle.trn'
-
使用
read_table()
方法将训练数据集加载到名为X_train
的 DataFrame 中,提供 CSV 文件的 URL,使用header=None
因为数据集没有列名,使用sep=' '
因为数据集中的每一列是由空格分隔的。使用head()
方法打印前五行:X_train = pd.read_table(train_url, header=None, sep=' ') X_train.head()
预期的输出将如下所示:
图 5.34:训练集的前五行
可以看到数据集包含 10 列,且都是数字类型。另外,注意目标变量(第
9
列)包含不同的类别值。 -
使用
pop()
方法提取目标变量(第9
列),并将其保存在名为y_train
的变量中:y_train = X_train.pop(9)
-
创建一个名为
test_url
的变量,包含测试集的 URL:test_url = 'https://raw.githubusercontent.com/PacktWorkshops'\ '/The-TensorFlow-Workshop/master/Chapter05/dataset'\ '/shuttle.tst'
-
使用
read_table()
方法将测试数据集加载到名为X_test
的 DataFrame 中,提供 CSV 文件的 URL,设置header=None
因为数据集没有列名,使用sep=' '
因为数据集中的每一列是由空格分隔的。使用head()
方法打印前五行。X_test = pd.read_table(test_url, header=None, sep=' ') X_test.head()
预期的输出将如下所示:
图 5.35:测试集的前五行
可以看到测试集与训练集非常相似。
-
使用
pop()
方法提取目标变量(第9
列),并将其保存在名为y_test
的变量中:y_test = X_test.pop(9)
-
导入 TensorFlow 库并使用
tf
作为别名:import tensorflow as tf
-
使用
tf.random.set_seed()
将 TensorFlow 的种子设置为8
,以获得可重现的结果:tf.random.set_seed(8)
-
使用
tf.keras.Sequential()
实例化一个顺序模型,并将其保存在一个名为model
的变量中:model = tf.keras.Sequential()
-
从
tensorflow.keras.layers
导入Dense()
类:from tensorflow.keras.layers import Dense
-
使用
Dense()
创建一个包含512
单元的全连接层,并指定 ReLu 作为激活函数,同时将输入形状设置为(9,)
,这对应于数据集中的特征数量。将其保存在一个名为fc1
的变量中:fc1 = Dense(512, input_shape=(9,), activation='relu')
-
使用
Dense()
创建一个包含512
单元的全连接层,并指定 ReLu 作为激活函数。将其保存在一个名为fc2
的变量中:fc2 = Dense(512, activation='relu')
-
使用
Dense()
创建一个包含128
单元的全连接层,并指定 ReLu 作为激活函数。将其保存在一个名为fc3
的变量中:fc3 = Dense(128, activation='relu')
-
再次使用
Dense()
创建一个包含128
单元的全连接层,并指定 ReLu 作为激活函数。将其保存在一个名为fc4
的变量中:fc4 = Dense(128, activation='relu')
-
使用
Dense()
创建一个包含 128 单元的全连接层,并指定 softmax 作为激活函数。将其保存在一个名为fc5
的变量中:fc5 = Dense(8, activation='softmax')
-
顺序地将所有五个全连接层添加到模型中,使用
add()
方法。model.add(fc1) model.add(fc2) model.add(fc3) model.add(fc4) model.add(fc5)
-
使用
summary()
方法打印模型的摘要:model.summary()
预期的输出将如下所示:
图 5.36:模型架构的总结
上述输出显示了模型中的五个层(如预期),并告知每个层的参数数量。例如,第一层包含
5,120
个参数,该模型的总参数数量为350,984
。所有这些参数将在拟合模型时进行训练。 -
从
tf.keras.losses
中实例化SparseCategoricalCrossentropy()
,并将其保存在名为loss
的变量中:loss = tf.keras.losses.SparseCategoricalCrossentropy()
-
从
tf.keras.optimizers
中实例化Adam()
,将学习率设置为0.001
,并将其保存在名为optimizer
的变量中:optimizer = tf.keras.optimizers.Adam(0.001)
-
使用
compile()
方法编译模型,指定优化器和损失参数,并将准确度作为需要报告的度量:model.compile(optimizer=optimizer, loss=loss, \ metrics=['accuracy'])
-
使用
fit()
方法在训练集上开始模型训练过程,训练五个 epochs:model.fit(X_train, y_train, epochs=5)
预期输出如下所示:
图 5.37:训练过程的日志
上述输出显示了训练过程中每个 epoch 的日志。请注意,处理一个 epoch 大约需要 7 秒,损失值从
0.5859
(第一 epoch)降至0.0351
(第五 epoch)。 -
使用
evaluate()
方法评估模型在测试集上的表现:model.evaluate(X_test, y_test)
预期输出如下所示:
图 5.38:模型在测试集上的表现
在这个练习中,你学习了如何构建和训练一个多类分类器,来预测由八个不同类别组成的结果。你的模型在训练集和测试集上的准确率接近0.997
,这非常了不起。这意味着你的模型在大多数情况下能正确预测类别。
现在,让我们在下面的活动中巩固你的学习。
活动 5.01:使用 TensorFlow 构建字符识别模型
在这个活动中,你的任务是构建和训练一个多类分类器,该分类器能够识别图像中的 26 个字母。在这个数据集中,图像已经转换为 16 个不同的统计特征,这些特征将作为我们的输入。该模型的目标是确定每个观测值属于 26 个字母中的哪一个。
原始数据集由 Odesta 公司 David J. Slate 分享,数据集可以在此找到:archive.ics.uci.edu/ml/datasets/Letter+Recognition
。
数据集可以从这里访问:packt.link/j8m3L
。
以下步骤将帮助你完成该活动:
-
使用 pandas 的
read_csv()
加载数据。 -
使用 pandas 的
pop()
方法提取目标变量。 -
将数据分为训练集(前 15,000 行)和测试集(后 5,000 行)。
-
使用五个完全连接的层,分别为
512
、512
、128
、128
和26
个单元,来构建多类分类器。 -
在训练集上训练这个模型。
-
使用
evaluate()
方法从 TensorFlow 评估模型在测试集上的表现。 -
使用
confusion_matrix()
方法从 TensorFlow 打印混淆矩阵。预期的输出如下:
图 5.39:测试集的混淆矩阵
注意
该活动的解决方案可以通过这个链接找到。
多标签分类
多标签分类是另一种分类类型,在这种类型中,你不仅预测一个目标变量(如二元分类或多类分类),而是同时预测多个响应变量。例如,你可以预测图像中不同物体的多个输出(例如,模型将预测给定图片中是否有猫、男人和汽车),或者你可以预测文章的多个主题(例如,文章是否涉及经济、国际新闻和制造业)。
使用神经网络实现多标签分类非常简单,你已经学会了构建它所需的一切。在 TensorFlow 中,多标签分类器的架构与多类分类器相同,最终的输出层有多个单元,对应于你要预测的目标变量的数量。但不同的是,你将使用 sigmoid 作为激活函数,binary cross-entropy 作为损失函数,而不是使用 softmax 和 categorical cross-entropy。
Sigmoid 函数将预测每个目标变量的发生概率:
图 5.40:多标签分类器的架构
在前面的示例中,你有三个目标变量,每个目标变量的发生概率是相互独立的(它们的和不会等于 1)。该模型预测目标2
和3
很可能是这个观测的输出。
从概念上讲,多标签分类结合了多个逻辑回归模型。它们将共享相同的参数(权重和偏差),但每个模型有独立的二进制输出。TensorFlow 中多类分类器的最后一层将像这样:
from tensorflow.keras.layers import Dense
Dense(3, activation='sigmoid')
将要使用的损失函数是二元交叉熵:
from tensorflow.keras.losses import BinaryCrossentropy
bce = BinaryCrossentropy()
现在,将你所学到的知识付诸实践,在以下活动中动手操作。
活动 5.02:使用 TensorFlow 构建电影类型标签模型
在这个活动中,你的任务是构建并训练一个多标签分类器,该分类器将预测电影的类型,涵盖 28 种可能的类别。每部电影可以同时被分配到多个类型。特征是从电影简介中提取的主要关键词。该活动使用的数据集是原始数据集的一个子集,包含了 20,000 行数据。
原始数据集由 IMDb 提供,可以从这里找到:www.uco.es/kdis/mllresources/#ImdbDesc
。
数据集的特征可以从这里访问:packt.link/yW5ru
。
数据集的目标可以从这里访问:packt.link/8f1mb
。
以下步骤将帮助你完成该活动:
-
使用 pandas 的
read_csv()
加载特征和目标。 -
将数据分为训练集(前 15,000 行)和测试集(后 5,000 行)。
-
构建一个多类别分类器,包含五个全连接层,分别是
512
、512
、128
、128
和28
个单元。 -
在训练集上训练此模型。
-
使用 TensorFlow 的
evaluate()
方法在测试集上评估其性能。预期的输出如下:
图 5.41:活动 5.02 的预期输出
注意
这个活动的解决方案可以通过此链接找到。
总结
本章开始时,你介绍了分类模型,并与回归模型进行了对比。你了解到,分类器的目标变量只能包含有限数量的可能值。
接着你探索了二元分类,其中响应变量只能取两个可能的值:0
或 1
。你深入了解了如何使用 TensorFlow 构建逻辑回归模型,采用 sigmoid 激活函数和二元交叉熵作为损失函数,并为预测视频游戏《Dota 2》中的获胜队伍构建了自己的二元分类器。
之后,你了解了可以用来评估分类器模型性能的不同指标。你练习了使用 TensorFlow 计算准确率、精确度、召回率和 F1 分数,并绘制了混淆矩阵,这是一个用来查看模型正确与错误预测的可视化工具。
接着你深入探讨了多类别分类的主题。这类模型与二元分类器的区别在于,它们的响应变量可以有两个以上的可能值。你研究了 softmax 激活函数和类别交叉熵损失函数,它们在 TensorFlow 中用于训练这类模型。
最后一部分,你了解了多标签分类,其中输出可以同时是多个类别。在 TensorFlow 中,这样的模型可以通过构建类似于多类别分类的架构来轻松实现,但分别使用 sigmoid 激活函数和二元交叉熵作为损失函数。
在下一章,你将学习如何通过应用一些正则化技术来防止模型过拟合,从而帮助模型更好地对未见数据进行泛化。
第六章:6. 正则化与超参数调整
概述
在本章中,您将了解超参数调整。您将通过使用 TensorFlow 在深度学习模型上执行正则化来获得实际经验,以减少过拟合。您将探讨 L1、L2 和 dropout 正则化等概念。最后,您将介绍 Keras Tuner 包,用于执行自动超参数调整。
到本章结束时,您将能够应用正则化和调整超参数,以减少过拟合模型的风险并提高其性能。
引言
在上一章中,您学习了分类模型如何在响应变量是离散的情况下解决问题。您还看到了用于评估此类分类器性能的不同指标。您通过使用 TensorFlow 构建和训练二进制、多类别和多标签分类器获得了实际经验。
在评估模型时,您将面临三种不同的情况:模型过拟合、模型欠拟合和模型表现良好。最后一种情况是理想的,即模型准确地预测正确结果并且能够很好地泛化到未见数据上。
如果模型欠拟合,意味着它既没有达到令人满意的性能,也没有准确预测目标变量。在这种情况下,数据科学家可以尝试调整不同的超参数,并找到最佳组合来提升模型的准确性。另一种可能性是改进输入数据集,处理诸如数据清洁度或特征工程等问题。
当模型只能在训练集上表现出色,并在测试集上表现不佳时,我们称其为过拟合。在这种情况下,模型只学习了与训练数据相关的数据模式。正则化有助于降低过拟合的风险。
正则化技术
数据科学家的主要目标是训练一个在训练过程中能够实现高性能并且在未见数据上能够泛化良好的模型。该模型应能够在训练过程中使用的数据和新数据上预测正确的结果。这也是为什么模型总是在测试集上进行评估的原因。这一组数据用作评估模型在生产中输出正确结果能力的代理。
图 6.1: 模型既不过拟合也不欠拟合
在 Figure 6.1 中,线性模型(线)似乎能够相对准确地预测训练集(圆圈)和测试集(三角形)的结果。
但有时模型无法很好地泛化,会对训练集过拟合。在这种情况下,模型在训练集和测试集上的表现会有很大的差异。
图 6.2: 模型过拟合
图 6.2 显示模型(线条)只学会了对训练集(圆圈)进行准确预测,并且在测试集(三角形)上的表现很差。这个模型显然是过拟合的。
幸运的是,有一些正则化技术,数据科学家可以利用这些技术来减少并防止过拟合,相关内容将在以下章节中定义。
L1 正则化
对于深度学习模型,当某些特征的权重高于它们应有的值时,就会发生过拟合。模型过于强调这些特征,因为它认为它们对预测训练集非常重要。不幸的是,这些特征对测试集或任何新数据的相关性较低。正则化技术会惩罚这些权重并减少它们对模型预测的影响。
正则化有多种方式。其中一种方式是将正则化组件添加到成本函数中:
图 6.3: 将正则化组件添加到成本函数
添加这个正则化组件会导致模型的权重变小,因为神经网络在进行前向和反向传播时会尽量减少成本函数的值。
一个非常流行的正则化组件是 L1。它的公式如下:
图 6.4: L1 正则化
是一个超参数,用于定义 L1 正则化的惩罚程度。
W
是模型的权重。使用 L1 正则化时,你将权重的绝对值之和加到模型的损失中。
L1 正则化有时被称为 0
。因此,只有相关特征用于进行预测。
在 TensorFlow 中,你可以使用以下代码片段定义 L1 正则化:
from tensorflow.keras.regularizers import l1
l1_reg = l1(l=0.01)
l
参数对应于 超参数。实例化的 L1 正则化可以被添加到 TensorFlow Keras 的任何层中:
from tensorflow.keras.layers import Dense
Dense(10, kernel_regularizer=l1_reg)
在上面的示例中,你将之前定义的 L1 正则化器添加到了一个具有 10
个单元的全连接层中。
L2 正则化
L2 正则化与 L1 相似,都将正则化组件添加到成本函数中,但它们的公式不同:
图 6.5: L2 正则化
L2 正则化倾向于减少不相关特征的权重。它们会接近 0
,但不会完全为 0
。因此,它减少了这些特征的影响,但不像 L1 那样完全禁用它们。
在 TensorFlow 中,你可以按以下方式定义 L2 正则化:
from tensorflow.keras.regularizers import l2
from tensorflow.keras.layers import Dense
l2_reg = l2(l=0.01)
Dense(20, kernel_regularizer=l2_reg)
在前面的示例中,你定义了一个 L2 正则化器并将其添加到一个具有 20
个单元的全连接层中。
TensorFlow 提供了另一个正则化器类,它结合了 L1 和 L2 正则化器。你可以使用以下代码片段实例化它:
from tensorflow.keras.regularizers
import l1_l2
l1_l2_reg = l1_l2(l1=0.01, l2=0.001)
在前面的示例中,您实例化了 L1 和 L2 正则化器,并将 L1 和 L2 的系数分别指定为 0.01
和 0.001
。您可以观察到,相比 L2,L1 正则化的权重较大。这些值是超参数,可以根据数据集进行微调。
在接下来的练习中,您将通过对模型应用 L2 正则化来实践这一点。
练习 6.01: 使用 L2 正则化器预测 Connect-4 游戏结果
在本练习中,您将使用 TensorFlow 构建并训练两个多分类模型,以预测 Connect-4 游戏中玩家一的结果。
该数据集的每一行包含不同游戏局面的情况和不同的棋盘位置。对于每个局面,模型尝试预测第一个玩家的结果:胜、负或平。第一个模型没有任何正则化,而第二个模型应用了 L2 正则化:
注意
数据集可以通过以下链接访问:packt.link/xysRc
。
原始数据集可以在以下位置找到:archive.ics.uci.edu/ml/datasets/Connect-4
。
-
打开一个新的 Jupyter notebook。
-
导入 pandas 库,并使用
pd
作为别名:import pandas as pd
-
创建一个名为
file_url
的变量,其中包含数据集的 URL:file_url = 'https://raw.githubusercontent.com/PacktWorkshops'\ '/The-TensorFlow-Workshop/master/Chapter06/dataset'\ '/connect-4.csv'
-
使用
read_csv()
函数将数据集加载到名为data
的 DataFrame 中,并提供 CSV 文件的 URL。使用head()
函数打印前五行:data = pd.read_csv(file_url) data.head()
预期的输出将如下所示:
](https://github.com/OpenDocCN/freelearn-dl-pt3-zh/raw/master/docs/tf-ws/img/B16341_06_06.jpg)
图 6.6: 数据集的前五行
上图展示了数据集的前五行。
-
使用
pop()
方法提取目标变量(class
列),并将其保存为名为target
的变量:target = data.pop('class')
-
导入 TensorFlow 库,并使用
tf
作为别名。然后,从tensorflow.keras.layers
中导入Dense
类:import tensorflow as tf from tensorflow.keras.layers import Dense
-
设置种子为
8
,以获得可重复的结果:tf.random.set_seed(8)
-
使用
tf.keras.Sequential()
实例化一个顺序模型,并将其存储在名为model
的变量中:model = tf.keras.Sequential()
-
使用
Dense()
创建一个包含512
个单元的全连接层,并指定 ReLu 作为激活函数,输入形状为(42,)
,对应数据集中的特征数量。将其保存为名为fc1
的变量:fc1 = Dense(512, input_shape=(42,), activation='relu')
-
使用
Dense()
创建三个全连接层,分别包含512
、128
和128
个单元,并指定 ReLu 作为激活函数。将它们分别保存为fc2
、fc3
和fc4
变量:fc2 = Dense(512, activation='relu') fc3 = Dense(128, activation='relu') fc4 = Dense(128, activation='relu')
-
使用
Dense()
创建一个包含三个单元的全连接层(对应类别的数量),并指定 softmax 作为激活函数。将其保存为名为fc5
的变量:fc5 = Dense(3, activation='softmax')
-
使用
add()
方法按顺序将所有五个全连接层添加到模型中:model.add(fc1) model.add(fc2) model.add(fc3) model.add(fc4) model.add(fc5)
-
使用
summary()
方法打印模型的摘要:model.summary()
预期的输出将如下所示:
](https://github.com/OpenDocCN/freelearn-dl-pt3-zh/raw/master/docs/tf-ws/img/B16341_06_07.jpg)
图 6.7:模型架构总结
-
从
tf.keras.losses
实例化SparseCategoricalCrossentropy()
函数,并将其保存为名为loss
的变量:loss = tf.keras.losses.SparseCategoricalCrossentropy()
-
从
tf.keras.optimizers
实例化Adam()
,将学习率设置为0.001
并保存为名为optimizer
的变量:optimizer = tf.keras.optimizers.Adam(0.001)
-
使用
compile()
方法编译模型,并指定你在步骤 14和步骤 15中创建的优化器和损失函数,以及accuracy
作为显示的度量指标:model.compile(optimizer=optimizer, loss=loss, \ metrics=['accuracy'])
-
使用
fit()
方法启动模型训练过程,训练五个周期,并将数据拆分为包含 20% 数据的验证集:model.fit(data, target, epochs=5, validation_split=0.2)
预期输出将如下所示:
图 6.8:训练过程的日志
前面的输出显示模型出现了过拟合。它在训练集上的准确度为
0.85
,但在验证集上的准确度仅为0.58
。现在,训练另一个带有 L2 正则化的模型。 -
创建五个完全连接的层,类似于先前模型的结构,并为
kernel_regularizer
参数指定 L2 正则化器。将正则化器因子设置为0.001
。将这些层保存在五个变量中,分别命名为reg_fc1
、reg_fc2
、reg_fc3
、reg_fc4
和reg_fc5
:reg_fc1 = Dense(512, input_shape=(42,), activation='relu', \ kernel_regularizer=tf.keras.regularizers\ .l2(l=0.1)) reg_fc2 = Dense(512, activation='relu', \ kernel_regularizer=tf.keras.regularizers\ .l2(l=0.1)) reg_fc3 = Dense(128, activation='relu', \ kernel_regularizer=tf.keras.regularizers\ .l2(l=0.1)) reg_fc4 = Dense(128, activation='relu', \ kernel_regularizer=tf.keras.regularizers\ .l2(l=0.1)) reg_fc5 = Dense(3, activation='softmax')
-
使用
tf.keras.Sequential()
实例化一个顺序模型,将其保存在名为model2
的变量中,并使用add()
方法按顺序将所有五个完全连接的层添加到模型中:model2 = tf.keras.Sequential() model2.add(reg_fc1) model2.add(reg_fc2) model2.add(reg_fc3) model2.add(reg_fc4) model2.add(reg_fc5)
-
打印模型摘要:
model2.summary()
预期输出将如下所示:
图 6.9:模型架构总结
-
使用
compile()
方法编译模型,并指定你在步骤 14和步骤 15中创建的优化器和损失函数,以及accuracy
作为显示的度量指标:model2.compile(optimizer=optimizer, loss=loss, \ metrics=['accuracy'])
-
使用
fit()
方法启动模型训练过程,训练五个周期,并将数据拆分为包含 20% 数据的验证集:model2.fit(data, target, epochs=5, validation_split=0.2)
预期输出将如下所示:
图 6.10:训练过程的日志
在加入 L2 正则化后,模型在训练集(0.68
)和测试集(0.58
)上的准确度差异较小。模型不再像之前那样过拟合,但其性能仍不理想。
现在你已经知道如何将 L1 和 L2 正则化应用于神经网络,下一部分将介绍另一种正则化技术,称为丢弃法(dropout)。
丢弃法正则化
与 L1 和 L2 正则化不同,丢弃法是专门针对神经网络的正则化技术。其背后的逻辑非常简单:网络将随机将某些特征的权重置为0
。这将迫使模型依赖于其他本应被忽视的特征,从而提高它们的权重。
图 6.11:神经网络的丢弃法
上述示例展示了一个 dropout 为 50%的架构。这意味着在每次迭代中,模型的 50%的单元被关闭。以下代码片段展示了如何在 TensorFlow 中创建一个 50% dropout 层:
from tensorflow.keras.layers import Dropout
do = Dropout(0.5)
在下一个练习中,你将通过应用 dropout 扩展前面的模型。
练习 6.02:使用 Dropout 预测 Connect-4 游戏结果
在这个练习中,你将使用与练习 6.01相同的数据集,使用 L2 正则化器预测 Connect-4 游戏结果。你将使用 dropout 技术作为正则化器,在 TensorFlow 中构建并训练一个多类别模型,预测 Connect-4 游戏中玩家 1 的类别结果:
注释
数据集可以在此访问:packt.link/0Bo1B
。
原始数据集可以在此找到:archive.ics.uci.edu/ml/datasets/Connect-4
。
-
打开一个新的 Jupyter notebook。
-
导入 pandas 库并使用
pd
作为别名:import pandas as pd
-
创建一个变量
file_url
,用于存储数据集的 URL:file_url = 'https://raw.githubusercontent.com/PacktWorkshops'\ '/The-TensorFlow-Workshop/master/Chapter06/dataset'\ '/connect-4.csv'
-
使用
read_csv()
函数将数据集加载到一个 DataFramedata
中,并提供 CSV 文件的 URL。使用head()
函数打印前五行:data = pd.read_csv(file_url) data.head()
期望的输出如下所示:
图 6.12:数据集的前五行
-
使用
pop()
方法提取目标变量(名为class
的列),并将其保存到一个名为target
的变量中:target = data.pop('class')
-
导入 TensorFlow 库并使用
tf
作为别名。然后,从tensorflow.keras.layers
导入Dense
类:import tensorflow as tf from tensorflow.keras.layers import Dense
-
将种子设置为
8
,以获得可重复的结果:tf.random.set_seed(8)
-
使用
tf.keras.Sequential()
实例化一个顺序模型,并将其存储在一个名为model
的变量中:model = tf.keras.Sequential()
-
创建一个
512
个单元的全连接层,使用Dense()
并指定 ReLu 作为激活函数,输入形状为(42,)
,对应数据集中的特征数量。将其保存为一个名为fc1
的变量:fc1 = Dense(512, input_shape=(42,), activation='relu')
-
创建三个全连接层,分别为
512
、128
和128
个单元,使用Dense()
并指定 ReLu 作为激活函数。将它们分别保存为三个变量,命名为fc2
、fc3
和fc4
:fc2 = Dense(512, activation='relu') fc3 = Dense(128, activation='relu') fc4 = Dense(128, activation='relu')
-
创建一个具有三个单元(对应类别数)的全连接层,使用
Dense()
并指定 softmax 作为激活函数。将其保存为一个名为fc5
的变量:fc5 = Dense(3, activation='softmax')
-
顺序地将所有五个全连接层添加到模型中,每个层之间插入一个
0.75
的 dropout 层,使用add()
方法:model.add(fc1) model.add(Dropout(0.75)) model.add(fc2) model.add(Dropout(0.75)) model.add(fc3) model.add(Dropout(0.75)) model.add(fc4) model.add(Dropout(0.75)) model.add(fc5)
-
打印模型的摘要:
model.summary()
期望的输出如下所示:
图 6.13:模型架构总结
-
从
tf.keras.losses
实例化一个SparseCategoricalCrossentropy()
函数,并将其保存为一个名为loss
的变量:loss = tf.keras.losses.SparseCategoricalCrossentropy()
-
从
tf.keras.optimizers
中实例化Adam()
,学习率设置为0.001
,并将其保存在名为optimizer
的变量中:optimizer = tf.keras.optimizers.Adam(0.001)
-
使用
compile()
方法编译模型,指定优化器和损失函数,并设置accuracy
为要显示的指标:model.compile(optimizer=optimizer, loss=loss, \ metrics=['accuracy'])
-
使用
fit()
方法开始模型训练过程,进行五个 epoch,并将数据分成 20%的验证集:model.fit(data, target, epochs=5, validation_split=0.2)
输出将如下所示:
图 6.14:训练过程的日志
在加入 dropout 后,模型在训练集(0.69
)和测试集(0.59
)之间的准确度分数变得相似。模型的过拟合程度不如之前严重,但其性能仍然不理想。
现在你已经学会了如何将 L1、L2 或 dropout 作为模型的正则化方法。在深度学习中,还有一种非常简单的技术可以避免过拟合,那就是提前停止。
提前停止
神经网络过拟合的另一个原因是训练过程。你训练模型的时间越长,模型就越会试图提升其性能。通过训练更长的时间(更多的 epoch),它最终会开始找到只与训练集相关的模式。在这种情况下,训练集和测试集(或验证集)之间的得分差异将在一定数量的 epoch 后开始增大。
为了防止这种情况发生,当两个数据集之间的差距开始增大时,你可以停止模型训练。这个技术叫做提前停止。
图 6.15:通过提前停止来防止过拟合
上面的图表显示了一个模型在训练集和测试集(或验证集)上的损失值变化,随着 epoch 的增加。在早期的 epoch 中,两个数据集的损失值相差较大。随着训练的进行,模型开始学习到预测相关的模式,并且两者的损失逐渐收敛。但过了一段时间,它们开始分歧。训练集的损失不断下降,而测试集(或验证集)的损失则在增加。你可以观察到模型出现了过拟合,并且只在优化训练集。停止训练,正好在两个损失开始增大时,可以防止模型过拟合。
在 TensorFlow 中,你可以通过设置回调函数来实现这一点,回调函数会分析每个 epoch 结束时模型的表现,并比较训练集和测试集之间的得分。要定义提前停止回调函数,你需要做如下操作:
from tensorflow.keras.callbacks import EarlyStopping
EarlyStopping(monitor='val_accuracy', patience=5)
上面的代码展示了如何实例化一个EarlyStopping
类,该类会监控验证集的准确度得分,并在连续五个 epoch 内没有改进的情况下停止训练过程。
在下一个活动中,你将练习将 L1 和 L2 正则化应用于模型。
活动 6.01:使用 L1 和 L2 正则化器预测收入
census-income-train.csv
数据集包含从美国人口普查局进行的 1994 年和 1995 年现有人口调查中提取的加权人口普查数据。该数据集是美国人口普查局共享的原始数据集的子集。在本次活动中,您的任务是构建并训练一个回归模型,根据个人的人口普查数据预测收入。可以通过以下链接访问数据集:packt.link/G8xFd
。
以下步骤将帮助您完成活动:
-
打开一个新的 Jupyter 笔记本。
-
导入所需的库。
-
创建一个名为
usecols
的列表,包含列名AAGE
、ADTIND
、ADTOCC
、SEOTR
、WKSWORK
和PTOTVAL
。 -
使用
read_csv()
方法加载数据。 -
将数据拆分为训练集(前 15,000 行)和测试集(最后 5,000 行)。
-
构建一个多类分类器,该分类器具有五个全连接层,分别包含
512
、512
、128
、128
和26
个单元。 -
在训练集上训练模型。
预期的输出结果如下:
](https://github.com/OpenDocCN/freelearn-dl-pt3-zh/raw/master/docs/tf-ws/img/B16341_06_16.jpg)
图 6.16:训练过程的日志
注意
本活动的解决方案可以通过此链接找到。
在接下来的部分,您将看到如何调整超参数以获得更好的结果。
超参数调整
之前,您已经了解了如何通过使用不同的正则化技术来处理过拟合模型。这些技术帮助模型更好地泛化到未见过的数据,但正如您所看到的,它们也可能导致性能下降,使得模型欠拟合。
使用神经网络时,数据科学家可以访问不同的超参数,调整它们以改善模型的表现。例如,您可以尝试不同的学习率,看看是否有一个学习率能获得更好的结果,您可以尝试每个隐藏层的不同单元数,或者您可以测试不同的 dropout 比例,看是否能够在过拟合和欠拟合之间实现更好的平衡。
然而,选择一个超参数可能会影响另一个超参数的效果。因此,随着您想调整的超参数和取值数量的增加,测试的组合数量将呈指数增长。如果您需要手动完成这些组合的模型训练,这将需要大量的时间。幸运的是,某些包可以自动扫描您定义的超参数搜索空间,并为您找到最佳的组合。在接下来的部分,您将看到如何使用其中一个包:Keras Tuner。
Keras Tuner
不幸的是,Keras Tuner 包并不包含在 TensorFlow 中。您需要通过运行以下命令手动安装它:
pip install keras-tuner
这个包非常简单易用。需要理解两个概念:超参数和调整器。
超参数是用于定义将在调优器评估的参数的类。你可以使用不同类型的超参数,主要有以下几种:
-
hp.Boolean
:一个选择True
或False
的选项 -
hp.Int
:一个具有整数范围的选择 -
hp.Float
:一个具有小数范围的选择 -
hp.Choice
:一个从可能值列表中选择的选项
以下代码片段展示了如何定义一个超参数 learning_rate
,它只能取四个值中的一个——0.1
、0.01
、0.001
或 0.0001
:
hp.Choice('learning_rate', values = [0.1, 0.01, 0.001, 0.0001])
Keras Tuner 包中的调优器是一种算法,它将查看超参数搜索空间,测试一些组合,并找到给出最佳结果的组合。Keras Tuner 包提供了不同的调优器,在接下来的部分中,你将了解其中的三种:随机搜索、Hyperband 和 贝叶斯优化。
一旦使用你选择的算法定义了超参数,你可以调用 search()
方法来启动在训练集和测试集上的超参数调优过程,如下所示:
tuner.search(X_train, y_train, validation_data=(X_test, y_test))
搜索完成后,你可以通过 get_best_hyperparameters()
获取最佳组合,然后具体查看你定义的某个超参数:
best_hps = tuner.get_best_hyperparameters()[0]
best_hps.get('learning_rate')
最后,hypermodel.build()
方法将使用找到的最佳超参数实例化一个 TensorFlow Keras 模型:
model = tuner.hypermodel.build(best_hps)
就这么简单。现在,让我们来看一下随机搜索调优器。
随机搜索
随机搜索是该包中可用的算法之一。顾名思义,它通过在搜索空间中随机采样来定义待测试的组合。尽管该算法不会测试每一个可能的组合,但随机搜索通常能够提供非常好的结果。
注意
测试每一个搜索空间组合的算法叫做网格搜索。
图 6.17:网格搜索与随机搜索的比较
前面的图示例展示了网格搜索与随机搜索之间的区别。你可以看到,网格搜索将搜索空间划分为网格,并测试每一个组合,但有些可能会导致相同的损失值,从而降低效率。另一方面,随机搜索更高效地覆盖了搜索空间,并帮助找到最优解。
在 Keras Tuner 中,在实例化调优器之前,你需要定义一个模型构建函数,该函数将定义用于训练的 TensorFlow Keras 模型的架构,并设置你希望测试的超参数。以下是一个这样的函数示例:
def model_builder(hp):
model = tf.keras.Sequential()
hp_lr = hp.Choice('learning_rate', \
values = [0.1, 0.01, 0.001, 0.0001])
model.add(Dense(512, input_shape=(100,), activation='relu'))
model.add(Dense(128, activation='relu'))
model.add(Dense(10, activation='softmax'))
loss = tf.keras.losses.SparseCategoricalCrossentropy()
optimizer = tf.keras.optimizers.Adam(hp_lr)
model.compile(optimizer=optimizer, loss=loss, \
metrics=['accuracy'])
return model
在前面的代码片段中,你创建了一个由三个全连接层(512
、128
和 10
单元)组成的模型,该模型将使用分类交叉熵损失函数和 Adam 优化器进行训练。你定义了一个将由 Keras Tuner 评估的 learning_rate
超参数。
一旦定义了模型构建函数,你可以像下面这样实例化一个随机搜索调参器:
import kerastuner as kt
tuner = kt.RandomSearch(model_builder, objective='val_accuracy', \
max_trials=10)
在之前的代码中,你实例化了一个RandomSearch
调参器,它将使用验证准确度作为objective
指标,查看model_builder
函数中定义的模型和超参数,并进行最多10
次试验。
在下一个练习中,你将使用随机搜索找到模型的最佳超参数集。
练习 6.03:使用 Keras Tuner 的随机搜索预测 Connect-4 游戏结果
在这个练习中,你将使用与练习 6.01相同的数据集,使用 L2 正则化器预测 Connect-4 游戏结果。你将构建并训练一个多类模型,使用 Keras Tuner 包通过随机搜索找到 L2 正则化的最佳正则化因子,预测 Connect-4 游戏中玩家 1 的胜负结果:
注意
数据集可以通过以下链接访问:packt.link/aTSbC
。
原始数据集可以在这里找到:archive.ics.uci.edu/ml/datasets/Connect-4
。
-
打开一个新的 Jupyter notebook。
-
导入 pandas 库,并使用
pd
作为别名:import pandas as pd
-
创建一个名为
file_url
的变量,包含数据集的 URL:file_url = 'https://raw.githubusercontent.com/PacktWorkshops'\ '/The-TensorFlow-Workshop/master/Chapter06/dataset'\ '/connect-4.csv'
-
使用
read_csv()
方法将数据集加载到一个名为data
的 DataFrame 中,并提供 CSV 文件的 URL。使用head()
方法打印前五行:data = pd.read_csv(file_url) data.head()
输出将如下所示:
图 6.18:数据集的前五行
-
使用
pop()
方法提取目标变量(名为class
的列),并将其保存在一个名为target
的变量中:target = data.pop('class')
-
从
sklearn.model_selection
导入train_test_split
:from sklearn.model_selection import train_test_split
-
使用
train_test_split()
将数据拆分为训练集和测试集,20%的数据用于测试,42
作为random_state
:X_train, X_test, y_train, y_test = train_test_split\ (data, target, \ test_size=0.2, \ random_state=42)
-
安装
kerastuner
包,然后导入并将其别名设为kt
:!pip install keras-tuner import kerastuner as kt
-
导入 TensorFlow 库,并使用
tf
作为别名。然后,从tensorflow.keras.layers
导入Dense
类:import tensorflow as tf from tensorflow.keras.layers import Dense
-
使用
tf.random.set_seed()
将种子设置为8
,以获得可重复的结果:tf.random.set_seed(8)
-
定义一个名为
model_builder
的函数,该函数将创建一个与练习 6.02相同架构的顺序模型,使用 L2 正则化,但这次为正则化因子提供一个hp.Choice
超参数:def model_builder(hp): model = tf.keras.Sequential() p_l2 = hp.Choice('l2', values = [0.1, 0.01, 0.001, 0.0001]) reg_fc1 = Dense(512, input_shape=(42,), activation='relu', \ kernel_regularizer=tf.keras.regularizers\ .l2(l=hp_l2)) reg_fc2 = Dense(512, activation='relu', \ kernel_regularizer=tf.keras.regularizers\ .l2(l=hp_l2)) reg_fc3 = Dense(128, activation='relu', \ kernel_regularizer=tf.keras.regularizers\ .l2(l=hp_l2)) reg_fc4 = Dense(128, activation='relu', \ kernel_regularizer=tf.keras.regularizers\ .l2(l=hp_l2)) reg_fc5 = Dense(3, activation='softmax') model.add(reg_fc1) model.add(reg_fc2) model.add(reg_fc3) model.add(reg_fc4) model.add(reg_fc5) loss = tf.keras.losses.SparseCategoricalCrossentropy() optimizer = tf.keras.optimizers.Adam(0.001) model.compile(optimizer = optimizer, loss = loss, \ metrics = ['accuracy']) return model
-
实例化一个
RandomSearch
调参器,将val_accuracy
赋值给objective
,并将10
赋值给max_trials
:tuner = kt.RandomSearch(model_builder, objective='val_accuracy', \ max_trials=10)
-
使用
search()
方法在训练集和测试集上启动超参数搜索:tuner.search(X_train, y_train, validation_data=(X_test, y_test))
-
使用
get_best_hyperparameters()
提取最佳的超参数组合(索引0
),并将其保存到一个名为best_hps
的变量中:best_hps = tuner.get_best_hyperparameters()[0]
-
提取
l2
正则化超参数的最佳值,保存到一个名为best_l2
的变量中,并打印其值:best_l2 = best_hps.get('l2') best_l2
你应该得到以下结果:
0.0001
随机搜索找到的
l2
超参数的最佳值是0.0001
。 -
使用
fit()
方法开始模型训练过程,训练五个周期,并使用测试集作为validation_data
:model = tuner.hypermodel.build(best_hps) model.fit(X_train, y_train, epochs=5, \ validation_data=(X_test, y_test))
你将得到以下输出:
图 6.19:训练过程的日志
使用随机搜索调优器,你找到了 L2 正则化的最佳值(0.0001
),该值帮助模型在训练集上达到了 0.83
的准确度,在测试集上达到了 0.81
的准确度。这些得分相比 练习 6.01,使用 L2 正则化预测 Connect-4 游戏结果(训练集 0.69
和测试集 0.59
)有了很大的提升。
在下一部分,你将使用另一种 Keras 调优器,名为 Hyperband。
Hyperband
Hyperband 是 Keras Tuner 包中另一个可用的调优器。像随机搜索一样,它从搜索空间中随机选择候选项,但效率更高。其背后的思想是测试一组组合仅进行一到两次迭代,只保留表现最好的候选项,并对其进行更长时间的训练。因此,算法不会像随机搜索那样浪费时间训练表现不佳的组合,而是直接将它们从下一轮中丢弃。只有那些实现更高性能的组合才会进行更长时间的训练。要实例化 Hyperband 调优器,执行以下命令:
tuner = kt.Hyperband(model_builder, objective='val_accuracy', \
max_epochs=5)
这个调优器以模型构建函数和目标度量作为输入参数,类似于随机搜索。但它需要一个额外的参数max_epochs
,对应于在超参数搜索期间,模型允许训练的最大迭代次数。
练习 6.04:使用 Keras Tuner 的 Hyperband 预测 Connect-4 游戏结果
在这个练习中,你将使用与 练习 6.01,使用 L2 正则化预测 Connect-4 游戏结果 相同的数据集。你将构建并训练一个多分类模型,使用 Keras Tuner 包通过 Hyperband 查找最佳学习率和输入层的单元数,以预测 Connect-4 游戏中玩家 1 的结果:
注意
数据集可以通过此链接访问:packt.link/WLgen
。
原始数据集可以通过以下链接找到:archive.ics.uci.edu/ml/datasets/Connect-4
。
-
打开一个新的 Jupyter notebook。
-
导入 pandas 库并将其别名设为
pd
:import pandas as pd
-
创建一个名为
file_url
的变量,其中包含数据集的 URL:file_url = 'https://raw.githubusercontent.com/PacktWorkshops'\ '/The-TensorFlow-Workshop/master/Chapter06/dataset'\ '/connect-4.csv'
-
使用
read_csv()
方法将数据集加载到名为data
的 DataFrame 中,并提供 CSV 文件的 URL。使用head()
方法打印前五行:data = pd.read_csv(file_url) data.head()
输出结果如下:
图 6.20:数据集的前五行
-
使用
pop()
方法提取目标变量(class
),并将其保存到名为target
的变量中:target = data.pop('class')
-
从
sklearn.model_selection
导入train_test_split
:from sklearn.model_selection import train_test_split
-
使用
train_test_split()
将数据划分为训练集和测试集,20%的数据用于测试,42
作为random_state
:X_train, X_test, y_train, y_test = train_test_split\ (data, target, \ test_size=0.2, \ random_state=42)
-
安装
keras-tuner
包,然后导入并将其别名为kt
:!pip install keras-tuner import kerastuner as kt
-
导入 TensorFlow 库并使用
tf
作为别名,然后从tensorflow.keras.layers
导入Dense
类:import tensorflow as tf from tensorflow.keras.layers import Dense
-
使用
tf.random.set_seed()
将种子设置为8
,以获得可重复的结果:tf.random.set_seed(8)
-
定义一个名为
model_builder
的函数,创建一个与练习 6.02、使用 Dropout 预测 Connect-4 游戏结果相同架构的顺序模型,并应用 L2 正则化,正则化因子为0.0001
。但这次,提供一个超参数hp.Choice
用于学习率(0.01
、0.001
或0.0001
),并使用hp.Int
函数设置输入全连接层单元数(在128
到512
之间,步长为64
):def model_builder(hp): model = tf.keras.Sequential() hp_units = hp.Int('units', min_value=128, max_value=512, \ step=64) reg_fc1 = Dense(hp_units, input_shape=(42,), \ activation='relu', \ kernel_regularizer=tf.keras.regularizers\ .l2(l=0.0001)) reg_fc2 = Dense(512, activation='relu', \ kernel_regularizer=tf.keras.regularizers\ .l2(l=0.0001)) reg_fc3 = Dense(128, activation='relu', \ kernel_regularizer=tf.keras.regularizers\ .l2(l=0.0001)) reg_fc4 = Dense(128, activation='relu', \ kernel_regularizer=tf.keras.regularizers\ .l2(l=0.0001)) reg_fc5 = Dense(3, activation='softmax') model.add(reg_fc1) model.add(reg_fc2) model.add(reg_fc3) model.add(reg_fc4) model.add(reg_fc5) loss = tf.keras.losses.SparseCategoricalCrossentropy() hp_learning_rate = hp.Choice('learning_rate', \ values = [0.01, 0.001, 0.0001]) optimizer = tf.keras.optimizers.Adam(hp_learning_rate) model.compile(optimizer = optimizer, loss = loss, \ metrics = ['accuracy']) return model
-
实例化一个 Hyperband 调优器,并将
val_accuracy
分配给objective
度量,将5
赋值给max_epochs
:tuner = kt.Hyperband(model_builder, objective='val_accuracy', \ max_epochs=5)
-
使用
search()
在训练集和测试集上启动超参数搜索:tuner.search(X_train, y_train, validation_data=(X_test, y_test))
-
使用
get_best_hyperparameters()
提取最佳超参数组合(索引0
),并将其保存到名为best_hps
的变量中:best_hps = tuner.get_best_hyperparameters()[0]
-
提取输入层单元数的最佳值,将其保存在名为
best_units
的变量中,并打印其值:best_units = best_hps.get('units') best_units
你将获得以下输出:
192
Hyperband 找到的输入层单元数的最佳值是
192
。 -
提取学习率的最佳值,将其保存在名为
best_lr
的变量中,并打印其值:best_lr = best_hps.get('learning_rate') best_lr
-
输出将是以下内容:
0.001
Hyperband 找到的学习率超参数的最佳值是
0.001
。 -
使用
fit()
方法开始模型训练过程,训练 5 个 epoch,并使用测试集作为validation_data
:model.fit(X_train, y_train, epochs=5, \ validation_data=(X_test, y_test))
你将获得以下输出:
图 6.21:训练过程的日志
使用 Hyperband 作为调优器,你找到了输入层单元数(192
)和学习率(0.001
)的最佳值。使用这些超参数,最终模型在训练集和测试集上的准确率都达到了0.81
。模型没有过拟合,且达到了令人满意的准确度。
另一个非常流行的调优器是贝叶斯优化,你将在接下来的章节中学习到它。
贝叶斯优化
贝叶斯优化是另一种非常流行的用于自动超参数调整的算法。它使用概率来确定最佳的超参数组合。其目标是从一组超参数中迭代地构建优化目标函数的概率模型。在每次迭代中,概率模型都会根据获得的结果进行更新。因此,与随机搜索和 Hyperband 不同,贝叶斯优化会考虑过去的结果以改善新的结果。下面的代码片段将展示如何在 Keras Tuner 中实例化一个贝叶斯优化器:
tuner = kt.BayesianOptimization(model_builder, \
objective='val_accuracy', \
max_trials=10)
预期参数类似于随机搜索,包括模型构建函数,objective
指标和最大试验次数。
在下一个活动中,你将使用贝叶斯优化来预测一个人的收入。
活动 6.02:使用 Keras Tuner 进行贝叶斯优化预测收入
在这个活动中,你将使用与“活动 6.01”中相同的数据集,“使用 L1 和 L2 正则化预测收入”。你的任务是基于人口普查数据构建和训练一个回归器,以预测个人的收入。你将使用 Keras Tuner 进行自动超参数调整,并找到学习率、输入层单元数和 L2 正则化的最佳组合。
接下来的步骤将帮助你完成活动:
-
用
read_csv()
从 pandas 加载数据。 -
使用
pop()
方法提取目标变量。 -
将数据分割为训练集(前 15,000 行)和测试集(后 5,000 行)。
-
创建包含五个全连接层的多类分类器模型,分别为
512
、512
、128
、128
和26
个单元,并调整三个不同的超参数:学习率(在0.01
到0.001
之间)、输入层单元数(在128
到512
之间,步长为64
)和 L2 正则化(在0.1
、0.01
和0.001
之间)。 -
使用贝叶斯优化找到最佳超参数组合。
-
使用找到的最佳超参数在训练集上训练模型。
预期输出如下:
图 6.22:训练过程的日志
注意
此活动的解决方案可通过此链接找到。
摘要
你在这一章的旅程开始于对模型训练不同场景的介绍。当一个模型在训练集上的表现远远优于测试集时,我们称其为过拟合。欠拟合模型只能在训练后才能取得良好结果。最后,一个好的模型在训练集和测试集上都能表现良好。
接着,你接触了几种可以帮助防止模型过拟合的正则化技术。你首先了解了 L1 和 L2 正则化,它们向成本函数中添加了惩罚项。这一额外的惩罚项有助于通过减少某些特征的权重来简化模型。随后,你学习了两种特定于神经网络的正则化技术:dropout 和早停。Dropout 随机丢弃模型架构中的一些单元,迫使模型考虑其他特征来进行预测。早停是一种机制,一旦测试集的性能开始恶化,便自动停止模型的训练。
在此之后,你学习了如何使用 Keras Tuner 包进行自动超参数调优。你考虑了三种特定类型的调优器:随机搜索、Hyperband 和贝叶斯优化。你了解了如何实例化它们,执行超参数搜索,并提取最佳值和模型。这个过程帮助你在训练的模型上获得了更好的性能,尤其是在练习和活动中。
在下一章中,你将学习更多关于卷积神经网络(CNNs)的内容。这种架构在过去几年中为计算机视觉领域带来了突破性的成果。接下来的章节将向你展示如何使用这种架构识别图像中的物体。
第七章:7. 卷积神经网络
概述
本章你将学习卷积神经网络(CNNs)如何处理图像数据。你还将学习如何在图像数据上正确使用 CNN。
本章结束时,你将能够使用 TensorFlow 为任何图像数据集创建自己的 CNN,用于分类和物体识别。
介绍
本章讲解的是 CNN。CNN 使用卷积层,这些卷积层非常适合从图像中提取特征。它们使用与任务相关的学习滤波器。简单来说,它们非常擅长在图像中找到模式。
在上一章中,你探讨了正则化和超参数调整。你使用了 L1 和 L2 正则化,并为分类模型添加了 dropout,防止在 connect-4
数据集上发生过拟合。
在你深入了解深度学习与 CNN 的过程中,你将经历很大的转变。在本章中,你将学习 CNN 如何处理图像数据,并学会如何将这些概念应用到你自己的图像分类问题中。这里正是 TensorFlow 大显身手的地方。
CNN
CNN 与你迄今为止构建的人工神经网络(ANNs)有许多共同的组成部分。关键的区别是网络中包含了一个或多个卷积层。卷积层通过滤波器,也叫做核,对输入数据进行卷积操作。可以把卷积看作是图像转换器。你有一个输入图像,这个图像经过 CNN 处理后会得到一个输出标签。每一层都有独特的功能或特殊能力,能够在图像中检测出如曲线或边缘等模式。CNN 将深度神经网络的强大功能与核卷积结合,能够转换图像,使得这些图像的边缘或曲线对模型来说更加容易识别。CNN 中有三个关键组件:
-
输入图像:原始图像数据
-
滤波器/核:图像转换机制
-
输出标签:图像分类
以下图示为例,展示了 CNN 的工作原理:图像从左侧输入网络,右侧生成输出。在隐藏层中,图像的组件被识别出来,较早的隐藏层识别的是更基础的组件,如边缘。图像组件在隐藏层中结合,形成来自数据集的可识别特征。例如,在一个 CNN 中,用于将图像分类为飞机或汽车时,可识别的特征可能是类似于轮子或螺旋桨的滤波器。这些特征的组合将帮助判断图像是飞机还是汽车。
最后,输出层是一个密集层,用于确定模型的具体输出。对于二分类模型,这可能是一个带有一个单元的密集层,使用 sigmoid 激活函数。对于更复杂的多类别分类,这可能是一个带有多个单元的密集层,单元的数量由类别数决定,并且使用 softmax 激活函数来为每个输入图像确定一个输出标签。
图 7.1:CNN
一个常见的 CNN 配置包括一个卷积层,后面跟着一个池化层。这些层通常以这种顺序(卷积和池化)一起使用,我们稍后会探讨这样做的原因,但目前,你可以把这些池化层看作是通过总结过滤器的结果来减少输入图像的大小。
在深入了解卷积层之前,首先需要理解计算机视角下的数据是怎样的。
图像表示
首先,考虑计算机如何处理图像。对计算机来说,图像就是数字。为了能够处理图像进行分类或物体识别,你需要理解模型如何将图像输入转化为数据。图像文件中的像素只是数据的一部分。
在下图中,你可以看到一个灰度图像中数字八的像素值示例。对于28x28
像素的图像,共有784
个像素。每个像素的值在0
到255
之间,表示该像素的亮度或暗度。在右侧,有一个大的列向量,其中列出了每个像素的值。这个向量被模型用来识别图像。
图 7.2:像素值
现在你已经知道了输入数据的样子,接下来是时候更仔细地看看卷积过程——更具体地说,是卷积层。
卷积层
把卷积看作是一个图像变换器,包含三个关键元素。首先是输入图像,然后是过滤器,最后是特征图。
本节将依次介绍这些内容,帮助你深入了解图像在卷积层中的过滤过程。卷积是将过滤窗口应用到输入数据的过程,最终得到一个激活图,这个激活图对于二维数据来说是一个3x3
的映射,其中过滤器的具体值在训练过程中被学习到。过滤器以与过滤器大小相同的窗口尺寸在输入数据上滑动,然后计算过滤器与输入数据片段的标量积,得到所谓的激活值。随着该过程在整个输入数据上重复进行,使用相同的过滤器,最终生成激活图,也称为特征图。
这个概念在以下图中得到说明,其中有两个卷积层,产生两组特征图。在第一个卷积层生成特征图后,它们会传递到第二个卷积层。第二个卷积层的特征图将传递到分类器中:
](https://github.com/OpenDocCN/freelearn-dl-pt3-zh/raw/master/docs/tf-ws/img/B16341_07_03.jpg)
图 7.3:用于分类的卷积
滤波器每次操作的移动距离或步数称为0
。这就是有效填充。
让我们回顾一下几个关键词。这里有一个2x2
的卷积核。还有步长,即卷积核每次移动的像素数。最后,有零填充,即在图像周围是否添加像素。这样可以确保输出与输入的尺寸相同。
创建模型
从第一章开始,你接触到过不同类型的维度张量。需要注意的一点是,你将只使用Conv2D
。Conv2D
的层名称仅指卷积核或滤波器的移动。所以,如果你回忆一下卷积过程的描述,它就是简单地在二维空间中滑动卷积核。所以,对于一个平的、方形的图像,卷积核只会在两个维度中滑动。
在实现Conv2D
时,你需要传入一些参数:
-
第一个参数是
filter
。滤波器是输出空间的维度。 -
指定
strides
,即卷积核在每次操作中移动的像素数。 -
然后,指定
padding
,这通常是valid
或same
,取决于你是否希望输出与输入具有相同的维度。 -
最后,你还可以指定
activation
。在这里,你将指定希望应用于输出的激活函数。如果不指定激活函数,它就是一个线性激活。
在继续之前,回顾一下第四章,回归与分类模型,其中提到密集层是每个神经元都与前一层中的每个神经元相连的层。正如你在以下代码中看到的,你可以通过model.add(Dense(32))
轻松添加一个密集层。32
是神经元的数量,后面跟着输入形状。AlexNet是一个 CNN 的例子,具有多个卷积核,从图像中提取有趣的信息。
](https://github.com/OpenDocCN/freelearn-dl-pt3-zh/raw/master/docs/tf-ws/img/B16341_07_04.jpg)
图 7.4:AlexNet 由五个卷积层和三个连接层组成
注意
AlexNet 是由 Alex Krizhevsky 设计的 CNN 的名称。
可以使用顺序模型来构建 CNN。可以使用不同的方法添加层;在这里,我们将使用顺序添加层到模型的框架,通过模型的add
方法或在实例化模型时传入所有层的列表:
model = models.Sequential()
model.add(Dense(32, input_shape=(250,)))
以下是一个代码块,展示了你将在本章后续使用的代码:
our_cnn_model = models.Sequential([layers.Conv2D\
(filters = 32, \
kernel_size = (3,3),
input_shape=(28, 28, 1)), \
layers.Activation('relu'), \
layers.MaxPool2D\
(pool_size = (2, 2)), \
layers.Conv2D\
(filters = 64, \
kernel_size = (3,3)), \
layers.Activation('relu'), \
layers.MaxPool2D\
(pool_size = (2,2)), \
layers.Conv2D\
(filters = 64, \
kernel_size = (3,3)), \
layers.Activation('relu')])
在处理你想在二维中进行卷积的数据(如图像)时,使用Conv2D
层。对于参数,设置滤波器的数量为32
,然后是3x3
像素的内核大小(在示例中是(3, 3)
)。在第一层中,你总是需要指定input_shape
的维度,包括高度、宽度和深度。input_shape
是你将使用的图像的大小。你还可以选择应用于层末端的激活函数。
现在你已经学会了如何在模型中构建 CNN 层,你将在第一个练习中练习此操作。在这个练习中,你将构建 CNN 的第一个构建块,初始化模型,并向模型添加一个卷积层。
练习 7.01:创建第一层以构建 CNN
作为一个 TensorFlow 自由职业者,你被要求向潜在的雇主展示几行代码,演示如何构建 CNN 的第一层。他们要求你保持简洁,但提供创建 CNN 层的前几步。在这个练习中,你将完成创建 CNN 的第一步——即添加第一层卷积层。
按照以下步骤完成本练习:
-
打开一个新的 Jupyter 笔记本。
-
导入 TensorFlow 库以及
tensorflow.keras
中的models
和layers
类:import tensorflow as tf from tensorflow.keras import models, layers
-
检查 TensorFlow 版本:
print(tf.__version__)
你应该会得到以下输出:
2.6.0
-
现在,使用
models.Sequential
创建你的模型。第一层(Conv2D
)将需要节点数(filters
)、滤波器大小(3,3
)以及输入的形状。你第一层的input_shape
将决定输入图像的形状。添加一个 ReLU 激活层:image_shape = (300, 300, 3) our_first_layer = models.Sequential([layers.Conv2D\ (filters = 16, \ kernel_size = (3,3), \ input_shape = image_shape), \ layers.Activation('relu')])
很简单。你刚刚迈出了创建第一个 CNN 的第一步。
现在,你将继续学习通常跟随在卷积层后的层——池化层。
池化层
池化是一种常常添加到 CNN 中的操作,用于通过减少卷积层输出中的像素数量来减少图像的维度。池化层会将输入图像缩小,从而提高计算效率,并减少参数数量,限制过拟合的风险。
池化层紧跟在卷积层后面,是 CNN 结构中另一个重要部分。本节将重点介绍两种池化类型:
-
最大池化
-
平均池化
最大池化
最大池化(max pooling)中,滤波器或内核仅保留输入矩阵中最大的像素值。为了更清楚地理解发生了什么,考虑以下示例。假设你有一个4x4
的输入。最大池化的第一步是将4x4
矩阵划分为四个象限。每个象限的大小为2x2
。应用一个2
大小的滤波器。这意味着你的滤波器将完全像一个2x2
矩阵。
首先,将滤波器放置在输入的顶部。对于最大池化,这个滤波器会查看它覆盖的2x2
区域内的所有值。它会找到最大值,将该值发送到输出,并将其存储在特征图的左上角。
](https://github.com/OpenDocCN/freelearn-dl-pt3-zh/raw/master/docs/tf-ws/img/B16341_07_05.jpg)
图 7.5:最大池化
然后,滤波器将向右移动并重复相同的过程,将值存储在 2x2
矩阵的右上角。一旦这个操作完成,滤波器将向下滑动并从最左边开始,再次重复相同的过程,查找最大值,然后将其存储在 2x2
矩阵的正确位置。
记住,滑动步幅被称为 2
。这个过程会一直重复,直到每个四个象限中的最大值分别为 8
、5
、7
和 5
。再次,为了得到这些数字,你使用了 2x2
的滤波器,并在该 2x2
矩阵内筛选出最大值。
因此,在这种情况下,你的步幅为 2,因为你移动了两个像素。这些是filter
和stride
的值都是2
。图 7.6 显示了使用 3 x 3 滤波器和步幅 1
时,最大池化的实现可能是什么样的。
图 7.6 中显示了两个步骤。首先从特征图的左上角开始。使用 3x3
滤波器,你将查看以下数字:2
、8
、2
、5
、4
、9
、8
、4
和 6
,并选择最大值 9
。9
将被放置在池化特征图的左上角。使用步幅 1
,你会将滤波器滑动一个位置向右,如灰色所示。
现在,查找 8
、2
、1
、4
、9
、6
、4
、6
和 4
中的最大值。再次,9
是最大值,因此在池化特征图的顶行中间位置添加 9
(如灰色所示)。
](https://github.com/OpenDocCN/freelearn-dl-pt3-zh/raw/master/docs/tf-ws/img/B16341_07_06.jpg)
图 7.6:池化特征图
上述池化大小为 (2, 2)
。它指定了你将使用的下采样因子。以下是你可以用来实现 MaxPool2D
的更详细步骤:
layers.MaxPool2D(pool_size=(2, 2), strides=None, \
padding='valid')
MaxPool2D
实例。代码片段初始化了一个池化大小为 2x2
的最大池化层,stride
的值未指定,因此它将默认为池化大小值。padding
参数设置为 valid
,意味着没有添加填充。以下代码片段演示了它在 CNN 中的使用。
image_shape = (300, 300, 3)
our_first_model = models.Sequential([
layers.Conv2D(filters = 16, kernel_size = (3,3), \
input_shape = image_shape), \
layers.Activation('relu'), \
layers.MaxPool2D(pool_size = (2, 2)), \
layers.Conv2D(filters = 32, kernel_size = (3,3)), \
layers.Activation('relu')])
在上面的例子中,创建了一个包含两个卷积层的顺序模型,每个层后面跟着一个 ReLU 激活函数,而第一个卷积层的激活函数后跟着一个最大池化层。
现在你已经了解了最大池化,让我们来看另一种池化方法:平均池化。
平均池化
平均池化 的操作方式与最大池化类似,但它不是提取滤波器内的最大权重值,而是计算平均值。然后它将该值传递给特征图。图 7.7 突出了最大池化和平均池化之间的区别。
在图 7.7中,考虑左侧的 4x4
矩阵。左上象限中的数字平均值为 13
。这就是平均池化值。如果进行最大池化,左上象限将输出 20
到其特征图,因为 20
是滤波器框内的最大值。这是最大池化与平均池化之间的比较,filter
和 stride
参数都设置为 2
:
图 7.7:最大池化与平均池化
对于平均池化,您将使用 AveragePooling2D
替代 MaxPool2D
。
要实现平均池化代码,您可以使用以下代码:
layers.AveragePooling2D(pool_size=(2, 2), strides=None, \
padding='valid')
AveragePooling2D
层。与最大池化类似,pool_size
、strides
和 padding
参数可以进行修改。以下代码片段演示了它在 CNN 中的使用:
image_shape = (300, 300, 3)
our_first_model = models.Sequential([
layers.Conv2D(filters = 16, kernel_size = (3,3), \
input_shape = image_shape), \
layers.Activation('relu'), \
layers.AveragePooling2D(pool_size = (2, 2)), \
layers.Conv2D(filters = 32, kernel_size = (3,3)), \
layers.Activation('relu')])
记住使用池化层的好处是一个好主意。其一是,如果您对图像进行下采样,图像会缩小。这意味着您将拥有 更少的处理数据 和更少的乘法运算,从而加快速度。
到目前为止,您已经创建了第一个 CNN 层,并学会了如何使用池化层。现在,您将利用迄今为止学到的知识,为以下练习构建 CNN 的池化层。
练习 7.02:为 CNN 创建池化层
您收到了来自潜在雇主的电子邮件,关于您申请的 TensorFlow 自由职业职位,该职位是在 练习 7.01 中的 创建 CNN 的第一层。邮件询问您是否能展示如何为 CNN 编写池化层的代码。在本练习中,您将通过添加池化层来构建您的基础模型,正如潜在雇主所要求的那样:
-
打开一个新的 Jupyter notebook,并导入 TensorFlow 库:
import tensorflow as tf from tensorflow.keras import models, layers
-
使用
models.Sequential
创建您的模型。第一个层,Conv2D
,将需要节点数、滤波器大小以及张量的形状,和前一个练习一样。接下来是一个激活层,最后是神经网络的一个节点:image_shape = (300, 300, 3) our_first_model = models.Sequential([ layers.Conv2D(filters = 16, kernel_size = (3,3), \ input_shape = image_shape), \ layers.Activation('relu')])
-
现在,通过使用模型的
add
方法,添加一个MaxPool2D
层:our_first_model.add(layers.MaxPool2D(pool_size = (2, 2))
在这个模型中,您已经创建了一个带有卷积层的 CNN,之后是 ReLU 激活函数,然后是最大池化层。该模型接收尺寸为
300x300
且有三个颜色通道的图像。
现在,您已经成功地向 CNN 添加了一个 MaxPool2D
层,接下来的步骤是添加一个 展平层,以便您的模型可以使用所有数据。
展平层
添加展平层是一个重要步骤,因为您需要将数据提供给神经网络以便其处理。请记住,在执行卷积操作后,数据仍然是多维的。因此,为了将数据转换回一维形式,您将使用展平层。为此,您将池化后的特征图展平为一列,如下图所示。在图 7.8中,您可以看到,从图表左侧的输入矩阵开始,使用最终池化的特征图,并将其拉伸成一个单列向量:
图 7.8:展平层
以下是实现的展平层:
image_shape = (300, 300, 3)
our_first_model = models.Sequential([
layers.Conv2D(filters = 16, kernel_size = (3,3), \
input_shape = image_shape), \
layers.Activation('relu'), \
layers.MaxPool2D(pool_size = (2, 2)), \
layers.Conv2D(filters = 32, kernel_size = (3,3)), \
layers.Activation('relu'), \
layers.MaxPool2D(pool_size = (2, 2)), \
layers.Flatten()])
在这里,作为此模型的最终层,添加了一个展平层。现在您已经创建了第一层 CNN 和池化层,接下来将把所有部分拼接在一起,并在接下来的练习中构建完整的 CNN。
练习 7.03:构建 CNN
您是作为自由职业者被聘用的,您的工作来源于练习 7.01,创建 CNN 的第一层,以及练习 7.02,为 CNN 创建池化层。现在您已经获得了这份工作,您的第一个任务是帮助您的初创公司构建原型产品,向投资者展示并筹集资金。该公司正在开发一个马匹或人类分类应用程序,他们希望您立刻开始。他们告诉您现在只需要分类器能够正常工作,之后会有改进的空间。
在本次练习中,您将使用 horses_or_humans
数据集为模型构建卷积基础层。在此数据集中,图像没有居中。目标图像以不同角度和不同位置显示在框架中。您将在本章中继续在此基础上构建,逐步完善。
注意
数据集可以通过 tensorflow_datasets
包下载。
-
导入所有必要的库:
import numpy as np import matplotlib.pyplot as plt import matplotlib.image as mpimg import tensorflow as tf import tensorflow_datasets as tfds from tensorflow.keras import models, layers from tensorflow.keras.optimizers import RMSprop from keras_preprocessing import image as kimage
首先,您需要导入 TensorFlow 库。您将使用
tensorflow_datasets
加载数据集,使用tensorflow.keras.models
构建一个顺序 TensorFlow 模型,使用tensorflow.keras.layers
为 CNN 模型添加层,使用RMSprop
作为优化器,并使用matplotlib.pyplot
和matplotlib.image
进行一些快速可视化。 -
从
tensorflow_datasets
包中加载您的数据集:(our_train_dataset, our_test_dataset), \ dataset_info = tfds.load('horses_or_humans',\ split = ['train', 'test'],\ data_dir = 'content/',\ shuffle_files = True,\ with_info = True) assert isinstance(our_train_dataset, tf.data.Dataset)
在这里,您使用了以
tfds
导入的tensorflow_datasets
包。您使用tfds.load()
函数加载了horses_or_humans
数据集。这是一个二分类图像数据集,包含两个类别:马和人。注意
更多关于数据集的信息可以在
laurencemoroney.com/datasets.html
找到。更多关于
tensorflow_datasets
包的信息可以在www.tensorflow.org/datasets
找到。split = ['train', 'test']
参数指定了要加载的数据分割。在这个例子中,你将训练集和测试集分别加载到our_train_dataset
和our_test_dataset
中。指定with_info = True
以将数据集的元数据加载到dataset_info
变量中。加载后,使用assert
来确保加载的数据集是tf.data.Dataset
对象类的实例。 -
查看使用加载的元数据
dataset_info
中的数据集信息:image_shape = dataset_info.features["image"].shape print(f'Shape of Images in the Dataset: \t{image_shape}') print(f'Number of Classes in the Dataset: \ \t{dataset_info.features["label"].num_classes}') names_of_classes = dataset_info.features["label"].names for name in names_of_classes: print(f'Label for class "{name}": \ \t\t{dataset_info.features["label"].str2int(name)}')
你应该得到以下输出:
图 7.9:horses_or_humans 数据集信息
-
现在,查看数据集中图像的数量及其类别分布:
print(f'Total examples in Train Dataset: \ \t{len(our_train_dataset)}') pos_tr_samples = sum(i['label'] for i in our_train_dataset) print(f'Horses in Train Dataset: \t\t{len(our_train_dataset) \ - pos_tr_samples}') print(f'Humans in Train Dataset: \t\t{pos_tr_samples}') print(f'\nTotal examples in Test Dataset: \ \t{len(our_test_dataset)}') pos_ts_samples = sum(i['label'] for i in our_test_dataset) print(f'Horses in Test Dataset: \t\t{len(our_test_dataset) \ - pos_ts_samples}') print(f'Humans in Test Dataset: \t\t{pos_ts_samples}')
你应该得到以下输出:
图 7.10:horses_or_humans 数据集分布
-
现在,使用
tfds.show_examples()
函数查看训练数据集中的一些样本图像:fig = tfds.show_examples(our_train_dataset, dataset_info)
该功能用于交互式使用,它显示并返回训练数据集中的图像绘图。
你的输出应该类似于以下内容:
图 7.11:样本训练图像
-
查看测试数据集中的一些样本图像:
fig = tfds.show_examples(our_test_dataset, dataset_info)
你将得到以下输出:
图 7.12:样本测试图像
-
最后,使用
our_model = models.Sequential
创建模型。设置第一个Conv2D
层,并将filters
设置为16
。卷积核为3x3
。使用 ReLU 激活函数。由于这是第一个卷积层,你还需要将input_shape
设置为image_shape
,即你正在处理的彩色图像的维度。接下来,添加MaxPool2D
池化层。然后,再添加一个Conv2D
和MaxPool2D
层对,增加模型深度,接着添加展平层和全连接层:our_cnn_model = models.Sequential([ layers.Conv2D(filters = 16, kernel_size = (3,3), \ input_shape = image_shape),\ layers.Activation('relu'),\ layers.MaxPool2D(pool_size = (2, 2)),\ layers.Conv2D(filters = 32, kernel_size = (3,3)),\ layers.Activation('relu'),\ layers.MaxPool2D(pool_size = (2, 2)),\ layers.Flatten(),\ layers.Dense(units = 512),\ layers.Activation('relu'),\ layers.Dense(units = 1),\ layers.Activation('sigmoid') ])
-
使用
RMSProp
编译模型,将optimizer
设置为推荐的默认值0.001
,loss
设置为binary_crossentropy
,并将metrics
设置为acc
以衡量准确率。使用summary()
方法打印模型摘要:our_cnn_model.compile(optimizer=RMSprop(learning_rate=0.001), \ loss='binary_crossentropy',\ metrics=['acc'], loss_weights=None,\ weighted_metrics=None, run_eagerly=None,\ steps_per_execution=None) print(our_cnn_model.summary())
这将打印出模型摘要,详细说明每个层的类型、输出形状和参数:
图 7.13:模型摘要
在上面的截图中,你可以看到左侧列出了各个层和类型。各层按从上到下的顺序排列。输出形状显示在中间。每个层旁边列出了多个参数。在底部,你会看到总参数数、可训练参数数和不可训练参数数。
你已经能够深入了解卷积层和池化层了。现在,让我们来探讨在使用图像数据时的另一个重要组成部分:图像增强。
图像增强
增强定义为通过增大尺寸或数量使某物变得更好。这正是数据或图像增强的作用。你通过增强为模型提供更多版本的图像训练数据。记住,数据越多,模型的性能就越好。通过增强你的数据,你可以以一种方式转换你的图像,使得模型在真实数据上的泛化能力更强。为此,你需要转换现有的图像,以便你能将增强后的图像与原始图像数据集一起使用,从而训练出比原先更多样化的模型。这改善了结果并防止了过拟合。看看以下三张图片:
图 7.14:增强后的豹子图像
很明显,这三张图中的豹子是同一只,它们只是处于不同的姿势。由于卷积,神经网络仍然能够理解这一点。然而,利用图像增强,你可以提高模型学习平移不变性的能力。
与大多数其他类型的数据不同,图像可以进行平移、旋转和移动,从而产生原始图像的变体。这会产生更多的数据,而对于卷积神经网络(CNNs),更多的数据和数据变化将创造出表现更好的模型。为了能够创建这些图像增强操作,看看如何在 TensorFlow 中使用加载的tf.data.Dataset
对象。你将使用dataset.map()
函数将图像增强的预处理函数映射到你的数据集上,也就是our_train_dataset
:
from tensorflow import image as tfimage
from tensorflow.keras.preprocessing import image as kimage
你将使用tensorflow.image
和tensorflow.keras.preprocessing.image
包来实现这个目的。这些包提供了许多图像处理函数,可以用于图像数据增强:
augment_dataset(image, label):
image = kimage.random_shift(image, wrg = 0.1, hrg = 0.1)
image = tfimage.random_flip_left_right(image)
return image, label
其他函数包括以下内容:
-
kimage.random_rotation
:此函数允许你在指定的角度范围内随机旋转图像。 -
kimage.random_brightness
:此函数随机调整亮度级别。 -
kimage.random_shear
:此函数应用剪切变换。 -
kimage.random_zoom
:此函数随机缩放图像。 -
tfimage.random_flip_left_right
:此函数随机左右翻转图像。 -
tfimage.random_flip_up_down
:此函数随机上下翻转图像。
在下一步,你将使用tf.data.Dataset.map()
函数传入你想要增强的数据:
augment_dataset(image, label):
image = kimage.random_shift(image, wrg = 0.1, hrg = 0.1)
image = tfimage.random_flip_left_right(image)
return image, label
our_train_dataset = our_train_dataset.map(augment_dataset)
model.fit(our_train_dataset,\
epochs=50,\
validation_data=our_test_dataset)
在前面的代码块中,使用fit()
时,你只需传入你已经创建的生成器。你需要传入epochs
值。如果不这样做,生成器将永远不会停止。fit()
函数返回历史记录(每次迭代绘制损失等)。
在您开始训练模型之前,您还需要为our_train_dataset
添加一些功能。使用batch()
函数,您可以指定每个批次将训练多少张图像。使用cache()
函数,您可以将数据集缓存到内存中以提高性能。使用shuffle()
函数,您可以将数据集的洗牌缓冲区设置为数据集的整个长度,从而实现真正的随机性。prefetch()
函数也有助于提高性能:
our_train_dataset = our_train_dataset.cache()
our_train_dataset = our_train_dataset.map(augment_dataset)
our_train_dataset = our_train_dataset.shuffle\
(len(our_train_dataset))
our_train_dataset = our_train_dataset.batch(128)
our_train_dataset = our_train_dataset.prefetch\
(tf.data.experimental.AUTOTUNE)
现在您已经了解了如何在训练模型中实现数据增强,接下来仔细看看这些变换所做的工作。
这是random_rotation
、random_shift
和random_brightness
实现的一个例子。使用以下代码可以将图像随机旋转到指定值:
image = kimage.random_rotation(image, rg = 135)
在图 7.15中,您可以看到random_rotation
的效果。
图 7.15:旋转范围
图像被随机旋转了最多 135 度。
random_shift
用于随机在宽度上偏移像素。请注意以下代码中的.15
,这意味着图像最多可以随机偏移 15 像素:
image = kimage.random_shift(image, wrg = 0.15, hrg = 0)
以下图示展示了图像宽度最多可随机调整 15 像素:
图 7.16:宽度偏移范围
再次使用random_shift
,它随机调整高度 15 像素:
image = kimage.random_shift(image, wrg = 0, hrg = 0.15)
图 7.17展示了图像高度最多可以随机调整 15 像素:
图 7.17:高度偏移范围
使用random_brightness
进行随机亮度调整时,您将使用一个浮动值范围按百分比调节图像的亮度或暗度。低于1.0
的任何值都会使图像变暗。所以,在这个例子中,图像的亮度会在 10%到 90%之间随机变暗:
image = kimage.random_brightness(image, brightness_range=(0.1,0.9))
在下图中,您已经使用random_brightness
调整了亮度:
图 7.18:亮度范围
现在您已经了解了其中一些图像增强选项,接下来看看如何利用批量归一化来提高模型的性能。
批量归一化
在 2015 年,批量归一化(Batch Normalization),也叫批归一化(Batch Norm),由Christian Szegedy和Sergey Ioffe提出。批归一化是一种减少训练周期数、提高性能的技术。批归一化对一个小批量的输入进行标准化,并“归一化”输入层。它通常在卷积层后使用,如下图所示:
图 7.19:批量归一化
以下图示展示了批量归一化的一种常见实现方式。在以下例子中,您可以看到在卷积层后面有一个批量归一化层,且重复了三次。然后是一个展平层,接着是两个全连接层:
图 7.20:层序列
批归一化有助于模型更好地泛化。在每个批次进行训练时,模型具有不同的均值和标准差。由于批次均值和标准差与真实的总体均值和标准差略有不同,这些变化充当噪声,使模型整体表现更好。
以下是BatchNormalization
实现的示例。你可以简单地添加一个批归一化层,然后跟随一个激活层:
model.add(layers.Conv2D(filters = 64, kernel_size = (3, 3), use_bias=False))
model.add(layers.BatchNormalization())
model.add(layers.Activation("relu"))
到目前为止,你已经创建了一个 CNN 模型并学习了如何使用图像增强。现在你将把所有内容结合起来,并在接下来的练习中构建一个具有额外卷积层的 CNN。
练习 7.04:构建一个带有额外卷积层的 CNN
你的新雇主对你在练习 7.03中完成的工作非常满意,构建一个 CNN。现在,最小可行产品(MVP)或原型已经完成,是时候构建一个更好的模型了。
在本练习中,你将向模型中添加额外的 ANN 层。你将向之前创建的卷积基础层中添加额外的层。你将再次使用horses_or_humans
数据集。
让我们开始吧。
因为你在练习 7.03中扩展了构建一个 CNN并使用相同的数据,所以从上一个练习的最后一步继续开始:
-
创建一个函数来重新缩放图像,然后使用
map
方法将该函数应用于训练集和测试集。继续使用数据集的cache
、shuffle
、batch
和prefetch
方法来构建训练和测试数据集管道:normalization_layer = layers.Rescaling(1./255) our_train_dataset = our_train_dataset.map\ (lambda x: (normalization_layer(x['image']), \ x['label']), \ num_parallel_calls = \ tf.data.experimental.AUTOTUNE) our_train_dataset = our_train_dataset.cache() our_train_dataset = our_train_dataset.shuffle\ (len(our_train_dataset)) our_train_dataset = our_train_dataset.batch(128) our_train_dataset = \ our_train_dataset.prefetch(tf.data.experimental.AUTOTUNE) our_test_dataset = our_test_dataset.map\ (lambda x: (normalization_layer(x['image']), \ x['label']),\ num_parallel_calls = \ tf.data.experimental.AUTOTUNE) our_test_dataset = our_test_dataset.cache() our_test_dataset = our_test_dataset.batch(32) our_test_dataset = our_test_dataset.prefetch\ (tf.data.experimental.AUTOTUNE)
-
拟合模型。指定
epochs
和validation_steps
的值,并将verbose
设置为1
:history = our_cnn_model.fit\ (our_train_dataset, \ validation_data = our_test_dataset, \ epochs=15, \ validation_steps=8, \ verbose=1)
输出看起来像这样:
图 7.21:模型拟合过程
-
从测试数据集中获取一批数据并绘制该批次中的第一张图片。将图片转换为数组,然后使用模型预测图片显示的内容:
from matplotlib.pyplot import imshow for images, lables in our_test_dataset.take(1): imshow(np.asarray(images[0])) image_to_test = kimage.img_to_array(images[0]) image_to_test = np.array([image_to_test]) prediction = our_cnn_model.predict(image_to_test) print(prediction) if prediction > 0.5: print("Image is a human") else: print("Image is a horse")
输出将包含以下细节:
图 7.22:带有元数据的图像测试输出
对于预测,你有一张来自测试集的人物图片,用来查看分类结果是什么。
-
看一下每一层的变化情况。通过创建一个包含 CNN 中所有层名称的列表,以及另一个包含每一层的随机样本预测的列表来查看这一过程。接下来,迭代层名称列表及其相应的预测,并绘制特征:
layer_outputs = [] for layer in our_cnn_model.layers[1:]: layer_outputs.append(layer.output) layer_names = [] for layer in our_cnn_model.layers: layer_names.append(layer.name) features_model = models.Model(inputs = our_cnn_model.input, \ outputs = layer_outputs) random_sample = our_train_dataset.take(1) layer_predictions = features_model.predict(random_sample) for layer_name, prediction in zip(layer_names, \ layer_predictions): if len(prediction.shape) != 4: continue num_features = prediction.shape[-1] size = prediction.shape[1] grid = np.zeros((size, size * num_features)) for i in range(num_features): img = prediction[0, :, :, i] img = ((((img - img.mean()) / img.std()) * 64) + 128) img = np.clip(img, 0, 255).astype('uint8') grid[:, i * size : (i + 1) * size] = img scale = 20\. / num_features plt.figure(figsize=(scale * num_features, scale)) plt.title(layer_name) plt.imshow(grid)
你应该得到如下结果:
图 7.23:不同层的转换
现在,您已经创建了自己的 CNN 模型,并用它来判断一张图像是马还是人,接下来你将专注于如何分类图像是否属于某个特定类别。
二分类图像分类
二分类是分类模型中最简单的方法,因为它将图像分类为两个类别。在这一章中,我们从卷积操作开始,讨论了如何将其用作图像转换器。接着,你了解了池化层的作用,以及最大池化和平均池化之间的区别。然后,我们还探讨了如何通过平坦层将池化特征图转换为单列。接下来,你学习了如何以及为什么使用图像增强,以及如何使用批量归一化。这些都是区分 CNN 和其他 ANN 的关键组件。
在卷积基础层、池化层和归一化层之后,CNN 通常像你迄今为止构建的许多 ANN 一样,结构包括一个或多个全连接层。与其他二分类器类似,二分类图像分类器以一个单元和一个 sigmoid 激活函数的全连接层结束。为了提供更多的实用性,图像分类器可以被配置为分类两个以上的对象。此类分类器通常称为对象分类器,关于这一点,你将在下一节中学习。
对象分类
在本节中,你将学习对象检测和分类。接下来的步骤包括对一个包含多个类别的数据集进行图像分类。我们将介绍的三种不同类型的对象分类模型是图像分类、带定位的分类和检测:
-
图像分类:这包括用固定数量的类别进行训练,然后尝试确定图像中展示的是哪个类别。想想 MNIST 手写数据集。对于这些问题,你将使用传统的 CNN。
-
带定位的分类:这种类型的模型尝试预测图像中对象所在的位置。对于这些模型,你可以使用简化版的You Only Look Once(YOLO)或 R-CNN。
-
检测:最后一种类型是检测。这是指模型能够检测出多个不同的对象,并且确定它们的位置。为此,你可以使用 YOLO 或 R-CNN:图 7.24:对象分类类型
图 7.24:对象分类类型
接下来,我们将简要了解使用 Fashion-MNIST
数据集进行图像分类。Fashion-MNIST
数据集来自 Zalando 的商品图片。Zalando 是一家总部位于德国柏林的以时尚为主的电子商务公司。该数据集包含 10 个类别,训练集有 60,000 张 28x28
的灰度图像,测试集有 10,000 张图像。
-
导入 TensorFlow:
import tensorflow as tf
-
接下来,进行一些额外的导入,例如 NumPy、Matplotlib,以及当然的层和模型。你会注意到这里会使用额外的 dropout 层。如果你还记得,dropout 层有助于防止过拟合:
import numpy as np import matplotlib.pyplot as plt import tensorflow_datasets as tfds from tensorflow.keras.layers import Input, Conv2D, Dense, Flatten, \ Dropout, GlobalMaxPooling2D, Activation, Rescaling from tensorflow.keras.models import Model from sklearn.metrics import confusion_matrix, ConfusionMatrixDisplay import itertools import matplotlib.pyplot as plt
-
使用
tdfs
加载Fashion-MNIST
数据集,这是他们决定包含的其中一个数据集。其他的还包括CIFAR-10
和CIFAR-100
,仅举几个例子:(our_train_dataset, our_test_dataset), \ dataset_info = tfds.load(\ 'fashion_mnist' , split = ['train', 'test'] , data_dir = 'content/FashionMNIST/' , shuffle_files = True , as_supervised = True , with_info = True) assert isinstance(our_train_dataset, tf.data.Dataset)
-
检查数据的属性:
image_shape = dataset_info.features["image"].shape print(f'Shape of Images in the Dataset: \t{image_shape}') num_classes = dataset_info.features["label"].num_classes print(f'Number of Classes in the Dataset: \t{num_classes}') names_of_classes = dataset_info.features["label"].names print(f'Names of Classes in the Dataset: \t{names_of_classes}\n') for name in names_of_classes: print(f'Label for class \ "{name}": \t\t{dataset_info.features["label"].\ str2int(name)}')
这将产生以下输出:
图 7.25:数据属性详情
-
现在,打印训练集和测试集的总示例数:
print(f'Total examples in Train Dataset: \ \t{len(our_train_dataset)}') print(f'Total examples in Test Dataset: \ \t{len(our_test_dataset)}')
这将产生以下输出:
图 7.26:训练集和测试集的详细信息
-
使用功能 API 构建你的模型:
input_layer = Input(shape=image_shape) x = Conv2D(filters = 32, kernel_size = (3, 3), \ strides=2)(input_layer) x = Activation('relu')(x) x = Conv2D(filters = 64, kernel_size = (3, 3), strides=2)(x) x = Activation('relu')(x) x = Conv2D(filters = 128, kernel_size = (3, 3), strides=2)(x) x = Activation('relu')(x) x = Flatten()(x) x = Dropout(rate = 0.2)(x) x = Dense(units = 512)(x) x = Activation('relu')(x) x = Dropout(rate = 0.2)(x) x = Dense(units = num_classes)(x) output = Activation('softmax')(x) our_classification_model = Model(input_layer, output)
-
编译并拟合你的模型。使用
compile()
方法,选择adam
作为优化器,将损失设置为sparse_categorical_crossentropy
,并设置accuracy
作为评估指标。然后,在你的训练集和验证集上调用model.fit()
:our_classification_model.compile( optimizer='adam', \ loss='sparse_categorical_crossentropy', metrics=['accuracy'], loss_weights=None, weighted_metrics=None, run_eagerly=None, steps_per_execution=None ) history = our_classification_model.fit(our_train_dataset, validation_data=our_test_dataset, epochs=15)
这将产生以下输出:
图 7.27:返回历史记录的函数
-
使用
matplotlib.pyplot
绘制损失和准确率:def plot_trend_by_epoch(tr_values, val_values, title): epoch_number = range(len(tr_values)) plt.plot(epoch_number, tr_values, 'r') plt.plot(epoch_number, val_values, 'b') plt.title(title) plt.xlabel('epochs') plt.legend(['Training '+title, 'Validation '+title]) plt.figure() hist_dict = history.history tr_accuracy, val_accuracy = hist_dict['accuracy'], \ hist_dict['val_accuracy'] plot_trend_by_epoch(tr_accuracy, val_accuracy, "Accuracy")
这将产生以下绘图作为输出:
图 7.28:使用 matplotlib.pyplot 绘制的准确率图
-
绘制验证损失和训练损失。使用以下代码:
tr_loss, val_loss = hist_dict['loss'], hist_dict['val_loss'] plot_trend_by_epoch(tr_loss, val_loss, "Loss")
这将产生以下绘图作为输出:
图 7.29:验证损失和训练损失
正如你从准确率和损失曲线相对于周期的变化中看到的,准确率在增加,损失在减少。在验证集上,两者开始趋于平稳,这是停止训练的良好信号,以防止过拟合训练数据集。
在下一个练习中,你将构建一个 CNN,用来将图像分类为 CIFAR-10
数据集中的 10 个不同类别。
练习 7.05:构建 CNN
该初创公司现在希望扩大其能力,并与更多的类别和更大的图像数据集合作。你的挑战是准确预测图像的类别。
你将使用的数据集是 CIFAR-10
数据集,该数据集包含 60,000 张 32x32
彩色图像,分为 10 类:飞机、汽车、鸟类、猫、鹿、狗、青蛙、马、船和卡车。每个类别有 6,000 张图像,整个数据集包含 50,000 张训练图像和 10,000 张测试图像。
有关数据集的更多信息,请参阅 学习小图像中的多个特征层(www.cs.toronto.edu/~kriz/learning-features-2009-TR.pdf
),Alex Krizhevsky,2009:
-
启动一个新的 Jupyter 笔记本并导入 TensorFlow 库:
import tensorflow as tf
-
导入其他必要的附加库:
import numpy as np import matplotlib.pyplot as plt import tensorflow_datasets as tfds from tensorflow.keras.layers import Input, Conv2D, Dense, Flatten, \ Dropout, GlobalMaxPooling2D, Activation, Rescaling from tensorflow.keras.models import Model from sklearn import metrics import confusion_matrix, \ ConfusionMatrixDisplay import itertools import matplotlib.pyplot as plt
-
从
tfds
直接加载CIFAR-10
数据集,如下所示:(our_train_dataset, our_test_dataset), \ dataset_info = tfds.load('cifar10',\ split = ['train', 'test'],\ data_dir = 'content/Cifar10/',\ shuffle_files = True,\ as_supervised = True,\ with_info = True) assert isinstance(our_train_dataset, tf.data.Dataset)
-
使用以下代码打印数据集的属性:
image_shape = dataset_info.features["image"].shape print(f'Shape of Images in the Dataset: \t{image_shape}') num_classes = dataset_info.features["label"].num_classes print(f'Number of Classes in the Dataset: \t{num_classes}') names_of_classes = dataset_info.features["label"].names print(f'Names of Classes in the Dataset: \t{names_of_classes}\n') for name in names_of_classes: print(f'Label for class "{name}": \ \t\t{dataset_info.features["label"].str2int(name)}') print(f'Total examples in Train Dataset: \ \t{len(our_train_dataset)}') print(f'Total examples in Test Dataset: \ \t{len(our_test_dataset)}')
这将输出以下结果,包括属性和类别数量:
图 7.30:类别数量
-
构建训练和测试数据管道,如练习 7.03,构建 CNN所示:
normalization_layer = Rescaling(1./255) our_train_dataset = our_train_dataset.map\ (lambda x, y: (normalization_layer(x), y),\ num_parallel_calls = \ tf.data.experimental.AUTOTUNE) our_train_dataset = our_train_dataset.cache() our_train_dataset = our_train_dataset.shuffle\ (len(our_train_dataset)) our_train_dataset = our_train_dataset.batch(128) our_train_dataset = our_train_dataset.prefetch\ (tf.data.experimental.AUTOTUNE) our_test_dataset = our_test_dataset.map\ (lambda x, y: (normalization_layer(x), y),\ num_parallel_calls = \ tf.data.experimental.AUTOTUNE) our_test_dataset = our_test_dataset.cache() our_test_dataset = our_test_dataset.batch(1024) our_test_dataset = our_test_dataset.prefetch\ (tf.data.experimental.AUTOTUNE)
-
使用功能式 API 构建模型。设置形状、层类型、步长和激活函数:
input_layer = Input(shape=image_shape) x = Conv2D(filters = 32, \ kernel_size = (3, 3), strides=2)(input_layer) x = Activation('relu')(x) x = Conv2D(filters = 64, kernel_size = (3, 3), strides=2)(x) x = Activation('relu')(x) x = Conv2D(filters = 128, kernel_size = (3, 3), strides=2)(x) x = Activation('relu')(x) x = Flatten()(x) x = Dropout(rate = 0.5)(x) x = Dense(units = 1024)(x) x = Activation('relu')(x) x = Dropout(rate = 0.2)(x) x = Dense(units = num_classes)(x) output = Activation('softmax')(x) our_classification_model = Model(input_layer, output)
-
编译并拟合你的模型。如果可能,请确保使用 GPU,因为这将显著加速过程。如果你决定不使用 GPU,而你的计算机在计算方面有困难,你可以相应地减少训练轮数(epochs)的数量:
our_classification_model.compile( optimizer='adam', \ loss='sparse_categorical_crossentropy', metrics=['accuracy'], loss_weights=None, weighted_metrics=None, run_eagerly=None, steps_per_execution=None ) print(our_classification_model.summary()) history = our_classification_model.fit(our_train_dataset, validation_data=our_test_dataset, epochs=15)
该函数将返回以下历史记录:
图 7.31:拟合模型
-
通过绘制每个 epoch 的损失和准确率,获得模型性能的可视化表示:
def plot_trend_by_epoch(tr_values, val_values, title): epoch_number = range(len(tr_values)) plt.plot(epoch_number, tr_values, 'r') plt.plot(epoch_number, val_values, 'b') plt.title(title) plt.xlabel('epochs') plt.legend(['Training '+title, 'Validation '+title]) plt.figure() hist_dict = history.history tr_loss, val_loss = hist_dict['loss'], hist_dict['val_loss'] plot_trend_by_epoch(tr_loss, val_loss, "Loss")
这将产生以下图:
图 7.32:损失图
-
接下来,使用以下代码获取准确率图:
tr_accuracy, val_accuracy = hist_dict['accuracy'], \ hist_dict['val_accuracy'] plot_trend_by_epoch(tr_accuracy, val_accuracy, "Accuracy")
这将生成如下图:
图 7.33:准确率图
-
绘制未标准化的混淆矩阵:
test_labels = [] test_images = [] for image, label in tfds.as_numpy(our_test_dataset.unbatch()): test_images.append(image) test_labels.append(label) test_labels = np.array(test_labels) predictions = our_classification_model.predict(our_test_dataset).argmax(axis=1) conf_matrix = confusion_matrix(test_labels, predictions) disp = ConfusionMatrixDisplay(conf_matrix, \ display_labels = names_of_classes) fig = plt.figure(figsize = (12, 12)) axis = fig.add_subplot(111) disp.plot(values_format = 'd', ax = axis)
这将输出以下结果:
图 7.34:未标准化的混淆矩阵
-
使用以下代码绘制带标准化的混淆矩阵:
conf_matrix = conf_matrix.astype\ ('float') / conf_matrix.sum(axis=1) \ [:, np.newaxis] disp = ConfusionMatrixDisplay(\ conf_matrix, display_labels = names_of_classes) fig = plt.figure(figsize = (12, 12)) axis = fig.add_subplot(111) disp.plot(ax = axis)
输出结果如下所示:
图 7.35:带标准化的混淆矩阵
-
查看模型预测错误的其中一张图片。使用以下代码绘制其中一个错误预测的图像:
incorrect_predictions = np.where(predictions != test_labels)[0] index = np.random.choice(incorrect_predictions) plt.imshow(test_images[index]) print(f'True label: {names_of_classes[test_labels[index]]}') print(f'Predicted label: {names_of_classes[predictions[index]]}')
输出结果如下所示:
图 7.36:真实结果与预测结果
你会注意到它显示真实标签:鸟
和预测标签:猫
。这意味着模型预测这张图片是猫,但实际是鸟。图片模糊,因为分辨率只有32x32
;然而,结果还不错。可以公平地说,对于人类来说,很难判断这张图片是狗还是猫。
现在你已经完成了本章内容,是时候通过活动 7.01,构建一个包含更多 ANN 层的 CNN,来检验你所学到的一切,其中你将构建一个包含更多 ANN 层的 CNN。
活动 7.01:构建一个包含更多 ANN 层的 CNN
你所在的初创公司很喜欢你到目前为止的工作。他们已将你指派去创建一个新的模型,能够对 100 个不同类别的图像进行分类。
在这个活动中,你将把你所学的一切付诸实践,构建自己的分类器,并使用 CIFAR-100
数据集。CIFAR-100
是 CIFAR-10
数据集的更高级版本,包含 100 个类别,广泛用于机器学习研究中的性能基准测试。
-
启动一个新的 Jupyter notebook。
-
导入 TensorFlow 库。
-
导入你需要的额外库,包括 NumPy、Matplotlib、Input、Conv2D、Dense、Flatten、Dropout、GlobalMaxPooling2D、Activation、Model、confusion_matrix 和 itertools。
-
直接从
tensorflow_datasets
加载CIFAR-100
数据集,并查看其元数据中的属性,构建训练和测试数据管道:图 7.37:CIFAR-100 数据集的属性
-
创建一个函数来重新缩放图像。然后,通过重新缩放、缓存、打乱、批处理和预取图像,构建测试和训练数据管道。
-
使用功能性 API 构建模型,使用
Conv2D
和Flatten
等层。 -
使用
model.compile
和model.fit
编译并训练模型:图 7.38:模型拟合
-
使用
plt.plot
绘制损失图。记得使用在model.fit()
过程中收集的历史数据:图 7.39:损失与迭代次数
-
使用
plt.plot
绘制准确率:图 7.40:准确率与迭代次数
-
为数据集中的不同类别指定标签。
-
使用
plt.imshow
显示一个误分类的示例:
图 7.41:错误分类示例
注意
这个活动的解决方案可以通过这个链接找到。
总结
本章介绍了卷积神经网络(CNN)。我们回顾了神经元、层、模型架构和张量等核心概念,帮助理解如何创建有效的 CNN。
你学习了卷积操作,并探索了卷积核和特征图。我们分析了如何组装一个 CNN,并探讨了不同类型的池化层及其应用时机。
你还学习了步幅操作以及如何使用填充来在图像周围创建额外的空间。然后,我们深入研究了 flatten 层,它如何将数据转换为 1D 数组以供下一层使用。在最后的活动中,你将学到的所有知识付诸实践,面临多个分类问题,包括 CIFAR-10
甚至 CIFAR-100
。
完成本章后,你现在已经能够自信地实现 CNN,并直接应对图像分类问题。
在下一章,你将学习预训练模型,并了解如何通过在预训练模型上添加 ANN 层并根据自己的训练数据微调权重,来将其用于自己的应用程序。
第八章:8. 预训练网络
概述
在本章中,您将分析预训练模型。您将通过实践体验使用 TensorFlow 上提供的不同最先进的模型架构。您将探索诸如迁移学习和微调等概念,并了解 TensorFlow Hub 及其发布的深度学习资源。
到本章结束时,您将能够直接使用 TensorFlow 和 TensorFlow Hub 中的预训练模型。
引言
在上一章中,您学习了 卷积神经网络(CNNs)如何分析图像并学习相关模式,以便分类其主要对象或识别其中的物体。您还了解了用于此类模型的不同类型的层。
但是,与其从头开始训练一个模型,不如利用已经计算好的权重重用现有的模型,这样会更高效。这正是 迁移学习 和 微调 的核心内容。本章中,您将学习如何将这些技术应用到自己的项目和数据集中。
您还将了解 ImageNet 比赛以及深度学习研究人员用来将自己的模型与最先进算法进行基准对比的相应数据集。最后,您将学习如何使用 TensorFlow Hub 的资源来构建自己的模型。
ImageNet
ImageNet 是一个大型数据集,包含超过 1400 万张用于图像分类或物体检测的标注图像。它由 Fei-Fei Li 及其团队在 2007 年首次整合。其目标是构建一个计算机视觉研究人员能够受益的数据集。
该数据集首次在 2009 年发布,自 2010 年起,每年都会组织一场名为 ImageNet 大规模视觉识别挑战赛(ILSVRC)的年度竞赛,涵盖图像分类和物体检测任务。
图 8.1:来自 ImageNet 的图像示例
多年来,一些最著名的卷积神经网络(CNN)架构(如 AlexNet、Inception、VGG 和 ResNet)在 ILSVRC 比赛中取得了惊人的成果。在下面的图表中,您可以看到一些最著名的 CNN 架构在这场比赛中的表现。在不到 10 年的时间里,性能从 50% 的准确率提升到接近 90%。
图 8.2:来自 paperswithcode.com 的模型基准测试
您将在下一节中看到如何使用这些模型进行迁移学习。
迁移学习
在上一章中,你亲自实践了训练不同的 CNN 模型进行图像分类。尽管你取得了不错的结果,但模型花费了相当长的时间来学习相关参数。如果你继续训练这些模型,可能会得到更好的结果。使用图形处理单元(GPUs)可以缩短训练时间,但它仍然需要一些时间,尤其是在处理更大或更复杂的数据集时。
深度学习研究人员已经发布了他们的工作,以造福社区。每个人都可以通过采用现有的模型架构并进行定制,而不是从零开始设计架构,来获益。更重要的是,研究人员还分享了他们模型的权重。这样,你不仅可以重用一个架构,还可以利用它已经进行的所有训练。这就是迁移学习的核心。通过重用预训练模型,你无需从头开始。这些模型是经过大规模数据集(如 ImageNet)训练的,已经学会了如何识别成千上万种不同类别的物体。你可以直接使用这些最新的模型,而无需再次训练它们。是不是很棒?与其训练一个模型几个星期,你现在可以直接使用现有的模型。
TensorFlow 提供了一个经过 ImageNet 数据集预训练的最新模型列表,可用于其 Keras API 中的迁移学习。
注意
你可以在以下链接找到 TensorFlow 中提供的所有预训练模型的完整列表:www.tensorflow.org/api_docs/python/tf/keras/applications
。
在 TensorFlow 中导入预训练模型非常简单,如以下示例所示,其中你加载了InceptionV3
模型:
import tensorflow as tf
from tensorflow.keras.applications import InceptionV3
现在你已经导入了预训练模型的类,你需要通过指定输入图像的尺寸和imagenet
作为要加载的预训练权重来实例化它:
model = InceptionV3(input_shape=(224, 224, 3), \
weights='imagenet', include_top=True)
include_top=True
参数指定你将重用与原始在 ImageNet 上训练的模型相同的顶层(即最后一层)。这意味着最后一层是用来预测该数据集中 1,000 个类别的。
现在你已经实例化了预训练模型,你可以从中进行预测:
model.predict(input_image)
如果你想使用这个预训练模型来预测与 ImageNet 类别不同的类别,你需要将顶层替换为另一个将被训练来识别输入数据集中特定类别的层。
首先,你需要通过指定include_top=False
来移除这个层:
model = InceptionV3(input_shape=(224, 224, 3), \
weights='imagenet', include_top=False)
在前面的示例中,你已经加载了一个InceptionV3
模型。下一步将是冻结这个模型的所有层,这样它们的权重就不会被更新:
model.trainable = False
在此之后,您将实例化一个新的全连接层,设置所需的单元数量和激活函数。在以下示例中,您希望预测 50 个不同的类别。为此,您创建一个具有 20
个单元的密集层,并使用 softmax 作为激活函数:
top_layer = tf.keras.layers.Dense(20, activation='softmax')
然后,您需要使用 Keras 的 Sequential API 将这个全连接层添加到您的基础模型中:
new_model = tf.keras.Sequential([model, top_layer])
现在,您可以训练这个模型,并且只有顶层的权重会被更新。其他所有层都已被冻结:
new_model.compile(loss='sparse_categorical_crossentropy', \
optimizer=tf.keras.optimizers.Adam(0.001))
new_model.fit(X_train, t_train, epochs=50)
仅用几行代码,您就加载了 Inception V3 模型,这是一个在 2016 年 ILSVRC 比赛中获胜的最先进的模型。您学会了如何将其适应到自己的项目和数据集中。
在下一个练习中,您将亲手实践迁移学习。
练习 8.01:使用迁移学习对猫和狗进行分类
在这个练习中,您将使用迁移学习正确地将图像分类为猫或狗。您将使用一个预训练的模型 NASNet-Mobile,该模型已经在 TensorFlow 中可用,并且带有 ImageNet 上的预训练权重。
注意
本练习中使用的原始数据集由 Google 提供。该数据集包含 25,000 张猫和狗的图像,可以在此找到:storage.googleapis.com/mledu-datasets/cats_and_dogs_filtered.zip
。
-
打开一个新的 Jupyter notebook。
-
导入 TensorFlow 库:
import tensorflow as tf
-
创建一个名为
file_url
的变量,包含数据集的链接:file_url = 'https://storage.googleapis.com'\ '/mledu-datasets/cats_and_dogs_filtered.zip'
-
使用
tf.keras.get_file
下载数据集,参数为'cats_and_dogs.zip'
、origin=file_url
和extract=True
,并将结果保存到一个名为zip_dir
的变量中:zip_dir = tf.keras.utils.get_file('cats_and_dogs.zip', \ origin=file_url, extract=True)
-
导入
pathlib
库:import pathlib
-
创建一个名为
path
的变量,使用pathlib.Path(zip_dir).parent
获取cats_and_dogs_filtered
目录的完整路径:path = pathlib.Path(zip_dir).parent / 'cats_and_dogs_filtered'
-
创建两个变量,分别叫做
train_dir
和validation_dir
,它们分别表示train
和validation
文件夹的完整路径:train_dir = path / 'train' validation_dir = path / 'validation'
-
创建四个变量,分别叫做
train_cats_dir
、train_dogs_dir
、validation_cats_dir
和validation_dogs_dir
,它们分别表示训练集和验证集中的cats
和dogs
文件夹的完整路径:train_cats_dir = train_dir / 'cats' train_dogs_dir = train_dir /'dogs' validation_cats_dir = validation_dir / 'cats' validation_dogs_dir = validation_dir / 'dogs'
-
导入
os
包。接下来的步骤中,您需要计算文件夹中图像的数量:import os
-
创建两个变量,分别叫做
total_train
和total_val
,用于获取训练集和验证集的图像数量:total_train = len(os.listdir(train_cats_dir)) \ + len(os.listdir(train_dogs_dir)) total_val = len(os.listdir(validation_cats_dir)) \ + len(os.listdir(validation_dogs_dir))
-
从
tensorflow.keras.preprocessing
导入ImageDataGenerator
:from tensorflow.keras.preprocessing.image import ImageDataGenerator
-
实例化两个
ImageDataGenerator
类,并将它们命名为train_image_generator
和validation_image_generator
。这两个类将通过除以255
来重新缩放图像:train_image_generator = ImageDataGenerator(rescale=1./255) validation_image_generator = ImageDataGenerator(rescale=1./255)
-
创建三个变量,分别叫做
batch_size
、img_height
和img_width
,它们的值分别是16
、224
和224
:batch_size = 16 img_height = 224 img_width = 224
-
使用
flow_from_directory()
方法创建一个名为train_data_gen
的数据生成器,并指定批量大小、训练文件夹的路径、目标大小和类别模式:train_data_gen = train_image_generator.flow_from_directory\ (batch_size = batch_size, \ directory = train_dir, \ shuffle=True, \ target_size = (img_height, img_width), \ class_mode='binary')
-
使用
flow_from_directory()
方法创建一个名为val_data_gen
的数据生成器,并指定批量大小、验证文件夹的路径、目标大小和类别模式:val_data_gen = validation_image_generator.flow_from_directory\ (batch_size = batch_size, \ directory = validation_dir, \ target_size=(img_height, img_width), \ class_mode='binary')
-
从
tensorflow.keras
导入numpy
为np
,tensorflow
为tf
,并导入layers
:import numpy as np import tensorflow as tf from tensorflow.keras import layers
-
将
8
(完全是随便设定的)作为 NumPy 和 TensorFlow 的seed
:np.random.seed(8) tf.random.set_seed(8)
-
从
tensorflow.keras.applications
导入NASNETMobile
模型:from tensorflow.keras.applications import NASNetMobile
-
使用 ImageNet 权重实例化模型,移除顶层,并指定正确的输入维度:
base_model = NASNetMobile(include_top=False, \ input_shape=(img_height, img_width, 3),\ weights='imagenet')
-
冻结此模型的所有层:
base_model.trainable = False
-
使用
summary()
方法打印模型的摘要:base_model.summary()
预期输出如下:
图 8.3:模型摘要
-
创建一个新模型,将
NASNETMobile
模型与两个新的顶层(分别为500
和1
个单元)及 ReLU 和 sigmoid 激活函数结合:model = tf.keras.Sequential([base_model,\ layers.Flatten(), layers.Dense(500, \ activation='relu'), layers.Dense(1, \ activation='sigmoid')])
-
通过提供
binary_crossentropy
作为loss
函数,Adam 优化器的学习率为0.001
,并将accuracy
作为要显示的指标,来编译模型:model.compile(loss='binary_crossentropy', \ optimizer=tf.keras.optimizers.Adam(0.001), \ metrics=['accuracy'])
-
拟合模型,提供训练和验证数据生成器,并运行五个周期:
model.fit(train_data_gen, \ steps_per_epoch = total_train // batch_size, \ epochs=5, \ validation_data = val_data_gen, \ validation_steps = total_val // batch_size)
预期输出如下:
图 8.4:模型训练输出
您可以观察到,模型在训练集上达到了0.99
的准确率,在验证集上达到了0.98
的准确率。考虑到您只训练了最后两层,并且训练时间不到一分钟,这是一个相当了不起的结果。这就是应用迁移学习和使用预训练的最先进模型的好处。
在下一节中,您将看到如何对一个预训练模型进行微调。
微调
之前,您使用迁移学习将预训练模型应用到您自己的数据集上。您使用了在像 ImageNet 这样的庞大数据集上训练的最先进模型的权重。这些模型学会了识别图像中的不同模式的相关参数,并帮助您在不同数据集上取得了惊人的结果。
但是这种方法有一个问题。迁移学习通常效果很好,前提是您尝试预测的类别属于与 ImageNet 相同的类别列表。如果是这种情况,从 ImageNet 学到的权重也会与您的数据集相关。例如,上一个练习中的cats
和dogs
类别就存在于 ImageNet 中,因此其权重对于这个数据集也会有用。
然而,如果你的数据集与 ImageNet 非常不同,那么这些预训练模型的权重可能并不完全适用。例如,如果你的数据集包含卫星图像,而你试图判断房屋屋顶是否安装了太阳能板,这与 ImageNet 的数据集相比会有很大不同。最后几层的权重将非常特定于 ImageNet 中的类别,例如猫胡须或汽车车轮(对于卫星图像数据集来说这些并不太有用),而早期层的权重则更为通用,如用于检测形状、颜色或纹理(这些可以应用于卫星图像数据集)。
因此,仍然利用早期层的一些权重是很有帮助的,但需要训练最终层,使得模型能够学习与你的数据集相关的特定模式,并提高其性能。
这种技术称为微调。其背后的理念非常简单:你冻结早期的层,只更新最终层的权重。让我们看看如何在 TensorFlow 中实现这一点:
-
首先,实例化一个没有顶层的预训练
MobileNetV2
模型:from tensorflow.keras.applications import MobileNetV2 base_model = MobileNetV2(input_shape=(224, 224, 3), \ weights='imagenet', include_top=False)
-
接下来,遍历前几层,并通过将其设置为不可训练来冻结它们。在以下示例中,你将只冻结前
100
层:for layer in base_model.layers[:100]: layer.trainable = False
-
现在,你需要将自定义的顶层添加到基础模型中。在以下示例中,你将预测 20 个不同的类别,因此需要添加一个包含
20
个单元并使用 softmax 激活函数的全连接层:prediction_layer = tf.keras.layers.Dense(20, activation='softmax') model = tf.keras.Sequential([base_model, prediction_layer])
-
最后,你将编译并训练这个模型:
model.compile(loss='sparse_categorical_crossentropy', \ optimizer = tf.keras.optimizers.Adam(0.001)) model.fit(features_train, label_train, epochs=5)
这将显示一系列日志,如以下截图所示:
图 8.5:在预训练的 MobileNetV2 模型上进行微调的结果
就这样。你已经在一个预训练的 MobileNetV2 模型上进行了微调。你使用了 ImageNet 的前 100 个预训练权重,并仅根据你的数据集更新了从第 100 层开始的权重。
在接下来的活动中,你将实践刚刚学到的内容,并将微调应用于一个预训练的模型。
活动 8.01:使用微调进行水果分类
Fruits 360
数据集(arxiv.org/abs/1712.00580
),最初由Horea Muresan 和 Mihai Oltean, 通过深度学习从图像中识别水果,Acta Univ. Sapientiae, Informatica Vol. 10, Issue 1, pp. 26-42, 2018共享,包含超过 82,000 张 120 种不同类型水果的图像。你将使用该数据集的一个子集,其中包含超过 16,000 张图像。训练集和验证集的图像数量分别为11398
和4752
。
在这个活动中,你的任务是训练一个NASNetMobile
模型来识别不同种类的水果图像(分类为 120 个不同的类别)。你将使用微调来训练这个模型的最终层。
注意
数据集可以在这里找到:packt.link/OFUJj
。
以下步骤将帮助你完成此活动:
-
使用 TensorFlow 导入数据集并解压文件。
-
创建一个数据生成器,使用以下数据增强:
Rescale = 1./255, rotation_range = 40, width_shift_range = 0.1, height_shift_range = 0.1, shear_range = 0.2, zoom_range = 0.2, horizontal_flip = True, fill_mode = 'nearest
-
从 TensorFlow 加载一个预训练的
NASNetMobile
模型。 -
冻结模型的前
600
层。 -
在
NASNetMobile
上添加两个全连接层:– 一个全连接层,
Dense(1000, activation=relu)
– 一个全连接层,
Dense(120, activation='softmax')
-
指定一个学习率为
0.001
的 Adam 优化器。 -
训练模型。
-
在测试集上评估模型。
预期输出如下:
](https://github.com/OpenDocCN/freelearn-dl-pt3-zh/raw/master/docs/tf-ws/img/B16341_08_06.jpg)
图 8.6:活动的预期输出
注
该活动的解决方案可以通过这个链接找到。
现在你已经了解了如何使用来自 TensorFlow 的预训练模型,接下来你将学习如何从 TensorFlow Hub 获取模型。
TensorFlow Hub
TensorFlow Hub 是一个由 Google、NVIDIA 和 Kaggle 等出版商共享的 TensorFlow 模块库。TensorFlow 模块是基于 TensorFlow 构建的自包含模型,可以用于不同的任务。简单来说,它是一个外部集合,包含了用于迁移学习和微调的发布的 TensorFlow 模块。通过 TensorFlow Hub,你可以访问不同于直接通过 TensorFlow 核心 API 提供的深度学习模型或权重。
注
你可以在这里找到关于 TensorFlow Hub 的更多信息:tfhub.dev/
。
为了使用它,你首先需要安装它:
pip install tensorflow-hub
安装完成后,你可以使用 load()
方法加载可用的分类模型,并指定模块的链接:
import tensorflow_hub as hub
MODULE_HANDLE = 'https://tfhub.dev/tensorflow/efficientnet'\
'/b0/classification/1'
module = hub.load(MODULE_HANDLE)
在前面的例子中,你加载了EfficientNet B0模型,该模型是在 ImageNet 上训练的。你可以在 TensorFlow Hub 页面找到更多关于它的细节:tfhub.dev/tensorflow/efficientnet/b0/classification/1
。
注
TensorFlow Hub 提供了一个搜索引擎,帮助你找到特定的模块:tfhub.dev/s?subtype=module,placeholder
。
默认情况下,从 TensorFlow Hub 加载的模块包含模型的最终层,但没有激活函数。对于分类任务,你需要添加一个激活层。为此,你可以使用 Keras 的 Sequential API。只需使用 KerasLayer
类将模型转换为 Keras 层:
import tensorflow as tf
model = tf.keras.Sequential([
hub.KerasLayer(MODULE_HANDLE,input_shape=(224, 224, 3)),
tf.keras.layers.Activation('softmax')
])
然后,你可以使用最终模型进行预测:
model.predict(data)
你刚刚使用来自 TensorFlow Hub 的模型进行了迁移学习。这与之前使用 Keras API 学习的内容非常相似,在 Keras 中,你通过设置 include_top=True
加载了一个完整的模型。使用 TensorFlow Hub,你可以访问一个预训练模型库,用于物体检测或图像分割。
在下一部分,你将学习如何从 TensorFlow Hub 预训练模块中提取特征。
特征提取
TensorFlow Hub 提供了下载没有最终层的模型的选项。在这种情况下,你将使用 TensorFlow 模块作为特征提取器;你可以在其上设计自定义的最终层。在 TensorFlow Hub 中,用于特征提取的模块被称为特征向量:
import tensorflow_hub as hub
MODULE_HANDLE = 'https://tfhub.dev/google/efficientnet/b0'\
'/feature-vector/1'
module = hub.load(MODULE_HANDLE)
注意
若要查找 TensorFlow Hub 上所有可用的特征向量,你可以使用其搜索引擎:tfhub.dev/s?module-type=image-feature-vector&tf-version=tf2
。
一旦加载,你可以通过 Sequential API 将自己的最终层添加到特征向量上:
model = tf.keras.Sequential([
hub.KerasLayer(MODULE_HANDLE, input_shape=(224, 224, 3)),
tf.keras.layers.Dense(20, activation='softmax')
])
在前面的示例中,你添加了一个包含 20
单元的全连接层,并使用了 softmax 激活函数。接下来,你需要编译并训练你的模型:
model.compile(optimizer=optimizer, \
loss='sparse_categorical_crossentropy', \
metrics=['accuracy'])
model.fit(X_train, epochs=5)
至此,你已经使用了 TensorFlow Hub 上的特征向量,并添加了自定义的最终层,以便在你的数据集上训练最终的模型。
现在,测试一下你到目前为止所获得的知识,在下一活动中进行验证。
活动 8.02:使用 TensorFlow Hub 进行迁移学习
在此活动中,你需要通过迁移学习正确分类猫狗图片。与从头开始训练模型不同,你将从 TensorFlow Hub 获取EfficientNet B0特征向量,该特征向量包含了预计算的权重,可以识别不同类型的物体。
你可以在这里找到数据集:packt.link/RAAtm
。
以下步骤将帮助你完成此活动:
-
使用 TensorFlow 导入数据集并解压文件。
-
创建一个数据生成器,用于执行重缩放。
-
从 TensorFlow Hub 加载预训练的EfficientNet B0特征向量。
-
在特征向量上添加两个全连接层:
– 一个全连接层,使用
Dense(500, activation=relu)
– 一个全连接层,使用
Dense(1, activation='sigmoid')
-
指定一个学习率为
0.001
的 Adam 优化器。 -
训练模型。
-
在测试集上评估模型。
预期的输出如下:
图 8.7:活动的预期输出
预期的准确率应该在训练集和验证集上达到 1.0
左右。
注意
此活动的解决方案可以通过此链接找到。
总结
在本章中,你学习了两个非常重要的概念:迁移学习和微调。两者都帮助深度学习实践者利用现有的预训练模型,并将其适应自己的项目和数据集。
迁移学习是重复使用已在大型数据集(如 ImageNet,其中包含超过 1400 万张图像)上训练过的模型。TensorFlow 在其核心 API 中提供了这类预训练模型的列表。您还可以通过 TensorFlow Hub 访问来自谷歌和 NVIDIA 等知名出版商的其他模型。
最后,您进行了一些动手实践,微调了一个预训练模型。您学会了如何冻结模型的早期层,并根据输入数据集的特定要求仅训练最后几层。
这两种技术对社区来说是一项重大突破,因为它们为有意应用深度学习模型的任何人提供了访问先进模型的便利。
在接下来的章节中,您将研究另一种类型的模型架构,循环神经网络(RNNs)。这种类型的架构非常适合顺序数据,如时间序列或文本。
第九章:9. 循环神经网络
概述
在本章中,您将学习如何处理真实的序列数据。您将扩展对人工神经网络(ANN)模型和循环神经网络(RNN)架构的理解,以便训练序列数据。您还将学习如何构建一个具有 LSTM 层的 RNN 模型,用于自然语言处理。
到本章结束时,您将通过实际操作获得应用多个 LSTM 层构建 RNN 用于股票价格预测的经验。
引言
序列数据指的是每个数据点都依赖于前一个数据点的数据集。可以把它想象成一句话,由一系列彼此相关的单词组成。动词将与主语相关,副词则与动词相关。另一个例子是股票价格,其中某一天的价格与前几天的价格相关。传统的神经网络并不适合处理这类数据。存在一种特定类型的架构,可以处理数据序列。本章将介绍这种模型——即循环神经网络(RNN)。
RNN 模型是一种特定类型的深度学习架构,其中模型的输出会反馈到输入中。这类模型有其自身的挑战(称为消失梯度和爆炸梯度),这些问题将在本章稍后讨论。
在许多方面,RNN 代表了大脑可能如何工作。RNN 使用记忆来帮助它们学习。但是,信息只沿一个方向流动,它们怎么做到这一点呢?要理解这一点,您需要先回顾序列数据。这是一种需要工作记忆才能有效处理数据的数据类型。到目前为止,您只探索了非序列模型,如感知机或 CNN。在本章中,您将研究诸如 RNN、LSTM 或 GRU 之类的序列模型。
图 9.1:序列模型与非序列模型
序列数据
序列数据是按顺序发生并与过去和未来数据相关的信息。一个序列数据的例子是时间序列数据;就像你感知的时间一样,时间只朝一个方向流动。
假设您有一个球(如图 9.2所示),并且您想预测这个球接下来会朝哪个方向移动。如果您没有关于球被投掷的方向的任何先前信息,那么您只能猜测。然而,如果除了球的当前位置之外,您还拥有球之前位置的信息,问题就变得简单多了。为了能够预测球的下一个位置,您需要将之前位置的信息以序列(或有序)形式提供,以便对未来事件做出预测。
图 9.2:球的运动方向
循环神经网络(RNN)能够通过内部记忆帮助信息的顺序保持有效,从而发挥作用。
接下来我们将展示一些顺序数据的示例。
顺序数据的示例
顺序数据是一种特殊类型的数据,其中每一条信息的顺序很重要,它们彼此依赖。
顺序数据的一个例子是金融数据,比如股票价格。如果你想预测给定股票的未来数据值,就需要使用先前的时间值。事实上,你将在练习 9.01中进行股票预测,为顺序数据训练一个人工神经网络——Nvidia 股票预测。
音频和文本也可以视为顺序数据。音频可以分割成一系列声波,而文本可以分割成一系列字符或单词。声波或字符、单词序列应该按照顺序处理,以传达预期的结果。除了这两个日常遇到的例子外,顺序处理在很多其他领域也非常有用,比如分析医学信号(如脑电图)、预测股票价格以及推断和理解基因序列等。顺序数据有三种类型:
-
多对一 从多个输入产生一个输出。
-
一对多 从一个输入产生多个输出。
-
多对多 从多个输入产生多个输出。
图 9.3:顺序数据的分类
另一个例子是,假设你有一个语言模型,输入是一个句子或短语,你正在尝试预测接下来的单词,如下图所示:
图 9.4:句子示例
比如,你被给定了这句话 yesterday I took my car out for a…
,并且你想预测下一个单词是 drive
。你可以通过构建一个深度神经网络(如前馈神经网络)来实现这一点。然而,你会立即遇到一个问题:前馈网络只能接受固定长度的输入向量;你必须从一开始就指定输入的大小。
因此,你的模型需要能够处理可变长度输入的方式。你可以通过使用固定窗口来实现这一点。这意味着你强制将输入向量限制为特定的长度。例如,你可以将句子拆分为两个连续单词的组(也称为双元语法),然后预测下一个单词。这意味着,无论你尝试在哪个位置进行下一步预测,模型仅会将前两个单词作为输入。你需要考虑如何以数字的形式表示这些数据。一种方法是使用固定长度的向量,并为第一个单词分配一些空间,为第二个单词分配一些空间。在这些空间中,编码每个单词的身份。然而,这种方法存在问题。
为什么?因为你只使用了部分可用信息(即仅仅是两个连续的单词)。你只能访问有限的数据窗口,这不足以提供足够的上下文来准确预测下一个单词。这意味着你无法有效地建模长期依赖性。这在像图 9.5中的句子中非常重要,在那里你显然需要来自句子早期的信息,以便准确预测下一个单词。
图 9.5:句子示例
如果你只看过去的两个或三个单词,你就无法做出下一个预测,而你知道这个预测是意大利语
。因此,这意味着你确实需要一种方法来整合从句子开始到结束的所有信息。
为了做到这一点,你可以使用一组计数作为固定长度向量,并使用整个句子。这种方法被称为词袋模型。
你有一个固定长度的向量,不管句子的身份是什么,但不同之处在于,添加了对这个词汇表的计数。你可以将其作为输入传递给模型,以生成预测结果。
然而,这里还有一个大问题。仅仅使用计数意味着你失去了所有的顺序信息以及所有关于前历史的信息。
请参考图 9.6。所以,这两句话,尽管语义完全相反,但在这种词袋格式中会有完全相同的表示。因为它们有完全相同的单词列表,只是顺序不同。所以,显然,这种方法行不通。另一个想法是简单地扩展固定窗口。
图 9.6:词袋示例
现在,考虑图 9.7。你可以这样表示你的句子,将句子输入到模型中,并生成预测。问题是,如果你将这个向量输入到一个前馈神经网络中,这些输入(yesterday I took my car
)每个都会有一个单独的权重与网络连接。因此,如果你反复看到句子开头的单词yesterday
,网络可能会学会yesterday
代表一个时间或环境。然而,如果yesterday
突然出现在固定长度向量的后面,在句子的末尾,网络可能会很难理解yesterday
的含义。这是因为位于向量末尾的参数可能以前从未见过yesterday
这个词,而句子开头的参数没有在整个序列中共享。
图 9.7:句子示例
因此,你需要能够处理可变长度的输入和长期依赖,跟踪顺序,并且具有可以在整个序列中共享的参数。具体来说,你需要开发能够执行以下操作的模型:
-
处理可变长度的输入序列。
-
跟踪数据中的长期依赖。
-
保持序列顺序的信息。
-
在整个序列中共享参数。
如何在信息仅朝一个方向流动的模型中做到这一点?你需要一种不同的神经网络。你需要一个递归模型。你将在以下练习中实践处理顺序数据。
练习 9.01:为顺序数据训练 ANN——Nvidia 股票预测
在本练习中,你将构建一个简单的人工神经网络(ANN)模型来预测 Nvidia 股票价格。但与之前章节的示例不同,这次输入数据是顺序的。因此,你需要手动处理一些数据,创建一个数据集,该数据集将包含给定日期的股票价格作为目标变量,并且将前 60 天的价格作为特征。你需要在2019-01-01
之前和之后将数据分为训练集和测试集。
注意
你可以在这里找到NVDA.csv
数据集:packt.link/Mxi80
。
-
打开一个新的 Jupyter 或 Colab 笔记本。
-
导入所需的库。使用
numpy
进行计算,matplotlib
用于绘制可视化,pandas
帮助处理数据集,MinMaxScaler
用于将数据集缩放到 0 和 1 之间:import numpy as np import matplotlib.pyplot as plt import pandas as pd from sklearn.preprocessing import StandardScaler, MinMaxScaler
-
使用
read_csv()
函数读取 CSV 文件,并将数据集存储在 pandas 的 DataFrame 中,命名为data
,以便进行操作:import io data = pd.read_csv('NVDA.csv')
-
对你的数据调用
head()
函数,查看 DataFrame 的前五行:data.head()
你应该得到以下输出:
图 9.8:输出的前五行
上表展示了原始数据。你可以看到,每一行代表一天,包含了当天开盘价和收盘价、最高价、最低价以及调整后的收盘价(例如考虑到股息或股票拆分等因素)。
-
现在,拆分训练数据。使用
Date
列中所有 2019 年 1 月 1 日之前的数据作为训练数据。将其保存为data_training
。通过使用copy()
方法,将其保存在单独的文件中:data_training = data[data['Date']<'2019-01-01'].copy()
-
现在,拆分测试数据。使用
Date
列中所有 2019 年 1 月 1 日及之后的数据,将其保存为data_test
。通过使用copy()
方法,将其保存在单独的文件中:data_test = data[data['Date']>='2019-01-01'].copy()
-
使用
drop()
方法删除数据框中的Date
和Adj Close
列。记住,你使用了Date
列来划分训练集和测试集,因此不需要日期信息。使用axis = 1
来指定你还希望删除列标签。为了确保操作成功,调用head()
函数查看数据框的前五行:training_data = data_training.drop\ (['Date', 'Adj Close'], axis = 1) training_data.head()
你应该得到如下输出:
图 9.9:新的训练数据
这是你在删除那两列之后应该得到的输出:
-
创建一个来自
MinMaxScaler
的缩放器,将training_data
缩放到 0 和 1 之间。使用fit_transform
函数将模型拟合到数据并根据拟合的模型转换数据:scaler = MinMaxScaler() training_data = scaler.fit_transform(training_data) training_data
你应该得到如下输出:
图 9.10:已缩放的训练数据
-
将数据拆分为
X_train
和y_train
数据集:X_train = [] y_train = []
-
检查
training_data
的形状:training_data.shape[0]
你应该得到如下输出:
868
你可以看到训练集包含 868 个观测值。
-
创建一个训练数据集,包含过去 60 天的股价数据,这样你就可以预测第 61 天的收盘价。在这里,
X_train
将有两列。第一列将存储从 0 到 59 的值,第二列将存储从 1 到 60 的值。在y_train
的第一列存储第 61 个值(索引为 60),在第二列存储第 62 个值(索引为 61)。使用for
循环创建 60 个时间步的数据:for i in range(60, training_data.shape[0]): X_train.append(training_data[i-60:i]) y_train.append(training_data[i, 0])
-
将
X_train
和y_train
转换为 NumPy 数组:X_train, y_train = np.array(X_train), np.array(y_train)
-
对
X_train
和y_train
调用shape()
函数:X_train.shape, y_train.shape
你应该得到如下输出:
((808, 60, 5), (808,))
上面的代码片段显示,准备好的训练集包含
808
个观测值,数据集包含 60 天的五个特征(Open
、Low
、High
、Close
和Volume
)。 -
将数据转换为形状为样本矩阵(每个样本的样本数和特征数)的二维矩阵。将 60 天的所有特征堆叠在一起,得到输出形状为
(808, 300)
。使用以下代码完成此操作:X_old_shape = X_train.shape X_train = X_train.reshape(X_old_shape[0], \ X_old_shape[1]*X_old_shape[2]) X_train.shape
你应该得到如下输出:
(808, 300)
-
现在,构建一个 ANN。你需要一些额外的库来实现这一点。使用
Sequential
来初始化神经网络,Input
来添加输入层,Dense
来添加全连接层,Dropout
来帮助防止过拟合:from tensorflow.keras import Sequential from tensorflow.keras.layers import Input, Dense, Dropout
-
通过调用
regressor_ann = Sequential()
初始化神经网络。regressor_ann = Sequential()
-
添加一个输入层,
shape
设置为300
:regressor_ann.add(Input(shape = (300,)))
-
然后,添加第一层全连接层,设置为
512
个单元,这将是你输出空间的维度。使用 ReLU 激活函数。最后,添加一个丢弃层,在训练过程中移除 20%的单元,以防止过拟合:regressor_ann.add(Dense(units = 512, activation = 'relu')) regressor_ann.add(Dropout(0.2))
-
添加另一个具有
128
个单元的全连接层,使用 ReLU 作为激活函数,丢弃率为0.3
:regressor_ann.add(Dense(units = 128, activation = 'relu')) regressor_ann.add(Dropout(0.3))
-
添加另一个具有
64
个单元的全连接层,使用 ReLU 作为激活函数,丢弃率为0.4
:regressor_ann.add(Dense(units = 64, activation = 'relu')) regressor_ann.add(Dropout(0.4))
-
再次添加一个具有
128
个单元的全连接层,使用 ReLU 作为激活函数,丢弃率为0.3
:regressor_ann.add(Dense(units = 16, activation = 'relu')) regressor_ann.add(Dropout(0.5))
-
添加一个具有一个单元的最终全连接层:
regressor_ann.add(Dense(units = 1))
-
查看模型的摘要:
regressor_ann.summary()
你将获得关于模型层和参数的宝贵信息。
图 9.11: 模型摘要
-
使用
compile()
方法配置你的模型以进行训练。选择 Adam 作为优化器,并使用均方误差作为损失函数的度量标准:regressor_ann.compile(optimizer='adam', \ loss = 'mean_squared_error')
-
最后,拟合你的模型并设置训练周期为
10
。将批量大小设置为32
:regressor_ann.fit(X_train, y_train, epochs=10, batch_size=32)
你应该得到以下输出:
图 9.12: 训练模型
-
测试并预测股票价格,并准备数据集。通过调用
head()
方法检查数据:data_test.head()
你应该得到以下输出:
图 9.13: DataFrame 的前五行
-
使用
tail(60)
方法创建一个past_60_days
变量,包含训练集中最后 60 天的数据。将past_60_days
变量通过append()
函数添加到测试数据中,并将ignore_index
设置为True
:past_60_days = data_training.tail(60) df = past_60_days.append(data_test, ignore_index = True)
-
现在,通过重复你在步骤 8到15中的操作,准备你的测试数据进行预测:
df = df.drop(['Date', 'Adj Close'], axis = 1) inputs = scaler.transform(df) X_test = [] y_test = [] for i in range(60, inputs.shape[0]): X_test.append(inputs[i-60:i]) y_test.append(inputs[i, 0]) X_test, y_test = np.array(X_test), np.array(y_test) X_old_shape = X_test.shape X_test = X_test.reshape(X_old_shape[0], \ X_old_shape[1] * X_old_shape[2]) X_test.shape, y_test.shape
你应该得到以下输出:
((391, 300), (391,))
-
对你的股票价格进行一些预测,通过对
X_test
调用predict()
方法进行测试:y_pred = regressor_ann.predict(X_test)
-
在查看结果之前,先使用你之前导入的
StandardScaler
工具类中的scaler.scale_
方法反转之前的缩放操作,这样输出的数值将处于正确的尺度:scaler.scale_
你应该得到以下输出:
图 9.14: 使用 StandardScaler
-
使用前面数组中的第一个值来设置你的缩放比例,为
y_pred
和y_test
的乘法做好准备。回想一下,你正在将数据从早期的缩放状态中转换回来,当时你将所有值转换为 0 到 1 之间:scale = 1/3.70274364e-03 scale
你应该得到以下输出:
270.0700067909643
-
将
y_pred
和y_test
乘以scale
,以将数据转换回正确的数值:y_pred = y_pred*scale y_test = y_test*scale
-
回顾真实的 Nvidia 股票价格和你的预测:
plt.figure(figsize=(14,5)) plt.plot(y_test, color = 'black', label = "Real NVDA Stock Price") plt.plot(y_pred, color = 'gray',\ label = 'Predicted NVDA Stock Price') plt.title('NVDA Stock Price Prediction') plt.xlabel('time') plt.ylabel('NVDA Stock Price') plt.legend() plt.show()
你应该得到以下输出:
](https://github.com/OpenDocCN/freelearn-dl-pt3-zh/raw/master/docs/tf-ws/img/B16341_09_15.jpg)
图 9.15:真实的 Nvidia 股价与你的预测值
在前面的图表中,你可以看到你训练的模型能够捕捉到一些 Nvidia 股价的趋势。可以观察到,预测值与真实值相差很大。从这个结果可以明显看出,人工神经网络并不适合处理顺序数据。
在这个练习中,你看到了简单的人工神经网络无法处理顺序数据的局限性。在下一节中,你将了解递归神经网络,它们被设计用来从顺序数据的时间维度中学习。然后,在练习 9.02,构建具有 LSTM 层的 RNN 进行 Nvidia 股票预测中,你将使用 RNN 对相同的 Nvidia 股价数据集进行预测,并比较你的结果。
递归神经网络
递归型神经网络的第一个形式是由 John Hopfield 在 1982 年创建的。他有两个动机来做这件事:
-
数据的顺序处理
-
神经连接建模
本质上,RNN 在每个时间步骤处理输入数据,并将信息存储在其内存中,这些信息将用于下一步。信息首先被转换成向量,以便机器进行处理。然后,RNN 逐一处理向量序列。在处理每个向量时,它会传递前一个隐藏状态。隐藏状态保留了来自前一步的信息,充当一种记忆。它通过结合输入和前一个隐藏状态与 tanh 函数来完成这一过程,该函数将值压缩在-1
和1
之间。
本质上,这就是 RNN 的功能。RNN 不需要大量计算,并且在处理短序列时效果很好。
](https://github.com/OpenDocCN/freelearn-dl-pt3-zh/raw/master/docs/tf-ws/img/B16341_09_16.jpg)
图 9.16:RNN 数据流
现在将注意力转向将神经网络应用于涉及数据顺序处理的问题。你已经稍微了解过为什么这些任务需要与之前所见的网络架构有根本不同的原因。
RNN 架构
本节将介绍 RNN 的关键原理,它们与之前所学的内容有何根本不同,以及 RNN 计算是如何工作的。
但在此之前,先回顾一下之前讨论过的标准前馈神经网络。
在前馈神经网络中,数据只在一个方向上传播,即从输入到输出。
因此,你需要一种不同的网络架构来处理顺序数据。RNN 特别适合处理一系列输入的情况,而不是单一输入。对于那些需要将数据序列传播以获得单一输出的问题来说,RNN 非常适用。
举个例子,假设你正在训练一个模型,输入是一个单词序列,输出是与该序列相关的情感。同样,考虑那些情况,在这些情况下,你不仅仅返回一个输出,而是拥有一个输入序列,并将其传播通过你的网络,其中序列中的每个时间步都会生成一个输出。
简单来说,RNN 是一种网络,它提供了一种机制,可以使先前处理过的数据在时间上持续存在,并利用这些数据进行未来的预测。
图 9.17:RNN 计算
在上面的图中,在某个时间步 t
,RNN 接收 X
t 作为输入,并在该时间步计算预测值 Y
t,这就是网络的输出。
除了这个输出外,它还保存了一个内部状态,叫做更新 H
t。这个时间步 t
的内部状态随后可以用来补充下一个时间步 t+1
的输入。所以,基本上,它为下一个步骤提供了前一个步骤的信息。这个机制被称为 递归,因为信息在网络内从一个时间步传递到下一个时间步。
这里究竟发生了什么呢?这是通过使用一个简单的递归关系来处理顺序数据。RNN 保持内部状态 H
t,并将其与下一个输入数据 X
t+1 结合,做出预测 Y
t+1,并存储新的内部状态 H
t+1。关键的思想是,状态更新是前一个状态时间步与当前网络接收到的输入的组合。
需要注意的是,在这个计算过程中,使用的是相同的 f
函数和 W
,以及每个时间步都使用相同的一组参数,而这些参数是在训练过程中学习得到的。为了更好地理解这些网络是如何工作的,可以逐步执行 RNN 算法:
-
你首先需要初始化你的 RNN 以及该网络的隐藏状态。你可以指定一个句子,并基于该句子预测下一个单词。RNN 计算实际上就是它们循环处理这个句子中的每个单词。
-
在每个时间步,你将当前正在考虑的单词以及 RNN 的上一个隐藏状态一起输入到网络中。然后,它可以根据这些信息预测序列中的下一个单词,并用来更新其隐藏状态。
-
最后,在你循环完句子中的所有单词后,你对缺失单词的预测就是该最终时间步的 RNN 输出。
如下图所示,这个 RNN 计算包含了内部状态的更新以及正式的输出向量。
图 9.18:RNN 数据流
给定输入向量 X
t,RNN 应用一个函数来更新它的隐藏状态。这个函数只是一个标准的神经网络操作,它包括乘以权重矩阵和应用非线性激活函数。关键的区别在于,在这种情况下,你同时将输入向量 X
t 和前一个状态作为输入传递给这个函数 H
t-1。
接下来,你应用一个非线性激活函数,如 tanh,来处理上一步的输出。你有这两个权重矩阵,最后,在给定时间步的输出 y
t 就是这个内部状态的一个修改和转化版本。
当你遍历完句子中的所有单词后,你对缺失单词的预测就是在所有单词通过模型后,RNN 在最后一个时间步的输出。如前所述,RNN 计算包括内部状态更新和正式的输出向量。
另一种表示 RNN 的方式是将其模块沿时间展开。你可以将 RNN 看作是多个相同网络的副本,每个副本将信息传递给它的后代。
图 9.19:带时间的计算图
在这种表示方式中,你可以明确显示你的权重矩阵,从变换输入的权重开始,到 H
权重,它们用于将前一个隐藏状态转换为当前隐藏状态,最后是将隐藏状态转换为输出。
需要注意的是,你在每个时间步使用相同的权重矩阵。从这些输出中,你可以在每个时间步计算损失。损失的计算将完成你的网络前向传播。最后,要定义总损失,你只需将所有时间步的损失相加。由于你的损失依赖于每个时间步,这意味着在训练网络时,你还必须将时间作为一个组件来考虑。
现在,你对这些 RNN 是如何构建和运作的有了一些基本的了解,可以逐步演示如何从头开始在 TensorFlow 中实现一个简单的 RNN。
以下代码段使用了来自 keras.models.Sequential
的简单 RNN。你将单元数指定为 1
,并将第一个输入维度设置为 None
,因为 RNN 可以处理任何数量的时间步。简单的 RNN 默认使用 tanh 激活函数:
model = keras.models.Sequential([
keras.layers.SimpleRNN\
(1, input_shape=[None, 1])
])
前面的代码创建了一个只有一个神经元的单层网络。
这很简单。现在你需要堆叠一些额外的递归层。代码类似,但这里有一个关键区别。你会注意到,除了最后一层外,所有层都有 return_sequences=True
。这是为了确保输出是一个三维数组。如你所见,前两层每层都有 20
个单元:
model = keras.models.Sequential\
([Keras.layers.SimpleRNN\
(20, return_sequences=True, input_shape=[None, 1]), \
Keras.layers.SimpleRNN(20, return_sequences=True), \
Keras.layers.SimpleRNN(1)])
RNN 被定义为一个层,你可以通过继承层类来构建它。你还可以将权重矩阵和 RNN 单元的隐藏状态初始化为零。
这里的关键步骤是定义调用函数,它描述了如何在给定输入X
的情况下,通过网络进行前向传播。为了分解这个调用函数,你首先需要根据之前讨论的方程更新隐藏状态。
取上一个隐藏状态和输入X
,将它们与相关的权重矩阵相乘,求和后通过一个非线性函数,如双曲正切(tanh),然后传递下去。
然后,输出只是隐藏状态的变换版本,在每个时间步,你返回当前的输出和更新后的隐藏状态。
TensorFlow 通过内置的全连接层使得这一过程变得简单。RNN 也适用同样的情况。TensorFlow 已经实现了这些类型的 RNN 单元,使用简单的 RNN 层。但这种类型的层存在一些限制,比如梯度消失问题。在接下来的章节中,你将深入探讨这个问题,然后再研究不同类型的递归层。
梯度消失问题
如果仔细观察梯度在这个重复模块链中的流动,你会发现,在每个时间步之间,你需要进行矩阵乘法。这意味着,梯度的计算——即关于参数的损失函数的导数,追溯到你的初始状态——需要多次进行这个权重矩阵的乘法运算,以及反复使用激活函数的导数。
你可能会遇到两种特别有问题的情况:梯度爆炸问题或梯度消失问题。
梯度爆炸问题是指梯度由于矩阵乘法操作变得越来越大,导致无法再进行优化。缓解这个问题的一种方法是执行所谓的梯度裁剪。这相当于缩放大梯度,使它们的值变小并接近1
。
你也可能遇到相反的问题,即梯度过小。这就是所谓的梯度消失问题。当你进行这些重复的乘法时,梯度会变得越来越小(接近0
),以至于你无法继续训练网络。这在训练 RNN 时是一个非常真实的问题。
例如,考虑一种情况,你不断将一个数字与介于零和一之间的数字相乘。随着不断重复这个过程,这个数字会不断缩小,直到最终消失变为 0。当这种情况发生在梯度上时,很难将误差传播到更远的过去,因为梯度越来越小。
考虑之前的语言模型示例,你在尝试预测下一个单词。如果你要预测以下短语中的最后一个单词,那么接下来的单词是什么就比较明确了。关键的相关信息之间没有太大的间隔,比如单词“鱼”和需要预测的位置之间。
图 9.20:词预测
然而,也有一些情况需要更多的上下文信息,比如以下的例子。句子开头的信息,她住在西班牙
,表明紧接在她说流利的
之后的单词很可能是某种语言的名称,西班牙语
。
图 9.21:句子示例
但是,你需要句子开头的上下文信息,即西班牙
,才能填补相关的空白并确定正确的语言。随着语义上重要的单词之间的空隙增大,RNN(循环神经网络)变得越来越无法连接这些点并将这些相关信息联系起来。这就是梯度消失问题的表现。
如何缓解这个问题呢?第一个技巧很简单。你可以选择 tanh 或 sigmoid 作为激活函数。这两种函数的导数都小于1
。
另一个可以使用的简单技巧是初始化网络参数的权重。事实证明,将权重初始化为单位矩阵有助于在反向传播过程中防止它们迅速收敛到零。
但最终最稳健的解决方案是使用一个稍微复杂一点的循环单元,它可以更有效地跟踪数据中的长期依赖关系。它通过控制哪些信息被传递,哪些信息被用来更新其内部状态来实现这一点。具体而言,这就是“门控单元”的概念,类似于 LSTM 层,这是下一节的重点。
长短期记忆网络
LSTM(长短期记忆网络)非常适合学习长期依赖关系并克服梯度消失问题。它们是处理序列数据的高效模型,在深度学习领域广泛使用。
LSTM 具有链式结构。在 LSTM 中,重复单元包含不同的交互层。关键点是,这些层相互作用,选择性地控制信息在单元内的流动。
LSTM 的关键构建块是称为“门”的结构,它使 LSTM 能够选择性地向其单元状态中添加或删除信息。门由一个神经网络层组成,例如 sigmoid 层。
图 9.22:LSTM 架构
花点时间思考一下这样的门在 LSTM 中会执行什么操作。在这种情况下,sigmoid 函数会强制其输入值保持在0
到1
之间。你可以将这个机制视为捕捉通过门传递的信息应该保留多少。它介于零和一之间。这有效地控制了信息的流动。
LSTM 通过四个简单的步骤处理信息:
-
LSTM 的第一步是决定将从单元状态中丢弃哪些信息,以忘记无关的历史信息。这是先前内部状态
H
t-1 和输入X
t 的函数,因为其中一些信息可能不重要。 -
接下来,LSTM 决定哪些部分的新信息是相关的,并将其用于将信息存储在单元状态中。
-
然后,它结合了先前信息中的相关部分以及当前输入,利用这些信息选择性地更新其单元状态。
-
最后,它返回一个输出,这就是输出门,控制哪些编码在单元状态中的信息被发送到网络。[图 9.23:LSTM 处理步骤]
](https://github.com/OpenDocCN/freelearn-dl-pt3-zh/raw/master/docs/tf-ws/img/B16341_09_23.jpg)
图 9.23:LSTM 处理步骤
LSTM 的关键点是它们调节信息流动和存储的顺序。再次说明,LSTM 的操作过程如下:
-
忘记无关的历史信息
-
存储新的和重要的信息
-
使用其内部记忆来更新内部状态
-
生成输出
LSTM 的一个重要特性是,所有这些不同的门控和更新机制共同作用,创建了一个内部单元状态C
,它允许梯度在时间中不间断地流动。你可以将其看作是一条单元状态的高速公路,梯度可以在其中不受阻碍地流动。这使得你能够缓解和减轻标准 RNN 中出现的梯度消失问题。
LSTM 能够独立于输出维持这个独立的单元状态,并通过使用门控来控制信息流动,通过忘记无关的历史信息、存储相关的新信息、选择性更新其单元状态,然后返回一个经过过滤的版本作为输出。
在训练和 LSTM 的应用中,关键点是保持独立的单元状态,使得 LSTM 可以高效地进行时间反向传播,稍后会进一步讨论。
现在你已经了解了 RNN 的基本工作原理、时间反向传播算法以及一些 LSTM 架构的内容,你可以在以下示例中应用这些概念。
请考虑以下 LSTM 模型:
regressor = Sequential()
regressor.add(LSTM(units= 50, activation = 'relu', \
return_sequences = True, \
input_shape = (X_train.shape[1], 5)))
regressor.add(Dropout(0.2))
regressor.add(LSTM(units= 60, activation = 'relu', \
return_sequences = True))
regressor.add(Dropout(0.3))
regressor.add(LSTM(units= 80, activation = 'relu', \
return_sequences = True))
regressor.add(Dropout(0.4))
regressor.add(LSTM(units= 120, activation = 'relu'))
regressor.add(Dropout(0.5))
regressor.add(Dense(units = 1))
首先,你通过调用regressor = Sequential()
初始化了神经网络。同样需要注意的是,在最后一行中,你省略了return_sequences = True
,因为这是最终输出:
regressor = Sequential()
接着,添加 LSTM 层。首先,将 LSTM 层设置为50
个单元。使用 relu 激活函数,并指定训练集的形状。最后,添加 dropout 层,使用regressor.add(Dropout(0.2))
。0.2
表示 20%的层会被移除。设置return_sequences = True
,以便返回最后一个输出。
同样,向 LSTM 模型中添加三个 LSTM 层和一个全连接层。
现在你已经熟悉了处理顺序数据的基本概念,接下来是时候使用一些真实数据来完成以下练习了。
练习 9.02:构建带有 LSTM 层的 RNN – 英伟达股价预测
在本次练习中,你将使用与练习 9.01相同的数据集,即为顺序数据训练 ANN – 英伟达股价预测。你将继续尝试基于过去 60 天的数据预测英伟达的股价。但这次,你将训练一个 LSTM 模型。你需要在和2019-01-01
日期之前和之后将数据拆分为训练集和测试集。
注意
你可以在这里找到NVDA.csv
数据集:packt.link/Mxi80
。
你需要像在练习 9.01中一样准备数据集,即为顺序数据训练 ANN – 英伟达股价预测(步骤 1至步骤 15),然后再应用以下代码:
-
开始构建 LSTM。你需要一些额外的库来实现。使用
Sequential
初始化神经网络,使用Dense
添加全连接层,使用LSTM
添加 LSTM 层,并使用Dropout
来帮助防止过拟合:from tensorflow.keras import Sequential from tensorflow.keras.layers import Dense, LSTM, Dropout
-
通过调用
regressor = Sequential()
来初始化神经网络。添加四个 LSTM 层,分别为50
、60
、80
和120
个单元。使用 ReLU 激活函数,并将return_sequences
设置为True
,除了最后一个 LSTM 层。为第一个 LSTM 层提供训练集的形状。最后,添加 dropout 层,分别使用 20%、30%、40%和 50%的丢弃率:regressor = Sequential() regressor.add(LSTM(units= 50, activation = 'relu',\ return_sequences = True,\ input_shape = (X_train.shape[1], 5))) regressor.add(Dropout(0.2)) regressor.add(LSTM(units= 60, activation = 'relu', \ return_sequences = True)) regressor.add(Dropout(0.3)) regressor.add(LSTM(units= 80, activation = 'relu', \ return_sequences = True)) regressor.add(Dropout(0.4)) regressor.add(LSTM(units= 120, activation = 'relu')) regressor.add(Dropout(0.5)) regressor.add(Dense(units = 1))
-
使用
summary()
方法检查模型的摘要:regressor.summary()
你应该得到以下输出:
图 9.24:模型摘要
如前图所示,摘要提供了有关所有模型层和参数的有价值信息。这是确保层次顺序正确,且它们具有适当的输出形状和参数的好方法。
-
使用
compile()
方法配置你的模型以进行训练。选择 Adam 作为优化器,使用均方误差作为损失函数的度量:regressor.compile(optimizer='adam', loss = 'mean_squared_error')
-
拟合你的模型,并设置运行
10
个 epoch。将批次大小设置为32
:regressor.fit(X_train, y_train, epochs=10, batch_size=32)
你应该得到以下输出:
图 9.25:训练模型
-
测试并预测股价,并准备数据集。通过调用
head()
函数检查你的数据:data_test.head()
你应该得到以下输出:
图 9.26:DataFrame 的前五行
-
调用
tail(60)
方法查看最后 60 天的数据。你将在下一步中使用这些信息:data_training.tail(60)
你应该得到以下输出:
图 9.27:DataFrame 的最后 10 行
-
使用
tail(60)
方法创建past_60_days
变量:past_60_days = data_training.tail(60)
-
使用
append()
函数将past_60_days
变量添加到你的测试数据中。将True
设置为ignore_index
。删除Date
和Adj Close
列,因为你不需要这些信息:df = past_60_days.append(data_test, ignore_index = True) df = df.drop(['Date', 'Adj Close'], axis = 1)
-
检查 DataFrame,确保你已经成功删除了
Date
和Adj Close
列,可以通过使用head()
函数来验证:df.head()
你应该得到以下输出:
图 9.28:检查 DataFrame 的前五行
-
使用
scaler.transform
来自StandardScaler
对输入数据进行标准化:inputs = scaler.transform(df) inputs
你应该得到以下输出:
图 9.29:DataFrame 标准化
从前面的结果来看,你可以看到标准化后,所有的值现在都接近
0
。 -
将你的数据分割成
X_test
和y_test
数据集。创建一个测试数据集,其中包含前 60 天的股票价格,以便你可以测试第 61 天的收盘股票价格。在这里,X_test
将有两列。第一列将存储从 0 到 59 的值,第二列将存储从 1 到 60 的值。在y_test
的第一列,存储第 61 个值(索引为 60),在第二列,存储第 62 个值(索引为 61)。使用for
循环在 60 个时间步骤内创建数据:X_test = [] y_test = [] for i in range(60, inputs.shape[0]): X_test.append(inputs[i-60:i]) y_test.append(inputs[i, 0])
-
将
X_test
和y_test
转换为 NumPy 数组:X_test, y_test = np.array(X_test), np.array(y_test) X_test.shape, y_test.shape
你应该得到以下输出:
((391, 60, 5), (391,))
上述结果显示共有
391
个观测值,对于每一个观测值,你有过去60
天的以下五个特征数据:Open
、High
、Low
、Close
和Volume
。目标变量则包含391
个值。 -
通过调用
regressor.predict(X_test)
测试一些股票价格的预测:y_pred = regressor.predict(X_test)
-
在查看结果之前,使用
StandardScaler
工具类中导入的scaler.scale_
,反转你之前进行的缩放操作,以便得到正确尺度的输出:scaler.scale_
你应该得到以下输出:
图 9.30:使用 StandardScaler
-
使用前面数组中的第一个值来设置你的缩放值,为乘法运算
y_pred
和y_test
做准备。回想一下,你正在将数据从之前将所有值转换到 0 和 1 之间的缩放状态中转换回来:scale = 1/3.70274364e-03 scale
你应该得到以下输出:
270.0700067909643
-
将
y_pred
和y_test
乘以scale
以将数据转换回正确的值:y_pred = y_pred*scale y_test = y_test*scale
-
使用
y_pred
查看 NVIDIA 股票的预测结果:y_pred
你应该得到以下输出:
图 9.31:检查预测
上述结果显示了预测的未来日期的 Nvidia 股票价格。
-
绘制实际的 Nvidia 股票价格和你的预测:
plt.figure(figsize=(14,5)) plt.plot(y_test, color = 'black', label = "Real NVDA Stock Price") plt.plot(y_pred, color = 'gray',\ label = 'Predicted NVDA Stock Price') plt.title('NVDA Stock Price Prediction') plt.xlabel('time') plt.ylabel('NVDA Stock Price') plt.legend() plt.show()
你应该得到以下输出:
图 9.32:NVIDIA 股票价格可视化
正如在 图 9.32 中的灰色线所示,与你的预测模型相比,实际的股票价格(由黑线表示)显示你的预测模型相当准确。
在本练习中,你构建了一个带有 LSTM 层的 RNN 模型,用于 Nvidia 股票预测,并完成了训练、测试和预测步骤。
现在,测试你在本章中所学到的知识,完成以下活动。
活动 9.01:构建一个具有多个 LSTM 层的 RNN 来预测电力消耗
household_power_consumption.csv
数据集包含一个家庭四年内,每分钟采样率的电力消耗测量数据。你需要基于之前的测量值预测给定分钟的电力消耗。
你的任务是调整一个带有额外 LSTM 层的 RNN 模型,以便按分钟级别预测家庭电力消耗。你将构建一个包含三层 LSTM 的 RNN 模型。
注意
你可以在这里找到数据集:packt.link/qrloK
。
执行以下步骤以完成此活动:
-
加载数据。
-
通过将
Date
和Time
列合并成一个单一的Datetime
列来准备数据,然后可以使用这个列来排序数据并填补缺失值。 -
对数据进行标准化,并删除
Date
、Time
、Global_reactive_power
和Datetime
列,因为它们在预测中不需要。 -
对给定的分钟数据进行重塑,包含前 60 分钟的值。
-
将数据分为训练集和测试集,分别为索引
217440
之前和之后的数据,该索引对应最后一个月的数据。 -
定义并训练一个由三层不同 LSTM 层组成的 RNN 模型,分别为
20
、40
和80
单元,后接50%
的 dropout 和 ReLU 作为激活函数。 -
使用训练好的模型对测试集进行预测。
-
将预测值与整个数据集中的实际值进行比较。
你应该得到以下输出:
图 9.33:活动 9.01 的预期输出
注意
这个活动的解决方案可以通过此链接找到。
在下一节中,你将学习如何将 RNN 应用于文本。
自然语言处理
自然语言处理(NLP)是一个快速发展的领域,既充满挑战又具有回报。NLP 将传统上对机器来说很难理解的有价值数据转化为可以使用的信息。这些数据可以是句子、单词、字符、文本和音频等形式。为什么对机器来说这是一个如此困难的任务?为了回答这个问题,请考虑以下例子。
记住这两句话:it is what it is 和 is it what it is。这两句话虽然语义完全相反,但在这种词袋模型格式中,它们会有完全相同的表示。这是因为它们包含完全相同的单词,只是顺序不同。因此,你知道需要使用顺序模型来处理这个问题,但还有什么需要注意的呢?有几种工具和技术已经被开发出来来解决这些问题。但在此之前,你需要学习如何预处理顺序数据。
数据预处理
简单回顾,预处理通常包含训练模型所需的所有步骤。一些常见的步骤包括数据清洗、数据转换和数据降维。对于自然语言处理,更具体地说,这些步骤可能包括以下所有、部分或没有:
-
标记化
-
填充
-
小写转换
-
移除停用词
-
移除标点符号
-
词干提取
以下部分提供了对你将要使用的步骤的更深入描述。目前,这是每个步骤的概述:
-
数据集清理包括将大小写转换为小写、移除标点符号等。
-
标记化是将字符序列分割成指定单元的过程,这些单元称为标记(tokens)。
-
填充是通过填充使输入句子大小相同的一种方法。填充序列意味着确保序列具有统一的长度。
-
词干提取是将单词截断到它们的词干。例如,单词“rainy”和“raining”都具有词干“rain”。
数据集清理
在这里,你创建了一个 clean_text
函数,它在清理后返回一个包含单词的列表。你将所有文本转换为小写并使用 lower()
方法保存,并使用 utf8
编码进行字符标准化:
def clean_text(txt):
txt = "".join(v for v in txt if v not in string.punctuation)\
.lower()
txt = txt.encode("utf8").decode("ascii",'ignore')
return txt
corpus = [clean_text(x) for x in all_headlines]
生成序列和标记化
TensorFlow 提供了一个专门的类来生成 N-gram 标记序列 —— Tokenizer
来自 keras.preprocessing.text
:
from keras.preprocessing.text import Tokenizer
tokenizer = Tokenizer()
一旦你实例化了 Tokenizer()
,你可以使用 fit_on_texts()
方法从语料库中提取标记。此步骤会为语料库中的每个唯一单词分配一个整数索引:
tokenizer.fit_on_texts(corpus)
在标记化器经过语料库训练后,你可以通过 word_index
属性访问分配给语料库中每个单词的索引:
tokenizer.word_index
你可以使用texts_to_sequences()
方法将句子转换为标记化版本:
tokenizer.texts_to_sequences([sentence])
你可以创建一个函数,通过以下代码片段从输入语料库生成标记化句子的 N-gram 序列:
def get_seq_of_tokens(corpus):
tokenizer.fit_on_texts(corpus)
all_words = len(tokenizer.word_index) + 1
input_sequences = []
for line in corpus:
token_list = tokenizer.texts_to_sequences([line])[0]
for i in range(1, len(token_list)):
n_gram_sequence = token_list[:i+1]
input_sequences.append(n_gram_sequence)
return input_sequences, all_words
inp_sequences, all_words = get_seq_of_tokens(corpus)
inp_sequences[:10]
get_seq_of_tokens()
函数在给定的语料库上训练一个Tokenizer()
。然后,你需要遍历语料库中的每一行,并将它们转换为标记化的等效形式。最后,对于每个标记化的句子,你可以从中创建不同的 N-gram 序列。
接下来,你将看到如何通过填充处理可变长度的句子。
填充序列
如前所述,深度学习模型期望固定长度的输入。但对于文本来说,句子的长度可以变化。一种克服这种情况的方法是将所有句子转换为相同的长度。你需要设置句子的最大长度。然后,对于短于此阈值的句子,你可以添加填充,填充将通过添加特定的标记值来填补空白。另一方面,较长的句子将被截断以适应这一约束。你可以使用pad_sequences()
来实现这一点:
from keras.preprocessing.sequence import pad_sequences
你可以创建generate_padded_sequences
函数,该函数将接受input_sequences
并生成其填充版本:
def generate_padded_sequences(input_sequences):
max_sequence_len = max([len(x) for x in input_sequences])
input_sequences = np.array(pad_sequences\
(input_sequences, \
maxlen=max_sequence_len, \
padding='pre'))
predictors, label = input_sequences[:,:-1], \
input_sequences[:,-1]
label = ku.to_categorical(label, num_classes=all_words)
return predictors, label, max_sequence_len
predictors, label, max_sequence_len = generate_padded_sequences\
(inp_sequences)
现在你已经知道如何处理原始文本,接下来请查看下一节中的建模步骤。
时间反向传播(BPTT)
有很多类型的序列模型。你已经使用了简单的 RNN、深度 RNN 和 LSTM。我们来看看几个用于自然语言处理的附加模型。
记住,你是通过首先进行网络的前向传播,从输入到输出,来训练前馈模型的。这是标准的前馈模型,其中层之间是密集连接的。要训练这种模型,你可以通过网络反向传播梯度,计算每个权重参数的损失的导数。然后,你可以调整参数以最小化损失。
但在 RNN 中,如前所述,网络的前向传播也包括时间上的前进,根据输入和先前状态更新单元状态,并生成输出Y
。在该时间步长,计算损失并最终将来自各个时间步的这些损失加总起来,得到总损失。
这意味着,与其在单个时间步长通过单个前馈网络反向传播误差,不如在每个单独的时间步长进行反向传播误差,最后,再跨越所有时间步长——从当前所在位置一直回溯到序列的开始。
这就是为什么它被称为时间反向传播(Backpropagation through Time)。正如你所看到的,所有误差都在时间上反向流动,回到数据序列的开始。
机器翻译的一个伟大例子,以及 RNN 在工业中最强大且广泛使用的应用之一,就是谷歌翻译。在机器翻译中,你输入一个语言中的序列,任务是训练 RNN 将该序列转换为新的语言。这是通过使用一个双重结构来完成的,该结构有一个编码器,它将原语言中的句子编码成状态向量,然后是一个解码器。解码器将这个编码表示作为输入并解码成新语言。
然而,这种方法存在一个关键问题:所有输入到编码器结构中的内容必须编码为单一向量。在实际操作中,这可能成为一个巨大的信息瓶颈,因为你可能有大量的文本需要翻译。为了解决这个问题,谷歌的研究人员开发了一种称为注意力机制的 RNN 扩展。
现在,解码器不再仅仅访问最终的编码状态,它可以访问原始句子中所有时间步的状态。这些将编码器状态连接到解码器的向量权重是在训练过程中由网络学习的。这就是所谓的注意力机制,因为在网络学习过程中,它会将注意力集中在输入句子的不同部分。
通过这种方式,它有效地捕捉了对原始句子中重要信息的某种记忆访问。因此,借助注意力机制和门控单元等构建块,像 LSTM 和 RNN 这样的模型近年来取得了巨大进展,并在现实世界中被成功应用。
到目前为止,你应该已经对 RNN 是如何工作的以及它们为何如此强大以处理序列数据有了一定的了解。你已经了解了如何通过定义递归关系使用 RNN 执行序列建模任务。你还学习了如何训练 RNN,并看到了像 LSTM 这样的门控单元如何帮助我们建模长期依赖性。
在接下来的练习中,你将看到如何使用 LSTM 模型来预测文本中的下一个单词。
练习 9.03:构建一个带 LSTM 层的 RNN 进行自然语言处理
在本练习中,你将使用带 LSTM 层的 RNN 来预测新闻标题的最后一个单词。
Articles.csv
数据集包含由新闻标题组成的原始文本。你将训练一个 LTSM 模型,该模型将预测给定句子的下一个单词。
注意
你可以在这里找到数据集:packt.link/RQVoB
。
执行以下步骤以完成此练习:
-
导入所需的库:
from keras.preprocessing.sequence import pad_sequences from keras.layers import Embedding, LSTM, Dense, Dropout from keras.preprocessing.text import Tokenizer from keras.callbacks import EarlyStopping from keras.models import Sequential import keras.utils as ku import pandas as pd import numpy as np import string, os import warnings warnings.filterwarnings("ignore") warnings.simplefilter(action='ignore', category=FutureWarning)
你应该得到以下输出:
Using TensorFlow backend.
-
通过将
curr_dir
设置为content
,在本地加载数据集。创建all_headlines
变量。使用for
循环遍历文件夹中的文件,并提取标题。删除所有值为Unknown
的标题。打印all_headlines
的长度:curr_dir = '/content/' all_headlines = [] for filename in os.listdir(curr_dir): if 'Articles' in filename: article_df = pd.read_csv(curr_dir + filename) all_headlines.extend(list(article_df.headline.values)) break all_headlines = [h for h in all_headlines if h != "Unknown"] len(all_headlines)
输出将如下所示:
831
-
创建
clean_text
方法,在清洗文本后返回一个包含单词的列表。使用lower()
方法将所有文本转换为小写,并使用utf8
编码进行字符标准化。最后,从你的语料库中输出 10 条头条新闻:def clean_text(txt): txt = "".join(v for v in txt \ if v not in string.punctuation).lower() txt = txt.encode("utf8").decode("ascii",'ignore') return txt corpus = [clean_text(x) for x in all_headlines] corpus[:10]
你应该得到以下输出:
图 9.34:语料库
-
使用
tokenizer.fit
从语料库中提取词元。每个整数输出对应于一个特定的单词。使用input_sequences
,训练出将是一个list []
的特征。通过token_list = tokenizer.texts_to_sequences
,将每个句子转换为其标记化的等效形式。使用n_gram_sequence = token_list
,生成 N-gram 序列。通过input_sequences.append(n_gram_sequence)
,将每个 N-gram 序列追加到特征列表中:tokenizer = Tokenizer() def get_seq_of_tokens(corpus): tokenizer.fit_on_texts(corpus) all_words = len(tokenizer.word_index) + 1 input_sequences = [] for line in corpus: token_list = tokenizer.texts_to_sequences([line])[0] for i in range(1, len(token_list)): n_gram_sequence = token_list[:i+1] input_sequences.append(n_gram_sequence) return input_sequences, all_words inp_sequences, all_words = get_seq_of_tokens(corpus) inp_sequences[:10]
你应该得到以下输出:
图 9.35:N-gram 词元
-
填充序列并获取
predictors
和target
变量。使用pad_sequence
来填充序列,使它们的长度相等:def generate_padded_sequences(input_sequences): max_sequence_len = max([len(x) for x in input_sequences]) input_sequences = np.array\ (pad_sequences(input_sequences, \ maxlen=max_sequence_len, \ padding='pre')) predictors, label = input_sequences[:,:-1], \ input_sequences[:,-1] label = ku.to_categorical(label, num_classes=all_words) return predictors, label, max_sequence_len predictors, label, max_sequence_len = generate_padded_sequences\ (inp_sequences)
-
准备你的模型进行训练。添加一个输入嵌入层
model.add(Embedding)
。添加一个具有100
单元的 LSTM 隐藏层,并添加 10% 的 dropout。然后,添加一个具有 softmax 激活函数的全连接层。使用compile
方法配置你的模型进行训练,将损失函数设置为categorical_crossentropy
,并使用 Adam 优化器:def create_model(max_sequence_len, all_words): input_len = max_sequence_len - 1 model = Sequential() model.add(Embedding(all_words, 10, input_length=input_len)) model.add(LSTM(100)) model.add(Dropout(0.1)) model.add(Dense(all_words, activation='softmax')) model.compile(loss='categorical_crossentropy', \ optimizer='adam') return model model = create_model(max_sequence_len, all_words) model.summary()
你应该得到以下输出:
图 9.36:模型总结
-
使用
model.fit
拟合你的模型,并将其设置为运行100
个训练周期。将verbose
设置为5
:model.fit(predictors, label, epochs=100, verbose=5)
你应该得到以下输出:
图 9.37:训练模型
-
编写一个函数,该函数将接收输入文本、一个模型以及要预测的下一个单词的数量。该函数将准备输入文本,以便将其输入到模型中,模型将预测下一个单词:
def generate_text(seed_text, next_words, \ model, max_sequence_len): for _ in range(next_words): token_list = tokenizer.texts_to_sequences\ ([seed_text])[0] token_list = pad_sequences([token_list], \ maxlen=max_sequence_len-1,\ padding='pre') predicted = model.predict_classes(token_list, verbose=0) output_word = "" for word,index in tokenizer.word_index.items(): if index == predicted: output_word = word break seed_text += " "+output_word return seed_text.title()
-
使用
print
函数输出一些生成的文本。添加你自己的单词让模型使用并生成。例如,在the hottest new
中,整数5
是模型输出的单词数:print (generate_text("the hottest new", 5, model,\ max_sequence_len)) print (generate_text("the stock market", 4, model,\ max_sequence_len)) print (generate_text("russia wants to", 3, model,\ max_sequence_len)) print (generate_text("french citizen", 4, model,\ max_sequence_len)) print (generate_text("the one thing", 15, model,\ max_sequence_len)) print (generate_text("the coronavirus", 5, model,\ max_sequence_len))
你应该得到以下输出:
图 9.38:生成的文本
在这个结果中,你可以看到你的模型为每个句子生成的文本。
在这个练习中,你成功地预测了一些新闻头条。不出所料,其中一些可能并不太令人印象深刻,但也有一些还不错。
现在你已经掌握了关于 RNN 的所有基础知识,尝试通过执行下一个活动来测试自己。
活动 9.02:构建一个 RNN 用于预测推文的情感
tweets.csv
数据集包含与一家航空公司相关的推文列表。每条推文都被分类为正面、负面或中立情感。
你被分配了分析公司推文样本的任务。你的目标是构建一个 RNN 模型,能够预测每条推文的情感:正面或负面。
注意
你可以在这里找到tweets.csv
:packt.link/dVUd2
。
执行以下步骤以完成此活动。
-
导入必要的包。
-
准备数据(合并
Date
和Time
列,命名为datetime
,对数据进行排序,并填补缺失值)。 -
准备文本数据(对单词进行标记化并添加填充)。
-
将数据集拆分为训练集和测试集,训练集包含前 10,000 条推文,测试集包含剩余的推文。
-
定义并训练一个由两层不同的 LSTM 组成的 RNN 模型,分别包含
50
和100
个单元,后跟 20%的 dropout 和 ReLU 激活函数。 -
使用训练好的模型对测试集进行预测。
你应该得到以下输出:
图 9.39:活动 9.02 的预期输出
注意
本活动的解决方案可以通过此链接找到。
总结
在本章中,你探讨了不同的递归模型用于处理序列数据。你了解到每个序列数据点都依赖于之前的序列数据点,例如自然语言文本。你还学会了为什么必须使用允许模型使用数据序列并按顺序生成下一个输出的模型。
本章介绍了可以对序列数据进行预测的 RNN 模型。你观察到 RNN 可以进行自我循环,这使得模型的输出可以反馈到输入中。你回顾了使用这些模型时所面临的挑战,如梯度消失和爆炸,并学习了如何解决这些问题。
在下一章中,你将学习如何利用自定义的 TensorFlow 组件在模型中使用,包括损失函数和层。
第十章:10. 自定义 TensorFlow 组件
概述
在本章中,您将更深入地了解 TensorFlow 框架,并构建自定义模块。到本章结束时,您将学会如何创建自定义的 TensorFlow 组件,并将其用于模型中,例如损失函数和层。
介绍
在前面的章节中,您学习了如何从预定义的 TensorFlow 模块构建 CNN 或 RNN 模型。您一直在使用 TensorFlow 提供的 API 之一——顺序 API。这个 API 是开始构建 "简单" 深度学习架构的一个好方法,几行代码就可以实现。但如果您想要获得更高的性能,可能需要构建自己的自定义架构。在这种情况下,您将需要使用另一个叫做函数式 API 的 API。研究人员在定义模型架构时会使用函数式 API。通过学习如何使用它,您将能够创建自定义损失函数或模块,例如来自 ResNet 架构的残差块。
TensorFlow APIs
在使用 TensorFlow 时,您可以从顺序 API、函数式 API 或子类 API 中选择来定义模型。对于大多数人来说,顺序 API 是首选。然而,随着时间的推移,当您接触到更多的复杂性时,您的需求也会扩展。
顺序 API 是用于创建 TensorFlow 模型的最简单 API。它通过将不同的层一个接一个地堆叠来工作。例如,您将创建一个顺序模型,首先是卷积层,然后是 dropout 层,最后是全连接层。这个模型是顺序的,因为输入数据将按顺序传递到每一层。
函数式 API 提供了更大的灵活性。您可以定义不同的层,这些层相互之间不按顺序交互。例如,您可以创建两个不同的层,它们都会输入到第三个层。这可以通过函数式 API 轻松实现。
Layer
或 Model
。您可以定义自己的自定义层或模型,但这意味着您需要遵守继承的 TensorFlow 类的所有要求,例如编写强制性的函数。
以下图表提供了 TensorFlow 提供的三种不同 API 的快速概述:
图 10.1:显示所有三种 API 比较的图示
在接下来的部分中,您将学习如何定义自定义损失函数。
实现自定义损失函数
机器学习中有几种常用的损失函数。在第五章,分类中,您学习了不同类型的损失函数,并在不同的分类模型中使用它们。TensorFlow 提供了许多内置的损失函数可供选择。以下是一些更常见的损失函数:
-
平均绝对误差(MAE)
-
均方误差(MSE)
-
二进制交叉熵
-
分类交叉熵
-
Hinge
-
Huber
-
均方对数误差(MSLE)
提醒一下,你可以将损失函数视为一种指南针,它可以帮助你清晰地了解算法中哪些部分是有效的,哪些部分是无效的。损失值越高,模型的准确度越低,反之亦然。
虽然 TensorFlow 提供了多种现成的损失函数,但在某些情况下,你很可能需要为特定需求创建自己的损失函数。例如,如果你正在构建一个预测股票价格的模型,你可能需要定义一个损失函数,以显著惩罚那些大幅错误的预测值。
接下来的部分将向你展示如何构建自定义损失函数。
使用函数式 API 构建自定义损失函数
你在前几章中看到过如何使用 TensorFlow 提供的预定义损失函数。但如果你想构建自己的自定义函数,可以使用函数式 API 或者模型子类化方法。假设你想创建一个损失函数,它将预测值与实际值之间的差异的四次方作为误差:
图 10.2:自定义损失函数的公式
在创建自定义损失函数时,你总是需要两个参数:y_true
(实际值)和y_pred
(预测值)。损失函数会计算这两个值之间的差异,并返回一个误差值,表示模型的预测值与实际值之间的距离。在 MAE 的情况下,损失函数将返回这个误差的绝对值。另一方面,MSE 会将实际值和预测值之间的差值平方。但在前面的例子中,误差应该被提升到 4
次方。
让我们看看如何使用函数式 API 来实现这一点。首先,你需要通过以下命令导入 TensorFlow 库:
import tensorflow as tf
然后,你需要创建一个名为custom_loss
的函数,该函数接收y_true
和y_pred
作为输入参数。接下来,你将使用pow
函数将计算得到的误差的四次方。最后,你将返回计算得到的误差:
def custom_loss(y_true, y_pred):
custom_loss=tf.math.pow(y_true - y_pred, 4)
return custom_loss
你已经使用函数式 API 创建了自己的自定义损失函数。现在,你可以在训练模型之前,将它传递给compile
方法,而不是使用预定义的损失函数:
model.compile(loss=custom_loss,optimizer=optimizer)
完成这些步骤后,你可以像在前几章中那样训练你的模型。TensorFlow 会使用你自定义的损失函数来优化模型的学习过程。
使用子类化 API 构建自定义损失函数
还有另一种定义自定义损失函数的方法:使用子类化 API。在这种情况下,你将定义一个自定义类,而不是创建一个函数。如果你希望添加更多自定义属性或方法,这种方法非常有用。通过子类化,你可以创建一个自定义类,该类将继承keras.losses
模块中的Loss
类的属性和方法。然后,你需要定义__init__()
和call()
方法,这是Loss
类要求的方法。__init__
方法是定义自定义类所有属性的地方,而call()
方法则是你定义计算损失逻辑的地方。
以下是如何使用子类化 API 实现自定义损失函数的简要示例,其中误差应提升到4
的幂:
class MyCustomLoss(keras.losses.Loss):
def __init__(self, threshold=1.0, **kwargs):
super().__init__(**kwargs)
def call(self, y_true, y_pred):
return tf.math.pow(y_true - y_pred, 4)
在前面的示例中,你重新实现了之前相同的损失函数(4 的幂),但使用了从keras.losses.Loss
进行子类化的方法。你首先通过__init__()
方法初始化了类的属性,并使用self
参数,这指向类的实例。
然后,在call()
方法中,你定义了损失函数的逻辑,它计算了误差并将其提升到 4 的幂。
现在你已经掌握了损失函数的基础知识,接下来是时候在下一个练习中自己动手构建一个损失函数了。
练习 10.01:构建自定义损失函数
在这个练习中,你将创建一个自定义损失函数,用于训练一个卷积神经网络(CNN)模型,以区分苹果和西红柿的图片。
你将在本次练习中使用Apple-or-Tomato
数据集。该数据集是 GitHub 上的Fruits 360
数据集的一个子集。Fruits 360
数据集包含 1,948 张总色彩图像,图像尺寸为 100x100 像素。Apple-or-Tomato
数据集包含 992 张苹果图像,其中 662 张在训练集中,330 张在测试集中。番茄图像总数为 956 张,其中 638 张在训练集中,318 张在测试集中。
注意
你可以在以下链接获取Apple-or-Tomato
数据集:packt.link/28kZY
。
你可以在这里找到Fruits 360
数据集:github.com/Horea94/Fruit-Images-Dataset/archive/master.zip
。
首先,打开一个新的 Colab 或 Jupyter Notebook。如果你使用的是 Google Colab,你需要先将数据集下载到 Google Drive 中:
-
打开一个新的 Jupyter Notebook 或 Google Colab 笔记本。
-
如果你使用的是 Google Colab,请使用以下代码将数据集本地上传。否则,请跳到步骤 4。点击
Choose Files
选择 CSV 文件并点击Open
。保存文件为uploaded
。然后,进入你保存数据集的文件夹:from google.colab import files uploaded = files.upload()
-
在当前文件夹中解压数据集:
!unzip \*.zip
-
创建一个变量
directory
,它包含数据集的路径:directory = "/content/gdrive/My Drive/Datasets/apple-or-tomato/"
-
导入
pathlib
库:import pathlib
-
创建一个变量
path
,该变量使用pathlib.Path
包含数据集的完整路径:path = pathlib.Path(directory)
-
创建两个变量,
train_dir
和validation_dir
,并分别赋予训练和验证文件夹的完整路径:train_dir = path / 'training_set' validation_dir = path / 'test_set'
-
创建四个变量,分别为
train_apple_dir
、train_tomato_dir
、validation_apple_dir
和validation_tomato_dir
,它们将分别获取训练和验证集中的apple
和tomato
文件夹的完整路径:train_apple_dir = train_dir / 'apple' train_tomato_dir = train_dir /'tomato' validation_apple_dir = validation_dir / 'apple' validation_tomato_dir = validation_dir / 'tomato'
-
导入
os
包:import os
-
创建两个变量,分别为
total_train
和total_val
,它们将获取训练集和验证集中的图像数量:total_train = len(os.listdir(train_apple_dir)) + \ len(os.listdir(train_tomato_dir)) total_val = len(os.listdir(validation_apple_dir)) + \ len(os.listdir(validation_tomato_dir))
-
从
tensorflow.keras.preprocessing
模块导入ImageDataGenerator
:from tensorflow.keras.preprocessing.image import ImageDataGenerator
-
实例化两个
ImageDataGenerator
类,分别为train_image_generator
和validation_image_generator
,它们将通过除以 255 来重新缩放图像:train_image_generator = ImageDataGenerator(rescale=1./255) validation_image_generator = ImageDataGenerator(rescale=1./255)
-
创建三个变量,分别为
batch_size
、img_height
和img_width
,并将它们的值设置为32
、224
和224
:batch_size = 32 img_height = 224 img_width = 224
-
使用
flow_from_directory()
创建一个名为train_data_gen
的数据生成器,并指定批量大小、训练文件夹的路径、shuffle
参数的值、目标的大小和类模式:train_data_gen = train_image_generator.flow_from_directory\ (batch_size=batch_size, directory=train_dir, \ shuffle=True, \ target_size=(img_height, img_width), \ class_mode='binary')
-
使用
flow_from_directory()
创建一个名为val_data_gen
的数据生成器,并指定批量大小、验证文件夹的路径、目标大小和类模式:val_data_gen = validation_image_generator.flow_from_directory\ (batch_size=batch_size, directory=validation_dir, \ target_size=(img_height, img_width), \ class_mode='binary')
-
导入
matplotlib
并创建一个for
循环,该循环将遍历train_data_gen
中的五张图像并绘制它们:import matplotlib.pyplot as plt for _ in range(5): img, label = train_data_gen.next() plt.imshow(img[0]) plt.show()
你应该得到以下输出:
图 10.3:数据集中的图像样本
上述结果展示了该数据集中一些包含的图像示例。
-
导入 TensorFlow 库:
import tensorflow as tf
-
创建自定义损失函数,该函数将计算误差的平方:
def custom_loss_function(y_true, y_pred): print("y_pred ",y_pred) print("y_true ", y_true) squared_difference = tf.square(float(y_true)-float(y_pred)) return tf.reduce_mean(squared_difference, axis=-1)
-
从
tensorflow.keras.applications
模块导入NASNetMobile
模型:from tensorflow.keras.applications import NASNetMobile
-
使用 ImageNet 权重实例化此模型,移除顶部层,并指定正确的输入维度:
base_model = NASNetMobile(include_top=False,\ input_shape=(100, 100, 3), \ weights='imagenet')
-
冻结此模型的所有层,以确保不会更新
NASNetMobile
的模型权重:base_model.trainable = False
-
从
tensorflow.keras.layers
模块导入Flatten
和Dense
层:from tensorflow.keras.layers import Flatten, Dense
-
创建一个新模型,将
NASNetMobile
模型与两个新的顶部层(分别有 500 个和 1 个单元)以及 ReLu 和 sigmoid 作为激活函数进行组合:model = tf.keras.Sequential([ base_model, layers.Flatten(), layers.Dense(500, activation='relu'), layers.Dense(1, activation='sigmoid') ])
-
打印你的模型摘要:
model.summary()
你将得到以下输出:
图 10.4:模型总结
在这里,你可以看到左侧的层。你会看到
Output Shape
—例如,(None, 224, 224, 3)
。接下来,Param #
下方显示了参数的数量。在底部,你将找到摘要,包括可训练和不可训练的参数。 -
通过提供自定义损失函数、Adam 优化器和精度度量,来编译此模型并显示结果:
model.compile( optimizer='adam', loss=custom_loss_function, metrics=['accuracy'])
-
拟合模型并提供训练和验证数据生成器、每个 epoch 的步数以及验证步骤的数量:
history = model.fit( Train_data_gen, steps_per_epoch=total_train // batch_size, epochs=5, validation_data=val_data_gen, validation_steps=total_val // batch_size)
你应该得到以下输出:
图 10.5:训练进度截图
上面的截图显示了 TensorFlow 在训练模型期间显示的信息。你可以看到每个 epoch 在训练集和验证集上达到的准确度。在第五个 epoch 时,模型在训练集和验证集上的准确度均为96%
。
在这个练习中,你已经成功地构建了自己的损失函数,并用它训练了一个二分类器,识别苹果或番茄的图像。在接下来的章节中,你将更进一步,构建你自己的自定义层。
实现自定义层
之前,你已经学习了如何使用 TensorFlow 的功能性 API 或子类化方法实现自定义损失函数。这些概念也可以应用于为深度学习模型创建自定义层。在本节中,你将从零开始构建一个 ResNet 模块。
ResNet 块简介
残差神经网络,或称ResNet,最早由何恺明在 2015 年的论文《深度残差学习用于图像识别》中提出。他引入了一个新的概念——残差块,用以解决梯度消失问题,这一问题限制了训练非常深的网络(具有大量层)的能力。
一个残差块由多个层组成。但不同于将每一层堆叠并顺序执行的单一路径,残差块包含两条不同的路径。第一条路径有两个不同的卷积层。第二条路径,称为跳跃连接,将输入传递给第一条路径的最后一层。因此,残差块的输入将经过第一条路径的卷积层序列,结果将与来自第二条路径(跳跃连接)的原始输入结合,如图 10.6所示。简言之,这条额外的路径允许架构在更深层次上传递梯度,而不会影响整体性能。
图 10.6:跳跃连接
如你所见,如果你想为前面的残差块构建一个架构,使用 TensorFlow 的顺序 API 会相当困难。这里,你需要构建一个非常定制化的层。这就是为什么你需要使用功能性 API 或模型子类化的方法。
使用功能性 API 构建自定义层
在本节中,你将学习如何使用 TensorFlow 的功能性 API 来构建自定义层。
首先,你将构建一个函数,该函数将输入作为张量并对其添加 ReLU 激活和批量归一化。例如,在下面的代码片段中,relu_batchnorm_layer
函数接收输入并返回一个张量。这将创建一个包含 ReLU 激活和批量归一化的复合层:
def relu_batchnorm_layer(input):
return BatchNormalization()(ReLU()(input))
现在,创建一个函数来实现你的残差块。你需要接收一个张量作为输入,并将其传递给两个 Conv2D 层。然后,你将第二个 Conv2D 层的输出与原始输入相加,这代表了跳跃连接。该加法的输出将传递给你在前面代码片段中定义的 relu_batchnorm_layer()
函数。最后,输出将传递给另一个 Conv2D 层:
def simple_residual_block(input, filters: int, kernel_size: int = 3):
int_output = Conv2D(filters=filters, kernel_size=kernel_size,
padding="same")(input)
int_output = Conv2D(filters=filters, kernel_size=1, strides=2,
padding="same")(int_output)
output = Add()([int_output,input])
output = relu_batchnorm_layer(output)
return output
现在,你可以在模型中使用这个自定义层。在以下代码片段中,你将定义一个简单的模型,其中包含一个 Conv2D 层,后跟一个残差块:
inputs = Input(shape=(100, 100, 3))
num_filters = 32
t = BatchNormalization()(inputs)
t = Conv2D(kernel_size=3,
strides=1,
filters=32,
padding="same")(t)
t = relu_batchnorm_layer(t)
t = residual_block(t, filters=num_filters)
t = AveragePooling2D(4)(t)
t = Flatten()(t)
outputs = Dense(1, activation='sigmoid')(t)
model = Model(inputs, outputs)
接下来,我们将在下面的章节中使用子类化来构建自定义层。
使用子类化构建自定义层
之前,你已经学习了如何使用函数式 API 创建简化版的残差块。现在,你将看到如何使用模型子类化来创建自定义层。
首先,你需要导入 Model
类和一些层:
from tensorflow.keras.models import Model
from tensorflow.keras.layers import Dense, Dropout, Softmax, concatenate
然后,你将使用模型子类化来创建一个包含两个全连接层的模型。首先,定义一个名为 MyModel
的模型子类。从这个类生成的对象将是具有两个全连接层的模型。
在 init
方法中定义两个全连接层。例如,第一个可以有 64
个单元并使用 ReLU 激活函数,而第二个可以有 10
个单元并且没有激活函数(在这种情况下,默认的激活函数是线性函数)。之后,在 call
方法中,通过调用之前定义的全连接层来设置前向传播。首先,你可以放置 dense_1
层来接收输入,接着是 dense_2
层,它返回层的输出:
class MyModel(Model):
def __init__(self):
super(MyModel, self).__init__()
self.dense_1 = Dense(64, activation='relu')
self.dense_2 = Dense(10)
def call(self, inputs):,
X = self.dense_1(inputs)
return self.dense_2(X)
下一步是实例化模型。为此,只需在括号内调用类,不带参数。接下来,用随机输入调用模型以创建权重。对于输入,本例使用一个包含 10
个元素的一维向量,但你也可以使用不同的输入。然后,你可以打印模型的摘要,查看之前定义的全连接层。
考虑以下模型摘要:
model = MyModel()
model(tf.random.uniform([1,10]))
model.summary()
生成的输出应如下所示:
图 10.7:模型概述
现在,你可以通过添加一个名为 training
的关键字参数来修改 call
方法。如果你希望在训练和推理时有不同的行为,这将非常有用。例如,你可以创建一个只有在 training
为 true
时才会激活的 dropout 层。首先,你需要在 init
方法中定义一个 dropout 层,并设置学习率为 0.4
。然后,在 call
方法中,写一个 if
语句,默认情况下 training
为 true
。在其中,调用 dropout 层:
class MyModel(Model):
def __init__(self):
super(MyModel, self).__init__()
self.dense_1 = Dense(64, activation='relu')
self.dense_2 = Dense(10)
self.dropout = Dropout(0.4)
def call(self, inputs, training=True):
X = self.dense_1(inputs)
if training:
X = self.dropout(X)
return self.dense_2(X)
现在,考虑模型总结:
model = MyModel()
model(tf.random.uniform([1,10]))
model.summary()
执行上述命令后,模型总结将如下所示:
图 10.8:模型总结
在以下练习中,你将构建一个自定义层。
练习 10.02:构建自定义层
Healthy-Pneumonia
数据集是 National Institute for Health NIH
数据集的一个子集。该数据集包含 9,930 张总共 100x100 像素的彩色图像。pneumonia-or-healthy
数据集包含 1,965 张健康图像,其中训练集有 1,375 张图像,测试集有 590 张图像。
你将创建一个自定义的 ResNet 块,包含一个 Conv2D 层、一个批归一化层和一个 ReLU 激活函数。你将对图像进行二分类,以区分健康图像和肺炎图像。
注意
你可以在这里获取 pneumonia-or-healthy
数据集:packt.link/IOpUX
。
要开始,打开一个新的 Colab 或 Jupyter Notebook。如果你使用的是 Google Colab,你需要先将数据集下载到你的 Google Drive 中:
-
打开一个新的 Jupyter notebook 或 Google Colab。
-
如果你使用的是 Google Colab,可以使用以下代码在本地上传数据集。否则,请转到 第 4 步。点击
Choose Files
,找到 CSV 文件并点击Open
。将文件保存为uploaded
,然后进入你保存数据集的文件夹:from google.colab import files uploaded = files.upload()
-
在当前文件夹中解压数据集:
!unzip \*.zip
-
创建一个变量
directory
,它包含数据集的路径:directory = "/content/gdrive/My Drive/Datasets/pneumonia-or-healthy/"
-
导入
pathlib
库:import pathlib
-
创建一个变量
path
,它使用pathlib.Path
包含数据的完整路径:path = pathlib.Path(directory)
-
创建两个变量,分别为
train_dir
和validation_dir
,它们分别存储训练集和验证集文件夹的完整路径:train_dir = path / 'training_set' validation_dir = path / 'test_set'
-
创建四个变量,分别为
train_healthy_dir
、train_pneumonia_dir
、validation_healthy_dir
和validation_pneumonia_dir
,它们分别存储训练集和验证集的健康和肺炎文件夹的完整路径:train_healthy_dir = train_dir / 'healthy' train_pneumonia_dir = train_dir /'pneumonia' validation_healthy_dir = validation_dir / 'healthy' validation_pneumonia_dir = validation_dir / 'pneumonia'
-
导入
os
包:import os
-
创建两个变量,分别为
total_train
和total_val
,用来获取训练集和验证集中的图像数量:total_train = len(os.listdir(train_healthy_dir)) + \ len(os.listdir(train_pneumonia_dir)) total_val = len(os.listdir(validation_healthy_dir)) + \ len(os.listdir(validation_pneumonia_dir))
-
从
tensorflow.keras.preprocessing
导入ImageDataGenerator
:from tensorflow.keras.preprocessing.image import ImageDataGenerator
-
实例化两个
ImageDataGenerator
类,并分别命名为train_image_generator
和validation_image_generator
,它们将通过除以 255 来重新缩放图像:train_image_generator = ImageDataGenerator(rescale=1./255) validation_image_generator = ImageDataGenerator(rescale=1./255)
-
创建三个变量,分别命名为
batch_size
、img_height
和img_width
,其值分别为32
、100
和100
:batch_size = 32 img_height = 100 img_width = 100
-
使用
flow_from_directory()
创建一个名为train_data_gen
的数据生成器,并指定批量大小、训练文件夹的路径、shuffle
参数的值、目标大小和类别模式:train_data_gen = train_image_generator.flow_from_directory\ (batch_size=batch_size, directory=train_dir, \ shuffle=True, \ target_size=(img_height, img_width), \ class_mode='binary')
-
使用
flow_from_directory()
创建一个名为val_data_gen
的数据生成器,并指定批量大小、验证文件夹的路径、目标大小和类别模式:val_data_gen = validation_image_generator.flow_from_directory\ (batch_size=batch_size, directory=validation_dir, \ target_size=(img_height, img_width), \ class_mode='binary')
-
导入
matplotlib
并创建一个for
循环,该循环将遍历train_data_gen
中的五张图像并绘制它们:import matplotlib.pyplot as plt for _ in range(5): img, label = train_data_gen.next() plt.imshow(img[0]) plt.show()
你应该看到以下输出:
图 10.9:数据集中样本图像
上述结果展示了该数据集中包含的部分图像示例。
-
导入 TensorFlow 库:
import tensorflow as tf
-
导入
Input
、Conv2D
、ReLU
、BatchNormalization
、Add
、AveragePooling2D
、Flatten
和Dense
:from tensorflow.keras.layers import Input, Conv2D, ReLU, \ BatchNormalization, Add, \ AveragePooling2D, Flatten, Dense
-
构建一个函数,该函数将输入作为张量并向其添加 ReLU 和批量归一化:
def relu_batchnorm_layer(input): return BatchNormalization()(ReLU()(input))
-
创建一个函数来构建残差块。你需要将一个张量(
input
)作为输入,并将其传递给两个步幅为2
的 Conv2D 层。接下来,将输入添加到输出中,然后进行 ReLU 和批量归一化,返回一个张量。再添加一个kernel_size=1
的 Conv2D 层。将其结果添加到前一个 Conv2D 层的输出中。最后,应用relu_batchnorm_layer()
并返回其值。你将对所有 Conv2D 层应用完全相同的滤波器(数量和维度由构造函数的两个输入参数定义):def residual_block(input, filters: int, kernel_size: int = 3): int_output = Conv2D(filters=filters, kernel_size=kernel_size, strides=(2), padding="same")(input) int_output = relu_batchnorm_layer(int_output) int_output = Conv2D(filters=filters, kernel_size=kernel_size, padding="same")(int_output) int_output2 = Conv2D(filters=filters, kernel_size=1, strides=2, padding="same")(input) output = Add()([int_output2, int_output]) output = relu_batchnorm_layer(output) return output
-
导入
Model
模块:from tensorflow.keras.models import Model
-
使用
keras.layers.Input()
定义模型的输入层。这里,输入的形状为 100 像素×100 像素,并且有三种颜色(RGB):inputs = Input(shape=(100, 100, 3))
-
对输入应用批量归一化,接着应用一个
32
个滤波器、大小为3*3
、步幅为1
、填充为same
的 Conv2D 层。最后,对其输出应用relu_batchnorm_layer()
函数:t = BatchNormalization()(inputs) t = Conv2D(kernel_size=3, strides=1, filters=32, padding="same")(t) t = relu_batchnorm_layer(t)
-
将前一层的输出传递给
residual_block()
函数,使用32
个滤波器。然后,传递其输出到一个具有四个单元的平均池化层,接着将其结果展平,并将其传递给一个包含1
个单元的全连接层,激活函数为 sigmoid:t = residual_block(t, filters=32) t = AveragePooling2D(4)(t) t = Flatten()(t) outputs = Dense(1, activation='sigmoid')(t)
-
实例化一个
Model()
类,使用原始输入和全连接层的输出:model = Model(inputs, outputs)
-
获取模型的摘要:
model.summary()
你将看到一个摘要,包含可训练和不可训练的参数,如下所示:
图 10.10:模型摘要
-
编译模型,提供二元交叉熵作为损失函数,Adam 作为优化器,并将准确度作为显示的度量:
model.compile( optimizer='adam', loss=binary_crossentropy, metrics=['accuracy'])
-
训练模型并提供训练和验证数据生成器、训练周期数、每周期的步骤数和验证步骤:
history = model.fit( Train_data_gen, steps_per_epoch=total_train // batch_size, epochs=5, validation_data=val_data_gen, validation_steps=total_val // batch_size )
你应该获得如下输出:
图 10.11:训练进度截图
上面的截图展示了 TensorFlow 在训练模型过程中显示的信息。你可以看到每个 epoch 在训练集和验证集上达成的准确度。
在本次练习中,你为网络创建了自己的自定义层。现在,让我们通过以下活动来测试你迄今为止学到的知识。
活动 10.01:构建带有自定义层和自定义损失函数的模型
table-or-glass
数据集是从 Open Images V6
数据集中提取的一部分图像。Open Images V6
数据集包含约 900 万张图像。table-or-glass
数据集由 7,484 张总颜色图像组成,每张图像的尺寸为 100×100 像素。table-or-glass
数据集包含 3,741 张玻璃图像,其中 2,618 张在训练集,1,123 张在测试集中。总共有 3,743 张桌面图像,其中 2,618 张在训练集,1,125 张在测试集中。你需要训练一个更复杂的模型,能够使用自定义的 ResNet 块和自定义损失函数区分玻璃和桌面图像。
注意
你可以在这里找到数据集:packt.link/bE5F6
。
以下步骤将帮助你完成本次活动:
-
导入数据集并将文件解压到本地文件夹。
-
为训练集和测试集创建图像列表。
-
分析目标变量的分布。
-
对图像进行预处理(标准化和重塑)。
-
创建一个自定义损失函数,用于计算平均平方误差。
-
创建一个自定义的残差块构造函数。
-
训练你的模型。
-
打印准确度和损失的学习曲线。
注意
本次活动的解决方案可以通过此链接找到。
总结
本章展示了如何构建和利用自定义的 TensorFlow 组件。你学会了如何设计和实现自定义损失函数、层和残差块。使用 TensorFlow 的功能性 API 或模型子类化,使你能够构建更复杂的深度学习模型,这些模型可能更适合你的项目。
在下一章,也是最后一章中,你将探索并构建生成模型,这些模型能够学习数据中的模式和关系,并利用这些关系生成新的、独特的数据。
第十一章:11. 生成模型
概述
本章将向你介绍生成模型——它们的组成部分、如何运作以及它们能做什么。你将从生成长短期记忆(LSTM)网络开始,学习如何使用它们生成新的文本。然后你将学习生成对抗网络(GANs)以及如何创建新数据,接着是深度卷积生成对抗网络(DCGANs)以及如何创建你自己的图像。
到本章结束时,你将知道如何有效地使用不同类型的 GANs 并生成各种类型的新数据。
介绍
在本章中,你将探索生成模型,它们是无监督学习算法的一种类型,可以生成全新的人工数据。生成模型与预测模型的不同之处在于,它们的目标是从与训练数据相同的分布中生成新的样本。尽管这些模型的目的可能与其他章节中介绍的模型非常不同,但你可以并且将会使用前面章节中学到的许多概念,包括加载和预处理各种数据文件、调整超参数以及构建卷积神经网络和递归神经网络(RNNs)。在本章中,你将学习如何使用 LSTM 模型基于初始种子数据生成新的数据样本,并完成数据序列的生成。
你还将学习另一种方法,即两个神经网络以对抗的方式进行竞争的概念,也就是生成器生成样本,判别器试图区分生成样本和真实样本。随着两个模型的同时训练,生成器会生成更加真实的样本,而判别器随着时间的推移能够更准确地区分“真实”数据和“假”数据。这些协同工作的网络被称为 GANs。生成模型可用于生成新的文本数据、音频样本和图像。
在本章中,你将主要集中在生成模型的三个领域——文本生成或语言建模、GANs 和 DCGANs。
文本生成
在第九章,递归神经网络中,你已经接触过自然语言处理(NLP)和文本生成(也称为语言建模),并解决了一些序列数据问题。在本节中,你将扩展你的序列模型,使用相同的数据集生成扩展的标题来进行文本生成。
在本书之前的内容中,你看到过序列数据,其中数据集中的每个点都依赖于前一个点,且数据的顺序很重要。回想一下第九章《循环神经网络》中的袋子词汇示例。在袋子词汇方法中,你只是使用一组词频来推导它们的含义。正如你在图 11.1中看到的,这两句话的语义完全相反,但在袋子词汇格式中是相同的。虽然这种方法对于某些问题可能有效,但它并不是预测下一个词或词语的理想方法。
图 11.1:具有不同语义的相同单词的示例
考虑以下语言模型的例子。给定一个句子或短语,yesterday I took my car out for a
,然后要求预测下一个词是什么。在这里,完成该序列的适当单词是drive
。
图 11.2:句子示例
要成功地处理序列数据,你需要一个能够存储序列值的神经网络。为此,你可以使用 RNN 和 LSTM。用于生成新序列(例如文本生成或语言建模)的 LSTM 被称为生成 LSTM。
让我们简单回顾一下 RNN 和 LSTM。
本质上,RNN 会自我回环,存储信息并重复这个过程,形成一个连续的循环。信息首先被转化为向量,以便机器进行处理。然后,RNN 按顺序逐一处理这些向量。每当 RNN 处理一个向量时,这个向量会通过前一个隐藏状态传递。通过这种方式,隐藏状态保留了来自前一步的信息,充当了一种记忆。它通过将输入和前一个隐藏状态结合,并使用 tanh 函数将值压缩到 -1
和 1
之间来实现这一点。
本质上,这就是 RNN 的工作原理。RNN 不需要大量的计算,并且在处理短序列时效果很好。简单来说,RNN 是一种具有回环的网络,可以使信息在时间中持续存在。
图 11.3:RNN 数据流
RNN 确实存在一些挑战——最显著的就是梯度爆炸和梯度消失问题。
梯度爆炸问题指的是当梯度变得过大以至于无法进行优化时发生的情况。相反的情况可能是梯度过小,这就是所谓的梯度消失问题。当你进行多次乘法运算时,梯度变得越来越小。这发生时,梯度的大小决定了权重更新的大小,梯度爆炸或消失意味着网络无法再进行训练。当训练 RNN 时,这个问题特别严重,因为网络的输出会反馈到输入中。梯度爆炸和消失的问题已经在第九章,循环神经网络中讨论过,关于如何解决这些问题的更多细节可以在那里找到。
LSTM 可以选择性地控制每个 LSTM 节点内信息的流动。通过增加控制,你可以更容易地调整模型,以防止梯度出现潜在问题。
图 11.4:LSTM 架构
那么,是什么让 LSTM 能够在多个时间步长中跟踪和存储信息呢?你会从第九章,循环神经网络中回忆起,LSTM 的关键构建块是一个叫做门控的结构,它使 LSTM 能够选择性地向其单元状态添加或移除信息。
门控由边界函数组成,例如 sigmoid 或 tanh。例如,如果函数是 sigmoid,它会强制其输入保持在零和一之间。直观地理解,你可以将其视为捕获通过门控传递的信息应保留多少。这个值应在零和一之间,有效地限制信息的流动。
LSTM 通过四个简单的步骤处理信息。
它们首先忘记不相关的历史信息。其次,它们执行计算来存储新信息中相关的部分,第三,它们将这两步结合起来,选择性地更新它们的内部状态。最后,它们生成一个输出。
图 11.5:LSTM 处理步骤
这部分内容回顾了 LSTM 以及它们如何选择性地控制和调节信息流动。现在你已经复习了 LSTM 及其架构,可以通过回顾你的代码和 LSTM 模型,将这些概念付诸实践。
你可以通过以下方式使用顺序模型创建 LSTM 模型。该 LSTM 包含四个隐藏层,分别具有50
、60
、80
和120
个单元,并使用 ReLU 激活函数。return_sequences
参数对于除最后一层之外的所有层都设置为True
,因为它们不是网络中的最终 LSTM 层:
regressor = Sequential()
regressor.add(LSTM(units= 50, activation = 'relu', \
return_sequences = True, \
input_shape = (X_train.shape[1], 5)))
regressor.add(Dropout(0.2))
regressor.add(LSTM(units= 60, activation = 'relu', \
return_sequences = True))
regressor.add(Dropout(0.3))
regressor.add(LSTM(units= 80, activation = 'relu', \
return_sequences = True))
regressor.add(Dropout(0.4))
regressor.add(LSTM(units= 120, activation = 'relu'))
regressor.add(Dropout(0.5))
regressor.add(Dense(units = 1))
现在你已经回顾了如何创建带有 LSTM 层的 RNN,接下来你将学习如何将它们应用于自然语言文本,并在一个序列中生成新文本。
扩展 NLP 序列模型以生成文本
NLP 接收自然语言形式的数据,传统上这些数据对机器来说非常难以理解,然后将其转换为可以用于机器学习应用的数据。这些数据可以是字符、单词、句子或段落的形式。本节将专注于文本生成。
作为一个快速的回顾,预处理 通常包括训练模型所需的所有步骤。一些常见的步骤包括数据清洗、转换和数据减少。对于 NLP,更具体地说,步骤可能是以下全部或部分:
-
数据集清理 包括将大小写转换为小写,去除标点符号。
-
标记化 是将字符序列分解为指定单元的过程,称为token。
-
填充 是一种通过填充使不同大小的输入句子相同的方法。
-
填充序列 是确保序列具有统一长度的过程。
-
rainy
和raining
都有词干rain
。
让我们更详细地看一下这个过程是什么样子的。
数据集清洗
在这里,你创建了一个名为clean_text
的函数,它返回经过清洗后的单词列表。现在,使用lower()
方法将所有文本保存为小写,用utf8
编码进行字符标准化。最后,输出你的语料库中的 10 个标题:
def clean_text(txt):
txt = "".join(v for v in txt \
if v not in string.punctuation).lower()
txt = txt.encode("utf8").decode("ascii",'ignore')
return txt
corpus = [clean_text(x) for x in all_headlines]
corpus[:10]
以这种方式清理文本是将文本标准化以输入模型的绝佳方法。在相同编码中将所有单词转换为小写确保文本的一致性。它还确保模型不会将同一单词的大小写或不同编码视为不同的单词。
生成序列和标记化
神经网络期望输入数据以一致的数值格式提供。就像处理图像分类模型的图像一样,其中每个图像表示为三维数组,并且通常调整大小以满足模型的期望一样,文本必须进行类似处理。幸运的是,Keras 具有许多实用的类和函数来帮助处理神经网络的文本数据。其中一个类是从 Keras 导入的Tokenizer
:
from keras.preprocessing.text import Tokenizer
生成 n-gram Tokens 的序列
在这里,你创建了一个名为get_seq_of_tokens
的函数。使用tokenizer.fit_on_texts
,你从语料库中提取 token。每个整数输出对应一个特定的单词。input_seq
参数初始化为空列表,[]
。使用token_list =
tokenizer.texts_to_sequences
,你将文本转换为 token 序列的等效形式。使用n_gram_sequence = token_list
,你生成 n-gram 序列。通过input_seq.append(n_gram_sequence)
,你将每个序列追加到特征列表中:
tokenizer = Tokenizer()
def get_seq_of_tokens(corpus):
tokenizer.fit_on_texts(corpus)
all_words = len(tokenizer.word_index) + 1
input_seq = []
for line in corpus:
token_list = tokenizer.texts_to_sequences([line])[0]
for i in range(1, len(token_list)):
n_gram_sequence = token_list[:i+1]
input_seq.append(n_gram_sequence)
return input_seq, all_words
your_sequences, all_words = get_seq_of_tokens(corpus)
your_sequences[:10]
get_seq_of_tokens
确保语料库被拆分成等长的序列。如果语料库对于网络期望的输入来说太短,结果序列将需要填充。
填充序列
在这里,你创建一个 generate_padded_sequences
函数,接受 input_seq
作为输入。pad_sequences
函数用于填充序列,使它们的长度相等。在函数中,首先通过计算每个输入序列的长度来确定最大序列长度。一旦确定了最大序列长度,所有其他序列将被填充以匹配。接下来,创建 predictors
和 label
参数。label
参数是序列中的最后一个单词,predictors
参数是所有前面的单词。最后,label
参数被转换为分类数组:
def generate_padded_sequences(input_seq):
max_sequence_len = max([len(x) for x in input_seq])
input_seq = np.array(pad_sequences\
(input_seq, maxlen=max_sequence_len, \
padding='pre'))
predictors, label = input_seq[:,:-1],input_seq[:,-1]
label = keras.utils.to_categorical(label, num_classes=all_words)
return predictors, label, max_sequence_len
predictors, label, max_sequence_len = generate_padded_sequences\
(your_sequences)
现在你已经学习了处理自然语言的一些预处理和清理步骤,包括清理、生成 n-gram 序列以及填充序列以保持一致的长度,你已经准备好进行本章的第一个练习,即文本生成。
练习 11.01:生成文本
在这个练习中,你将使用来自练习 9.02,构建一个带有 LSTM 层的 RNN 用于 Nvidia 股票预测,的 LSTM 模型来扩展你的预测序列并生成新文本。在那个练习中,你创建了一个 LSTM 模型,通过将历史股价输入模型来预测 Nvidia 的股票价格。该模型能够使用 LSTM 层理解历史股价中的模式,以进行未来的预测。
在这个练习中,你将使用与文本相同的原理,通过将历史标题输入模型来进行预测。你将使用 articles.csv
数据集进行这个练习。数据集包含来自《纽约时报》的 831 个新闻标题,格式为 CSV。除了标题,数据集还包含关于新闻文章的多个属性,包括出版日期、打印页码和关键词。你需要使用给定的数据集生成新的新闻标题。
注意
你可以在这里找到 articles.csv
:packt.link/RQVoB
。
执行以下步骤完成本次练习:
-
打开一个新的 Jupyter 或 Colab 笔记本。
-
导入以下库:
from keras.preprocessing.sequence import pad_sequences from keras.models import Sequential from keras.layers import Embedding, LSTM, Dense, Dropout import tensorflow.keras.utils as ku from keras.preprocessing.text import Tokenizer import pandas as pd import numpy as np from keras.callbacks import EarlyStopping import string, os import warnings warnings.filterwarnings("ignore") warnings.simplefilter(action='ignore', category=FutureWarning)
你应该得到以下输出:
Using TensorFlow backend.
-
通过将
your_dir
设置为content/
,本地加载数据集。创建一个空的your_headlines
参数,并使用for
循环遍历:your_dir = 'content/' your_headlines = [] for filename in os.listdir(your_dir): if 'Articles' in filename: article_df = pd.read_csv(your_dir + filename) your_headlines.extend(list(article_df.headline.values)) break your_headlines = [h for h in your_headlines if h != "Unknown"] len(our_headlines)
输出将表示数据集中标题的数量:
831
-
现在,创建一个
clean_text
函数来返回清理后的单词列表。使用lower()
方法将文本转换为小写,并使用utf8
编码进行字符标准化。最后,从你的语料库中输出 20 个标题:def clean_text(txt): txt = "".join(v for v in txt \ if v not in string.punctuation).lower() txt = txt.encode("utf8").decode("ascii",'ignore') return txt corpus = [clean_text(x) for x in all_headlines] corpus[60:80]
你应该得到以下输出:
图 11.6:语料库
-
使用
tokenizer.fit
从语料库中提取词元。每个整数输出对应一个特定的词。input_seq
参数初始化为空列表[]
。使用token_list =
tokenizer.texts_to_sequences
,将每个句子转换为其词元化的等效形式。使用n_gram_sequence = token_list
,你可以生成 n-gram 序列。通过input_seq.append(n_gram_sequence)
,将每个序列添加到特征列表中:tokenizer = Tokenizer() def get_seq_of_tokens(corpus): tokenizer.fit_on_texts(corpus) all_words = len(tokenizer.word_index) + 1 input_seq = [] for line in corpus: token_list = tokenizer.texts_to_sequences([line])[0] for i in range(1, len(token_list)): n_gram_sequence = token_list[:i+1] input_seq.append(n_gram_sequence) return input_seq, all_words your_sequences, all_words = get_seq_of_tokens(corpus) your_sequences[:20]
你应该得到以下输出:
图 11.7:n-gram 词元
输出显示了头条新闻的 n-gram 词元。对于每个头条新闻,n-gram 的数量由头条新闻的长度决定。
-
填充序列并获取变量
predictors
和target
:def generate_padded_sequences(input_seq): max_sequence_len = max([len(x) for x in input_seq]) input_seq = np.array(pad_sequences\ (input_seq, maxlen=max_sequence_len, \ padding='pre')) predictors, label = input_seq[:,:-1],input_seq[:,-1] label = ku.to_categorical(label, num_classes=all_words) return predictors, label, max_sequence_len predictors, label, \ max_sequence_len = generate_padded_sequences(inp_seq)
-
准备好你的模型进行训练。添加一个输入嵌入层,使用
model.add(Embedding)
,一个隐藏的 LSTM 层,使用model.add(LSTM(100))
,并设置 10%的 dropout。然后,使用model.add(Dense)
添加输出层,使用 softmax 激活函数。通过compile()
方法配置模型进行训练,并将损失函数设置为categorical_crossentropy
。使用 Adam 优化器:def create_model(max_sequence_len, all_words): input_len = max_sequence_len - 1 model = Sequential() model.add(Embedding(all_words, 10, input_length=input_len)) model.add(LSTM(100)) model.add(Dropout(0.1)) model.add(Dense(all_words, activation='softmax')) model.compile(loss='categorical_crossentropy', \ optimizer='adam') return model model = create_model(max_sequence_len, all_words) model.summary()
你应该得到以下输出:
图 11.8:模型概览
-
训练模型并将
epochs
设置为200
,verbose
设置为5
:model.fit(predictors, label, epochs=200, verbose=5)
你应该得到以下输出:
图 11.9:训练模型
-
创建一个函数,该函数将在给定的种子文本、生成单词的数量、模型和最大序列长度的基础上生成一个头条新闻。该函数将包括一个
for
循环来迭代生成单词的次数。在每次迭代中,tokenizer 将对文本进行分词,然后对序列进行填充,并预测序列中的下一个单词。接着,迭代会将 token 转换回单词并将其添加到句子中。一旦for
循环完成,生成的头条新闻将被返回:def generate_text(seed_text, next_words, model, max_sequence_len): for _ in range(next_words): token_list = tokenizer.texts_to_sequences([seed_text])[0] token_list = pad_sequences([token_list], \ maxlen = max_sequence_len-1, \ padding='pre') predicted = model.predict\ (token_list, verbose=0) output_word = "" for word,index in tokenizer.word_index.items(): if index == predicted.any(): output_word = word break seed_text += " "+output_word return seed_text.title()
-
最后,通过
print
函数输出一些生成的文本,打印你在步骤 9中创建的函数的输出。使用种子词10 ways
、europe looks to
、best way
、homeless in
、unexpected results
和critics warn
,以及对应生成的单词数,即11
、8
、10
、10
、10
和10
,来生成文本:print (generate_text("10 ways", 11, model, max_sequence_len)) print (generate_text("europe looks to", 8, model, \ max_sequence_len)) print (generate_text("best way", 10, model, max_sequence_len)) print (generate_text("homeless in", 10, model, max_sequence_len)) print (generate_text("unexpected results", 10, model,\ max_sequence_len)) print (generate_text("critics warn", 10, model, \ max_sequence_len))
你应该得到以下输出:
图 11.10:生成的文本
输出显示了使用提供的种子文本生成的头条新闻。生成的词汇受限于训练数据集中包含的内容,而该数据集本身相对较小,导致了一些无意义的结果。
现在你已经在第一个练习中使用 LSTM 生成了文本,接下来我们将通过使用 GAN 生成基于给定数据集的新图像来处理图像。
生成对抗网络
GAN(生成对抗网络)是通过学习训练数据集中的模式和潜在表示来生成新的、合成数据的网络。GAN 通过使用两个彼此对抗的网络来实现这一点。这些网络被称为生成器和判别器。
为了了解这些网络是如何相互竞争的,可以考虑以下示例。这个示例将跳过一些细节,这些细节将在后面的章节中更加清楚。
假设有两个实体:一个伪造货币的人和一个商家。伪造者试图制造一种看起来真实的货币,以欺骗商家认为该货币是合法的。相比之下,商家则试图识别任何假币,以免自己拿到一张无价值的纸,而不是合法的货币。
这基本上就是 GAN 的工作原理。在这个例子中,伪造者是生成器,而商家则是判别器。生成器创建一个图像并将其传递给判别器。判别器检查图像是否真实,然后两个网络相互竞争,推动彼此的改进。
生成器的任务是创建一个可以欺骗判别器的合成数据样本。生成器将尝试欺骗判别器,让它认为样本是真实的。判别器的任务是能够正确地分类由生成器创建的合成样本。
](https://github.com/OpenDocCN/freelearn-dl-pt3-zh/raw/master/docs/tf-ws/img/B16341_11_11.jpg)
图 11.11:GAN 生成的图像
下一节将更详细地讨论生成器和判别器以及它们各自的功能,随后再在对抗网络部分中讨论它们的结合。
生成器网络
如前所述,GAN 被用于机器学习中的无监督学习任务。GAN 由两个模型(生成器和判别器)组成,这两个模型会自动发现并学习输入数据中的模式。两个模型相互竞争,分析、捕捉并创造数据中的变化。GAN 可以用来生成看起来像是来自原始数据的新数据。
首先是生成器模型。生成器是如何创建合成数据的呢?
生成器接收输入为一个固定长度的随机向量,称为潜在向量,它进入生成器网络。这有时被称为随机噪声种子。从中生成一个新的样本。生成的实例随后会被送到判别器进行分类。通过随机噪声,生成器学习哪些输出更具说服力,并在此方向上继续改进。
图 11.12:生成器网络中的输入输出模型
](https://github.com/OpenDocCN/freelearn-dl-pt3-zh/raw/master/docs/tf-ws/img/B16341_11_12.jpg)
图 11.12:生成器网络中的输入输出模型
在下图中,你可以看到鉴别器从真实数据和生成器两者处获取输入。生成器神经网络试图生成看起来像真实数据的输出,以骗过鉴别器。
生成器无法看到真实数据是什么。生成器的主要目标是让鉴别器将其输出分类为真实的。
图 11.13:鉴别器模型的两个数据来源
GAN 包括以下组件:
-
含噪输入向量
-
鉴别器网络
-
生成器损失
反向传播用于通过计算权重对输出的影响来调整权重的最优方向。反向传播方法用于获取梯度,这些梯度有助于改变生成器的权重。
图 11.14:GAN 中的反向传播
单次生成器迭代的基本过程如下所示:
-
基于数据集中的真实数据,使用采样随机噪声。
-
生成器从噪声中生成输出。
-
鉴别器将输出分类为“真实”或“假”。
-
从此分类中计算出损失,然后通过生成器和鉴别器进行反向传播以获得梯度。
-
梯度用于调整生成器的权重。
现在,编写生成器的代码,第一步是定义生成器模型。你从使用define_your_gen
创建生成器函数开始。生成器的输出数量应与您要合成的数据的大小匹配。因此,生成器的最后一层应该是一个密集层,其单元数等于期望的输出大小:
model.add(Dense(n_outputs, activation='linear'))
模型无法编译,因为它与生成器模型不完全匹配。
代码块看起来可能如下所示:
def define_your_gen(latent_dim, n_outputs=2):
model = Sequential()
model.add(Dense(5, activation='relu', \
kernel_initializer='he_uniform', \
input_dim=latent_dim))
model.add(Dense(n_outputs, activation='linear'))
return model
生成器构成了 GAN 的一半;另一半是鉴别器。
鉴别器网络
鉴别器是一个神经网络模型,学习从生成器输入的假数据中识别真实数据。训练数据的两个来源是:真实数据样本和假生成器样本:
-
真实数据实例在训练过程中作为正样本被鉴别器使用。
-
由生成器创建的合成数据实例在训练过程中作为假例使用。
图 11.15:鉴别器网络的输入
在鉴别器的训练过程中,鉴别器与生成器和鉴别器损失连接。它需要真实数据和生成器的合成数据,但仅使用鉴别器损失进行权重更新。
图 11.16:带有鉴别器损失的反向传播
现在让我们看看判别器如何与一些代码一起工作。
你的第一步是通过define_disc()
定义你的判别器模型。
模型接收来自生成器的一个向量,并预测该样本是真实的还是虚假的。因此,你使用二分类。
你正在创建一个简单的 GAN,因此只需要一个隐藏层。使用model.add(Dense(25)
来创建隐藏层。
再次提醒,你的激活函数将是 ReLU,使用activation='relu'
,权重初始化方式为he_uniform
,即kernel_initializer='he_uniform'
。
你的输出层只需要一个节点来进行二分类。为了确保输出为零或一,你将使用 sigmoid 激活函数:
model.add(Dense(1, activation='sigmoid'))
模型将尝试最小化你的损失函数。使用 Adam 作为你的随机梯度下降法:
model.compile(loss='binary_crossentropy', \
optimizer='adam', metrics=['accuracy'])
这是你的判别器模型代码:
def define_disc(n_inputs=2):
model = Sequential()
model.add(Dense(25, activation='relu', \
kernel_initializer='he_uniform', \
input_dim=n_inputs))
model.add(Dense(1, activation='sigmoid'))
model.compile(loss='binary_crossentropy', \
optimizer='adam', metrics=['accuracy'])
return model
现在你知道如何创建组成 GAN 的两个模型,在下一节中,你可以学习如何将它们结合起来,创建你的 GAN。
对抗网络
GAN 由两个网络组成,一个是生成器,表示为 ,另一个是判别器,表示为
。这两个网络进行对抗性博弈。生成器网络试图学习训练数据的潜在分布并生成相似的样本,而判别器网络则试图识别生成器生成的虚假样本。
生成器网络接收一个样本并生成一个虚假的数据样本。生成器的训练目标是增加判别器网络犯错误的概率。判别器网络通过二分类来判断数据是生成的还是来自真实样本,并借助 sigmoid 函数来实现。sigmoid 函数确保输出为零或一。
以下列表概述了典型 GAN 的工作原理:
-
首先,一个噪声向量或输入向量被输入到生成器网络中。
-
生成器创建合成数据样本。
-
真实数据和合成数据一起传递给判别器。
-
然后,判别器识别数据并将其分类为真实或虚假。
-
模型被训练,损失通过反向传播进入判别器和生成器网络。
图 11.17:具有输入和输出的 GAN 模型
要编写一个对抗网络,以下步骤是必要的。每个步骤将在后续章节中详细描述:
-
将生成器和判别器模型结合在你的 GAN 中。
-
使用类别标签生成真实样本。
-
在潜在空间中创建点,作为生成器的输入。
-
使用生成器生成虚假样本。
-
评估判别器的性能。
-
训练生成器和判别器。
-
创建潜在空间、生成器、判别器和 GAN,并在训练数据上训练 GAN。
现在,既然你已经探索了生成器和判别器的内部工作原理,接下来看看如何将这两个模型组合起来相互竞争。
结合生成模型和判别模型
define_your_gan()
函数创建你的组合模型。
在创建组合 GAN 模型时,通过指定discriminator.trainable = False
来冻结判别器模型的权重。这可以防止在更新生成器权重时,判别器的权重被更新。
现在,你可以通过model.add(generator)
和model.add(discriminator)
将两个模型添加到组合模型中。
然后,在编译模型时指定binary_crossentropy
作为损失函数,Adam 作为优化器:
def define_your_gan(generator, discriminator):
discriminator.trainable = False
model = Sequential()
model.add(generator)
model.add(discriminator)
model.compile(loss='binary_crossentropy', optimizer='adam')
return model
使用类别标签生成真实样本
现在,从数据集中提取真实样本,并与假样本进行对比。你可以使用之前定义的generate_real()
函数。在函数的第一行,rand(n) - 0.5
,生成范围在-0.5
到0.5
之间的n
个随机数。使用hstack
来堆叠你的数组。现在,你可以使用y = ones((n, 1))
来生成类别标签:
def generate_real(n):
X1 = rand(n) - 0.5
X2 = X1 * X1
X1 = X1.reshape(n, 1)
X2 = X2.reshape(n, 1)
X = hstack((X1, X2))
y = ones((n, 1))
return X, y
为生成器创建潜在点
接下来,使用生成器模型来创建假样本。你需要通过gen_latent_points()
函数在潜在空间中生成相同数量的点。这些潜在点将被传递给生成器以创建样本。该函数使用 NumPy 的randn
函数生成均匀随机样本。生成的数量将对应潜在维度与待生成样本数的乘积。然后,这个随机数数组将被重新调整形状以匹配生成器的预期输入:
def gen_latent_points(latent_dim, n):
x_input = randn(latent_dim * n)
x_input = x_input.reshape(n, latent_dim)
return x_input
使用生成器生成假样本和类别标签
gen_fake()
函数生成类别标签为零的假样本。该函数使用前一步创建的函数生成潜在点。然后,生成器将基于潜在点生成样本。最后,类别标签y
将生成一个零的数组,表示这是合成数据:
def gen_fake(generator, latent_dim, n):
x_input = gen_latent_points(latent_dim, n)
X = generator.predict(x_input)
y = zeros((n, 1))
return X, y
评估判别模型
以下performance_summary()
函数用于绘制真实和假数据点。该函数生成真实值和合成数据,并通过判别器在识别合成图像时的准确性来评估其性能。然后,最终将真实和合成图像绘制出来以进行视觉检查:
def performance_summary(epoch, generator, \
discriminator, latent_dim, n=100):
x_real, y_real = generate_real(n)
_, acc_real = discriminator.evaluate\
(x_real, y_real, verbose=0)
x_fake, y_fake = gen_fake\
(generator, latent_dim, n)
_, acc_fake = discriminator.evaluate\
(x_fake, y_fake, verbose=0)
print(epoch, acc_real, acc_fake)
plt.scatter(x_real[:, 0], x_real[:, 1], color='green')
plt.scatter(x_fake[:, 0], x_fake[:, 1], color='red')
plt.show()
训练生成器和判别器
现在,通过train()
函数训练你的模型。该函数包含一个for
循环,用于迭代训练轮次。在每一轮中,真实数据的大小等于批次大小的一半,然后生成合成数据。接着,判别器在真实数据上进行训练,随后是在合成数据上的训练。然后,GAN 模型也会进行训练。当训练轮次是输入参数n_eval
的倍数时,将生成性能总结:
def train(g_model, d_model, your_gan_model, \
latent_dim, n_epochs=1000, n_batch=128, n_eval=100):
half_batch = int(n_batch / 2)
for i in range(n_epochs):
x_real, y_real = generate_real(half_batch)
x_fake, y_fake = gen_fake\
(g_model, latent_dim, half_batch)
d_model.train_on_batch(x_real, y_real)
d_model.train_on_batch(x_fake, y_fake)
x_gan = gen_latent_points(latent_dim, n_batch)
y_gan = ones((n_batch, 1))
your_gan_model.train_on_batch(x_gan, y_gan)
if (i+1) % n_eval == 0:
performance_summary(i, g_model, d_model, latent_dim)
创建潜在空间、生成器、判别器、GAN 和训练数据
你可以将所有步骤结合起来构建并训练模型。在这里,latent_dim
设置为 5
,表示五个潜在维度:
latent_dim = 5
generator = define_gen(latent_dim)
discriminator = define_discrim()
your_gan_model = define_your_gan(generator, discriminator)
train(generator, discriminator, your_gan_model, latent_dim)
在这一部分,你了解了 GAN、不同的组成部分、生成器和判别器,以及如何将它们结合起来创建一个对抗网络。现在你将使用这些概念来生成你自己 GAN 的序列。
练习 11.02:使用 GAN 生成序列
在这个练习中,你将使用 GAN 创建一个模型,该模型生成一个二次函数(y=x²
),x
的值在 -0.5
和 0.5
之间。你将创建一个生成器,它将模拟正态分布,然后对这些值进行平方运算以模拟二次函数。你还将创建一个判别器,它将区分真实的二次函数和生成器的输出。接下来,你将把它们结合起来创建你的 GAN 模型。最后,你将训练你的 GAN 模型并评估模型,将生成器的结果与真实的二次函数进行比较。
执行以下步骤来完成此练习:
-
打开一个新的 Jupyter 或 Colab 笔记本,并导入以下库:
from keras.models import Sequential from numpy import hstack, zeros, ones from numpy.random import rand, randn from keras.layers import Dense import matplotlib.pyplot as plt
-
定义生成器模型。首先,通过
define_gen
创建你的生成器函数。对于生成器网络的最后一层,使用 Keras 的
linear
激活函数,因为输出向量应包含连续的实数值,类似于正态分布。输出向量的第一个元素的范围是[-0.5,0.5]
。由于你只会考虑这两个值之间的x
值,第二个元素的范围是[0.0,0.25]
:def define_gen(latent_dim, n_outputs=2): model = Sequential() model.add(Dense(15, activation='relu', \ kernel_initializer='he_uniform', \ input_dim=latent_dim)) model.add(Dense(n_outputs, activation='linear')) return model
-
现在,使用
define_disc()
定义你的判别器。判别器网络有一个二进制输出,用于识别输入是真实的还是伪造的。因此,使用 sigmoid 作为激活函数,并使用二元交叉熵作为损失函数。你正在创建一个简单的 GAN,因此使用一个包含
25
个节点的隐藏层。使用 ReLU 激活函数和he_uniform
权重初始化。你的输出层只需要一个节点用于二分类。使用 Adam 作为优化器。模型将尝试最小化你的损失函数:def define_disc(n_inputs=2): model = Sequential() model.add(Dense(25, activation='relu', \ kernel_initializer='he_uniform', \ input_dim=n_inputs)) model.add(Dense(1, activation='sigmoid')) model.compile(loss='binary_crossentropy', \ optimizer='adam', metrics=['accuracy']) return model
-
现在,使用
model.add(generator)
和model.add(discriminator)
将两个模型添加到一起。然后,在编译模型时指定二元交叉熵作为损失函数,Adam 作为优化器:def define_your_gan(generator, discriminator): discriminator.trainable = False model = Sequential() model.add(generator) model.add(discriminator) model.compile(loss='binary_crossentropy', optimizer='adam') return model
-
从数据集中提取真实样本,以便与伪造样本进行比较。使用之前定义的
generate_real()
函数。rand(n) – 0.5
创建一个范围在-0.5
到0.5
之间的n
个随机数。使用hstack
来堆叠你的数组。现在,使用y = ones((n, 1))
生成类别标签:def generate_real(n): X1 = rand(n) - 0.5 X2 = X1 * X1 X1 = X1.reshape(n, 1) X2 = X2.reshape(n, 1) X = hstack((X1, X2)) y = ones((n, 1)) return X, y
-
接下来,设置生成器模型来生成伪造样本。使用你的
gen_latent_points()
函数在潜在空间中生成相同数量的点。然后,将它们传递给生成器并用来创建样本:def gen_latent_points(latent_dim, n): x_input = randn(latent_dim * n) x_input = x_input.reshape(n, latent_dim) return x_input
-
使用生成器生成带有类别标签的伪造样本:
def gen_fake(generator, latent_dim, n): x_input = gen_latent_points(latent_dim, n) X = generator.predict(x_input) y = zeros((n, 1)) return X, y
-
评估鉴别器模型。
performance_summary()
函数将绘制真实数据和虚假数据点:def performance_summary(epoch, generator, \ discriminator, latent_dim, n=100): x_real, y_real = generate_real(n) _, acc_real = discriminator.evaluate\ (x_real, y_real, verbose=0) x_fake, y_fake = gen_fake\ (generator, latent_dim, n) _, acc_fake = discriminator.evaluate\ (x_fake, y_fake, verbose=0) print(epoch, acc_real, acc_fake) plt.scatter(x_real[:, 0], x_real[:, 1], color='green') plt.scatter(x_fake[:, 0], x_fake[:, 1], color='red') plt.show()
-
现在,使用
train()
函数训练你的模型:def train(g_model, d_model, your_gan_model, \ latent_dim, n_epochs=1000, \ n_batch=128, n_eval=100): half_batch = int(n_batch / 2) for i in range(n_epochs): x_real, y_real = generate_real(half_batch) x_fake, y_fake = gen_fake\ (g_model, latent_dim, half_batch) d_model.train_on_batch(x_real, y_real) d_model.train_on_batch(x_fake, y_fake) x_gan = gen_latent_points(latent_dim, n_batch) y_gan = ones((n_batch, 1)) your_gan_model.train_on_batch(x_gan, y_gan) if (i+1) % n_eval == 0: performance_summary(i, g_model, d_model, latent_dim)
-
创建一个潜在维度的参数,并将其设置为
5
。然后,使用各自的函数创建生成器、鉴别器和 GAN。使用train
函数训练生成器、鉴别器和 GAN 模型:latent_dim = 5 generator = define_gen(latent_dim) discriminator = define_disc() your_gan_model = define_your_gan(generator, discriminator) train(generator, discriminator, your_gan_model, latent_dim)
你将得到以下输出:
图 11.18:真实数据与虚假数据的分布
输出显示生成器通过生成越来越接近二次函数的点而逐步改进。在早期的训练过程中,生成器生成的点(以蓝色点表示)与真实的二次函数(以红色点表示)相似度较低。然而,在最后的训练周期中,生成器生成的点几乎与真实点重合,证明生成器几乎已经捕捉到了真实的底层函数——二次函数。
在这个练习中,你利用生成模型的不同组件生成符合二次函数的数据。如你在图 11.18中所见,到最后一个周期时,虚假数据与真实数据相似,表明生成器能够很好地捕捉到二次函数。
现在是书籍的最后一部分,关于 DCGANs,你将开始创建自己的图像。
深度卷积生成对抗网络(DCGANs)
DCGANs 使用卷积神经网络而非简单神经网络来构建鉴别器和生成器。它们能够生成更高质量的图像,且常用于此目的。
生成器是一组具有分数步长卷积的卷积层,也称为转置卷积。具有转置卷积的层在每一层卷积中对输入图像进行上采样,这会增加每一层之后图像的空间维度。
鉴别器是一组具有步长卷积的卷积层,因此在每一层卷积中都会对输入图像进行下采样,减少每一层之后图像的空间维度。
请看以下两张图片。你能分辨出哪一张是假图像,哪一张是真实图像吗?花点时间仔细观察每一张图。
图 11.19:人脸示例
你可能会感到惊讶,发现展示的两张图片都不是现实中的人像。这些图像是通过使用真实人物的图像生成的,但它们并不代表真实的人物。它们是由两个相互竞争的神经网络生成的。
如你所知,GAN 由两个不同的神经网络组成:鉴别器和生成器。最明显的不同是,这两个网络的输入和输出是不同的。这是理解 GAN 如何运作的关键。
对于鉴别器,输入是一张图像——一个三维张量(高度、宽度、颜色)。输出是一个单一的数字,用于进行分类。在图 11.20中,你可以看到[0.95]
。这意味着该番茄图像有 95%的概率是真实的。
对于生成器,输入是一个生成的随机种子向量。输出是一张图像。
生成器网络学习生成与数据集中图像相似的图像,而鉴别器则学习区分原始图像和生成图像。在这种竞争的方式下,它们学习生成像训练数据集中一样逼真的图像。
图 11.20:鉴别器和生成器网络
让我们看看生成器是如何训练的。从图 11.20中可以提取的一个关键点是,生成器网络的权重是静态的,而鉴别器网络的权重是经过训练的。这一点很重要,因为它使你能够区分 GAN 损失函数如何根据生成器和鉴别器的权重更新独立变化。
请注意,X
(随机种子)被输入模型以产生y
。你的模型输出的是你预测的结果。
图 11.21:生成器是如何训练的
另一个需要记住的重要点是,生成器在训练时从未接触过任何真实数据。生成器的唯一目标是欺骗鉴别器。
现在,考虑一下鉴别器网络的训练过程。鉴别器在一个由真实和虚假(生成)图像组成的训练数据集上进行训练。真实图像是从原始数据集中随机抽取的,并标记为 1。生成器网络生成相同数量的虚假图像,并标记为 0。
图 11.22:鉴别器是如何训练的
原始的“普通”GAN 与 DCGAN 之间的核心区别在于架构的不同。普通 GAN 的池化层被 DCGAN 生成器中的转置卷积层和鉴别器中的步幅卷积层所替代。DCGAN 的生成器和鉴别器都使用批量归一化层,生成器输出层和鉴别器输入层除外。此外,DCGAN 的全连接隐藏层被移除。最后,DCGAN 中的激活函数通常不同,以反映卷积层的使用。在生成器中,所有层使用 ReLU,除输出层外,输出层使用 tanh;在鉴别器中,所有层使用 Leaky ReLU。
训练一个 DCGAN
首先,你需要设置所有定义你的 DCGAN 的常量。
您想要生成的图像分辨率由gen_res
参数指定。最终的分辨率将是图像的高度和宽度为32*gen_res
。您将使用gen_res = 3
,这将导致图像分辨率为96x96
。
图像通道,img_chan
,表示每个像素的数字数量。对于彩色图像,您需要为每个颜色通道提供一个像素值:3
。
您的预览图像的行数和列数(img_rows
和img_cols
)将是您希望在每行和每列显示多少图像。例如,如果您选择预览图像的行数为4
,预览列数为4
,则总共会显示 16 张图像。
data_path
是数据存储在您计算机上的位置。这为代码提供了访问和存储数据所需的路径。
epoch
是训练数据时的迭代次数。
批大小,num_batch
,是每次迭代时的训练样本数。
缓冲区大小,num_buffer
,是用于随机打乱的数据。您只需将其设置为您的数据集大小。
种子向量,seed_vector
,是用于生成图像的种子向量的大小。
请参阅以下示例,了解如何初始化定义您 DCGAN 的所有常量:
gen_res = 3
gen_square = 32 * gen_res
img_chan = 3
img_rows = 5
img_cols = 5
img_margin = 16
seed_vector = 200
data_path = '/content/drive/MyDrive/Datasets\
'/apple-or-tomato/training_set/'
epochs = 1000
num_batch = 32
num_buffer = 1000
现在,您可以构建生成器和判别器。首先定义您的生成器函数,使用def create_generator
,并将seed_size
和channels
作为参数:
def create_generator(seed_size, channels):
model = Sequential()
现在,您将创建由输入种子生成的图像;不同的种子编号将生成不同的图像,而您的种子大小将决定生成多少不同的图像。
接下来,添加一个密集层,输出空间的维度为4*4*256
,并使用 ReLU 激活函数。input_dim
是输入形状,您将其设置为seed_size
。
使用以下代码添加一个层,将您的输入重塑为匹配4*4*256
的输出空间:
model.add(Reshape((4,4,256)))
您的UpSampling2D
层是一个简单的层,它将输入的尺寸加倍。它必须跟随一个卷积层(Conv2D
):
model.add(UpSampling2D())
添加您的Conv2D
层,并将输入设置为256
。您可以为3x3
的卷积滤波器选择kernel_size=3
。通过padding="same"
,您可以确保该层的输出与其输入具有相同的空间尺寸:
model.add(Conv2D(256,kernel_size=3,padding="same"))
使用批量归一化来规范化您的各个层,并帮助防止梯度问题。动量可以在0.0
到0.99
之间选择。在这里,使用momentum=0.8
:
model.add(BatchNormalization(momentum=0.8))
在您的最终 CNN 层,您将使用 tanh 激活函数,以确保输出图像的范围为-1
到1
:
model.add(Conv2D(channels,kernel_size=3,padding="same"))
model.add(Activation("tanh"))
完整的代码块应如下所示:
def create_generator(seed_size, channels):
model = Sequential()
model.add(Dense(4*4*256,activation="relu", \
input_dim=seed_size))
model.add(Reshape((4,4,256)))
model.add(UpSampling2D())
model.add(Conv2D(256,kernel_size=3,padding="same"))
model.add(BatchNormalization(momentum=0.8))
model.add(Activation("relu"))
model.add(UpSampling2D())
model.add(Conv2D(256,kernel_size=3,padding="same"))
model.add(BatchNormalization(momentum=0.8))
model.add(Activation("relu"))
model.add(UpSampling2D())
model.add(Conv2D(128,kernel_size=3,padding="same"))
model.add(BatchNormalization(momentum=0.8))
model.add(Activation("relu"))
if gen_res>1:
model.add(UpSampling2D(size=(gen_res,gen_res)))
model.add(Conv2D(128,kernel_size=3,padding="same"))
model.add(BatchNormalization(momentum=0.8))
model.add(Activation("relu"))
model.add(Conv2D(channels,kernel_size=3,padding="same"))
model.add(Activation("tanh"))
return model
现在,您可以定义您的判别器:
def create_discriminator(image_shape):
model = Sequential()
这里,使用 Conv2D
层。你可以选择 kernel_size=3
来作为 3x3
卷积滤波器。通过 strides=2
,你指定了“滑动窗口”的步幅。设置 input_shape=image_shape
来确保它们匹配,再通过 padding="same"
来确保该层的输出与输入具有相同的空间维度。为所有判别器层添加 LeakyReLU 激活函数:
model.add(Conv2D(32, kernel_size=3, \
strides=2, input_shape=image_shape, \
padding="same"))
model.add(LeakyReLU(alpha=0.2))
Flatten
层将你的数据转换成一个单一的特征向量,以便输入到最后一层:
model.add(Flatten())
对于你的激活函数,使用 sigmoid 作为二分类输出:
model.add(Dense(1, activation='sigmoid'))
完整的代码块应如下所示:
def create_discriminator(image_shape):
model = Sequential()
model.add(Conv2D(32, kernel_size=3, strides=2, \
input_shape=image_shape,
padding="same"))
model.add(LeakyReLU(alpha=0.2))
model.add(Dropout(0.25))
model.add(Conv2D(64, kernel_size=3, strides=2, \
padding="same"))
model.add(ZeroPadding2D(padding=((0,1),(0,1))))
model.add(BatchNormalization(momentum=0.8))
model.add(LeakyReLU(alpha=0.2))
model.add(Dropout(0.25))
model.add(Conv2D(128, kernel_size=3, strides=2, \
padding="same"))
model.add(BatchNormalization(momentum=0.8))
model.add(LeakyReLU(alpha=0.2))
model.add(Dropout(0.25))
model.add(Conv2D(256, kernel_size=3, strides=1, \
padding="same"))
model.add(BatchNormalization(momentum=0.8))
model.add(LeakyReLU(alpha=0.2))
model.add(Dropout(0.25))
model.add(Conv2D(512, kernel_size=3, \
strides=1, padding="same"))
model.add(BatchNormalization(momentum=0.8))
model.add(LeakyReLU(alpha=0.2))
model.add(Dropout(0.25))
model.add(Flatten())
model.add(Dense(1, activation='sigmoid'))
return model
接下来,创建你的损失函数。由于判别器和生成器网络的输出不同,你需要为它们定义两个独立的损失函数。此外,它们需要在独立的网络传递中分别训练。
你可以使用 tf.keras.losses.BinaryCrossentropy
来计算 cross_entropy
。这个函数计算真实标签与预测标签之间的损失。然后,使用 tf.ones
和 tf.zeros
从 real_output
和 fake_output
参数定义 discrim_loss
函数,以计算 total_loss
:
cross_entropy = tf.keras.losses.BinaryCrossentropy()
def discrim_loss(real_output, fake_output):
real_loss = cross_entropy(tf.ones_like(real_output), \
real_output)
fake_loss = cross_entropy(tf.zeros_like(fake_output), \
fake_output)
total_loss = real_loss + fake_loss
return total_loss
def gen_loss(fake_output):
return cross_entropy(tf.ones_like(fake_output), \
fake_output)
生成器和判别器都使用 Adam 优化器,并且具有相同的学习率和动量:
gen_optimizer = tf.keras.optimizers.Adam(1.5e-4,0.5)
disc_optimizer = tf.keras.optimizers.Adam(1.5e-4,0.5)
在这里,你有了你单独的训练步骤。非常重要的是你每次只能修改一个网络的权重。使用 tf.GradientTape()
,你可以同时训练判别器和生成器,但它们是独立进行的。这就是 TensorFlow 自动微分的工作原理。它会计算导数。你将看到它创建了两个“带子”——gen_tape
和 disc_tape
。
然后,为判别器创建 real_output
和 fake_output
。用这个来计算生成器的损失(g_loss
)。现在,你可以计算判别器的损失(d_loss
),计算生成器和判别器的梯度,分别使用 gradients_of_generator
和 gradients_of_discriminator
,并应用它们:
@tf.function
def train_step(images):
seed = tf.random.normal([num_batch, seed_vector])
with tf.GradientTape() as gen_tape, \
tf.GradientTape() as disc_tape:
gen_imgs = generator(seed, training=True)
real_output = discriminator(images, training=True)
fake_output = discriminator(gen_imgs, training=True)
g_loss = gen_loss(fake_output)
d_loss = discrim_loss(real_output, fake_output)
gradients_of_generator = gen_tape.gradient(\
g_loss, generator.trainable_variables)
gradients_of_discriminator = disc_tape.gradient(\
d_loss, discriminator.trainable_variables)
gen_optimizer.apply_gradients(zip(
gradients_of_generator, generator.trainable_variables))
disc_optimizer.apply_gradients(zip(
gradients_of_discriminator,
discriminator.trainable_variables))
return g_loss,d_loss
接下来,使用 fixed_seeds
创建一些固定的种子,每个图像显示一个种子,每个种子向量也是如此。这样做是为了你能跟踪相同的图像,观察随时间变化的变化。通过 for epoch in range
,你可以跟踪时间。通过 for image_batch in dataset
遍历每个批次。现在,继续通过 generator_loss
和 discriminator_loss
跟踪生成器和判别器的损失。现在,你可以看到所有这些信息的良好展示,随着训练的进行:
def train(dataset, epochs):
fixed_seed = np.random.normal\
(0, 1, (img_rows * img_cols, seed_vector))
start = time.time()
for epoch in range(epochs):
epoch_start = time.time()
g_loss_list = []
d_loss_list = []
for image_batch in dataset:
t = train_step(image_batch)
g_loss_list.append(t[0])
d_loss_list.append(t[1])
generator_loss = sum(g_loss_list) / len(g_loss_list)
discriminator_loss = sum(d_loss_list) / len(d_loss_list)
epoch_elapsed = time.time()-epoch_start
print (f'Epoch {epoch+1}, gen loss={generator_loss}', \
f'disc loss={discriminator_loss},'\
f' {time_string(epoch_elapsed)}')
save_images(epoch,fixed_seed)
elapsed = time.time()-start
print (f'Training time: {time_string(elapsed)}')
在本节的最后,你迈出了使用生成网络的进一步一步。你学习了如何训练一个 DCGAN,并且如何将生成器和判别器一起使用,以创造你自己的图像。
在下一个练习中,你将实现本节到目前为止所学的内容。
练习 11.03:使用 DCGAN 生成图像
在本次练习中,你将使用 DCGAN 从头开始生成自己的图像。你将构建一个包含卷积层的生成器和判别器的 DCGAN。然后,你将用番茄的图像训练你的 DCGAN,并在训练过程中,定期从生成器输出生成的图像,以跟踪生成器的表现。
注意
你可以在这里找到tomato-or-apple
数据集:packt.link/6Z8vW
。
对于本次练习,建议你使用 Google Colab:
-
加载 Google Colab 和 Google Drive:
try: from google.colab import drive drive.mount('/content/drive', force_remount=True) COLAB = True print("Note: using Google CoLab") %tensorflow_version 2.x except: print("Note: not using Google CoLab") COLAB = False
你的输出应该类似于这样:
Mounted at /content/drive Note: using Google Colab
-
导入相关库:
import tensorflow as tf from tensorflow.keras.layers import Input, Reshape, Dropout, Dense from tensorflow.keras.layers import Flatten, BatchNormalization from tensorflow.keras.layers import Activation, ZeroPadding2D from tensorflow.keras.layers import LeakyReLU from tensorflow.keras.layers import UpSampling2D, Conv2D from tensorflow.keras.models import Sequential, Model, load_model from tensorflow.keras.optimizers import Adam import zipfile import numpy as np from PIL import Image from tqdm import tqdm import os import time import matplotlib.pyplot as plt from skimage.io import imread
-
格式化时间字符串以跟踪你的时间使用:
def time_string(sec_elapsed): hour = int(sec_elapsed / (60 * 60)) minute = int((sec_elapsed % (60 * 60)) / 60) second = sec_elapsed % 60 return "{}:{:>02}:{:>05.2f}".format(hour, minute, second)
-
将生成分辨率设置为
3
。同时,将img_rows
和img_cols
设置为5
,将img_margin
设置为16
,这样你的预览图像将是一个5x5
的数组(25 张图像),并有 16 像素的边距。将
seed_vector
设置为200
。将data_path
设置为你存储图像数据集的位置。正如你所看到的,你在这里使用的是 Google Drive。如果你不知道数据路径,可以简单地找到文件的位置,右键点击并选择复制路径
。将你的周期数设置为1000
。最后,打印参数:
gen_res = 3 gen_square = 32 * gen_res img_chan = 3 img_rows = 5 img_cols = 5 img_margin = 16 seed_vector = 200 data_path = '/content/drive/MyDrive/Datasets'\ '/apple-or-tomato/training_set/' epochs = 5000 num_batch = 32 num_buffer = 60000 print(f"Will generate a resolution of {gen_res}.") print(f"Will generate {gen_square}px square images.") print(f"Will generate {img_chan} image channels.") print(f"Will generate {img_rows} preview rows.") print(f"Will generate {img_cols} preview columns.") print(f"Our preview margin equals {img_margin}.") print(f"Our data path is: {data_path}.") print(f"Our number of epochs are: {epochs}.") print(f"Will generate a batch size of {num_batch}.") print(f"Will generate a buffer size of {num_buffer}.")
你的输出应该类似于这样:
图 11.23:显示参数的输出
-
加载并预处理图像。在这里,你将保存一个 NumPy 预处理文件。加载之前训练的 NumPy 文件。图像的二进制文件名包含图像的尺寸信息:
training_binary_path = os.path.join(data_path,\ f'training_data_{gen_square}_{gen_square}.npy') print(f"Looking for file: {training_binary_path}") if not os.path.isfile(training_binary_path): start = time.time() print("Loading training images...") train_data = [] images_path = os.path.join(data_path,'tomato') for filename in tqdm(os.listdir(images_path)): path = os.path.join(images_path,filename) images = Image.open(path).resize((gen_square, gen_square),Image.ANTIALIAS) train_data.append(np.asarray(images)) train_data = np.reshape(train_data,(-1,gen_square, gen_square,img_chan)) train_data = train_data.astype(np.float32) train_data = train_data / 127.5 - 1. print("Saving training images...") np.save(training_binary_path,train_data) elapsed = time.time()-start print (f'Image preprocessing time: {time_string(elapsed)}') else: print("Loading the training data...") train_data = np.load(training_binary_path)
-
批处理并打乱数据。使用
tensorflow.data.Dataset
对象库及其功能来打乱数据集并创建批次:train_dataset = tf.data.Dataset.from_tensor_slices(train_data) \ .shuffle(num_buffer).batch(num_batch)
-
构建生成器:
def create_generator(seed_size, channels): model = Sequential() model.add(Dense(4*4*256,activation="relu", \ input_dim=seed_size)) model.add(Reshape((4,4,256))) model.add(UpSampling2D()) model.add(Conv2D(256,kernel_size=3,padding="same")) model.add(BatchNormalization(momentum=0.8)) model.add(Activation("relu")) model.add(UpSampling2D()) model.add(Conv2D(256,kernel_size=3,padding="same")) model.add(BatchNormalization(momentum=0.8)) model.add(Activation("relu")) model.add(UpSampling2D()) model.add(Conv2D(128,kernel_size=3,padding="same")) model.add(BatchNormalization(momentum=0.8)) model.add(Activation("relu")) if gen_res>1: model.add(UpSampling2D(size=(gen_res,gen_res))) model.add(Conv2D(128,kernel_size=3,padding="same")) model.add(BatchNormalization(momentum=0.8)) model.add(Activation("relu")) model.add(Conv2D(channels,kernel_size=3,padding="same")) model.add(Activation("tanh")) return model
-
构建判别器:
def create_discriminator(image_shape): model = Sequential() model.add(Conv2D(32, kernel_size=3, strides=2, \ input_shape=image_shape, padding="same")) model.add(LeakyReLU(alpha=0.2)) model.add(Dropout(0.25)) model.add(Conv2D(64, kernel_size=3, \ strides=2, padding="same")) model.add(ZeroPadding2D(padding=((0,1),(0,1)))) model.add(BatchNormalization(momentum=0.8)) model.add(LeakyReLU(alpha=0.2)) model.add(Dropout(0.25)) model.add(Conv2D(128, kernel_size=3, strides=2, \ padding="same")) model.add(BatchNormalization(momentum=0.8)) model.add(LeakyReLU(alpha=0.2)) model.add(Dropout(0.25)) model.add(Conv2D(256, kernel_size=3, strides=1, \ padding="same")) model.add(BatchNormalization(momentum=0.8)) model.add(LeakyReLU(alpha=0.2)) model.add(Dropout(0.25)) model.add(Conv2D(512, kernel_size=3, strides=1, \ padding="same")) model.add(BatchNormalization(momentum=0.8)) model.add(LeakyReLU(alpha=0.2)) model.add(Dropout(0.25)) model.add(Flatten()) model.add(Dense(1, activation='sigmoid')) return model
-
在训练过程中,展示生成的图像以获得一些关于进展的见解。保存这些图像。在每 100 个周期时,保存一组图像,以评估进展:
def save_images(cnt,noise): img_array = np.full(( img_margin + (img_rows * (gen_square+img_margin)), img_margin + (img_cols * (gen_square+img_margin)), 3), 255, dtype=np.uint8) gen_imgs = generator.predict(noise) gen_imgs = 0.5 * gen_imgs + 0.5 img_count = 0 for row in range(img_rows): for col in range(img_cols): r = row * (gen_square+16) + img_margin c = col * (gen_square+16) + img_margin img_array[r:r+gen_square,c:c+gen_square] \ = gen_imgs[img_count] * 255 img_count += 1 output_path = os.path.join(data_path,'output') if not os.path.exists(output_path): os.makedirs(output_path) filename = os.path.join(output_path,f"train-{cnt}.png") im = Image.fromarray(img_array) im.save(filename)
-
现在,创建一个生成噪声的生成器:
generator = create_generator(seed_vector, img_chan) noise = tf.random.normal([1, seed_vector]) gen_img = generator(noise, training=False) plt.imshow(gen_img[0, :, :, 0])
你的输出应该类似于这样:
图 11.24:显示噪声的输出
-
输入以下命令查看生成的图像之一:
img_shape = (gen_square,gen_square,img_chan) discriminator = create_discriminator(img_shape) decision = discriminator(gen_img) print (decision)
你的输出应该类似于这样:
tf.Tensor([[0.4994658]], shape=(1,1), dtype=float32)
-
创建你的损失函数。由于判别器和生成器网络的输出不同,你需要为它们定义两个独立的损失函数。此外,它们需要在通过网络的独立传递中分别训练。使用
tf.keras.losses.BinaryCrossentropy
作为cross_entropy
。它计算真实标签与预测标签之间的损失。然后,使用tf.ones
和tf.zeros
从real_output
和fake_output
定义discrim_loss
函数来计算total_loss
:cross_entropy = tf.keras.losses.BinaryCrossentropy() def discrim_loss(real_output, fake_output): real_loss = cross_entropy(tf.ones_like(real_output), \ real_output) fake_loss = cross_entropy(tf.zeros_like(fake_output), \ fake_output) total_loss = real_loss + fake_loss return total_loss def gen_loss(fake_output): return cross_entropy(tf.ones_like(fake_output), \ fake_output)
-
创建两个 Adam 优化器(一个用于生成器,一个用于判别器),为每个优化器使用相同的学习率和动量:
gen_optimizer = tf.keras.optimizers.Adam(1.5e-4,0.5) disc_optimizer = tf.keras.optimizers.Adam(1.5e-4,0.5)
-
创建一个函数来实现单独的训练步骤。使用
tf.GradientTape()
,同时训练鉴别器和生成器,但它们彼此分开进行。然后,创建
real_output
和fake_output
用于鉴别器。将其用于生成器的损失(g_loss
)。接着,计算鉴别器的损失(d_loss
),并计算生成器和鉴别器的梯度,分别使用gradients_of_generator
和gradients_of_discriminator
,并应用它们:@tf.function def train_step(images): seed = tf.random.normal([num_batch, seed_vector]) with tf.GradientTape() as gen_tape, \ tf.GradientTape() as disc_tape: gen_imgs = generator(seed, training=True) real_output = discriminator(images, training=True) fake_output = discriminator(gen_imgs, training=True) g_loss = gen_loss(fake_output) d_loss = discrim_loss(real_output, fake_output) gradients_of_generator = gen_tape.gradient(\ g_loss, generator.trainable_variables) gradients_of_discriminator = disc_tape.gradient(\ d_loss, discriminator.trainable_variables) gen_optimizer.apply_gradients(zip( gradients_of_generator, generator.trainable_variables)) disc_optimizer.apply_gradients(zip( gradients_of_discriminator, discriminator.trainable_variables)) return g_loss,d_loss
-
创建一个固定种子的数组,
fixed_seeds
的数量等于沿一个维度显示的图像数,另一个维度则是种子向量,以便你能够跟踪相同的图像。这使你能够看到单个种子如何随时间变化。使用for image_batch in dataset
循环遍历每个批次。继续跟踪生成器和鉴别器的损失,分别使用generator_loss
和discriminator_loss
。在训练过程中,你会得到一个很好的信息展示:def train(dataset, epochs): fixed_seed = np.random.normal(0, 1, (img_rows * img_cols, seed_vector)) start = time.time() for epoch in range(epochs): epoch_start = time.time() g_loss_list = [] d_loss_list = [] for image_batch in dataset: t = train_step(image_batch) g_loss_list.append(t[0]) d_loss_list.append(t[1]) generator_loss = sum(g_loss_list) / len(g_loss_list) discriminator_loss = sum(d_loss_list) / len(d_loss_list) epoch_elapsed = time.time()-epoch_start print (f'Epoch {epoch+1}, gen loss={generator_loss}', \ f'disc loss={discriminator_loss},'\ f' {time_string(epoch_elapsed)}') save_images(epoch,fixed_seed) elapsed = time.time()-start print (f'Training time: {time_string(elapsed)}')
-
在你的训练数据集上进行训练:
train(train_dataset, epochs)
你的输出应该类似于以下内容:
图 11.25:训练输出
-
仔细观察生成的图像,
train-0
、train-100
、train-250
、train-500
和train-999
。这些图像是在训练过程中自动保存的,正如在train
函数中指定的那样:a = imread('/content/drive/MyDrive/Datasets'\ '/apple-or-tomato/training_set/output/train-0.png') plt.imshow(a)
你将得到如下输出:
图 11.26:完成第一个周期后的输出图像
现在,运行以下命令:
a = imread('/content/drive/MyDrive/Datasets'\
'/apple-or-tomato/training_set/output/train-100.png')
plt.imshow(a)
你将得到如下输出:
图 11.27:完成第 101 个周期后的输出图像
此外,运行以下命令:
a = imread('/content/drive/MyDrive/Datasets'\
'/apple-or-tomato/training_set/output/train-500.png')
plt.imshow(a)
你将得到如下输出:
图 11.28:完成第 501 个周期后的输出图像
现在,运行以下命令:
a = imread('/content/drive/MyDrive/Datasets'\
'/apple-or-tomato/training_set/output/train-999.png')
plt.imshow(a)
你将得到如下输出:
图 11.29:完成第 1,000 个周期后的输出图像
输出显示,在经过 1,000 个周期后,生成器生成的合成番茄图像与真实番茄非常相似,并且图像在训练过程中逐步改善。
在这个练习中,你使用 DCGAN 生成了自己的图像。从图 11.29中可以看出,结果令人印象深刻。尽管有些图像很容易辨别为伪造的,但其他一些看起来非常真实。
在下一部分,你将完成一个最终的活动,将本章学到的所有知识付诸实践,并使用 GAN 生成你自己的图像。
活动 11.01:使用 GAN 生成图像
在本次活动中,你将构建一个 GAN 来生成新图像。然后,通过分别创建经典 GAN 和 DCGAN,并在相同数据集上训练它们 500 个周期,比较两者的结果。此活动将展示模型架构对输出的影响,并说明为什么选择合适的模型如此重要。你将使用banana-or-orange
数据集。你将只使用香蕉训练集的图像进行训练并生成新图像。
注意
你可以在这里找到banana-or-orange
数据集:packt.link/z6TCy
。
执行以下步骤完成任务:
-
加载 Google Colab 和 Google Drive。
导入相关库,包括
tensorflow
、numpy
、zipfile
、tqdm
、zipfile
、skimage
、time
和os
。 -
创建一个函数来格式化时间字符串,以便跟踪你的时间使用情况。
-
将生成分辨率设置为
3
。同时,将img_rows
和img_cols
设置为5
,并将img_margin
设置为16
,以便预览图像为5x5
的数组(25 张图像),并具有 16 像素的边距。将seed_vector
设为200
,data_path
设为你存储图像数据集的位置,epochs
设为500
。最后,打印参数。 -
如果存在先前执行时处理的 NumPy 文件,则将其加载到内存中;否则,预处理数据并保存图像二进制数据。
-
批量处理并打乱数据。使用
tensorflow.data.Dataset
对象库,利用其功能来打乱数据集并创建批次。 -
构建 DCGAN 的生成器。
-
构建 DCGAN 的判别器。
-
构建经典 GAN 的生成器。
-
构建经典 GAN 的判别器。
-
创建一个函数来生成并保存图像,可以在模型训练过程中查看进度。
-
接下来,初始化 DCGAN 的生成器并查看输出。
-
初始化经典 GAN 的生成器并查看输出。
-
打印 DCGAN 判别器在种子图像上的决策结果。
-
打印经典 GAN 判别器在种子图像上的决策结果。
-
创建你的损失函数。由于判别器和生成器网络的输出不同,你可以为它们定义两个独立的损失函数。此外,它们需要在网络中独立训练。两个 GAN 可以使用相同的损失函数来训练它们的判别器和生成器。你可以使用
tf.keras.losses.BinaryCrossentropy
来计算cross_entropy
,该函数计算真实标签和预测标签之间的损失。然后,使用tf.ones
和tf.zeros
从real_output
和fake_output
定义discrim_loss
函数来计算total_loss
。 -
创建两个 Adam 优化器,一个用于生成器,一个用于判别器。为每个优化器使用相同的学习率和动量。
-
创建判别器的
real_output
和fake_output
。然后,使用这些计算生成器损失(g_loss
)。接着,计算判别器损失(d_loss
)以及生成器和判别器的梯度,使用gradients_of_generator
和gradients_of_discriminator
并应用这些梯度。将这些步骤封装在一个函数中,传入生成器、判别器和图像,并返回生成器损失(g_loss
)和判别器损失(d_loss
)。 -
接下来,创建一定数量的固定种子,
fixed_seeds
的数量等于要显示的图像数,这样你就可以跟踪相同的图像。这让你可以看到每个种子随时间的演变,并通过for epoch in range
跟踪你的训练进度。现在,通过for image_batch in dataset
循环遍历每个批次,继续跟踪生成器和判别器的损失,使用generator_loss
和discriminator_loss
。现在,你已经有了一个很好的信息显示界面,展示了训练过程中的所有数据。 -
在你的训练数据集上训练 DCGAN 模型。
-
在你的训练数据集上训练基础模型。
-
查看在第 100 次训练轮次(epoch)后由 DCGAN 模型生成的图像。
-
查看在第 500 次训练轮次(epoch)后由 DCGAN 模型生成的图像。
-
查看在第 100 次训练轮次(epoch)后由基础 GAN 模型生成的图像。
-
查看在第 500 次训练轮次(epoch)后由基础 GAN 模型生成的图像。
注意
这项活动的解决方案可以通过这个链接找到。
总结
在本章中,你了解了一类非常激动人心的机器学习模型——生成模型。你通过使用生成 LSTM(长短期记忆网络)在语言建模挑战中生成文本输出,发现了这一新兴且不断发展的机器学习领域的惊人潜力。
然后,你了解了生成对抗模型。你实现了一个生成对抗网络(GAN),用来生成符合正态分布的点数据。你还更进一步研究了深度卷积生成对抗网络(DCGAN),发现了如何使用 GAN 的一个强大应用,在训练的番茄和香蕉图像中创建出具有可被人类识别的水果特征的新图像。
我们希望你喜欢《TensorFlow 工作坊》的最后一章,并且享受了整本书的学习过程。
让我们回顾一下你完成的这段精彩旅程。首先,你通过学习 TensorFlow 的基础知识,了解了如何对人工神经网络(ANN)的基本构建块——张量(tensors)进行操作。然后,你学会了如何加载和预处理各种数据类型,包括表格数据、图像、音频文件和文本。
接下来,你了解了与 TensorFlow 配合使用的各种资源,包括用于可视化模型重要组件的 TensorBoard,访问预训练模型的 TensorFlow Hub,以及在托管环境中构建和训练模型的 Google Colab。然后,你深入了解了如何构建顺序模型来解决回归和分类问题。
为了提高模型性能,你学习了正则化和超参数调优,这些方法用于确保你的模型不仅能在训练数据上表现良好,还能在新的、未见过的数据上也表现优异。从那里,你探索了卷积神经网络,它们在处理图像数据时表现出色。之后,你深入学习了如何利用预训练网络来解决自己的问题,并根据自己的数据进行微调。接着,你学习了如何构建和训练 RNN,这些网络在处理序列数据时非常有效,例如股票价格或自然语言。在书籍的后部分,你探索了使用功能性 API 的更高级的 TensorFlow 能力,并学习了如何在 TensorFlow 中开发你可能需要的任何东西,最后,你学会了如何通过生成模型使用 TensorFlow 进行更多创意性的工作。
通过这本书,你不仅迈出了使用 TensorFlow 的第一步,还学会了如何创建模型并为复杂问题提供解决方案。从头到尾的旅程充满了兴奋,我们祝愿你在未来的进步中好运。
附录
1. 使用 TensorFlow 进行机器学习简介
活动 1.01:在 TensorFlow 中执行张量加法
解答:
-
导入 TensorFlow 库:
import tensorflow as tf
-
使用 TensorFlow 的
Variable
类创建两个秩为0
的张量:var1 = tf.Variable(2706, tf.int32) var2 = tf.Variable(2386, tf.int32)
-
创建一个新变量,将创建的两个标量相加,并打印结果:
var_sum = var1 + var2 var_sum.numpy()
这将产生以下输出:
5092
该输出显示
产品 A
在地点 X
的总收入。 -
使用 TensorFlow 的
Variable
类创建两个张量,一个是秩为0
的标量,另一个是秩为1
的向量:scalar1 = tf.Variable(95, tf.int32) vector1 = tf.Variable([2706, 2799, 5102], \ tf.int32)
-
创建一个新变量,将创建的标量和向量相加,并打印结果:
vector_scalar_sum = scalar1 + vector1 vector_scalar_sum.numpy()
这将产生以下输出:
array([2801, 2894, 5197])
结果是
销售员 1
在地点 X
的新销售目标。 -
现在使用 TensorFlow 的
Variable
类创建三个秩为 2 的张量,表示每个产品、销售人员和地点的收入:matrix1 = tf.Variable([[2706, 2799, 5102], \ [2386, 4089, 5932]], tf.int32) matrix2 = tf.Variable([[5901, 1208, 645], \ [6235, 1098, 948]], tf.int32) matrix3 = tf.Variable([[3908, 2339, 5520], \ [4544, 1978, 4729]], tf.int32)
-
创建一个新变量,作为三个张量的和,并打印结果:
matrix_sum = matrix1 + matrix2 + matrix3 matrix_sum.numpy()
这将产生以下输出:
图 1.42:矩阵求和结果作为 NumPy 变量的输出
结果表示每个产品在每个地点的总收入。
在本活动中,你对秩为 0
、1
和 2
的张量进行了加法运算,并展示了标量(秩为 0 的张量)可以与其他秩的张量相加,这被称为标量加法。
活动 1.02:在 TensorFlow 中执行张量重塑和转置
解答:
-
导入 TensorFlow 库:
import tensorflow as tf
-
使用 TensorFlow 的
Variable
类创建一个包含 24 个元素的单维数组。验证矩阵的形状:array1 = tf.Variable([*range(24)]) array1.shape.as_list()
这将产生以下输出:
[24]
-
使用 TensorFlow 的
reshape
函数将矩阵重塑为 12 行 2 列。验证新矩阵的形状:reshape1 = tf.reshape(array1, shape=[12, 2]) reshape1.shape.as_list()
这将产生以下输出:
[12, 2]
-
使用 TensorFlow 的
reshape
函数将矩阵重塑为3x4x2
的形状。验证新矩阵的形状:reshape2 = tf.reshape(array1, shape=[3, 4, 2]) reshape2.shape.as_list()
这将产生以下输出:
[3, 4, 2]
-
通过使用 TensorFlow 的
rank
函数验证这个新张量的秩为3
:tf.rank(reshape2).numpy()
这将产生以下输出:
3
-
转置 步骤 3 中创建的张量。验证新张量的形状:
transpose1 = tf.transpose(reshape1) transpose1.shape.as_list()
这将产生以下输出:
[2, 12]
在本活动中,你练习了对各种秩的张量进行重塑和转置,并学习了如何通过重塑改变张量的秩。你使用 TensorFlow 的 reshape
和 transpose
函数模拟了将 24 个小学生分组为不同大小的班级项目。
活动 1.03:应用激活函数
解答:
-
导入 TensorFlow 库:
import tensorflow as tf
-
创建一个
3x4
的张量作为输入,其中行表示来自不同销售代表的销售数据,列表示经销商提供的各种汽车,值表示与建议零售价(MSRP)的平均百分比差异。根据销售人员是否能够以高于或低于 MSRP 的价格销售,值可以是正数或负数:input1 = tf.Variable([[-0.013, 0.024, 0.06, 0.022], \ [0.001, -0.047, 0.039, 0.016], \ [0.018, 0.030, -0.021, -0.028]], \ tf.float32)
-
创建一个
4x1
的weights
张量,形状为4x1
,表示汽车的建议零售价(MSRP):weights = tf.Variable([[19995.95], [24995.50], \ [36745.50], [29995.95]], \ tf.float32)
-
创建一个大小为
3x1
的偏置张量,表示与每个销售人员相关的固定成本:bias = tf.Variable([[-2500.0],[-2500.0],[-2500.0]], \ tf.float32)
-
将输入矩阵与权重进行矩阵乘法,显示所有汽车的平均偏差值,并加上偏置来减去销售人员的固定成本:
output = tf.matmul(input1,weights) + bias output
以下是输出:
图 1.43: 矩阵乘法的输出
-
应用 ReLU 激活函数来突出显示净正销售的销售人员:
output = tf.keras.activations.relu(output) output
这将产生以下输出:
图 1.44: 应用激活函数后的输出
这个结果显示了那些有净正销售的销售人员的结果;那些净负销售的销售人员被置为零。
在这个活动中,你对不同大小的张量进行了张量乘法、张量加法,并且应用了激活函数。你首先定义了张量,然后对其中两个进行了矩阵乘法,接着加上了一个偏置张量,最后对结果应用了激活函数。
2. 加载和处理数据
活动 2.01:加载表格数据并使用 MinMaxScaler 重新缩放数值字段
解答:
-
打开一个新的 Jupyter 笔记本来实现此活动。将文件保存为
Activity2-01.ipnyb
。 -
在新的 Jupyter Notebook 单元格中,导入 pandas 库,如下所示:
import pandas as pd
-
创建一个新的 pandas DataFrame,命名为
df
,并将Bias_correction_ucl.csv
文件读取到其中。通过打印结果 DataFrame 来检查数据是否正确加载:df = pd.read_csv('Bias_correction_ucl.csv')
注意
确保你根据文件在系统上的位置更改路径(高亮部分)。如果你在存储 CSV 文件的同一目录中运行 Jupyter 笔记本,可以不做任何修改直接运行上述代码。
-
使用
drop
方法删除date
列。由于你正在删除列,请将1
传递给axis
参数,将True
传递给inplace
参数:df.drop('Date', inplace=True, axis=1)
-
绘制
Present_Tmax
列的直方图,表示数据集中不同日期和气象站的最高温度:ax = df['Present_Tmax'].hist(color='gray') ax.set_xlabel("Normalized Temperature") ax.set_ylabel("Frequency")
输出将如下所示:
图 2.20: Present_Tmax 列的温度与频率的直方图
结果直方图显示了
Present_Tmax
列值的分布。 -
导入
MinMaxScaler
并使用它来拟合和转换特征 DataFrame:from sklearn.preprocessing import MinMaxScaler scaler = MinMaxScaler() df2 = scaler.fit_transform(df) df2 = pd.DataFrame(df2, columns=df.columns)
-
绘制变换后的
Present_Tmax
列的直方图:ax = df2['Present_Tmax'].hist(color='gray') ax.set_xlabel("Normalized Temperature") ax.set_ylabel("Frequency")
输出结果如下:
图 2.21:重新缩放的 Present_Tmax
列的直方图
结果的直方图显示温度值范围从 0
到 1
,这一点从直方图的 x 轴范围可以看出。通过使用 MinMaxScaler
,这些值将始终保持最小值为 0
,最大值为 1
。
在本次活动中,您进一步对数值字段进行了预处理。在这里,您对数值字段进行了缩放,使其最小值为 0
,最大值为 1
。如果数值字段不是正态分布的,这比标准缩放器更有用。它还确保结果字段限制在最小值和最大值之间。
活动 2.02:加载用于批处理的图像数据
解决方案:
-
打开一个新的 Jupyter Notebook 来实现此活动。将文件保存为
Activity2-02.ipnyb
。 -
在新的 Jupyter Notebook 单元格中,导入 Keras 的预处理包中的
ImageDataGenerator
类:from tensorflow.keras.preprocessing.image \ import ImageDataGenerator
-
实例化
ImageDataGenerator
类,并传入rescale
参数,值为1/255
,以将图像值转换为介于0
和1
之间:train_datagen = ImageDataGenerator(rescale = 1./255,\ shear_range = 0.2,\ rotation_range= 180,\ zoom_range = 0.2,\ horizontal_flip = True)
-
使用数据生成器的
flow_from_directory
方法将数据生成器指向图像数据。传入目标大小、批量大小和类别模式等参数:training_set = train_datagen.flow_from_directory\ ('image_data',\ target_size = (64, 64),\ batch_size = 25,\ class_mode = 'binary')
-
创建一个函数来显示批次中的图像:
import matplotlib.pyplot as plt def show_batch(image_batch, label_batch):\ lookup = {v: k for k, v in training_set.class_indices.items()} label_batch = [lookup[label] for label in \ label_batch] plt.figure(figsize=(10,10)) for n in range(25): ax = plt.subplot(5,5,n+1) plt.imshow(image_batch[n]) plt.title(label_batch[n].title()) plt.axis('off')
-
从数据生成器中获取一个批次,并将其传递给函数以显示图像及其标签:
image_batch, label_batch = next(training_set) show_batch(image_batch, label_batch)
输出结果如下:
图 2.22:从批次中增强的图像
输出显示了一个批次的 25 张图像及其各自的标签,这些图像通过旋转、缩放和剪切进行增强。增强后的图像显示了相同的物体,但具有不同的像素值,这有助于创建更强健的模型。
活动 2.03:加载用于批处理的音频数据
解决方案:
-
打开一个新的 Jupyter Notebook 来实现此活动。将文件保存为
Activity2-03.ipnyb
。 -
在新的 Jupyter Notebook 单元格中,导入 TensorFlow 和
os
库:import tensorflow as tf import os
-
创建一个函数,使用 TensorFlow 的
read_file
函数加载音频文件,然后用decode_wav
函数返回音频数据,最终返回结果张量的转置:def load_audio(file_path, sample_rate=44100): # Load audio at 44.1kHz sample-rate audio = tf.io.read_file(file_path) audio, sample_rate = tf.audio.decode_wav\ (audio,\ desired_channels=-1,\ desired_samples=sample_rate) return tf.transpose(audio)
-
使用
os.list_dir
将音频数据的路径加载为列表:prefix = " ../Datasets/data_speech_commands_v0.02"\ "/zero/" paths = [os.path.join(prefix, path) for path in \ os.listdir(prefix)]
-
创建一个函数,接收数据集对象,对其进行洗牌,并使用在步骤 2中创建的函数加载音频数据。然后,将绝对值和
log1p
函数应用到数据集。此函数将每个值加1
,然后取对数。接着,重复数据集对象,批量化,并使用等于批量大小的缓冲区大小预取数据:def prep_ds(ds, shuffle_buffer_size=1024, \ batch_size=16): # Randomly shuffle (file_path, label) dataset ds = ds.shuffle(buffer_size=shuffle_buffer_size) # Load and decode audio from file paths ds = ds.map(load_audio) # Take the absolute value ds = ds.map(tf.abs) # Apply log1p function ds = ds.map(tf.math.log1p) # Repeat dataset forever ds = ds.repeat() # Prepare batches ds = ds.batch(batch_size) # Prefetch ds = ds.prefetch(buffer_size=batch_size) return ds
-
使用 TensorFlow 的
from_tensor_slices
函数创建数据集对象,并传入音频文件的路径。然后,将你在第 5 步中创建的函数应用到数据集对象上:ds = tf.data.Dataset.from_tensor_slices(paths) train_ds = prep_ds(ds)
-
获取数据集的第一批数据并打印出来:
for x in train_ds.take(1):\ print(x)
输出将如下所示:
图 2.23:音频数据的一批数据
输出显示了第一批 MFCC 频谱值的张量形式。
-
绘制第一批音频文件:
import matplotlib.pyplot as plt plt.plot(x[0,:,:].numpy().T, color = 'gray') plt.xlabel('Sample') plt.ylabel('Value'))
输出将如下所示:
图 2.24:预处理音频数据批次的可视化表示
上述图表展示了预处理后的音频数据。你可以看到,值是非负的,最小值为0
,并且数据是以对数比例缩放的。
3. TensorFlow 开发
活动 3.01:使用 TensorBoard 可视化张量变换
解决方案:
-
导入 TensorFlow 库并设置种子:
import tensorflow as tf tf.random.set_seed(42)
-
设置日志目录并初始化一个文件写入对象以写入追踪记录:
logdir = 'logs/' writer = tf.summary.create_file_writer(logdir)
-
创建一个 TensorFlow 函数,用于将两个张量相乘,并使用
ones_like
函数将1
加到结果张量的所有元素中,ones_like
函数创建一个与矩阵乘法结果相同形状的张量。然后,对张量的每个值应用 sigmoid 函数:@tf.function def my_func(x, y): r1 = tf.matmul(x, y) r2 = r1 + tf.ones_like(r1) r3 = tf.keras.activations.sigmoid(r2) return r3
-
创建形状为
5x5x5
的两个张量:x = tf.random.uniform((5, 5, 5)) y = tf.random.uniform((5, 5, 5))
-
开启图形追踪:
tf.summary.trace_on(graph=True, profiler=True)
-
将该函数应用于两个张量,并将追踪记录导出到日志目录:
z = my_func(x, y) with writer.as_default(): tf.summary.trace_export(name="my_func_trace",\ step=0,\ profiler_outdir=logdir)
-
在命令行启动 TensorBoard 并在浏览器中查看图形:
tensorboard --logdir=./logs
你应该得到如下图像:
图 3.19:TensorBoard 中张量变换的可视化表示
结果表示为张量变换创建的图形。你可以看到,在图形的左下角,执行了矩阵乘法操作,操作的张量为命名为x
和y
的张量,位于名为MatMul
的节点上。在右下角,使用ones_like
函数创建了张量。输入节点表示张量的形状和常量值。创建两个张量后,它们被输入到一个表示加法函数的节点中,然后输出被输入到一个表示应用 sigmoid 函数的节点中。最终节点表示输出张量的创建。
在这个活动中,你创建了张量变换的函数,并在 TensorBoard 中展示了变换的可视化表示。
活动 3.02:从 TensorFlow Hub 使用预训练模型进行词嵌入
解决方案:
-
导入 TensorFlow 和 TensorFlow Hub 并打印库的版本:
import tensorflow as tf import tensorflow_hub as hub print('TF version: ', tf.__version__) print('HUB version: ', hub.__version__)
你应该获得 TensorFlow 和 TensorFlow Hub 的版本。
图 3.20:Google Colab 中 TensorFlow 和 TensorFlow Hub 的版本输出
-
设置通用句子编码器模块的句柄:
module_handle ="https://tfhub.dev/google"\ "/universal-sentence-encoder/4"
-
使用 TensorFlow Hub 的
KerasLayer
类创建 hub 层,传入以下参数:module_handle
、input_shape
和dtype
:hub_layer = hub.KerasLayer(module_handle, input_shape=[],\ dtype=tf.string)
-
创建一个包含待编码字符串的列表:
text = ['The TensorFlow Workshop']
-
应用
hub_layer
到文本上,将句子嵌入为一个向量:hub_layer(text)
你应该得到以下输出:
图 3.21:嵌入向量的输出
在这里,你可以看到文本已经被转换成了一个 512 维的嵌入向量。嵌入向量是一个一维张量,将文本映射为一个连续变量的向量,如前面的图所示。
在这个活动中,你使用了 Google Colab 环境从 TensorFlow Hub 下载了一个模型。你使用了一个通用句子编码器将句子嵌入为一个 512 维的向量。这个活动展示了通过在强大的远程服务器上只用几行简短的代码,你可以为任何应用访问最先进的机器学习模型。
4. 回归和分类模型
活动 4.01:使用 TensorFlow 创建多层 ANN
解决方案:
-
打开一个新的 Jupyter 笔记本来实现这个活动。
-
导入 TensorFlow 和 pandas 库:
import tensorflow as tf import pandas as pd
-
使用 pandas 的
read_csv
函数加载数据集:df = pd.read_csv('superconductivity.csv')
注意
确保你根据系统中的 CSV 文件位置更改路径(高亮部分)。如果你从与 CSV 文件存储在同一目录的 Jupyter 笔记本中运行代码,可以直接运行前面的代码而无需修改。
-
删除
date
列并删除任何包含空值的行:df.dropna(inplace=True)
-
创建目标和特征数据集:
target = df['critical_temp'] features = df.drop('critical_temp', axis=1)
-
重新缩放特征数据集:
from sklearn.preprocessing import StandardScaler scaler = StandardScaler() feature_array = scaler.fit_transform(features) features = pd.DataFrame(feature_array, columns=features.columns)
-
初始化一个
Sequential
类的 Keras 模型:model = tf.keras.Sequential()
-
使用模型的
add
方法为模型添加输入层,并将input_shape
设置为特征数据集中的列数。向模型中添加四个大小分别为64
、32
、16
和8
的隐藏层,第一个隐藏层使用 ReLU 激活函数,然后添加一个包含一个单元的输出层:model.add(tf.keras.layers.InputLayer\ (input_shape=features.shape[1],), \ name='Input_layer')) model.add(tf.keras.layers.Dense(64, activation='relu', \ name='Dense_layer_1')) model.add(tf.keras.layers.Dense(32, name='Dense_layer_2')) model.add(tf.keras.layers.Dense(16, name='Dense_layer_3')) model.add(tf.keras.layers.Dense(8, name='Dense_layer_4')) model.add(tf.keras.layers.Dense(1, name='Output_layer'))
-
使用 RMSprop 优化器编译模型,学习率为
0.001
,损失使用均方误差:model.compile(tf.optimizers.RMSprop(0.001), loss='mse')
-
创建一个 TensorBoard 回调:
tensorboard_callback = tf.keras.callbacks\ .TensorBoard(log_dir="./logs")
-
将模型拟合到训练数据上,训练
100
个 epoch,批次大小为32
,验证集比例为 20%:model.fit(x=features.to_numpy(), y=target.to_numpy(), \ epochs=100, callbacks=[tensorboard_callback], \ batch_size=32, validation_split=0.2)
你应该得到以下输出:
图 4.16:拟合过程的输出,显示了每个 epoch 的训练时间和损失
-
在训练数据上评估模型:
loss = model.evaluate(features.to_numpy(), target.to_numpy()) print('loss:', loss)
这将导致以下输出:
loss: 165.735601268987
-
通过在命令行中调用以下内容来可视化 TensorBoard 中的模型架构和模型拟合过程:
tensorboard –-logdir=logs/
模型架构应如下所示:
图 4.17:在 TensorBoard 中显示模型架构的可视化表示
-
在 TensorBoard 中可视化模型拟合过程。你应该得到如下输出:
图 4.18:训练和验证集在 TensorBoard 中显示的损失函数与 epoch 的关系
在模型拟合过程中,每个 epoch 后会计算训练集和验证集的损失,并在 TensorBoard 的SCALARS
标签页中显示。在 TensorBoard 中,你可以看到训练集的均方误差在每个 epoch 后持续减少,但在验证集上则趋于平稳。
在本次活动中,你进一步练习了使用 TensorFlow 构建模型,并在 TensorBoard 中查看其架构和训练过程。在这一部分,你学习了如何使用 TensorFlow 构建、训练和评估用于回归任务的 ANN(人工神经网络)。你使用了Dense
类的 Keras 层,这是一种简单的方式来创建完全连接的层,并在层的输出上包括激活函数。通过传递所需的单位数,Keras 可以简单地创建这些层。Keras 还负责配置权重和偏差的初始化,以及任何其他机器学习工作流程中常见的额外参数。
活动 4.02:使用 TensorFlow 创建多层分类 ANN
解决方案:
-
打开一个新的 Jupyter 笔记本来实现此活动。
-
导入 TensorFlow 和 pandas 库:
import tensorflow as tf import pandas as pd
-
使用 pandas 的
read_csv
函数加载数据集:df = pd.read_csv('superconductivity.csv')
注意
确保根据 CSV 文件在你系统上的位置更改路径(高亮部分)。如果你在与 CSV 文件存储在同一目录下运行 Jupyter 笔记本,则无需修改代码即可运行上述代码。
-
删除任何包含空值的行:
df.dropna(inplace=True)
-
当
critical_temp
列的值大于77.36
时,将目标值设置为true
,小于时设置为false
。特征数据集是数据集中剩余的列:target = df['critical_temp'].apply(lambda x: 1 if x>77.36 else 0) features = df.drop('critical_temp', axis=1)
-
重新缩放特征数据集:
from sklearn.preprocessing import StandardScaler scaler = StandardScaler() feature_array = scaler.fit_transform(features) features = pd.DataFrame(feature_array, columns=features.columns)
-
初始化一个
Sequential
类的 Keras 模型:model = tf.keras.Sequential()
-
使用模型的
add
方法添加一个输入层,并将input_shape
设置为特征数据集中的列数。向模型添加三个隐藏层,大小分别为32
、16
和8
,然后添加一个输出层,包含1
个单位并使用 sigmoid 激活函数:model.add(tf.keras.layers.InputLayer\ (input_shape=features.shape[1], \ name='Input_layer')) model.add(tf.keras.layers.Dense(32, name='Hidden_layer_1')) model.add(tf.keras.layers.Dense(16, name='Hidden_layer_2')) model.add(tf.keras.layers.Dense(8, name='Hidden_layer_3')) model.add(tf.keras.layers.Dense(1, name='Output_layer', \ activation='sigmoid'))
-
使用 RMSprop 优化器编译模型,学习率设置为
0.0001
,损失函数使用二元交叉熵,并计算准确度指标:model.compile(tf.optimizers.RMSprop(0.0001), \ loss= 'binary_crossentropy', metrics=['accuracy'])
-
创建一个 TensorBoard 回调:
tensorboard_callback = tf.keras.callbacks.TensorBoard\ (log_dir="./logs")
-
将模型拟合到训练数据上,进行
50
个周期,并且设置验证集比例为 20%:model.fit(x=features.to_numpy(), y=target.to_numpy(),\ epochs=50, callbacks=[tensorboard_callback],\ validation_split=0.2)
你应该会得到以下输出:
图 4.19:拟合过程的输出,显示了每个周期、每个样本的训练时间、损失和准确度,并在验证集上进行了评估
-
在训练数据上评估模型:
loss, accuracy = model.evaluate(features.to_numpy(), \ target.to_numpy()) print(f'loss: {loss}, accuracy: {accuracy}')
这将显示以下输出:
loss: 0.21984571637242145, accuracy: 0.8893383145332336
-
通过在命令行中调用以下命令,在 TensorBoard 中可视化模型架构和模型拟合过程:
tensorboard –-logdir=logs/
你应该会在浏览器中看到类似以下的屏幕:
图 4.20:TensorBoard 中模型架构的可视化表示
损失函数可以如下所示进行可视化:
图 4.21:TensorBoard 中显示训练和验证集准确度与损失随周期变化的可视化表示
在模型拟合过程中,每个周期后计算训练集和验证集的准确度和损失,并在 TensorBoard 的 SCALARS
标签页中显示。从 TensorBoard 中你可以看到,损失指标(二元交叉熵)在训练集上每个周期后都会持续下降,但在验证集上则趋于平稳。
在这个活动中,你通过构建一个多层人工神经网络(ANN),练习了如何在 TensorFlow 中构建分类模型,以判断某种材料是否会在氮气的沸点上方或下方表现出超导性。此外,你还使用 TensorBoard 查看模型的架构,并在训练过程中监控关键指标,包括损失和准确率。
5. 分类模型
活动 5.01:使用 TensorFlow 构建字符识别模型
解决方案:
-
打开一个新的 Jupyter 笔记本。
-
导入 pandas 库并使用
pd
作为别名:import pandas as pd
-
创建一个名为
file_url
的变量,其中包含数据集的 URL:file_url = 'https://raw.githubusercontent.com/PacktWorkshops'\ '/The-TensorFlow-Workshop/master/Chapter05'\ '/dataset/letter-recognition.data'
-
使用
read_csv()
方法将数据集加载到一个名为data
的DataFrame()
函数中,提供 CSV 文件的 URL,并设置header=None
,因为数据集没有提供列名。使用head()
方法打印前五行。data = pd.read_csv(file_url, header=None) data.head()
预期的输出将如下所示:
图 5.42:数据的前五行
你可以看到数据集包含了
17
列,并且所有列都是数字型的。列0
是目标
变量,每个值对应字母表中的一个字母。 -
使用
pop()
方法提取目标变量(列0
),并将其保存在名为target
的变量中:target = data.pop(0)
-
将
data
划分为训练集,保留前 15,000 个观测值,并将其保存在名为X_train
的变量中。对target
进行相同的划分,并将前 15,000 个案例保存在名为y_train
的变量中:X_train = data[:15000] y_train = target[:15000]
-
将
data
划分为测试集,保留最后 5,000 个观测值,并将其保存在名为X_test
的变量中。对target
进行相同的划分,并将最后 5,000 个案例保存在名为y_test
的变量中:X_test = data[15000:] y_test = target[15000:]
-
导入 TensorFlow 库,并使用
tf
作为别名:import tensorflow as tf
-
使用
tf.random.set_seed()
将种子设置为8
,以获得可重复的结果:tf.random.set_seed(8)
-
使用
tf.keras.Sequential()
实例化一个顺序模型,并将其保存在名为model
的变量中:model = tf.keras.Sequential()
-
从
tensorflow.keras.layers
导入Dense()
类:from tensorflow.keras.layers import Dense
-
使用
Dense()
创建一个包含512
个单元的全连接层,指定 ReLu 为激活函数,输入形状为(16,)
,这对应数据集中的特征数量。将其保存在名为fc1
的变量中:fc1 = Dense(512, input_shape=(16,), activation='relu')
-
使用
Dense()
创建一个包含512
个单元的全连接层,并指定 ReLu 为激活函数。将其保存在名为fc2
的变量中:fc2 = Dense(512, activation='relu')
-
使用
Dense()
创建一个包含128
个单元的全连接层,并指定 ReLu 为激活函数。将其保存在名为fc3
的变量中:fc3 = Dense(128, activation='relu')
-
使用
Dense()
创建一个包含128
个单元的全连接层,并指定 ReLu 为激活函数。将其保存在名为fc4
的变量中:fc4 = Dense(128, activation='relu')
-
使用
Dense()
创建一个包含26
个单元的全连接层,并指定 softmax 为激活函数。将其保存在名为fc5
的变量中:fc5 = Dense(26, activation='softmax')
-
使用
add()
方法顺序地将所有五个全连接层添加到模型中。model.add(fc1) model.add(fc2) model.add(fc3) model.add(fc4) model.add(fc5)
-
使用
summary()
方法打印模型的总结。model.summary()
预期的输出如下:
图 5.43:模型架构总结
上述输出显示了模型中有五个层(符合预期),并且告诉你每个层的参数数量。
-
从
tf.keras.losses
实例化SparseCategoricalCrossentropy()
,并将其保存在名为loss
的变量中:loss = tf.keras.losses.SparseCategoricalCrossentropy()
-
从
tf.keras.optimizers
实例化Adam()
,学习率为0.001
,并将其保存在名为optimizer
的变量中:optimizer = tf.keras.optimizers.Adam(0.001)
-
使用
compile()
方法编译模型,指定刚刚创建的优化器和损失函数参数,并使用准确度作为报告的指标:model.compile(optimizer=optimizer, loss=loss, \ metrics=['accuracy'])
-
使用
fit()
方法开始模型的训练过程,在训练集上训练五个 epoch:model.fit(X_train, y_train, epochs=5)
预期的输出如下:
图 5.44:训练过程的日志
上述输出显示了模型训练过程中每个 epoch 的日志。请注意,处理一个 epoch 大约需要 2 秒,且准确率从
0.6229
(第一个 epoch)提高到0.9011
(第五个 epoch)。 -
使用
evaluate()
方法评估模型在测试集上的表现。model.evaluate(X_test, y_test)
预期的输出如下:
图 5.45:模型在测试集上的表现
-
使用
predict()
方法预测测试集上每个类别的概率,并将结果保存在名为preds_proba
的变量中:preds_proba = model.predict(X_test)
-
使用
argmax()
方法并设置axis=1
,将类别概率转换为单一的预测值:preds = preds_proba.argmax(axis=1)
-
从
tensorflow.math
导入confusion_matrix
:from tensorflow.math import confusion_matrix
-
打印测试集上的混淆矩阵:
confusion_matrix(y_test, preds)
预期的输出如下所示:
图 5.46:测试集的混淆矩阵
上面的输出显示,模型大多数时候正确地预测了 26 个字母(大多数值位于对角线上)。它在训练集和测试集上的准确率约为 0.89。这个活动结束了多类别分类部分。在接下来的部分,你将了解另一种分类方法——多标签分类。
活动 5.02:使用 TensorFlow 构建电影类型标记模型
解决方案:
-
打开一个新的 Jupyter notebook。
-
导入 pandas 库并将其别名设为
pd
:import pandas as pd
-
创建一个名为
feature_url
的变量,包含数据集的 URL:feature_url = 'https://raw.githubusercontent.com'\ '/PacktWorkshops'/The-TensorFlow-Workshop'\ '/master/Chapter05'/dataset/IMDB-F-features.csv'
-
使用
read_csv()
方法将数据集加载到名为feature
的 DataFrame 中,并提供 CSV 文件的 URL。使用head()
方法打印前五行:feature = pd.read_csv(feature_url) feature.head()
预期的输出如下所示:
图 5.47:特征数据的前五行
-
创建一个名为
target_url
的变量,包含数据集的 URL:target_url = 'https://raw.githubusercontent.com'\ '/PacktWorkshops/The-TensorFlow-Workshop'\ '/master/Chapter05'/dataset/IMDB-F-targets.csv'
-
使用
read_csv()
方法将数据集加载到名为target
的 DataFrame 中,并提供 CSV 文件的 URL。使用head()
方法打印前五行:target = pd.read_csv(target_url) target.head()
预期的输出如下所示:
图 5.48:目标数据的前五行
-
将数据拆分为训练集,保留前 15,000 条观察数据并将其保存在名为
X_train
的变量中。对target
进行相同的拆分,将前 15,000 条数据保存在名为y_train
的变量中:X_train = feature[:15000] y_train = target[:15000]
-
将数据拆分为测试集,保留最后的 5,000 条观察数据并将其保存在名为
X_test
的变量中。对target
进行相同的拆分,将最后的 5,000 条数据保存在名为y_test
的变量中:X_test = feature[15000:] y_test = target[15000:]
-
导入 TensorFlow 库并将其别名设为
tf
:import tensorflow as tf
-
使用
tf.random.set_seed()
将tensorflow
的种子设置为8
,这将帮助获得可重复的结果:tf.random.set_seed(8)
-
使用
tf.keras.Sequential()
实例化一个顺序模型,并将其保存在名为model
的变量中:model = tf.keras.Sequential()
-
从
tensorflow.keras.layers
导入Dense()
类:from tensorflow.keras.layers import Dense
-
使用
Dense()
创建一个包含512
个单元的全连接层,并指定 ReLu 作为激活函数,输入形状为(1001,)
,对应数据集的特征数量。将其保存在名为fc1
的变量中:fc1 = Dense(512, input_shape=(1001,), activation='relu')
-
使用
Dense()
创建一个包含512
个单元的全连接层,并指定 ReLu 作为激活函数。将其保存在一个名为fc2
的变量中:fc2 = Dense(512, activation='relu')
-
使用
Dense()
创建一个包含128
个单元的全连接层,并指定 ReLu 作为激活函数。将其保存在一个名为fc3
的变量中:fc3 = Dense(128, activation='relu')
-
使用
Dense()
创建一个包含128
个单元的全连接层,并指定 ReLu 作为激活函数。将其保存在一个名为fc4
的变量中:fc4 = Dense(128, activation='relu')
-
使用
Dense()
创建一个包含28
个单元的全连接层,并指定 sigmoid 作为激活函数。将其保存在一个名为fc5
的变量中:fc5 = Dense(28, activation='sigmoid')
-
使用
add()
方法依次将所有五个全连接层添加到模型中。model.add(fc1) model.add(fc2) model.add(fc3) model.add(fc4) model.add(fc5)
-
使用
summary()
方法打印模型的总结。model.summary()
预期的输出将如下所示:
图 5.49:模型架构的总结
-
从
tf.keras.losses
实例化BinaryCrossentropy()
并将其保存在一个名为loss
的变量中:loss = tf.keras.losses.BinaryCrossentropy()
-
从
tf.keras.optimizers
实例化Adam()
,设置学习率为0.001
,并将其保存在一个名为optimizer
的变量中:optimizer = tf.keras.optimizers.Adam(0.001)
-
使用
compile()
方法编译模型,并指定刚刚创建的优化器和损失函数参数,同时设置准确率为报告的指标:model.compile(optimizer=optimizer, loss=loss, \ metrics=['accuracy'])
-
使用
fit()
方法开始在训练集上进行模型训练,训练 20 个 epoch:model.fit(X_train, y_train, epochs=20)
预期的输出将如下所示:
图 5.50:训练过程的日志
你可以观察到,模型在训练了 20 个 epoch 后,准确率逐步提高,在第九个 epoch 时达到了
61.67%
。 -
使用
evaluate()
方法评估模型在测试集上的表现:model.evaluate(X_test, y_test)
预期的输出将如下所示:
图 5.51:模型在测试集上的表现
上面的输出显示,模型在测试集上达到了0.13
的准确率,这个值非常低,而在训练集上的准确率为0.62
。这个模型在学习如何正确预测不同类型电影的模式上存在困难。你可以尝试不同的架构,使用不同数量的隐藏层和单元。你还可以尝试不同的学习率和优化器。由于训练集和测试集上的得分差异很大,说明模型发生了过拟合,只学到了与训练集相关的模式。
6. 正则化和超参数调整
活动 6.01:使用 L1 和 L2 正则化器预测收入
解决方案:
-
打开一个新的 Jupyter 笔记本。
-
导入 pandas 库并使用
pd
作为别名:import pandas as pd
-
创建一个名为
usecols
的列表,包含列名AAGE
、ADTIND
、ADTOCC
、SEOTR
、WKSWORK
和PTOTVAL
:usecols = ['AAGE','ADTIND','ADTOCC','SEOTR','WKSWORK', 'PTOTVAL']
-
创建一个名为
train_url
的变量,包含训练集的 URL:train_url = 'https://raw.githubusercontent.com/PacktWorkshops'\ '/The-TensorFlow-Workshop/master/Chapter06'\ '/dataset/census-income-train.csv'
-
使用
read_csv()
方法将训练数据集加载到train_data
的 DataFrame 中。为usecols
参数提供 CSV 文件的 URL 和usecols
列表。使用head()
方法打印前五行:train_data = pd.read_csv(train_url, usecols=usecols) train_data.head()
预期输出如下:
图 6.23:训练集的前五行
-
使用
pop()
方法提取目标变量(PTOTVAL
),并将其保存在名为train_target
的变量中:train_target = train_data.pop('PTOTVAL')
-
创建一个名为
test_url
的变量,包含测试集的 URL:test_url = 'https://github.com/PacktWorkshops'\ '/The-TensorFlow-Workshop/blob/master/Chapter06'\ '/dataset/census-income-test.csv?raw=true'
-
使用
read_csv()
方法将测试数据集加载到X_test
的 DataFrame 中。为usecols
参数提供 CSV 文件的 URL 和usecols
列表。使用head()
方法打印前五行:test_data = pd.read_csv(test_url, usecols=usecols) test_data.head()
预期输出如下:
图 6.24:测试集的前五行
-
使用
pop()
方法提取目标变量(PTOTVAL
),并将其保存在名为test_target
的变量中:test_target = test_data.pop('PTOTVAL')
-
导入 TensorFlow 库并使用
tf
作为别名。然后,从tensorflow.keras.layers
导入Dense
类:import tensorflow as tf from tensorflow.keras.layers import Dense
-
使用
tf.random.set_seed()
将种子设置为8
,以获取可复现的结果:tf.random.set_seed(8)
-
使用
tf.keras.Sequential()
实例化一个顺序模型,并将其保存在名为model
的变量中:model = tf.keras.Sequential()
-
从
tensorflow.keras.layers
导入Dense
类:from tensorflow.keras.layers import Dense
-
使用
Dense()
创建一个包含1048
个单元的全连接层,指定 ReLu 作为激活函数,并将输入形状设置为(5,)
,这对应数据集中的特征数量。将其保存在名为fc1
的变量中:fc1 = Dense(1048, input_shape=(5,), activation='relu')
-
使用
Dense()
创建三个全连接层,分别包含512
、128
和64
个单元,并指定 ReLu 作为激活函数。将它们分别保存在名为fc2
、fc3
和fc4
的三个变量中:fc2 = Dense(512, activation='relu') fc3 = Dense(128, activation='relu') fc4 = Dense(64, activation='relu')
-
使用
Dense()
创建一个包含三个单元的全连接层(对应类别数量),并指定 softmax 作为激活函数。将其保存在名为fc5
的变量中:fc5 = Dense(3, activation='softmax')
-
使用
Dense()
创建一个包含单个单元的全连接层。将其保存在名为fc5
的变量中:fc5 = Dense(1)
-
按顺序使用
add()
方法将五个全连接层添加到模型中:model.add(fc1) model.add(fc2) model.add(fc3) model.add(fc4) model.add(fc5)
-
打印模型的总结:
model.summary()
你将得到以下输出:
图 6.25:模型架构的总结
-
从
tf.keras.optimizers
实例化Adam()
,学习率为0.05
,并将其保存到名为optimizer
的变量中:optimizer = tf.keras.optimizers.Adam(0.05)
-
编译模型,指定优化器,并将
mse
设置为损失函数和显示的度量:model.compile(optimizer=optimizer, loss='mse', metrics=['mse'])
-
使用
fit()
方法开始模型训练过程,训练五个周期,并将数据拆分为 20%的验证集:model.fit(train_data, train_target, epochs=5, \ validation_split=0.2)
预期输出如下:
图 6.26:训练过程的日志
上述输出表明模型存在过拟合问题。在训练集上的 MSE 分数为
1005740
,而在验证集上的 MSE 分数为1070237
。现在,训练另一个模型,使用 L1 和 L2 正则化。 -
创建五个全连接层,类似于之前的模型,并为
kernel_regularizer
参数指定 L1 和 L2 正则化器。正则化器因子使用0.001
。将其保存为五个变量,分别命名为reg_fc1
、reg_fc2
、reg_fc3
、reg_fc4
和reg_fc5
:reg_fc1 = Dense(1048, input_shape=(5,), activation='relu', \ kernel_regularizer=tf.keras.regularizers\ .l1_l2(l1=0.001, l2=0.001)) reg_fc2 = Dense(512, activation='relu', \ kernel_regularizer=tf.keras.regularizers\ .l1_l2(l1=0.001, l2=0.001)) reg_fc3 = Dense(128, activation='relu', \ kernel_regularizer=tf.keras.regularizers\ .l1_l2(l1=0.001, l2=0.001)) reg_fc4 = Dense(64, activation='relu', \ kernel_regularizer=tf.keras.regularizers\ .l1_l2(l1=0.001, l2=0.001)) reg_fc5 = Dense(1, activation='relu')
-
使用
tf.keras.Sequential()
实例化一个顺序模型,将其存储在名为model2
的变量中,并使用add()
方法将五个全连接层按顺序添加到模型中:model2 = tf.keras.Sequential() model2.add(reg_fc1) model2.add(reg_fc2) model2.add(reg_fc3) model2.add(reg_fc4) model2.add(reg_fc5)
-
打印模型概览:
model2.summary()
输出结果如下:
图 6.27:模型架构概览
-
使用
compile()
方法编译模型,指定优化器,并将mse
作为损失函数和要显示的评估指标:optimizer = tf.keras.optimizers.Adam(0.1) model2.compile(optimizer=optimizer, loss='mse', metrics=['mse'])
-
使用
fit()
方法启动模型训练过程,进行五轮训练,并将数据分为验证集,其中 20%的数据用于验证:model2.fit(train_data, train_target, epochs=5, \ validation_split=0.2)
输出结果如下:
图 6.28:训练过程日志
添加 L1 和 L2 正则化后,模型在训练集(4028182
)和测试集(3970020
)上的准确度分数相似。因此,模型没有严重的过拟合问题。
活动 6.02:使用 Keras Tuner 中的贝叶斯优化预测收入
解决方案:
-
打开一个新的 Jupyter 笔记本。
-
导入 pandas 库,并使用
pd
作为别名:import pandas as pd
-
创建一个名为
usecols
的列表,包含以下列名:AAGE
、ADTIND
、ADTOCC
、SEOTR
、WKSWORK
和PTOTVAL
:usecols = ['AAGE','ADTIND','ADTOCC','SEOTR','WKSWORK', 'PTOTVAL']
-
创建一个名为
train_url
的变量,包含训练集的 URL:train_url = 'https://raw.githubusercontent.com/PacktWorkshops'\ '/The-TensorFlow-Workshop/master/Chapter06'\ '/dataset/census-income-train.csv'
-
使用
read_csv()
方法将训练数据集加载到名为train_data
的 DataFrame 中,并将 CSV 文件的 URL 和usecols
列表提供给usecols
参数。使用head()
方法打印前五行:train_data = pd.read_csv(train_url, usecols=usecols) train_data.head()
-
你将获得如下输出:
图 6.29:训练集的前五行
-
使用
pop()
方法提取目标变量(PTOTVAL
),并将其保存到名为train_target
的变量中:train_target = train_data.pop('PTOTVAL')
-
创建一个名为
test_url
的变量,包含测试集的 URL:test_url = 'https://github.com/PacktWorkshops'\ '/The-TensorFlow-Workshop/blob/master/Chapter06'\ '/dataset/census-income-test.csv?raw=true'
-
使用
read_csv()
方法将测试数据集加载到名为X_test
的 DataFrame 中,并将 CSV 文件的 URL 和usecols
列表提供给usecols
参数。使用head()
方法打印前五行:test_data = pd.read_csv(test_url, usecols=usecols) test_data.head()
输出结果如下:
图 6.30:测试集的前五行
-
使用
pop()
方法提取目标变量(PTOTVAL
),并将其保存到名为test_target
的变量中:test_target = test_data.pop('PTOTVAL')
-
导入 TensorFlow 库并使用
tf
作为别名。然后,从tensorflow.keras.layers
导入Dense
类:import tensorflow as tf from tensorflow.keras.layers import Dense
-
使用
tf.random.set_seed()
设置种子为8
,以获得可复现的结果:tf.random.set_seed(8)
-
定义一个名为
model_builder
的函数,用于创建一个与活动 6.01、使用 L1 和 L2 正则化预测收入相同架构的顺序模型。 但这次,提供一个超参数hp.Choice
来选择学习率,hp.Int
来设置输入层的单元数,hp.Choice
来设置 L2 正则化:def model_builder(hp): model = tf.keras.Sequential() hp_l2 = hp.Choice('l2', values = [0.1, 0.01, 0.001]) hp_units = hp.Int('units', min_value=128, max_value=512, step=64) reg_fc1 = Dense(hp_units, input_shape=(5,), activation='relu', \ kernel_regularizer=tf.keras.regularizers\ .l2(l=hp_l2)) reg_fc2 = Dense(512, activation='relu', \ kernel_regularizer=tf.keras.regularizers\ .l2(l=hp_l2)) reg_fc3 = Dense(128, activation='relu', \ kernel_regularizer=tf.keras.regularizers\ .l2(l=hp_l2)) reg_fc4 = Dense(128, activation='relu', \ kernel_regularizer=tf.keras.regularizers\ .l2(l=hp_l2)) reg_fc5 = Dense(1) model.add(reg_fc1) model.add(reg_fc2) model.add(reg_fc3) model.add(reg_fc4) model.add(reg_fc5) hp_learning_rate = hp.Choice('learning_rate', \ values = [0.01, 0.001]) optimizer = tf.keras.optimizers.Adam(hp_learning_rate) model.compile(optimizer=optimizer, loss='mse', metrics=['mse']) return model
-
安装
keras-tuner
包,然后导入它并赋予kt
别名:!pip install keras-tuner import kerastuner as kt
-
实例化一个
BayesianOptimization
调优器,并将val_mse
赋值给objective
,将10
赋值给max_trials
:tuner = kt.BayesianOptimization(model_builder, \ objective = 'val_mse', \ max_trials = 10)
-
使用
search()
方法在训练集和测试集上启动超参数搜索:tuner.search(train_data, train_target, \ validation_data=(test_data, test_target))
-
使用
get_best_hyperparameters()
提取最佳超参数组合(索引为0
),并将其保存在名为best_hps
的变量中:best_hps = tuner.get_best_hyperparameters()[0]
-
提取输入层单元数的最佳值,将其保存在名为
best_units
的变量中,并打印其值:best_units = best_hps.get('units') best_units
你将得到以下输出:
128
Hyperband 找到的输入层单元数的最佳值是
128
。 -
提取学习率的最佳值,将其保存在名为
best_lr
的变量中,并打印其值:best_lr = best_hps.get('learning_rate') best_lr
Hyperband 找到的学习率超参数的最佳值是
0.001
:0.001
-
提取 L2 正则化的最佳值,将其保存在名为
best_l2
的变量中,并打印其值:best_l2 = best_hps.get('l2') best_l2
-
Hyperband 找到的学习率超参数的最佳值是
0.001
:0.001
-
使用
fit()
方法开始模型训练过程,训练五个周期,并将测试集作为validation_data
:model = tuner.hypermodel.build(best_hps) model.fit(X_train, y_train, epochs=5, \ validation_data=(X_test, y_test))
你应该得到类似以下的输出:
图 6.31:训练过程的日志
使用贝叶斯优化,你找到了输入层单元数(128
)、学习率(0.001
)和 L2 正则化(0.001
)的最佳超参数组合。使用这些超参数,最终模型在训练集上的 MSE 得分为994174
,在测试集上的 MSE 得分为989335
。这相比于活动 6.01、使用 L1 和 L2 正则化预测收入,有了很大的改进,且模型没有出现过拟合。
7. 卷积神经网络
活动 7.01:构建具有更多 ANN 层的 CNN
解决方案:
有多种方法可以得出此活动的解决方案。以下步骤描述了其中一种方法,类似于本章之前在CIFAR-10
数据集上使用的步骤:
-
启动一个新的 Jupyter 笔记本。
-
导入 TensorFlow 库:
import tensorflow as tf
-
导入所需的附加库:
import numpy as np import matplotlib.pyplot as plt import tensorflow as tf import tensorflow_datasets as tfds from tensorflow.keras.layers import Input, Conv2D, Dense, Flatten, \ Dropout, Activation, Rescaling from tensorflow.keras.models import Model from sklearn.metrics import confusion_matrix, ConfusionMatrixDisplay
-
直接从
tensorflow_datasets
加载CIFAR-100
数据集并查看其属性:(c100_train_dataset, c100_test_dataset), \ dataset_info = tfds.load('cifar100',\ split = ['train', 'test'],\ data_dir = 'content/Cifar100/',\ shuffle_files = True,\ as_supervised = True,\ with_info = True) assert isinstance(c100_train_dataset, tf.data.Dataset) image_shape = dataset_info.features["image"].shape print(f'Shape of Images in the Dataset: \t{image_shape}') num_classes = dataset_info.features["label"].num_classes print(f'Number of Classes in the Dataset: \t{num_classes}') names_of_classes = dataset_info.features["label"].names print(f'Names of Classes in the Dataset: \t{names_of_classes}\n') print(f'Total examples in Train Dataset: \ \t{len(c100_train_dataset)}') print(f'Total examples in Test Dataset: \ \t{len(c100_test_dataset)}')
这将给出以下输出:
图 7.42:CIFAR-100 数据集的属性
-
使用重缩放层对图像进行重缩放。然后,通过重缩放、缓存、打乱、批处理和预取图像,构建测试和训练数据管道:
normalization_layer = Rescaling(1./255) c100_train_dataset = c100_train_dataset.map\ (lambda x, y: (normalization_layer(x), y), \ num_parallel_calls = \ tf.data.experimental.AUTOTUNE) c100_train_dataset = c100_train_dataset.cache() c100_train_dataset = c100_train_dataset.shuffle\ (len(c100_train_dataset)) c100_train_dataset = c100_train_dataset.batch(32) c100_train_dataset = c100_train_dataset.prefetch(tf.data.experimental.AUTOTUNE) c100_test_dataset = c100_test_dataset.map\ (lambda x, y: (normalization_layer(x), y), \ num_parallel_calls = \ tf.data.experimental.AUTOTUNE) c100_test_dataset = c100_test_dataset.cache() c100_test_dataset = c100_test_dataset.batch(128) c100_test_dataset = \ c100_test_dataset.prefetch(tf.data.experimental.AUTOTUNE)
-
使用函数式 API 构建模型:
input_layer = Input(shape=image_shape) x = Conv2D(filters = 32, kernel_size = \ (3, 3), strides=2)(input_layer) x = Activation('relu')(x) x = Conv2D(filters = 64, kernel_size = (3, 3), strides=2)(x) x = Activation('relu')(x) x = Conv2D(filters = 128, kernel_size = (3, 3), strides=2)(x) x = Activation('relu')(x) x = Flatten()(x) x = Dropout(rate = 0.5)(x) x = Dense(units = 1024)(x) x = Activation('relu')(x) x = Dropout(rate = 0.2)(x) x = Dense(units = num_classes)(x) output = Activation('softmax')(x) c100_classification_model = Model(input_layer, output)
-
编译并拟合模型:
c100_classification_model.compile(\ optimizer='adam', \ loss='sparse_categorical_crossentropy', \ metrics = ['accuracy'], loss_weights = None, \ weighted_metrics = None, run_eagerly = None, \ steps_per_execution = None ) history = c100_classification_model.fit\ (c100_train_dataset, \ validation_data=c100_test_dataset, \ epochs=15)
输出将如下图所示:
图 7.43:模型拟合
-
使用以下代码绘制损失和准确率:
def plot_trend_by_epoch(tr_values, val_values, title): epoch_number = range(len(tr_values)) plt.plot(epoch_number, tr_values, 'r') plt.plot(epoch_number, val_values, 'b') plt.title(title) plt.xlabel('epochs') plt.legend(['Training '+title, 'Validation '+title]) plt.figure() hist_dict = history.history tr_loss, val_loss = hist_dict['loss'], \ hist_dict['val_loss'] plot_trend_by_epoch(tr_loss, val_loss, "Loss") tr_accuracy, val_accuracy = hist_dict['accuracy'], \ hist_dict['val_accuracy'] plot_trend_by_epoch(tr_accuracy, val_accuracy, "Accuracy")
损失图将如下所示:
图 7.44:损失图
准确率图将如下所示:
图 7.45:准确率图
-
显示一个错误分类的示例。使用以下代码:
test_labels = [] test_images = [] for image, label in tfds.as_numpy(c100_test_dataset.unbatch()): test_images.append(image) test_labels.append(label) test_labels = np.array(test_labels) predictions = c100_classification_model.predict\ (c100_test_dataset).argmax(axis=1) incorrect_predictions = np.where(predictions != test_labels)[0] index = np.random.choice(incorrect_predictions) plt.imshow(test_images[index]) print(f'True label: {names_of_classes[test_labels[index]]}') print(f'Predicted label: {names_of_classes[predictions[index]]}')
这将产生以下输出:
图 7.46:错误分类示例
输出显示了一个错误分类的例子:预测为狮子,真实标签为老鼠。在这个任务中,类别数为 100,这使得它比练习 7.05、构建 CNN(仅有 10 个类别)要困难得多。不过,你可以看到,在经过 15 个 epoch 后,准确率持续提高,损失不断减少,甚至在验证数据集上也是如此。如果让模型继续训练更多的 epoch,你可以期待更好的模型性能。
8. 预训练网络
活动 8.01:通过微调进行水果分类
解决方案:
-
打开一个新的 Jupyter notebook。
-
导入 TensorFlow 库为
tf
:import tensorflow as tf
-
创建一个名为
file_url
的变量,包含数据集的链接:file_url = 'https://github.com/PacktWorkshops/'\ 'The-TensorFlow-Workshop/blob/master'\ '/Chapter08/dataset/fruits360.zip'
-
使用
tf.keras.get_file
下载数据集,参数为'fruits360.zip'
、origin=file_url
和extract=True
,并将结果保存到名为zip_dir
的变量中:zip_dir = tf.keras.utils.get_file('fruits360.zip', \ origin=file_url, extract=True)
-
导入
pathlib
库:import pathlib
-
创建一个名为
path
的变量,使用pathlib.Path(zip_dir).parent
存储fruits360_filtered
目录的完整路径:path = pathlib.Path(zip_dir).parent / 'fruits360_filtered'
-
创建两个变量
train_dir
和validation_dir
,分别存储训练(Training
)和验证(Test
)文件夹的完整路径:train_dir = path / 'Training' validation_dir = path / 'Test'
-
创建两个变量
total_train
和total_val
,获取训练集和验证集中的图像数量:total_train = 11398 total_val = 4752
-
从
tensorflow.keras.preprocessing
导入ImageDataGenerator
:from tensorflow.keras.preprocessing.image import ImageDataGenerator
-
创建一个名为
train_img_gen
的ImageDataGenerator
模型,进行数据增强:train_img_gen = ImageDataGenerator(rescale=1./255, \ rotation_range=40, \ width_shift_range=0.1, \ height_shift_range=0.1, \ shear_range=0.2, \ zoom_range=0.2, \ horizontal_flip=True, \ fill_mode='nearest'))
-
创建一个名为
val_img_gen
的ImageDataGenerator
模型,按255
进行重缩放:val_img_gen = ImageDataGenerator(rescale=1./255)
-
创建四个变量
batch_size
、img_height
、img_width
和channel
,并分别赋值为32
、224
、224
和3
:Batch_size = 32 img_height = 224 img_width = 224 channel = 3
-
使用
flow_from_directory()
创建一个名为train_data_gen
的数据生成器,并指定批处理大小、训练文件夹和目标大小:train_data_gen = train_image_generator.flow_from_directory\ (batch_size=batch_size, directory=train_dir, \ target_size=(img_height, img_width))
-
使用
flow_from_directory()
创建一个名为val_data_gen
的数据生成器,并指定批处理大小、验证文件夹和目标大小:val_data_gen = validation_image_generator.flow_from_directory\ (batch_size=batch_size, directory=validation_dir,\ target_size=(img_height, img_width))
-
导入
numpy
为np
,tensorflow
为tf
,以及从tensorflow.keras
导入layers
:import numpy as np import tensorflow as tf from tensorflow.keras import layers
-
设置
8
为numpy
和tensorflow
的随机种子:np.random.seed(8) tf.random.set_seed(8)
-
从
tensorflow.keras.applications
导入NASNetMobile
:from tensorflow.keras.applications import NASNetMobile
-
实例化一个
NASNetMobile
模型并保存到名为base_model
的变量中:base_model = NASNetMobile(input_shape=(img_height, img_width, \ channel), \ weights='imagenet', include_top=False)
-
打印
NASNetMobile
模型的摘要:base_model.summary()
预期输出如下:
图 8.8:模型总结
-
使用
tf.keras.Sequential()
创建一个新模型,将基础模型添加到Flatten
和Dense
层中,并将此模型保存到名为model
的变量中:model = tf.keras.Sequential([base_model,\ layers.Flatten(),\ layers.Dense(500, \ activation='relu'), \ layers.Dense(120, \ activation='softmax')])
-
实例化一个
tf.keras.optimizers.Adam()
类,设置学习率为0.001
,并将其保存到名为optimizer
的变量中:optimizer = tf.keras.optimizers.Adam(0.001)
-
使用
compile()
方法编译神经网络,设置categorical_crossentropy
为损失函数,使用学习率为0.001
的 Adam 优化器,并将accuracy
作为显示的指标:model.compile(loss='categorical_crossentropy', \ optimizer=optimizer, metrics=['accuracy'])
-
使用
fit()
方法训练神经网络。该模型可能需要几分钟才能完成训练:model.fit(train_data_gen, steps_per_epoch=len(features_train) // batch_size,\ epochs=5,\ validation_data=val_data_gen,\ validation_steps=len(features_test) // batch_size\ )
预期输出如下:
图 8.9:训练模型的 Epochs
在本活动中,您使用了微调技术,将预先在 ImageNet 上训练的NASNetMobile
模型自定义为一个包含水果图像的数据集。您冻结了模型的前 700 层,并仅在五个 Epoch 上训练了最后几层。您在训练集上的准确率为0.9549
,在测试集上的准确率为0.8264
。
活动 8.02:使用 TensorFlow Hub 进行迁移学习
解决方案:
-
打开一个新的 Jupyter 笔记本。
-
导入 TensorFlow 库:
import tensorflow as tf
-
创建一个名为
file_url
的变量,包含指向数据集的链接:file_url = 'https://storage.googleapis.com'\ '/mledu-datasets/cats_and_dogs_filtered.zip'
-
使用
tf.keras.get_file
下载数据集,设置参数cats_and_dogs.zip
、origin=file_url
和extract=True
,并将结果保存到名为zip_dir
的变量中:zip_dir = tf.keras.utils.get_file('cats_and_dogs.zip', \ origin=file_url, extract=True)
-
导入
pathlib
库:import pathlib
-
创建一个名为
path
的变量,使用pathlib.Path(zip_dir).parent
获取cats_and_dogs_filtered
目录的完整路径:path = pathlib.Path(zip_dir).parent / 'cats_and_dogs_filtered'
-
创建两个变量,分别命名为
train_dir
和validation_dir
,并赋值为train
和validation
文件夹的完整路径:train_dir = path / 'train' validation_dir = path / 'validation'
-
创建两个变量,分别命名为
total_train
和total_val
,它们将获取训练集和验证集的图像数量(分别为2000
和1000
):total_train = 2000 total_val = 1000
-
从
tensorflow.keras.preprocessing
导入ImageDataGenerator
:from tensorflow.keras.preprocessing.image import ImageDataGenerator
-
实例化两个
ImageDataGenerator
类,并分别命名为train_image_generator
和validation_image_generator
。它们将通过除以255
来重新缩放图像:train_image_generator = ImageDataGenerator(rescale=1./255) validation_image_generator = ImageDataGenerator(rescale=1./255)
-
创建三个变量,分别命名为
batch_size
、img_height
和img_width
,并分别赋值为32
、224
和224
:batch_size = 32 img_height = 224 img_width = 224
-
创建一个名为
train_data_gen
的数据生成器,使用flow_from_directory()
并指定批次大小、训练文件夹路径、目标大小和类的模式:train_data_gen = train_image_generator.flow_from_directory\ (batch_size=batch_size, directory=train_dir, \ shuffle=True, target_size=(img_height, \ img_width), \ class_mode='binary')
-
创建一个名为
val_data_gen
的数据生成器,使用flow_from_directory()
并指定批次大小、验证文件夹路径、目标大小和类的模式:val_data_gen = validation_image_generator.flow_from_directory\ (batch_size=batch_size, \ directory=validation_dir, \ target_size=(img_height, img_width), \ class_mode='binary')
-
导入
numpy
作为np
,tensorflow
作为tf
,以及从tensorflow.keras
导入layers
:import numpy as np import tensorflow as tf from tensorflow.keras import layers
-
设置
8
(这是完全任意的)作为numpy
和 tensorflow 的seed
:np.random.seed(8) tf.random.set_seed(8)
-
导入
tensorflow_hub
,如下所示:import tensorflow_hub as hub
-
从 TensorFlow Hub 加载 EfficientNet B0 特征向量:
MODULE_HANDLE = 'https://tfhub.dev/google/efficientnet/b0'\ '/feature-vector/1' module = hub.load(MODULE_HANDLE)
-
创建一个新的模型,结合 EfficientNet B0 模块和两个新的顶层,单位分别为
500
和1
,激活函数为 ReLu 和 sigmoid:model = tf.keras.Sequential\ ([hub.KerasLayer(MODULE_HANDLE,\ input_shape=(224, 224, 3)), layers.Dense(500, activation='relu'), layers.Dense(1, activation='sigmoid')])
-
编译该模型,提供
binary_crossentropy
作为loss
函数,使用学习率为0.001
的 Adam 优化器,并设置accuracy
作为显示的指标:model.compile(loss='binary_crossentropy', \ optimizer=tf.keras.optimizers.Adam(0.001), \ metrics=['accuracy'])
-
拟合模型并提供训练和验证数据生成器。运行五个 epoch:
model.fit(train_data_gen, \ steps_per_epoch = total_train // batch_size, \ epochs=5, \ validation_data=val_data_gen, \ validation_steps=total_val // batch_size)
预期的输出如下:
图 8.10:模型训练输出
在本次活动中,您通过使用来自 TensorFlow Hub 的迁移学习,取得了非常高的准确率(训练集和测试集的准确率分别为1
和0.99
)。您使用了EfficientNet B0特征向量,结合了两个自定义的最终层,最终模型几乎完美地预测了猫狗图像。
9. 循环神经网络
活动 9.01:构建一个具有多个 LSTM 层的 RNN 来预测电力消耗
解决方案:
执行以下步骤以完成此活动。
-
打开一个新的 Jupyter 或 Colab 笔记本。
-
导入所需的库。使用
numpy
、pandas
、datetime
和MinMaxScaler
将数据集缩放到零到一之间:import numpy as np import pandas as pd import datetime from sklearn.preprocessing import MinMaxScaler
-
使用
read_csv()
函数读取 CSV 文件并将数据集存储在一个 pandas DataFrame 中,命名为data
:data = pd.read_csv("household_power_consumption.csv")
-
使用以下代码通过组合
Date
和Time
列来创建一个新的列Datetime
:data['Date'] = pd.to_datetime(data['Date'], format="%d/%m/%Y") data['Datetime'] = data['Date'].dt.strftime('%Y-%m-%d') + ' ' \ + data['Time'] data['Datetime'] = pd.to_datetime(data['Datetime'])
-
使用
Datetime
列按升序排序 DataFrame:data = data.sort_values(['Datetime'])
-
创建一个名为
num_cols
的列表,其中包含具有数值值的列——Global_active_power
、Global_reactive_power
、Voltage
、Global_intensity
、Sub_metering_1
、Sub_metering_2
和Sub_metering_3
:num_cols = ['Global_active_power', 'Global_reactive_power', \ 'Voltage', 'Global_intensity', 'Sub_metering_1', \ 'Sub_metering_2', 'Sub_metering_3']
-
将
num_cols
中列出的所有列转换为数值数据类型:for col in num_cols: data[col] = pd.to_numeric(data[col], errors='coerce')
-
在数据上调用
head()
函数,查看 DataFrame 的前五行:data.head()
你应该会得到以下输出:
图 9.40:DataFrame 的前五行
-
在数据上调用
tail()
函数,查看 DataFrame 的最后五行:data.tail()
你应该会得到以下输出:
图 9.41:DataFrame 的最后五行
-
迭代
num_cols
中的列,并使用以下代码用平均值填充缺失值:for col in num_cols: data[col].fillna(data[col].mean(), inplace=True)
-
使用
drop()
删除 DataFrame 中的Date
、Time
、Global_reactive_power
和Datetime
列,并将结果保存在名为df
的变量中:df = data.drop(['Date', 'Time', 'Global_reactive_power', 'Datetime'], \ axis = 1)
-
使用
MinMaxScaler
从 DataFrame 中创建一个缩放器,将数据转换到 0 到 1 之间。使用fit_transform
将模型拟合到数据,并根据拟合的模型转换数据:scaler = MinMaxScaler() scaled_data = scaler.fit_transform(df) scaled_data
你应该得到以下输出:
图 9.42:标准化训练数据
前面的截图显示数据已经标准化。现在数值位于 0 和 1 之间。
-
创建两个空列表,命名为
X
和y
,它们将用于存储特征和目标变量:X = [] y = []
-
创建一个训练数据集,包含前 60 分钟的用电量,以便预测下一分钟的值。使用
for
循环在 60 个时间步长中创建数据:for i in range(60, scaled_data.shape[0]): X.append(scaled_data [i-60:i]) y.append(scaled_data [i, 0])
-
将
X
和y
转换为 NumPy 数组,为训练模型做准备:X, y = np.array(X), np.array(y)
-
将数据集分为训练集和测试集,分别使用索引
217440
之前和之后的数据:X_train = X[:217440] y_train = y[:217440] X_test = X[217440:] y_test = y[217440:]
-
构建 LSTM 时,你需要一些额外的库。使用
Sequential
初始化神经网络,使用Dense
添加密集层,使用LSTM
添加 LSTM 层,使用Dropout
帮助防止过拟合:from tensorflow.keras import Sequential from tensorflow.keras.layers import Dense, LSTM, Dropout
-
初始化你的神经网络。添加 LSTM 层,其中包含
20
、40
和80
个单元。使用 ReLU 激活函数,并将return_sequences
设置为True
。input_shape
应为训练集的维度(特征数和天数)。最后,添加 Dropout 层:regressor = Sequential() regressor.add(LSTM(units= 20, activation = 'relu',\ return_sequences = True,\ input_shape = (X_train.shape[1], X_train.shape[2]))) regressor.add(Dropout(0.5)) regressor.add(LSTM(units= 40, \ activation = 'relu', \ return_sequences = True)) regressor.add(Dropout(0.5)) regressor.add(LSTM(units= 80, \ activation = 'relu')) regressor.add(Dropout(0.5)) regressor.add(Dense(units = 1))
-
使用
summary()
函数打印模型的架构:regressor.summary()
前面的命令提供了有关模型、层和参数的宝贵信息:
图 9.43:模型摘要
-
使用
compile()
方法配置你的模型进行训练。选择 Adam 作为优化器,使用均方误差来衡量损失函数:regressor.compile(optimizer='adam', loss = 'mean_squared_error')
-
拟合你的模型,并设置运行两次迭代。将批处理大小设置为
32
:regressor.fit(X_train, y_train, epochs=2, batch_size=32)
-
使用
regressor.predict(X_test)
将测试集的预测值保存在名为y_pred
的变量中:y_pred = regressor.predict(X_test)
-
查看测试集数据中最后一小时的真实家庭用电量和你的预测值:
plt.figure(figsize=(14,5)) plt.plot(y_test[-60:], color = 'black', \ label = "Real Power Consumption") plt.plot(y_pred[-60:], color = 'gray', \ label = 'Predicted Power Consumption') plt.title('Power Consumption Prediction') plt.xlabel('time') plt.ylabel('Power Consumption') plt.legend() plt.show()
你应该得到以下输出:
图 9.44:家庭用电量预测可视化
如图 9.44所示,你的结果相当不错。你可以观察到,大多数情况下,预测值接近实际值。
活动 9.02:构建 RNN 预测推文情感
解决方案:
执行以下步骤完成此活动:
-
打开一个新的 Jupyter 或 Colab 笔记本。
-
导入所需的库。使用
numpy
进行计算,使用pandas
处理数据集:import numpy as np import pandas as pd
-
使用
read_csv
方法读取 CSV 文件,并将数据集存储在 pandas 的 DataFramedata
中:data = pd.read_csv("https://raw.githubusercontent.com"\ "/PacktWorkshops/The-TensorFlow-Workshop"\ "/master/Chapter09/Datasets/tweets.csv")
-
调用数据的
head()
方法,查看 DataFrame 的前五行:data.head()
你应该得到以下输出:
图 9.45:数据框的前五行
在前面的截图中,你可以看到存储在
airline_sentiment
列中的不同情感。 -
在数据上调用
tail()
以查看数据框的最后五行:data.tail()
你应该得到以下输出:
图 9.46:数据框的最后五行
-
创建一个新的数据框
df
,该数据框仅包含text
作为特征,airline_sentiment
作为目标变量:df = data[['text','airline_sentiment']]
-
使用以下命令,通过移除
airline_sentiment
等于neutral
的所有行,来子集化df
:df = df[df['airline_sentiment'] != 'neutral']
-
将
airline_sentiment
列转换为数值类型,将negative
替换为0
,positive
替换为1
。将结果保存到变量y
中:y = df['airline_sentiment'].map({'negative':0, 'positive':1}).values
-
创建一个变量
X
,它将包含df
中text
列的数据:X = df['text']
-
从
tensorflow.keras.preprocessing.text
导入Tokenizer
,从tensorflow.keras.preprocessing.sequence
导入pad_sequences
:from tensorflow.keras.preprocessing.text import Tokenizer from tensorflow.keras.preprocessing.sequence \ import pad_sequences
-
实例化
Tokenizer()
类,并将num_words
设置为10000
。这将仅保留前 10,000 个最频繁的单词,并将其保存到一个名为tokenizer
的变量中:tokenizer = Tokenizer(num_words=10000)
-
在数据
X
上拟合tokenizer
:tokenizer.fit_on_texts(X)
-
打印
tokenizer
中的词汇:tokenizer.word_index
你应该得到如下输出:
图 9.47:由 tokenizer 定义的词汇
从输出词汇中,你可以看到单词
to
被分配了索引1
,the
被分配了索引2
,依此类推。你可以用它将原始文本映射成其数值版本。 -
创建
vocab_size
变量,包含 tokenizer 词汇的长度,并加上一个额外的字符,用于表示未知单词:vocab_size = len(tokenizer.word_index) + 1
-
使用
tokenizer
的词汇,将X
中的原始文本转换为编码版本。将结果保存在名为encoded_tweets
的变量中:encoded_tweets = tokenizer.texts_to_sequences(X)
-
对
encoded_tweets
进行填充,填充字符为0
,最大长度为 280 个字符。将结果保存在名为padded_tweets
的变量中:padded_tweets = pad_sequences(encoded_tweets, maxlen=280, padding='post')
-
打印
padded_tweets
的形状:padded_tweets.shape
你应该得到如下结果:
(11541, 280)
-
如你所见,准备好的推文现在都具有相同的长度,即 280 个字符。
-
随机排列
padded_tweets
的索引。将结果保存到indices
变量中:indices = np.random.permutation(padded_tweets.shape[0])
-
创建两个变量,
train_idx
和test_idx
,分别包含前 10,000 个索引和剩余的索引:train_idx = indices[:10000] test_idx = indices[10000:]
-
使用
padded_tweets
和y
,将数据拆分为训练集和测试集,并保存为四个不同的变量,分别命名为X_train
、X_test
、y_train
和y_test
:X_train = padded_tweets[train_idx,] X_test = padded_tweets[test_idx,] y_train = y[train_idx,] y_test = y[test_idx,]
-
你将需要一些额外的库来构建你的模型。使用以下代码导入
Sequential
、Dense
、LSTM
、Dropout
和Embedding
:from tensorflow.keras import Sequential from tensorflow.keras.layers import Dense, LSTM, Dropout, Embedding
-
初始化你的神经网络。通过提供词汇表长度、嵌入层长度和输入长度来添加嵌入层。添加两个 LSTM 层,单元数分别为
50
和100
。使用 ReLU 激活函数,并将return_sequences
设置为True
。然后,为每个 LSTM 层添加一个丢弃层,丢弃率为 20%。最后,添加一个全连接层,并将 sigmoid 作为最终激活函数:model = Sequential() model.add(Embedding(vocab_size, embedding_vector_length, input_length=280)) model.add(LSTM(units= 50, activation = 'relu', return_sequences = True)) model.add(Dropout(0.2)) model.add(LSTM(100, activation = 'relu')) model.add(Dropout(0.2)) model.add(Dense(1, activation='sigmoid'))
-
使用
summary()
函数检查模型的摘要:model.summary()
你应该会得到以下输出:
图 9.48:模型摘要
-
使用
compile()
方法配置你的模型进行训练。选择adam
作为优化器,binary_crossentropy
作为损失函数,并将accuracy
作为显示的指标:model.compile(optimizer='adam', loss='binary_crossentropy', metrics=['accuracy'])
-
拟合你的模型并设置为运行两个 epoch。将批处理大小设置为
32
:model.fit(X_train, y_train, epochs=2, batch_size=32)
你应该会得到以下输出:
图 9.49:训练模型
正如你在图 9.49中看到的,模型在训练集上的准确率为0.7978
,并且几乎没有数据预处理。你可以尝试通过去除停用词或极其频繁的词汇,如the
和a
,来改善这一点,因为这些词汇并不真正帮助评估推文的情感,看看是否能够在测试集上达到相同的表现。你可以推断出该模型能够正确预测训练数据中约 80%的推文情感。
10. 自定义 TensorFlow 组件
活动 10.01:构建具有自定义层和自定义损失函数的模型
解决方案:
要开始,打开一个新的 Colab 或 Jupyter Notebook。如果你使用的是 Google Colab,你需要先将数据集下载到 Google Drive 中:
-
打开一个新的 Jupyter Notebook 或 Google Colab Notebook。
-
如果你使用的是 Google Colab,你可以使用以下代码在本地上传数据集。否则,跳到步骤 4。点击
选择文件
来导航到 CSV 文件并点击打开
。将文件保存为uploaded
。然后,进入你保存数据集的文件夹:from google.colab import files uploaded = files.upload()
-
解压当前文件夹中的数据集:
!unzip \*.zip
-
创建一个变量
directory
,它包含数据集的路径:directory = "/content/gdrive/My Drive/Datasets/pneumonia-or-healthy/"
-
导入所有必要的库:
import numpy as np import pandas as pd import pathlib import os import matplotlib.pyplot as plt from keras.models import Sequential from keras import optimizers from tensorflow.keras.preprocessing.image import ImageDataGenerator import tensorflow as tf from tensorflow.keras.layers import Input, Conv2D, ReLU, \ BatchNormalization,Add, AveragePooling2D, Flatten, Dense from tensorflow.keras.models import Model
-
创建一个变量
path
,该变量包含使用pathlib.Path
访问数据的完整路径:path = pathlib.Path(directory)
-
创建两个变量,分别命名为
train_dir
和validation_dir
,它们保存训练和验证文件夹的完整路径:train_dir = path / 'training_set' validation_dir = path / 'test_set'
-
创建四个变量,分别命名为
train_table_dir
、train_glass_dir
、validation_table_dir
和validation_glass_dir
,它们分别保存训练和验证集中的眼镜和桌子文件夹的完整路径:train_table_dir = train_dir / 'table' train_glass_dir = train_dir /'glass' validation_table_dir = validation_dir / 'table' validation_glass_dir = validation_dir / 'glass'
-
创建四个变量,这些变量将包含用于训练和验证集的眼镜和桌子的图像数量:
num_train_table = len([f for f in os.listdir(train_table_dir)if \ os.path.isfile(os.path.join\ (train_table_dir, f))]) num_train_glass = len([f for f in os.listdir(train_glass_dir)if \ os.path.isfile(os.path.join\ (train_glass_dir, f))]) num_validation_table = len([f for f in os.listdir\ (validation_table_dir)if os.path.isfile(os.path.join(validation_table_dir, f))]) num_validation_glass = len([f for f in os.listdir\ (validation_glass_dir)if \ os.path.isfile\ (os.path.join\ (validation_glass_dir, f))])
-
显示一个条形图,展示眼镜和桌子的图像总数:
plt.bar(['table', 'glass'], \ [num_train_table + num_validation_table, \ num_train_glass + num_validation_glass], \ align='center', \ alpha=0.5) plt.show()
你应该会得到以下输出:
图 10.12:眼镜和桌子图像数量
上述图表显示数据集是平衡的。眼镜和桌子的图像几乎一样多,每种大约有 3500 张图像。
-
创建两个变量,分别命名为
total_train
和total_val
,它们将获取训练集和验证集的图像数量:total_train = len(os.listdir(train_table_dir)) + \ len(os.listdir(validation_table_dir)) total_val = len(os.listdir(train_glass_dir)) + \ len(os.listdir(validation_glass_dir))
-
导入
ImageDataGenerator
类:from tensorflow.keras.preprocessing.image \ import ImageDataGenerator
-
实例化两个
ImageDataGenerator
类,train_image_generator
和validation_image_generator
,它们会通过除以 255 来重新缩放图像:train_image_generator = ImageDataGenerator(rescale=1./255) validation_image_generator = ImageDataGenerator(rescale=1./255)
-
创建三个变量,分别命名为
batch_size
、img_height
和img_width
,它们的值分别为32
、100
和100
:batch_size = 32 img_height = 100 img_width = 100
-
使用
flow_from_directory()
方法创建一个数据生成器,命名为train_data_gen
,并指定批量大小、训练文件夹路径、shuffle
参数的值、目标大小和类别模式:train_data_gen = train_image_generator.flow_from_directory\ (batch_size=batch_size, directory=train_dir, \ shuffle=True, \ target_size=(img_height, img_width), \ class_mode='binary')
-
使用
flow_from_directory()
方法创建一个数据生成器,命名为val_data_gen
,并指定批量大小、验证文件夹路径、目标大小和类别模式:val_data_gen = validation_image_generator.flow_from_directory\ (batch_size=batch_size, directory=validation_dir,\ target_size=(img_height, img_width), \ class_mode='binary')
-
创建你的自定义损失函数。使用
def
定义并选择一个名称作为自定义损失函数,本例中为custom_loss_function
。然后,添加两个参数,y_true
和y_pred
。接下来,创建一个变量squared_difference
来存储y_true
减去y_pred
的平方。最后,使用tf.reduce_mean
返回由squared_difference
计算出的损失值:def custom_loss_function(y_true, y_pred): squared_difference = tf.square(float(y_true) - float(y_pred)) return tf.reduce_mean(squared_difference, axis=-1)
-
创建一个函数,将输入作为张量并为其添加 ReLU 和批归一化:
def relu_batchnorm_layer(input): return BatchNormalization()(ReLU()(input))
-
创建一个函数来构建残差块。你需要将一个张量作为输入并将其传递给两个 Conv2D 层。接下来,将输入加到输出上,然后添加 ReLU 和批归一化。
由于你在
residual_block
中使用了Add
层进行跳跃连接,因此需要确保其输入始终具有相同的形状。downsample
参数用于指定第一个 Conv2D 层的步幅。若True
,则指定strides=2
;若False
,则指定strides=1
。当strides=1
时,输出(int_output
)与输入相同大小。而当strides=2
时,int_output
的维度会减半。为了解决这个问题,在跳跃连接中添加一个kernel_size=1
的 Conv2D 层:def residual_block(input, downsample: bool, filters: int, \ kernel_size: int = 3): int_output = Conv2D(filters=filters, kernel_size=kernel_size, strides= (1 if not downsample else 2), padding="same")(input) int_output = relu_batchnorm_layer(int_output) int_output = Conv2D(filters=filters, kernel_size=kernel_size, padding="same")(int_output) if downsample: int_output2 = Conv2D(filters=filters, kernel_size=1, strides=2, padding="same")(input) output = Add()([int_output2, int_output]) else: output = Add()([input, int_output]) output = relu_batchnorm_layer(output) return output
-
现在,使用
keras.layers.Input()
层来定义模型的输入层。这里,形状是 100 像素乘 100 像素,并且有三种颜色(RGB)。然后,使用自定义架构创建模型。最后,使用model = Model(inputs, outputs)
引用输入和输出张量:inputs = Input(shape=(100, 100, 3)) num_filters = 32 t = BatchNormalization()(inputs) t = Conv2D(kernel_size=3, strides=1, filters=32, padding="same")(t) t = relu_batchnorm_layer(t) num_blocks_list = [1, 3, 5, 6, 1] for i in range(len(num_blocks_list)): num_blocks = num_blocks_list[i] for j in range(num_blocks): t = residual_block(t, downsample=(j==0 and i!=0), filters=num_filters) num_filters *= 2 t = AveragePooling2D(4)(t) t = Flatten()(t) outputs = Dense(1, activation='sigmoid')(t) model = Model(inputs, outputs)
-
获取你的模型摘要:
model.summary()
运行前述命令时,将显示模型的摘要:
![图 10.13:模型摘要]
](https://github.com/OpenDocCN/freelearn-dl-pt3-zh/raw/master/docs/tf-ws/img/B16341_10_13.jpg)
图 10.13:模型摘要
-
使用自定义损失函数、Adam 优化器和准确率作为度量标准,编译该模型并显示:
model.compile( optimizer='adam', loss=custom_loss_function, metrics=['accuracy'] )
-
拟合模型,并提供训练和验证数据生成器、epoch 数量、每个 epoch 的步骤数以及验证步骤数:
history = model.fit( Train_data_gen, steps_per_epoch=total_train // batch_size, epochs=5, validation_data=val_data_gen, validation_steps=total_val // batch_size )
你应该获得以下输出:
图 10.14:训练进度截图
上述截图显示了在训练模型过程中,TensorFlow 展示的信息。你可以看到每个 epoch 在训练集和验证集上获得的准确率。在第五个 epoch,模型在训练集上的准确率为
85.9%
,在验证集上的准确率为88.5%
。 -
绘制你的训练和验证准确率:
plt.plot(history.history['accuracy']) plt.plot(history.history['val_accuracy']) plt.title('Training Accuracy vs Validation Accuracy') plt.ylabel('Accuracy') plt.xlabel('Epoch') plt.legend(['Train', 'Validation'], loc='upper left') plt.show()
你应该获得以下输出:
图 10.15:训练和验证准确率
上述图表显示了每个 epoch 在训练集和验证集上的准确度分数。
-
绘制你的训练和验证损失:
plt.plot(history.history['loss']) plt.plot(history.history['val_loss']) plt.title('Training Loss vs Validation Loss') plt.ylabel('Loss') plt.xlabel('Epoch') plt.legend(['Train', 'Validation'], loc='upper left') plt.show()
你应该获得以下输出:
图 10.16:训练和验证损失
上述图表显示了每个 epoch 的训练集和验证集的损失分数。
通过本活动,你已成功构建了一个自定义的 MSE 损失函数和自定义的残差块层,并在玻璃与桌面数据集上训练了该自定义深度学习模型。你现在知道如何超越 TensorFlow 提供的默认类,构建你自己的自定义深度学习模型。
11. 生成模型
活动 11.01:使用 GAN 生成图像
解决方案:
执行以下步骤以完成此活动:
-
加载 Google Colab 和 Google Drive:
try: from google.colab import drive drive.mount('/content/drive', force_remount=True) COLAB = True print("Note: using Google CoLab") %tensorflow_version 2.x except: print("Note: not using Google CoLab") COLAB = False
你的输出应该类似于此:
Mounted at /content/drive Note: using Google CoLab
-
导入你将使用的库:
import tensorflow as tf from tensorflow.keras.models import Sequential, Model, load_model from tensorflow.keras.layers import InputLayer, Reshape, Dropout, Dense from tensorflow.keras.layers import Flatten, BatchNormalization from tensorflow.keras.layers import UpSampling2D, Conv2D from tensorflow.keras.layers import Activation, ZeroPadding2D from tensorflow.keras.optimizers import Adam from tensorflow.keras.layers import LeakyReLU import zipfile import matplotlib.pyplot as plt import numpy as np from PIL import Image from tqdm import tqdm import os import time from skimage.io import imread
-
创建一个函数来格式化时间字符串,以跟踪你的时间使用情况:
def time_string(sec_elapsed): hour = int(sec_elapsed / (60 * 60)) minute = int((sec_elapsed % (60 * 60)) / 60) second = sec_elapsed % 60 return "{}:{:>02}:{:>05.2f}".format(hour, minute, second)
-
将生成分辨率设置为
3
。同时,将img_rows
和img_cols
设置为5
,img_margin
设置为16
,这样你的预览图像将是一个5x5
数组(25 张图片),并有 16 像素的边距。将seed_vector
设置为200
,data_path
设置为存储图像数据集的位置,epochs
设置为500
。最后,打印参数:gen_res = 3 gen_square = 32 * gen_res img_chan = 3 img_rows = 5 img_cols = 5 img_margin = 16 seed_vector = 200 data_path = 'banana-or-orange/training_set/' epochs = 500 num_batch = 32 num_buffer = 60000 print(f"Will generate a resolution of {gen_res}.") print(f"Will generate {gen_square}px square images.") print(f"Will generate {img_chan} image channels.") print(f"Will generate {img_rows} preview rows.") print(f"Will generate {img_cols} preview columns.") print(f"Our preview margin equals {img_margin}.") print(f"Our data path is: {data_path}.") print(f"Our number of epochs are: {epochs}.") print(f"Will generate a batch size of {num_batch}.") print(f"Will generate a buffer size of {num_buffer}.")
你的输出应该类似于此:
图 11.30:显示参数的输出
-
如果之前执行时已经存在一个 NumPy 预处理文件,则将其加载到内存中;否则,预处理数据并保存图像二进制文件:
training_binary_path = os.path.join(data_path, f'training_data_{gen_square}_{gen_square}.npy') print(f"Looking for file: {training_binary_path}") if not os.path.isfile(training_binary_path): start = time.time() print("Loading training images…") train_data = [] images_path = os.path.join(data_path,'banana') for filename in tqdm(os.listdir(images_path)): path = os.path.join(images_path,filename) images = Image.open(path).resize((gen_square, gen_square),\ Image.ANTIALIAS) train_data.append(np.asarray(images)) train_data = np.reshape(train_data,(-1,gen_square, gen_square,img_chan)) train_data = train_data.astype(np.float32) train_data = train_data / 127–5 - 1. print("Saving training image binary...") np.save(training_binary_path,train_data) elapsed = time.time()-start print (f'Image preprocess time: {time_string(elapsed)}') else: print("Loading training data...") train_data = np.load(training_binary_path)
-
对数据进行批处理并打乱顺序。使用
tensorflow.data.Dataset
对象库,利用其函数打乱数据集并创建批次:train_dataset = tf.data.Dataset.from_tensor_slices(train_data) \ .shuffle(num_buffer).batch(num_batch)
-
为 DCGAN 构建生成器:
def create_dc_generator(seed_size, channels): model = Sequential() model.add(Dense(4*4*256,activation="relu",input_dim=seed_size)) model.add(Reshape((4,4,256))) model.add(UpSampling2D()) model.add(Conv2D(256,kernel_size=3,padding="same")) model.add(BatchNormalization(momentum=0.8)) model.add(Activation("relu")) model.add(UpSampling2D()) model.add(Conv2D(256,kernel_size=3,padding="same")) model.add(BatchNormalization(momentum=0.8)) model.add(Activation("relu")) # Output resolution, additional upsampling model.add(UpSampling2D()) model.add(Conv2D(128,kernel_size=3,padding="same")) model.add(BatchNormalization(momentum=0.8)) model.add(Activation("relu")) if gen_res>1: model.add(UpSampling2D(size=(gen_res,gen_res))) model.add(Conv2D(128,kernel_size=3,padding="same")) model.add(BatchNormalization(momentum=0.8)) model.add(Activation("relu")) # Final CNN layer model.add(Conv2D(channels,kernel_size=3,padding="same")) model.add(Activation("tanh")) return model
-
为 DCGAN 构建判别器:
def create_dc_discriminator(image_shape): model = Sequential() model.add(Conv2D(32, kernel_size=3, strides=2, \ input_shape=image_shape, padding="same")) model.add(LeakyReLU(alpha=0.2)) model.add(Dropout(0.25)) model.add(Conv2D(64, kernel_size=3, strides=2, padding="same")) model.add(ZeroPadding2D(padding=((0,1),(0,1)))) model.add(BatchNormalization(momentum=0.8)) model.add(LeakyReLU(alpha=0.2)) model.add(Dropout(0.25)) model.add(Conv2D(128, kernel_size=3, strides=2, padding="same")) model.add(BatchNormalization(momentum=0.8)) model.add(LeakyReLU(alpha=0.2)) model.add(Dropout(0.25)) model.add(Conv2D(256, kernel_size=3, strides=1, padding="same")) model.add(BatchNormalization(momentum=0.8)) model.add(LeakyReLU(alpha=0.2)) model.add(Dropout(0.25)) model.add(Conv2D(512, kernel_size=3, strides=1, padding="same")) model.add(BatchNormalization(momentum=0.8)) model.add(LeakyReLU(alpha=0.2)) model.add(Dropout(0.25)) model.add(Flatten()) model.add(Dense(1, activation='sigmoid')) return model
-
为原始 GAN 构建生成器:
def create_generator(seed_size, channels): model = Sequential() model.add(Dense(96*96*3,activation="tanh",input_dim=seed_size)) model.add(Reshape((96,96,3))) return model
-
为原始 GAN 构建判别器:
def create_discriminator(img_size): model = Sequential() model.add(InputLayer(input_shape=img_size)) model.add(Dense(1024, activation="tanh")) model.add(Flatten()) model.add(Dense(1, activation='sigmoid')) return model
-
创建一个函数来生成并保存图像,用于查看模型训练过程中的进展:
def save_images(generator, cnt, noise, prefix=None): img_array = np.full(( img_margin + (img_rows * (gen_square+img_margin)), img_margin + (img_cols * (gen_square+img_margin)), 3), 255, dtype=np.uint8) gen_imgs = generator.predict(noise) gen_imgs = 0.5 * gen_imgs + 0.5 img_count = 0 for row in range(img_rows): for col in range(img_cols): r = row * (gen_square+16) + img_margin c = col * (gen_square+16) + img_margin img_array[r:r+gen_square,c:c+gen_square] \ = gen_imgs[img_count] * 255 img_count += 1 output_path = os.path.join(data_path,'output') if not os.path.exists(output_path): os.makedirs(output_path) filename = os.path.join(output_path,f"train{prefix}-{cnt}.png") im = Image.fromarray(img_array) im.save(filename)
-
初始化 DCGAN 的生成器并查看输出:
dc_generator = create_dc_generator(seed_vector, img_chan) noise = tf.random.normal([1, seed_vector]) gen_img = dc_generator(noise, training=False) plt.imshow(gen_img[0, :, :, 0])
你的输出应该类似于此:
图 11.31:显示 DCGAN 生成器噪声的输出
-
初始化原始 GAN 的生成器并查看输出:
generator = create_generator(seed_vector, img_chan) gen_van_img = generator(noise, training=False) plt.imshow(gen_van_img[0, :, :, 0])
你应该得到以下输出:
图 11.32:显示原始 GAN 生成器噪声的输出
-
打印 DCGAN 判别器在种子图像上评估的决策:
img_shape = (gen_square,gen_square,img_chan) discriminator = create_discriminator(img_shape) decision = discriminator(gen_img) print (decision)
你的输出应该如下所示:
tf.Tensor([[0.4994658]], shape=(1,1), dtype=float32)
-
打印原始 GAN 在种子图像上评估的决策:
discriminator = create_discriminator(img_shape) decision = discriminator(gen_img) print(decision)
你的输出应该如下所示:
tf.Tensor([[0.5055983]], shape=(1,1), dtype=float32)
-
创建你的损失函数。由于判别器和生成器网络的输出不同,你可以为它们分别定义两个独立的损失函数。此外,它们需要在独立的网络传递中分别进行训练。两个 GAN 都可以为它们的判别器和生成器使用相同的损失函数。你可以使用
tf.keras.losses.BinaryCrossentropy
来计算cross_entropy
。这会计算真实标签和预测标签之间的损失。然后,使用tf.ones
和tf.zeros
从real_output
和fake_output
定义discrim_loss
函数,以计算total_loss
:cross_entropy = tf.keras.losses.BinaryCrossentropy() def discrim_loss(real_output, fake_output): real_loss = cross_entropy(tf.ones_like(real_output), real_output) fake_loss = cross_entropy(tf.zeros_like(fake_output), fake_output) total_loss = real_loss + fake_loss return total_loss def gen_loss(fake_output): return cross_entropy(tf.ones_like(fake_output), fake_output)
-
创建两个 Adam 优化器,一个用于生成器,一个用于判别器。每个优化器使用相同的学习率和动量:
gen_optimizer = tf.keras.optimizers.Adam(1.5e-4,0.5) disc_optimizer = tf.keras.optimizers.Adam(1.5e-4,0.5)
在这里,你有你的单个训练步骤。非常重要的一点是,你每次只能修改一个网络的权重。通过
tf.GradientTape()
,你可以同时训练判别器和生成器,但它们彼此独立。这就是 TensorFlow 自动求导的工作方式。它会计算导数。你会看到它创建了两个“录音带”——gen_tape
和disc_tape
。可以把它们看作是每个计算过程的记录。 -
为判别器创建
real_output
和fake_output
。将此用作生成器的损失(g_loss
)。然后,计算判别器损失(d_loss
)以及生成器和判别器的梯度,通过gradients_of_generator
和gradients_of_discriminator
并应用它们。将这些步骤封装在一个函数中,传入生成器、判别器和图像,并返回生成器损失(g_loss
)和判别器损失(d_loss
):@tf.function def train_step(generator, discriminator, images): seed = tf.random.normal([num_batch, seed_vector]) with tf.GradientTape() as gen_tape, \ tf.GradientTape() as disc_tape: gen_imgs = generator(seed, training=True) real_output = discriminator(images, training=True) fake_output = discriminator(gen_imgs, training=True) g_loss = gen_loss(fake_output) d_loss = discrim_loss(real_output, fake_output) gradients_of_generator = gen_tape.gradient(\ g_loss, generator.trainable_variables) gradients_of_discriminator = disc_tape.gradient(\ d_loss, discriminator.trainable_variables) gen_optimizer.apply_gradients(zip( gradients_of_generator, generator.trainable_variables)) disc_optimizer.apply_gradients(zip( gradients_of_discriminator, discriminator.trainable_variables)) return g_loss,d_loss
-
创建一个固定种子列表,
fixed_seeds
的数量等于要显示的图像数量,这样你就可以跟踪相同的图像。这使你能够看到每个种子如何随时间变化,通过for epoch in range
来跟踪你的时间。现在,通过for image_batch in dataset
循环处理每个批次。继续跟踪生成器和判别器的损失,使用generator_loss
和discriminator_loss
。现在,你可以在训练过程中查看所有这些信息:def train(generator, discriminator, dataset, epochs, prefix=None): fixed_seed = np.random.normal(0, 1, (img_rows * img_cols, seed_vector)) start = time.time() for epoch in range(epochs): epoch_start = time.time() g_loss_list = [] d_loss_list = [] for image_batch in dataset: t = train_step(image_batch) g_loss_list.append(t[0]) d_loss_list.append(t[1]) generator_loss = sum(g_loss_list) / len(g_loss_list) discriminator_loss = sum(d_loss_list) / len(d_loss_list) epoch_elapsed = time.time() - epoch_start if (epoch + 1) % 100 == 0: print (f'Epoch {epoch+1}, gen loss={generator_loss}, disc loss={discriminator_loss},'\ f' {time_string(epoch_elapsed)}') save_images(epoch,fixed_seed) elapsed = time.time()-start print (f'Training time: {time_string(elapsed)}')
-
在你的训练数据集上训练 DCGAN 模型:
train(dc_generator, dc_discriminator, train_dataset, \ epochs, prefix='-dc-gan')
你的输出应该如下所示:
图 11.33:DCGAN 模型训练过程中的输出
输出显示了每个周期生成器和判别器的损失。
-
在你的训练数据集上训练香草模型:
train(generator, discriminator, train_dataset, epochs, \ prefix='-vanilla')
你的输出应该类似于以下内容:
图 11.34:香草 GAN 模型训练过程中的输出
-
查看 DCGAN 模型在第 100 个周期后的生成图像:
a = imread('banana-or-orange/training_set/output'\ '/train-dc-gan-99.png') plt.imshow(a)
你将看到类似下面的输出:
图 11.35:DCGAN 模型在 100 个周期后的输出图像
-
查看 DCGAN 模型在第 500 个周期后的生成图像:
a = imread('/ banana-or-orange/training_set'\ '/output/train-dc-gan-499.png') plt.imshow(a)
你将看到类似下面的输出:
图 11.36:DCGAN 模型在 500 个周期后的输出图像
-
查看香草 GAN 模型在第 100 个周期后的生成图像:
a = imread('banana-or-orange/training_set'\ '/output/train-vanilla-99.png') plt.imshow(a)
你将看到类似下面的输出:
图 11.37:香草 GAN 模型在 100 个周期后的输出图像
-
查看香草 GAN 模型在第 500 个周期后的生成图像:
a = imread('/ banana-or-orange/training_set'\ '/output/train-vanilla-499.png') plt.imshow(a)
你将看到类似下面的输出:
图 11.38:香草 GAN 模型在 500 个周期后的输出图像
输出显示了香草 GAN 在 500 个周期后生成的图像。你可以看到它们与 DCGAN 生成的图像有很大不同。
你刚刚完成了本书的最后一项活动。你用 DCGAN 创建了自己的图像,并将其与香草 GAN 模型进行比较。如图 11.36和图 11.38所示,DCGAN 模型的结果明显不同,图像呈现出香蕉形状并具有不同的变化和方向。即使某些图像比其他的更像香蕉,但所有图像至少都有香蕉的可识别特征,比如颜色、形状和黑色尖端的存在。然而,香草 GAN 模型的结果看起来更像是训练数据集的像素平均值,整体上无法很好地代表现实中的香蕉。所有图像似乎都具有相同的方向,这可能是另一个表明结果更像是训练数据像素平均值的指示。
Matthew Moocarme
Anthony So
Anthony Maddalone
嘿!
我们是 Matthew Moocarme、Anthony So 和 Anthony Maddalone,本书的作者。我们真的希望你喜欢阅读本书,并且觉得它对学习 TensorFlow 有帮助。
如果你能在亚马逊上留下评论,分享你对《TensorFlow 工作坊》的看法,这将对我们(以及其他潜在的读者)大有帮助!
请访问链接 packt.link/r/1800205252
。
或者
扫描二维码留下您的评论。
您的评论将帮助我们了解本书中的优点和未来版本可以改进的地方,因此我们非常感激。
祝一切顺利,
马修·穆卡姆,安东尼·索,安东尼·马多隆