Spark-深度学习秘籍-全-
Spark 深度学习秘籍(全)
原文:
annas-archive.org/md5/0b821d0906a13124b659b6b5635b3940译者:飞龙
前言
随着深度学习在现代工业中迅速获得广泛应用,组织正在寻求将流行的大数据工具与高效的深度学习库结合起来的方法。这将有助于深度学习模型以更高的效率和速度进行训练。
在Apache Spark 深度学习实战的帮助下,你将通过具体的实践步骤生成深度学习算法的结果,而不会陷入理论的困扰。从为深度学习设置 Apache Spark,到实现各种神经网络,本书将处理常见及不常见的问题,以便在分布式环境中进行深度学习。此外,你还将获得 Spark 中深度学习代码的访问权限,这些代码可以重复使用来解决类似问题,或稍作修改来解决不同问题。你还将学习如何使用 Spark 进行数据流处理和聚类。一旦掌握了基础知识,你将探索如何使用流行的库(如 TensorFlow 和 Keras)在 Spark 中实现和部署深度学习模型,如 CNN、RNN 和 LSTM。最终,本书是一本旨在教授如何在 Spark 上实际应用模型的实用手册,因此我们不会深入探讨理论和数学。
本章所使用模型的背后,我们会引用额外的资源以供参考。
每个模型的信息可以通过引用来获得。
本书结束时,你将具备在 Apache Spark 上训练和部署高效深度学习模型的专业知识。
本书的适用人群
本书适合那些具备基本机器学习和大数据概念理解的人,特别是那些希望通过自上而下的方式扩展知识的人。本书提供了深度学习和机器学习算法的即插即用方式。没有编程经验的人,尤其是没有 Python 经验的人,可以通过按步骤执行本书中的食谱轻松实现算法。本书中的大部分代码都可以自解释。每个代码块执行特定的功能,或在数据挖掘、操作、转换和拟合深度学习模型中执行某个动作。
本书旨在通过有趣的项目(如股票价格预测)为读者提供实践经验,同时帮助读者更牢固地理解深度学习和机器学习的概念。这是通过书中每章提供的许多在线资源链接实现的,包括已发布的论文、教程和指南。
本书内容
第一章,为深度学习配置 Spark,涵盖了你在虚拟 Ubuntu 桌面环境中开始开发 Spark 所需的所有内容。
第二章,使用 Spark 创建神经网络,解释了如何从零开始开发神经网络,而不使用任何深度学习库,如 TensorFlow 或 Keras。
第三章,卷积神经网络的痛点,讲解了在进行图像识别时,卷积神经网络的一些痛点,以及如何克服这些问题。
第四章,循环神经网络的痛点,介绍了前馈神经网络和循环神经网络。我们描述了一些循环神经网络中出现的痛点,以及如何通过使用 LSTM 来解决这些问题。
第五章,使用 Spark ML 预测消防部门呼叫,介绍了如何开发一个分类模型,预测来自旧金山的消防部门呼叫,使用 Spark 机器学习工具。
第六章,在生成网络中使用 LSTM,提供了一种实操方法,使用小说或大型文本语料库作为输入数据来定义和训练 LSTM 模型,同时使用训练好的模型生成自己的输出序列。
第七章,使用 TF-IDF 进行自然语言处理,讲解了将聊天机器人对话数据分类以进行升级的步骤。
第八章,使用 XGBoost 进行房地产价值预测,聚焦于使用 Kings County 房屋销售数据集训练一个简单的线性模型,并利用该模型预测房价,然后再深入探讨一个稍微复杂一点的模型,进一步提高预测准确度。
第九章,使用 LSTM 预测苹果股票市场价格,重点介绍了如何使用 Keras 中的 LSTM 创建深度学习模型来预测 AAPL 股票的股市价格。
第十章,使用深度卷积神经网络进行人脸识别,利用 MIT-CBCL 数据集中的 10 个不同被试的面部图像,训练和测试一个深度卷积神经网络模型。
第十一章,使用 Word2Vec 创建和可视化词向量,强调了向量在机器学习中的重要性,并指导用户如何利用 Google 的 Word2Vec 模型训练不同的模型并可视化从小说中生成的词向量。
第十二章,使用 Keras 创建电影推荐引擎,聚焦于使用深度学习库 Keras 为用户构建电影推荐引擎。
第十三章,使用 TensorFlow 在 Spark 上进行图像分类,重点介绍了利用迁移学习识别世界上排名前两位的足球运动员:克里斯蒂亚诺·罗纳尔多和利昂内尔·梅西。
为了从本书中获得最大收益
-
利用所有提供的链接,更好地理解本书中使用的一些术语。
-
互联网是当今世界上最大的大学。利用 YouTube、Udemy、edX、Lynda 和 Coursera 等网站,观看它们关于深度学习和机器学习概念的视频。
-
不要只是阅读这本书然后忘记它。请在阅读时实践每一步。建议你在阅读每一章时打开 Jupyter Notebook,这样你可以在阅读过程中实践每个食谱,并同时检查每个步骤的输出。
下载示例代码文件
你可以从你的账户在 www.packtpub.com 下载这本书的示例代码文件。如果你从其他地方购买了本书,可以访问 www.packtpub.com/support 并注册,将文件直接发送到你的邮箱。
你可以按照以下步骤下载代码文件:
-
登录或注册 www.packtpub.com。
-
选择“支持”标签。
-
点击“代码下载与勘误”。
-
在搜索框中输入书名,并按照屏幕上的指示操作。
文件下载后,请确保使用最新版本的解压工具解压或提取文件夹:
-
WinRAR/7-Zip for Windows
-
Zipeg/iZip/UnRarX for Mac
-
7-Zip/PeaZip for Linux
这本书的代码包也托管在 GitHub 上,地址是 github.com/PacktPublishing/Apache-Spark-Deep-Learning-Cookbook。如果代码有更新,它将会在现有的 GitHub 仓库中进行更新。
我们还提供来自我们丰富的书籍和视频目录的其他代码包,访问github.com/PacktPublishing/,快来看看吧!
使用的约定
本书中使用了许多文本约定。
CodeInText:表示文本中的代码词汇、数据库表名、文件夹名称、文件名、文件扩展名、路径名、虚拟网址、用户输入和 Twitter 账号。例如:"Save under the trained folder inside the working directory."
代码块的设置如下:
print('Total Rows')
df.count()
print('Rows without Null values')
df.dropna().count()
print('Row with Null Values')
df.count()-df.dropna().count()
任何命令行输入或输出都写作如下:
nltk.download("punkt")
nltk.download("stopwords")
粗体:表示新术语、重要单词或屏幕上显示的单词。例如,菜单或对话框中的词汇会以这种形式出现在文本中。这里有一个示例:"Right-click on the page and click on Save As..."
警告或重要提示以此形式出现。
提示和技巧以此形式出现。
章节
本书中会有多个常见标题(准备工作、如何操作...、原理...、更多内容...和另见)。
为了清晰地说明如何完成食谱,请按以下方式使用这些部分:
准备工作
本节告诉你在本教程中可以期待什么,并描述了如何设置任何软件或完成食谱所需的任何初步设置。
如何操作...
本节包含遵循食谱所需的步骤。
原理...
本节通常包含对上一节发生内容的详细解释。
更多内容...
本节包含有关食谱的附加信息,帮助你更好地了解食谱。
另见
本节提供了有用的链接,指向食谱中其他有用的信息。
联系我们
我们欢迎读者的反馈。
一般反馈:请发送电子邮件至 feedback@packtpub.com,并在邮件主题中注明书名。如果你对本书的任何部分有问题,请通过 questions@packtpub.com 给我们发送邮件。
勘误表:尽管我们已尽最大努力确保内容的准确性,但错误难免。如果你发现本书中的错误,我们将感激你向我们报告。请访问 www.packtpub.com/submit-errata,选择你的书籍,点击“勘误提交表格”链接,并填写相关信息。
盗版:如果你在互联网上发现任何形式的非法复制我们作品的内容,我们将感激你提供相关网址或网站名称。请通过copyright@packtpub.com联系我们,并附上相关材料的链接。
如果你有兴趣成为作者:如果你在某个领域有专业知识,且有兴趣写书或参与书籍的编写,请访问 authors.packtpub.com。
评论
请留下评论。一旦你阅读并使用了本书,不妨在你购买书籍的网站上留下评论?潜在读者可以看到并参考你的公正意见来做出购买决策,我们在 Packt 可以了解你对我们产品的看法,作者也能看到你对他们书籍的反馈。谢谢!
如需了解更多关于 Packt 的信息,请访问 packtpub.com。
第一章:设置 Spark 进行深度学习开发
本章将涵盖以下内容:
-
下载 Ubuntu Desktop 镜像
-
在 macOS 上使用 VMWare Fusion 安装和配置 Ubuntu
-
在 Windows 上使用 Oracle VirtualBox 安装和配置 Ubuntu
-
在 Google Cloud Platform 上安装和配置 Ubuntu Desktop
-
在 Ubuntu Desktop 上安装和配置 Spark 及其先决条件
-
将 Jupyter 笔记本与 Spark 集成
-
启动和配置 Spark 集群
-
停止 Spark 集群
介绍
深度学习是机器学习算法的专注研究,采用神经网络作为其主要学习方法。深度学习在过去几年迅速崛起。微软、谷歌、Facebook、亚马逊、苹果、特斯拉及许多其他公司都在他们的应用程序、网站和产品中使用深度学习模型。与此同时,Spark,作为一个基于内存的大数据计算引擎,极大简化了以创纪录的速度和轻松度处理海量信息的过程。事实上,Spark 已经成为数据工程师、机器学习工程师和数据科学家首选的大数据开发工具。
由于深度学习模型在处理更多数据时表现更好,Spark 与深度学习的协同作用促成了二者的完美结合。与执行深度学习算法的代码几乎同等重要的是,能够实现最佳开发的工作环境。许多才俊渴望开发神经网络,以帮助解答研究中的重要问题。不幸的是,深度学习模型开发面临的最大障碍之一是缺乏必要的技术资源来在大数据上进行学习。本章的目的是为在 Spark 上进行深度学习开发创建一个理想的虚拟开发环境。
下载 Ubuntu Desktop 镜像
Spark 可以为各种操作系统进行配置,无论它们是本地部署还是云端部署。对于我们的目的,Spark 将安装在基于 Linux 的虚拟机上,操作系统为 Ubuntu。选择 Ubuntu 作为虚拟机的操作系统有几个优势,最重要的一个就是成本。由于 Ubuntu 基于开源软件,因此它是免费的,不需要许可费用。成本始终是一个考虑因素,本书的主要目标之一是尽量减少启动 Spark 框架上深度学习所需的财务支出。
准备就绪
下载镜像文件需要满足一些最低推荐要求:
-
最低要求 2 GHz 双核处理器
-
最低要求 2 GB 系统内存
-
至少 25 GB 的空闲硬盘空间
如何操作...
按照以下步骤下载 Ubuntu Desktop 镜像:
-
为了创建 Ubuntu Desktop 虚拟机,必须首先从官方网站下载该文件:
www.ubuntu.com/download/desktop. -
截至本文撰写时,Ubuntu Desktop 16.04.3 是最新的可供下载版本。
-
下载完成后,访问以下
.iso格式的文件:ubuntu-16.04.3-desktop-amd64.iso
它是如何工作的...
虚拟环境通过隔离与物理或主机机器的关系,为开发者提供了一个理想的开发工作空间。开发者可能使用各种不同的机器作为主机环境,例如运行 macOS 的 MacBook、运行 Windows 的 Microsoft Surface,甚至是运行在云上的虚拟机,如 Microsoft Azure 或 AWS;然而,为了确保代码执行的输出结果的一致性,将在 Ubuntu Desktop 中部署一个虚拟环境,该环境可以在多种主机平台上使用和共享。
还有更多...
根据主机环境是 Windows 还是 macOS,桌面虚拟化软件有几种选择。当使用 macOS 时,有两种常见的虚拟化软件应用:
-
VMWare Fusion
-
Parallels
参见
要了解更多关于 Ubuntu Desktop 的信息,可以访问 www.ubuntu.com/desktop。
在 macOS 上使用 VMWare Fusion 安装和配置 Ubuntu
本节将重点介绍使用VMWare Fusion构建基于 Ubuntu 操作系统的虚拟机。
准备中
你的系统中需要先安装 VMWare Fusion。如果你目前没有安装,可以从以下网站下载试用版:
www.vmware.com/products/fusion/fusion-evaluation.html
如何操作...
按照下面的步骤,在 macOS 上使用 VMWare Fusion 配置 Ubuntu:
- 一旦 VMWare Fusion 启动并运行,点击左上角的+按钮开始配置过程,选择“新建...”选项,如下图所示:

- 一旦选择完成,选择从磁盘或镜像安装的选项,如下图所示:

- 选择从 Ubuntu Desktop 网站下载的操作系统
iso文件,如下图所示:

- 下一步将询问你是否选择 Linux Easy Install。建议选择此选项,并为 Ubuntu 环境设置显示名称/密码组合,如下图所示:

- 配置过程几乎完成。显示虚拟机摘要,并提供自定义设置的选项,以增加内存和硬盘,如下图所示:

- 虚拟机的硬盘空间从 20 GB 到 40 GB 都足够;然而,将内存提升至 2 GB 或 4 GB 将有助于提高虚拟机在后续章节中执行 Spark 代码时的性能。通过选择虚拟机设置中的处理器和内存选项,将内存更新为所需的大小,如下图所示:

它是如何工作的...
该设置允许手动配置必要的设置,以便成功在 VMWare Fusion 上启动 Ubuntu Desktop。可以根据主机机器的需要和可用性增加或减少内存和硬盘存储。
还有更多内容...
剩下的就是第一次启动虚拟机,这将启动系统的安装过程。一旦所有设置完成并且用户登录后,Ubuntu 虚拟机就可以用于开发了,如下图所示:

另见
除了 VMWare Fusion 外,Mac 上还有另一个提供类似功能的产品,叫做 Parallels Desktop for Mac。要了解有关 VMWare 和 Parallels 的更多信息,并决定哪个程序更适合你的开发需求,请访问以下网站:
-
www.vmware.com/products/fusion.html下载并安装 Mac 版 VMWare Fusion -
parallels.com下载并安装 Mac 版 Parallels Desktop
在 Windows 上使用 Oracle VirtualBox 安装和配置 Ubuntu
与 macOS 不同,Windows 有多个选项可以虚拟化系统。这主要是因为在 Windows 上进行虚拟化非常常见,因为大多数开发者使用 Windows 作为主机环境,并需要虚拟环境进行测试,而不影响依赖 Windows 的任何依赖项。
准备工作
Oracle 的 VirtualBox 是一种常见的虚拟化产品,可以免费使用。Oracle VirtualBox 提供了一个简单的流程,可以在 Windows 环境中快速启动并运行 Ubuntu Desktop 虚拟机。
如何操作...
按照以下步骤使用 VirtualBox 在 Windows 上配置 Ubuntu:
- 启动 Oracle VM VirtualBox 管理器。接下来,点击新建图标创建一个新的虚拟机,并指定虚拟机的名称、类型和版本,如下图所示:

- 选择专家模式,因为在配置步骤中有几个步骤将被合并,如下图所示:

理想的内存大小应至少设置为 2048 MB,或者根据主机资源,最好设置为 4096 MB。
- 此外,为执行深度学习算法的 Ubuntu 虚拟机设置一个至少为 20 GB 的最佳硬盘大小(如果不是更多),如下面的截图所示:

- 将虚拟机管理器指向下载了 Ubuntu
iso文件的启动盘位置,然后开始创建过程,如下图所示:

- 安装完成后,选择开始图标以完成虚拟机并准备好进行开发,如下图所示:

工作原理...
设置允许手动配置必要的设置,以便在 Oracle VirtualBox 上成功运行 Ubuntu 桌面。与 VMWare Fusion 一样,内存和硬盘存储可以根据主机的需要和可用性进行增加或减少。
还有更多...
请注意,一些运行 Microsoft Windows 的机器默认未设置为虚拟化,用户可能会收到一条初始错误提示,表明 VT-x 未启用。这个问题可以通过重新启动时进入 BIOS 并启用虚拟化来解决。
另见
要了解更多关于 Oracle VirtualBox 的信息并决定它是否适合使用,请访问以下网站并选择 Windows 主机以开始下载过程:www.virtualbox.org/wiki/Downloads。
为 Google Cloud Platform 安装和配置 Ubuntu 桌面
之前,我们看到如何使用 VMWare Fusion 在本地设置 Ubuntu 桌面。在本节中,我们将学习如何在 Google Cloud Platform 上执行相同的操作。
准备工作
唯一的要求是 Google 帐号用户名。首先,使用 Google 帐号登录到 Google Cloud Platform。Google 提供一个免费的 12 个月订阅,并为您的账户充值 300 美元。设置过程中会要求您提供银行信息;但 Google 不会在未明确告知的情况下收费。请继续验证您的银行账户,然后就可以开始了。
如何操作...
按照步骤配置 Ubuntu 桌面以适配 Google Cloud Platform:
- 登录到 Google Cloud Platform 后,访问一个类似于以下截图的仪表板:

Google Cloud Platform 仪表板
- 首先,点击屏幕左上角的产品服务按钮。在下拉菜单中,在计算下点击 VM 实例,如下图所示:

-
创建一个新的实例并命名。我们在此案例中命名为
ubuntuvm1。在启动实例时,Google Cloud 会自动创建一个项目,实例将会在该项目 ID 下启动。项目可以根据需要重新命名。 -
点击 创建实例 后,选择您所在的区域。
-
在启动磁盘下选择 Ubuntu 16.04LTS,这是将在云中安装的操作系统。请注意,LTS 代表版本,并且会获得 Ubuntu 开发者的长期支持。
-
接下来,在启动磁盘选项下,选择 SSD 持久磁盘,并将大小增加到 50 GB,为实例增加一些存储空间,如下截图所示:

-
接下来,将访问范围设置为 允许对所有 Cloud API 的完全访问。
-
在防火墙下,请勾选 允许 HTTP 流量 和 允许 HTTPS 流量,如下面截图所示:

选择允许 HTTP 流量和 HTTPS 流量
- 一旦按照本节中所示配置完实例,点击创建按钮来创建实例。
点击创建按钮后,您会发现实例被创建,且具有唯一的内部和外部 IP 地址。我们将在后续阶段使用这个 IP 地址。SSH 指的是安全的 shell 隧道,本质上是一种加密的客户端-服务器通信方式。可以把它想象成数据从您的笔记本到 Google 的云服务器,经过加密隧道来回传输。
- 点击新创建的实例。在下拉菜单中,点击 在浏览器窗口中打开,如下面截图所示:

- 您会看到 Google 打开一个新的 shell/终端窗口,如下截图所示:

- 一旦打开 shell,您应该看到如下截图所示的窗口:

- 在 Google 云 Shell 中输入以下命令:
$ sudo apt-get update
$ sudo apt-get upgrade
$ sudo apt-get install gnome-shell
$ sudo apt-get install ubuntu-gnome-desktop
$ sudo apt-get install autocutsel
$ sudo apt-get install gnome-core
$ sudo apt-get install gnome-panel
$ sudo apt-get install gnome-themes-standard
- 当提示是否继续时,输入
y然后按 ENTER,如下截图所示:

- 完成前面的步骤后,输入以下命令来设置
vncserver并允许连接到本地 shell:
$ sudo apt-get install tightvncserver
$ touch ~/.Xresources
- 接下来,通过输入以下命令来启动服务器:
$ tightvncserver
- 这将提示您输入密码,该密码稍后将用于登录到 Ubuntu Desktop 虚拟机。密码限制为八个字符,并需要设置和验证,如下面截图所示:

- shell 会自动生成一个启动脚本,如下面截图所示。可以通过复制并粘贴其
PATH来访问和编辑该启动脚本:

- 在我们的例子中,查看和编辑脚本的命令是:
:~$ vim /home/amrith2kmeanmachine/.vnc/xstartup
这个PATH在不同情况下可能不同,请确保设置正确的PATH。在 Mac 上,vim命令会在文本编辑器中打开脚本。
本地终端生成了一个启动脚本和一个日志文件。需要在文本编辑器中打开并编辑该启动脚本,接下来将讨论这一过程。
- 输入
vim命令后,启动脚本的界面应类似于以下截图:

- 输入
i进入INSERT模式。接下来,删除启动脚本中的所有文本,界面应如以下截图所示:

- 将以下代码复制粘贴到启动脚本中:
#!/bin/sh
autocutsel -fork
xrdb $HOME/.Xresources
xsetroot -solid grey
export XKL_XMODMAP_DISABLE=1
export XDG_CURRENT_DESKTOP="GNOME-Flashback:Unity"
export XDG_MENU_PREFIX="gnome-flashback-"
unset DBUS_SESSION_BUS_ADDRESS
gnome-session --session=gnome-flashback-metacity --disable-acceleration-check --debug &
- 启动脚本应出现在编辑器中,如下截图所示:

-
按
Esc退出INSERT模式,并键入:wq保存并退出文件。 -
配置完启动脚本后,在 Google shell 中输入以下命令以终止服务器并保存更改:
$ vncserver -kill :1
- 该命令应产生一个进程 ID,类似于以下截图所示:

- 通过输入以下命令重新启动服务器:
$ vncserver -geometry 1024x640
接下来的步骤将重点介绍如何确保从本地主机到 Google Cloud 实例的 shell 隧道安全。在本地 shell/终端输入任何内容之前,请确保已经安装 Google Cloud。如果尚未安装,请按照以下网址中的快速入门指南进行安装:
cloud.google.com/sdk/docs/quickstart-mac-os-x
- 安装 Google Cloud 后,打开你机器上的终端并输入以下命令来连接到 Google Cloud 计算实例:
$ gcloud compute ssh \
YOUR INSTANCE NAME HERE \
--project YOUR PROJECT NAME HERE \
--zone YOUR TIMEZONE HERE \
--ssh-flag "-L 5901:localhost:5901"
- 确保在前面的命令中正确指定了实例名称、项目 ID 和区域。按下 ENTER 后,本地 shell 中的输出会变成以下截图所示:

-
一旦看到实例名称后跟
":~$",就表示本地主机/笔记本与 Google Cloud 实例之间的连接已成功建立。在成功 SSH 连接到实例后,我们需要一款名为VNC Viewer的软件来查看并与已经在 Google Cloud Compute 引擎上成功设置的 Ubuntu 桌面进行交互。接下来的步骤将讨论如何实现这一目标。 -
可以使用以下链接下载 VNC Viewer:
www.realvnc.com/en/connect/download/viewer/
- 安装完成后,点击打开 VNC Viewer,在搜索栏中输入
localhost::5901,如以下截图所示:

- 接下来,在出现以下屏幕时点击继续:

- 这将提示您输入虚拟机的密码。请输入您在第一次启动
tightvncserver命令时设置的密码,如下图所示:

- 最终,您将进入 Google Cloud Compute 上的 Ubuntu 虚拟机桌面。通过 VNC Viewer 查看时,您的 Ubuntu Desktop 屏幕现在应显示如下截图所示的内容:

它是如何工作的...
您现在已经成功设置了 VNC Viewer 以与 Ubuntu 虚拟机/桌面进行交互。每当 Google Cloud 实例未在使用时,建议暂停或关闭实例,以避免产生额外费用。云计算方法非常适合那些可能无法访问具有高内存和存储的物理资源的开发者。
还有更多...
虽然我们讨论了将 Google Cloud 作为 Spark 的云选项,但也可以在以下云平台上使用 Spark:
-
Microsoft Azure
-
亚马逊网络服务
另请参见
若要了解更多有关 Google Cloud Platform 的信息并注册免费订阅,请访问以下网站:
在 Ubuntu Desktop 上安装和配置 Spark 及其先决条件
在 Spark 启动之前,需要在全新的 Ubuntu Desktop 上安装一些必要的先决条件。本节将重点介绍在 Ubuntu Desktop 上安装和配置以下内容:
-
Java 8 或更高版本
-
Anaconda
-
Spark
准备就绪
本节唯一的要求是具有管理员权限,以便在 Ubuntu Desktop 上安装应用程序。
如何操作...
本节将通过以下步骤指导您在 Ubuntu Desktop 上安装 Python 3、Anaconda 和 Spark:
- 通过终端应用程序在 Ubuntu 上安装 Java,您可以通过搜索该应用程序并将其锁定到左侧的启动栏来找到它,如下图所示:

- 在虚拟机上通过终端执行以下命令进行 Java 的初步测试:
java -version
- 在终端中执行以下四个命令来安装 Java:
sudo apt-get install software-properties-common
$ sudo add-apt-repository ppa:webupd8team/java
$ sudo apt-get update
$ sudo apt-get install oracle-java8-installer
- 在接受 Oracle 的必要许可协议后,再次通过在终端中执行
java -version来对虚拟机上的 Java 进行二次测试。Java 安装成功后,终端将显示以下结果:
$ java -version
java version "1.8.0_144"
Java(TM) SE Runtime Environment (build 1.8.0_144-b01)
Java HotSpot(TM) 64-Bit Server VM (build 25.144-b01, mixed mode)
- 接下来,安装最新版本的 Anaconda。当前版本的 Ubuntu Desktop 已预装 Python。虽然 Ubuntu 中预装了 Python 非常方便,但所安装的版本是 Python 2.7,如下所示:
$ python --version
Python 2.7.12
- 当前 Anaconda 版本为 v4.4,当前 Python 3 版本为 v3.6。下载完成后,通过以下命令访问
Downloads文件夹查看 Anaconda 安装文件:
$ cd Downloads/
~/Downloads$ ls
Anaconda3-4.4.0-Linux-x86_64.sh
- 进入
Downloads文件夹后,执行以下命令启动 Anaconda 的安装:
~/Downloads$ bash Anaconda3-4.4.0-Linux-x86_64.sh
Welcome to Anaconda3 4.4.0 (by Continuum Analytics, Inc.)
In order to continue the installation process, please review the license agreement.
Please, press ENTER to continue
请注意,Anaconda 的版本以及安装的其他软件可能会有所不同,因为更新版本会定期发布。我们在本章和本书中使用的 Anaconda 版本可以从repo.continuum.io/archive/Anaconda3-4.4.0-Linux-x86.sh下载。
- Anaconda 安装完成后,重启终端应用程序,通过在终端中执行
python --version命令来确认 Python 3 现在是默认的 Python 环境:
$ python --version
Python 3.6.1 :: Anaconda 4.4.0 (64-bit)
- Python 2 版本仍然可以在 Linux 下使用,但在执行脚本时需要显式调用,如下命令所示:
~$ python2 --version
Python 2.7.12
- 访问以下网站,开始 Spark 的下载和安装过程:
spark.apache.org/downloads.html
- 选择下载链接。以下文件将被下载到 Ubuntu 的
Downloads文件夹中:
spark-2.2.0-bin-hadoop2.7.tgz
- 通过执行以下命令在终端查看文件:
$ cd Downloads/
~/Downloads$ ls
spark-2.2.0-bin-hadoop2.7.tgz
- 通过执行以下命令提取
tgz文件:
~/Downloads$ tar -zxvf spark-2.2.0-bin-hadoop2.7.tgz
- 使用
ls命令再次查看Downloads目录,显示了tug文件和提取后的文件夹:
~/Downloads$ ls
spark-2.2.0-bin-hadoop2.7 spark-2.2.0-bin-hadoop2.7.tgz
- 执行以下命令,将提取的文件夹从
Downloads文件夹移动到Home文件夹:
~/Downloads$ mv spark-2.2.0-bin-hadoop2.7 ~/
~/Downloads$ ls
spark-2.2.0-bin-hadoop2.7.tgz
~/Downloads$ cd
~$ ls
anaconda3 Downloads Pictures Templates
Desktop examples.desktop Public Videos
Documents Music spark-2.2.0-bin-hadoop2.7
- 现在,
spark-2.2.0-bin-hadoop2.7文件夹已经被移动到Home文件夹,可以通过点击左侧工具栏中的Files图标查看,如下图所示:

- Spark 现在已安装。通过在终端执行以下脚本,启动 Spark:
~$ cd ~/spark-2.2.0-bin-hadoop2.7/
~/spark-2.2.0-bin-hadoop2.7$ ./bin/pyspark
- 通过执行以下命令在终端进行最后测试,以确保
SparkContext正在驱动本地环境中的集群,确保 Spark 已成功运行:
>>> sc
<SparkContext master=local[*] appName=PySparkShell>
它是如何工作的...
本节解释了安装 Python、Anaconda 和 Spark 过程背后的理由。
- Spark 运行在Java 虚拟机(JVM)上,Java 软件开发工具包(SDK)是 Spark 在 Ubuntu 虚拟机上运行的前提安装条件。
为了让 Spark 在本地机器或集群上运行,安装时需要最低版本的 Java 6。
-
Ubuntu 建议使用
sudo apt install方法安装 Java,因为它确保下载的包是最新的。 -
请注意,如果当前未安装 Java,终端中将显示以下消息:
The program 'java' can be found in the following packages: * default-jre * gcj-5-jre-headless * openjdk-8-jre-headless * gcj-4.8-jre-headless * gcj-4.9-jre-headless * openjdk-9-jre-headless Try: sudo apt install <selected package>-
虽然 Python 2 仍然可用,但它已被视为遗留版本。Python 2 将于 2020 年停止支持;因此,建议所有新的 Python 开发都使用 Python 3,正如本书中的示例一样。直到最近,Spark 只支持 Python 2。现在情况已经不同,Spark 支持 Python 2 和 3。安装 Python 3 及其众多依赖库和工具包的一个便捷方法是通过 Anaconda。Anaconda 是一个免费的开源 Python 和 R 发行版,它负责管理数据科学相关任务中最常用的 Python 包的安装和维护。
-
在安装 Anaconda 过程中,确认以下条件是很重要的:
-
Anaconda 安装在
/home/username/Anaconda3位置 -
Anaconda 安装程序会将 Anaconda3 的安装位置添加到
/home/username/.bashrc中的PATH环境变量。
-
-
在安装了 Anaconda 之后,下载 Spark。与 Python 不同,Spark 在 Ubuntu 上没有预安装,因此需要手动下载并安装。
-
为了进行深度学习开发,将为 Spark 选择以下偏好设置:
-
Spark 版本:2.2.0(2017 年 7 月 11 日)
-
软件包类型:预构建的 Apache Hadoop 2.7 及更高版本
-
下载类型:直接下载
-
-
一旦 Spark 成功安装,执行 Spark 命令行后的输出应该类似于以下截图所示:
![]()
-
初始化 Spark 时需要注意的两个重要特性是,它位于
Python 3.6.1|Anaconda 4.4.0 (64-bit)| 框架下,并且 Spark 的版本为 2.2.0。 -
恭喜!Spark 已成功安装在本地 Ubuntu 虚拟机上。但是,并不是一切都完成了。Spark 开发最理想的方式是在 Jupyter notebook 中执行 Spark 代码,特别是在深度学习的情况下。幸运的是,Jupyter 已经在本节早些时候通过 Anaconda 安装。
更多内容...
你可能会问,为什么我们不直接使用
pip install pyspark来在 Python 中使用 Spark。之前的 Spark 版本需要通过本节中描述的安装过程。自 Spark 2.2.0 起,未来版本将开始支持通过pip直接安装。我们在本节中使用完整的安装方法,以确保你能够正确安装并完全集成 Spark,尤其是如果你正在使用 Spark 的早期版本。另见
要了解更多关于 Jupyter notebooks 及其与 Python 集成的信息,请访问以下网站:
要了解更多关于 Anaconda 的信息并下载 Linux 版本,请访问以下网站:
将 Jupyter notebooks 与 Spark 集成
在第一次学习 Python 时,使用 Jupyter 笔记本作为交互式开发环境(IDE)非常有用。这也是 Anaconda 如此强大的主要原因之一。它完全整合了 Python 和 Jupyter 笔记本之间的所有依赖关系。同样的操作也可以通过 PySpark 和 Jupyter 笔记本来实现。尽管 Spark 是用Scala编写的,PySpark 则允许代码在 Python 中进行转换。
准备就绪
本节的大部分工作只需要从终端访问
.bashrc脚本。如何操作...
默认情况下,PySpark 未配置为在 Jupyter 笔记本中工作,但稍微调整一下
.bashrc脚本可以解决这个问题。我们将在本节中介绍这些步骤:- 通过执行以下命令来访问
.bashrc脚本:
$ nano .bashrc- 滚动到脚本的末尾应显示最后修改的命令,该命令应该是之前部分安装过程中 Anaconda 设置的
PATH。PATH应如下所示:
# added by Anaconda3 4.4.0 installer export PATH="/home/asherif844/anaconda3/bin:$PATH"- 在下面,Anaconda 安装程序添加的
PATH可以包括一个自定义函数,帮助将 Spark 安装与 Anaconda3 中的 Jupyter 笔记本安装进行通信。为了本章及后续章节的目的,我们将该函数命名为sparknotebook。对于sparknotebook(),配置应如下所示:
function sparknotebook() { export SPARK_HOME=/home/asherif844/spark-2.2.0-bin-hadoop2.7 export PYSPARK_PYTHON=python3 export PYSPARK_DRIVER_PYTHON=jupyter export PYSPARK_DRIVER_PYTHON_OPTS="notebook" $SPARK_HOME/bin/pyspark }-
更新后的
.bashrc脚本保存后应如下所示:![]()
-
保存并退出
.bashrc文件。建议通过执行以下命令并重新启动终端应用程序来通知.bashrc文件已更新:
$ source .bashrc它是如何工作的...
本节的目标是将 Spark 直接集成到 Jupyter 笔记本中,以便我们不再在终端中进行开发,而是利用在笔记本中开发的优势。本节解释了如何将 Spark 集成到 Jupyter 笔记本中。
-
我们将创建一个命令函数
sparknotebook,可以通过终端调用它,从 Anaconda 安装中通过 Jupyter 笔记本打开 Spark 会话。这需要在.bashrc文件中设置两个配置:-
设置 PySpark 的 Python 版本为 Python 3
-
为 Python 设置 PySpark 驱动程序以在 Jupyter 中运行
-
-
现在可以通过执行以下命令直接从终端访问
sparknotebook函数:
$ sparknotebook-
然后,函数应通过默认的网页浏览器启动全新的 Jupyter 笔记本会话。可以通过点击右侧的New按钮并选择Notebook下的Python 3来创建一个带有
.ipynb扩展名的新的 Python 脚本,如下图所示:![]()
-
一如既往,就像在终端层面为 Spark 所做的那样,
sc的简单脚本将会在 notebook 中执行,以确认 Spark 已通过 Jupyter 启动:![]()
-
理想情况下,
Version、Master和AppName应该与执行sc命令时的输出相同。如果是这样,那么 PySpark 已经成功安装并配置好了,可以与 Jupyter notebook 一起使用。
还有更多...
需要注意的是,如果我们通过终端调用 Jupyter notebook 时没有指定
sparknotebook,我们的 Spark 会话将无法启动,并且在执行SparkContext脚本时会收到错误。我们可以通过在终端执行以下命令来访问传统的 Jupyter notebook:
jupyter-notebook一旦我们启动 notebook,我们可以尝试执行与之前相同的
sc.master脚本,但这次我们会收到以下错误:![]()
另见
有许多在线管理的 Spark 服务,这些公司通过 notebook 接口提供 Spark,其中 Spark 的安装和配置已经为您管理好了。以下是这些服务:
-
Hortonworks (
hortonworks.com/) -
Cloudera (
www.cloudera.com/) -
MapR (
mapr.com/) -
DataBricks (
databricks.com/)
启动和配置 Spark 集群
对于大多数章节,我们首先要做的就是初始化并配置我们的 Spark 集群。
准备工作
在初始化集群之前导入以下内容。
from pyspark.sql import SparkSession
如何操作...
本节介绍了初始化和配置 Spark 集群的步骤。
- 使用以下脚本导入
SparkSession:
from pyspark.sql import SparkSession- 使用以下脚本配置名为
spark的SparkSession:
spark = SparkSession.builder \ .master("local[*]") \ .appName("GenericAppName") \ .config("spark.executor.memory", "6gb") \ .getOrCreate()它是如何工作的...
本节解释了
SparkSession作为开发 Spark 应用程序的入口点的工作原理。-
从 Spark 2.0 开始,不再需要创建
SparkConf和SparkContext来开始在 Spark 中的开发。这些步骤不再需要,因为导入SparkSession就可以处理集群的初始化。此外,需要注意的是,SparkSession是pyspark的sql模块的一部分。 -
我们可以为
SparkSession分配属性:-
master:将 Spark 主节点 URL 分配到我们local机器上,并使用最大可用核心数运行。 -
appName:为应用程序分配一个名称 -
config:将6gb分配给spark.executor.memory -
getOrCreate:确保如果没有可用的SparkSession则创建一个,如果有现有的,则获取它。
-
还有更多...
对于开发目的,当我们在较小的数据集上构建应用时,可以使用
master("local")。如果我们要部署到生产环境中,我们则需要指定master("local[*]"),以确保使用最大可用核心数并获得最佳性能。另请参见
要了解更多关于
SparkSession.builder的信息,请访问以下网站:spark.apache.org/docs/2.2.0/api/java/org/apache/spark/sql/SparkSession.Builder.html停止 Spark 集群
一旦我们完成在集群上的开发,关闭集群以节省资源是理想的做法。
如何操作…
本节将演示如何停止
SparkSession。- 执行以下脚本:
spark.stop()- 执行以下脚本来确认会话是否已关闭:
sc.master它是如何工作的…
本节解释如何确认 Spark 集群已被关闭。
- 如果集群已被关闭,当你在笔记本中执行另一个 Spark 命令时,将会收到以下截图中的错误信息:
![]()
还有更多…
在本地环境中工作时,关闭 Spark 集群可能并不是那么重要;然而,当 Spark 部署在云环境中时,关闭集群就显得至关重要,因为在云环境中,你将为计算资源付费。
-
第二章:在 Spark 中创建神经网络
本章将介绍以下几个食谱:
-
在 PySpark 中创建数据框
-
操作 PySpark 数据框中的列
-
将 PySpark 数据框转换为数组
-
在散点图中可视化数组
-
设置权重和偏差以输入到神经网络中
-
为神经网络归一化输入数据
-
验证数组以优化神经网络性能
-
使用 sigmoid 设置激活函数
-
创建 sigmoid 导数函数
-
计算神经网络中的成本函数
-
根据身高和体重预测性别
-
可视化预测分数
介绍
本书的许多部分将重点介绍使用 Python 中的库(如 TensorFlow 和 Keras)构建深度学习算法。虽然这些库对于在不深入学习深度学习的微积分和线性代数的情况下构建深度神经网络非常有用,但本章将深入探讨如何在 PySpark 中构建一个简单的神经网络,以根据身高和体重进行性别预测。理解神经网络基础的最佳方法之一是从零开始构建一个模型,而不是依赖任何流行的深度学习库。一旦神经网络框架的基础构建完成,理解和使用一些更流行的深度神经网络库将变得更加简单。
在 PySpark 中创建数据框
数据框将作为构建深度学习模型时使用的所有数据的框架。类似于 Python 中的pandas库,PySpark 也有自己的内置功能来创建数据框。
准备开始
在 Spark 中创建数据框有几种方法。一种常见的方法是导入.txt、.csv或.json文件。另一种方法是手动将字段和数据行输入到 PySpark 数据框中,虽然这个过程有点繁琐,但它非常有帮助,尤其是在处理小数据集时。为了根据身高和体重预测性别,本章将手动在 PySpark 中构建一个数据框。所使用的数据集如下:

本章将手动将数据集添加到 PySpark 中,但数据集也可以通过以下链接查看和下载:
github.com/asherif844/ApacheSparkDeepLearningCookbook/blob/master/CH02/data/HeightAndWeight.txt
最后,我们将通过启动一个配置了 Jupyter Notebook 的 Spark 环境来开始本章和未来的章节,该环境是在第一章中创建的,为深度学习设置您的 Spark 环境,并使用以下终端命令:
sparknotebook
如何进行...
在使用 PySpark 时,必须首先导入并初始化SparkSession,然后才能创建任何数据框:
- 使用以下脚本导入
SparkSession:
from pyspark.sql import SparkSession
- 配置
SparkSession:
spark = SparkSession.builder \
.master("local") \
.appName("Neural Network Model") \
.config("spark.executor.memory", "6gb") \
.getOrCreate()
sc = spark.sparkContext
- 在这种情况下,
SparkSession的appName命名为Neural Network Model,并且会话内存被分配为6gb。
它是如何工作的...
本节解释了如何创建我们的 Spark 集群并配置我们的第一个 dataframe。
-
在 Spark 中,我们使用
.master()来指定我们是否将在分布式集群上运行作业或在本地运行。为了本章及后续章节的目的,我们将在本地执行 Spark,并指定一个工作线程,使用.master('local')。这对于测试和开发来说是可以的,正如我们在本章所做的那样;然而,如果将其部署到生产环境中,可能会遇到性能问题。在生产环境中,建议使用.master('local[*]'),将 Spark 设置为尽可能在本地可用的所有工作节点上运行。如果我们机器上有 3 个核心,并且想要将节点数设置为匹配,我们将指定.master('local[3]')。 -
dataframe变量df首先通过插入每列的行值,然后通过插入列头名称来创建,使用以下脚本:
df = spark.createDataFrame([('Male', 67, 150), # insert column values
('Female', 65, 135),
('Female', 68, 130),
('Male', 70, 160),
('Female', 70, 130),
('Male', 69, 174),
('Female', 65, 126),
('Male', 74, 188),
('Female', 60, 110),
('Female', 63, 125),
('Male', 70, 173),
('Male', 70, 145),
('Male', 68, 175),
('Female', 65, 123),
('Male', 71, 145),
('Male', 74, 160),
('Female', 64, 135),
('Male', 71, 175),
('Male', 67, 145),
('Female', 67, 130),
('Male', 70, 162),
('Female', 64, 107),
('Male', 70, 175),
('Female', 64, 130),
('Male', 66, 163),
('Female', 63, 137),
('Male', 65, 165),
('Female', 65, 130),
('Female', 64, 109)],
['gender', 'height','weight']) # insert header values
- 在 PySpark 中,
show()函数提供了预览前 20 行的能力,如以下截图所示,使用上面的脚本时:

还有更多内容...
.show()功能默认显示 20 行,如果没有明确指定。如果我们只想显示 dataframe 的前 5 行,我们需要明确声明,如以下脚本所示:df.show(5)。
参见:
为了深入了解 SparkSQL、dataframe、函数和 PySpark 中的数据集,请访问以下网站:
spark.apache.org/docs/latest/sql-programming-guide.html
在 PySpark dataframe 中操作列
dataframe 几乎完成了;然而,在构建神经网络之前,有一个问题需要解决。与其将gender值保留为字符串,不如将其转换为数字整数以便于计算,随着本章内容的推进,这一点将变得更加明显。
准备工作
本节将需要导入以下内容:
from pyspark.sql import functions
如何操作...
本节将演示如何将字符串转换为 dataframe 中的数字值:
-
女性 --> 0
-
男性 --> 1
- 将 dataframe 中的列值转换需要导入
functions:
from pyspark.sql import functions
- 接下来,使用以下脚本将
gender列修改为数字值:
df = df.withColumn('gender',functions.when(df['gender']=='Female',0).otherwise(1))
- 最后,使用以下脚本重新排序列,使得
gender成为 dataframe 中的最后一列:
df = df.select('height', 'weight', 'gender')
它是如何工作的...
本节解释了如何应用 dataframe 的操作。
-
functions from pyspark.sql有多种有用的逻辑应用,可用于在 Spark dataframe 中的列上应用 if-then 转换。在我们的案例中,我们将Female转换为 0,将Male转换为 1。 -
转换为数字的函数通过
.withColumn()转换应用于 Spark 数据框。 -
Spark 数据框的
.select()特性类似于传统的 SQL,通过按要求的顺序和方式选择列。 -
数据框的最终预览将显示更新后的数据集,如以下截图所示:

还有更多...
除了数据框的withColumn()方法外,还有withColumnRenamed()方法,用于重命名数据框中的列。
将 PySpark 数据框转换为数组
为了构建神经网络的基础结构,必须将 PySpark 数据框转换为数组。Python 有一个非常强大的库numpy,它使得处理数组变得简单。
准备工作
numpy库应该已经包含在anaconda3 Python 包的安装中。然而,如果由于某种原因numpy库不可用,可以通过在终端使用以下命令进行安装:

pip install或sudo pip install将确认所请求的库是否已满足需求:
import numpy as np
如何操作...
本节逐步讲解如何将数据框转换为数组:
- 使用以下脚本查看从数据框收集的数据:
df.select("height", "weight", "gender").collect()
- 使用以下脚本将收集的值存储到名为
data_array的数组中:
data_array = np.array(df.select("height", "weight", "gender").collect())
- 执行以下脚本以访问数组的第一行:
data_array[0]
- 类似地,执行以下脚本以访问数组的最后一行:
data_array[28]
它是如何工作的...
本节说明了如何将数据框转换为数组:
- 我们的数据框的输出可以通过
collect()收集,并如以下截图所示查看:

- 数据框被转换为数组,来自该脚本的数组输出可以在以下截图中看到:

- 可以通过引用数组的索引来访问任意一组
height(身高)、weight(体重)和gender(性别)值。该数组的形状为(29,3),长度为 29 个元素,每个元素由三项组成。虽然数组的长度是 29,但索引从[0]开始,到[28]结束。数组的形状以及第一行和最后一行的输出可以在以下截图中查看:

- 数组的第一个和最后一个值可以与原始数据框进行比较,以确认转换过程中值和顺序未发生变化。
还有更多...
除了查看数组中的数据点外,检索数组中每个特征的最小值和最大值也非常有用:
- 要检索
height(身高)、weight(体重)和gender(性别)的最小值和最大值,可以使用以下脚本:
print(data_array.max(axis=0))
print(data_array.min(axis=0))
- 脚本的输出可以在以下截图中看到:

最大的身高为74英寸,最小的身高为60英寸。最大的体重为188磅,而最小的体重为107磅。性别的最小值和最大值不太相关,因为我们已将其分配为数值0和1。
另见
要了解更多关于 numpy 的信息,请访问以下网站:
在散点图中可视化一个数组
本章开发的神经网络的目标是预测一个个体的性别,前提是已知其身高和体重。理解身高、体重和性别之间关系的强大方法是通过可视化馈送神经网络的数据点。这可以通过流行的 Python 可视化库matplotlib来实现。
准备中
与numpy一样,matplotlib应该在安装 anaconda3 Python 包时自动可用。不过,如果由于某种原因matplotlib不可用,可以通过以下命令在终端安装:

pip install或sudo pip install将确认所需的库已满足安装要求。
如何操作…
本节介绍了通过散点图可视化数组的步骤。
- 导入
matplotlib库,并使用以下脚本配置该库以在 Jupyter 笔记本中可视化图形:
import matplotlib.pyplot as plt
%matplotlib inline
- 接下来,使用
numpy的min()和max()函数确定散点图的x轴和 y 轴的最小值和最大值,如以下脚本所示:
min_x = data_array.min(axis=0)[0]-10
max_x = data_array.max(axis=0)[0]+10
min_y = data_array.min(axis=0)[1]-10
max_y = data_array.max(axis=0)[1]+10
- 执行以下脚本以绘制每个
性别的身高和体重:
# formatting the plot grid, scales, and figure size
plt.figure(figsize=(9, 4), dpi= 75)
plt.axis([min_x,max_x,min_y,max_y])
plt.grid()
for i in range(len(data_array)):
value = data_array[i]
# assign labels values to specific matrix elements
gender = value[2]
height = value[0]
weight = value[1]
# filter data points by gender
a = plt.scatter(height[gender==0],weight[gender==0], marker
= 'x', c= 'b', label = 'Female')
b = plt.scatter(height[gender==1],weight[gender==1], marker
= 'o', c= 'b', label = 'Male')
# plot values, title, legend, x and y axis
plt.title('Weight vs Height by Gender')
plt.xlabel('Height (in)')
plt.ylabel('Weight (lbs)')
plt.legend(handles=[a,b])
它是如何工作的…
本节解释了如何将数组绘制为散点图:
-
matplotlib库已导入 Jupyter 笔记本,并配置为在 Jupyter 笔记本的单元格中内联绘制可视化图表 -
确定 x 轴和 y 轴的最小值和最大值,以便调整我们的图表大小并为我们提供最佳的图形外观。脚本的输出可以在以下截图中看到:

-
每个轴上添加了
10个点的像素缓冲区,以确保所有数据点都被捕获而不会被裁剪。 -
创建一个循环,遍历每一行的值,并绘制
体重与身高的关系。 -
此外,为
女性分配了不同的样式点,x,而为男性分配了o。 -
绘制体重与身高按性别分类的脚本输出可以在以下截图中看到:

还有更多…
散点图提供了一种快速且直观的方式,帮助理解数据的情况。散点图的右上象限和左下象限之间存在明显的分界。所有高于 140 磅的数据点表示男性,所有低于该值的数据点则表示女性,如下图所示:

这个散点图有助于确认当我们选择一个随机的身高和体重来预测性别时,后续在本章中创建神经网络的结果应该是什么。
另请参见
要了解更多关于matplotlib的信息,请访问以下网站:
设置神经网络输入的权重和偏差
PySpark 框架和数据现在已经准备好了。接下来是构建神经网络。无论神经网络的复杂性如何,开发过程都会遵循类似的路径:
-
输入数据
-
添加权重和偏差
-
求数据与权重的乘积之和
-
应用激活函数
-
评估输出并与期望的结果进行比较
本节将重点介绍设置权重,这些权重将作为输入并传递到激活函数中。
准备工作
对简单神经网络构建模块的初步理解,有助于理解本节及本章其余部分的内容。每个神经网络都有输入和输出。在我们的案例中,输入是个体的身高和体重,输出是性别。为了获得输出,输入会与值(也称为权重:w1 和 w2)相乘,然后加上偏差(b)。这个方程式被称为求和函数 z,其形式如下:
z = (input1) x (w1) + (input2) x (w2) + b
权重和偏差最初只是随机生成的值,可以通过numpy来进行操作。权重实际上会通过增加或减少其对输出的影响来“加重”输入。偏差则起着稍微不同的作用,它会根据预测需要,将求和(z)的基线向上或向下移动。每个 z 的值随后会通过激活函数转化为一个在 0 到 1 之间的预测值。激活函数是一个转换器,它为我们提供一个可以转换为二进制输出(男性/女性)的值。然后,预测输出将与实际输出进行比较。最初,预测输出和实际输出之间的差异会很大,因为在开始时权重是随机的。然而,使用一种被称为反向传播的过程,可以通过梯度下降技术最小化实际输出与预测输出之间的差异。一旦我们得到一个微不足道的差异,便会保存 w1、w2 和 b 的值,以供神经网络使用。
如何实现……
本节讲解了设置神经网络的权重和偏置的步骤。
- 使用以下脚本设置值生成器的随机性:
np.random.seed(12345)
- 使用以下脚本设置权重和偏置:
w1 = np.random.randn()
w2 = np.random.randn()
b= np.random.randn()
它是如何工作的…
本节解释了如何初始化权重和偏置,以便在本章后面的部分使用:
-
权重是通过
numpy随机生成的,并设置了随机种子,以确保每次生成相同的随机数 -
权重将被赋予通用变量
w1和w2 -
偏置同样是通过
numpy随机生成的,并设置了随机种子,以确保每次生成相同的随机数 -
偏置将被赋予一个通用变量
b -
这些值被插入到一个求和函数
z中,生成一个初始得分,该得分将输入到另一个函数——激活函数,稍后将在本章中讨论 -
目前,所有三个变量都是完全随机的。
w1、w2和b的输出可以在以下截图中看到:

还有更多…
最终目标是获得与实际输出匹配的预测输出。求和权重和数值的乘积有助于实现这一过程的一部分。因此,一个随机输入0.5和0.5的求和输出为:
z = 0.5 * w1 + 0.5 * w2 + b
或者,使用当前随机的权重值w1和w2,将得到以下输出:
z = 0.5 * (-0.2047) + 0.5 * (0.47894) + (-0.51943) = -7.557
变量z被赋值为权重与数据点的乘积和。目前,权重和偏置是完全随机的。然而,正如本节前面提到的,通过一种叫做反向传播*的过程,使用梯度下降法,权重将被调整,直到得到一个更理想的结果。梯度下降法就是识别出最优权重值的过程,这样我们就能以最小的误差得到最佳的预测结果。识别最优值的过程涉及到寻找函数的局部最小值。梯度下降法将在本章稍后部分进行讨论。
另见
若要了解更多关于人工神经网络中权重和偏置的信息,请访问以下网站:
en.wikipedia.org/wiki/Artificial_neuron
对神经网络的输入数据进行标准化
当输入被标准化时,神经网络的工作效率更高。这可以最小化某一特定输入对整体结果的影响,而不会让其他数值较低的输入影响过大。本节将标准化当前个体的height和weight输入。
准备就绪
输入值的标准化需要获得这些值的均值和标准差,进行最终计算。
如何操作…
本节讲解了标准化身高和体重的步骤。
- 使用以下脚本将数组切片为输入和输出:
X = data_array[:,:2]
y = data_array[:,2]
- 通过以下脚本可以计算 29 个个体的均值和标准差:
x_mean = X.mean(axis=0)
x_std = X.std(axis=0)
- 创建一个标准化函数,使用以下脚本对
X进行标准化:
def normalize(X):
x_mean = X.mean(axis=0)
x_std = X.std(axis=0)
X = (X - X.mean(axis=0))/X.std(axis=0)
return X
它是如何工作的...
本节解释了如何对身高和体重进行标准化。
-
data_array矩阵被分割成两个矩阵:-
X由身高和体重组成 -
y由性别组成
-
-
两个数组的输出可以在以下截图中看到:

X组件是输入,是唯一需要进行标准化处理的部分。*y*组件,即性别,目前将被忽略。标准化过程涉及提取所有 29 个个体身高和体重的均值和标准差。身高和体重的均值与标准差的输出可以在以下截图中看到:

-
身高的均值约为 67 英寸,身高的标准差约为 3.4 英寸。体重的均值约为 145 磅,体重的标准差约为 22 磅。
-
一旦提取出来,输入值将使用以下公式进行标准化:
X_norm = (X - X_mean)/X_std。 -
X数组通过 Python 函数normalize()进行标准化,X数组现在被分配为新标准化数据集的值,如下截图所示:

参见
如果想要了解更多关于统计学中的标准化,访问以下网站:
en.wikipedia.org/wiki/Normalization_(statistics)
验证数组以确保神经网络的最佳性能
一些简单的验证可以确保我们的数组在接下来的神经网络中获得最佳性能。
准备工作
本节将使用numpy的numpy.stack()函数来进行一些魔法操作。
如何操作...
以下步骤将展示如何验证我们的数组已经标准化。
- 执行以下步骤,打印数组输入的均值和标准差:
print('standard deviation')
print(round(X[:,0].std(axis=0),0))
print('mean')
print(round(X[:,0].mean(axis=0),0))
- 执行以下脚本,将身高、体重和性别组合成一个数组,
data_array:
data_array = np.column_stack((X[:,0], X[:,1],y))
它是如何工作的...
本节解释了如何验证和构建数组,以便在神经网络中得到最佳的未来使用。
- 身高的新的
均值应该是 0,标准差应该是 1。可以在以下截图中看到:

-
这是标准化数据集的确认,因为它包含均值为 0,标准差为 1。
-
原始的
data_array对神经网络不再有用,因为它包含了身高、体重和性别的原始、未标准化的输入值。 -
然而,通过一点点
numpy的魔法,data_array可以被重构以包含归一化后的height和weight,以及gender。这可以通过numpy.stack()来实现。新数组data_array的输出可以在下面的截图中看到:

还有更多内容...
我们的数组现在已经准备好了。我们的身高和体重输入已被归一化,性别输出标记为 0 或 1。
另见
要了解更多关于numpy.stack()的信息,请访问以下网址:
docs.scipy.org/doc/numpy/reference/generated/numpy.stack.html
使用 sigmoid 设置激活函数
激活函数在神经网络中用于帮助确定输出,无论是“是”还是“否”,真假,或者在我们的例子中是 0 还是 1(男性/女性)。此时,输入已被归一化,并与权重和偏差:w1、w2和b相加。然而,当前这些权重和偏差是完全随机的,尚未优化以产生与实际输出相匹配的预测结果。构建预测结果的缺失环节就在于激活函数或sigmoid函数,如下图所示:

如果通过求和产生的数字非常小,它将产生一个 0 的激活。同样,如果求和产生的数字非常大,它将产生一个 1 的激活。这个函数非常有用,因为它将输出限制为二进制结果,这对于分类非常有用。这些输出的后果将在本章的其余部分中讨论和阐明。
准备工作
sigmoid函数类似于逻辑回归函数,因为它计算 0 到 1 之间的概率结果。此外,它给出了中间范围的所有值。因此,可以设置一个条件,将任何大于 0.5 的值映射为 1,任何小于 0.5 的值映射为 0。
如何实现...
本节介绍了如何使用样本数据创建和绘制 sigmoid 函数的步骤。
- 使用以下脚本创建
sigmoid函数,如下所示:
def sigmoid(input):
return 1/(1+np.exp(-input))
- 使用以下脚本为
sigmoid曲线创建样本x值:
X = np.arange(-10,10,1)
- 此外,使用以下脚本为
sigmoid曲线创建样本y值:
Y = sigmoid(X)
- 使用以下脚本绘制这些点的
x和y值:
plt.figure(figsize=(6, 4), dpi= 75)
plt.axis([-10,10,-0.25,1.2])
plt.grid()
plt.plot(X,Y)
plt.title('Sigmoid Function')
plt.show()
它是如何工作的...
本节解释了 sigmoid 函数背后的数学原理。
sigmoid函数是用于分类的逻辑回归的专用版本。逻辑回归的计算通过以下公式表示:

-
逻辑回归函数的变量表示如下:
-
L表示函数的最大值
-
k 代表曲线的陡度
-
x[midpoint] 代表函数的中点值
-
-
由于
sigmoid函数具有斜率为 1、中点为 0、最大值为 1,它产生了如下函数:

- 我们可以绘制一个一般的 sigmoid 函数,x 值范围从-5 到 5,y 值范围从 0 到 1,如下图所示:

- 我们使用 Python 创建了自己的
sigmoid函数,并使用-10到10之间的样本数据将其绘制出来。我们的图表与之前的常见 sigmoid 图表非常相似。我们sigmoid函数的输出可以在以下截图中看到:

另见
要了解更多关于sigmoid函数的来源,请访问以下网站:
en.wikipedia.org/wiki/Sigmoid_function
创建 sigmoid 导数函数
Sigmoid 函数是一个独特的函数,其中 sigmoid 函数的导数值包括 sigmoid 函数的值。你可能会问,这有什么大不了的?然而,由于 sigmoid 函数已经被计算出来,它可以在进行多层反向传播时简化和提高处理效率。此外,正是 sigmoid 函数的导数在计算中被用来推导出最佳的w1、w2和b值,以得出最准确的预测输出。
准备工作
从微积分中对导数的初步理解将有助于理解 sigmoid 导数函数。
如何操作...
本节将讲解创建 sigmoid 导数函数的步骤。
- 就像
sigmoid函数一样,可以使用以下 Python 脚本创建sigmoid函数的导数:
def sigmoid_derivative(x):
return sigmoid(x) * (1-sigmoid(x))
- 使用以下脚本将
sigmoid函数的导数与原始sigmoid函数并排绘制:
plt.figure(figsize=(6, 4), dpi= 75)
plt.axis([-10,10,-0.25,1.2])
plt.grid()
X = np.arange(-10,10,1)
Y = sigmoid(X)
Y_Prime = sigmoid_derivative(X)
c=plt.plot(X, Y, label="Sigmoid",c='b')
d=plt.plot(X, Y_Prime, marker=".", label="Sigmoid Derivative", c='b')
plt.title('Sigmoid vs Sigmoid Derivative')
plt.xlabel('X')
plt.ylabel('Y')
plt.legend()
plt.show()
它是如何工作的...
本节解释了 sigmoid 函数的导数背后的数学原理,以及如何使用 Python 创建 sigmoid 函数的导数的逻辑。
- 神经网络将需要
sigmoid函数的导数,以便为gender预测一个准确的输出。sigmoid函数的导数是通过以下公式计算的:

- 然后我们可以使用原始的 sigmoid 函数
sigmoid()在 Python 中创建 sigmoid 函数的导数sigmoid_derivate()。我们可以将这两个函数并排绘制,如下图所示:

- Sigmoid 导数跟踪原始 Sigmoid 函数的斜率。在图表的早期,当 Sigmoid 的斜率完全水平时,Sigmoid 导数也是 0.0。当 Sigmoid 的值接近 1 时,斜率几乎完全水平,Sigmoid 导数的值也保持为 0.0。Sigmoid 斜率的峰值出现在 x 轴的中点。因此,这也是 Sigmoid 导数的峰值。
另见
要深入了解导数,请访问以下网站:
www.khanacademy.org/math/calculus-home/taking-derivatives-calc
在神经网络中计算成本函数
到此时,是时候将本章早些时候突出显示的所有部分结合起来计算成本函数,该函数将被神经网络用来确定给定当前可用的 29 个数据点,预测结果与原始或实际结果的匹配程度。成本函数的目的是识别实际值和预测值之间的差异。然后使用梯度下降来增减w1、w2 和 b 的值,以减少成本函数的值,并最终实现我们的目标,即推导出一个与实际值相匹配的预测值。
准备开始
成本函数的公式如下:
cost(x) = (predicted - actual)²
如果成本函数看起来很熟悉,那是因为它实际上只是最小化实际输出和预测值之间平方差的另一种方式。梯度下降或反向传播在神经网络中的目的是最小化成本函数,直到其值接近 0。此时,权重和偏差(w1、w2 和 b)将不再是由numpy生成的随机不重要值,而是对神经网络模型有实际贡献的显著权重。
如何做...
本节介绍计算成本函数的步骤。
- 设置学习率值为
0.1,逐步改变权重和偏差,直到使用以下脚本选择最终输出:
learningRate = 0.1
- 使用以下脚本初始化一个名为
allCosts的 Python 列表。
allCosts = []
- 创建一个
for循环,使用以下脚本遍历 100,000 个场景:

- 使用以下脚本绘制在 100,000 次迭代中收集的成本值:
plt.plot(all_costs)
plt.title('Cost Value over 100,000 iterations')
plt.xlabel('Iteration')
plt.ylabel('Cost Value')
plt.show()
- 可以使用以下脚本查看权重和偏差的最终值:
print('The final values of w1, w2, and b')
print('---------------------------------')
print('w1 = {}'.format(w1))
print('w2 = {}'.format(w2))
print('b = {}'.format(b))
工作原理...
本节解释了成本函数如何用于生成权重和偏差。
-
将实现一个
for循环,对权重和偏差进行梯度下降,调整值直到成本函数接近 0。 -
循环将遍历成本函数 100,000 次。每次,从 29 个个体中随机选择一个
height和weight的值。 -
从随机的
height和weight计算得出求和值z,然后输入用于计算通过sigmoid函数得出的predictedGender得分。 -
计算成本函数并将其添加到一个列表中,该列表追踪 100,000 次迭代中的所有成本函数,名为
allCosts。 -
计算一系列关于求和值(
z)以及cost函数(cost)的偏导数。 -
这些计算最终用于更新权重和偏置,以使它们(
w1、w2和b)在 100,000 次迭代过程中,使得成本函数的值接近 0。 -
最终目标是随着迭代次数的增加,成本函数的值逐渐减小。可以在以下截图中看到 100,000 次迭代中成本函数值的输出:

-
在迭代过程中,成本值从约 0.45 下降到约 0.01。
-
此外,我们可以查看生成最小成本函数值的
w1、w2和b的最终输出,截图如下:

还有更多...
现在可以测试权重和偏置的最终值,以计算成本函数如何有效计算预测值,并与实际得分进行比较。
以下脚本将循环遍历每个个体,并基于权重(w1、w2)和偏置(b)计算预测性别得分:
for i in range(len(data_array)):
random_individual = data_array[i]
height = random_individual[0]
weight = random_individual[1]
z = height*w1 + weight*w2 + b
predictedGender=sigmoid(z)
print("Individual #{} actual score: {} predicted score: {}".format(i+1,random_individual[2],predictedGender))
可以在以下截图中看到脚本的输出:

所有 29 个实际得分在四舍五入后大致与预测得分相匹配。虽然这对于确认模型在训练数据上产生了匹配结果是有益的,但最终的测试将是确定模型是否能对新引入的个体进行准确的性别预测。
参见
要了解更多关于使用梯度下降法最小化成本函数或平方(差异)误差函数的信息,请访问以下网站:
en.wikipedia.org/wiki/Gradient_descent
基于身高和体重预测性别
预测模型只有在能够基于新信息进行预测时才有用。这对于简单的逻辑回归或线性回归,或更复杂的神经网络模型都是适用的。
准备开始
现在有趣的部分开始了。此部分的唯一要求是提取男性和女性个体的样本数据点,并使用他们的身高和体重值来衡量在前一部分中创建的模型的准确性。
如何进行...
本节演示了如何根据身高和体重预测性别的步骤。
- 创建一个名为
input_normalize的 Python 函数,用于输入新的身高和体重值,并输出一个归一化的身高和体重,如以下脚本所示:
def input_normalize(height, weight):
inputHeight = (height - x_mean[0])/x_std[0]
inputWeight = (weight - x_mean[1])/x_std[1]
return inputHeight, inputWeight
- 为身高
70英寸和体重180磅的值分配一个名为score的变量, 如以下脚本所示:
score = input_normalize(70, 180)
- 创建另一个名为
predict_gender的 Python 函数,通过对w1、w2和b进行求和并应用sigmoid函数,输出一个介于 0 和 1 之间的概率分数gender_score以及性别描述,如以下脚本所示:
def predict_gender(raw_score):
gender_summation = raw_score[0]*w1 + raw_score[1]*w2 + b
gender_score = sigmoid(gender_summation)
if gender_score <= 0.5:
gender = 'Female'
else:
gender = 'Male'
return gender, gender_score
它是如何工作的...
本节解释了如何利用身高和体重的新输入生成性别的预测分数。
-
创建了一个函数用于输入新的身高和体重值,并将实际值转换为归一化的身高和体重值,分别称为
inputHeight和inputWeight。 -
一个变量
score用于存储归一化的值,另一个函数predictGender被创建用来输入得分值,并根据上一节中创建的w1、w2和b的值输出一个性别分数和描述。这些值已经通过梯度下降法进行预调整,以调整值并最小化cost函数。 -
将
score值应用于predict_gender函数应该能显示性别描述和分数,如下图所示:

-
显示
70英寸身高和180磅体重的规格是男性的高预测值(99.999%)。 -
对
身高为50英寸和体重为150磅的测试可能会揭示出不同的性别,如下图所示:

- 类似地,该输入从
sigmoid函数输出了一个非常低的分数(0.00000000839),表明这些特征与女性性别紧密相关。
另见
为了了解更多关于测试、训练和验证数据集的信息,请访问以下网站:
en.wikipedia.org/wiki/Training,_test,_and_validation_sets
可视化预测分数
虽然我们可以根据某个人的身高和体重单独预测性别,但可以使用每个数据点绘制整个数据集的图形并评分,以确定输出是预测为女性还是男性。
准备工作
本节不需要任何依赖。
如何操作...
本节将介绍如何将所有预测点可视化在图表中。
- 使用以下脚本计算图形的最小值和最大值:
x_min = min(data_array[:,0])-0.1
x_max = max(data_array[:,0])+0.1
y_min = min(data_array[:,1])-0.1
y_max = max(data_array[:,1])+0.1
increment= 0.05
print(x_min, x_max, y_min, y_max)
- 生成x和y值,步长为 0.05 个单位,然后创建一个名为
xy_data的数组,如以下脚本所示:
x_data= np.arange(x_min, x_max, increment)
y_data= np.arange(y_min, y_max, increment)
xy_data = [[x_all, y_all] for x_all in x_data for y_all in y_data]
- 最后,使用与本章早些时候使用的类似脚本来生成性别得分并填充图表,如下脚本所示:
for i in range(len(xy_data)):
data = (xy_data[i])
height = data[0]
weight = data[1]
z_new = height*w1 + weight*w2 + b
predictedGender_new=sigmoid(z_new)
# print(height, weight, predictedGender_new)
ax = plt.scatter(height[predictedGender_new<=0.5],
weight[predictedGender_new<=0.5],
marker = 'o', c= 'r', label = 'Female')
bx = plt.scatter(height[predictedGender_new > 0.5],
weight[predictedGender_new>0.5],
marker = 'o', c= 'b', label = 'Male')
# plot values, title, legend, x and y axis
plt.title('Weight vs Height by Gender')
plt.xlabel('Height (in)')
plt.ylabel('Weight (lbs)')
plt.legend(handles=[ax,bx])
它是如何工作的…
本节解释了如何创建数据点,以生成将在图表中绘制的预测值。
- 图表的最小值和最大值是基于数组值计算的。脚本的输出可以在以下截图中看到:

- 我们为每个数据点生成 x 和 y 值,范围在最小值和最大值之间,以 0.05 的增量进行计算,然后将每个(x, y)点输入到预测得分中以绘制值。女性性别得分被分配为红色,而男性性别得分被分配为蓝色,如下截图所示:

- 图表显示了根据所选的
height和weight,性别得分之间的分界线。
第三章:卷积神经网络的痛点
本章将介绍以下内容:
-
痛点 #1:导入 MNIST 图像
-
痛点 #2:可视化 MNIST 图像
-
痛点 #3:将 MNIST 图像导出为文件
-
痛点 #4:增强 MNIST 图像
-
痛点 #5:利用其他来源的训练图像
-
痛点 #6:优先考虑 CNN 的高级库
介绍
卷积神经网络(CNNs)在过去几年里有了一定的复兴。它们在图像识别方面取得了巨大的成功。随着现代智能手机的普及,这一点变得尤其相关,因为现在任何人都可以轻松拍摄大量物体照片并将其发布在社交媒体网站上。正是因为这一现象,卷积神经网络现在需求量很大。
有几个特性可以使 CNN 表现最佳。它们需要以下特性:
-
大量的训练数据
-
视觉和空间数据
-
强调过滤(池化)、激活和卷积,而不是传统神经网络中更明显的全连接层。
虽然 CNNs 获得了很大的流行,但在使用时仍然存在一些限制,主要是由于它们的计算需求以及获得良好性能模型所需的训练数据量。我们将专注于可以应用于数据的技术,这些技术将有助于卷积神经网络的开发,同时解决这些限制。在后续章节中,我们将在开发图像分类模型时应用其中一些技术。
痛点 #1:导入 MNIST 图像
用于图像分类的最常见数据集之一是MNIST数据集,它包含了成千上万的手写数字样本。美国国家标准与技术研究院(MNIST)根据 Yann LeCun、Corinna Cortes 和 Christopher J.C. Burges 的说法,具有以下几个优点:
对于那些希望在真实世界数据上尝试学习技术和模式识别方法,同时在预处理和格式化方面花费最少精力的人来说,这是一个很好的数据库。
有几种方法可以将 MNIST 图像导入到我们的 Jupyter 笔记本中。我们将在本章中介绍以下两种方法:
-
通过 TensorFlow 库直接导入
-
通过 MNIST 网站手动操作
需要注意的一点是,我们将主要使用 MNIST 图像作为改进卷积神经网络性能的示例。所有应用于 MNIST 图像的这些技术,也可以应用于任何用于训练 CNN 的图像。
准备工作
唯一的要求是安装TensorFlow。它可能不会随 anaconda3 包预安装;因此,通过简单的pip安装可以确认TensorFlow的可用性,或者在未安装时进行安装。TensorFlow可以在终端轻松安装,如下图所示:

如何操作……
TensorFlow库内置了一组可以直接使用的示例。这些示例数据集之一是MNIST。本节将演示如何访问这些图像。
- 使用以下脚本将
TensorFlow导入库,并指定别名为tf:
import tensorflow as tf
- 使用以下脚本从库中下载并提取图像,并保存到本地文件夹:
from tensorflow.examples.tutorials.mnist import input_data
data = input_data.read_data_sets('MNIST/', one_hot=True)
- 使用以下脚本检索用于评估图像分类准确性的训练和测试数据集的最终计数:
print('Image Inventory')
print('----------')
print('Training: ' + str(len(data.train.labels)))
print('Testing: '+ str(len(data.test.labels)))
print('----------')
它是如何工作的……
本节解释了用于访问 MNIST 数据集的过程:
-
一旦我们收到
TensorFlow库已正确安装的确认,就可以将其导入到笔记本中。 -
我们可以确认
TensorFlow的版本,并将图像提取到本地的MNIST/文件夹中。提取过程会显示在笔记本的输出中,如下图所示:

-
提取出的四个文件命名如下:
-
t10k-images-idx3-ubyte.gz -
t10k-labels-idx1-ubyte.gz -
train-images-idx3-ubyte.gz -
train-labels-idx1-ubyte.gz
-
-
它们已被下载到
MNIST/子文件夹中,如下图所示:

- 此外,四个文件也可以在我们的笔记本中查看,如下图所示:

-
这四个文件是测试和训练图像以及随附的测试和训练标签,用于标识测试和训练数据集中的每张图像。此外,明确地定义了
one_hot = True特性。这表明标签使用了独热编码,这有助于在建模过程中进行特征选择,因为每一列的值要么是 0,要么是 1。 -
库的一个子类也被导入,用于将 MNIST 的手写图像存储到指定的本地文件夹中。包含所有图像的文件夹大小约为 12MB,包含 55,000 张训练图像和 10,000 张测试图像,如下图所示:

- 这 10,000 张图像将用于测试我们模型的准确性,该模型将在 55,000 张图像上进行训练。
还有更多……
有时,在尝试通过TensorFlow直接访问 MNIST 数据集时,可能会出现错误或警告。正如前面在本节中所看到的,当导入 MNIST 时,我们收到了以下警告:
WARNING:tensorflow:From
更新说明:
请使用 tensorflow/models 中的官方/mnist/dataset.py 等替代方案。
该数据集可能会在未来的 TensorFlow 版本中被弃用,因此不再直接可用。有时我们在通过 TensorFlow 提取 MNIST 图像时可能会遇到典型的 HTTP 403 错误,这可能是由于网站暂时不可用所致。无论哪种情况,都不必担心,有一种手动方法可以通过以下链接下载四个 .gz 文件:
文件位于网站上,如下图所示:

下载文件并将其保存在可访问的本地文件夹中,类似于直接从 TensorFlow 获取的文件。
另见
要了解更多关于 MNIST 手写数字数据库的信息,请访问以下网站:yann.lecun.com/exdb/mnist/。
要了解更多关于独热编码的信息,请访问以下网站:hackernoon.com/what-is-one-hot-encoding-why-and-when-do-you-have-to-use-it-e3c6186d008f.
痛点 #2:可视化 MNIST 图像
在 Jupyter Notebook 中处理图形时,绘制图像通常是一个主要的痛点。显示来自训练数据集的手写图像至关重要,特别是在比较与手写图像相关联的标签的实际值时。
准备工作
可视化手写图像时,唯一需要导入的 Python 库是 numpy 和 matplotlib。这两个库应该已经通过 Anaconda 中的包可用。如果由于某种原因它们不可用,可以通过以下命令在终端使用 pip 安装:
-
pip install matplotlib -
pip install numpy
如何做...
本节将逐步演示如何在 Jupyter Notebook 中可视化 MNIST 手写图像:
- 导入以下库
numpy和matplotlib,并使用以下脚本配置matplotlib以inline方式绘制图像:
import numpy as np
import matplotlib.pyplot as plt
%matplotlib inline
- 使用以下脚本绘制前两张示例图像:
for i in range(2):
image = data.train.images[i]
image = np.array(image, dtype='float')
label = data.train.labels[i]
pixels = image.reshape((28, 28))
plt.imshow(pixels, cmap='gray')
print('-----------------')
print(label)
plt.show()
它是如何工作的...
本节将讲解如何在 Jupyter Notebook 中查看 MNIST 手写图像的过程:
-
在 Python 中生成一个循环,将从训练数据集中抽取两张图像。
-
最初,图像仅是介于 0 和 1 之间的浮动格式值,存储在
numpy数组中。数组的值是一个带标签的图像,称为image。然后,image数组被重塑为一个 28 x 28 的矩阵,称为pixels,其中任何值为 0 的位置为黑色,任何非 0 的颜色位置则为灰色。值越高,灰色的阴影颜色就越浅。下图展示了数字 8 的一个例子:

- 循环的输出生成了两个手写图像,分别为数字 7 和 3,以及它们的标签,如下图所示:

- 除了绘制图像之外,训练数据集中的标签也会显示在图像上方。标签是一个长度为 10 的数组,所有 10 个数字的值仅为 0 或 1。对于数字 7,数组中的第 8 个元素的值为 1;对于数字 3,数组中的第 4 个元素的值为 1。其他所有值均为 0。
还有更多...
图像的数值可能并不立即显现出来。虽然大多数人能够识别出第一张图像是数字 7,第二张图像是数字 3,但从标签数组中确认这一点会更有帮助。
数组中有 10 个元素,每个元素表示标签 0 至 9 的值,按数字顺序排列。由于第一个数组在第 8 个位置具有正值或 1,这表示图像的值是 7,因为 7 在数组中的第 8 个索引位置。其他所有值应为 0。此外,第二张图像在第 4 个位置的值为 1,表示图像值为 3。
另见
Leun、Cortes 和 Burges 在以下声明中讨论了为什么图像像素设置为 28 x 28:
来自 NIST 的原始黑白(二值)图像经过大小标准化,适应于一个 20x20 像素的框,同时保持其纵横比。标准化算法使用抗锯齿技术,使得图像包含灰度级。图像通过计算像素的质心,然后平移图像,使该点位于 28x28 区域的中心,从而将图像居中。
--Leun、Cortes 和 Burges 来自 yann.lecun.com/exdb/mnist/.
痛点 #3:将 MNIST 图像导出为文件
我们通常需要直接在图像上工作,而不是作为数组向量。本节将引导我们将数组转换为 .png 图像。
准备工作
导出向量为图像需要导入以下库:
import image from matplotlib
如何操作...
本节将介绍如何将 MNIST 数组样本转换为本地文件夹中的文件。
- 使用以下脚本创建一个子文件夹,将图像保存到
MNIST/的主文件夹中:
if not os.path.exists('MNIST/images'):
os.makedirs('MNIST/images/')
os.chdir('MNIST/images/')
- 遍历 MNIST 数组的前 10 个样本,并使用以下脚本将它们转换为
.png文件:
from matplotlib import image
for i in range(1,10):
png = data.train.images[i]
png = np.array(png, dtype='float')
pixels = png.reshape((28, 28))
image.imsave('image_no_{}.png'.format(i), pixels, cmap = 'gray')
- 执行以下脚本查看从
image_no_1.png到image_no_9.png的图像列表:
print(os.listdir())
它是如何工作的…
本节解释了如何将 MNIST 数组转换为图像,并保存到本地文件夹。
-
我们创建一个名为
MNIST/images的子文件夹,以帮助存储临时的.png图像,并将它们与 MNIST 数组和标签分开存储。 -
我们再次遍历
data.train中的图像,获取九个可以用于采样的数组。然后,这些图像会以.png文件格式保存到本地目录,格式为:'image_no_{}.png'.format(i), pixels, cmap = 'gray' -
我们可以在本地目录中看到九张图像的输出,正如以下截图所示:

还有更多…
除了查看目录中的图像列表,我们还可以在 Linux 中查看我们目录中的图像,正如以下截图所示:

参见
要了解更多关于matplotlib中的image.imsave,请访问以下网站:
matplotlib.org/api/_as_gen/matplotlib.pyplot.imsave.html
痛点 #4:增强 MNIST 图像
使用图像识别的主要缺点之一是一些可用图像的多样性不足。这可能导致卷积神经网络无法像我们希望的那样优化运行,且由于训练数据的多样性不足,结果可能不理想。针对这个缺点,有一些技术可以绕过,我们将在本节中讨论其中的一种。
准备开始
再次说明,大部分繁重的工作已经为我们完成。我们将使用一个流行的 Python 包augmentor,它经常用于机器学习和深度学习建模,用于生成现有图像的扭曲和增强版本,以增加图像的多样性。
首先,必须通过以下脚本使用pip安装该包:pip install augmentor
然后我们应该确认包已经安装,正如下面的截图所示:

然后我们需要从 augmentor 导入管道类:
from Augmentor import Pipeline
如何做...
本节将介绍如何增加我们的九张样本图像的频率和增强效果。
- 使用以下脚本初始化
augmentor函数:
from Augmentor import Pipeline
augmentor = Pipeline('/home/asherif844/sparkNotebooks/Ch03/MNIST/images')
- 执行以下脚本,使得
augmentor函数可以按以下规格旋转我们的图像:
augmentor.rotate(probability=0.9, max_left_rotation=25, max_right_rotation=25)
- 执行以下脚本,使得每张图像通过两个迭代进行增强,每次迭代 10 次:
for i in range(1,3):
augmentor.sample(10)
它是如何工作的…
本节将解释如何利用我们的九张图像生成额外的扭曲图像。
-
我们需要为图像转换创建一个
Pipeline,并指定将要使用的图像位置。这可以确保以下内容:-
图像的源位置
-
将要转换的图像数量
-
图像的目标位置
-
-
我们可以看到,我们的目标位置是通过一个名为
/output/的子文件夹创建的,如下图所示:

-
augmentor函数被配置为将每张图像旋转最多 25 度向右或 25 度向左,概率为 90%。基本上,概率配置决定了增强操作发生的频率。 -
创建了一个循环,通过每张图像两次,并对每张图像应用两次变换;然而,由于我们确实给每个变换添加了概率,因此某些图像可能不会被转换,而其他图像可能会被转换超过两次。变换完成后,我们应该会收到一条消息,指示变换已完成,如下图所示:

- 一旦完成增强操作,我们可以访问
/output/子目录,查看每个数字是如何略微变化的,如下图所示:

- 我们可以看到,数字 3、1、8、0 和 9 的几种变体,它们的旋转角度各不相同。我们现在已经三倍增加了我们的样本数据集,并增加了更多的多样性,而不需要去提取更多的图像用于训练和测试。
还有更多...
我们只应用了rotate变换;然而,实际上有多种变换和增强功能可以应用到图像上:
-
透视歪斜
-
弹性失真
-
剪切
-
裁剪
-
镜像
并非所有这些变换在增加训练数据集的频率和多样性时都必要,但使用某些功能的组合并评估模型性能可能是有益的。
参见
要了解更多关于augmentor的信息,请访问以下网站:
augmentor.readthedocs.io/en/master/
痛点#5:利用其他来源的训练图像
有时候,进行卷积神经网络操作时可能没有足够的资源。资源可能在计算角度或数据收集角度上受到限制。在这种情况下,我们依赖其他来源来帮助我们对图像进行分类。
准备工作
利用预训练模型作为测试其他数据集结果来源的技术被称为迁移学习。其优势在于,大部分用于训练图像的 CPU 资源被外包给预训练模型。最近,迁移学习已成为深度学习的一个常见扩展。
如何操作...
本节解释了迁移学习的工作原理。
-
收集一系列你感兴趣分类的数据集或图像,就像你在传统机器学习或深度学习中做的那样。
-
将数据集分成训练集和测试集,例如 75/25 或 80/20。
-
选择一个预训练模型来识别你希望分类的图像的模式和特征。
-
构建一个深度学习管道,将训练数据与预训练模型连接,并生成识别测试数据所需的权重和参数。
-
最后,评估模型在测试数据上的表现。
它是如何工作的…
本节解释了将迁移学习应用于 MNIST 数据集的过程。
-
我们在使用迁移学习时无疑是在走捷径,因为我们可能在资源、时间或两者都有限的情况下,利用已经完成的前期工作,并希望它能帮助我们解决新的问题。
-
由于我们面临的是图像分类问题,应该使用一个曾经处理过常见图像分类的预训练模型。市面上有许多常见的模型,但有两个特别突出:
-
微软开发的 ResNet 模型。
-
谷歌开发的 Inception 模型。
-
-
两种模型对于图像分类都很有用,因为微软和谷歌都有广泛的图像资源,可以用来训练一个强大的模型,从而在更详细的层面上提取特征。
-
在 Spark 中,你可以构建一个深度学习管道,并调用一个名为
DeepImageFeaturizer的类,应用InceptionV3模型到从训练数据中收集的一组特征。然后,使用某种二分类或多分类评估器在测试数据上评估训练数据集。 -
深度学习或机器学习中的管道是指从数据收集的初始环境到应用模型对收集的数据进行最终评估或分类的环境之间的工作流过程。
还有更多…
像所有事物一样,使用迁移学习也有利有弊。正如我们在本节前面讨论的,迁移学习非常适合在你资源有限,无法对大数据集进行建模时使用。也有可能源数据在预训练模型中没有展示出许多独特的特征,从而导致模型性能不佳。你总是可以选择将一个预训练模型换成另一个,并重新评估模型的性能。同样,迁移学习是一种“快速失败”的方法,当其他选择不可用时可以使用。
另请参见
想了解更多关于微软 ResNet 的信息,请访问以下网站:
想了解更多关于谷歌 Inception 的信息,请访问以下网站:
www.tensorflow.org/tutorials/image_recognition
要了解更多关于 InceptionV3 的信息,你可以阅读康奈尔大学的以下论文,标题为《重新思考计算机视觉中的 Inception 架构》:
痛点 #6:优先考虑 CNN 的高级库
有许多库可以用于执行卷积神经网络。一些被认为是低级的,如 TensorFlow,其中许多配置和设置需要大量的编码。这对于缺乏经验的开发人员来说是一个主要的痛点。还有一些库,比如 Keras,它们是建立在像 TensorFlow 这样的库之上的高级框架。这些库需要的代码更少,可以更快地开始构建卷积神经网络。许多初学者在构建神经网络时,往往会尝试使用 TensorFlow 实现一个模型,但在过程中会遇到许多问题。本节将建议首先使用 Keras 构建一个卷积神经网络,以预测 MNIST 数据集中的手写图像。
准备就绪
在本节中,我们将使用 Keras 来训练一个识别 MNIST 手写图像的模型。你可以通过在终端执行以下命令来安装 Keras:
pip install keras
如何操作...
本节将演示如何构建一个模型,以识别来自 MNIST 的手写图像。
- 使用以下脚本根据以下变量从 MNIST 数据集创建测试和训练图像及标签:
xtrain = data.train.images
ytrain = np.asarray(data.train.labels)
xtest = data.test.images
ytest = np.asarray(data.test.labels)
- 使用以下脚本重新调整测试和训练数组的形状:
xtrain = xtrain.reshape( xtrain.shape[0],28,28,1)
xtest = xtest.reshape(xtest.shape[0],28,28,1)
ytest= ytest.reshape(ytest.shape[0],10)
ytrain = ytrain.reshape(ytrain.shape[0],10)
- 从
keras导入以下内容,以构建卷积神经网络模型:
import keras
import keras.backend as K
from keras.models import Sequential
from keras.layers import Dense, Flatten, Conv2D
- 使用以下脚本设置图像排序:
K.set_image_dim_ordering('th')
- 使用以下脚本初始化
Sequential模型:
model = Sequential()
- 使用以下脚本向
model添加层:
model.add(Conv2D(32, kernel_size=(3, 3),activation='relu',
input_shape=(1,28,28)))
model.add(Flatten())
model.add(Dense(128, activation='relu'))
model.add(Dense(10, activation='sigmoid'))
- 使用以下脚本编译
model:
model.compile(optimizer='adam',loss='binary_crossentropy',
metrics=['accuracy'])
- 使用以下脚本训练
model:
model.fit(xtrain,ytrain,batch_size=512,epochs=5,
validation_data=(xtest, ytest))
- 使用以下脚本测试
model的性能:
stats = model.evaluate(xtest, ytest)
print('The accuracy rate is {}%'.format(round(stats[1],3)*100))
print('The loss rate is {}%'.format(round(stats[0],3)*100))
它是如何工作的...
本节解释了如何在 Keras 上构建卷积神经网络,以识别 MNIST 中的手写图像。
-
对于任何模型开发,我们需要识别我们的测试和训练数据集,以及特征和标签。在我们的案例中,这非常简单,因为 TensorFlow 中的 MNIST 数据已经拆分为
data.train.images作为特征,data.train.labels作为标签。此外,我们还需要将标签转换为数组,因此我们使用np.asarray()来处理ytest和ytrain。 -
目前,
xtrain、xtest、ytrain和ytest数组的形状不适合在 Keras 中用于卷积神经网络。正如我们在本章早些时候所提到的,MNIST 图像的特征表示 28x28 像素的图像,而标签表示从 0 到 9 的十个值之一。x数组将被重塑为(,28,28,1),y数组将被重塑为(,10)。新数组的shape可以通过以下截图看到:

- 如前所述,Keras 是一个高级库,因此它在没有 TensorFlow 等低级库的帮助下无法执行张量或卷积操作。为了配置这些操作,我们将
backend设置为K,表示Keras,并将图像维度排序image_dim_ordering设置为tf,以便与 TensorFlow 配合使用。
请注意,后端也可以设置为其他低级库,如 Theano。如果使用 Theano,而不是 tf,我们需要将维度排序设置为 th。此外,我们还需要重构特征的形状。然而,在过去几年中,Theano 并未获得像 TensorFlow 那样的普及度。
-
一旦我们导入了构建 CNN 模型所需的库,就可以开始构建模型的序列或层,
Sequential()。为了演示,我们将模型保持尽可能简单,仅使用 4 层,以证明即使在最小复杂度下,我们也能获得高准确率。每一层都是通过.add()方法添加的。-
第一层设置为构建一个二维(
Conv2D)卷积层,这是处理空间图像(如 MNIST 数据)时常见的做法。由于它是第一层,我们必须显式定义输入数据的input_shape。此外,我们还指定了一个kernel_size,用于设置卷积窗口的高度和宽度。通常,这个窗口是一个 3x3 或 5x5 的窗口,用于 32 个滤波器。此外,我们还需要为该层设置一个激活函数,使用修正线性单元(relu)是一个有效的选择,尤其是在神经网络的早期阶段,它有助于提高效率。 -
接下来,第二层将第一个层的输入展开,以获取一个分类结果,我们可以用来判断该图像是否属于可能的 10 个数字之一。
-
第三步,我们将第二层的输出传递给一个具有 128 个隐藏层并使用
relu激活函数的dense层。密集连接层中的函数包含了input_shape和kernel_size以及偏置项,用于为每个 128 个隐藏层生成输出。 -
最后一层是输出层,它将决定预测的 MNIST 图像值。我们添加了另一个
dense层,并使用sigmoid函数输出每个可能的 10 种情况的概率。Sigmoid 函数对于二分类或多分类问题的输出非常有用。
-
-
下一步是使用
adam优化器编译模型,并使用accuracy评估metrics。adam优化器在 CNN 模型中很常见,同时当处理具有 10 个可能输出的多分类情形时,使用categorical_crossentropy作为损失函数也是常规做法。 -
我们使用每次处理
512张图像的batch_size,在5次训练(即epochs)中训练模型。每个训练周期的损失和准确率都会被记录,并可以在下面的截图中查看:

- 我们通过在测试数据集上评估训练后的模型来计算准确率和损失率,如下图所示:

-
我们的模型似乎表现良好,准确率为 98.6%,损失率为 5%。
-
我们在 Keras 中构建了一个简单的卷积神经网络,仅用五行代码设计了实际模型。Keras 是一个非常好的工具,可以在短时间内用很少的代码启动模型。一旦你准备好进行更复杂的模型开发和控制,使用 TensorFlow 构建卷积神经网络可能更为合适。
还有更多...
除了检索模型的准确性,我们还可以通过执行以下脚本来生成 CNN 建模过程每一层的形状:
model.summary()
model.summary()的输出可以在以下截图中看到:

我们看到第一层的输出形状(None, 24, 24, 32)在第二层通过乘法 24 x 24 x 32 被展平为形状(None, 18432)。另外,我们看到第三层和第四层的形状是我们使用 Dense 层函数分配给它们的。
另请参见
要了解有关在 Keras 中开发 2D 卷积层的更多信息,请访问以下网站:
keras.io/layers/convolutional/#conv2d
要学习如何在 TensorFlow 中使用 MNIST 图像构建卷积神经网络,请访问以下网站:
www.tensorflow.org/versions/r1.4/get_started/mnist/pros
第四章:递归神经网络的痛点
本章将涵盖以下内容:
-
前馈网络介绍
-
递归神经网络的顺序工作原理
-
痛点 #1 – 梯度消失问题
-
痛点 #2 – 梯度爆炸问题
-
LSTM 的顺序工作原理
介绍
递归神经网络在涉及学习和预测序列数据的任务中已经证明了其极高的效率。然而,当谈到自然语言时,长期依赖的问题就变得至关重要,这基本上是指记住特定对话、段落或句子的上下文,以便做出更好的预测。例如,考虑以下句子:
去年,我偶然访问了中国。中国的食物不仅与世界其他地方的中国菜不同,而且人们也非常热情好客。在我在这个美丽的国家待了三年期间,我学会了说非常流利的...
如果将前面的句子输入到递归神经网络中,试图预测句子的下一个词(比如中文),网络将会感到困难,因为它没有记住句子的上下文。这就是我们所说的长期依赖问题。为了正确预测“中文”这个词,网络需要知道句子的上下文,并记住我去年曾经去过中国这一事实。因此,递归神经网络在执行此类任务时变得低效。然而,长短期记忆单元(LSTM)能够解决这个问题,LSTM 能够记住长期依赖,并在单元状态中存储信息。关于 LSTM 将在后文讨论,本章的主要内容将重点介绍神经网络的基本概念、激活函数、递归网络、递归网络的一些主要痛点或缺陷,以及如何通过使用 LSTM 来克服这些缺陷。
前馈网络介绍
要理解递归网络,首先你必须了解前馈网络的基础。这两种网络的命名都源于它们通过网络节点进行一系列数学运算的方式。其中一种仅通过每个节点单向传递信息(每个节点只经过一次),而另一种则通过循环将信息反馈到同一个节点(有点像反馈循环)。因此,很容易理解为什么前者被称为前馈网络,而后者是递归网络。
准备工作
理解任何神经网络图时,最重要的概念是计算图的概念。计算图其实就是神经网络中的各个节点通过连接互相连接,每个节点执行特定的数学函数。
如何进行...
前馈神经网络通过一组计算节点(实际上是排列在层中的数学运算符和激活函数)来传递输入(到输入层),以计算网络的输出。输出层是神经网络的最后一层,通常包含线性函数。输入层和输出层之间的层称为隐藏层,通常包含非线性元素或函数:
- 以下图(a)显示了在具有多个层的前馈神经网络中,节点如何相互连接:

前馈神经网络
-
前馈神经网络主要通过在隐藏层节点中使用的函数(激活函数)类型来相互区分。它们也通过训练过程中优化网络其他参数的算法来相互区别。
-
上述图中显示的节点之间的关系不需要对每个节点都完全填充;优化策略通常从大量隐藏节点开始,并通过在训练过程中消除连接,甚至可能是节点,来调整网络。在训练过程中可能并不需要使用每个节点。
它是如何工作的...
神经元是任何神经网络的基本结构单元。神经元可以被视为一个简单的数学函数或运算符,它作用于输入流并生成输出。神经元的输入被节点的权重矩阵乘以,对所有输入求和,进行平移后,再通过激活函数处理。这些基本上是数学中的矩阵运算,如下所示:
-
神经元的计算图表示如前图(b)所示。
-
单个神经元或节点的传递函数写作如下:

这里,x[i]是第i个节点的输入,w[i]是与第i个节点相关的权重项,b是通常为防止过拟合而添加的偏置项,f(⋅)是作用于输入流向节点的激活函数,y是节点的输出。
-
使用 Sigmoid 激活函数的神经元通常用于神经网络的隐藏层,而恒等函数通常用于输出层。
-
激活函数通常选择的方式是确保节点的输出是严格递增的、平滑的(连续的第一导数),或者是渐近的。
-
以下的逻辑函数用作 Sigmoid 激活函数:

-
使用反向传播算法训练的神经网络,如果激活函数是反对称的,可能会学习得更快,即 f(-x) = -f(x),如 sigmoidal 激活函数的情况。反向传播算法将在本章的后续部分中详细讨论。
-
然而,逻辑斯蒂函数并不是反对称的,但通过简单的缩放和平移,可以将其变为反对称,从而得到双曲正切函数,其一阶导数由f'(x) = 1 - f²(x)描述,如以下数学公式所示:

- sigmoidal 函数及其导数的简单形式使得能够快速且准确地计算优化权重和偏差选择所需的梯度,并进行二阶误差分析。
还有更多...
在神经网络的每个神经元/节点中,都会执行一系列的矩阵运算。以下图所示,给出了一种更具数学性的方式来可视化前馈网络,这将帮助你更好地理解每个节点/神经元的运算:

-
直观地看,我们可以看到,输入(无论是向量还是矩阵)首先与权重矩阵相乘。接着给这一项加上偏置,并通过激活函数(例如 ReLU、tanh、sigmoid、阈值函数等)激活,产生输出。激活函数在确保网络能够学习线性和非线性函数方面至关重要。
-
然后,这个输出流入下一个神经元作为其输入,重复执行相同的操作。若干个这样的神经元结合在一起形成一层(执行特定的功能或学习输入向量的某个特征),而多层这样的神经元结合在一起形成一个前馈神经网络,能够完全学习识别输入,如下图所示:

- 假设我们的前馈网络已经训练完成,用于分类狗和猫的图像。一旦网络训练完成,如下图所示,它将在面对新图像时,学会将图像标记为狗或猫:

-
在这种网络中,当前的输出与之前或未来的输出之间没有关系。
-
这意味着前馈网络基本上可以接触到任何随机的图像集合,而它首先接触到的图像不会必然改变它如何分类第二张或第三张图像。因此,我们可以说,在时间步 t 时的输出与在时间步 t - 1 时的输出是独立的。
-
前馈网络在图像分类等数据不具有顺序性的情况下效果良好。前馈网络在处理两个相关变量时也表现出色,例如温度和位置、高度和体重、车速和品牌等。
-
然而,也可能存在当前输出依赖于前面时间步的输出的情况(数据的顺序性很重要)。
-
设想阅读一本书的情景。你对书中句子的理解,依赖于你对句子中所有单词的理解。在这种情况下,无法使用前馈网络来预测句子中的下一个单词,因为输出在这种情况下依赖于先前的输出。
-
同样,也有许多情况下输出需要依赖于先前的输出或某些来自先前输出的信息(例如股市数据、自然语言处理、语音识别等)。前馈网络可以如以下图所示进行修改,以捕获先前输出的信息:

-
在时间步t时,来自t的输入以及来自t-1的历史信息都会提供给网络,以获得t时刻的输出。
-
类似地,来自t的信息以及新的输入会在时间步t+1时输入到网络中,从而产生t+1时刻的输出。前面图表的右侧是表示此类网络的一种泛化方式,其中网络的输出作为输入流回,以供未来时间步使用。这样的网络称为递归神经网络(RNN)。
另见
激活函数:在人工神经网络中,节点的激活函数决定了该节点在给定输入或输入集时产生的输出。输出y[k]由输入u[k]和偏置b[k]给出,这些值通过激活函数φ(.)传递,如下所示:

激活函数有多种类型。以下是常用的几种:
- 阈值函数:


从前面图表中可以看出,这种函数将神经元的输出值限制在 0 到 1 之间。许多情况下这可能非常有用。然而,这个函数是不可微分的,意味着它不能用于学习非线性,这在使用反向传播算法时至关重要。
- Sigmoid 函数:


Sigmoid 函数是一个逻辑函数,具有 0 和 1 的下限和上限,类似于阈值函数。该激活函数是连续的,因此也是可微的。在 sigmoid 函数中,前述函数的斜率参数由α给出。该函数本质上是非线性的,这在提高性能方面至关重要,因为它能够适应输入数据中的非线性,而不像常规线性函数那样。具备非线性能力确保了权重和偏差的微小变化会导致神经元输出的显著变化。
- 双曲正切函数(tanh):

该函数使激活函数的范围从-1 到+1,而不是像之前的情况那样在 0 和 1 之间。
- 修正线性单元(ReLU)函数:ReLU 是许多逻辑单元之和的平滑近似,并产生稀疏活动向量。以下是该函数的公式:


ReLU 函数图
在上图中,softplus
(x) = log ( 1 + e^x) 是对修正函数的平滑近似。
- Maxout 函数:该函数采用一种称为“dropout”的技术,并通过改进 dropout 技术的快速近似模型平均,提高优化的准确性。
Maxout 网络不仅学习隐藏单元之间的关系,还学习每个隐藏单元的激活函数。通过主动丢弃隐藏单元,网络在训练过程中被迫寻找其他路径以从给定的输入得到输出。下图是其工作原理的图形化表示:

Maxout 网络
上图展示了一个包含五个可见单元、三个隐藏单元和每个隐藏单元两个神经元的 Maxout 网络。Maxout 函数由以下公式给出:


这里的 W..[ij]是通过访问矩阵 W∈
在第二坐标i和第三坐标j上的输入大小所获得的均值向量。中间单元的数量(k)称为 Maxout 网络使用的分段数。下图展示了 Maxout 函数与 ReLU 和参数化修正线性单元(PReLU)函数的比较:

Maxout、ReLU 和 PReLU 函数的图形比较
RNN 的顺序工作
循环神经网络是一种人工神经网络,旨在识别和学习数据序列中的模式。以下是一些此类顺序数据的示例:
-
手写文字
-
如客户评价、书籍、源代码等文本
-
口语/自然语言
-
数值时间序列/传感器数据
-
股票价格变化数据
准备就绪
在递归神经网络中,前一个时间步的隐藏状态会在下一个时间步反馈到网络中,正如下图所示:

基本上,朝上箭头进入网络表示每个时间步的 RNN 输入(矩阵/向量),而朝上箭头从网络中出来表示每个 RNN 单元的输出。横向箭头表示将特定时间步(由特定神经元学习到的信息)传递到下一个时间步。
有关使用 RNN 的更多信息,请访问:
如何进行...
在递归网络的每个节点/神经元中,都进行一系列的矩阵乘法步骤。输入向量/矩阵首先与权重向量/矩阵相乘,然后加上偏置,最后通过激活函数生成输出(与前馈网络相似):
- 以下图表以计算图的形式展示了 RNN 的直观和数学表示方法:

-
在第一个时间步(即 t=0)时,h[0] 根据前图右侧的第一个公式计算。由于 h^(-1) 不存在,因此中间项变为零。
-
输入矩阵 x[0] 与权重矩阵 w[i] 相乘,并在此基础上加上一个偏置 b[h]。
-
前面两个矩阵相加后,通过激活函数 g[h] 计算得到 h[0]。
-
类似地,y[0] 通过前图右侧的第二个公式计算,方法是将 h[0] 与权重矩阵 w[y] 相乘,添加偏置 b[y],然后通过激活函数 g[y] 处理。
-
在下一个时间步(即 t=1)时,h^((t-1)) 确实存在,它就是 h[0]。这个项与权重矩阵 w[R] 相乘后,连同新的输入矩阵 x[1] 一起作为输入提供给网络。
-
这个过程会在多个时间步中重复,权重、矩阵和偏置会在不同的时间步流经整个网络。
-
这个过程在一个迭代中完成,这就是网络的前向传播。
它是如何工作的...
要训练前馈神经网络,最常用的技术是通过时间的反向传播。这是一种监督学习方法,通过在每个时间步后更新网络中的权重和偏置来减少损失函数。执行多个训练周期(也称为迭代),在每个周期中,损失函数确定的错误通过一种叫做梯度下降的技术向后传播。在每个训练周期结束时,网络更新其权重和偏置,以生成一个接近目标输出的输出,直到达到足够小的误差:
-
反向传播算法基本上在每次迭代中执行以下三个基本步骤:
-
输入数据的前向传播并计算损失函数
-
梯度和误差的计算
-
通过时间的反向传播以及相应的权重和偏置调整
-
-
在加上偏置并通过激活函数后的输入加权和被传入网络并得到输出后,网络立即比较预测输出与实际输出(正确输出)之间的差异。
-
接下来,网络通过将网络输出减去实际/正确输出来计算误差。
-
下一步是基于计算出的误差对整个网络进行反向传播,然后更新权重和偏置,观察误差是增加还是减少。
-
网络还记得是通过增加权重和偏置,还是通过减少权重和偏置,导致错误的增加。
-
基于前面的推论,网络在每次迭代中继续更新权重和偏置,以使得误差最小化。以下示例将使问题更清晰。
-
考虑一个简单的例子,教学机器如何将一个数字翻倍,如下表所示:

-
如你所见,通过随机初始化权重(W = 3),我们得到了 0、3、6 和 9 的输出。
-
错误是通过将正确输出列减去模型输出列来计算的。平方误差就是每个错误项自乘的结果。通常,使用平方误差是一种更好的做法,因为它消除了错误项中的负值。
-
模型接着意识到,为了最小化误差,权重需要被更新。
-
假设模型在下一次迭代中将权重更新为W = 4,这将导致以下输出:

-
现在,模型意识到,通过将权重增加到W = 4,错误实际上增加了。因此,模型在下一次迭代中将权重更新为W = 2,从而得到实际/正确的输出。
-
请注意,在这个简单的例子中,当权重增加时,错误增加,而当权重减少时,错误减少,如下所示:

- 在实际的神经网络中,每次迭代都会执行多次这样的权重更新,直到模型收敛到实际/正确的输出。
还有更多内容...
如前所述,当权重增加时,错误增加;而当权重减小时,错误减小。但这并不总是如此。网络使用以下图表来确定如何更新权重以及何时停止更新权重:

-
让权重在第一次迭代开始时初始化为零。随着网络通过将权重从 A 点增加到 B 点来更新权重,错误率开始降低。
-
一旦权重达到 B 点,错误率变得最小。网络会不断追踪错误率。
-
在进一步增加权重从 B 点到 C 点时,网络意识到错误率开始再次增加。因此,网络停止更新权重并恢复到 B 点的权重,因为它们是最优的。
-
在下一个场景中,考虑一个权重被随机初始化为某个值(假设为 C 点)的情况,如下图所示:

-
在进一步增加这些随机权重时,错误也会增加(从 C 点开始,图中小箭头表示从 B 点移开)。
-
网络意识到错误增加,开始从 C 点减少权重,以便错误减少(图中箭头从 C 点向 B 点移动)。这一权重减少一直持续到错误达到最小值(图中的 B 点)。
-
网络即使在达到 B 点后仍继续更新权重(如图中箭头从 B 点指向 A 点)。它随即意识到错误再次增加。因此,网络停止更新权重并恢复到最小错误值的权重(即 B 点的权重)。
-
这就是神经网络在反向传播后如何执行权重更新的方式。这种权重更新是基于动量的。它依赖于每次迭代中计算的每个神经元的梯度,如下图所示:

基本上,每当输入流入神经元时,都会根据输出计算每个输入的梯度。在反向传播的过程中,链式法则被用来计算梯度。
另请参阅
关于反向传播背后的数学原理的详细解释,可以参考以下链接:
-
mattmazur.com/2015/03/17/a-step-by-step-backpropagation-example/ -
becominghuman.ai/back-propagation-is-very-simple-who-made-it-complicated-97b794c97e5
Andrej Karpathy 的博客包含了大量关于递归神经网络的有用信息。以下是一个解释它们为何如此有效的链接:
痛点 #1 – 消失梯度问题
递归神经网络非常适合处理包含序列数据的任务。然而,它们也有一些缺点。本节将重点讨论并分析其中一个缺点,即消失梯度问题。
准备开始
消失梯度问题这个名称源于在反向传播步骤中,一些梯度消失或变为零。从技术上讲,这意味着在网络的反向传递过程中没有误差项被反向传播。当网络变得更深、更复杂时,这会成为一个问题。
如何做到这一点……
本节将描述消失梯度问题如何在递归神经网络中发生:
-
在使用反向传播时,网络首先计算误差,这个误差就是模型输出与实际输出的差的平方(例如平方误差)。
-
使用这个误差,模型接着计算权重变化(de/dw)与误差变化之间的关系。
-
计算出的导数与学习率相乘
得到
w,这实际上就是权重的变化。这个
w 被加到原始权重上,更新为新的权重。 -
假设 de/dw(误差关于权重的梯度或变化率)的值远小于 1,那么这个值与学习率相乘
(学习率总是远小于 1)将得到一个非常小的、可忽略的数字。 -
这是因为反向传播过程中权重更新只对最新的时间步准确,且随着反向传播通过之前的时间步,这种准确性会降低,经过多次时间步后几乎变得微不足道。
-
在某些情况下,句子可能非常长,神经网络需要根据上下文预测句子中的下一个单词。为了做出这样的预测,网络需要从许多前面的时间步获取信息(这些被称为长期依赖)。随着句子长度的增加,网络需要反向传播的时间步数也在增加。在这种情况下,递归网络无法记住过去许多时间步的信息,因此无法做出准确的预测。
-
当发生这种情况时,网络需要更多的复杂计算,结果是迭代次数大幅增加,在此过程中,误差项的变化逐渐消失(随着时间推移减小),权重的变化(
w)变得微乎其微。因此,新的或更新后的权重几乎等于之前的权重。 -
由于没有发生权重更新,网络停止学习或无法更新其权重,这会导致模型过拟合数据,进而产生问题。
-
整个过程如下面的图示所示:

它是如何工作的……
本节将描述梯度消失问题的一些后果:
-
这个问题发生在我们使用某种基于梯度的优化技术训练神经网络模型时。
-
一般来说,添加更多的 隐藏层 趋向于使网络能够学习更复杂的任意函数,从而在预测未来结果时表现得更好。深度学习因其拥有大量的 隐藏层(从 10 到 200 层不等)而产生了巨大差异。现在可以理解复杂的序列数据,并执行语音识别、图像分类、图像描述等任务。
-
前面步骤导致的问题是,在某些情况下,梯度变得非常小,几乎消失,这会阻止权重在未来的时间步更新其值。
-
在最坏的情况下,这可能导致网络的训练过程停止,这意味着网络停止学习它原本打算通过训练步骤学习的不同特征。
-
反向传播背后的主要思想是,它使我们这些研究人员能够监控和理解机器学习算法如何处理和学习各种特征。当梯度消失时,就无法解释网络的运作方式,因此识别和调试错误变得更加具有挑战性。
还有更多……
以下是解决梯度消失问题的一些方法:
-
克服这个问题的一种方法是使用 ReLU 激活函数。它计算函数 f(x)=max(0,x)(即,激活函数简单地将输出的下限阈值设为零),并防止网络生成负梯度。
-
另一种解决这个问题的方法是对每一层分别进行无监督训练,然后通过反向传播对整个网络进行微调,正如 Jürgen Schmidhuber 在他关于神经网络多层次结构的研究中所做的那样。该论文的链接将在以下部分提供。
-
解决这个问题的第三种方法是使用 LSTM(长短期记忆)单元或 GRUs(门控递归单元),它们是特殊类型的 RNN。
参见
以下链接提供了有关消失梯度问题的更深入描述,并介绍了一些解决该问题的方法:
痛点 #2 – 梯度爆炸问题
递归神经网络的另一个缺点是梯度爆炸问题。这与消失梯度问题类似,但恰恰相反。有时,在反向传播过程中,梯度会爆炸到非常大的值。与消失梯度问题一样,当网络架构变得更深时,梯度爆炸问题就会发生。
准备工作
梯度爆炸问题这一名称源于这样一个事实:在反向传播步骤中,一些梯度会消失或变为零。从技术上讲,这意味着在网络的反向传播过程中没有误差项被反向传播。当网络变得更深更复杂时,这就成为了一个问题。
如何解决...
本节将描述递归神经网络中的梯度爆炸问题:
-
梯度爆炸问题与消失梯度问题非常相似,但正好相反。
-
当递归神经网络中出现长期依赖时,误差项在网络中反向传播时有时会爆炸或变得非常大。
-
这个误差项乘以学习率,会导致一个极大的
w。这会产生与之前权重差异很大的新权重。之所以叫做梯度爆炸问题,是因为梯度的值变得过大。 -
梯度爆炸问题通过以下算法图示进行说明:

如何运作...
由于神经网络使用基于梯度的优化技术来学习数据中存在的特征,因此,保持这些梯度非常重要,以便网络能够根据梯度的变化计算误差。本节将描述递归神经网络中梯度爆炸问题的发生:
-
在使用反向传播时,网络首先计算误差,这只是模型输出与实际输出的差的平方(如平方误差)。
-
使用此误差,模型接着计算相对于权重变化的误差变化(de/dw)。
-
计算得到的导数乘以学习率
给出
w,这只是权重的变化。项
w 被加到原始权重中,用于更新为新的权重。 -
假设 de/dw(误差相对于权重的梯度或变化率)的值大于 1,那么该项乘以学习率
会得到一个非常非常大的数,这对于网络进一步优化权重没有任何用处,因为权重已不再处于相同的范围内。 -
这是因为反向传播过程中,权重更新仅对最近的时间步是准确的,而在反向传播经过之前的时间步时,准确性会降低,并且当权重更新穿过许多时间步时,几乎变得微不足道。
-
随着输入数据中序列数量的增加,网络需要反向传播的时间步长数量也随之增加。在这种情况下,循环神经网络无法记住来自过去多个时间步的信息,因此无法准确预测未来时间步。
-
当出现这种情况时,网络需要进行更多复杂的计算,导致迭代次数大幅增加,并且在此过程中误差项的变化超过 1,权重的变化(
w)爆炸。结果,新权重与之前的权重相比完全超出了范围。 -
由于没有权重更新发生,网络停止学习或无法在指定范围内更新其权重,这是一个问题,因为这将导致模型对数据过拟合。
还有更多…
解决爆炸梯度问题的一些方法如下:
-
可以应用某些梯度裁剪技术来解决爆炸梯度问题。
-
另一种防止此问题的方法是使用截断的时间反向传播(Backpropagation Through Time),在这种方法中,我们可以选择一个较小的时间步长(例如,15)来开始反向传播,而不是从最后一个时间步(或输出层)开始。这意味着网络将仅在一个实例中通过最后 15 个时间步进行反向传播,并且只学习与这 15 个时间步相关的信息。这类似于将数据的小批量输入网络,因为在数据集非常大的情况下,计算每个数据集元素的梯度会变得非常耗费计算资源。
-
防止梯度爆炸的最终方法是通过监控它们并相应地调整学习率。
另见
有关消失梯度和爆炸梯度问题的更详细解释,请参见以下链接:
-
www.dlology.com/blog/how-to-deal-with-vanishingexploding-gradients-in-keras/ -
machinelearningmastery.com/exploding-gradients-in-neural-networks/
LSTM 的顺序工作方式
长短期记忆单元(LSTM)只是比递归网络稍微先进一些的架构。LSTM 可以被看作是具有学习顺序数据中长期依赖关系能力的特殊类型的递归神经网络。其背后的主要原因是 LSTM 包含内存,并能够在其单元内存储和更新信息,而递归神经网络则不能。
准备工作
长短期记忆单元的主要组件如下:
-
输入门
-
忘记门
-
更新门
这些门中的每一个由一个 Sigmoid 层和随后的逐点乘法操作组成。Sigmoid 层输出的数字在零和一之间。这些值描述了每个组件允许通过各自门的多少信息。值为零表示门不会允许任何信息通过,而值为一则表示门允许所有信息通过。
理解 LSTM 单元的最佳方式是通过计算图,就像在递归神经网络(RNN)的情况下那样。
LSTM 最初是由 Sepp Hochreiter 和 Jurgen Schmidhuber 在 1997 年开发的。以下是他们发表的论文链接:
如何实现...
本节将描述单个 LSTM 单元的内部组件,主要是单元内存在的三种不同门。多个这样的单元堆叠在一起形成一个 LSTM 网络:
-
LSTM 也具有类似 RNN 的链式结构。标准的 RNN 基本上是由重复单元模块组成,类似于一个简单的函数(例如 tanh)。
-
由于每个单元内存在内存,LSTM 相比 RNN 有能力在更长时间内保持信息。这使得它们能够在输入序列的早期阶段学习重要信息,并且使其在每个时间步结束时对模型做出的决策产生重要影响。
-
通过能够从输入序列的早期阶段开始存储信息,LSTM 可以主动保留可以通过时间和层反向传播的误差,而不是让这些误差消失或爆炸。
-
LSTM 能够在多个时间步长上学习信息,因此通过保留在这些层中反向传播的误差,它们具有更密集的层架构。
-
被称为 "门" 的单元结构赋予 LSTM 保持信息、添加信息或从 单元状态 中删除信息的能力。
-
以下图展示了 LSTM 的结构。在理解 LSTM 时,关键在于理解 LSTM 网络架构和单元状态,这可以通过以下方式进行可视化:

-
在前面的图中,x[t] 和 h[t-1] 是输入到单元的两个输入。x[t] 是当前时间步的输入,而 h[t-1] 是前一个时间步的输入(即前一个时间步中前一单元的输出)。除了这两个输入,我们还有 h[, ],它是 LSTM 单元在通过其门操作这两个输入后,生成的当前输出(即时间步 t)。
-
在前面的图中,r[t] 代表从输入门中产生的输出,该门接收输入 h[t-1] 和 x[t],将这些输入与其权重矩阵 W[z] 相乘,并通过 sigmoid 激活函数处理它们。
-
类似地,术语 z[t] 代表了从遗忘门中产生的输出。这个门有一组权重矩阵(用 W[r] 表示),这些权重矩阵是特定于这个门的,并且控制着门的功能。
-
最后,有
[t],它是从更新门中产生的输出。在这种情况下,输出有两个部分。第一部分是一个 sigmoid 层,也叫做 输入门层,其主要功能是决定哪些值需要更新。下一层是一个 tanh 层,该层的主要功能是创建一个包含可以添加到单元状态中的新值的向量或数组。
它是如何工作的...
由多个 LSTM 单元/单元组合而成的 LSTM 网络。这样的网络架构如以下图所示:

-
在前面的图中,完整的 LSTM 单元由 "A" 表示。单元接收一个序列输入中的当前输入 (x**[i]),并生成 (h**[i]),这就是当前隐藏状态的输出。该输出随后作为输入发送到下一个 LSTM 单元。
-
LSTM 单元比 RNN 单元稍微复杂一些。RNN 单元只有一个对当前输入进行处理的功能/层,而 LSTM 单元有三层,这三层是控制信息在任一时间点通过单元流动的三个门。
-
这个单元的行为很像计算机中的硬盘内存。因此,单元具有允许在其单元状态中写入、读取和存储信息的能力。单元还决定存储哪些信息,以及何时允许读取、写入和删除信息。这是通过相应开关的门来实现的。
-
LSTM 单元中的门是模拟的,这与今天计算机中的数字存储系统不同。这意味着这些门只能通过 sigmoid 进行逐元素相乘来控制,从而产生介于 0 和 1 之间的概率值。高值会导致门保持开启,而低值则会导致门保持关闭。
-
在神经网络操作方面,模拟系统相比数字系统具有优势,因为它们是可微的。这使得模拟系统更适合像反向传播这样主要依赖梯度的任务。
-
各个门基于信息的强度和重要性来传递、阻止或仅让部分信息流过它们。信息在每个时间步通过每个门特定的权重矩阵集进行过滤。因此,每个门完全控制如何处理它接收到的信息。
-
与每个门相关的权重矩阵,像调节输入和隐藏状态的权重一样,都会根据递归网络的学习过程和梯度下降进行调整。
-
第一个门叫做遗忘门,它控制从前一个状态中保留哪些信息。这个门将前一个单元的输出(h[t] - 1)与当前输入(x**[t])一起作为输入,应用 sigmoid 激活函数(
),以便为每个隐藏单元产生一个介于 0 和 1 之间的输出值。然后,它会与当前状态进行逐元素相乘(如前图中的第一个操作所示)。 -
第二个门叫做更新门,它的主要功能是根据当前输入更新单元状态。这个门将与遗忘门相同的输入(h**[t-1] 和 x**[t])传递到 sigmoid 激活层(
),然后通过 tanh 激活层,接着执行这两个结果的逐元素相乘。接下来,进行与当前状态的逐元素加法(如前图中的第二个操作所示)。 -
最后,有一个输出门,它控制哪些信息以及多少信息被传递到相邻的单元,以作为其在下一时间步的输入。当前单元的状态通过 tanh 激活层,然后与单元输入(h**[t-1] 和 x**[t])进行逐元素相乘,在经过 sigmoid 层(
)后进行这个操作。 -
更新门作为一个过滤器,决定单元格向下一个单元输出什么信息。这个输出,h[t],随后被传递到下一个 LSTM 单元作为其输入,如果多个 LSTM 单元堆叠在一起,也会传递给上层。
还有更多内容...
与前馈神经网络和循环神经网络(RNN)相比,LSTM 在处理上取得了重大突破。人们或许会想,未来的下一步大进展是什么,或者说是什么进步呢?许多研究人员相信,“注意力机制”将是人工智能领域的下一个大突破。随着数据量的日益增长,处理每一 bit 数据已变得不可能。此时,注意力机制可能成为一个潜在的游戏规则改变者,它使得网络仅关注高优先级或感兴趣的数据或区域,并忽略无用的信息。例如,如果 RNN 被用来创建图像描述引擎,它会根据每个输出的词语,选择图像的一部分进行注意力聚焦。
徐等人最近(2015 年)发表的论文正是探讨了这个问题。他们研究了在 LSTM 单元中加入注意力机制。阅读这篇论文是学习神经网络中注意力机制应用的一个良好起点。使用注意力机制在各种任务中取得了一些不错的结果,且目前在这一领域仍有许多研究正在进行中。徐等人论文的链接如下:
arxiv.org/pdf/1502.03044v2.pdf
注意力机制并不是 LSTM 的唯一变体。其他一些活跃的研究是基于使用网格 LSTM 的技术,正如 Kalchbrenner 等人在论文中使用的那样,相关论文链接为:arxiv.org/pdf/1507.01526v1.pdf。
另请参见
还有一些与 RNN 和 LSTM 在生成网络中的应用相关的有用信息和论文,可以通过访问以下链接找到:
第五章:使用 Spark ML 预测消防部门呼叫
本章将涵盖以下内容:
-
下载旧金山消防部门呼叫数据集
-
确定逻辑回归模型的目标变量
-
为逻辑回归模型准备特征变量
-
应用逻辑回归模型
-
评估逻辑回归模型的准确性
介绍
分类模型是预测定义的类别结果的常用方法。我们经常使用分类模型的输出。每当我们去电影院看电影时,我们都关心电影是否符合预期?数据科学领域中最常用的分类模型之一是逻辑回归。逻辑回归模型产生一个由 sigmoid 函数激活的响应。sigmoid 函数使用模型的输入,并产生一个介于 0 和 1 之间的输出。这个输出通常是一个概率分数。许多深度学习模型也用于分类目的。常见的是,逻辑回归模型与深度学习模型一起使用,以帮助建立基准,以此为基础来评估深度学习模型。sigmoid 激活函数是深度神经网络中使用的多种激活函数之一,在深度学习中用于产生概率输出。我们将利用 Spark 中的内置机器学习库来构建一个逻辑回归模型,预测旧金山消防部门的来电是否与火灾有关,而不是其他事件。
下载旧金山消防部门呼叫数据集
旧金山市在收集辖区内消防部门的呼叫服务方面做得非常好。正如他们网站上所述,每条记录都包含呼叫号、事件号、地址、单位标识符、呼叫类型和处置方式。包含旧金山消防部门呼叫数据的官方网站可以通过以下链接访问:
data.sfgov.org/Public-Safety/Fire-Department-Calls-for-Service/nuek-vuh3
有关数据集的一些常规信息,包含列和行的数量,如下图所示:

该数据集最后更新于 2018 年 3 月 26 日,包含大约 460 万个数据行和 34 个列。
准备工作
数据集以.csv格式提供,可以下载到本地计算机上,然后导入 Spark 中。
如何实现...
本节将介绍如何下载并导入.csv文件到我们的 Jupyter notebook 中。
- 从网站下载数据集,通过选择“导出”然后选择 CSV,如下图所示:

-
如果尚未这样做,请将下载的数据集命名为
Fire_Department_Calls_for_Service.csv -
将数据集保存到任何本地目录中,理想情况下,它应该保存在与本章中使用的 Spark 笔记本相同的文件夹中,如下图所示:

- 一旦数据集保存到与笔记本相同的目录中,执行以下
pyspark脚本,将数据集导入到 Spark 中,并创建一个名为df的数据框:
from pyspark.sql import SparkSession
spark = SparkSession.builder \
.master("local") \
.appName("Predicting Fire Dept Calls") \
.config("spark.executor.memory", "6gb") \
.getOrCreate()
df = spark.read.format('com.databricks.spark.csv')\
.options(header='true', inferschema='true')\
.load('Fire_Department_Calls_for_Service.csv')
df.show(2)
它是如何工作的……
数据集被保存到与 Jupyter 笔记本相同的目录中,便于导入到 Spark 会话中。
-
通过从
pyspark.sql导入SparkSession,初始化本地pyspark会话。 -
通过读取 CSV 文件并使用
header = 'true'和inferschema = 'true'选项,创建一个名为df的数据框。 -
最后,理想情况下,始终运行一个脚本,显示已通过数据框导入 Spark 的数据,以确认数据已成功加载。脚本的结果,显示了旧金山消防部门来电数据集的前两行,如下图所示:

请注意,当我们将文件读取到 Spark 时,我们使用 .load() 将 .csv 文件导入到 Jupyter 笔记本中。对于我们的目的,这种方式是可以的,因为我们使用的是本地集群,但如果我们使用 Hadoop 的集群,这种方法将不可行。
还有更多……
数据集附带一个数据字典,定义了 34 列中每一列的标题。可以通过以下链接从同一网站访问该数据字典:
另见
旧金山政府网站允许在线可视化数据,可以用来快速进行数据分析。通过选择网站上的“可视化”下拉菜单,可以访问该可视化应用程序,如下图所示:

确定逻辑回归模型的目标变量
逻辑回归模型作为一种分类算法,旨在预测二元结果。在本节中,我们将指定数据集中最合适的列,用于预测接收到的来电是否与火灾或非火灾事件有关。
准备工作
本节中我们将可视化许多数据点,这将需要以下内容:
-
通过在命令行执行
pip install matplotlib,确保已安装matplotlib。 -
运行
import matplotlib.pyplot as plt,并通过运行%matplotlib inline确保图表能够在单元格中显示。
此外,pyspark.sql中的一些函数也需要进行操作,这需要importing functions as F。
如何操作...
本节将演示如何可视化来自旧金山消防部门的数据。
- 执行以下脚本,以获取
Call Type Group列中唯一值的初步识别:
df.select('Call Type Group').distinct().show()
-
有五个主要类别:
-
报警。 -
潜在生命威胁。 -
非生命威胁。 -
火灾。 -
null。
-
-
不幸的是,其中一个类别是
null值。获取每个唯一值的行计数,将有助于确定数据集中的null值数量。执行以下脚本生成Call Type Group列中每个唯一值的行计数:
df.groupBy('Call Type Group').count().show()
- 不幸的是,超过 280 万行数据没有与
Call Type Group关联的值。这占总可用数据(460 万行)的 60%以上。执行以下脚本,以查看空值不平衡的条形图:
df2 = df.groupBy('Call Type Group').count()
graphDF = df2.toPandas()
graphDF = graphDF.sort_values('count', ascending=False)
import matplotlib.pyplot as plt
%matplotlib inline
graphDF.plot(x='Call Type Group', y = 'count', kind='bar')
plt.title('Call Type Group by Count')
plt.show()
- 可能需要选择另一个指标来确定目标变量。相反,我们可以分析
Call Type,以识别与火灾相关的呼叫与其他所有呼叫的区别。执行以下脚本来分析Call Type:
df.groupBy('Call Type').count().orderBy('count', ascending=False).show(100)
- 与
Call Type Group不同,似乎没有任何null值。Call Type共有 32 个独特类别;因此,它将作为火灾事件的目标变量。执行以下脚本,标记Call Type中包含Fire的列:
from pyspark.sql import functions as F
fireIndicator = df.select(df["Call Type"],F.when(df["Call Type"].like("%Fire%"),1)\
.otherwise(0).alias('Fire Indicator'))
fireIndicator.show()
- 执行以下脚本,以检索
Fire Indicator的不同计数:
fireIndicator.groupBy('Fire Indicator').count().show()
- 执行以下脚本,将
Fire Indicator列添加到原始数据框df中:
df = df.withColumn("fireIndicator",\
F.when(df["Call Type"].like("%Fire%"),1).otherwise(0))
- 最后,将
fireIndicator列添加到数据框df中,并通过执行以下脚本确认:
df.printSchema()
它是如何工作的...
构建成功的逻辑回归模型的关键步骤之一是确定一个二元目标变量,该变量将用于预测结果。本节将讲解如何选择我们的目标变量:
- 通过识别
Call Type Group列的唯一值,进行潜在目标列的数据分析。我们可以查看Call Type Group列中的唯一值,如以下截图所示:

-
目标是识别
Call Type Group列中是否存在缺失值,以及如何处理这些缺失值。有时,列中的缺失值可以直接删除,而其他时候则需要进行处理以填充这些值。 -
以下截图显示了有多少
null值:

- 此外,我们还可以绘制
null值的数量,以更直观地了解值的分布情况,如以下截图所示:

-
由于在
Call Type Group中有超过 280 万行缺失数据,如df.groupBy脚本和条形图所示,删除这些值没有意义,因为它们占数据集中总行数的 60%以上。因此,需要选择另一个列作为目标指标。 -
在分析
Call Type列时,我们发现 32 个唯一的可能值中没有空值。这使得Call Type成为逻辑回归模型的一个更好的目标变量。以下是Call Type列分析的截图:

- 由于逻辑回归在二元结果下效果最佳,因此在
df数据框中使用withColumn()操作符创建了一个新列,用于表示某个呼叫是否与火灾相关事件有关(0 或 1)。这个新列名为fireIndicator,可以在以下截图中看到:

- 我们可以通过执行
groupBy().count()来识别火灾呼叫与其他呼叫的普遍程度,如下图所示:

- 最佳实践是在执行新修改数据框的
printSchema()脚本后确认新列已经附加到现有数据框中。新架构的输出可以在以下截图中看到:

还有更多内容...
在这一部分中,使用了pyspark.sql模块进行了一些列操作。withColumn()操作符通过添加一个新列或修改现有的同名列来返回一个新的数据框或修改现有的数据框。这个操作符不应与withColumnRenamed()操作符混淆,后者同样返回一个新的数据框,但通过修改现有列的名称来更改为新列。最后,我们需要执行一些逻辑操作,将与Fire相关的值转换为 0,而没有Fire的转换为 1。这需要使用pyspark.sql.functions模块,并结合where函数,相当于 SQL 中的case语句。这个函数通过以下语法创建了一个case语句公式:
CASE WHEN Call Type LIKE %Fire% THEN 1 ELSE 0 END
新数据集的结果中,Call Type和fireIndicator两列的内容如下所示:

另请参见
若要了解有关 Spark 中pyspark.sql模块的更多信息,请访问以下网站:
spark.apache.org/docs/2.2.0/api/python/pyspark.sql.html
准备逻辑回归模型的特征变量
在前一节中,我们确定了将用于预测火灾报警的目标变量,并将其用于逻辑回归模型。本节将重点介绍如何识别所有有助于模型确定目标变量的特征。这就是所谓的特征选择。
准备就绪
本节将需要从pyspark.ml.feature导入StringIndexer。为了确保正确的特征选择,我们需要将字符串类型的列映射为索引列。这将帮助为分类变量生成不同的数字值,便于机器学习模型处理独立变量,从而预测目标结果。
如何操作...
本节将介绍如何为我们的模型准备特征变量的步骤。
- 执行以下脚本,以仅选择与任何火灾指标无关的字段,从而更新数据框
df:
df = df.select('fireIndicator',
'Zipcode of Incident',
'Battalion',
'Station Area',
'Box',
'Number of Alarms',
'Unit sequence in call dispatch',
'Neighborhooods - Analysis Boundaries',
'Fire Prevention District',
'Supervisor District')
df.show(5)
- 下一步是识别数据框中是否有空值,如果存在则将其删除。执行以下脚本以识别包含空值的行数:
print('Total Rows')
df.count()
print('Rows without Null values')
df.dropna().count()
print('Row with Null Values')
df.count()-df.dropna().count()
- 有 16,551 行包含缺失值。执行以下脚本以更新数据框,删除所有包含空值的行:
df = df.dropna()
- 执行以下脚本以检索更新后的
fireIndicator目标计数:
df.groupBy('fireIndicator').count().orderBy('count', ascending = False).show()
- 从
pyspark.ml.feature导入StringIndexer类,为每个分类变量分配数值,如下脚本所示:
from pyspark.ml.feature import StringIndexer
- 使用以下脚本创建一个包含所有将用于模型的特征变量的 Python 列表:
column_names = df.columns[1:]
- 执行以下脚本以指定输出列格式
outputcol,该列将通过从输入列inputcol中的特征列表进行stringIndexed来生成:
categoricalColumns = column_names
indexers = []
for categoricalCol in categoricalColumns:
stringIndexer = StringIndexer(inputCol=categoricalCol, outputCol=categoricalCol+"_Index")
indexers += [stringIndexer]
- 执行以下脚本以创建一个
model,该模型将用于拟合输入列,并将新定义的输出列添加到现有的数据框df:
models = []
for model in indexers:
indexer_model = model.fit(df)
models+=[indexer_model]
for i in models:
df = i.transform(df)
- 执行以下脚本以定义数据框
df中最终选择的特征,这些特征将用于模型:
df = df.select(
'fireIndicator',
'Zipcode of Incident_Index',
'Battalion_Index',
'Station Area_Index',
'Box_Index',
'Number of Alarms_Index',
'Unit sequence in call dispatch_Index',
'Neighborhooods - Analysis Boundaries_Index',
'Fire Prevention District_Index',
'Supervisor District_Index')
它是如何工作的...
本节将解释准备特征变量步骤背后的逻辑。
- 只有数据框中真正与火灾指示无关的指标才会被选择用来构建预测结果的逻辑回归模型。这样做的原因是为了去除数据集中的潜在偏差,这些偏差可能已经揭示了预测的结果。这样可以最大限度地减少人为干预对最终结果的影响。更新后的数据框输出可以在以下截图中查看:

请注意,Neighborhooods - Analysis of Boundaries 列最初是在我们提取的数据中拼写错误的。为了保持一致性,我们将继续使用错误拼写,直到本章结束。然而,可以通过 Spark 中的 withColumnRenamed() 函数将列名重命名。
-
最终选择的列如下所示:
-
火警指示器 -
事件的邮政编码 -
营 -
站点区域 -
箱 -
警报次数 -
呼叫调度中的单元序列 -
Neighborhooods - Analysis Boundaries -
消防预防区 -
监管区
-
-
这些列被选择是为了避免在建模中出现数据泄漏。数据泄漏在建模中很常见,它可能导致无效的预测模型,因为它可能包括那些直接影响我们要预测的结果的特征。理想情况下,我们希望使用与结果真正独立的特征。有几个列看起来存在泄漏,因此它们被从我们的数据框和模型中移除。
-
所有缺失或为空的行都会被识别并移除,以便从模型中获得最佳性能,而不夸大或低估关键特征。可以计算并显示缺失值的行数,结果为 16,551,如以下脚本所示:

- 我们可以查看火灾相关的呼叫频率与非火灾呼叫的比较,以下截图展示了这一情况:

StringIndexer被导入以帮助将多个类别或字符串特征转换为数值,以便在逻辑回归模型中进行计算。特征的输入需要是向量或数组格式,这对数值型数据是理想的。以下截图显示了将用于模型的所有特征列表:

- 为每个类别变量构建一个索引器,指定模型中将使用的输入列(
inputCol)和输出列(outputCol)。数据框中的每一列都经过调整或转换,重建一个新的输出,并使用更新后的索引,范围从 0 到该列的唯一值的最大计数。新的列在末尾添加了_Index。在创建更新后的列时,原始列仍然保留在数据框中,如以下截图所示:

- 我们可以查看其中一个新创建的列,并将其与原始列进行比较,看看字符串是如何被转换为数值类别的。以下截图展示了
Neighborhooods - Analysis Boundaries和Neighborhooods - Analysis Boundaries_Index的比较:

-
然后,数据框被裁剪,只保留数值型数据,并去除已经转换的原始类别变量。从建模的角度来看,非数值型数据不再起作用,因此被从数据框中删除。
-
新列已打印出来,以确认数据框中每个值的类型是双精度或整数,如下图所示:

还有更多...
最后查看经过修改的数据框,您会发现它只包含数值型数据,如下图所示:

另请参见
要了解更多关于StringIndexer的信息,请访问以下网站:spark.apache.org/docs/2.2.0/ml-features.html#stringindexer。
应用逻辑回归模型
现在已经为将模型应用到数据框做好准备。
准备工作
本节将重点介绍应用一种非常常见的分类模型——逻辑回归,这将涉及从 Spark 中导入以下一些内容:
from pyspark.ml.feature import VectorAssembler
from pyspark.ml.evaluation import BinaryClassificationEvaluator
from pyspark.ml.classification import LogisticRegression
如何操作...
本节将逐步介绍应用模型和评估结果的过程。
- 执行以下脚本,将数据框中的所有特征变量放入一个名为
features的列表中:
features = df.columns[1:]
- 执行以下操作以导入
VectorAssembler并配置将分配给特征向量的字段,通过指定inputCols和outputCol:
from pyspark.ml.feature import VectorAssembler
feature_vectors = VectorAssembler(
inputCols = features,
outputCol = "features")
- 执行以下脚本,使用
transform函数将VectorAssembler应用到数据框中:
df = feature_vectors.transform(df)
- 修改数据框,移除除
fireIndicator和features之外的所有列,如下脚本所示:
df = df.drop( 'Zipcode of Incident_Index',
'Battalion_Index',
'Station Area_Index',
'Box_Index',
'Number of Alarms_Index',
'Unit sequence in call dispatch_Index',
'Neighborhooods - Analysis Boundaries_Index',
'Fire Prevention District_Index',
'Supervisor District_Index')
- 修改数据框,将
fireIndicator重命名为label,如下脚本所示:
df = df.withColumnRenamed('fireIndicator', 'label')
- 将整个数据框
df按 75:25 的比例拆分为训练集和测试集,并设置随机种子为12345,如下脚本所示:
(trainDF, testDF) = df.randomSplit([0.75, 0.25], seed = 12345)
- 从
pyspark.ml.classification导入LogisticRegression库,并配置以从数据框中引入label和features,然后在训练数据集trainDF上进行拟合,如下脚本所示:
from pyspark.ml.classification import LogisticRegression
logreg = LogisticRegression(labelCol="label", featuresCol="features", maxIter=10)
LogisticRegressionModel = logreg.fit(trainDF)
- 转换测试数据框
testDF,应用逻辑回归模型。新的数据框包含预测结果的分数,命名为df_predicted,如下脚本所示:
df_predicted = LogisticRegressionModel.transform(testDF)
它是如何工作的...
本节将解释应用模型和评估结果步骤背后的逻辑。
-
分类模型在将所有特征合并为一个单一向量进行训练时效果最佳。因此,我们通过将所有特征收集到一个名为
features的单一列表中,开始向量化过程。由于我们的标签是数据框的第一列,因此我们将其排除,并将之后的每一列作为特征列或特征变量。 -
向量化过程继续进行,将
features列表中的所有变量转换为一个输出到features列的单一向量。这个过程需要从pyspark.ml.feature导入VectorAssembler。 -
应用
VectorAssembler会通过创建一个名为features的新列来转换数据框,具体见下图:

-
在这一点上,模型中唯一需要使用的列是标签列
fireIndicator和features列。其他所有列可以从数据框中删除,因为它们在建模过程中不再需要。 -
此外,为了帮助逻辑回归模型,我们将把名为
fireIndicator的列更名为label。df.show()脚本的输出可以在以下截图中看到,其中包括新重命名的列:

- 为了减少过拟合,数据框将被拆分为测试数据集和训练数据集,以便在训练数据集
trainDF上训练模型,并在测试数据集testDF上进行测试。设置了一个随机种子12345,以确保每次执行该单元时随机性的一致性。我们可以在以下截图中看到数据拆分的行数:

-
然后,从
pyspark.ml.classification导入逻辑回归模型LogisticRegression,并配置以输入数据框中与特征和标签相关的列名。此外,逻辑回归模型会被分配给一个名为logreg的变量,接着对我们的数据集trainDF进行训练。 -
创建了一个新的数据框
predicted_df,基于对测试数据框testDF的变换,逻辑回归模型对其评分后,该模型为predicted_df创建了三个额外的列。根据评分,额外的三个列是rawPrediction、probability和prediction,具体见下图:

- 最后,可以对
df_predicted中的新列进行分析,具体见下图:

还有更多...
有一件重要的事情需要记住,因为它最初可能看起来有些直觉不合常理,那就是我们的概率阈值在数据框中设置为 50%。任何概率大于或等于 0.500 的调用都会被预测为 0.0,而任何概率小于 0.500 的调用则会被预测为 1.0。这个设置是在管道开发过程中完成的,只要我们知道阈值是什么,并且了解预测是如何分配的,我们就能很好地应对。
另请参阅
要了解更多关于VectorAssembler的信息,请访问以下网站:
spark.apache.org/docs/latest/ml-features.html#vectorassembler
评估逻辑回归模型的准确性
现在我们准备好评估预测是否正确分类为火灾事件的性能。
准备开始
我们将进行模型分析,这需要导入以下内容:
from sklearn import metrics
如何操作...
本节讲解了评估模型性能的步骤。
- 使用
.crosstab()函数创建混淆矩阵,如以下脚本所示:
df_predicted.crosstab('label', 'prediction').show()
- 从
sklearn导入metrics模块来帮助使用以下脚本测量准确性:
from sklearn import metrics
- 创建两个变量,分别代表数据框中的
actual和predicted列,这些列将用于衡量准确性,使用以下脚本:
actual = df_predicted.select('label').toPandas()
predicted = df_predicted.select('prediction').toPandas()
- 使用以下脚本计算准确率预测分数:
metrics.accuracy_score(actual, predicted)
它是如何工作的...
本节解释了如何评估模型的性能。
- 为了计算模型的准确率,重要的是能够识别我们的预测有多准确。通常,使用混淆矩阵交叉表来可视化这一点,它展示了正确和错误的预测分数。我们使用
df_predicted数据框中的crosstab()函数创建了一个混淆矩阵,显示我们有 964,980 个负类正确预测(标签为 0),以及 48,034 个正类正确预测(标签为 1),如下图所示:

-
从本节前面的内容我们知道,
testDF数据框总共有 1,145,589 行;因此,我们可以使用以下公式计算模型的准确性:(TP + TN) / 总数。准确率为 88.4%。 -
需要注意,并非所有的假分数都具有相同的意义。例如,从火灾安全的角度来看,将一次通话错误地归类为与火灾无关,而实际上它与火灾有关,比将其错误地归类为火灾事件要更为严重。这种情况称为假阴性。为了衡量假阴性(FN),有一个叫做召回率的指标。
-
虽然我们可以像最后一步那样手动计算准确率,但最好能够自动计算准确率。这可以通过导入
sklearn.metrics模块轻松实现,它是一个常用于评分和模型评估的模块。 -
sklearn.metrics接受两个参数:我们用于标签的实际结果和从逻辑回归模型中得出的预测值。因此,创建了两个变量,actual和predicted,并使用accuracy_score()函数计算准确率,如下图所示:

- 准确率与我们手动计算的结果相同,为 88.4%。
还有更多…
我们现在知道我们的模型能够以 88.4%的准确率准确预测来电是否与火灾相关。乍一听,这似乎是一个强有力的预测;然而,始终需要将其与基准得分进行比较,其中每个电话都被预测为非火灾电话。预测的数据框架df_predicted中标签1和0的分布情况如下图所示:

我们可以在同一个数据框架上运行一些统计信息,使用df_predicted.describe('label').show()脚本来计算标签值1的均值。该脚本的输出可以在下面的截图中看到:

一个基础模型在预测值为1时的预测率为 14.94%,换句话说,它的预测率为100 - 14.94%,即为 0 时的预测率为 85.06%。因此,由于 85.06%小于模型预测率 88.4%,该模型相比于盲目猜测(是否为火灾相关的电话)提供了更好的改进。
另请参见
要了解更多关于准确度与精确度的知识,请访问以下网站:
www.mathsisfun.com/accuracy-precision.html
第六章:在生成网络中使用 LSTMs
阅读完本章后,您将能够完成以下任务:
-
下载将作为输入文本使用的小说/书籍
-
准备和清理数据
-
句子标记化
-
训练并保存 LSTM 模型
-
使用模型生成相似的文本
简介
由于循环神经网络(RNNs)在反向传播方面的缺点,长短期记忆单元(LSTMs)和门控循环单元(GRUs)近年来在学习序列输入数据时越来越受欢迎,因为它们更适合解决梯度消失和梯度爆炸的问题。
下载将作为输入文本使用的小说/书籍
在本食谱中,我们将按步骤进行操作,下载我们将作为输入文本使用的小说/书籍,以执行本食谱。
准备工作
-
将输入数据以
.txt文件的形式放入工作目录。 -
输入可以是任何类型的文本,如歌词、小说、杂志文章或源代码。
-
大多数经典文本不再受版权保护,可以免费下载安装并用于实验。获取免费书籍的最佳地方是古腾堡计划。
-
在本章中,我们将使用鲁德亚德·吉卜林的《丛林之书》作为输入,训练我们的模型并生成统计上相似的文本作为输出。以下截图显示了如何下载必要的
.txt格式文件:

- 访问网站并搜索所需的书籍后,点击 Plain Text UTF-8 并下载。UTF-8 基本上指定了编码类型。文本可以复制粘贴或直接保存到工作目录,方法是点击链接。
如何实现...
在开始之前,查看并分析数据总是有帮助的。通过查看数据,我们可以看到数据中有很多标点符号、空格、引号、大写字母和小写字母。在对数据进行任何分析或将其输入 LSTM 网络之前,我们需要先准备数据。我们需要一些库来简化数据的处理:
- 通过执行以下命令导入必要的库:
from keras.preprocessing.text import Tokenizer
from keras.utils import to_categorical
from keras.models import Sequential
from keras.layers import Dense, lSTM, Dropout, Embedding
import numpy as np
from pickle import dump
import string
- 上述命令的输出如下截图所示:

- 总是检查当前工作目录并选择所需的文件夹作为工作目录是个好主意。在我们的例子中,
.txt文件名为junglebook.txt,它位于名为Chapter 8的文件夹中。因此,我们将选择该文件夹作为整个章节的工作目录。这可以通过以下截图所示的方式完成:

- 接下来,通过定义名为
load_document的函数将文件加载到程序内存中,可以通过执行以下命令完成此操作:
def load_document(name):
file = open(name, 'r')
text = file.read()
file.close()
return text
- 使用先前定义的函数将文档加载到内存中,并使用以下脚本打印文本文件的前
2000个字符:
input_filename = 'junglebook.txt'
doc = load_document(input_filename)
print(doc[:2000])
- 运行前述函数以及命令会产生以下截图所示的输出:

上述代码的输出显示在这里的截图中:

以下截图是之前输出的继续:

- 如前面截图所示,
.txt文件中的前2000个字符被打印出来。通常,分析数据时先查看它的内容是一个好主意,这样可以更好地了解如何进行后续的预处理步骤。
它的工作原理...
-
array函数将用于处理以数组形式表示的数据。numpy库提供了这个函数。 -
由于我们的数据仅为文本数据,我们将需要字符串库来处理所有输入数据为字符串形式,然后将单词编码为整数,便于输入。
-
tokenizer函数将用于将所有句子拆分为标记,其中每个标记表示一个单词。 -
为了将字典保存到 pickle 文件中,需要使用
dump函数,因此需要 pickle 库。 -
keras库中的to_categorical函数将类向量(整数)转换为二进制类矩阵,例如,用于categorical_crossentropy,我们在后面的步骤中将需要它,以便将标记映射到唯一的整数,并反向操作。 -
本章中所需的其他 Keras 层包括 LSTM 层、全连接层、丢弃层和嵌入层。模型将按顺序定义,因此我们需要从
keras库中导入顺序模型。
还有更多...
-
你还可以使用相同的模型处理不同类型的文本,如网站上的客户评价、推文、结构化文本(如源代码)、数学理论等。
-
本章的目的是理解 LSTM 如何学习长期依赖性,以及与循环神经网络相比,它们在处理顺序数据时如何表现得更好。
-
另一个好主意是将宝可梦的名称输入模型,并尝试生成你自己的宝可梦名称。
另见
有关使用的不同库的更多信息,请访问以下链接:
数据的准备和清理
本章节的这一部分将讨论在将数据输入模型之前的各种数据准备和文本预处理步骤。我们如何准备数据,实际上取决于我们打算如何建模,进而决定了我们如何使用它。
准备中
语言模型将基于统计数据,预测给定输入文本序列的每个单词的概率。预测出的单词将作为输入反馈给模型,从而生成下一个单词。
一个关键决策是输入序列的长度应该是多少。它们需要足够长,以便模型能够学习单词预测的上下文。这个输入长度还将决定在我们使用模型时,用于生成新序列的种子文本的长度。
为了简化起见,我们将随意选择一个 50 个单词的长度作为输入序列的长度。
如何操作...
基于对文本的回顾(我们之前已进行过),以下是可以对输入文件中的文本进行清理和预处理的一些操作。我们已呈现了一些关于文本预处理的选项。然而,作为练习,你可能想要探索更多清理操作:
-
将破折号
–替换为空格,以便更好地拆分单词 -
根据空格拆分单词
-
删除输入文本中的所有标点符号,以减少输入模型的文本中唯一字符的数量(例如,Why? 变为 Why)
-
删除所有非字母单词,以去除独立的标点符号标记和表情符号
-
将所有单词从大写转换为小写,以进一步减少标记总数的大小,并消除任何不一致和数据冗余
词汇表的大小是语言建模中的决定性因素,并且会影响模型的训练时间。较小的词汇表能使模型更高效,训练更快。虽然在某些情况下较小的词汇表更好,但在其他情况下,为了防止过拟合,较大的词汇表有其作用。为了预处理数据,我们需要一个函数,它接受整个输入文本,基于空格将其拆分,删除所有标点符号,规范化所有字母大小写,并返回一个标记序列。为此,定义 clean_document 函数,执行以下命令:
import string
def clean_document(doc):
doc = doc.replace('--', ' ')
tokens = doc.split()
table = str.maketrans('', '', string.punctuation)
tokens = [w.translate(table) for w in tokens]
tokens = [word for word in tokens if word.isalpha()]
tokens = [word.lower() for word in tokens]
return tokens
- 之前定义的函数基本上会将加载的文档/文件作为参数,并返回一个干净的标记数组,如以下截图所示:

- 接下来,打印出一些标记和统计数据,以便更好地理解
clean_document函数的作用。此步骤通过执行以下命令完成:
tokens = clean_document(doc)
print(tokens[:200])
print('Total Tokens: %d' % len(tokens))
print('Total Unique Tokens: %d' % len(set(tokens)))
- 上述命令组的输出会打印前两百个标记,具体内容如以下截图所示:


- 接下来,使用以下命令将所有这些标记组织成序列,每个序列包含 50 个单词(随意选择)。
length = 50 + 1
sequences = list()
for i in range(length, len(tokens)):
seq = tokens[i-sequence_length:i]
line = ' '.join(seq)
sequences.append(line)
print('Total Sequences: %d' % len(sequences))
可以通过打印文档生成的所有序列来查看总数,如下图所示:

- 使用以下命令,定义
save_doc函数,将所有生成的标记以及序列保存到工作目录中的文件:
def save_document(lines, name):
data = '\n'.join(lines)
file = open(name, 'w')
file.write(data)
file.close()
要保存序列,请使用以下两个命令:
output_filename = 'junglebook_sequences.txt'
save_document(sequences, output_filename)
- 该过程在下图中有所示例:

- 接下来,使用
load_document函数将保存的文档(包含所有保存的标记和序列)加载到内存中,load_document函数定义如下:
def load_document(name):
file = open(name, 'r')
text = file.read()
file.close()
return text
# function to load document and split based on lines
input_filename = 'junglebook_sequences.txt'
doc = load_document(input_filename)
lines = doc.split('\n')

它的工作原理...
-
clean_document函数删除所有空格、标点符号、大写字母和引号,并将整个文档分割成标记,每个标记是一个单词。 -
通过打印文档中标记的总数和唯一标记的总数,我们会发现
clean_document函数生成了 51,473 个标记,其中 5,027 个标记(或单词)是唯一的。 -
save_document函数将所有这些标记以及生成每个包含 50 个单词的序列所需的唯一标记进行保存。请注意,通过循环遍历所有生成的标记,我们能够生成一个包含 51,422 个序列的长列表。这些序列将作为输入用于训练语言模型。 -
在训练所有 51,422 个序列之前,最好先将标记和序列保存到文件中。保存后,可以使用定义的
load_document函数将文件重新加载到内存中。 -
序列被组织为 50 个输入标记和一个输出标记(这意味着每个序列有 51 个标记)。为了预测每个输出标记,前 50 个标记将作为模型的输入。我们可以通过从第 51 个标记开始遍历标记列表,并将前 50 个标记作为一个序列来实现这一点,然后重复这个过程,直到所有标记的列表结束。
另见
访问以下链接,了解使用各种函数进行数据准备的更好方法:
对句子进行标记化
在定义并将数据输入到 LSTM 网络之前,重要的是将数据转换为神经网络能够理解的形式。计算机理解所有内容都是二进制代码(0 和 1),因此文本或字符串格式的数据需要转换为一热编码变量。
准备就绪
要了解一热编码如何工作,请访问以下链接:
-
machinelearningmastery.com/how-to-one-hot-encode-sequence-data-in-python/ -
scikit-learn.org/stable/modules/generated/sklearn.preprocessing.OneHotEncoder.html -
stackoverflow.com/questions/37292872/how-can-i-one-hot-encode-in-python -
hackernoon.com/what-is-one-hot-encoding-why-and-when-do-you-have-to-use-it-e3c6186d008f
如何操作...
完成前面的部分后,你应该能够清理整个语料库并分割句子。接下来的步骤,涉及一热编码和句子标记化,可以按照以下方式进行:
-
一旦标记和序列被保存到文件并加载到内存中,它们必须被编码为整数,因为模型中的词嵌入层期望输入序列由整数而非字符串组成。
-
这是通过将词汇表中的每个单词映射到一个唯一的整数并对输入序列进行编码来实现的。稍后,在进行预测时,可以将预测结果转换(或映射)回数字,以便在相同的映射中查找相关单词,并将其从整数反向映射回单词。
-
要执行这种编码,利用 Keras API 中的
Tokenizer类。在编码之前,必须先对整个数据集进行训练,以便它能找到所有独特的标记,并为每个标记分配一个独特的整数。执行这些命令的方法如下:
tokenizer = Tokenizer()
tokenizer.fit_on_texts(lines)
sequences = tokenizer.texts_to_sequences(lines)
-
你还需要在稍后定义嵌入层之前计算词汇表的大小。这是通过计算映射字典的大小来确定的。
-
因此,在指定嵌入层的词汇表大小时,应该将其指定为比实际词汇表大 1。词汇表大小因此定义如下:
vocab_size = len(tokenizer.word_index) + 1
print('Vocabulary size : %d' % vocab_size)
-
一旦输入序列被编码,它们需要被分成输入和输出元素,这可以通过数组切片来实现。
-
分离后,将输出单词进行独热编码。这意味着将其从整数转换为一个 n 维向量,其中每个维度对应词汇表中的一个单词,并且用 1 表示该单词在词汇表中整数值对应的索引。Keras 提供了
to_categorical()函数,可以用来为每个输入-输出序列对对输出单词进行独热编码。 -
最后,指定给嵌入层输入序列的长度。我们知道有 50 个单词,因为模型是通过指定序列长度为 50 来设计的,但一种好的通用方式是使用输入数据形状的第二维(列数)来指定序列长度。
-
这可以通过执行以下命令来完成:
sequences = array(sequences)
Input, Output = sequences[:,:-1], sequences[:,-1]
Output = to_categorical(Output, num_classes=vocab_size)
sequence_length = Input.shape[1]
它是如何工作的...
本节将描述您在执行前一节命令时必须看到的输出:
- 在运行分词命令并计算词汇表长度后,您必须看到如下所示的输出:

-
单词从 1 开始分配值,直到总词数(例如,本例中为 5,027)。嵌入层需要为词汇表中从索引 1 到最大索引的每个单词分配一个向量表示。词汇表末尾的单词的索引将是 5,027;这意味着数组的长度必须是 5,027 + 1。
-
数组切片和将句子分割成每个序列 50 个单词的输出应如下所示:

to_categorical()函数用于让模型学习预测下一个单词的概率分布。
还有更多...
有关在 Python 中重塑数组的更多信息,请参阅以下链接:
-
docs.scipy.org/doc/numpy/reference/generated/numpy.reshape.html -
machinelearningmastery.com/index-slice-reshape-numpy-arrays-machine-learning-python/
训练并保存 LSTM 模型
现在,您可以从准备好的数据中训练一个统计语言模型。
将要训练的模型是一个神经语言模型。它有一些独特的特点:
-
它使用分布式表示法来表示单词,这样具有相似含义的不同单词将具有相似的表示。
-
它在学习模型的同时学习表示
-
它学会使用前 50 个单词的上下文来预测下一个单词的概率
具体而言,您将使用一个嵌入层来学习单词的表示,并使用长短期记忆(LSTM)递归神经网络来学习基于上下文预测单词。
正在准备
学到的嵌入需要知道词汇表的大小和输入序列的长度,如前面所述。它还有一个参数,用来指定将使用多少维度来表示每个词。即嵌入向量空间的大小。
常见的值为 50、100 和 300。我们这里使用 100,但可以考虑测试更小或更大的值,并评估这些值的指标。
网络将包含以下内容:
-
两个 LSTM 隐藏层,每个隐藏层有 200 个记忆单元。更多的记忆单元和更深的网络可能会获得更好的结果。
-
一个丢弃层(dropout),丢弃率为 0.3 或 30%,有助于网络减少对每个神经元/单元的依赖,减少过拟合数据。
-
一个包含 200 个神经元的密集全连接层连接到 LSTM 隐藏层,用于解释从序列中提取的特征。
-
输出层,它预测下一个词作为一个大小为词汇表的向量,并为词汇表中的每个词分配一个概率。
-
第二个密集或全连接层使用 softmax 分类器,以确保输出具有归一化概率的特征(例如在 0 和 1 之间)。
如何做...
- 模型通过以下命令定义,并在下方截图中进行说明:
model = Sequential()
model.add(Embedding(vocab_size, 100, input_length=sequence_length))
model.add(LSTM(200, return_sequences=True))
model.add(LSTM(200))
model.add(Dropout(0.3))
model.add(Dense(200, activation='relu'))
model.add(Dense(vocab_size, activation='softmax'))
print(model.summary())

-
打印模型摘要,仅确保模型按照预期构建。
-
编译模型,指定需要的类别交叉熵损失以适配模型。将训练的周期数设为 75,并且使用批量大小为 250 的小批量训练。这是通过以下命令实现的:
model.compile(loss='categorical_crossentropy', optimizer='adam',
metrics=['accuracy'])
model.fit(Input, Output, batch_size=250, epochs=75)
- 前面命令的输出结果如以下截图所示:

- 一旦模型编译完成,使用以下命令将其保存:
model.save('junglebook_trained.h5')
dump(tokenizer, open('tokenizer.pkl', 'wb'))

它是如何工作的...
-
模型是在 Keras 框架中使用
Sequential()函数构建的。模型中的第一层是一个嵌入层,它将词汇表的大小、向量维度和输入序列长度作为参数。 -
接下来的两层是 LSTM 层,每层有 200 个记忆单元。可以尝试更多的记忆单元和更深的网络,检查是否能够提高准确性。
-
下一个层是一个丢弃层,丢弃概率为 30%,这意味着在训练过程中有 30%的机会某个记忆单元不被使用。这样可以防止数据过拟合。同样,丢弃概率可以进行调整和优化。
-
最后的两层是两个全连接层。第一层具有
relu激活函数,第二层是 softmax 分类器。打印模型摘要以检查模型是否按照要求构建。 -
注意,在这种情况下,总的可训练参数数量为 2,115,228。模型摘要还显示了每一层将在模型中训练的参数数量。
-
模型以 250 的迷你批次,在 75 个周期内进行训练,以最小化训练时间。在我们的案例中,将周期数增加到 100 以上,并在训练时使用较小的批次,能显著提高模型的准确度,同时减少损失。
-
在训练过程中,你将看到性能摘要,包括每个批次更新结束时根据训练数据评估的损失和准确度。在我们的案例中,经过 75 个周期后,我们获得了接近 40%的准确率。
-
模型的目标不是以 100%的准确率记住文本,而是捕捉输入文本的特性,比如在自然语言和句子中存在的长期依赖关系和结构。
-
训练完成后,模型会保存在名为
junglebook_trained.h5的工作目录中。 -
我们还需要在以后将模型加载到内存中进行预测时,将单词映射到整数。这些信息存储在
Tokenizer对象中,并通过Pickle库中的dump()函数进行保存。
还有更多...
Jason Brownlee 在 Machine Learning Mastery 网站上的博客包含了大量有关开发、训练和调整用于自然语言处理的机器学习模型的有用信息。你可以通过以下链接访问它们:
machinelearningmastery.com/deep-learning-for-nlp/
machinelearningmastery.com/lstms-with-python/
machinelearningmastery.com/blog/
另见
进一步的信息,关于不同的 keras 层和本节中使用的其他函数,可以通过以下链接找到:
使用模型生成相似文本
现在你已经有了一个训练好的语言模型,可以开始使用它。在这个例子中,你可以用它生成具有与源文本相同统计特性的全新文本序列。虽然这在实际应用中并不实用(至少对于这个示例而言),但它提供了一个具体的例子,展示了语言模型学到的内容。
准备工作
- 重新加载训练序列。你可以通过使用我们最初开发的
load_document()函数来实现。代码如下:
def load_document(name):
file = open(name, 'r')
text = file.read()
file.close()
return text
# load sequences of cleaned text
input_filename = 'junglebook_sequences.txt'
doc = load_document(input_filename)
lines = doc.split('\n')
上述代码的输出如下截图所示:

-
请注意,输入文件名现在是
'junglebook_sequences.txt',它将把保存的训练序列加载到内存中。我们需要这些文本,以便选择一个源序列作为输入,供模型生成新的文本序列。 -
模型将需要 50 个单词作为输入。
后续,需要指定期望的输入长度。这可以通过计算加载数据的一行的长度来确定,并减去 1 作为期望的输出单词(该单词也位于同一行),如下面所示:
sequence_length = len(lines[0].split()) - 1 -
接下来,通过执行以下命令将训练好的模型加载到内存中:
from keras.models import load_model
model = load_model('junglebook.h5')
- 生成文本的第一步是准备一个种子输入。为此,从输入文本中选择一行随机文本。选择后,打印它,以便你了解所使用的内容。此操作如下所示:
from random import randint
seed_text = lines[randint(0,len(lines))]
print(seed_text + '\n')

如何做……
-
现在,你已经准备好逐个生成新单词。首先,使用训练模型时所用的相同分词器将种子文本编码为整数,可以通过以下代码完成:
encoded = tokenizer.texts_to_sequences([seed_text])[0]

- 模型可以通过调用
model.predict_classes()直接预测下一个单词,该方法将返回具有最高概率的单词索引:
prediction = model.predict_classes(encoded, verbose=0)
- 查找 Tokenizers 映射中的索引,以获取相关的单词,如下面的代码所示:
out_word = ''
for word, index in tokenizer.word_index.items():
if index == prediction:
out_word = word
break
- 将此单词附加到种子文本并重复此过程。重要的是,输入序列将变得太长。我们可以在输入序列被编码为整数后,对其进行截断至所需长度。Keras 提供了
pad_sequences()函数,可以用来执行此截断,如下所示:
encoded = pad_sequences([encoded], maxlen=seq_length, truncating='pre')
- 将所有这些内容封装成一个名为
generate_sequence()的函数,该函数接受模型、分词器、输入序列长度、种子文本和要生成的单词数作为输入。然后,它返回由模型生成的单词序列。你可以使用以下代码来实现:
from random import randint
from pickle import load
from keras.models import load_model
from keras.preprocessing.sequence import pad_sequences
def load_document(filename):
file = open(filename, 'r')
text = file.read()
file.close()
return text
def generate_sequence(model, tokenizer, sequence_length, seed_text, n_words):
result = list()
input_text = seed_text
for _ in range(n_words):
encoded = tokenizer.texts_to_sequences([input_text])[0]
encoded = pad_sequences([encoded], maxlen=seq_length, truncating='pre')
prediction = model.predict_classes(encoded, verbose=0)
out_word = ''
for word, index in tokenizer.word_index.items():
if index == prediction:
out_word = word
break
input_text += ' ' + out_word
result.append(out_word)
return ' '.join(result)
input_filename = 'junglebook_sequences.txt'
doc = load_document(input_filename)
lines = doc.split('\n')
seq_length = len(lines[0].split()) - 1

它是如何工作的……
现在,我们已经准备好生成新的单词序列,前提是我们有一些种子文本:
- 首先,使用以下命令再次将模型加载到内存中:
model = load_model('junglebook.h5')
- 接下来,通过输入以下命令加载分词器:
tokenizer = load(open('tokenizer.pkl', 'rb'))
- 使用以下命令随机选择一段种子文本:
seed_text = lines[randint(0,len(lines))]
print(seed_text + '\n')
- 最后,通过以下命令生成新的序列:
generated = generate_sequence(model, tokenizer, sequence_length, seed_text, 50)
print(generated)
- 打印生成的序列时,你将看到类似于以下截图的输出:

-
模型首先打印出 50 个随机种子文本单词,然后是 50 个生成的文本单词。在这种情况下,随机种子文本如下所示:
篮子里装满了干草,并将蚂蚱放进去,或者抓住两只螳螂让它们打斗,或者串起一条红黑相间的丛林坚果项链,或者看着蜥蜴在岩石上晒太阳,或者看蛇在泥坑附近捕捉青蛙,然后他们唱着长长的歌。
模型生成的 50 个单词的文本如下:
在评论的结尾带有奇怪的本地音调,以及他曾见证过真相的鬣狗,它们因周围的噪音而感到不安,仿佛看到峡谷尽头的画面,闻着被咬伤的味道,最好的公牛在黎明时分是本地的。
-
注意模型输出了它根据从输入文本中学到的内容生成的随机单词序列。你还会注意到,模型在模仿输入文本和生成自己的故事方面做得相当不错。尽管文本并不完全有意义,但它为我们提供了有价值的见解,说明了模型如何学习将统计上相似的单词排列在一起。
还有更多...
-
更改设置的随机种子后,网络生成的输出也会发生变化。你可能不会得到与前面示例完全相同的输出文本,但它会与用于训练模型的输入非常相似。
-
以下是通过多次运行生成文本片段获得的不同结果的一些截图:



- 模型甚至生成了它自己版本的《古腾堡计划许可证》,如下所示的截图:

-
通过将训练周期从大约 100 次增加到 200 次,模型的准确性可以提高到大约 60%。另一种提高学习效果的方法是通过以大约 50 到 100 的迷你批次进行训练。尝试调整不同的超参数和激活函数,看看哪些对结果的影响最好。
-
通过在定义模型时包含更多的 LSTM 层和丢弃层,模型也可以变得更密集。然而,要知道,如果模型更复杂并且训练周期更长,它将只会增加训练时间。
-
经过多次实验,理想的批量大小被确定为 50 到 100 之间,训练模型的理想周期次数被确定为 100 到 200 之间。
-
执行上述任务没有确定的方式。你还可以尝试不同的文本输入,如推文、客户评论或 HTML 代码。
-
其他可以执行的任务包括使用简化的词汇表(例如去除所有停用词)来进一步增强字典中的独特词汇;调节嵌入层的大小和隐藏层中记忆单元的数量;并扩展模型以使用预训练模型,如 Google 的 Word2Vec(预训练词汇模型),以查看它是否能产生更好的模型。
另见
有关本章最后部分使用的各种函数和库的更多信息,可以通过访问以下链接找到:
第七章:使用 TF-IDF 进行自然语言处理
本章将涉及以下内容:
-
下载治疗机器人会话文本数据集
-
分析治疗机器人会话数据集
-
可视化数据集中的词频
-
计算文本的情感分析
-
从文本中去除停用词
-
训练 TF-IDF 模型
-
评估 TF-IDF 模型性能
-
将模型性能与基准分数进行比较
介绍
自然语言处理(NLP)最近在新闻中频繁出现,如果你问五个人,你可能会得到十个不同的定义。最近,NLP 被用来帮助识别互联网上的机器人或水军,这些人试图传播假新闻,甚至更糟,像网络欺凌这样的战术。事实上,最近在西班牙发生了一起案件,一名学生在社交媒体账号上遭遇网络欺凌,这对学生的健康产生了严重影响,老师们开始介入。学校联系了研究人员,他们利用 NLP 方法,如 TF-IDF,帮助识别出几个可能的网络水军来源。最终,潜在的学生名单被提交给学校,并且在对质时,实际嫌疑人承认了自己是施害者。这一故事被发表在题为《监督式机器学习用于 Twitter 社交网络中水军档案的检测:应用于一起真实的网络欺凌案例》的论文中,作者为 Patxi Galan-García、Jose Gaviria de la Puerta、Carlos Laorden Gomez、Igor Santos 和 Pablo García Bringas。
本文重点介绍了利用多种方法分析文本并开发类人语言处理的能力。正是这种方法论将自然语言处理(NLP)融入到机器学习、深度学习和人工智能中。让机器能够理解文本数据并从中做出可能的决策,是自然语言处理的核心。用于自然语言处理的算法有很多,如下所示:
-
TF-IDF
-
Word2Vec
-
N-grams
-
潜在狄利克雷分配(LDA)
-
长短期记忆(LSTM)
本章将重点讨论一个包含个体与在线治疗网站上的聊天机器人对话的数据集。该聊天机器人的目的是识别需要立即转交给个体而不是继续与聊天机器人讨论的对话。最终,我们将重点使用 TF-IDF 算法对数据集进行文本分析,以确定聊天对话是否需要被分类为需要升级给个体处理的情况。TF-IDF代表词频-逆文档频率。这是一种常用于算法中的技术,用于识别一个单词在文档中的重要性。此外,TF-IDF 特别容易计算,尤其是在处理文档中的高词汇量时,并且能够衡量一个词的独特性。在处理聊天机器人数据时,这一点非常有用。主要目标是快速识别出一个独特的词,触发升级到个体的处理,从而提供即时支持。
下载治疗机器人会话文本数据集
本节将重点介绍下载和设置将用于本章自然语言处理(NLP)的数据集。
准备工作
本章使用的数据集基于治疗机器人与在线治疗网站访客之间的互动。它包含 100 次互动,每次互动都被标记为escalate或do_not_escalate。如果讨论需要更严肃的对话,机器人将标记讨论为escalate,转交给个体处理。否则,机器人将继续与用户讨论。
它是如何工作的……
本节将介绍下载聊天机器人数据集的步骤。
-
通过以下 GitHub 仓库访问数据集:
github.com/asherif844/ApacheSparkDeepLearningCookbook/tree/master/CH07/data -
到达仓库后,右键点击下图所示的文件:

-
下载
TherapyBotSession.csv并保存到与 Jupyter 笔记本SparkSession相同的本地目录中。 -
通过 Jupyter 笔记本使用以下脚本访问数据集,以构建名为
spark的SparkSession,并将数据集分配给 Spark 中的一个数据框(dataframe),名为df:
spark = SparkSession.builder \
.master("local") \
.appName("Natural Language Processing") \
.config("spark.executor.memory", "6gb") \
.getOrCreate()
df = spark.read.format('com.databricks.spark.csv')\
.options(header='true', inferschema='true')\
.load('TherapyBotSession.csv')
如何操作……
本节解释了聊天机器人数据如何进入我们的 Jupyter 笔记本。
- 可以通过点击仓库中的 TherapyBotSession.csv 查看数据集的内容,如下图所示:

- 一旦数据集下载完成,它就可以上传并转换成数据框
df。可以通过执行df.show()来查看数据框,如下图所示:

-
数据框中有 3 个主要字段是我们特别关注的:
-
id:每次访客与网站聊天机器人之间交易的唯一 ID。 -
label:由于这是一个监督学习方法,我们已经知道我们试图预测的结果,因此每笔交易已被分类为escalate或do_not_escalate。这个字段将在建模过程中使用,用来训练文本识别哪些单词会被归类为这两种情况之一。 -
chat:最后,我们有网站访客的chat文本,模型将对其进行分类。
-
还有更多...
数据框df有一些额外的列,_c3、_c4、_c5和_c6,这些列在模型中不会使用,因此可以使用以下脚本将其从数据集中排除:
df = df.select('id', 'label', 'chat')
df.show()
脚本的输出可以在下图中看到:

分析治疗机器人会话数据集
在对数据集应用模型之前,首先分析数据集总是很重要的。
准备工作
本节需要从pyspark.sql导入functions,以便在我们的数据框上执行。
import pyspark.sql.functions as F
如何操作...
以下部分将演示如何对文本数据进行剖析。
- 执行以下脚本以对
label列进行分组并生成计数分布:
df.groupBy("label") \
.count() \
.orderBy("count", ascending = False) \
.show()
- 使用以下脚本将一个新列
word_count添加到数据框df中:
import pyspark.sql.functions as F
df = df.withColumn('word_count', F.size(F.split(F.col('response_text'),' ')))
- 使用以下脚本按
label聚合平均单词数,avg_word_count:
df.groupBy('label')\
.agg(F.avg('word_count').alias('avg_word_count'))\
.orderBy('avg_word_count', ascending = False) \
.show()
它是如何工作的...
以下部分解释了通过分析文本数据获得的反馈。
- 收集跨多行的数据并按维度对结果进行分组是非常有用的。在这种情况下,维度是
label。使用df.groupby()函数来衡量按label分布的 100 次治疗交易的计数。我们可以看到,do_not_escalate与escalate的比例为65:35,如下图所示:

- 创建了一个新的列,
word_count,用于计算聊天机器人与在线访客之间 100 次交易中使用的单词数量。新创建的列word_count可以在下图中看到:

- 由于
word_count现已添加到数据框中,可以对其进行聚合,按label计算平均单词数。完成此操作后,我们可以看到,escalate会话的平均长度是do_not_escalate会话的两倍多,如下图所示:

可视化数据集中的单词计数
一张图片胜过千言万语,本节将证明这一点。不幸的是,从版本 2.2 开始,Spark 没有任何内建的绘图能力。为了在数据框中绘制值,我们必须转换为pandas。
准备工作
本节需要导入matplotlib用于绘图:
import matplotlib.pyplot as plt
%matplotlib inline
如何操作...
本部分详细说明了如何将 Spark 数据框转换为可在 Jupyter notebook 中查看的可视化图表。
- 使用以下脚本将 Spark 数据框转换为
pandas数据框:
df_plot = df.select('id', 'word_count').toPandas()
- 使用以下脚本绘制数据框:
import matplotlib.pyplot as plt
%matplotlib inline
df_plot.set_index('id', inplace=True)
df_plot.plot(kind='bar', figsize=(16, 6))
plt.ylabel('Word Count')
plt.title('Word Count distribution')
plt.show()
它是如何工作的...
本部分解释了如何将 Spark 数据框转换为 pandas,然后进行绘制。
-
Spark 数据框的一个子集被收集并使用 Spark 中的
toPandas()方法转换为pandas。 -
然后使用 matplotlib 绘制该数据子集,将 y 值设置为
word_count,将 x 值设置为id,如下图所示:

另见
除了 matplotlib 之外,Python 还有其他绘图库,如 bokeh、plotly 和 seaborn。
要了解更多关于 bokeh 的信息,请访问以下网站:
要了解更多关于 plotly 的信息,请访问以下网站:
要了解更多关于 seaborn 的信息,请访问以下网站:
计算文本的情感分析
情感分析是从单词或一系列单词中提取语气和情感的能力。本部分将利用 Python 中的技术计算数据集中 100 个事务的情感分析得分。
准备工作
本部分将需要使用 PySpark 中的函数和数据类型。此外,我们还将导入用于情感分析的 TextBlob 库。为了在 PySpark 中使用 SQL 和数据类型函数,必须导入以下内容:
from pyspark.sql.types import FloatType
此外,为了使用 TextBlob,必须导入以下库:
from textblob import TextBlob
如何实现...
以下部分将详细介绍如何将情感评分应用于数据集。
- 使用以下脚本创建一个情感评分函数
sentiment_score:
from textblob import TextBlob
def sentiment_score(chat):
return TextBlob(chat).sentiment.polarity
-
使用以下脚本将
sentiment_score应用于数据框中的每个对话回复: -
创建一个名为
sentiment_score_udf的lambda函数,将sentiment_score映射到 Spark 中的用户定义函数udf,并指定输出类型为FloatType(),如下脚本所示:
from pyspark.sql.types import FloatType
sentiment_score_udf = F.udf(lambda x: sentiment_score(x), FloatType())
- 如下脚本所示,将函数
sentiment_score_udf应用于数据框中的每个chat列:
df = df.select('id', 'label', 'chat','word_count',
sentiment_score_udf('chat').alias('sentiment_score'))
- 使用以下脚本按
label计算平均情感得分avg_sentiment_score:
df.groupBy('label')\
.agg(F.avg('sentiment_score').alias('avg_sentiment_score'))\
.orderBy('avg_sentiment_score', ascending = False) \
.show()
它是如何工作的...
本部分解释了如何将 Python 函数转换为 Spark 中的用户定义函数 udf,以便将情感分析得分应用于数据框中的每一列。
-
Textblob是一个 Python 中的情感分析库。它可以通过一个名为sentiment.polarity的方法计算情感得分,得分范围为 -1(非常负面)到 +1(非常正面),0 则表示中立。此外,Textblob还可以衡量主观性,范围从 0(非常客观)到 1(非常主观);不过,本章不会涉及主观性测量。 -
将 Python 函数应用到 Spark 数据框有几个步骤:
-
Textblob被导入,并且对chat列应用了一个名为sentiment_score的函数,以生成每个聊天对话的情感极性,结果会生成一个新的列,亦称为sentiment_score。 -
在 Spark 数据框上直接应用 Python 函数之前,必须首先通过 Spark 中的用户自定义函数转换
udf。 -
此外,函数的输出也必须明确声明,无论是整数还是浮动数据类型。在我们的例子中,我们明确声明函数的输出将使用来自
pyspark.sql.types的FloatType()。最后,情感得分会通过lambda函数应用到每一行数据中,这个lambda函数是在udf中调用的,称为sentiment_score_udf。
-
-
可以通过执行
df.show()来查看更新后的数据框,其中包含新创建的字段sentiment_score,如下所示的屏幕截图:

- 现在已经计算出了每个聊天对话的
sentiment_score,我们可以为每一行的情感极性指定一个从 -1(非常负面)到 +1(非常正面)的数值范围。就像我们处理词汇计数和平均词数一样,我们可以比较escalate对话和do_not_escalate对话的情感得分,看看它们是否有显著差异。我们可以按label计算一个平均情感得分avg_sentiment_score,如下所示的屏幕截图:

- 初步推测,
escalate的对话情感极性得分应该比do_not_escalate更负面。但实际上我们发现,escalate的情感极性比do_not_escalate稍微正面一些;然而,二者的情感极性都相当中立,接近 0。
另见
要了解更多关于 TextBlob 库的信息,请访问以下网站:
textblob.readthedocs.io/en/dev/
从文本中去除停用词
停用词是英语中常见的单词,通常在常见的自然语言处理(NLP)技术中被移除,因为它们可能会干扰分析。常见的停用词包括如 the 或 and 这样的词。
准备工作
本节需要导入以下库:
from pyspark.ml.feature import StopWordsRemover
from pyspark.ml import Pipeline
如何操作...
本节将演示如何去除停用词。
- 执行以下脚本,将
chat中的每个词提取到一个数组中的字符串:
df = df.withColumn('words',F.split(F.col('chat'),' '))
- 将一组常见的词语分配给一个变量
stop_words,并使用以下脚本将其视为停用词:
stop_words = ['i','me','my','myself','we','our','ours','ourselves',
'you','your','yours','yourself','yourselves','he','him',
'his','himself','she','her','hers','herself','it','its',
'itself','they','them','their','theirs','themselves',
'what','which','who','whom','this','that','these','those',
'am','is','are','was','were','be','been','being','have',
'has','had','having','do','does','did','doing','a','an',
'the','and','but','if','or','because','as','until','while',
'of','at','by','for','with','about','against','between',
'into','through','during','before','after','above','below',
'to','from','up','down','in','out','on','off','over','under',
'again','further','then','once','here','there','when','where',
'why','how','all','any','both','each','few','more','most',
'other','some','such','no','nor','not','only','own','same',
'so','than','too','very','can','will','just','don','should','now']
- 执行以下脚本来从 PySpark 导入
StopWordsRemover函数,并配置输入和输出列words和word without stop:
from pyspark.ml.feature import StopWordsRemover
stopwordsRemovalFeature = StopWordsRemover(inputCol="words",
outputCol="words without stop").setStopWords(stop_words)
- 执行以下脚本导入管道并定义停用词转换过程的
阶段,这些将应用到数据框:
from pyspark.ml import Pipeline
stopWordRemovalPipeline = Pipeline(stages=[stopwordsRemovalFeature])
pipelineFitRemoveStopWords = stopWordRemovalPipeline.fit(df)
- 最后,使用以下脚本将停用词移除转换
pipelineFitRemoveStopWords应用到数据框df:
df = pipelineFitRemoveStopWords.transform(df)
它是如何工作的...
本节解释了如何从文本中删除停用词。
-
正如我们在对
chat数据进行分析和探索时所做的那样,我们也可以调整chat对话的文本,将每个词分割成一个单独的数组。这将用于隔离停用词并将其移除。 -
提取的每个词作为字符串的新列称为
words,可以在以下截图中看到:

-
有多种方法可以将一组词汇分配给停用词列表。这些词汇中的一些可以通过一个名为
nltk的 Python 库自动下载和更新,nltk代表自然语言工具包。为了我们的目的,我们将利用一个包含 124 个常见停用词的列表来生成我们自己的列表。额外的词汇可以轻松地手动添加或删除。 -
停用词对文本没有任何价值,因此会通过指定
outputCol="words without stop"从新创建的列中删除。此外,通过指定inputCol = "words"来设置作为转换源的列。 -
我们创建了一个管道
stopWordRemovalPipeline,用来定义将转换数据的步骤或阶段序列。在这种情况下,用于转换数据的唯一阶段是特征stopwordsRemover。 -
管道中的每个阶段都可以有一个转换角色和一个估算角色。估算角色
pipeline.fit(df)被调用以生成一个名为pipelineFitRemoveStopWords的转换器函数。最后,调用transform(df)函数对数据框进行转换,以生成一个新的数据框,其中包含一个名为words without stop的新列。我们可以将两个列并排比较,检查它们之间的差异,如以下截图所示:

- 新列
words without stop不包含原始列words中被视为停用词的任何字符串。
另见:
要了解更多关于 nltk 停用词的信息,请访问以下网站:
要了解更多关于 Spark 机器学习管道的信息,请访问以下网站:
spark.apache.org/docs/2.2.0/ml-pipeline.html
要了解更多关于 PySpark 中 StopWordsRemover 特征的信息,请访问以下网站:
spark.apache.org/docs/2.2.0/api/python/pyspark.ml.html#pyspark.ml.feature.StopWordsRemover
训练 TF-IDF 模型
我们现在准备训练我们的 TF-IDF 自然语言处理(NLP)模型,并看看我们是否能将这些事务分类为 escalate 或 do_not_escalate。
准备工作
本节需要从 spark.ml.feature 和 spark.ml.classification 导入内容。
如何操作...
以下部分将逐步讲解训练 TF-IDF 模型的步骤。
- 创建一个新的用户定义函数
udf,用以下脚本为label列定义数值:
label = F.udf(lambda x: 1.0 if x == 'escalate' else 0.0, FloatType())
df = df.withColumn('label', label('label'))
- 执行以下脚本,以设置词向量化的 TF 和 IDF 列:
import pyspark.ml.feature as feat
TF_ = feat.HashingTF(inputCol="words without stop",
outputCol="rawFeatures", numFeatures=100000)
IDF_ = feat.IDF(inputCol="rawFeatures", outputCol="features")
- 设置管道
pipelineTFIDF,按以下脚本设置TF_和IDF_阶段的顺序:
pipelineTFIDF = Pipeline(stages=[TF_, IDF_])
- 使用以下脚本,将 IDF 估算器拟合并转换为数据框
df:
pipelineFit = pipelineTFIDF.fit(df)
df = pipelineFit.transform(df)
- 使用以下脚本将数据框按 75:25 的比例拆分,以进行模型评估:
(trainingDF, testDF) = df.randomSplit([0.75, 0.25], seed = 1234)
- 使用以下脚本导入并配置分类模型
LogisticRegression:
from pyspark.ml.classification import LogisticRegression
logreg = LogisticRegression(regParam=0.25)
- 将逻辑回归模型
logreg拟合到训练数据框trainingDF上。基于逻辑回归模型的transform()方法,会创建一个新的数据框predictionDF,如下脚本所示:
logregModel = logreg.fit(trainingDF)
predictionDF = logregModel.transform(testDF)
它是如何工作的...
以下部分解释了如何有效地训练一个 TF-IDF 自然语言处理模型。
- 理想情况下,标签应以数字格式而非类别形式出现,因为模型能够解释数字值,并在 0 和 1 之间进行输出分类。因此,
label列下的所有标签都会转换为数值形式的label,其值为 0.0 或 1.0,如下图所示:

-
TF-IDF 模型需要采用两步法,通过从
pyspark.ml.feature导入HashingTF和IDF来处理不同的任务。第一步仅涉及导入HashingTF和IDF,并为输入和输出列分配相应的值。numfeatures参数被设置为 100,000,以确保它大于数据框中不同词汇的数量。如果numfeatures小于词汇的不同数量,模型将不准确。 -
如前所述,管道的每个步骤都包含转换过程和估算过程。管道
pipelineTFIDF被配置为顺序排列各个步骤,其中IDF将紧随HashingTF。 -
HashingTF用于将“去除停用词后的词汇”转换为向量,存储在一个新的列rawFeatures中。随后,rawFeatures会被IDF处理,以估算大小并拟合数据框,生成一个新的列features,如下图所示:

-
为了进行训练,我们的数据框将以
75:25的比例保守地拆分,并且随机种子设置为1234。 -
由于我们的主要目标是将每个对话分类为
escalate(升级)或do_not_escalate(继续与机器人对话),因此我们可以使用传统的分类算法,如来自 PySpark 库的逻辑回归模型。逻辑回归模型配置了一个正则化参数regParam,其值为 0.025。我们使用该参数通过在稍微增加偏差的情况下最小化过拟合,从而稍微改善模型。 -
逻辑回归模型在
trainingDF上训练并拟合,然后创建一个新的数据框predictionDF,其中包含新转换的字段prediction,如以下截图所示:

还有更多...
虽然我们确实使用了用户定义函数 udf 来手动创建数值标签列,但我们也可以使用 PySpark 中的内置特性 StringIndexer 来为分类标签分配数值。要查看 StringIndexer 的实际操作,请访问 第五章,使用 Spark ML 预测消防部门电话。
另见
要了解更多关于 PySpark 中的 TF-IDF 模型,请访问以下网站:
spark.apache.org/docs/latest/mllib-feature-extraction.html#tf-idf
评估 TF-IDF 模型性能
此时,我们准备好评估模型的性能
准备工作
本节需要导入以下库:
-
metrics来自sklearn -
来自
pyspark.ml.evaluation的BinaryClassificationEvaluator
如何执行...
本节介绍了评估 TF-IDF NLP 模型的步骤。
- 使用以下脚本创建混淆矩阵:
predictionDF.crosstab('label', 'prediction').show()
- 使用来自 sklearn 的
metrics评估模型,使用以下脚本:
from sklearn import metrics
actual = predictionDF.select('label').toPandas()
predicted = predictionDF.select('prediction').toPandas()
print('accuracy score: {}%'.format(round(metrics.accuracy_score(actual, predicted),3)*100))
- 使用以下脚本计算 ROC 分数:
from pyspark.ml.evaluation import BinaryClassificationEvaluator
scores = predictionDF.select('label', 'rawPrediction')
evaluator = BinaryClassificationEvaluator()
print('The ROC score is {}%'.format(round(evaluator.evaluate(scores),3)*100))
它是如何工作的...
本节解释了我们如何使用评估计算来确定模型的准确性。
- 混淆矩阵有助于快速总结实际结果和预测结果之间的准确性。由于我们使用了 75:25 的拆分,因此应该从我们的训练数据集中看到 25 个预测结果。我们可以使用以下脚本来构建混淆矩阵:
predictionDF.crosstab('label', 'prediction').show()。脚本的输出可以在以下截图中看到:

- 我们现在处于评估模型准确性的阶段,通过将
prediction值与实际的label值进行比较。sklearn.metrics接受两个参数,分别是与label列关联的actual(实际)值和从逻辑回归模型中得出的predicted(预测)值。
请注意,我们再次使用 toPandas() 方法将 Spark 数据框的列值转换为 Pandas 数据框。
- 创建了两个变量,
actual和predicted,并使用metrics.accuracy_score()函数计算了 91.7%的准确率,如下截图所示:

- ROC(接收者操作特征)通常与测量真阳性率与假阳性率之间的曲线相关。曲线下的面积越大越好。与曲线相关的 ROC 得分是另一个可以用来衡量模型性能的指标。我们可以使用
BinaryClassificationEvaluator计算ROC,如以下截图所示:

另见
要了解更多关于 PySpark 中BinaryClassificationEvaluator的信息,请访问以下网站:
将模型性能与基准得分进行比较
虽然我们的模型准确率为 91.7%,这已经是很棒的结果,但与基准得分进行比较同样重要。本节将深入探讨这个概念。
如何做到这一点...
本节详细介绍了计算基准准确率的步骤。
- 执行以下脚本以从
describe()方法中检索平均值:
predictionDF.describe('label').show()
- 通过减去
1 - 平均值得分来计算基准准确率。
它是如何工作的...
本节解释了基准准确率背后的概念,以及如何利用它来理解我们模型的效果。
-
如果每个
chat对话都被标记为do_not_escalate,反之亦然,会不会使我们的基准准确率超过 91.7%?找出这个问题最简单的方法是运行predictionDF的label列上的describe()方法,使用以下脚本:predictionDF.describe('label').show() -
脚本的输出可以在下方的截图中看到:

-
label的平均值为 0.2083,约为 21%,这意味着label为 1 的情况仅占 21%。因此,如果我们将每个对话标记为do_not_escalate,我们将大约 79%的时间是正确的,这比我们模型的准确率 91.7%还低。 -
因此,我们可以说我们的模型表现优于盲目基准性能模型。
另见
要了解有关 PySpark 数据框中describe()方法的更多信息,请访问以下网站:
spark.apache.org/docs/2.2.0/api/python/pyspark.sql.html#pyspark.sql.DataFrame.describe
第八章:使用 XGBoost 进行房地产价值预测
房地产市场是定价竞争最激烈的市场之一。价格会因多种因素而大幅波动,例如位置、房产年龄、大小等。因此,准确预测房产(尤其是住房市场)的价格,已成为现代社会的一项挑战,目的是做出更好的投资决策。本章将专门讨论这一问题。
完成本章后,你将能够:
-
下载 King County 房屋销售数据集
-
执行探索性分析与可视化
-
绘制价格与其他特征之间的相关性
-
预测房屋价格
下载 King County 房屋销售数据集
我们无法在没有数据集的情况下建立模型。我们将在本节中下载数据。
准备工作
Kaggle (www.kaggle.com/) 是一个用于预测建模和分析竞赛的平台,统计学家和数据挖掘人员在其中竞争,旨在为公司和用户上传的数据集生成最优模型进行预测和描述。King County 房屋销售数据集包含了 1900 年至 2015 年间,在纽约 King County 销售的 21,613 套房屋的记录。数据集还包含了 21 个不同的变量,如位置、邮政编码、卧室数量、居住空间面积等,每个房屋都有这些信息。
如何操作...
-
可以从以下网址访问数据集:
www.kaggle.com/harlfoxem/housesalesprediction。该数据集来自 King County 的公共记录,免费下载并可用于任何分析。 -
一旦进入网站,你可以点击下载按钮,如下图所示:

King County 房屋销售数据集
-
从压缩的下载文件
housesalesprediction.zip中会出现一个名为kc_house_data.csv的文件。 -
将名为
kc_house_data.csv的文件保存在当前工作目录中,因为这将是我们的数据集。该文件将被加载到 IPython 笔记本中进行分析和预测。
它是如何工作的...
- 使用以下代码安装本章所需的库:
import numpy as np
import pandas as pd
import matplotlib.pyplot as plt
import seaborn as sns
import mpl_toolkits
from sklearn import preprocessing
from sklearn.preprocessing import LabelEncoder, OneHotEncoder
from sklearn.feature_selection import RFE
from sklearn import linear_model
from sklearn.cross_validation import train_test_split %matplotlib inline
- 上一步应产生一个输出,示例如下图所示:

- 始终建议检查当前工作目录,并将其设置为存储数据集的目录。示例如下图所示:

在我们的案例中,名为 Chapter 10 的文件夹被设为当前工作目录。
- 文件中的数据通过
read_csv()函数读取到名为dataframe的 Pandas 数据框中,并使用list(dataframe)命令列出特征/头部信息,示例如下图所示:

正如您可能已经注意到的那样,数据集包含 21 个不同的变量,如 id、日期、价格、卧室、浴室等。
更多内容...
本章使用的库及其函数如下:
-
Numpy,用于整理数组形式的数据以及以数组形式存储名称列表 -
Pandas,用于所有数据整理和以数据帧形式管理数据 -
Seaborn,这是一个用于探索性分析和绘图所需的可视化库 -
MPL_Toolkits,其中包含Matplotlib所需的多个函数和依赖项 -
本章需要的科学和统计库
Scikit Learn的函数 -
我们还需要一些其他库,如
XGBoost,但在构建模型时会根据需要导入
另请参阅
可以通过访问以下链接找到有关不同库的进一步文档:
执行探索性分析和可视化
在预测诸如 price 这样的变量时,通过可视化数据并查明依赖变量如何受其他变量影响,可以帮助分析。探索性分析提供了许多通过查看数据无法立即获得的见解。本章节将描述如何从大数据中可视化并得出见解。
准备工作
- 可以使用
dataframe.head()函数打印dataframe的头部,该函数产生一个输出,如下截图所示:

- 同样地,可以使用
dataframe.tail()函数打印dataframe的尾部,该函数产生一个输出,如下截图所示:

dataframe.describe()函数用于获取每列的最大、最小和平均值等一些基本统计数据。如下截图所示:

dataframe.describe() 函数的输出
-
正如您所见,数据集包含 21,613 条记录,这些记录是在 1900 年至 2015 年之间售出的房屋。
-
仔细查看统计数据后,我们意识到大多数售出的房屋平均有约三个卧室。我们还可以看到,房屋中卧室数量最少的是 0,而最大的房屋有 33 间卧室和 13,540 平方英尺的生活空间。
如何做...
- 让我们绘制整个数据集中卧室数量的分布,看看三卧室房屋与二卧室或一卧室房屋相比如何。这可以通过以下代码完成:
dataframe['bedrooms'].value_counts().plot(kind='bar') plt.title('No. of bedrooms')
plt.xlabel('Bedrooms')
plt.ylabel('Count')
sns.despine
- 我们还可以使用以下命令绘制相同数据的饼图:
dataframe['bedrooms'].value_counts().plot(kind='pie')
plt.title('No. of bedrooms')
- 接下来,让我们尝试查看在金县最常售出的房屋的楼层数。这可以通过绘制柱状图来完成,命令如下:
dataframe['floors'].value_counts().plot(kind='bar') plt.title('Number of floors')
plt.xlabel('No. of floors')
plt.ylabel('Count')
sns.despine
- 接下来,我们需要了解哪些位置的房屋销售数量最多。我们可以通过使用数据集中的
latitude和longitude变量来实现,代码如下:
plt.figure(figsize=(20,20))
sns.jointplot(x=dataframe.lat.values, y=dataframe.long.values, size=9)
plt.xlabel('Longitude', fontsize=10)
plt.ylabel('Latitude', fontsize=10)
plt.show()
sns.despine()
- 让我们通过执行以下命令,看看不同卧室数量的房屋价格如何比较:
plt.figure(figsize=(20,20))
sns.jointplot(x=dataframe.lat.values, y=dataframe.long.values, size=9)
plt.xlabel('Longitude', fontsize=10)
plt.ylabel('Latitude', fontsize=10)
plt.show()
sns.despine()
- 使用以下命令,我们可以得到房价与卧室数量之间的图表:
plt.figure(figsize=(20,20))
sns.jointplot(x=dataframe.lat.values, y=dataframe.long.values, size=9)
plt.xlabel('Longitude', fontsize=10)
plt.ylabel('Latitude', fontsize=10)
plt.show()
sns.despine()
- 同样,让我们看看价格与所有已售房屋的居住面积之间的关系。这可以通过使用以下命令来实现:
plt.figure(figsize=(8,8))
plt.scatter(dataframe.price, dataframe.sqft_living)
plt.xlabel('Price')
plt.ylabel('Square feet')
plt.show()
- 售出房屋的状况也为我们提供了一些重要信息。让我们将其与价格进行对比,来更好地了解一般的趋势。这可以通过以下命令实现:
plt.figure(figsize=(5,5))
plt.bar(dataframe.condition, dataframe.price)
plt.xlabel('Condition')
plt.ylabel('Price')
plt.show()
- 我们可以使用以下命令查看金县哪些邮政编码区域的房屋销售最多:
plt.figure(figsize=(8,8))
plt.scatter(dataframe.zipcode, dataframe.price)
plt.xlabel('Zipcode')
plt.ylabel('Price')
plt.show()
- 最后,绘制每套房屋的等级与价格的关系,以便通过使用以下命令找出基于房屋等级的销售趋势:
plt.figure(figsize=(10,10))
plt.scatter(dataframe.grade, dataframe.price)
plt.xlabel('Grade')
plt.ylabel('Price')
plt.show()
它是如何工作的...
- 卧室数量的图表应该生成如下输出:

-
很明显,三卧室房屋是销售最多的,其次是四卧室房屋,然后是二卧室房屋,出乎意料的是,五卧室和六卧室房屋的销售量也不低。
-
卧室数量的饼图生成的输出如下所示:

-
你会注意到,三卧室房屋大约占金县所有已售房屋的 50%。看起来大约 25%是四卧室房屋,剩下的 25%则由二卧室、五卧室、六卧室房屋等组成。
-
运行脚本以根据楼层数对大多数出售的房屋进行分类时,我们会看到以下输出:

-
很明显,单层房屋销售最多,其次是二层房屋。三层以上的房屋数量相对较少,这或许反映了金县居民的家庭规模和收入水平。
-
检查不同位置售出房屋的密度后,我们得到如下输出。从中可以明显看出,一些位置的房屋销售密度高于其他位置:


-
从前面图表观察到的趋势来看,很容易注意到,在经度 -122.2 到 -122.4 之间销售的房屋更多。同样,经度 47.5 到 47.8 之间销售的房屋密度比其他经度更高。这可能是对比其他社区更安全和居住质量更好的指示。
-
当绘制房屋价格与房屋卧室数量的关系时,我们意识到关于房屋卧室数量的趋势与房价成正比,直到六个卧室,然后成为反比,如下面的截图所示:

- 绘制每个房屋的生活区域与价格的关系图表明,随着房屋面积的增加,价格也在增加。最昂贵的房屋似乎有 12,000 平方英尺的生活面积,如下面的截图所示:

更多内容...
- 当绘制房屋条件与价格的关系时,我们再次注意到一个预期的趋势:随着条件评级的提高,房价也在增加,如下面的截图所示。有趣的是,五卧房的价格比四卧房略低,这可能是因为对这么大的房子的购买者较少:

- 房屋的邮政编码与价格的关系图显示了不同邮政编码地区房价的趋势。你可能已经注意到,像 98100 到 98125 这样的一些邮政编码区域,销售的房屋密度比其他地区高,而像 98040 这样的邮政编码地区的房价高于平均价格,可能表明这是一个更富裕的社区,如下面的截图所示:

- 房屋等级与价格的关系图显示了随着等级的增加,价格稳步增长的趋势。两者之间似乎存在明确的线性关系,正如下面截图中的结果所观察到的:


另请参阅
下面的链接很好地解释了为什么在对数据运行任何模型之前,数据可视化如此重要:
-
www.slideshare.net/Centerline_Digital/the-importance-of-data-visualization -
www.techchange.org/2015/05/19/data-visualization-analysis-international-development/
绘制价格与其他特征之间的相关性
现在,初步的探索性分析已经完成,我们对不同变量如何影响每栋房子的价格有了更好的了解。然而,我们并不知道每个变量在预测价格时的重要性。由于我们有 21 个变量,单纯将所有变量放入一个模型中构建会变得困难。因此,可能需要丢弃或忽略某些变量,特别是那些在预测中比其他变量重要性较低的变量。
准备工作
相关系数在统计学中用于衡量两个变量之间关系的强度。特别是,在执行线性回归时,Pearson 相关系数是最常用的系数。相关系数通常在 -1 到 +1 之间取值:
-
相关系数为 1 表示当一个变量增加时,另一个变量也会按照固定比例增加。例如,鞋码几乎与脚长呈完全正相关。
-
相关系数为 -1 表示当一个变量增加时,另一个变量会按照固定比例减少。例如,油箱中的油量与加速或换档机制几乎呈完全负相关(与第四档相比,在第一档行驶更长时间会消耗更多的油)。
-
零表示对于每一个增加,既没有正向增加也没有负向增加。这两个变量之间没有关系。
如何进行...
- 首先使用以下命令删除数据集中的
id和date特征。我们在预测中不会使用它们,因为 ID 变量都是唯一的,且在我们的分析中没有值,而日期需要用不同的函数来正确处理。这个任务留给读者完成:
x_df = dataframe.drop(['id','date',], axis = 1)
x_df
- 使用以下命令将因变量(本例中的房价)复制到一个新的
dataframe中:
y = dataframe[['price']].copy()
y_df = pd.DataFrame(y)
y_df
- 可以使用以下脚本手动找到价格与其他变量之间的相关性:
print('Price Vs Bedrooms: %s' % x_df['price'].corr(x_df['bedrooms']))
print('Price Vs Bathrooms: %s' % x_df['price'].corr(x_df['bathrooms']))
print('Price Vs Living Area: %s' % x_df['price'].corr(x_df['sqft_living']))
print('Price Vs Plot Area: %s' % x_df['price'].corr(x_df['sqft_lot']))
print('Price Vs No. of floors: %s' % x_df['price'].corr(x_df['floors']))
print('Price Vs Waterfront property: %s' % x_df['price'].corr(x_df['waterfront']))
print('Price Vs View: %s' % x_df['price'].corr(x_df['view']))
print('Price Vs Grade: %s' % x_df['price'].corr(x_df['grade']))
print('Price Vs Condition: %s' % x_df['price'].corr(x_df['condition']))
print('Price Vs Sqft Above: %s' % x_df['price'].corr(x_df['sqft_above']))
print('Price Vs Basement Area: %s' % x_df['price'].corr(x_df['sqft_basement']))
print('Price Vs Year Built: %s' % x_df['price'].corr(x_df['yr_built']))
print('Price Vs Year Renovated: %s' % x_df['price'].corr(x_df['yr_renovated']))
print('Price Vs Zipcode: %s' % x_df['price'].corr(x_df['zipcode']))
print('Price Vs Latitude: %s' % x_df['price'].corr(x_df['lat']))
print('Price Vs Longitude: %s' % x_df['price'].corr(x_df['long']))
-
除了前面的方法,还有一种更简单的方法可以通过以下一行代码来查找一个变量与所有其他变量(或列)之间的相关性:
x_df.corr().iloc[:,-19] -
可以使用
seaborn库和以下脚本绘制相关变量:
sns.pairplot(data=x_df,
x_vars=['price'],
y_vars=['bedrooms', 'bathrooms', 'sqft_living',
'sqft_lot', 'floors', 'waterfront','view',
'grade','condition','sqft_above','sqft_basement',
'yr_built','yr_renovated','zipcode','lat','long'],
size = 5)
它是如何工作的...
- 在删除了
id和date变量后,新的dataframe(命名为x_df)包含 19 个变量或列,如以下截图所示。为了本书的目的,只打印出前十个条目:

输出的前 10 个条目

- 创建一个只包含因变量(价格)的新
dataframe时,您将看到如下输出。这个新dataframe命名为y_df。再次说明,这里只打印了价格列的前十条记录,供参考:

- 价格与其他变量的相关性如下图所示:

- 你可能已经注意到,
sqft_living变量与价格的相关性最高,其相关系数为 0.702035。下一个最相关的变量是grade,其相关系数为 0.667434,其次是sqft_above,其相关系数为 0.605567。Zipcode是与价格最不相关的变量,相关系数为-0.053202。
更多内容…
- 使用简化代码找到的相关系数给出了完全相同的值,但也给出了价格与其自身的相关系数,结果是 1.0000,正如预期的那样。以下截图展示了这一点:

- 使用
seaborn库绘制的相关系数如下图所示。请注意,每个图的 x 轴上是价格:


相关系数的绘制















另见
以下链接对 Pearson 相关系数及其手动计算方法提供了很好的解释:
en.wikipedia.org/wiki/Pearson_correlation_coefficient
www.statisticshowto.com/probability-and-statistics/correlation-coefficient-formula/
预测房价
本节将介绍如何使用当前dataframe中的所有特征来构建一个简单的线性模型以预测房价。然后,我们将评估该模型,并尝试在本节后半部分通过使用更复杂的模型来提高准确性。
准备工作
访问以下链接,了解线性回归是如何工作的以及如何在 Scikit Learn 库中使用线性回归模型:
en.wikipedia.org/wiki/Linear_regression
www.stat.yale.edu/Courses/1997-98/101/linreg.htm
newonlinecourses.science.psu.edu/stat501/node/251/
scikit-learn.org/stable/modules/generated/sklearn.linear_model.LinearRegression.html
scikit-learn.org/stable/modules/linear_model.html
如何做到...
- 从
x_df数据框中删除Price列,并使用以下脚本将其保存到一个名为x_df2的新数据框中:
x_df2 = x_df.drop(['price'], axis = 1)
- 声明一个名为
reg的变量,并将其等式赋值给 Scikit Learn 库中的LinearRegression()函数,使用以下脚本:
reg=linear_model.LinearRegression()
- 使用以下脚本将数据集拆分为测试集和训练集:
x_train,x_test,y_train,y_test = train_test_split(x_df2,y_df,test_size=0.4,random_state=4)
- 使用以下脚本在训练集上拟合模型:
reg.fit(x_train,y_train)
-
使用
reg.coef_命令打印应用线性回归到训练集和测试集后生成的系数。 -
使用以下脚本查看模型生成的预测列:
predictions=reg.predict(x_test)
predictions
- 使用以下命令打印模型的准确性:
reg.score(x_test,y_test)
它是如何工作的...
- 在将回归模型拟合到训练集后,输出应如下所示:

reg.coeff_命令生成 18 个系数,每个变量对应一个系数,如下截图所示:

-
特征/变量的系数中,值最正的那些对价格预测的影响更大,相比之下,值为负的特征/变量系数对价格预测的影响较小。这就是回归系数的重要性所在。
-
打印预测结果时,您必须看到一个输出,这是一个从 1 到 21,612 的值数组,每一行代表数据集中的一个值,截图如下所示:

- 最后,在打印模型的准确性时,我们得到了 70.37%的准确率,对于一个线性模型来说,这个结果还不错。以下是该截图:

还有更多...
线性模型在第一次尝试时表现尚可,但如果我们希望模型更准确,我们需要使用一个更复杂的模型,带有一些非线性部分,以便更好地拟合所有数据点。XGBoost 是我们在本节中将使用的模型,目的是通过线性回归提高准确性。方法如下:
-
使用
import xgboost命令导入XGBoost库。 -
如果出现错误,您需要通过终端使用
pip install安装该库。可以通过打开一个新的终端窗口并执行以下命令来完成此操作:
/usr/bin/ruby -e "$(curl -fsSL https://raw.githubusercontent.com/Homebrew/install/master/install)"
- 在此阶段,您应该会看到类似以下截图的输出:

- 在此阶段,系统会提示您输入密码。安装 homebrew 后,您将看到类似以下截图的输出:

-
接下来,使用以下命令安装 Python:
brew install python -
使用
brew doctor命令检查您的安装,并根据 homebrew 的建议进行操作。 -
一旦
Homebrew安装完成,使用以下命令进行 XGBoost 的 pip 安装:pip install xgboost -
安装完成后,您应该能够将 XGBoost 导入到 IPython 环境中。
一旦 XGBoost 成功导入到 Jupyter 环境中,您将能够使用库中的函数来声明并存储模型。可以按照以下步骤进行:
- 声明一个名为
new_model的变量来存储模型,并使用以下命令声明所有超参数:
new_model = xgboost.XGBRegressor(n_estimators=750, learning_rate=0.09, gamma=0, subsample=0.65, colsample_bytree=1, max_depth=7)
- 前述命令的输出必须看起来像以下截图所示:

- 将数据拆分为测试集和训练集,并使用以下命令将新模型拟合到拆分的数据上:
from sklearn.model_selection import train_test_split
traindf, testdf = train_test_split(x_train, test_size = 0.2)
new_model.fit(x_train,y_train)
- 此时,您将看到类似以下截图的输出:

- 最后,使用新训练的模型来预测房价,并使用以下命令评估新模型:
from sklearn.metrics import explained_variance_score
predictions = new_model.predict(x_test)
print(explained_variance_score(predictions,y_test))
- 执行前述命令后,您应该会看到类似以下截图的输出:

-
请注意,新模型的准确率现在是 87.79%,大约为 88%。这被认为是最优的。
-
在这种情况下,
number of estimators设置为 750。经过在 100 到 1,000 之间的实验后,确定 750 个估算器能够提供最佳的准确率。learning rate设置为 0.09。Subsample rate设置为 65%。Max_depth设置为 7。似乎max_depth对模型准确性的影响不大。然而,使用较慢的学习率时,准确率有所提高。通过调整各种超参数,我们进一步将准确率提高到 89%。 -
接下来的步骤包括对卧室、浴室、楼层、邮政编码等变量进行独热编码,并在模型拟合之前对所有变量进行标准化。尝试调优超参数,如 XGBoost 模型中的学习率、估算器数量、子采样率等,观察它们如何影响模型的准确性。这部分留给读者作为练习。
-
此外,您可能希望尝试在 XGBoost 中使用交叉验证,以找出模型中树的最佳数量,这将进一步提高准确性。
-
另一个可以尝试的练习是在训练过程中使用不同大小的测试和训练数据集,并在训练中加入
date变量。在我们的案例中,我们已将其拆分为 80%的训练数据和 20%的测试数据。尝试将测试集增加到 40%,并查看模型准确度的变化。
另请参阅
访问以下链接了解如何调整 XGBoost 模型中的超参数以及如何在 XGBoost 中实施交叉验证:
xgboost.readthedocs.io/en/latest/python/index.html
xgboost.readthedocs.io/en/latest/get_started/
第九章:使用 LSTM 预测苹果股票市场成本
股票市场预测已经进行多年,并且催生了一个完整的预测行业。考虑到如果预测准确,它可以带来可观的利润,这一点不应令人惊讶。理解何时是买入或卖出股票的最佳时机是掌握华尔街的关键。本章将重点介绍如何使用 Keras 上的 LSTM 创建深度学习模型,以预测苹果公司(AAPL)的股市报价。
本章将介绍以下食谱:
-
下载苹果公司的股市数据
-
探索并可视化苹果公司的股市数据
-
准备股票数据以供模型性能评估
-
构建 LSTM 模型
-
评估 LSTM 模型
下载苹果公司的股市数据
有许多资源可以下载苹果公司的股市数据。为了我们的目的,我们将使用 Yahoo! Finance 网站。
准备开始
本部分需要初始化一个 Spark 集群,供本章所有食谱使用。可以通过终端使用sparknotebook来初始化 Spark 笔记本,如下图所示:

可以通过以下脚本在 Jupyter 笔记本中初始化SparkSession:
spark = SparkSession.builder \
.master("local") \
.appName("StockMarket") \
.config("spark.executor.memory", "6gb") \
.getOrCreate()
如何操作...
以下部分将介绍如何下载苹果公司历史股市数据的步骤。
-
访问以下网站,以追踪苹果公司(股票代码:AAPL)的每日历史调整收盘股价:
finance.yahoo.com/quote/AAPL/history -
在“历史数据”标签中设置并应用以下参数:
-
时间范围:2000 年 1 月 1 日 - 2018 年 4 月 30 日。
-
显示:历史价格。
-
频率:每日。
-
-
点击下载数据链接,将数据集下载为
.csv文件,如下图所示:

- 下载文件
AAPL.csv,然后使用以下脚本将相同的数据集上传到 Spark 数据框:
df =spark.read.format('com.databricks.spark.csv')\
.options(header='true', inferschema='true')\
.load('AAPL.csv')
如何操作...
以下部分将解释如何将股市数据整合到 Jupyter 笔记本中。
-
Yahoo! Finance 是一个提供上市公司股市报价的好来源。苹果公司的股市报价(AAPL)在纳斯达克交易,历史报价可以用于模型开发和分析。Yahoo! Finance 提供了按日、周或月捕捉股市报价的选项。
-
本章的目的是按日预测股票,因为这将拉入最多的数据来训练我们的模型。我们可以通过追溯数据从 2000 年 1 月 1 日到 2018 年 4 月 30 日来实现这一点。
-
一旦我们设置好下载的参数,我们就可以从 Yahoo! Finance 获取格式良好的 CSV 文件,该文件可以轻松地转换为 Spark 数据框,几乎没有问题。
-
数据框将允许我们查看股票的日期、开盘价、最高价、最低价、收盘价、调整后收盘价和成交量。数据框中的列跟踪当天的开盘和收盘股价,以及当天交易的最高和最低股价。当天交易的股票数量也会被记录下来。执行
df.show()可以显示 Spark 数据框df的输出,具体如以下截图所示:

还有更多...
Python 曾经有股票市场 API,允许你自动连接并获取像苹果公司这样的上市公司股票报价。你需要输入参数并检索可以存储在数据框中的数据。然而,从 2018 年 4 月起,Yahoo! Finance API 已不再运行,因此不再是提取本章数据的可靠解决方案。
另见
Pandas_datareader是一个非常强大的库,用于从 Yahoo! Finance 等网站提取数据。要了解更多关于该库的信息,以及它如何在 Yahoo! Finance 重新上线后连接到该网站,请访问以下网站:
github.com/pydata/pandas-datareader
探索和可视化苹果公司股票市场数据
在对数据进行任何建模和预测之前,首先探索并可视化手头的数据,以发现任何潜在的亮点是非常重要的。
准备就绪
本节将对数据框进行转换和可视化操作。这将需要在 Python 中导入以下库:
-
pyspark.sql.functions -
matplotlib
如何操作...
以下部分介绍了探索和可视化股票市场数据的步骤。
- 使用以下脚本转换数据框中的
Date列,通过去除时间戳:
import pyspark.sql.functions as f
df = df.withColumn('date', f.to_date('Date'))
- 创建一个 for 循环,向数据框中添加三个额外的列。该循环将
date字段拆分为year、month和day,如下脚本所示:
date_breakdown = ['year', 'month', 'day']
for i in enumerate(date_breakdown):
index = i[0]
name = i[1]
df = df.withColumn(name, f.split('date', '-')[index])
-
使用以下脚本将 Spark 数据框的子集保存到名为
df_plot的pandas数据框中:df_plot = df.select('year', 'Adj Close').toPandas()。 -
使用以下脚本在笔记本中绘制并可视化
pandas数据框df_plot:
from matplotlib import pyplot as plt
%matplotlib inline
df_plot.set_index('year', inplace=True)
df_plot.plot(figsize=(16, 6), grid=True)
plt.title('Apple stock')
plt.ylabel('Stock Quote ($)')
plt.show()
-
使用以下脚本计算我们的 Spark 数据框的行数和列数:
df.toPandas().shape。 -
执行以下脚本来确定数据框中的空值:
df.dropna().count()。 -
执行以下脚本以提取
Open、High、Low、Close和Adj Close的统计数据:
df.select('Open', 'High', 'Low', 'Close', 'Adj Close').describe().show()
它是如何工作的...
以下部分解释了探索性数据分析中使用的技术和获得的洞察。
- 数据框中的
date列更像是一个日期时间列,所有时间值都以00:00:00结尾。对于我们在建模过程中所需的内容,这是不必要的,因此可以从数据集中删除。幸运的是,PySpark 有一个to_date函数,可以轻松地完成这项操作。数据框df使用withColumn()函数进行转换,现在只显示没有时间戳的日期列,如以下截图所示:

- 为了分析目的,我们希望从
date列中提取day、month和year。我们可以通过遍历自定义列表date_breakdown,以-分隔日期,然后使用withColumn()函数为年份、月份和日期添加新列。添加新列后的更新数据框可以在以下截图中看到:

一个重要的结论是,PySpark也有一个 SQL 函数用于日期,可以从日期时间戳中提取天、月或年。例如,如果我们要向数据框添加一个月份列,可以使用以下脚本:df.withColumn("month",f.month("date")).show()。这突出了在 Spark 中转换数据的多种方法。
-
Spark 数据框在可视化功能上不如
pandas数据框。因此,我们将从 Spark 数据框df中提取两列,并将它们转换为pandas数据框,用于绘制线性图或时间序列图。y 轴将是股票的调整后收盘价,x 轴将是日期的年份。 -
pandas 数据框
df_plot准备好用于绘制图表,一旦设置了一些格式化功能,如网格可见性、图表大小和标题及坐标轴标签。此外,我们明确指定数据框的索引需要指向年份列。否则,默认的索引会出现在 x 轴上,而不是年份。最终的时间序列图可以在以下截图中看到:

-
苹果在过去 18 年经历了广泛的增长。虽然有几年出现了下滑,但总体趋势是稳定向上的,近几年股价徘徊在$150 到$175 之间。
-
到目前为止,我们已经对数据框做了一些更改,因此重要的是要获取行和列的总数,这会影响数据集稍后如何分割用于测试和训练。如以下截图所示,我们总共有 10 列和 4,610 行:

-
执行
df.dropna().count()时,我们可以看到行数仍为 4,610,这与上一步的行数相同,表明没有任何行包含空值。 -
最后,我们可以对每个将用于模型的列进行行数、均值、标准差、最小值和最大值的统计。这有助于识别数据中是否存在异常。需要注意的一点是,所有五个将在模型中使用的字段的标准差都高于均值,这表明数据分布较广,并非都集中在均值附近。以下截图显示了 Open、High、Low、Close 和 Adj Close 的统计数据:

还有更多内容…
虽然 Spark 中的数据框没有pandas数据框那样的本地可视化功能,但有些公司提供企业级 Spark 管理服务,允许通过笔记本进行高级可视化,而无需使用matplotlib等库。Databricks 就是其中一家提供此功能的公司。
以下是使用 Databricks 笔记本内置功能进行可视化的示例:

另见
要了解更多关于 Databricks 的信息,请访问以下网站:databricks.com/。
要了解更多关于 Databricks 笔记本中可视化的信息,请访问以下网站:docs.databricks.com/user-guide/visualizations/index.html。
要了解如何通过 Microsoft Azure 订阅访问 Databricks,请访问以下网站:
azure.microsoft.com/en-us/services/databricks/
为模型性能准备股票数据
我们几乎准备好为苹果股票的价值表现构建预测算法了。剩下的任务是以确保最佳预测结果的方式准备数据。
准备就绪
在本节中,我们将对数据框执行转换和可视化操作。这需要导入以下 Python 库:
-
numpy -
MinMaxScaler()
如何操作…
本节将介绍为我们的模型准备股票市场数据的步骤。
- 执行以下脚本,通过
Adj Close计数对年份列进行分组:
df.groupBy(['year']).agg({'Adj Close':'count'})\
.withColumnRenamed('count(Adj Close)', 'Row Count')\
.orderBy(["year"],ascending=False)\
.show()
- 执行以下脚本以创建两个新的数据框,用于训练和测试:
trainDF = df[df.year < 2017]
testDF = df[df.year > 2016]
- 使用以下脚本将两个新的数据框转换为
pandas数据框,以便使用toPandas()获取行列计数:
trainDF.toPandas().shape
testDF.toPandas().shape
- 正如我们之前对
df做的那样,我们使用以下脚本对trainDF和testDF进行可视化:
trainDF_plot = trainDF.select('year', 'Adj Close').toPandas()
trainDF_plot.set_index('year', inplace=True)
trainDF_plot.plot(figsize=(16, 6), grid=True)
plt.title('Apple Stock 2000-2016')
plt.ylabel('Stock Quote ($)')
plt.show()
testDF_plot = testDF.select('year', 'Adj Close').toPandas()
testDF_plot.set_index('year', inplace=True)
testDF_plot.plot(figsize=(16, 6), grid=True)
plt.title('Apple Stock 2017-2018')
plt.ylabel('Stock Quote ($)')
plt.show()
- 我们根据数据框(日期列除外)创建两个新数组,
trainArray和testArray,使用以下脚本:
import numpy as np
trainArray = np.array(trainDF.select('Open', 'High', 'Low', 'Close','Volume', 'Adj Close' ).collect())
testArray = np.array(testDF.select('Open', 'High', 'Low', 'Close','Volume', 'Adj Close' ).collect())
- 为了将数组缩放到 0 和 1 之间,导入
sklearn中的MinMaxScaler,并使用以下脚本创建一个名为MinMaxScale的函数:
from sklearn.preprocessing import MinMaxScaler
minMaxScale = MinMaxScaler()
- 然后,将
MinMaxScaler应用于trainArray,并使用以下脚本创建两个经过缩放的新数组:
minMaxScale.fit(trainArray)
testingArray = minMaxScale.transform(testArray)
trainingArray = minMaxScale.transform(trainArray)
- 使用以下脚本将
testingArray和trainingArray都拆分为特征x和标签y:
xtrain = trainingArray[:, 0:-1]
xtest = testingArray[:, 0:-1]
ytrain = trainingArray[:, -1:]
ytest = testingArray[:, -1:]
- 执行以下脚本来检索所有四个数组形状的最终清单:
print('xtrain shape = {}'.format(xtrain.shape))
print('xtest shape = {}'.format(xtest.shape))
print('ytrain shape = {}'.format(ytrain.shape))
print('ytest shape = {}'.format(ytest.shape))
- 执行以下脚本绘制报价
open、high、low和close的训练数组:
plt.figure(figsize=(16,6))
plt.plot(xtrain[:,0],color='red', label='open')
plt.plot(xtrain[:,1],color='blue', label='high')
plt.plot(xtrain[:,2],color='green', label='low')
plt.plot(xtrain[:,3],color='purple', label='close')
plt.legend(loc = 'upper left')
plt.title('Open, High, Low, and Close by Day')
plt.xlabel('Days')
plt.ylabel('Scaled Quotes')
plt.show()
- 此外,我们使用以下脚本绘制
volume的训练数组:
plt.figure(figsize=(16,6))
plt.plot(xtrain[:,4],color='black', label='volume')
plt.legend(loc = 'upper right')
plt.title('Volume by Day')
plt.xlabel('Days')
plt.ylabel('Scaled Volume')
plt.show()
它是如何工作的...
本节解释了模型中所需的数据转换。
- 构建模型的第一步是将数据分割成训练数据集和测试数据集,以进行模型评估。我们的目标是使用 2000 年到 2016 年的所有股票报价来预测 2017-2018 年的股票趋势。我们从之前的部分得知,我们总共有 4,610 天的股票报价,但我们并不确切知道每年有多少数据。我们可以在数据框中使用
groupBy()函数来获取每年股票报价的唯一计数,详情请参见以下截图:

-
2016 年和 2017 年的合并数据大约占总数据的 7%,这个测试数据集的比例稍显较小。然而,出于此模型的目的,这应该足够。剩余的 93% 数据集将用于 2000 到 2016 年之间的训练。因此,通过过滤器创建了两个数据框,以确定是否包含或排除 2016 年之前或之后的行。
-
我们现在可以看到,测试数据集
testDF包含 333 行,而训练数据集trainDF包含 4,277 行。当两者合并时,我们得到了来自原始数据框df的总行数 4,610。最后,我们看到testDF仅由 2017 年和 2018 年的数据组成,其中 2017 年有 251 行,2018 年有 82 行,总共 333 行,详情请参见以下截图:

请注意,每当我们将 Spark 数据框转换为 pandas 数据框时,它可能并不总是适合大数据。虽然在我们使用相对较小的数据集时它可以正常工作,但转换为 pandas 数据框意味着所有数据都将加载到主驱动程序的内存中。一旦发生此转换,数据不会存储在 Spark 工作节点中,而是传输到主驱动节点。这并不是最优的,可能会导致内存溢出错误。如果你发现需要从 Spark 转换为 pandas 数据框来可视化数据,建议从 Spark 中抽取一个随机样本,或将 Spark 数据聚合成一个更易处理的数据集,然后在 pandas 中进行可视化。
- 一旦数据的子集通过
toPandas()转换,我们就可以使用matplotlib来可视化测试和训练数据框,利用pandas内建的图形功能。将数据框并排显示可以展示出,在未缩放的情况下,调整后的收盘价的 y 轴看起来相似。实际上,我们可以看到trainDF_plot接近于 0 开始,而testDF_plot则从接近 110 的位置开始,如下两张截图所示:


- 目前我们的股票值并不适合深度学习建模,因为没有用于归一化或标准化的基准。在使用神经网络时,最好将值保持在 0 和 1 之间,以匹配在用于激活的 sigmoid 或 step 函数中找到的结果。为了实现这一点,我们必须首先将
pyspark数据框trainDF和testDF转换为numpy数组,即trainArray和testArray。由于它们现在是数组而不是数据框,我们将不使用日期列,因为神经网络只对数值感兴趣。每个数组的第一个值可以在以下截图中看到:

-
缩放数组值到 0 和 1 之间有很多种方法。它涉及使用以下公式:
scaled array value = (array value - min array value) / (max array value - min array value)。幸运的是,我们无需手动进行这个计算。我们可以利用sklearn中的MinMaxScaler()函数来缩放这两个数组。 -
MinMaxScaler()函数会在训练数组trainArray上进行拟合,然后应用于创建两个新的数组,trainingArray和testingArray,它们的值会被缩放到 0 到 1 之间。每个数组的第一行可以在以下截图中看到:

- 现在,我们准备通过将数组切分成 x 和 y 来设置标签和特征变量,供训练和测试使用。数组中的前五个元素是特征或 x 值,最后一个元素是标签或 y 值。特征包括 Open、High、Low、Close 和 Volume 的值。标签由 Adj Close 组成。
trainingArray的第一行分解可以在以下截图中看到:

- 查看我们将在模型中使用的四个数组的形状,可以确认我们有 4,227 行训练数据、333 行测试数据、5 个特征元素(
x)和 1 个标签元素(y),如以下截图所示:

xtrain训练数组的值,包括开盘价、最低价、最高价和收盘价,可以使用新的调整后的 0 到 1 之间的比例绘制,如下图所示:

- 此外,体积也可以使用归一化后的体积得分(在 0 和 1 之间)进行绘制,如下图所示:

还有更多内容...
尽管我们确实使用了来自sklearn的MinMaxScaler,但同样重要的是要理解,pyspark.ml.feature中也有一个MinMaxScaler函数。它的功能完全相同,都是将每个特征重新缩放到 0 到 1 之间。如果我们在本章中通过 PySpark 原生的机器学习库进行预测,我们将使用来自pyspark.ml.feature的MinMaxScaler。
另见
要了解更多关于sklearn中MinMaxScaler的信息,请访问以下网站:
scikit-learn.org/stable/modules/generated/sklearn.preprocessing.MinMaxScaler.html.
要了解更多关于pyspark中MinMaxScaler的信息,请访问以下网站:
spark.apache.org/docs/2.2.0/ml-features.html#minmaxscaler.
构建 LSTM 模型
数据现在已转换为与 Keras 中的 LSTM 模型开发兼容的格式。因此,我们将在本节中设置并配置深度学习模型,以预测 2017 年和 2018 年苹果公司的股票报价。
准备工作
我们将在本节中对模型进行管理和超参数调优。这将需要在 Python 中导入以下库:
from keras import models
from keras import layers
如何操作...
本节将引导您完成设置和调优 LSTM 模型的步骤。
- 使用以下脚本从
keras导入以下库:
from keras import models, layers
- 使用以下脚本构建一个
Sequential模型:
model = models.Sequential()
model.add(layers.LSTM(1, input_shape=(1,5)))
model.add(layers.Dense(1))
model.compile(loss='mean_squared_error', optimizer='adam')
- 使用以下脚本将测试集和训练集转换为三维数组:
xtrain = xtrain.reshape((xtrain.shape[0], 1, xtrain.shape[1]))
xtest = xtest.reshape((xtest.shape[0], 1, xtest.shape[1]))
- 使用一个名为
loss的变量通过以下脚本拟合model:
loss = model.fit(xtrain, ytrain, batch_size=10, epochs=100)
- 使用以下脚本创建一个新的数组
predicted:
predicted = model.predict(xtest)
- 使用以下脚本将
predicted和ytest数组合并为一个统一的数组combined_array:
combined_array = np.concatenate((ytest, predicted), axis = 1)
它是如何工作的...
本节解释了如何配置 LSTM 神经网络模型以在我们的数据集上进行训练。
-
构建 LSTM 模型时,大部分功能来自于
keras的models和layers模块。 -
已构建的
LSTM模型将使用Sequential类进行定义,这种类非常适合处理具有序列依赖的时间序列数据。LSTM 模型的input_shape = (1,5)表示我们的训练数据集中有一个因变量和五个自变量。由于我们希望保持模型简单,因此只会使用一个Dense层来定义神经网络。编译 keras 模型时需要指定损失函数,因为我们正在处理一个循环神经网络,使用mean_squared_error(均方误差)计算最能反映预测值与实际值之间的差距。最后,模型编译时还需要定义优化器,以调整神经网络中的权重。adam优化器给出了很好的结果,尤其是在与循环神经网络配合使用时。 -
我们当前的数组
xtrain和xtest是二维数组;然而,为了将它们整合进 LSTM 模型,它们需要使用reshape()转换为三维数组,具体如以下截图所示:

- LSTM 模型使用
xtrain和ytrain进行拟合,批量大小设定为 10,训练 100 个 epochs。批量大小是定义一起训练的对象数量的设置。我们可以根据需要设置较低或较高的批量大小,但需要注意的是,批量数越少,所需的内存越多。此外,epoch 是衡量模型通过整个数据集的次数。最终,这些参数可以根据时间和内存分配进行调整。
每个 epoch 中的均方误差损失都被捕获并可视化。经过第五或第六个 epoch 后,我们可以看到损失逐渐减小,如以下截图所示:

- 我们现在可以创建一个新的数组
predicted,它基于应用于xtest的拟合模型,然后将其与ytest合并,进行并排比较,以便进行准确性验证。
另请参见
要了解更多关于 keras 中参数调整模型的内容,请访问以下网站:keras.io/models/model/
评估模型
现在到了关键时刻:我们将看看我们的模型是否能够为 2017 年和 2018 年的 AAPL 股票提供准确的预测。
准备工作
我们将使用均方误差进行模型评估。因此,我们需要导入以下库:
import sklearn.metrics as metrics
如何操作...
本节演示了如何可视化并计算 2017 年和 2018 年苹果股票的实际与预测股价。
- 使用以下脚本绘制
实际与预测股票的并排比较图,以便比较趋势:
plt.figure(figsize=(16,6))
plt.plot(combined_array[:,0],color='red', label='actual')
plt.plot(combined_array[:,1],color='blue', label='predicted')
plt.legend(loc = 'lower right')
plt.title('2017 Actual vs. Predicted APPL Stock')
plt.xlabel('Days')
plt.ylabel('Scaled Quotes')
plt.show()
- 使用以下脚本计算实际
ytest与predicted(预测的)股票之间的均方误差:
import sklearn.metrics as metrics
np.sqrt(metrics.mean_squared_error(ytest,predicted))
它是如何工作的...
本节解释了 LSTM 模型评估的结果。
- 从图形的角度来看,我们可以看到我们的预测与 2017 年至 2018 年的实际股票报价非常接近,如下面的屏幕截图所示:

- 我们的模型显示,预测值与 2017 年和 2018 年较早的实际值更接近。总体而言,虽然我们预测的分数与实际分数非常接近,但最好还是进行均方误差计算,以了解两者之间的偏差。正如我们所看到的,我们的均方误差为 0.05841,或约为 5.8%:

另请参阅
为了更好地了解在 sklearn 中如何计算均方误差,请访问以下网站:
scikit-learn.org/stable/modules/generated/sklearn.metrics.mean_squared_error.html.
第十章:使用深度卷积网络进行面部识别
在本章中,我们将涵盖以下几个教程:
-
下载并将 MIT-CBCL 数据集加载到内存中
-
从目录中绘制和可视化图像
-
图像预处理
-
模型构建、训练和分析
介绍
在当今世界,信息安全的维护变得越来越重要,同时也越来越困难。有多种方法可以加强这种安全性(如密码、指纹识别、PIN 码等)。然而,在使用方便性、准确性和低干扰性方面,面部识别算法表现非常出色。随着高速计算的普及和深度卷积网络的发展,这些算法的鲁棒性得到了进一步提高。它们已经发展得如此先进,以至于现在在许多电子设备(例如 iPhoneX)甚至银行应用中作为主要的安全特性。 本章的目标是开发一个稳健的、对姿势不变的面部识别算法,用于安全系统。在本章中,我们将使用公开提供的MIT-CBCL数据集,其中包含 10 个不同主题的面部图像。
下载并将 MIT-CBCL 数据集加载到内存中
在这个教程中,我们将了解如何下载 MIT-CBCL 数据集并将其加载到内存中。
随着到 2025 年预计价值达到 150 亿美元,生物识别行业正准备迎来前所未有的增长。一些用于生物识别认证的生理特征包括指纹、DNA、面部、视网膜或耳朵特征以及声音。虽然 DNA 认证和指纹技术已经相当先进,但面部识别也带来了其独特的优势。
由于深度学习模型的最新发展,使用便捷性和鲁棒性是面部识别算法如此受欢迎的驱动因素之一。
准备工作
以下关键点需要在此教程中考虑:
-
MIT-CBCL数据集包含 3,240 张图片(每个主题 324 张图片)。在我们的模型中,我们将安排数据增强,以提高模型的鲁棒性。我们将采用诸如平移、旋转、缩放和剪切等技术来获得这些增强数据。 -
我们将使用 20%的数据集来测试我们的模型(648 张图片),通过从数据集中随机选择这些图片。类似地,我们随机选择数据集中 80%的图片,并将其作为我们的训练数据集(2,592 张图片)。
-
最大的挑战是将图像裁剪成完全相同的大小,以便可以输入神经网络。
-
众所周知,当所有输入图像的大小相同时,设计网络要容易得多。然而,由于这些图像中的一些主题有侧面或旋转/倾斜的侧面,我们必须调整我们的网络以接受不同大小的输入图像。
如何实现...
步骤如下。
-
通过访问 FACE RECOGNITION HOMEPAGE 下载
MIT-CBCL数据集,其中包含多个用于人脸识别实验的数据库。以下提供了该主页的链接及截图:

- 导航到名为 MIT-CBCL 人脸识别数据库的链接并点击,如下图所示:

- 一旦点击它,您将进入一个许可页面,您需要接受许可协议并继续前往下载页面。在下载页面,点击
download now,下载一个约 116 MB 的 zip 文件。然后解压该文件到工作目录中。
它是如何工作的...
功能如下:
-
许可协议要求在任何项目中使用该数据库时进行适当引用。该数据库由麻省理工学院的研究团队开发。
-
特此感谢麻省理工学院以及生物与计算学习中心提供的人脸图像数据库。许可协议还要求提及论文标题 Component-based Face Recognition with 3D Morphable Models, First IEEE Workshop on Face Processing in Video, Washington, D.C., 2004, B. Weyrauch, J. Huang, B. Heisele, 和 V. Blanz。
-
以下截图描述了许可协议以及下载数据集的链接:

人脸识别数据库主页
-
下载并解压数据集后,您将看到一个名为 MIT-CBCL-facerec-database 的文件夹。
-
对于本章内容,我们只使用
training-synthetic文件夹中的图像,该文件夹包含所有 3,240 张图像,如下图所示:

还有更多内容...
本章内容中,您需要通过 Python 导入以下库:
-
os -
matplotlib -
numpy -
keras -
TensorFlow
本章的以下部分将处理导入必要的库以及在构建神经网络模型并将其加载之前对图像进行预处理。
另见
有关本章中使用的包的完整信息,请访问以下链接:
从目录中绘制和可视化图像
本节将描述如何在图像被预处理并输入神经网络进行训练之前,读取和可视化下载的图像。这是本章的重要步骤,因为需要可视化图像,以便更好地理解图像的大小,从而准确裁剪去除背景,仅保留必要的面部特征。
准备工作
在开始之前,完成导入必要库和函数的初始设置,并设置工作目录的路径。
如何操作...
步骤如下:
- 使用以下代码行下载所需的库。输出应该显示一行
Using TensorFlow backend,如接下来的截图所示:
%matplotlib inline
from os import listdir
from os.path import isfile, join
import matplotlib.pyplot as plt
import matplotlib.image as mpimg
import numpy as np
from keras.models import Sequential
from keras.layers import Dense, Dropout, Activation, Flatten, Conv2D
from keras.optimizers import Adam
from keras.layers.normalization import BatchNormalization
from keras.utils import np_utils
from keras.layers import MaxPooling2D
from keras.preprocessing.image import ImageDataGenerator
库的导入如图所示:

- 打印并设置当前工作目录,如下图所示。在我们的例子中,桌面被设置为工作目录:

- 使用下图所示的命令,直接从文件夹中读取所有图像:

- 使用
plt.imshow(images[])命令打印数据集中的一些随机图像,如下图所示,以便更好地了解图像中的面部轮廓。这也有助于了解图像的大小,后续步骤中需要用到:

- 这里展示的是来自第一张图片的不同测试对象的图像。



工作原理...
功能如下:
-
mypath变量设置了读取所有文件的路径。在这一步中,指定了training-synthetic文件夹,因为本章将只使用该文件夹中的文件。 -
onlyfiles变量用于计算在前一步提供的文件夹路径下的所有文件,通过遍历文件夹中的所有文件。这将在下一步中用于读取和存储图像。 -
images变量用于创建一个大小为 3,240 的空数组,用于存储图像,这些图像的大小为 200 x 200 像素。 -
接下来,通过在 for 循环中使用
onlyfiles变量作为参数遍历所有文件,读取文件夹中包含的每个图像,并使用matplotlib.image函数将其存储到之前定义的images数组中。 -
最后,通过指定不同的图像索引打印随机选择的图像时,您会注意到每张图像是一个 200 x 200 像素的数组,每个主体可能正面朝向,或者在左右两侧之间旋转零至十五度。
还有更多内容...
以下几点需要注意:
-
这个数据库的一个有趣特征是,每个文件名的第四个数字描述了该图像中的主体。
-
图像的名称是唯一的,第四个数字表示该图像中的个人。两个图像名称的例子是
0001_-4_0_0_60_45_1.pgm和0006_-24_0_0_0_75_15_1.pgm。可以很容易地理解,第四个数字分别代表第二个和第七个个体。 -
我们需要为后续预测存储这些信息。这将帮助神经网络在训练时知道它正在学习哪个主体的面部特征。
-
每个图像的文件名可以读入一个数组中,并且通过以下代码行,可以将十个主体分开:
y =np.empty([3240,1],dtype=int)
for x in range(0, len(onlyfiles)):
if onlyfiles[x][3]=='0': y[x]=0
elif onlyfiles[x][3]=='1': y[x]=1
elif onlyfiles[x][3]=='2': y[x]=2
elif onlyfiles[x][3]=='3': y[x]=3
elif onlyfiles[x][3]=='4': y[x]=4
elif onlyfiles[x][3]=='5': y[x]=5
elif onlyfiles[x][3]=='6': y[x]=6
elif onlyfiles[x][3]=='7': y[x]=7
elif onlyfiles[x][3]=='8': y[x]=8
elif onlyfiles[x][3]=='9': y[x]=9
-
上述代码将初始化一个大小为 3,240 的空一维
numpy数组(training-synthetic文件夹中的图像数量),并通过循环遍历整个文件集,将相关的主体存储在不同的数组中。 -
if语句基本上是在检查每个文件名中的第四个数字,并将该数字存储在已初始化的numpy数组中。 -
以下截图显示了 iPython 笔记本中的输出:

另见
以下博客描述了一种在 Python 中裁剪图像的方法,可以用于图像预处理,这在接下来的章节中会用到:
关于 Adam 优化器及其使用案例的更多信息,可以通过访问以下链接找到:
图像预处理
在上一节中,您可能已经注意到,并非所有图像都是面部的正面视图,还有些略微旋转的侧面轮廓。您可能还注意到每张图像中有些不必要的背景区域需要去除。本节将描述如何预处理和处理图像,使其准备好输入到网络中进行训练。
准备就绪
请考虑以下内容:
-
有许多算法被设计用来裁剪图像的显著部分;例如,SIFT、LBP、Haar-cascade 滤波器等等。
-
然而,我们将通过一个非常简单的朴素代码来解决这个问题,从图像中裁剪出面部部分。这是这个算法的一大创新。
-
我们发现不必要的背景部分的像素强度为 28。
-
记住,每个图像都是一个 200 x 200 像素的三通道矩阵。这意味着每个图像包含三个矩阵或张量,分别代表红色、绿色和蓝色的像素,强度范围从 0 到 255。
-
因此,我们将丢弃任何包含仅为 28 的像素强度的图像的行或列。
-
我们还将确保在裁剪操作后,所有图像都具有相同的像素大小,以实现卷积神经网络的最高并行化能力。
如何实现...
步骤如下:
- 定义
crop()函数来裁剪图像,只保留显著部分,如以下代码所示:
#function for cropping images to obtain only the significant part
def crop(img):
a=28*np.ones(len(img))
b=np.where((img== a).all(axis=1))
img=np.delete(img,(b),0)
plt.imshow(img)
img=img.transpose()
d=28*np.ones(len(img[0]))
e=np.where((img== d).all(axis=1))
img=np.delete(img,e,0)
img=img.transpose()
print(img.shape)
super_threshold_indices = img < 29
img[super_threshold_indices] = 0
plt.imshow (img)
return img[0:150, 0:128]
- 使用以下代码行循环遍历文件夹中的每个图像,并使用前面定义的函数裁剪它:
#cropping all the images
image = np.empty([3240,150,128],dtype=int)
for n in range(0, len(images)):
image[n]=crop(images[n])
- 接下来,随机选择一张图像并打印出来,检查它是否已经从 200 x 200 大小的图像裁剪为不同的尺寸。我们在这个案例中选择了图像 23。可以使用以下代码行完成此操作:
print (image[22])
print (image[22].shape)
- 接下来,将数据拆分为测试集和训练集,使用文件夹中的
80%图像作为训练集,剩余的20%作为测试集。可以使用以下命令完成此操作:
# Split data into 80/20 split for testing and training
test_ind=np.random.choice(range(3240), 648, replace=False) train_ind=np.delete(range(0,len(onlyfiles)),test_ind)
- 一旦数据完成拆分,使用以下命令将训练图像和测试图像分开:
# slicing the training and test images
y1_train=y[train_ind]
x_test=image[test_ind]
y1_test=y[test_ind]
- 接下来,将所有裁剪后的图像调整为 128 x 150 的大小,因为这是要输入神经网络的大小。可以使用以下命令完成此操作:
#reshaping the input images
x_train = x_train.reshape(x_train.shape[0], 128, 150, 1)
x_test = x_test.reshape(x_test.shape[0], 128, 150, 1)
- 一旦数据完成调整形状,将其转换为
float32类型,这将使得在下一个步骤进行归一化时更容易处理。从 int 类型转换为 float32 类型可以使用以下命令:
#converting data to float32
x_train = x_train.astype('float32')
x_test = x_test.astype('float32')
- 在重新调整形状并将数据转换为 float32 类型后,必须对其进行归一化,以便将所有值调整到相似的尺度。这是防止数据冗余的一个重要步骤。使用以下命令进行归一化:
#normalizing data
x_train/=255
x_test/=255
#10 digits represent the 10 classes
number_of_persons = 10
- 最后一步是将调整形状并归一化后的图像转换为向量,因为这是神经网络能够理解的唯一输入形式。使用以下命令将图像转换为向量:
#convert data to vectors
y_train = np_utils.to_categorical(y1_train, number_of_persons)
y_test = np_utils.to_categorical(y1_test, number_of_persons)
它是如何工作的...
功能如下:
-
crop()函数执行以下任务:-
将所有强度为 28 的像素与一个全是 1 的 numpy 数组相乘,并存储在变量
a中。 -
检查所有完全由像素强度为 28 的列组成的实例,并将其存储在变量
b中。 -
删除所有列(或Y轴),如果整个列的像素强度都是 28。
-
绘制结果图像。
-
-
- 转置图像,以便在所有行(或X轴)上执行前述一系列操作。
-
-
将所有像素强度为 28 的像素与 1 的
numpy数组相乘,并将其存储在变量d中。 -
检查所有实例,其中整列仅由像素强度为 28 的像素组成,并将其存储在变量
e中。 -
删除所有列(来自转置图像),其中整列的像素强度为 28。
-
转置图像,以恢复原始图像。
-
打印图像的形状。
-
每当发现像素强度小于 29 时,将这些像素的强度替换为零,这将通过将它们变为白色来裁剪所有这些像素。
-
绘制结果图像。
-
将结果图像的尺寸调整为 150 x 128 像素。
-
crop()函数的输出,如在 Jupyter Notebook 执行期间所见,展示在以下截图中:

- 接下来,定义的
crop()函数将应用于training-synthetic文件夹中包含的所有文件,通过遍历每个文件。这将导致如下所示的输出:

输出继续如下:

请注意,只有相关的面部特征被保留下来,所有裁剪后的图像的尺寸都小于 200 x 200,这是最初的尺寸。
-
在打印任意图像及其形状时,你会注意到每个图像现在都已调整为 150 x 128 像素的数组,你将看到以下输出:
![]()
-
将图像拆分为测试集和训练集,并将其划分为命名为
x_train、y1_train、x_test和y1_test的变量,将得到以下截图所示的输出:![]()
-
数据的分隔如下进行:

- 重新调整训练和测试图像的形状,并将数据类型转换为 float32,结果如以下截图所示:
![]()
还有更多内容...
请考虑以下内容:
-
一旦图像完成预处理,它们仍然需要标准化并转换为向量(在这种情况下是张量),然后才能输入到网络中。
-
标准化,在最简单的情况下,意味着将不同尺度上测量的值调整为一个公认的共同尺度,通常是在平均化之前。标准化数据始终是个好主意,因为它可以防止在梯度下降过程中出现梯度爆炸或消失问题,如梯度消失和爆炸问题所示。标准化还确保没有数据冗余。
-
数据归一化是通过将每张图像中的每个像素值除以
255来完成的,因为像素值范围是从 0 到255。这将产生如下所示的输出:![]()
-
接下来,使用来自
numpy_utils的to_categorical()函数,将图像转换为具有十个不同类别的输入向量,如下图所示:![]()
另见
其他资源如下:
-
有关数据归一化的更多信息,请查看以下链接:
-
有关过拟合以及为什么数据需要分成测试集和训练集的更多信息,请访问以下链接:
towardsdatascience.com/train-test-split-and-cross-validation-in-python-80b61beca4b6 -
有关编码变量及其重要性的更多信息,请访问以下链接:
模型构建、训练和分析
我们将使用keras库中的标准顺序模型来构建 CNN。该网络将包括三层卷积层、两层最大池化层和四层全连接层。输入层和后续的隐藏层有 16 个神经元,而最大池化层的池大小为(2,2)。四个全连接层由两层密集层、一层展平层和一层丢弃层组成。使用丢弃率 0.25 来减少过拟合问题。这个算法的另一个创新之处在于使用数据增强来对抗过拟合现象。数据增强通过旋转、平移、剪切和缩放图像到不同程度来适应模型。
relu函数作为输入层和隐藏层的激活函数,而softmax分类器则用于输出层,以根据预测输出对测试图像进行分类。
准备开始
将要构建的网络可以通过下图进行可视化:

如何做...
步骤如下:
- 使用以下命令,在 Keras 框架中使用
Sequential()函数定义模型:
model = Sequential()
model.add(Conv2D(16, (3, 3), input_shape=(128,150,1)))
model.add(Activation('relu'))
model.add(Conv2D(16, (3, 3)))
model.add(Activation('relu'))
model.add(MaxPooling2D(pool_size=(2,2)))
model.add(Conv2D(16,(3, 3)))
model.add(Activation('relu'))
model.add(MaxPooling2D(pool_size=(2,2)))
model.add(Flatten())
model.add(Dense(512))
model.add(Activation('relu'))
model.add(Dropout(0.25))
model.add(Dense(10))
model.add(Activation('softmax'))
-
打印模型的摘要,以便更好地理解模型的构建方式,并确保它按照前面的规格构建。这可以通过使用
model.summary()命令来完成。 -
接下来,使用以下命令编译模型:
model.compile(loss='categorical_crossentropy', optimizer=Adam(), metrics= ['accuracy'])
- 为了防止过拟合并进一步提高模型的准确性,实现某种形式的数据增强。在此步骤中,图像将进行剪切、水平和垂直轴上的平移、缩放和旋转。模型学习并识别这些异常的能力将决定模型的鲁棒性。使用以下命令对数据进行增强:
# data augmentation to minimize overfitting
gen = ImageDataGenerator(rotation_range=8,
width_shift_range=0.08, shear_range=0.3,
height_shift_range=0.08,zoom_range=0.08)
test_gen = ImageDataGenerator()
train_generator = gen.flow(x_train, y_train, batch_size=16)
test_generator = test_gen.flow(x_test, y_test, batch_size=16)
- 最后,使用以下命令在数据增强后拟合和评估模型:
model.fit_generator(train_generator, epochs=5, validation_data=test_generator)
scores = model.evaluate(x_test, y_test, verbose=0)
print("Recognition Error: %.2f%%" % (100-scores[1]*100))
它是如何工作的...
功能如下:
-
通过使用顺序函数,定义了一个九层的卷积神经网络,每一层执行以下功能:
-
第一层是一个卷积层,具有 16 个神经元,并对输入张量/矩阵进行卷积。特征图的大小定义为 3 x 3 的矩阵。需要为第一层指定输入形状,因为神经网络需要知道期望的输入类型。由于所有图像已被裁剪为 128 x 150 像素的大小,因此这也将是定义第一层输入形状的标准。该层使用的激活函数是修正线性单元(relu)。
-
网络的第二层(第一个隐藏层)是另一个卷积层,同样包含 16 个神经元。此层的激活函数也将使用
relu。 -
网络的第三层(第二个隐藏层)是一个最大池化层,池化大小为 2 x 2。该层的功能是提取通过前两层卷积学习到的所有有效特征,并减少包含所有学习到的特征的矩阵的大小。卷积不过是特征图和输入矩阵之间的矩阵乘法(在我们的例子中是图像)。形成卷积过程的结果值会被网络存储在矩阵中。这些存储值中的最大值将定义输入图像中的某个特征。最大池化层将保留这些最大值,并丢弃与之无关的特征。
-
网络的第四层(第三个隐藏层)是另一个卷积层,特征图大小再次为 3 x 3。该层使用的激活函数仍然是
relu函数。 -
网络的第五层(第四个隐藏层)是一个最大池化层,池化大小为 2 x 2。
-
网络的第六层(第五个隐藏层)是一个展平层,将包含所有学习到的特征(以数字形式存储)的矩阵转换为单行,而不是多维矩阵。
-
-
-
网络中的第七层(第六个隐藏层)是一个具有 512 个神经元的全连接层,并使用
relu激活函数。每个神经元基本上会处理某个权重和偏置,这只是对特定图像所学习到的所有特征的表示。这样做是为了通过在全连接层上使用softmax分类器,轻松对图像进行分类。 -
网络中的第八层(第七个隐藏层)是一个丢弃层,丢弃概率为 0.25 或 25%。该层将在训练过程中随机
dropout25%的神经元,并通过鼓励网络使用多条替代路径来学习给定特征,从而帮助防止过拟合。 -
网络中的最终层是一个密集层,只有 10 个神经元和
softmax分类器。这是第八个隐藏层,也将作为网络的输出层。
-
-
定义模型后的输出应该类似以下截图:

-
打印
model.summary()函数时,您必须看到类似以下截图的输出:![]()
-
该模型使用类别交叉熵(categorical crossentropy)进行编译,这是一个衡量和计算网络损失的函数,用于在层与层之间传递信息。模型将使用 Keras 框架中的
Adam()优化器函数,该函数将基本决定网络在学习特征时如何优化权重和偏差。model.compile()函数的输出应该类似以下截图:

-
由于神经网络相当密集,并且总图像数量仅为 3,240,我们设计了一种方法来防止过拟合。通过执行数据增强,从训练集生成更多图像来实现这一点。在此步骤中,图像通过
ImageDataGenerator()函数生成。此函数将训练集和测试集作为输入,通过以下方式增强图像:-
旋转它们
-
剪切它们
-
水平平移图像,实际上就是扩宽图像
-
在水平方向上平移图像
-
在垂直方向上平移图像
-
前面的函数的输出应该类似以下截图:

- 最后,模型在训练 5 个 epoch 后被拟合到数据并评估。我们得到的输出如以下截图所示:

- 如您所见,我们获得了 98.46%的准确率,导致了 1.54%的误差率。这个结果相当不错,但卷积网络已经取得了很大进展,我们可以通过调优一些超参数或使用更深的网络来改善这个误差率。
还有更多...
使用更深的 CNN,增加了 12 层(多了一层卷积层和一层最大池化层),将准确率提升至 99.07%,如以下截图所示:

在每两层后使用数据归一化,进一步提高了准确率,达到了 99.85%,如以下截图所示:

你可能会得到不同的结果,但可以多次运行训练步骤。以下是一些你可以在未来进行实验的步骤,以更好地理解网络:
-
尝试更好地调整超参数,并实施更高的丢弃率,看看网络如何响应。
-
当我们尝试使用不同的激活函数或较小(较稀疏)的网络时,准确性大幅下降。
-
同时,改变特征图和最大池化层的大小,观察这如何影响训练时间和模型准确性。
-
尝试在一个较稀疏的卷积神经网络中增加更多的神经元,并调整它以提高准确性。这也可能导致更快的网络,在更短的时间内完成训练。
-
使用更多的训练数据。探索其他在线仓库,找到更大的数据库来训练网络。卷积神经网络通常在训练数据量增大时表现更好。
另见
以下已发布的论文是了解卷积神经网络的好资源。它们可以作为进一步阅读材料,帮助你更深入地了解卷积神经网络在各类应用中的应用:
-
papers.nips.cc/paper/4824-imagenet-classification-with-deep-convolutional-neural-networks -
www.cs.cmu.edu/~bhiksha/courses/deeplearning/Fall.2016/pdfs/Simard.pdf -
openaccess.thecvf.com/content_cvpr_2014/papers/Oquab_Learning_and_Transferring_2014_CVPR_paper.pdf -
www.aaai.org/ocs/index.php/IJCAI/IJCAI11/paper/download/3098/3425
第十一章:使用 Word2Vec 创建和可视化词向量
在本章中,我们将介绍以下内容:
-
获取数据
-
导入必要的库
-
数据准备
-
构建和训练模型
-
进一步可视化
-
进一步分析
介绍
在对文本数据进行神经网络训练并使用 LSTM 单元生成文本之前,理解文本数据(例如词语、句子、客户评论或故事)如何首先转换为词向量,并输入到神经网络中是非常重要的。本章将描述如何将文本转换为语料库,并从中生成词向量,使得使用欧几里得距离计算或余弦距离计算等技术将相似词语进行分组变得容易。
获取数据
第一步是获取一些数据来进行处理。在本章中,我们将需要大量的文本数据,将其转换为标记(tokens),并可视化以理解神经网络如何基于欧几里得距离和余弦相似度来对词向量进行排名。这是理解不同词语如何相互关联的重要步骤。反过来,这可以用于设计更好、更高效的语言和文本处理模型。
准备工作
请考虑以下内容:
-
模型的文本数据需要以
.txt格式保存文件,并且必须确保这些文件放在当前工作目录中。文本数据可以是任何内容,例如 Twitter 动态、新闻摘要、客户评论、计算机代码或保存在.txt格式中的完整书籍。在我们的例子中,我们使用了《权力的游戏》系列书籍作为模型的输入文本。然而,任何文本都可以代替书籍,且相同的模型仍然有效。 -
许多经典的文本已经不再受到版权保护。这意味着你可以免费下载这些书籍的所有文本,并将它们用于实验,例如创建生成模型。获得不再受版权保护的免费书籍的最佳网站是古腾堡计划(
www.gutenberg.org/)。
如何操作...
步骤如下:
- 首先访问古腾堡计划网站,浏览你感兴趣的书籍。点击书籍,然后点击 UTF-8,这样你就可以以纯文本格式下载该书籍。链接如以下截图所示:

古腾堡计划数据集下载页面
- 点击“Plain Text UTF-8”后,你应该会看到一个如下所示的页面。右键点击页面并选择“另存为...”,接下来,将文件重命名为你想要的名称,并保存到工作目录中:

-
现在,你应该能在当前工作目录中看到一个
.txt文件,文件名已按指定格式保存。 -
Project Gutenberg 在每本书的开头和结尾添加标准的页眉和页脚;这些并不是原始文本的一部分。请在文本编辑器中打开文件,删除页眉和页脚。
它是如何工作的...
该功能如下:
-
使用以下命令检查当前工作目录:
pwd。 -
工作目录可以使用
cd命令更改,如下所示截图:

-
请注意,在我们的案例中,文本文件存放在名为
USF的文件夹中,因此这被设置为工作目录。你也可以类似地将一个或多个.txt文件存放在工作目录中,作为模型的输入。 -
UTF-8 指定了文本文件中字符的编码类型。UTF-8 代表 Unicode Transformation Format。其中的 8 表示它使用 8 位 块来表示一个字符。
-
UTF-8 是一种折衷的字符编码,它可以像 ASCII 一样紧凑(如果文件仅为纯英文文本),但也能包含任何 Unicode 字符(尽管文件大小会有所增加)。
-
文本文件不需要是 UTF-8 格式,因为我们将在后期使用 codecs 库将所有文本编码为 Latin1 编码格式。
还有更多...
有关 UTF-8 和 Latin1 编码格式的更多信息,请访问以下链接:
另见
访问以下链接以更好地理解神经网络中单词向量的需求:
medium.com/deep-math-machine-learning-ai/chapter-9-1-nlp-word-vectors-d51bff9628c1
以下列出了一些与将单词转换为向量相关的其他有用文章:
monkeylearn.com/blog/word-embeddings-transform-text-numbers/
towardsdatascience.com/word-to-vectors-natural-language-processing-b253dd0b0817
导入必要的库
在我们开始之前,我们需要以下库和依赖项,这些都需要导入到我们的 Python 环境中。这些库将使我们的任务变得更轻松,因为它们提供了现成的函数和模型,可以直接使用,而不需要我们自己实现。这样也使得代码更简洁、可读。
准备开始
以下是创建单词向量、绘制图形以及在二维空间中可视化 n 维单词向量所需的库和依赖项:
-
future -
codecs -
glob -
`multiprocessing` -
os -
`pprint` -
re -
nltk -
Word2Vec -
sklearn -
numpy -
matplotlib -
pandas -
seaborn
如何实现...
步骤如下:
- 在你的 Jupyter notebook 中键入以下命令来导入所有必需的库:
from __future__ import absolute_import, division, print_function
import codecs
import glob
import logging
import multiprocessing
import os
import pprint
import re
import nltk
import gensim.models.word2vec as w2v
import sklearn.manifold
import numpy
as np
import matplotlib.pyplot as plt
import pandas as pd
import seaborn as sns
%pylab inline
- 你应该看到如下截图中的输出:

- 接下来,使用以下命令导入
stopwords和punkt库:
nltk.download("punkt")
nltk.download("stopwords")
- 你看到的输出应如下图所示:

它是如何工作的...
本节将描述本配方中使用的每个库的目的。
-
future库是 Python 2 和 Python 3 之间的桥梁。它充当这两个版本之间的桥梁,允许我们使用这两个版本的语法。 -
codecs库将用于执行文本文件中所有单词的编码处理。这构成了我们的数据集。 -
Regex 是用于快速查找或搜索文件的库。
glob函数允许快速高效地在大型数据库中搜索所需的文件。 -
multiprocessing库允许我们执行并发处理,这是通过运行多个线程并让每个线程运行不同的进程来实现的。这是一种通过并行化使程序运行更快的方法。 -
os库允许与操作系统(如 Mac、Windows 等)轻松交互,并执行诸如读取文件等功能。 -
pprint库提供了一种漂亮打印任意 Python 数据结构的能力,格式化后可以作为解释器的输入。 -
re模块提供了类似于 Perl 中的正则表达式匹配操作。 -
NLTK 是一个自然语言工具包,能够用非常简短的代码对单词进行标记化。当输入一个完整的句子时,
nltk函数会将句子拆分并输出每个单词的标记。基于这些标记,单词可以被组织成不同的类别。NLTK 通过将每个单词与一个庞大的预训练词库(称为词典)进行比较来实现这一点。 -
Word2Vec是谷歌的模型,训练于一个巨大的词向量数据集。它将语义相似的词汇彼此靠近。这将是本节最重要的库。 -
sklearn.manifold通过使用t-分布随机邻居嵌入(t-SNE)技术来实现数据集的降维。由于每个词向量是多维的,我们需要某种形式的降维技术将这些词的维度降低到较低的空间,以便可以在二维空间中进行可视化。
还有更多内容...
Numpy 是一个常用的 math 库。Matplotlib 是我们将使用的 plotting 库,pandas 通过允许轻松地重塑、切片、索引、子集化和操作数据,提供了很大的灵活性。
Seaborn库是另一个统计数据可视化库,我们需要与matplotlib一起使用。Punkt和Stopwords是两个数据处理库,它们简化了任务,比如将语料库中的一段文本拆分为标记(即通过分词)并移除stopwords。
另见
有关所使用的某些库的更多信息,请访问以下链接:
数据准备
在将数据输入模型之前,需要进行一系列数据预处理步骤。本节将描述如何清理数据并准备好输入模型。
准备工作
所有来自.txt文件的文本首先会被转换成一个大的语料库。通过从每个文件中读取每个句子并将其添加到一个空的语料库中来实现。接着,会执行一系列预处理步骤,以去除不规则项,如空格、拼写错误、stopwords等。然后,清理过的文本数据需要被分词,分词后的句子会通过循环添加到一个空的数组中。
如何操作...
步骤如下:
- 输入以下命令,以在工作目录中查找
.txt文件并打印找到的文件名:
book_names = sorted(glob.glob("./*.txt"))
print("Found books:")
book_names
在我们的案例中,有五本书,分别名为got1、got2、got3、got4和got5,它们保存在工作目录中。
- 创建一个
corpus,从第一个文件开始读取每个句子,对其进行编码,并将编码后的字符添加到corpus中,使用以下命令:
corpus = u''
for book_name in book_names:
print("Reading '{0}'...".format(book_name))
with codecs.open(book_name,"r","Latin1") as book_file:
corpus += book_file.read()
print("Corpus is now {0} characters long".format(len(corpus)))
print()
- 执行前述步骤中的代码,最终输出应该类似于以下截图所示:

- 使用以下命令从
punkt加载英语分词器tokenizer:
tokenizer = nltk.data.load('tokenizers/punkt/english.pickle')
- 使用以下命令将整个
corpus分词为句子:
raw_sentences = tokenizer.tokenize(corpus)
- 定义一个函数,将句子拆分为其组成的单词,并以以下方式移除不必要的字符:
def sentence_to_wordlist(raw):
clean = re.sub("[^a-zA-Z]"," ", raw)
words = clean.split()
return words
- 将所有原始句子添加到一个新的句子数组中,每个句子的每个单词都已被分词。可以通过以下代码实现:
sentences = []
for raw_sentence in raw_sentences:
if len(raw_sentence) > 0:
sentences.append(sentence_to_wordlist(raw_sentence))
- 打印语料库中的一个随机句子,直观地看到
tokenizer如何拆分句子并从结果中创建单词列表。可以使用以下命令来实现:
print(raw_sentences[50])
print(sentence_to_wordlist(raw_sentences[50]))
- 使用以下命令统计数据集中的所有标记:
token_count = sum([len(sentence) for sentence in sentences])
print("The book corpus contains {0:,} tokens".format(token_count))
它是如何工作的...
执行分词器并对语料库中的所有句子进行分词,应该会产生如下所示的输出:

接下来,去除不必要的字符,如连字符和特殊字符,可以通过以下方式完成。使用用户定义的sentence_to_wordlist()函数拆分所有句子,生成如下所示的输出:

将原始句子添加到一个名为sentences[]的新数组中,生成如下所示的输出:

当打印语料库中的总分词数时,我们注意到整个语料库中有 1,110,288 个分词。这在以下截图中得到了说明:

功能如下:
-
使用 NLTK 提供的预训练
tokenizer对整个语料库进行分词,通过将每个句子作为一个分词进行计数。每个分词的句子都被添加到变量raw_sentences中,该变量存储了所有分词后的句子。 -
下一步,将去除常见的停用词,并通过将每个句子拆分成单词来清理文本。
-
随机打印一个句子及其单词列表,以理解这一过程是如何工作的。在我们的例子中,我们选择打印
raw_sentences数组中的第 50 个句子。 -
统计并打印句子数组中的总分词数(在我们的例子中是句子)。在我们的案例中,我们看到
tokenizer创建了 1,110,288 个分词。
还有更多…
有关段落和句子分词的更多信息,请访问以下链接:
-
textminingonline.com/dive-into-nltk-part-ii-sentence-tokenize-and-word-tokenize -
stackoverflow.com/questions/37605710/tokenize-a-paragraph-into-sentence-and-then-into-words-in-nltk
另见
有关正则表达式工作原理的更多信息,请访问以下链接:
stackoverflow.com/questions/13090806/clean-line-of-punctuation-and-split-into-words-python
构建和训练模型
一旦我们拥有了以数组形式存储的分词数据,就可以将其输入到模型中。首先,我们需要为模型定义多个超参数。本节将介绍如何执行以下操作:
-
声明模型超参数
-
使用
Word2Vec构建模型 -
在准备好的数据集上训练模型
-
保存并检查训练过的模型
准备就绪
需要声明的一些模型超参数包括以下内容:
-
结果词向量的维度
-
最小词数阈值
-
在训练模型时运行的并行线程数
-
上下文窗口长度
-
下采样(针对频繁出现的词)
-
设置种子
一旦之前提到的超参数被声明,可以使用来自Gensim库的Word2Vec函数来构建模型。
如何操作...
步骤如下:
- 使用以下命令声明模型的超参数:
num_features = 300
min_word_count = 3
num_workers = multiprocessing.cpu_count()
context_size = 7
downsampling = 1e-3
seed = 1
- 使用声明的超参数构建模型,代码如下:
got2vec = w2v.Word2Vec(
sg=1,
seed=seed,
workers=num_workers,
size=num_features,
min_count=min_word_count,
window=context_size,
sample=downsampling
)
- 使用分词后的句子并遍历所有的词元来构建模型的词汇表。可以通过以下方式使用
build_vocab函数来完成:
got2vec.build_vocab(sentences,progress_per=10000, keep_raw_vocab=False, trim_rule=None)
- 使用以下命令训练模型:
got2vec.train(sentences, total_examples=got2vec.corpus_count, total_words=None, epochs=got2vec.iter, start_alpha=None, end_alpha=None, word_count=0, queue_factor=2, report_delay=1.0, compute_loss=False)
- 如果
trained目录尚未存在,则创建该目录。使用以下命令保存并检查点trained模型:
if not os.path.exists("trained"):
os.makedirs("trained")
got2vec.wv.save(os.path.join("trained", "got2vec.w2v"), ignore=[])
- 要在任何时刻加载保存的模型,使用以下命令:
got2vec = w2v.KeyedVectors.load(os.path.join("trained", "got2vec.w2v"))
它是如何工作的...
功能如下:
-
模型参数的声明不会产生任何输出,它仅仅是为存储模型参数的变量在内存中腾出空间。以下截图描述了这个过程:
![]()
-
模型是使用前述的超参数构建的。在我们的案例中,我们将模型命名为
got2vec,但模型的名称可以按您的喜好来命名。模型定义如下截图所示:![]()
-
在模型上运行
build_vocab命令后,应该会产生如下截图所示的输出:![]()
-
训练模型通过定义如下截图中的参数来完成:
![]()
-
上述命令会产生如下截图所示的输出:

- 保存、检查点和加载模型的命令会产生如下输出,如截图所示:

还有更多...
请考虑以下内容:
-
在我们的案例中,我们注意到
build_vocab函数从 1,110,288 个词中识别出 23,960 个不同的词类型。然而,这个数字会因文本语料的不同而有所变化。 -
每个词被表示为一个 300 维的向量,因为我们已经将维度声明为 300。增加这个数字会增加模型的训练时间,但也确保模型能更容易地推广到新数据。
-
发现 1e
3 的下采样率是一个不错的比率。该值用来告诉模型何时对频繁出现的词进行下采样,因为这些词在分析中并不重要。此类词的例子包括:this、that、those、them 等等。 -
设置一个种子以确保结果可重复。设置种子也能让调试变得更加容易。
-
训练模型大约需要 30 秒,使用普通 CPU 计算,因为模型不复杂。
-
模型在检查点时被保存在工作目录中的
trained文件夹下。
另见
欲了解更多关于Word2Vec模型和 Gensim 库的信息,请访问以下链接:
radimrehurek.com/gensim/models/word2vec.html
进一步可视化
本节将描述如何将所有训练好的词的维度压缩,并将其放入一个巨大的矩阵中进行可视化。由于每个词是一个 300 维的向量,因此需要将其降维到较低的维度,以便我们在二维空间中可视化它。
准备工作
在模型保存并检查点训练完成后,像上一节那样开始将模型加载到内存中。本节将使用的库和模块有:
-
tSNE -
pandas -
Seaborn -
numpy
如何操作...
步骤如下:
- 使用以下命令压缩 300 维词向量的维度:
tsne = sklearn.manifold.TSNE(n_components=2, random_state=0)
- 将所有词向量放入一个巨大的矩阵(命名为
all_word_vectors_matrix)中,并使用以下命令查看:
all_word_vectors_matrix = got2vec.wv.syn0
print (all_word_vectors_matrix)
- 使用
tsne技术通过以下命令将所有学习到的表示拟合到二维空间中:
all_word_vectors_matrix_2d = tsne.fit_transform(all_word_vectors_matrix)
- 使用以下代码收集所有的词向量以及它们关联的词:
points = pd.DataFrame(
[
(word, coords[0], coords[1])
for word, coords in [
(word, all_word_vectors_matrix_2d[got2vec.vocab[word].index])
for word in got2vec.vocab
]
],
columns=["word", "x", "y"]
)
- 可以使用以下命令获取前十个点的
X和Y坐标及其关联的词:
points.head(10)
- 使用以下命令绘制所有点:
sns.set_context("poster")
points.plot.scatter("x", "y", s=10, figsize=(15, 15))
- 可以放大绘制图中的选定区域进行更细致的检查。通过以下函数切片原始数据来实现这一点:
def plot_region(x_bounds, y_bounds):
slice = points[
(x_bounds[0] <= points.x) &
(points.x <= x_bounds[1]) &
(y_bounds[0] <= points.y) &
(points.y <= y_bounds[1])
]
ax = slice.plot.scatter("x", "y", s=35, figsize=(10, 8))
for i, point in slice.iterrows():
ax.text(point.x + 0.005, point.y + 0.005, point.word, fontsize=11)
- 使用以下命令绘制切片后的数据。切片数据可以被视为原始图中所有数据点的放大区域:
plot_region(x_bounds=(20.0, 25.0), y_bounds=(15.5, 20.0))
它是如何工作的...
功能如下:
-
t-SNE 算法是一种非线性降维技术。计算机在处理计算时可以轻松处理多个维度。然而,人类只能一次性可视化两到三维。因此,在尝试从数据中提取洞见时,这些降维技术非常有用。
-
将 t-SNE 应用于 300 维的词向量后,我们能够将其压缩成二维,进行绘图并查看。
-
通过将
n_components指定为 2,我们告诉算法必须将数据压缩到二维空间。一旦完成,我们将所有压缩后的向量加入到一个名为all_word_vectors_matrix的巨型矩阵中,如下图所示:![]()
-
t-SNE 算法需要对所有这些词向量进行训练。使用普通 CPU 训练大约需要五分钟。
-
一旦 t-SNE 在所有词向量上完成训练,它会输出每个单词的 2D 向量。这些向量可以通过将它们转换为数据框来作为点进行绘制。具体做法如下图所示:
![]()
-
我们看到,前面的代码生成了多个点,每个点代表一个单词及其 X 和 Y 坐标。检查数据框中的前二十个点时,输出如下图所示:

-
使用
all_word_vectors_2D变量绘制所有点时,你应该会看到一个类似于以下截图的输出:![]()
-
上述命令将生成整个文本中所有标记或单词的图表,如下图所示:
![]()
-
我们可以使用
plot_region函数来缩放到图表的某个区域,这样就可以实际看到单词及其坐标。此步骤如以下截图所示:![]()
-
可以通过设置
x_bounds和y_bounds值来放大或缩小图表的区域,如下图所示:![]()
-
可以通过更改
x_bounds和y_bounds值来可视化同一图表的不同区域,如下图所示:![]()

另请参见
下面是一些需要注意的附加要点:
-
要了解有关 t-SNE 算法如何工作的更多信息,请访问以下链接:
-
www.oreilly.com/learning/an-illustrated-introduction-to-the-t-sne-algorithm -
有关余弦距离相似度和排序的更多信息,请访问以下链接:
-
使用以下链接来探索
Seaborn库的不同功能:
进一步分析
本节将描述在可视化后可以对数据执行的进一步分析。例如,探索不同词向量之间的余弦距离相似度。
准备开始
以下链接是一个很好的博客,讲解了余弦距离相似度的原理,并讨论了一些相关的数学内容:
如何做到...
请考虑以下内容:
-
使用
Word2Vec的不同功能,可以执行各种自然语言处理任务。其中一个任务是给定某个单词后,找出最语义相似的单词(即,具有高余弦相似度或较短欧几里得距离的词向量)。这可以通过使用Word2Vec的most_similar函数来实现,如以下截图所示:
该截图显示了与单词Lannister相关的所有最接近的单词:
该截图显示了与单词Jon相关的所有词汇:![]()
它是如何工作的...
请考虑以下内容:
- 有多种方法可以测量单词之间的语义相似度。本节中我们使用的方法是基于余弦相似度的。我们还可以通过使用以下代码行来探索单词之间的线性关系:
def nearest_similarity_cosmul(start1, end1, end2):
similarities = got2vec.most_similar_cosmul(
positive=[end2, start1],
negative=[end1]
)
start2 = similarities[0][0]
print("{start1} is related to {end1}, as {start2} is related to {end2}".format(**locals()))
return start2
- 要找出与给定一组单词最接近的单词的余弦相似度,可以使用以下命令:
nearest_similarity_cosmul("Stark", "Winterfell", "Riverrun")
nearest_similarity_cosmul("Jaime", "sword", "wine")
nearest_similarity_cosmul("Arya", "Nymeria", "dragons")
-
上述过程在以下截图中进行了说明:
![]()
-
结果如下:
![]()
-
如本节所示,词向量构成了所有自然语言处理(NLP)任务的基础。在深入研究更复杂的 NLP 模型,如递归神经网络和长短期记忆(LSTM)单元之前,理解词向量及其构建数学模型是非常重要的。
另见
为了更好地理解余弦距离相似度、聚类和用于排序词向量的其他机器学习技术,可以进一步阅读以下内容。以下是一些关于此主题的有用论文链接:
第十二章:使用 Keras 创建电影推荐引擎
本章将涵盖以下内容:
-
下载 MovieLens 数据集
-
操作和合并 MovieLens 数据集
-
探索 MovieLens 数据集
-
为深度学习管道准备数据集
-
使用 Keras 应用深度学习管道
-
评估推荐引擎的准确性
介绍
2006 年,一家小型 DVD 租赁公司开始致力于让他们的推荐引擎提升 10%。这家公司就是 Netflix,而 Netflix 奖的奖金为 100 万美元。这个竞赛吸引了来自全球一些最大科技公司的工程师和科学家。获胜者的推荐引擎是用机器学习构建的。如今,Netflix 已经成为流媒体数据和向客户推荐下一部观看内容的科技巨头之一。
现在,评分无处不在,不管你在做什么。如果你正在寻找推荐去新餐馆吃饭、在线购买衣物、在当地影院观看新电影,或者在电视或网络上观看新剧集,极有可能会有一个网站或移动应用提供一些评分以及你所要购买的产品或服务的反馈。正是因为这种即时反馈的增加,推荐算法在过去几年变得更加受欢迎。本章将重点讲解如何使用深度学习库 Keras 为用户构建一个电影推荐引擎。
下载 MovieLens 数据集
有一个很棒的研究实验室,成立于 1992 年,位于美国明尼阿波利斯市,名为GroupLens,专注于推荐引擎,并慷慨地从 MovieLens 网站收集了数百万行数据。我们将使用它的数据集作为训练我们推荐引擎模型的数据源。
准备工作
MovieLens 数据集由 GroupLens 托管和维护,网址如下:
grouplens.org/datasets/movielens/。
需要注意的是,我们将使用的数据集将直接来自他们的网站,而不是来自第三方中介或仓库。此外,有两个不同的数据集供我们查询:
-
推荐用于新研究
-
推荐用于教育和开发
使用这个数据集的目的是纯粹为了教育目的,因此我们将从网站的教育和开发部分下载数据。教育数据仍包含足够多的行供我们的模型使用,因为它包含了 100,000 条评分,如下图所示:

此外,该数据集包含了超过 600 个匿名用户的信息,这些信息是从 1995 年 1 月 9 日到 2015 年 3 月 31 日这段时间内收集的。数据集最后一次更新是在 2017 年 10 月。
F Maxwell Harper 和 Joseph A Konstan, 2015. The MovieLens 数据集:历史与背景。ACM 交互智能系统期刊 (TiiS) 5, 4, 文章 19(2015 年 12 月),第 19 页。DOI:dx.doi.org/10.1145/2827872
如何操作...
本节将介绍如何下载并解压 MovieLens 数据集:
-
下载 MovieLens 数据集的研究版本,该版本可从以下网址公开下载:
grouplens.org/datasets/movielens/latest/。 -
将名为
ml-latest-small.zip的ZIP文件下载到我们本地的某个文件夹中,如以下截图所示:

-
当
ml-latest-small.zip被下载并解压时,应该提取以下四个文件:-
links.csv -
movies.csv -
ratings.csv -
`tags.csv`
-
-
执行以下脚本来启动我们的
SparkSession:
spark = SparkSession.builder \
.master("local") \
.appName("RecommendationEngine") \
.config("spark.executor.memory", "6gb") \
.getOrCreate()
- 通过执行以下脚本,确认以下六个文件可以访问:
import os
os.listdir('ml-latest-small/')
- 使用以下脚本将每个数据集加载到 Spark 数据框中:
movies = spark.read.format('com.databricks.spark.csv')\
.options(header='true', inferschema='true')\
.load('ml-latest-small/movies.csv')
tags = spark.read.format('com.databricks.spark.csv')\
.options(header='true', inferschema='true')\
.load('ml-latest-small/tags.csv')
links = spark.read.format('com.databricks.spark.csv')\
.options(header='true', inferschema='true')\
.load('ml-latest-small/links.csv')
ratings = spark.read.format('com.databricks.spark.csv')\
.options(header='true', inferschema='true')\
.load('ml-latest-small/ratings.csv')
- 通过执行以下脚本,确认每个数据集的行数:
print('The number of rows in movies dataset is {}'.format(movies.toPandas().shape[0]))
print('The number of rows in ratings dataset is {}'.format(ratings.toPandas().shape[0]))
print('The number of rows in tags dataset is {}'.format(tags.toPandas().shape[0]))
print('The number of rows in links dataset is {}'.format(links.toPandas().shape[0]))
它是如何工作的...
本节将重点解释 MovieLens 100K 数据集中每个数据集中的字段。请查看以下步骤:
- 数据集都可以在压缩文件
ml-latest-small.zip中找到,其中ratings.csv数据集将作为我们数据的伪事实表,因为它包含每个评分电影的交易记录。数据集ratings有以下四个列名,见下图:

- 数据集展示了每个 userId 在其使用期间的评分,从最早的评分到最新的评分。评分范围从 0.5 到 5.0 星,以下截图显示了
userId = 1的评分:

tags数据集包含一个标签列,其中包含用户用来描述特定 movieId 在特定时间戳下的特定单词或短语。如下图所示,userId 15 对 Sandra Bullock 在其一部电影中的表现并不特别喜爱:

movies数据集主要是一个查找表,用于电影类型与评分之间的关联。电影可以关联 19 种独特的类型;然而,需要注意的是,一部电影可以同时关联多种类型,如以下截图所示:

- 最终的数据集是
links数据集,它也充当一个查找表。它将 MovieLens 中的电影与这些电影在流行电影数据库网站上的相关数据连接起来,比如www.imdb.com,以及www.themoviedb.org。IMDB 的链接位于名为 imdbId 的列下,MovieDB 的链接位于名为 tmdbId 的列下,如下图所示:

- 在我们完成之前,确认我们确实得到了期望的行数总是一个好主意,这有助于确保我们在上传文件到笔记本时没有遇到任何问题。我们应该期望看到约 10 万行的评分数据集,如下图所示:

还有更多……
虽然我们在本章中不会使用 MovieLens 的 2000 万行数据集版本,但你可以选择在此推荐引擎中使用它。你仍然会有相同的四个数据集,只是数据量更大,尤其是ratings数据集。如果你选择这种方法,可以从以下网站下载完整的压缩数据集:
files.grouplens.org/datasets/movielens/ml-latest.zip
另请参见
要了解更多关于本章使用的 MovieLens 数据集背后的元数据,请访问以下网站:
files.grouplens.org/datasets/movielens/ml-latest-small-README.html
要了解更多关于本章使用的 MovieLens 数据集的历史和背景,请访问以下网站:
www.slideshare.net/maxharp3r/the-movielens-datasets-history-and-context
要了解更多关于Netflix 大奖的信息,请访问以下网站:
操作和合并 MovieLens 数据集
目前我们有四个独立的数据集,但最终我们希望将它们整合成一个数据集。本章将专注于将数据集缩减为一个。
准备工作
本节不需要导入 PySpark 库,但了解 SQL 连接的背景知识会非常有帮助,因为我们将探索多种连接数据框的方法。
如何做到……
本节将介绍以下步骤来连接 PySpark 中的数据框:
- 执行以下脚本将
ratings中的所有字段名称重命名,在名称末尾附加一个_1:
for i in ratings.columns:
ratings = ratings.withColumnRenamed(i, i+'_1')
- 执行以下脚本将
movies数据集与ratings数据集进行inner join,创建一个新的表格,名为temp1:
temp1 = ratings.join(movies, ratings.movieId_1 == movies.movieId, how = 'inner')
- 执行以下脚本将
temp1数据集与links数据集进行内连接,创建一个新的表格,名为temp2:
temp2 = temp1.join(links, temp1.movieId_1 == links.movieId, how = 'inner')
- 通过以下脚本,将
temp2与tags左连接,创建我们的最终合并数据集mainDF:
mainDF = temp2.join(tags, (temp2.userId_1 == tags.userId) & (temp2.movieId_1 == tags.movieId), how = 'left')
- 通过执行以下脚本,仅选择我们最终
mainDF数据集所需的列:
mainDF = mainDF.select('userId_1',
'movieId_1',
'rating_1',
'title',
'genres',
'imdbId',
'tmdbId',
'timestamp_1').distinct()
它是如何工作的…
本节将介绍我们将表格连接在一起的设计过程,以及哪些最终列会被保留:
- 正如前一节中提到的,
ratings数据框将作为我们的事实表,因为它包含每个用户随着时间推移的所有主要评分交易。ratings中的列将用于与其他三个表的每次后续连接,为了保持列的唯一性,我们将给每个列名后加上_1,如以下截图所示:

- 我们现在可以将三个查找表与评分表连接。前两个与评分表的连接是内连接,因为
temp1和temp2的行数仍为 100,004 行。从tags表连接的第三个连接需要是外连接,以避免丢失行。此外,连接需要应用于movieId和userId,因为一个标签在任何给定时间都是特定用户和特定电影唯一的。三个表temp1、temp2和mainDF的行数可以在下图中看到:

在处理数据集之间的连接时,我们经常会遇到三种类型的连接:内连接(inner)、左连接(left)和右连接(right)。内连接只有在数据集 1 和数据集 2 的连接键都可用时,才会生成结果集。左连接将生成数据集 1 中的所有行,以及仅包含与数据集 2 中的匹配键的行。右连接将生成数据集 2 中的所有行,以及仅包含与数据集 1 中的匹配键的行。稍后在本节中,我们将探索 Spark 中的 SQL 连接。
- 有趣的是,我们新创建的数据集
mainDF有 100,441 行,而不是原始ratings数据集中的 100,004 行,以及temp1和temp2。其中有 437 个评分与多个标签相关联。此外,我们还可以看到,大多数ratings_1的tag值为 null,如下图所示:

- 我们积累了不再需要的重复列。共有 14 列,如下图所示:

- 此外,我们已经确定,
tags字段相对没有用,因为它有超过 99k 的空值。因此,我们将使用select()函数从数据框中仅提取我们将用于推荐引擎的八列。然后,我们可以确认我们的最终新数据框mainDF具有正确的行数 100,004,如下图所示:

还有更多…
尽管我们通过在 Spark dataframe 中使用 PySpark 的函数进行了连接,但我们也可以通过将数据框注册为临时表,然后使用sqlContext.sql()进行连接:
- 首先,我们将使用
creatorReplaceTempView()将每个数据集注册为临时视图,如下脚本所示:
movies.createOrReplaceTempView('movies_')
links.createOrReplaceTempView('links_')
ratings.createOrReplaceTempView('ratings_')
- 接下来,我们将像操作任何其他关系数据库一样,使用
sqlContext.sql()函数编写 SQL 脚本,如下脚本所示:
mainDF_SQL = \
sqlContext.sql(
"""
select
r.userId_1
,r.movieId_1
,r.rating_1
,m.title
,m.genres
,l.imdbId
,l.tmdbId
,r.timestamp_1
from ratings_ r
inner join movies_ m on
r.movieId_1 = m.movieId
inner join links_ l on
r.movieId_1 = l.movieId
"""
)
- 最后,我们可以对新的数据框
mainDF_SQL进行分析,观察其与另一个数据框mainDF相同,且行数保持一致,如下截图所示:

另见
要了解更多关于 Spark 中 SQL 编程的信息,请访问以下网站:
spark.apache.org/docs/latest/sql-programming-guide.html
探索 MovieLens 数据集
在进行任何建模之前,熟悉源数据集并进行一些探索性数据分析是非常重要的。
准备工作
我们将导入以下库来帮助可视化和探索 MovieLens 数据集:matplotlib。
如何操作……
本节将逐步讲解如何分析 MovieLens 数据库中的电影评分:
- 通过执行以下脚本,获取
rating_1列的一些汇总统计数据:
mainDF.describe('rating_1').show
- 通过执行以下脚本,构建评分分布的直方图:
import matplotlib.pyplot as plt
%matplotlib inline
mainDF.select('rating_1').toPandas().hist(figsize=(16, 6), grid=True)
plt.title('Histogram of Ratings')
plt.show()
- 执行以下脚本,以在电子表格数据框中查看直方图的值:
mainDF.groupBy(['rating_1']).agg({'rating_1':'count'})\
.withColumnRenamed('count(rating_1)', 'Row Count').orderBy(["Row Count"],ascending=False)\
.show()
- 用户对评分选择的唯一计数可以通过执行以下脚本存储为数据框
userId_frequency:
userId_frequency = mainDF.groupBy(['userId_1']).agg({'rating_1':'count'})\
.withColumnRenamed('count(rating_1)', '# of Reviews').orderBy(["# of Reviews"],ascending=False)
- 使用以下脚本绘制
userID_frequency的直方图:
userId_frequency.select('# of Reviews').toPandas().hist(figsize=(16, 6), grid=True)
plt.title('Histogram of User Ratings')
plt.show()
它是如何工作的……
本节将讨论 MovieLens 数据库中评分和用户活动的分布。请查看以下步骤:
- 我们可以看到,用户对电影的平均评分约为 3.5,如下截图所示:

- 尽管平均评分为 3.54,但我们可以看到直方图显示中位数评分为 4,这表明用户评分明显偏向较高的评分,如下截图所示:

- 通过对直方图背后的数据进行进一步分析,我们可以看到用户最常选择 4.0 评分,其次是 3.0,再然后是 5.0。此外,有趣的是,用户更倾向于给出 0.0 评分,而非 0.5 评分,如下图所示:

- 我们可以查看用户评分选择的分布,看到一些用户在表达他们对电影的看法时非常活跃。例如,匿名用户 547 就发布了 2391 条评分,以下截图为证:

- 然而,当我们查看用户评分选择的分布时,我们确实发现,尽管有些用户自己做出了超过一千个选择,但绝大多数用户的选择次数少于 250 次,以下截图可见一斑:

- 上一张截图中直方图的分布呈长尾格式,表明大多数数据点位于直方图中心的两侧。这表明绝大多数评分是由少数用户定义的。
还有更多…
pyspark 数据框架具有与 pandas 数据框架类似的特性,可以对特定列执行一些汇总统计。
在 pandas 中,我们使用以下脚本执行汇总统计:dataframe['column'].describe()。
在 pyspark 中,我们使用以下脚本执行汇总统计:dataframe.describe('column').show()。
另见
要了解有关 PySpark 中 describe() 函数的更多信息,请访问以下网站:
spark.apache.org/docs/2.1.0/api/python/pyspark.sql.html#pyspark.sql.DataFrame.describe
为深度学习管道准备数据集
我们现在准备将数据集准备好,以便输入到我们将在 Keras 中构建的深度学习模型中。
准备工作
在为 Keras 准备数据集时,我们将导入以下库到我们的笔记本中:
-
import pyspark.sql.functions as F -
import numpy as np -
from pyspark.ml.feature import StringIndexer -
import keras.utils
如何操作...
本节通过以下步骤讲解如何为深度学习管道准备数据集:
- 执行以下脚本来清理列名:
mainDF = mainDF.withColumnRenamed('userId_1', 'userid')
mainDF = mainDF.withColumnRenamed('movieId_1', 'movieid')
mainDF = mainDF.withColumnRenamed('rating_1', 'rating')
mainDF = mainDF.withColumnRenamed('timestamp_1', 'timestamp')
mainDF = mainDF.withColumnRenamed('imdbId', 'imdbid')
mainDF = mainDF.withColumnRenamed('tmdbId', 'tmdbid')
rating列目前按 0.5 的增量进行划分。使用以下脚本将评分调整为四舍五入到整数:
import pyspark.sql.functions as F
mainDF = mainDF.withColumn("rating", F.round(mainDF["rating"], 0))
- 使用以下脚本将
genres列从字符串转换为基于genres标签频率的索引,并命名为genreCount:
from pyspark.ml.feature import StringIndexer
string_indexer = StringIndexer(inputCol="genres", outputCol="genreCount")
mainDF = string_indexer.fit(mainDF).transform(mainDF)
- 使用以下脚本对我们的数据框进行精简:
mainDF = mainDF.select('rating', 'userid', 'movieid', 'imdbid', 'tmdbid', 'timestamp', 'genreCount')
- 使用以下脚本将
mainDF分割为训练集和测试集,以便用于模型训练:
trainDF, testDF = mainDF.randomSplit([0.8, 0.2], seed=1234)
- 使用以下脚本将我们的两个 Spark 数据框
trainDF和testDF转换为四个numpy数组,以供深度学习模型使用:
import numpy as np
xtrain_array = np.array(trainDF.select('userid','movieid', 'genreCount').collect())
xtest_array = np.array(testDF.select('userid','movieid', 'genreCount').collect())
ytrain_array = np.array(trainDF.select('rating').collect())
ytest_array = np.array(testDF.select('rating').collect()
- 使用以下脚本将
ytrain_array和ytest_array转换为独热编码标签ytrain_OHE和ytest_OHE:
import keras.utils as u
ytrain_OHE = u.to_categorical(ytrain_array)
ytest_OHE = u.to_categorical(ytest_array)
它是如何工作的...
本节说明了如何为深度学习管道准备数据集:
- 为了在深度学习管道中更方便地使用,最好在数据传入管道之前清理列名和列的顺序。重命名列标题后,我们可以查看更新后的列,如以下脚本所示:

-
对
ratings列进行了一些操作,将 0.5 的增量值向上舍入到下一个整数。这将有助于在 Keras 中进行多分类时,将ratings分为六个类别,而不是 11 个类别。 -
为了将电影类型传入深度学习模型,我们需要将
genres的字符串值转换为数值标签。最常见的电影类型将被赋值为 0,其他类型依次增加值。在下图中,我们可以看到《心灵捕手》有两个关联的类型(剧情 | 爱情),这两种类型是第四常见的电影类型,genreCount的值为 3.0:

- 对于深度模型来说,
genres列不再需要,因为它将被genreCount列替代,如下图所示:

- 我们的主要数据框
mainDF被分割为trainDF和testDF,用于建模、训练和评估,采用 80/20 的比例划分。三个数据框的行数可以从下图看到:

- 数据被传入 Keras 深度学习模型,使用矩阵而非数据框。因此,我们的训练和测试数据框被转换为 numpy 数组,并被拆分为 x 和 y。
xtrain_array和xtest_array选取的特征为 userid、movieid 和 genreCount。这些是我们用来预测用户可能评分的唯一特征。我们会丢弃imdbid和tmdbid,因为它们直接与movieid相关,因此不会提供额外的价值。timestamp会被移除,以过滤与投票频率相关的偏差。最后,ytest_array和ytrain_array将包含评分的标签值。所有四个数组的shape如下图所示:

还有更多内容...
虽然 ytrain_array 和 ytest_array 都是矩阵格式的标签,但它们并未针对深度学习进行理想的编码。由于我们正在构建的是一个分类模型,因此我们需要将标签编码为模型可以理解的方式。这意味着我们将 0 到 5 的评分编码为 0 或 1 的值,基于其值元素。因此,如果某个评分获得了最高值 5,则应将其编码为 [0,0,0,0,0,1]。第一个位置保留为 0,第六个位置保留为 1,表示值为 5。我们可以使用 keras.utils 进行这种转换,并将分类变量转换为独热编码变量。这样,我们的训练标签的形状将从 (80146,1) 转换为 (80146,6),如下图所示:

另见
要了解更多关于 keras.utils 的信息,请访问以下网站:keras.io/utils/
使用 Keras 应用深度学习模型
此时,我们已经准备好将 Keras 应用于我们的数据。
准备工作
我们将使用以下来自 Keras 的功能:
-
from keras.models import Sequential -
from keras.layers import Dense, Activation
如何操作...
本节介绍了使用 Keras 在数据集上应用深度学习模型的步骤:
- 导入以下库,以从
keras构建一个Sequential模型,使用以下脚本:
from keras.models import Sequential
from keras.layers import Dense, Activation
- 使用以下脚本从
keras配置Sequential模型:
model = Sequential()
model.add(Dense(32, activation='relu', input_dim=xtrain_array.shape[1]))
model.add(Dense(10, activation='relu'))
model.add(Dense(ytrain_OHE.shape[1], activation='softmax'))
model.compile(optimizer='adam', loss='categorical_crossentropy', metrics=['accuracy'])
- 我们使用以下脚本
fit并训练模型,并将结果存储到一个名为accuracy_history的变量中:
accuracy_history = model.fit(xtrain_array, ytrain_OHE, epochs=20, batch_size=32)
工作原理...
本节解释了将 Keras 模型应用于数据集的配置,目的是根据选定的特征预测评分。
-
在 Keras 中,
Sequential模型只是各层的线性组合,具体如下:Dense用于定义深度神经网络中的全连接层。最后,Activation用于将输入的特征转换为可以作为预测结果的输出。神经网络中可以使用许多类型的激活函数;然而,在本章中,我们将使用relu和softmax。 -
Sequential模型被配置为包括三个Dense层:-
第一个层的
input_dim设置为来自xtrain_array的特征数量。shape特性使用xtrain_array.shape[1]拉取值 3。此外,第一层设置为在神经网络的第一层中有32个神经元。最后,三个输入参数使用relu激活函数进行激活。只有第一层需要显式定义输入维度。后续层不需要,因为它们可以从前一层推断维度的数量。 -
Sequential模型的第二层在神经网络中有10个神经元,并且激活函数设置为relu。修正线性单元(ReLU)通常在神经网络的早期阶段使用,因为它们在训练过程中非常有效。这是因为该函数的方程简单,任何小于 0 的值都会被抛弃,而其他激活函数并非如此。 -
Sequential模型的第三层和最后一层需要基于从 0 到 5 的评分所有可能的情况输出六个结果。这需要将输出设置为ytrain_OHE.shape[1]的值。输出通过softmax函数生成,这通常是在神经网络的最后一层,因为它在分类任务中非常有用。此时,我们的目标是对 0 到 5 之间的值进行分类。 -
一旦指定了各层,我们就必须
compile模型。 -
我们使用
adam优化模型,adam代表自适应矩估计。优化器对于配置模型使用的梯度下降学习率非常有效,梯度下降用来调整和更新神经网络的权重。adam是一种流行的优化器,据说它结合了其他常见优化器的一些最佳特性。 -
我们的损失函数设置为
categorical_crossentropy,该损失函数通常用于多类分类预测。损失函数评估模型在训练过程中的表现。
-
-
我们使用训练特征
xtrain_array和训练标签ytrain_OHE训练模型。模型在 20 个训练周期内进行训练,每次批次大小(batch_size)设为 32。每个训练周期的accuracy和loss输出被捕获在一个名为accuracy_history的变量中,可以在下图中看到:

还有更多...
虽然我们可以在每个训练周期输出损失和准确率分数,但将这两个输出可视化展示在每个 20 个训练周期中会更好。我们可以使用以下脚本来绘制这两个图:
plt.plot(accuracy_history.history['acc'])
plt.title('Accuracy vs. Epoch')
plt.xlabel('Epoch')
plt.ylabel('Accuracy')
plt.show()
plt.plot(accuracy_history.history['loss'])
plt.title('Loss vs. Epoch')
plt.xlabel('Epoch')
plt.ylabel('Loss')
plt.show()
脚本的输出可以在以下截图中看到:

看起来在第二个训练周期(epoch)之后,模型的损失和准确率都已经稳定下来。
另见
要了解更多关于如何开始使用 Keras 中的Sequential模型,请访问以下网站:keras.io/getting-started/sequential-model-guide/。
评估推荐引擎的准确性
现在我们可以计算基于 Keras 构建的深度学习模型的准确率。
准备就绪
评估Sequential模型的准确性需要使用 Keras 中的model.evaluate()函数。
如何操作...
我们只需执行以下脚本即可计算准确率分数accuracy_rate:
score = model.evaluate(xtest_array, ytest_OHE, batch_size=128)
accuracy_rate = score[1]*100
print('accuracy is {}%'.format(round(accuracy_rate,2)))
工作原理...
我们的模型性能基于使用测试特征xtest_array和测试标签ytest_OHE进行评估。我们可以使用model.evaluate()并将batch_size设置为128元素进行评估。我们可以看到准确率大约为 39%,如以下截图所示:

这意味着我们能够以约 39% 的准确率确定用户的评分,评分范围是 0 到 5。
另请参见
要了解有关使用 Keras 指标评估模型性能的更多信息,请访问以下网站:
第十三章:使用 TensorFlow 和 Spark 进行图像分类
本章将涵盖以下内容:
-
下载梅西和 C 罗各 30 张图片
-
配置带深度学习包的 PySpark 安装
-
将图片加载到 PySpark 数据框中
-
理解迁移学习
-
创建图像分类训练的流水线
-
评估模型性能
-
微调模型参数
介绍
在过去几年中,图像识别软件的需求日益增长。这种需求的增加与大数据存储的进展不无关系。Google Photos、Facebook 和 Apple 都使用图像分类软件来为用户标记照片。这些公司使用的大部分图像识别软件都是基于流行的库,如 TensorFlow,使用深度学习模型构建的。本章通过利用一组图像的训练来学习或识别另一组图像,拓展了深度学习的技术。这个概念被称为迁移学习。在本章中,我们将重点介绍如何利用迁移学习来识别世界上排名前两的足球运动员:
-
利昂内尔·梅西
-
克里斯蒂亚诺·罗纳尔多
看一下这张照片:

下载梅西和 C 罗各 30 张图片
在进行任何图片分类之前,我们必须先从网上下载我们足球运动员的图片。
准备就绪
有几个浏览器插件可以批量下载图片。由于 Ubuntu 预装了 Mozilla Firefox 浏览器,我们将使用它作为首选浏览器来安装批量图片下载扩展。
如何操作...
以下部分解释了如何批量下载图片。请参考这些步骤:
- 访问以下网站以下载和安装 Firefox 插件:
addons.mozilla.org/en-US/firefox/
- 搜索并选择“下载所有图片”插件,如下面的截图所示:

- 这将带我们到安装页面。此时,选择“添加到 Firefox”,如下面的截图所示:

-
确认你的安装,因为这个插件将需要权限访问你的浏览器下载历史,访问所有网站的数据,并发送通知。
-
一旦完成,你应该会在浏览器右上角看到一个小的图片图标,表示“下载所有图片”,如下面的截图所示:

- 我们现在准备好开始下载我们的足球运动员的图片了,使用的是新安装的 Firefox 扩展。我们可以访问许多不同的网站来下载图片,例如
www.google.com。为了本章节的目的,搜索克里斯蒂亚诺·罗纳尔多,并通过www.pexels.com下载他的图片,如下图所示:

- 接下来,点击“下载所有图片”图标,并按照下图所示指定图片的下载设置:

- 点击“保存”,这样你将能够选择将所有图片下载为一个
.zip文件并保存到本地目录。然后,你可以将该文件解压到一个文件夹,并浏览所有图片。在我们的示例中,所有图片已经被提取到/Home/sparkNotebooks/Ch13/football/ronaldo/,如下图所示:

- 在文件夹中所有可用的图片中,选择 30 张罗纳尔多的图片,并将其命名为
ronaldo1.jpg、ronaldo2.jpg…ronaldo30.jpg,如下图所示:

- 再次重复步骤,这次为梅西获取 30 张图片。最终的文件夹结构应该如下所示:

其工作原理...
本节解释了插件如何将图片批量下载到我们所需位置的过程:
-
目前有很多批量图片下载软件,并且已经集成到浏览器中。我们将使用 Firefox 的“下载所有图片”插件,快速下载梅西和罗纳尔多的图片。
-
我们想要在应用中设置下载低质量的图片,因此我们设置了最小阈值为 0 字节,最大阈值为 500 字节,并将图片类型设置为
jpg或jpeg。 -
最后,我们希望精心挑选出最能代表每位球员的 30 张图片,其中 20 张将作为训练数据集,剩余的 10 张将作为测试数据集。其他所有图片都可以删除。
-
所有的图片将按姓氏和 1 到 30 之间的数字进行标记或标签,以便于训练使用。例如,
Messi1.jpg、Messi2.jpg、Ronaldo1.jpg、Ronaldo2.jpg,依此类推。
还有更多内容...
尽管你可以使用你自己下载的图片,也可以通过访问以下网站下载梅西和罗纳尔多的图片,这些图片将用于本章节的训练目的:
对于梅西:
github.com/asherif844/ApacheSparkDeepLearningCookbook/tree/master/CH13/football/messi
对于罗纳尔多:
github.com/asherif844/ApacheSparkDeepLearningCookbook/tree/master/CH13/football/ronaldo
另请参阅
其他浏览器也有类似的插件和扩展。如果你使用的是 Google Chrome,可以从以下网站下载名为Download'em All 的类似插件:
chrome.google.com/webstore/detail/downloadem-all/ccdfjnniglfbpaplecpifdiglfmcebce?hl=en-US
配置 PySpark 安装与深度学习包
还有一些额外的配置需要在 PySpark 中完成,以实现来自 Databricks 的spark-deep-learning深度学习包。这些配置是在第一章中进行的,为深度学习设置您的 Spark 环境。
准备工作
这个配置需要在终端中进行更改,使用bash。
如何做到……
下面的部分详细介绍了如何配置 PySpark 与深度学习包:
- 打开终端应用程序,并输入以下命令:
nano .bashrc.
-
滚动到文档底部,并查找我们在第一章中创建的
sparknotebook()函数,为深度学习设置您的 Spark 环境。 -
更新函数的最后一行。当前应如下所示:
$SPARK_HOME/bin/pyspark.
将其更改为以下内容:
$SPARK_HOME/bin/pyspark --packages databricks:spark-deep-learning:0.1.0-spark2.1-s_2.11.
- 一旦配置更改完成,退出文档并执行以下脚本以确认所有必要的更改已保存:
source .bashrc.
工作原理……
下面的部分解释了如何修改 PySpark 以整合深度学习包,请看这些步骤:
- 访问 bash 允许我们在命令行上进行配置,就像下面的屏幕截图所示:

-
在我们的文档末尾,我们可以看到我们原始的函数
sparknotebook()仍然完好无损;然而,我们需要修改它以整合spark-deep-learning包。 -
由于这次修改直接针对 PySpark,而不是 Python 库,我们无法通过典型的
pip安装将其整合到我们的框架中。相反,我们将修改我们的 PySpark 配置,使其显示如下屏幕截图所示:

- 我们现在已经配置了 PySpark 安装,以整合包含各种解决方案模型构建 API 的深度学习库,如图像分类。
还有更多……
这个包,spark-deep-learning,由Databricks管理。Databricks 是由 Spark 的共同创始人之一 Ali Ghodsi 创立的,用于通过统一平台提供托管的 Spark 解决方案。
另请参阅
若想了解更多为 Spark 开发的其他第三方包,请访问以下网站:
加载图像到 PySpark 数据框中
我们现在已经准备好将我们的图像导入笔记本进行分类。
准备工作
在这一部分,我们将使用几个库及其依赖项,这将要求我们在 Ubuntu 桌面中的终端通过pip install安装以下包:
pip install tensorflow==1.4.1
pip install keras==2.1.5
pip install sparkdl
pip install tensorframes
pip install kafka
pip install py4j
pip install tensorflowonspark
pip install jieba
如何操作...
以下步骤将展示如何将图像解码为 Spark 数据框:
- 启动一个
spark会话,使用以下脚本:
spark = SparkSession.builder \
.master("local") \
.appName("ImageClassification") \
.config("spark.executor.memory", "6gb") \
.getOrCreate()
- 从 PySpark 中导入以下库以创建数据框,使用以下脚本:
import pyspark.sql.functions as f
import sparkdl as dl
- 执行以下脚本,创建两个数据框,分别用于梅西和罗纳尔多,并使用每个球员的主文件夹位置:
dfMessi = dl.readImages('football/messi/').withColumn('label', f.lit(0))
dfRonaldo = dl.readImages('football/ronaldo/').withColumn('label', f.lit(1))
- 将每个数据框按照
66.7/33.3的比例拆分为训练和测试集,并设置随机种子为12,使用以下脚本:
trainDFmessi, testDFmessi = dfMessi.randomSplit([66.7, 33.3], seed = 12)
trainDFronaldo, testDFronaldo = dfRonaldo.randomSplit([66.7, 33.3], seed = 12)
- 最后,使用以下脚本将训练数据框和测试数据框合并为两个新的数据框,
trainDF和testDF:
trainDF = trainDFmessi.unionAll(trainDFronaldo)
testDF = testDFmessi.unionAll(testDFronaldo)
它是如何工作的...
以下部分解释了如何将图像加载并读取到 Jupyter 笔记本中。请查看这些步骤:
-
我们总是通过启动 Spark 会话来开始一个 Spark 项目,以设置应用程序名称并设置 Spark 执行内存。
-
我们导入了
pyspark.sql.functions和sparkdl,以帮助基于编码图像构建数据框。当导入sparkdl时,我们看到它在后端使用了 TensorFlow,如下图所示:

- 数据框使用
sparkdl创建,包含三列:文件路径、图像和标签。Sparkdl 用于导入每个图像,并通过颜色和形状对其进行编码。此外,lit函数用于为训练目的,在标签列下为每个数据框添加一个字面值(0 或 1),如下图所示:

- 由于每位足球运动员有 30 张图片,采用 66.7/33.3 的比例来创建 18 张训练图像和 12 张测试图像,如下图所示:
请注意,在深度学习中,使用更多的图像进行训练会更好。然而,我们在本章中要证明的是,随着迁移学习作为深度学习的扩展的实施,我们可以使用更少的训练样本来分类图像,正如本章中梅西和罗纳尔多每人只有 30 张图片的情况。

- 为了构建我们的模型,我们只关心创建一个包含 36 张图片的训练数据框,以及一个包含其余 24 张图片的测试数据框。一旦我们合并了数据框,可以确认它们的大小是正确的,如下图所示:

还有更多…
这可能在过程中丢失,但重要的是要注意,将图像加载到数据框架中非常简单,只需要几行代码,使用 sparkdl.readImages。这展示了 Spark 提供的机器学习管道的强大功能。
另请参见
要了解更多关于 sparkdl包的信息,请访问以下仓库:
databricks.github.io/spark-deep-learning/site/api/python/sparkdl.html
理解迁移学习
本章剩余部分将涉及迁移学习技术;因此,我们将在本节中解释迁移学习如何在我们的架构中工作。
准备工作
本节不需要任何依赖项。
如何做到这一点…
本节介绍了迁移学习的工作步骤:
-
确定一个预训练模型,将其作为迁移到我们选择任务的训练方法。在我们的例子中,任务将是识别梅西和罗纳尔多的图像。
-
有多个可用的预训练模型可以使用。最流行的几个如下:
-
Xception
-
InceptionV3
-
ResNet50
-
VGG16
-
VGG19
-
-
从预训练的卷积神经网络中提取特征,并在多个过滤和池化层中保存这些特征,应用于特定的图像集。
-
预训练卷积神经网络的最终层被替换为我们基于数据集要分类的特定特征。
它是如何工作的…
本节解释了迁移学习的方法论:
-
在前几章中,我们讨论了机器学习模型,尤其是深度学习模型,如何在较大样本的训练中表现最佳。事实上,深度学习的一般格言是:越多越好。
-
然而,有些情况下可能没有足够的高量数据或图像来训练一个模型。正是在这种情况下,我们希望将一个领域的学习迁移到预测另一个领域的结果。提取特征并通过卷积神经网络中的多个层进行过滤的繁重工作已经由一些开发了许多预训练模型的机构(如 InceptionV3 和 ResNet50)完成。
-
InceptionV3 是在谷歌开发的,权重比 ResNet50 和 VGG 小。
-
ResNet50 使用 50 个权重层
-
VGG16 和 VGG19 分别具有 16 个和 19 个权重层
-
-
一些高级深度学习库,如 Keras,现在预先构建了这些预训练网络,简化了应用,只需要指定模型名称。
还有更多…
确定哪种预训练模型最适合特定数据或图像集,取决于所使用的图像类型。最好尝试不同的预训练模型,确定哪个模型能提供最佳的准确度。
另请参见
要了解更多关于 Inception V3 预训练模型的信息,请阅读以下论文:
要了解更多关于 VGG 预训练模型的信息,请阅读以下论文:
创建图像分类训练管道
我们现在准备好为我们的数据集构建深度学习管道。
准备工作
以下库将被导入,以协助管道开发:
-
LogisticRegression -
Pipeline
如何操作…
以下部分将讲解创建图像分类管道的步骤:
- 执行以下脚本以开始深度学习管道并配置分类参数:
from pyspark.ml.classification import LogisticRegression
from pyspark.ml import Pipeline
vectorizer = dl.DeepImageFeaturizer(inputCol="image",
outputCol="features",
modelName="InceptionV3")
logreg = LogisticRegression(maxIter=30,
labelCol="label")
pipeline = Pipeline(stages=[vectorizer, logreg])
pipeline_model = pipeline.fit(trainDF)
- 创建一个新的数据框
predictDF,其中包含原始测试标签以及新的预测分数,使用以下脚本:
predictDF = pipeline_model.transform(testDF)
predictDF.select('prediction', 'label').show(n = testDF.toPandas().shape[0], truncate=False)
它是如何工作的…
以下部分解释了如何为图像分类配置管道,以实现最佳性能:
LogisticRegression已被导入,因为它将是区分梅西和罗纳尔多图像的主要分类算法。DeepImageFeaturizer从sparkdl导入,用于基于图像创建特征,这些特征将作为逻辑回归算法的最终输入。
需要注意的是,通过DeepImageFeaturizer创建的特征将使用基于InceptionV3的预训练模型,并分配一个名为vectorizer的变量。
逻辑回归模型的调优次数最多为 30 次迭代。最后,管道将vectorizer和LogisticRegression变量输入并拟合到训练数据框trainDF中。vectorizer用于将图像转化为数值。DeepImageFeaturizer的输出可见于以下截图:

- 测试数据框
testDF通过应用拟合后的管道模型pipeline_model,转换成新的数据框predictDF,并创建一个名为“prediction”的新列。我们可以将标签列与预测列进行比较,如下截图所示:

还有更多…
InceptionV3是我们用于图像分类的图像分类模型;然而,我们完全可以选择其他预训练模型,并在管道中比较准确性。
另请参阅
要了解更多关于迁移学习的信息,请阅读威斯康星大学的以下文章:
ftp.cs.wisc.edu/machine-learning/shavlik-group/torrey.handbook09.pdf
评估模型性能
我们已经准备好评估我们的模型,看看我们能多好地区分梅西和罗纳尔多。
准备工作
由于我们将进行一些模型评估,因此需要导入以下库:
MulticlassClassificationEvaluator
如何操作...
以下部分介绍了评估模型性能的步骤:
- 执行以下脚本,从
predictDF数据框创建混淆矩阵:
predictDF.crosstab('prediction', 'label').show().
- 通过执行以下脚本,基于我们的 24 张罗纳尔多和梅西的测试图像计算准确度分数:
from pyspark.ml.evaluation import MulticlassClassificationEvaluator
scoring = predictDF.select("prediction", "label")
accuracy_score = MulticlassClassificationEvaluator(metricName="accuracy")
rate = accuracy_score.evaluate(scoring)*100
print("accuracy: {}%" .format(round(rate,2))).
工作原理...
以下部分解释了如何评估模型性能。请查看这些图像:
- 我们可以将数据框
predictDF转换为交叉表,创建混淆矩阵。这让我们能够理解模型中有多少真正例、假正例、真反例和假反例,如下图所示:

- 此时,我们准备好计算使用 36 张训练图像准确分类 24 张剩余的罗纳尔多和梅西测试图像的模型效果。从上一张截图来看,我们在 24 张测试图像中有 21 张正确分类。我们有 2 张梅西的图像被误分类为罗纳尔多,还有一张罗纳尔多的图像被误分类为梅西。这应该会得出 88%的准确率。我们可以看到,来自
MulticlassClassificationEvaluator的准确率得分也显示我们的准确率为 87.5%,如下截图所示:

还有更多内容...
尽管我们最终使用了准确率作为评估模型性能的基准指标,但我们也可以同样使用精度或召回率。此外,我们使用了MulticlassClassificationEvaluator来评估模型的准确性。由于在此特定情况下,我们处理的是二分类问题,只有罗纳尔多和梅西两种类型的图像,因此我们也可以使用BinaryClassificationEvaluator,如下图所示:

我们最终得到了相同的 87.5%的准确率。
另见
要了解更多关于 PySpark 逻辑回归函数中的MulticlassClassificationEvaluator,请访问以下网站:
spark.apache.org/docs/2.2.0/ml-classification-regression.html
微调模型参数
任何模型的准确率总是有提升空间。在本节中,我们将讨论一些可以调整的参数,以提高我们从上一节得到的 87.5%的模型准确率。
准备工作
本节不需要任何新的前提条件。
如何操作...
本节介绍了如何微调模型的步骤。
- 定义一个新的逻辑回归模型,添加
regParam和elasticNetParam参数,如下脚本所示:
logregFT = LogisticRegression(
regParam=0.05,
elasticNetParam=0.3,
maxIter=15,labelCol = "label", featuresCol="features")
- 使用以下脚本为新创建的模型配置一个新的管道:
pipelineFT = Pipeline(stages=[vectorizer, logregFT])
- 使用以下脚本将管道拟合到训练数据集
trainDF:
pipeline_model_FT = pipelineFT.fit(trainDF)
- 应用模型转换到测试数据集
testDF,以便使用以下脚本比较实际与预测的得分:
predictDF_FT = pipeline_model_FT.transform(testDF)
predictDF_FT.crosstab('prediction', 'label').show()
- 最后,使用以下脚本评估新模型的准确率
binary_rate_FT:
binary_rate_FT = binaryevaluator.evaluate(predictDF_FT)*100
print("accuracy: {}%" .format(round(binary_rate_FT,2)))
它是如何工作的…
本节解释了如何微调模型:
-
逻辑回归模型
logregFT通过调整regParam和elasticNetParam参数进行微调。两个参数分别对应逻辑回归模型的 γ 和 α 参数。正则化参数regParam用于在最小化损失函数和减少模型过拟合之间找到平衡。我们越是让模型复杂化,它就越容易过拟合而无法泛化,但也可能会得到较低的训练误差。此外,我们越简化模型,它过拟合的可能性就越小,但训练误差可能会更高。 -
弹性网络参数
elasticNetParam是另一种正则化技术,用于结合多个正则化器(L1 和 L2),以减少模型的过拟合。此外,我们还将迭代次数从 20 次减少到 15 次,以查看通过同时包含正则化和减少迭代次数是否能够提高准确度。 -
如同本章前面所做的,我们创建了一个流水线,融合了从图像生成的数值特征、
vectorizer,以及我们的逻辑回归模型logregFT。 -
然后,模型在训练数据
trainDF上进行拟合,并将模型的转换应用到测试数据testDF上。 -
我们可以再次通过交叉表对比模型的实际结果和预测结果,如下图所示:

-
现在,我们只有 1 张被错误分类的图像,而上一节中有 3 张。我们通过将
maxIter降低到15轮次,并将regParam设置为0.05,elasticNetParam设置为0.3来实现这一点。 -
我们的新准确率已经达到了
95.83%,如下图所示:

还有更多…
当然,我们的准确率从 87.5%提升到了 95.83%,仅仅通过将特定参数融入我们的模型。进一步的微调和调整参数可能会帮助我们确定是否能够达到 100%的准确率,用于图像分类模型。
参见
想了解更多关于逻辑回归中的正则化和弹性网络参数,请访问以下网站:
spark.apache.org/docs/2.2.0/mllib-linear-methods.html#logistic-regression








得到
w,这实际上就是权重的变化。这个
w 被加到原始权重上,更新为新的权重。
(学习率总是远小于 1)将得到一个非常小的、可忽略的数字。
w)变得微乎其微。因此,新的或更新后的权重几乎等于之前的权重。
w。这会产生与之前权重差异很大的新权重。之所以叫做梯度爆炸问题,是因为梯度的值变得过大。
给出
w,这只是权重的变化。项
w 被加到原始权重中,用于更新为新的权重。
会得到一个非常非常大的数,这对于网络进一步优化权重没有任何用处,因为权重已不再处于相同的范围内。
w)爆炸。结果,新权重与之前的权重相比完全超出了范围。
[t],它是从更新门中产生的输出。在这种情况下,输出有两个部分。第一部分是一个 sigmoid 层,也叫做 输入门层,其主要功能是决定哪些值需要更新。下一层是一个 tanh 层,该层的主要功能是创建一个包含可以添加到单元状态中的新值的向量或数组。
),以便为每个隐藏单元产生一个介于 0 和 1 之间的输出值。然后,它会与当前状态进行逐元素相乘(如前图中的第一个操作所示)。









3 的下采样率是一个不错的比率。该值用来告诉模型何时对频繁出现的词进行下采样,因为这些词在分析中并不重要。此类词的例子包括:this、that、those、them 等等。






该截图显示了与单词
该截图显示了与单词


浙公网安备 33010602011771号