Spark-深度学习秘籍-全-

Spark 深度学习秘籍(全)

原文:zh.annas-archive.org/md5/D22F0E873CEFD5D61BC00E51F025B8FB

译者:飞龙

协议:CC BY-NC-SA 4.0

前言

随着深度学习在现代工业中迅速被广泛采用,组织机构正在寻找将流行的大数据工具与高效的深度学习库结合起来的方法。这将有助于深度学习模型以更高的效率和速度进行训练。

借助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 机器学习为旧金山市的消防部门呼叫开发分类模型的过程。

第六章,在生成网络中使用 LSTMs,提供了使用小说或大型文本语料库作为输入数据来定义和训练 LSTM 模型的实际方法,同时还使用训练好的模型生成自己的输出序列。

第七章,使用 TF-IDF 进行自然语言处理,介绍了对聊天机器人对话数据进行升级分类的步骤。

第八章,使用 XGBoost 进行房地产价值预测,专注于使用 Kings County 房屋销售数据集训练一个简单的线性模型,并用它来预测房价,然后深入研究一个稍微复杂的模型来提高预测准确性。

第九章,使用 LSTM 预测苹果股票市场成本,专注于使用 Keras 上的 LSTM 创建深度学习模型,以预测 AAPL 股票的股市价格。

第十章,使用深度卷积网络进行人脸识别,利用 MIT-CBCL 数据集中 10 个不同主题的面部图像来训练和测试深度卷积神经网络模型。

第十一章,使用 Word2Vec 创建和可视化词向量,专注于机器学习中向量的重要性,并指导用户如何利用 Google 的 Word2Vec 模型训练不同的模型并可视化从小说中生成的词向量。

第十二章,使用 Keras 创建电影推荐引擎,专注于使用深度学习库 Keras 为用户构建电影推荐引擎。

第十三章,在 Spark 上使用 TensorFlow 进行图像分类,专注于利用迁移学习识别世界上两位顶级足球运动员:克里斯蒂亚诺·罗纳尔多和利昂内尔·梅西。

为了充分利用本书

  1. 利用提供的所有链接,更好地理解本书中使用的一些术语。

  2. 互联网是当今世界上最大的大学。使用 YouTube、Udemy、edX、Lynda 和 Coursera 等网站的视频,了解各种深度学习和机器学习概念。

  3. 不要只是读这本书然后忘记它。在阅读本书时,实际实施每一步。建议您在阅读每个配方时打开您的 Jupyter Notebook,这样您可以在阅读书籍的同时处理每个配方,并同时检查您获得的每个步骤的输出。

下载示例代码文件

您可以从www.packtpub.com的帐户中下载本书的示例代码文件。如果您在其他地方购买了这本书,可以访问www.packtpub.com/support并注册,以便将文件直接发送到您的邮箱。

您可以按照以下步骤下载代码文件:

  1. 登录或注册www.packtpub.com

  2. 选择“支持”选项卡。

  3. 单击“代码下载和勘误”。

  4. 在搜索框中输入书名,然后按照屏幕上的说明操作。

下载文件后,请确保使用最新版本的解压缩软件解压缩文件夹:

  • WinRAR/7-Zip 适用于 Windows

  • Zipeg/iZip/UnRarX 适用于 Mac

  • 7-Zip/PeaZip 适用于 Linux

该书的代码包也托管在 GitHub 上,网址为github.com/PacktPublishing/Apache-Spark-Deep-Learning-Cookbook。如果代码有更新,将在现有的 GitHub 存储库上进行更新。

我们还有来自我们丰富的图书和视频目录的其他代码包,可在 github.com/PacktPublishing/ 上找到。去看看吧!

使用的约定

本书中使用了许多文本约定。

CodeInText:表示文本中的代码词、数据库表名、文件夹名、文件名、文件扩展名、路径名、虚拟 URL、用户输入和 Twitter 句柄。这是一个例子:"保存在工作目录内的 trained 文件夹下。"

代码块设置如下:

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

粗体:表示一个新术语、一个重要词或屏幕上看到的词。例如,菜单或对话框中的单词会出现在文本中。这是一个例子:"右键单击页面,然后单击“另存为”…"

警告或重要说明会出现在这样。

提示和技巧会出现在这样。

部分

在本书中,您会经常看到几个标题(准备工作如何做它是如何工作的还有更多另请参阅)。

清晰地说明如何完成食谱,使用以下部分:

准备工作

本节告诉您在食谱中可以期望什么,并描述如何设置食谱所需的任何软件或任何初步设置。

如何做…

本节包含遵循食谱所需的步骤。

它是如何工作的…

本节通常包括对前一节中发生的事情的详细解释。

还有更多…

本节包含有关食谱的其他信息,以使您对食谱更加了解。

另请参阅

本节提供了指向食谱的其他有用信息的链接。

第一章:为深度学习开发设置 Spark

在本章中,将涵盖以下内容:

  • 下载 Ubuntu 桌面镜像

  • 在 macOS 上使用 VMWare Fusion 安装和配置 Ubuntu

  • 在 Windows 上使用 Oracle VirtualBox 安装和配置 Ubuntu

  • 在 Google Cloud Platform 上安装和配置 Ubuntu 桌面

  • 在 Ubuntu 桌面上安装和配置 Spark 和先决条件

  • 将 Jupyter 笔记本与 Spark 集成

  • 启动和配置 Spark 集群

  • 停止 Spark 集群

介绍

深度学习是机器学习算法的专注研究,其主要学习方法是使用神经网络。深度学习在过去几年内迅速发展。微软、谷歌、Facebook、亚马逊、苹果、特斯拉等许多公司都在其应用程序、网站和产品中使用深度学习模型。与此同时,作为运行在大数据源之上的内存计算引擎,Spark 已经使处理大量信息变得更加容易和快速。事实上,Spark 现在已成为数据工程师、机器学习工程师和数据科学家的主要大数据开发工具。

由于深度学习模型在处理更多数据时表现更好,Spark 和深度学习之间的协同作用实现了完美的结合。几乎与用于执行深度学习算法的代码一样重要的是能够实现最佳开发的工作环境。许多才华横溢的人渴望开发神经网络,以帮助回答他们研究中的重要问题。不幸的是,深度学习模型开发的最大障碍之一是获得学习大数据所需的技术资源。本章的目的是为 Spark 上的深度学习创建一个理想的虚拟开发环境。

下载 Ubuntu 桌面镜像

Spark 可以为各种操作系统设置,无论是在本地还是在云中。对于我们的目的,Spark 将安装在以 Ubuntu 为操作系统的基于 Linux 的虚拟机上。使用 Ubuntu 作为首选虚拟机有几个优势,其中最重要的是成本。由于它们基于开源软件,Ubuntu 操作系统是免费使用的,不需要许可证。成本始终是一个考虑因素,本出版物的主要目标之一是尽量减少在 Spark 框架上开始深度学习所需的财务开支。

准备就绪

下载镜像文件需要满足一些最低要求:

  • 至少 2GHz 双核处理器

  • 至少 2GB 的系统内存

  • 至少 25GB 的免费硬盘空间

操作步骤...

按照配方中的步骤下载 Ubuntu 桌面镜像:

  1. 要创建 Ubuntu 桌面的虚拟机,首先需要从官方网站下载文件:www.ubuntu.com/download/desktop.

  2. 截至目前,Ubuntu 桌面 16.04.3 是可供下载的最新版本。

  3. 一旦下载完成,以.iso 格式访问以下文件:

ubuntu-16.04.3-desktop-amd64.iso

工作原理...

虚拟环境通过隔离与物理或主机机器的关系,提供了一个最佳的开发工作空间。开发人员可能会使用各种类型的主机环境,如运行 macOS 的 MacBook,运行 Windows 的 Microsoft Surface,甚至在 Microsoft Azure 或 AWS 云上的虚拟机;然而,为了确保代码执行的一致性,将部署一个 Ubuntu 桌面内的虚拟环境,可以在各种主机平台上使用和共享。

还有更多...

根据主机环境的不同,桌面虚拟化软件有几种选择。在使用 macOS 时,有两种常见的虚拟化软件应用:

  • VMWare Fusion

  • Parallels

另请参阅

要了解有关 Ubuntu 桌面的更多信息,请访问www.ubuntu.com/desktop

在 macOS 上使用 VMWare Fusion 安装和配置 Ubuntu

本节将重点介绍使用 Ubuntu 操作系统构建虚拟机的过程,使用VMWare Fusion

准备就绪

您的系统需要先安装 VMWare Fusion。如果您目前没有安装,可以从以下网站下载试用版本:

www.vmware.com/products/fusion/fusion-evaluation.html

如何操作...

按照本文步骤配置在 macOS 上使用 VMWare Fusion 的 Ubuntu:

  1. 一旦 VMWare Fusion 启动并运行,点击左上角的+按钮开始配置过程,并选择 New...,如下截图所示:

  1. 选择后,选择从磁盘或镜像安装的选项,如下截图所示:

  1. 选择从 Ubuntu 桌面网站下载的操作系统的iso文件,如下截图所示:

  1. 下一步将询问是否要选择 Linux Easy Install。建议这样做,并为 Ubuntu 环境设置显示名称/密码组合,如下截图所示:

  1. 配置过程几乎完成了。显示虚拟机摘要,可以选择自定义设置以增加内存和硬盘,如下截图所示:

  1. 虚拟机需要 20 到 40 GB 的硬盘空间就足够了;但是,将内存增加到 2 GB 甚至 4 GB 将有助于虚拟机在执行后续章节中的 Spark 代码时的性能。通过在虚拟机的设置下选择处理器和内存,并将内存增加到所需的数量来更新内存,如下截图所示:

工作原理...

设置允许手动配置必要的设置,以便在 VMWare Fusion 上成功运行 Ubuntu 桌面。根据主机机器的需求和可用性,可以增加或减少内存和硬盘存储。

还有更多...

现在剩下的就是第一次启动虚拟机,这将启动系统安装到虚拟机的过程。一旦所有设置完成并且用户已登录,Ubuntu 虚拟机应该可以用于开发,如下截图所示:

另请参阅

除了 VMWare Fusion 外,在 Mac 上还有另一款提供类似功能的产品。它被称为 Parallels Desktop for Mac。要了解有关 VMWare 和 Parallels 的更多信息,并决定哪个程序更适合您的开发,请访问以下网站:

在 Windows 上使用 Oracle VirtualBox 安装和配置 Ubuntu

与 macOS 不同,在 Windows 中有几种虚拟化系统的选项。这主要是因为在 Windows 上虚拟化非常常见,因为大多数开发人员都在使用 Windows 作为他们的主机环境,并且需要虚拟环境进行测试,而不会影响依赖于 Windows 的任何依赖项。

准备就绪

Oracle 的 VirtualBox 是一款常见的虚拟化产品,可以免费使用。Oracle VirtualBox 提供了一个简单的过程,在 Windows 环境中运行 Ubuntu 桌面虚拟机。

如何操作...

按照本配方中的步骤,在 Windows 上使用VirtualBox配置 Ubuntu:

  1. 启动 Oracle VM VirtualBox Manager。接下来,通过选择新建图标并指定机器的名称、类型和版本来创建一个新的虚拟机,如下截图所示:

  1. 选择“专家模式”,因为一些配置步骤将被合并,如下截图所示:

理想的内存大小应至少设置为2048MB,或者更好的是4096MB,具体取决于主机机器上的资源。

  1. 此外,为在 Ubuntu 虚拟机上执行深度学习算法设置一个最佳硬盘大小至少为 20GB,如果可能的话更大,如下截图所示:

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

  1. 在安装一段时间后,选择启动图标以完成虚拟机,并准备好进行开发,如下截图所示:

工作原理...

该设置允许手动配置必要的设置,以便在 Oracle VirtualBox 上成功运行 Ubuntu 桌面。与 VMWare Fusion 一样,内存和硬盘存储可以根据主机机器的需求和可用性进行增加或减少。

还有更多...

请注意,一些运行 Microsoft Windows 的机器默认情况下未设置为虚拟化,并且用户可能会收到初始错误,指示 VT-x 未启用。这可以在重新启动时在 BIOS 中进行反转,并且可以启用虚拟化。

另请参阅

要了解更多关于 Oracle VirtualBox 并决定是否适合您,请访问以下网站并选择 Windows 主机开始下载过程:www.virtualbox.org/wiki/Downloads

安装和配置 Ubuntu 桌面以在 Google Cloud Platform 上运行

之前,我们看到了如何在 VMWare Fusion 上本地设置 Ubuntu 桌面。在本节中,我们将学习如何在Google Cloud Platform上进行相同的设置。

准备工作

唯一的要求是一个 Google 账户用户名。首先使用您的 Google 账户登录到 Google Cloud Platform。Google 提供一个免费的 12 个月订阅,账户中有 300 美元的信用额度。设置将要求您的银行详细信息;但是,Google 不会在未明确告知您的情况下向您收费。继续验证您的银行账户,然后您就可以开始了。

操作方法...

按照配方中的步骤配置 Ubuntu 桌面以在 Google Cloud Platform 上运行:

  1. 一旦登录到您的 Google Cloud Platform,访问一个看起来像下面截图的仪表板:

Google Cloud Platform 仪表板

  1. 首先,点击屏幕左上角的产品服务按钮。在下拉菜单中,在计算下,点击 VM 实例,如下截图所示:

  1. 创建一个新实例并命名它。在我们的案例中,我们将其命名为ubuntuvm1。在启动实例时,Google Cloud 会自动创建一个项目,并且实例将在项目 ID 下启动。如果需要,可以重命名项目。

  2. 点击创建实例后,选择您所在的区域。

  3. 在启动磁盘下选择Ubuntu 16.04LTS,因为这是将在云中安装的操作系统。请注意,LTS 代表版本,并且将获得来自 Ubuntu 开发人员的长期支持。

  4. 接下来,在启动磁盘选项下,选择 SSD 持久磁盘,并将大小增加到 50GB,以增加实例的存储空间,如下截图所示:

  1. 接下来,将访问范围设置为允许对所有云 API 进行完全访问

  2. 在防火墙下,请检查允许 HTTP 流量允许 HTTPS 流量,如下图所示:

选择选项允许 HTTP 流量和 HTTPS 流量

  1. 一旦实例配置如本节所示,点击“创建”按钮创建实例。

点击“创建”按钮后,您会注意到实例已经创建,并且具有唯一的内部和外部 IP 地址。我们将在后期需要这个。SSH 是安全外壳隧道的缩写,基本上是在客户端-服务器架构中进行加密通信的一种方式。可以将其视为数据通过加密隧道从您的笔记本电脑到谷歌的云服务器,以及从谷歌的云服务器到您的笔记本电脑的方式。

  1. 点击新创建的实例。从下拉菜单中,点击在浏览器窗口中打开,如下图所示:

  1. 您会看到谷歌在一个新窗口中打开了一个 shell/终端,如下图所示:

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

  1. 在 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
  1. 当提示是否继续时,输入y并选择 ENTER,如下图所示:

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

  1. 外壳自动生成了一个启动脚本,如下图所示。可以通过复制并粘贴其PATH来访问和编辑此启动脚本:

  1. 在我们的情况下,查看和编辑脚本的命令是:
:~$ vim /home/amrith2kmeanmachine/.vnc/xstartup

这个PATH在每种情况下可能会有所不同。确保设置正确的PATHvim命令会在 Mac 上的文本编辑器中打开脚本。

本地 shell 生成了一个启动脚本以及一个日志文件。启动脚本需要在文本编辑器中打开和编辑,接下来将讨论这一点。

  1. 输入vim命令后,启动脚本的屏幕应该看起来像下图所示:

  1. 输入i进入INSERT模式。接下来,删除启动脚本中的所有文本。然后它应该看起来像下图所示:

  1. 将以下代码复制粘贴到启动脚本中:
#!/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 &
  1. 脚本应该出现在编辑器中,如下截图所示:

  1. 按 Esc 退出INSERT模式,然后输入:wq以写入并退出文件。

  2. 启动脚本配置完成后,在 Google shell 中输入以下命令关闭服务器并保存更改:

$ vncserver -kill :1
  1. 此命令应该生成一个类似下图中的进程 ID:

  1. 通过输入以下命令重新启动服务器:
$ vncserver -geometry 1024x640

接下来的一系列步骤将专注于从本地主机安全地进入 Google Cloud 实例的外壳隧道。在本地 shell/终端上输入任何内容之前,请确保已安装 Google Cloud。如果尚未安装,请按照位于以下网站的快速入门指南中的说明进行安装:

cloud.google.com/sdk/docs/quickstart-mac-os-x

  1. 安装完 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"
  1. 确保在上述命令中正确指定实例名称、项目 ID 和区域。按下 ENTER 后,本地 shell 的输出会变成下图所示的样子:

  1. 一旦您看到实例名称后跟着":~$",这意味着本地主机/笔记本电脑和 Google Cloud 实例之间已成功建立了连接。成功通过 SSH 进入实例后,我们需要一个名为VNC Viewer的软件来查看和与已在 Google Cloud Compute 引擎上成功设置的 Ubuntu 桌面进行交互。接下来的几个步骤将讨论如何实现这一点。

  2. 可以使用以下链接下载 VNC Viewer:

www.realvnc.com/en/connect/download/viewer/

  1. 安装完成后,点击打开 VNC Viewer,并在搜索栏中输入localhost::5901,如下截图所示:

  1. 接下来,在提示以下屏幕时点击continue

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

  1. 您将最终被带入到您在 Google Cloud Compute 上的 Ubuntu 虚拟机的桌面。当在 VNC Viewer 上查看时,您的 Ubuntu 桌面屏幕现在应该看起来像以下截图:

工作原理...

您现在已成功为与 Ubuntu 虚拟机/桌面交互设置了 VNC Viewer。建议在 Google Cloud 实例不使用时暂停或关闭实例,以避免产生额外费用。云方法对于可能无法访问高内存和存储资源的开发人员来说是最佳的。

还有更多...

虽然我们讨论了 Google Cloud 作为 Spark 的云选项,但也可以在以下云平台上利用 Spark:

  • Microsoft Azure

  • Amazon Web Services

另请参阅

要了解更多关于 Google Cloud Platform 并注册免费订阅,请访问以下网站:

cloud.google.com/

在 Ubuntu 桌面上安装和配置 Spark 及其先决条件

在 Spark 可以运行之前,需要在新创建的 Ubuntu 桌面上安装一些必要的先决条件。本节将重点介绍在 Ubuntu 桌面上安装和配置以下内容:

  • Java 8 或更高版本

  • Anaconda

  • Spark

准备工作

本节的唯一要求是具有在 Ubuntu 桌面上安装应用程序的管理权限。

操作步骤...

本节将逐步介绍在 Ubuntu 桌面上安装 Python 3、Anaconda 和 Spark 的步骤:

  1. 通过终端应用程序在 Ubuntu 上安装 Java,可以通过搜索该应用程序并将其锁定到左侧的启动器上找到,如下截图所示:

  1. 通过在终端执行以下命令,在虚拟机上进行 Java 的初始测试:
java -version
  1. 在终端执行以下四个命令来安装 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
  1. 接受 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)
  1. 接下来,安装最新版本的 Anaconda。当前版本的 Ubuntu 桌面预装了 Python。虽然 Ubuntu 预装 Python 很方便,但安装的版本是 Python 2.7,如下输出所示:
$ python --version
Python 2.7.12
  1. 当前版本的 Anaconda 是 v4.4,Python 3 的当前版本是 v3.6。下载后,通过以下命令访问Downloads文件夹查看 Anaconda 安装文件:
$ cd Downloads/
~/Downloads$ ls
Anaconda3-4.4.0-Linux-x86_64.sh
  1. 进入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下载

  1. 安装完成 Anaconda 后,重新启动终端应用程序,通过在终端中执行python --version来确认 Python 3 现在是 Anaconda 的默认 Python 环境:
$ python --version
Python 3.6.1 :: Anaconda 4.4.0 (64-bit)
  1. Linux 仍然提供 Python 2 版本,但在执行脚本时需要显式调用,如下命令所示:
~$ python2 --version
Python 2.7.12
  1. 访问以下网站开始 Spark 下载和安装过程:

spark.apache.org/downloads.html

  1. 选择下载链接。以下文件将下载到 Ubuntu 的下载文件夹中:

spark-2.2.0-bin-hadoop2.7.tgz

  1. 通过执行以下命令在终端级别查看文件:
$ cd Downloads/
~/Downloads$ ls
spark-2.2.0-bin-hadoop2.7.tgz
  1. 通过执行以下命令提取tgz文件:
~/Downloads$ tar -zxvf spark-2.2.0-bin-hadoop2.7.tgz
  1. 使用ls查看下载目录,显示tgz文件和提取的文件夹:
~/Downloads$ ls
spark-2.2.0-bin-hadoop2.7 spark-2.2.0-bin-hadoop2.7.tgz
  1. 通过执行以下命令,将提取的文件夹从下载文件夹移动到主目录文件夹:
~/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
  1. 现在,spark-2.2.0-bin-hadoop2.7文件夹已移动到主目录文件夹中,在左侧工具栏上选择文件图标时可以查看,如下截图所示:

  1. Spark 现在已安装。通过在终端级别执行以下脚本来启动 Spark:
~$ cd ~/spark-2.2.0-bin-hadoop2.7/
~/spark-2.2.0-bin-hadoop2.7$ ./bin/pyspark
  1. 执行最终测试,以确保 Spark 在终端上运行,通过执行以下命令来确保SparkContext在本地环境中驱动集群:
>>> sc
<SparkContext master=local[*] appName=PySparkShell>

工作原理...

本节解释了 Python、Anaconda 和 Spark 的安装过程背后的原因。

  1. Spark 在Java 虚拟机JVM)上运行,Java 软件开发工具包SDK)是 Spark 在 Ubuntu 虚拟机上运行的先决条件安装。

为了使 Spark 在本地机器或集群上运行,安装需要最低版本的 Java 6。

  1. Ubuntu 建议使用sudo apt install方法安装 Java,因为这样可以确保下载的软件包是最新的。

  2. 请注意,如果尚未安装 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>
  1. 虽然 Python 2 也可以,但被视为传统 Python。 Python 2 将于 2020 年面临终止生命周期日期;因此,建议所有新的 Python 开发都使用 Python 3,就像本出版物中的情况一样。直到最近,Spark 只能与 Python 2 一起使用。现在不再是这种情况。Spark 可以与 Python 2 和 3 一起使用。通过 Anaconda 是安装 Python 3 以及许多依赖项和库的便捷方式。Anaconda 是 Python 和 R 的免费开源发行版。Anaconda 管理 Python 中用于数据科学相关任务的许多常用软件包的安装和维护。

  2. 在安装 Anaconda 过程中,重要的是确认以下条件:

  • Anaconda 安装在/home/username/Anaconda3位置

  • Anaconda 安装程序将 Anaconda3 安装位置前置到/home/username/.bashrc中的PATH

  1. 安装 Anaconda 后,下载 Spark。与 Python 不同,Spark 不会预先安装在 Ubuntu 上,因此需要下载和安装。

  2. 为了进行深度学习开发,将选择以下 Spark 的偏好设置:

  • Spark 版本2.2.0 (2017 年 7 月 11 日)

  • 软件包类型:预构建的 Apache Hadoop 2.7 及更高版本

  • 下载类型:直接下载

  1. 一旦 Spark 安装成功,通过在命令行执行 Spark 的输出应该看起来类似于以下截图:

  2. 初始化 Spark 时需要注意的两个重要特性是,它是在Python 3.6.1 | Anaconda 4.4.0 (64 位) | 框架下,并且 Spark 标志的版本是 2.2.0。

  3. 恭喜!Spark 已成功安装在本地 Ubuntu 虚拟机上。但是,还没有完成所有工作。当 Spark 代码可以在 Jupyter 笔记本中执行时,Spark 开发效果最佳,特别是用于深度学习。幸运的是,Jupyter 已经在本节前面执行的 Anaconda 分发中安装了。

还有更多...

也许你会问为什么我们不直接使用pip install pyspark在 Python 中使用 Spark。之前的 Spark 版本需要按照我们在本节中所做的安装过程。从 2.2.0 开始的未来版本的 Spark 将开始允许通过pip方法直接安装。我们在本节中使用完整的安装方法,以确保您能够在使用早期版本的 Spark 时安装和完全集成 Spark。

另请参阅

要了解更多关于 Jupyter 笔记本及其与 Python 的集成,请访问以下网站:

jupyter.org

要了解有关 Anaconda 的更多信息并下载 Linux 版本,请访问以下网站:

www.anaconda.com/download/

将 Jupyter 笔记本与 Spark 集成

初学 Python 时,使用 Jupyter 笔记本作为交互式开发环境(IDE)非常有用。这也是 Anaconda 如此强大的主要原因之一。它完全整合了 Python 和 Jupyter 笔记本之间的所有依赖关系。PySpark 和 Jupyter 笔记本也可以做到同样的事情。虽然 Spark 是用 Scala 编写的,但 PySpark 允许在 Python 中进行代码转换。

做好准备

本节大部分工作只需要从终端访问.bashrc脚本。

如何操作...

PySpark 默认情况下未配置为在 Jupyter 笔记本中工作,但稍微调整.bashrc脚本即可解决此问题。我们将在本节中逐步介绍这些步骤:

  1. 通过执行以下命令访问.bashrc脚本:
$ nano .bashrc
  1. 滚动到脚本的最后应该会显示最后修改的命令,这应该是在上一节安装过程中由 Anaconda 设置的PATHPATH应该如下所示:
# added by Anaconda3 4.4.0 installer
export PATH="/home/asherif844/anaconda3/bin:$PATH"
  1. 在 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
}
  1. 更新后的.bashrc脚本应该保存后如下所示:

  2. 保存并退出.bashrc文件。建议通过执行以下命令并重新启动终端应用程序来确认.bashrc文件已更新:

$ source .bashrc

它是如何工作的...

本节的目标是将 Spark 直接集成到 Jupyter 笔记本中,以便我们不是在终端上进行开发,而是利用在笔记本中开发的好处。本节解释了在 Jupyter 笔记本中进行 Spark 集成的过程。

  1. 我们将创建一个名为sparknotebook的命令函数,我们可以从终端调用它,通过 Anaconda 安装打开一个 Spark 会话的 Jupyter 笔记本。这需要在.bashrc文件中设置两个设置:

  2. PySpark Python 设置为 python 3

  3. 将 PySpark 驱动程序设置为 Jupyter 的 Python

  4. 现在可以直接从终端访问sparknotebook函数,方法是执行以下命令:

$ sparknotebook
  1. 然后,该函数应通过默认的 Web 浏览器启动全新的 Jupyter 笔记本会话。可以通过单击右侧的“新建”按钮并在“笔记本”下选择“Python 3”来创建 Jupyter 笔记本中的新 Python 脚本,其扩展名为.ipynb,如下截图所示:

  2. 再次,就像在终端级别为 Spark 做的那样,将在笔记本中执行sc的简单脚本,以确认 Spark 是否通过 Jupyter 正常运行:

  3. 理想情况下,版本、主节点和应用名称应与在终端执行sc时的输出相同。如果是这种情况,那么 PySpark 已成功安装和配置为与 Jupyter 笔记本一起工作。

还有更多...

重要的是要注意,如果我们通过终端调用 Jupyter 笔记本而没有指定sparknotebook,我们的 Spark 会话将永远不会启动,并且在执行SparkContext脚本时会收到错误。

我们可以通过在终端执行以下内容来访问传统的 Jupyter 笔记本:

jupyter-notebook

一旦我们启动笔记本,我们可以尝试执行与之前相同的sc.master脚本,但这次我们将收到以下错误:

另请参阅

在线提供了许多公司提供 Spark 的托管服务,通过笔记本界面,Spark 的安装和配置已经为您管理。以下是:

启动和配置 Spark 集群

对于大多数章节,我们将要做的第一件事是初始化和配置我们的 Spark 集群。

准备就绪

在初始化集群之前导入以下内容。

  • from pyspark.sql import SparkSession

如何做...

本节介绍了初始化和配置 Spark 集群的步骤。

  1. 使用以下脚本导入SparkSession
from pyspark.sql import SparkSession
  1. 使用以下脚本配置名为sparkSparkSession
spark = SparkSession.builder \
    .master("local[*]") \
    .appName("GenericAppName") \
    .config("spark.executor.memory", "6gb") \
.getOrCreate()

它是如何工作的...

本节解释了SparkSession作为在 Spark 中开发的入口点的工作原理。

  1. 从 Spark 2.0 开始,不再需要创建SparkConfSparkContext来开始在 Spark 中进行开发。导入SparkSession将处理初始化集群。此外,重要的是要注意,SparkSessionpysparksql模块的一部分。

  2. 我们可以为我们的SparkSession分配属性:

  3. master:将 Spark 主 URL 分配给在我们的local机器上运行,并使用最大可用的核心数。

  4. appName:为应用程序分配一个名称

  5. config:将spark.executor.memory分配为6gb

  6. getOrCreate:确保如果没有可用的SparkSession,则创建一个,并在可用时检索现有的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的步骤。

  1. 执行以下脚本:

spark.stop()

  1. 通过执行以下脚本来确认会话是否已关闭:

sc.master

它是如何工作的...

本节将解释如何确认 Spark 集群已关闭。

  1. 如果集群已关闭,当在笔记本中执行另一个 Spark 命令时,将会收到以下截图中看到的错误消息:

还有更多...

在本地环境中工作时,关闭 Spark 集群可能并不那么重要;然而,在 Spark 部署在计算成本需要付费的云环境中,关闭集群将会很昂贵。

第二章:在 Spark 中创建神经网络

在本章中,将涵盖以下内容:

  • 在 PySpark 中创建数据框

  • 在 PySpark 数据框中操作列

  • 将 PySpark dataframe 转换为数组

  • 在散点图中可视化数组

  • 设置权重和偏差以输入神经网络

  • 对神经网络的输入数据进行归一化

  • 验证数组以获得最佳神经网络性能

  • 使用 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 笔记本配置的 Spark 环境开始本章和未来的章节:

sparknotebook

如何做...

在使用 PySpark 时,必须首先导入和初始化SparkSession,然后才能创建任何数据框:

  1. 使用以下脚本导入SparkSession
from pyspark.sql import SparkSession

  1. 配置SparkSession
spark = SparkSession.builder \
         .master("local") \
         .appName("Neural Network Model") \
         .config("spark.executor.memory", "6gb") \
         .getOrCreate()
sc = spark.sparkContext
  1. 在这种情况下,SparkSessionappName已命名为Neural Network Model,并且6gb已分配给会话内存。

它是如何工作的...

本节解释了如何创建我们的 Spark 集群并配置我们的第一个数据框。

  1. 在 Spark 中,我们使用.master()来指定我们是在分布式集群上运行作业还是在本地运行。在本章和其余章节中,我们将使用.master('local')在本地执行 Spark,并指定一个工作线程。这对于测试和开发目的是可以的,但如果部署到生产环境可能会遇到性能问题。在生产环境中,建议使用.master('local[*]')来设置 Spark 在本地可用的尽可能多的工作节点上运行。如果我们的机器上有 3 个核心,并且我们想要设置我们的节点数与之匹配,那么我们将指定.master('local[3]')

  2. 数据框变量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
  1. 在 PySpark 中,show()函数可以预览前 20 行,如使用上述脚本时所示:

还有更多...

如果没有明确说明,.show()功能默认显示 20 行。如果我们只想显示数据框的前 5 行,我们需要明确说明,如下脚本所示:df.show(5)

另请参阅

要了解有关 SparkSQL、数据框、函数和 PySpark 中数据集的更多信息,请访问以下网站:

spark.apache.org/docs/latest/sql-programming-guide.html

在 PySpark 数据框中操作列

数据框几乎完成了;但在构建神经网络之前,有一个需要解决的问题。与其将gender值保留为字符串,不如将该值转换为数值整数以进行计算,随着本章的进行,这一点将变得更加明显。

准备工作

这一部分需要导入以下内容:

  • from pyspark.sql import functions

如何做...

本节将介绍将数据框中的字符串转换为数值的步骤:

  • Female --> 0

  • Male --> 1

  1. 在数据框中转换列值需要导入functions
from pyspark.sql import functions
  1. 接下来,使用以下脚本将gender列修改为数值:
df = df.withColumn('gender',functions.when(df['gender']=='Female',0).otherwise(1))
  1. 最后,使用以下脚本重新排列列,使gender成为数据框中的最后一列:
df = df.select('height', 'weight', 'gender')

它是如何工作的...

本节解释了如何应用对数据框的操作。

  1. pyspark.sql中的functions具有几个有用的逻辑应用,可用于在 Spark 数据框中对列应用 if-then 转换。在我们的情况下,我们将Female转换为 0,Male转换为 1。

  2. 使用.withColumn()转换将数值应用于 Spark 数据框。

  3. 对于 Spark 数据框,.select()功能类似于传统 SQL,按照请求的顺序和方式选择列。

  4. 最终预览数据框将显示更新后的数据集,如下截图所示:

还有更多...

除了数据框的withColumn()方法外,还有withColumnRenamed()方法,用于重命名数据框中的列。

将 PySpark 数据框转换为数组

为了构建神经网络的基本组件,PySpark 数据框必须转换为数组。Python 有一个非常强大的库numpy,使得处理数组变得简单。

准备工作

numpy库应该已经随着anaconda3 Python 包的安装而可用。但是,如果由于某种原因numpy库不可用,可以使用终端上的以下命令进行安装:

pip installsudo pip install将通过使用请求的库来确认是否已满足要求:

import numpy as np

如何做...

本节将介绍将数据框转换为数组的步骤:

  1. 使用以下脚本查看从数据框中收集的数据:
df.select("height", "weight", "gender").collect()
  1. 使用以下脚本将收集的值存储到名为data_array的数组中:
data_array =  np.array(df.select("height", "weight", "gender").collect())
  1. 执行以下脚本以访问数组的第一行:
data_array[0]
  1. 同样,执行以下脚本以访问数组的最后一行:
data_array[28]

它是如何工作的...

本节解释了如何将数据框转换为数组:

  1. 我们的数据框的输出可以使用collect()收集,并如下截图所示查看:

  1. 数据框转换为数组,并且可以在以下截图中看到该脚本的输出:

  1. 可以通过引用数组的索引来访问任何一组heightweightgender值。数组的形状为(29,3),长度为 29 个元素,每个元素由三个项目组成。虽然长度为 29,但索引从[0]开始到[28]结束。可以在以下截图中看到数组形状以及数组的第一行和最后一行的输出:

  1. 可以将数组的第一个和最后一个值与原始数据框进行比较,以确认转换的结果没有改变值和顺序。

还有更多...

除了查看数组中的数据点外,还可以检索数组中每个特征的最小和最大点:

  1. 检索heightweightgender的最小和最大值,可以使用以下脚本:
print(data_array.max(axis=0))
print(data_array.min(axis=0))
  1. 脚本的输出可以在以下截图中看到:

最大height74英寸,最小height60英寸。最大重量为188磅,最小重量为107磅。性别的最小和最大值并不那么重要,因为我们已经为它们分配了01的数值。

另请参阅

要了解更多关于 numpy 的信息,请访问以下网站:

www.numpy.org

在散点图中可视化数组

本章将开发的神经网络的目标是在已知heightweight的情况下预测个体的性别。了解heightweightgender之间的关系的一个强大方法是通过可视化数据点来喂养神经网络。这可以通过流行的 Python 可视化库matplotlib来实现。

准备工作

numpy一样,matplotlib应该在安装 anaconda3 Python 包时可用。但是,如果由于某种原因matplotlib不可用,可以在终端使用以下命令进行安装:

pip installsudo pip install将通过使用所需的库来确认要求已经满足。

如何做到...

本节将介绍通过散点图可视化数组的步骤。

  1. 导入matplotlib库并使用以下脚本配置库以在 Jupyter 笔记本中可视化绘图:
 import matplotlib.pyplot as plt
 %matplotlib inline
  1. 接下来,使用numpymin()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
  1. 执行以下脚本来绘制每个genderheightweight
# 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])

它是如何工作的...

本节将解释如何将数组绘制为散点图:

  1. matplotlib库导入到 Jupyter 笔记本中,并配置matplotlib库以在 Jupyter 笔记本的单元格中内联绘制可视化

  2. 确定 x 和 y 轴的最小和最大值以调整我们的绘图,并给出一个最佳的外观图形。脚本的输出可以在以下截图中看到:

  1. 每个轴都添加了10个像素的缓冲区,以确保捕获所有数据点而不被切断。

  2. 创建一个循环来迭代每一行的值,并绘制weightheight

  3. 此外,Female gender分配了不同的样式点x,而Male gender分配了o

  4. 可以在以下截图中看到绘制 Weight vs Height by Gender 的脚本的输出:

还有更多...

散点图快速而简单地解释了数据的情况。散点图的右上象限和左下象限之间存在明显的分割。所有超过 140 磅的数据点表示Male gender,而所有低于该值的数据点属于Female gender,如下截图所示:

这个散点图将有助于确认当在本章后面创建神经网络时,选择随机身高和体重来预测性别的结果是什么。

另请参阅

要了解更多关于matplotlib的信息,请访问以下网站:

www.matplotlib.org

为输入神经网络设置权重和偏差。

PySpark 框架和数据现在已经完成。是时候转向构建神经网络了。无论神经网络的复杂性如何,开发都遵循类似的路径:

  1. 输入数据

  2. 添加权重和偏差

  3. 求和数据和权重的乘积

  4. 应用激活函数

  5. 评估输出并将其与期望结果进行比较

本节将重点放在设置权重上,这些权重创建了输入,输入进入激活函数。

准备工作

简单了解神经网络的基本构建模块对于理解本节和本章的其余部分是有帮助的。每个神经网络都有输入和输出。在我们的案例中,输入是个体的身高和体重,输出是性别。为了得到输出,输入与值(也称为权重:w1 和 w2)相乘,然后加上偏差(b)。这个方程被称为求和函数 z,并给出以下方程式:

z = (输入 1) x (w1) + (输入 2) x (w2) + b

权重和偏差最初只是随机生成的值,可以使用numpy执行。权重将通过增加或减少对输出的影响来为输入增加权重。偏差将在一定程度上起到不同的作用,它将根据需要将求和(z)的基线向上或向下移动。然后,z 的每个值通过激活函数转换为 0 到 1 之间的预测值。激活函数是一个转换器,它给我们一个可以转换为二进制输出(男/女)的值。然后将预测输出与实际输出进行比较。最初,预测和实际输出之间的差异将很大,因为在刚开始时权重是随机的。然而,使用一种称为反向传播的过程来最小化实际和预测之间的差异,使用梯度下降的技术。一旦我们在实际和预测之间达成可忽略的差异,我们就会存储神经网络的 w1、w2 和 b 的值。

如何做...

本节将逐步介绍设置神经网络的权重和偏差的步骤。

  1. 使用以下脚本设置值生成器的随机性:
np.random.seed(12345)
  1. 使用以下脚本设置权重和偏差:
w1 = np.random.randn()
w2 = np.random.randn()
b= np.random.randn()

工作原理...

本节解释了如何初始化权重和偏差,以便在本章的后续部分中使用:

  1. 权重是使用numpy随机生成的,并设置了随机种子以确保每次生成相同的随机数

  2. 权重将被分配一个通用变量w1w2

  3. 偏差也是使用numpy随机生成的,并设置了随机种子以确保每次生成相同的随机数

  4. 偏差将被分配一个通用变量b

  5. 这些值被插入到一个求和函数z中,它生成一个初始分数,将输入到另一个函数中,即激活函数,稍后在本章中讨论

  6. 目前,所有三个变量都是完全随机的。w1w2b的输出可以在以下截图中看到:

还有更多...

最终目标是获得一个预测输出,与实际输出相匹配。对权重和值进行求和的过程有助于实现这一过程的一部分。因此,随机输入的0.50.5将产生以下求和输出:

z = 0.5 * w1 + 0.5 * w2 + b 

或者,使用我们当前随机值w1w2,将得到以下输出:

z = 0.5 * (-0.2047) + 0.5 * (0.47894) + (-0.51943) = -7.557

变量z被分配为权重与数据点的乘积总和。目前,权重和偏差是完全随机的。然而,正如本节前面提到的,通过一个称为反向传播的过程,使用梯度下降,权重将被调整,直到确定出更理想的结果。梯度下降只是识别出我们的权重的最佳值的过程,这将给我们最好的预测输出,并且具有最小的误差。确定最佳值的过程涉及识别函数的局部最小值。梯度下降将在本章后面讨论。

另请参阅

要了解更多关于人工神经网络中权重和偏差的知识,请访问以下网站:

en.wikipedia.org/wiki/Artificial_neuron

为神经网络标准化输入数据

当输入被标准化时,神经网络的工作效率更高。这最小化了特定输入的幅度对其他可能具有较低幅度值的输入的整体结果的影响。本节将标准化当前个体的身高体重输入。

准备好

输入值的标准化需要获取这些值的平均值和标准差进行最终计算。

如何做...

本节将介绍标准化身高和体重的步骤。

  1. 使用以下脚本将数组切片为输入和输出:
X = data_array[:,:2]
y = data_array[:,2]
  1. 可以使用以下脚本计算 29 个个体的平均值和标准差:
x_mean = X.mean(axis=0)
x_std = X.std(axis=0)

  1. 创建一个标准化函数,使用以下脚本对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

它是如何工作的...

本节将解释身高和体重是如何被标准化的。

  1. data_array矩阵分为两个矩阵:

  2. X由身高和体重组成

  3. y由性别组成

  4. 两个数组的输出可以在以下截图中看到:

  1. X组件是输入,是唯一会经历标准化过程的部分。y组件,或性别,暂时将被忽略。标准化过程涉及提取所有 29 个个体的输入的平均值和标准差。身高和体重的平均值和标准差的输出可以在以下截图中看到:

  1. 身高的平均值约为 67 英寸,标准差约为 3.4 英寸。体重的平均值约为 145 磅,标准差约为 22 磅。

  2. 一旦它们被提取,使用以下方程对输入进行标准化:X_norm = (X - X_mean)/X_std

  3. 使用 Python 函数normalize()X数组进行标准化,现在X数组被分配到新创建的标准化集的值,如下截图所示:

另请参阅

要了解更多关于统计标准化的知识,请访问以下网站:

en.wikipedia.org/wiki/Normalization_(statistics)

验证数组以获得最佳神经网络性能

在确保我们的数组在即将到来的神经网络中获得最佳性能的过程中,一点验证工作可以走很长的路。

准备好

这一部分需要使用numpy.stack()函数进行一些numpy魔术。

如何做...

以下步骤将验证我们的数组是否已被标准化。

  1. 执行以下步骤以打印数组输入的平均值和标准差:
print('standard deviation')
print(round(X[:,0].std(axis=0),0))
print('mean')
print(round(X[:,0].mean(axis=0),0))
  1. 执行以下脚本将身高、体重和性别组合成一个数组data_array
data_array = np.column_stack((X[:,0], X[:,1],y))

它是如何工作的...

本节解释了数组如何被验证和构建,以便在神经网络中实现最佳的未来使用。

  1. 身高的新mean应为 0,standard deviation应为 1。这可以在以下截图中看到:

  1. 这是归一化数据集的确认,因为它包括平均值为 0 和标准差为 1。

  2. 原始的data_array对于神经网络不再有用,因为它包含了heightweightgender的原始、非归一化的输入值。

  3. 然而,通过一点点numpy魔法,data_array可以被重组,包括归一化的heightweight,以及gender。这是通过numpy.stack()完成的。新数组data_array的输出如下截图所示:

还有更多...

我们的数组现在已经准备就绪。我们的身高和体重的输入已经归一化,我们的性别输出标记为 0 或 1。

另请参阅

要了解有关numpy.stack()的更多信息,请访问以下网站:

docs.scipy.org/doc/numpy/reference/generated/numpy.stack.html

使用sigmoid设置激活函数

激活函数在神经网络中用于帮助确定输出,无论是是或否,真或假,或者在我们的情况下是 0 或 1(男/女)。此时,输入已经被归一化,并且已经与权重和偏差w1w2b相加。然而,权重和偏差目前完全是随机的,并且没有被优化以产生与实际输出匹配的预测输出。构建预测结果的缺失环节在于激活或sigmoid函数,如下图所示:

如果总和产生的数字非常小,它将产生激活为 0。同样,如果总和产生的数字相当大,它将产生激活为 1。这个函数很有用,因为它将输出限制为二进制结果,这对于分类非常有用。这些输出的后果将在本章的其余部分中讨论和澄清。

准备工作

sigmoid函数类似于逻辑回归函数,因为它计算出 0 到 1 之间的概率结果。此外,它给出了介于两者之间的范围。因此,可以设置条件,将大于 0.5 的任何值关联到 1,小于 0.5 的值关联到 0。

如何做到...

本节将逐步介绍使用样本数据创建和绘制sigmoid函数的步骤。

  1. 使用 Python 函数创建sigmoid函数,如下脚本所示:
def sigmoid(input):
  return 1/(1+np.exp(-input))
  1. 使用以下脚本为sigmoid曲线创建样本x值:
X = np.arange(-10,10,1)
  1. 此外,使用以下脚本为sigmoid曲线创建样本y值:
Y = sigmoid(X)
  1. 使用以下脚本绘制这些点的xy值:
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()

它是如何工作的...

本节介绍了 S 型函数背后的数学原理。

  1. sigmoid函数是逻辑回归的专门版本,用于分类。逻辑回归的计算用以下公式表示:

  1. 逻辑回归函数的变量代表以下含义:
  • L代表函数的最大值

  • k代表曲线的陡峭程度

  • x[midpoint]代表函数的中点值

  1. 由于sigmoid函数的陡度值为 1,中点为 0,最大值为 1,它产生以下函数:

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

  1. 我们使用 Python 创建了自己的sigmoid函数,并使用样本数据在-1010之间绘制了它。我们的绘图看起来与之前的通用sigmoid绘图非常相似。我们的sigmoid函数的输出如下截图所示:

另请参阅

要了解更多关于sigmoid函数起源的信息,请访问以下网站:

en.wikipedia.org/wiki/Sigmoid_function

创建 Sigmoid 导数函数

Sigmoid 函数是一个独特的函数,其中 Sigmoid 函数的导数值包括 Sigmoid 函数的值。也许你会问这有什么了不起。然而,由于 Sigmoid 函数已经计算,这使得在执行多层反向传播时处理更简单、更高效。此外,在计算中使用 Sigmoid 函数的导数来得出最佳的w1w2b值,以得出最准确的预测输出。

准备工作

对微积分中的导数有一定的了解将有助于理解 Sigmoid 导数函数。

如何做...

本节将介绍创建 Sigmoid 导数函数的步骤。

  1. 就像sigmoid函数一样,使用以下脚本可以使用 Python 创建sigmoid函数的导数:
def sigmoid_derivative(x):
    return sigmoid(x) * (1-sigmoid(x))
  1. 使用以下脚本绘制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 函数的导数的逻辑。

  1. 神经网络将需要sigmoid函数的导数来预测gender的准确输出。sigmoid函数的导数使用以下公式计算:

  1. 然后,我们可以使用 Python 中的原始 Sigmoid 函数sigmoid()创建 Sigmoid 函数的导数sigmoid_derivate()。我们可以在以下截图中将两个函数并排绘制:

  1. Sigmoid 导数跟踪原始 Sigmoid 函数的斜率。在绘图的早期阶段,当 Sigmoid 的斜率完全水平时,Sigmoid 导数也是 0.0。当 Sigmoid 的值接近 1 时,斜率也几乎完全水平。Sigmoid 的斜率的峰值在 x 轴的中点。因此,这也是 Sigmoid 导数的峰值。

另请参阅

要深入了解导数,请访问以下网站:

www.khanacademy.org/math/calculus-home/taking-derivatives-calc

在神经网络中计算成本函数

此时,是时候将本章前面强调的所有部分汇总起来,计算成本函数了,神经网络将使用该函数来确定预测结果与原始或实际结果的匹配程度,给定当前可用的 29 个个体数据点。成本函数的目的是确定实际值和预测值之间的差异。然后使用梯度下降来增加或减少w1w2b的值,以减少成本函数的值,最终实现我们的目标,得出与实际值匹配的预测值。

准备工作

成本函数的公式如下:

成本(x)=(预测-实际)²

如果成本函数看起来很熟悉,那是因为这实际上只是最小化实际输出和预测之间的平方差的另一种方式。神经网络中梯度下降或反向传播的目的是将成本函数最小化,直到该值接近 0。在那一点上,权重和偏差(w1w2b)将不再是由numpy生成的随机无关紧要的值,而是对神经网络模型有实际贡献的实际重要权重。

如何做...

本节将介绍计算成本函数的步骤。

  1. 设置学习率值为0.1,逐步改变权重和偏差,直到使用以下脚本选择最终输出:
learningRate = 0.1
  1. 使用以下脚本初始化一个名为allCosts的 Python 列表。
allCosts = []
  1. 创建一个for循环,使用以下脚本迭代 100,000 个场景:

  1. 使用以下脚本绘制 100,000 次迭代中收集的成本值:
plt.plot(all_costs)
plt.title('Cost Value over 100,000 iterations')
plt.xlabel('Iteration')
plt.ylabel('Cost Value')
plt.show()
  1. 可以使用以下脚本查看权重和偏差的最终值:
print('The final values of w1, w2, and b')
print('---------------------------------')
print('w1 = {}'.format(w1))
print('w2 = {}'.format(w2))
print('b = {}'.format(b))

它是如何工作的...

本节解释了如何使用成本函数生成权重和偏差。

  1. 将实施一个for循环,该循环将对权重和偏差执行梯度下降,以调整值,直到成本函数接近 0。

  2. 循环将迭代 100,000 次成本函数。每次从 29 个个体中随机选择heightweight的值。

  3. 从随机的heightweight计算出总和值z,并使用输入计算出sigmoid函数的predictedGender分数。

  4. 计算成本函数,并将其添加到跟踪 100,000 次迭代中的所有成本函数的列表allCosts中。

  5. 计算了一系列关于总和值(z)以及成本函数(cost)的偏导数。

  6. 这些计算最终用于根据成本函数更新权重和偏差,直到它们(w1w2b)在 100,000 次迭代中返回接近 0 的值。

  7. 最终,目标是使成本函数的值随着迭代次数的增加而减少。成本函数在 100,000 次迭代中的输出可以在下面的截图中看到:

  1. 在迭代过程中,成本值从约 0.45 下降到约 0.01。

  2. 此外,我们可以查看产生成本函数最低值的w1w2b的最终输出,如下截图所示:

还有更多...

现在可以测试权重和偏差的最终值,以计算成本函数的工作效果以及预测值与实际分数的比较。

以下脚本将通过每个个体创建一个循环,并基于权重(w1w2)和偏差(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

根据身高和体重预测性别

只有当预测模型实际上可以根据新信息进行预测时,它才有用。这适用于简单的逻辑或线性回归,或更复杂的神经网络模型。

准备好了

这就是乐趣开始的地方。本节的唯一要求是为男性和女性个体提取样本数据点,并使用其身高和体重值来衡量前一节中创建的模型的准确性。

如何做...

本节介绍了如何根据身高和体重预测性别的步骤。

  1. 创建一个名为input_normalize的 Python 函数,用于输入heightweight的新值,并输出归一化的身高和体重,如下脚本所示:
def input_normalize(height, weight):
    inputHeight = (height - x_mean[0])/x_std[0]
    inputWeight = (weight - x_mean[1])/x_std[1]
    return inputHeight, inputWeight
  1. height设置值为70英寸,为weight设置值为180磅,并将其分配给名为score的变量,如下脚本所示:
score = input_normalize(70, 180)
  1. 创建另一个 Python 函数,名为predict_gender,输出一个概率分数gender_score,介于 0 和 1 之间,以及一个性别描述,通过应用与w1w2b的求和以及sigmoid函数,如下脚本所示:
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

工作原理...

本节解释了如何使用身高和体重的新输入来生成性别的预测分数。

  1. 创建一个函数来输入新的身高和体重值,并将实际值转换为规范化的身高和体重值,称为inputHeightinputWeight

  2. 使用一个变量score来存储规范化的值,并创建另一个函数predictGender来输入分数值,并根据前一节中创建的w1w2b的值输出性别分数和描述。这些值已经经过梯度下降进行了预调整,以微调这些值并最小化cost函数。

  3. score值应用到predict_gender函数中,应该显示性别描述和分数,如下截图所示:

  1. 似乎70英寸的height180磅的weight的规格是男性的高预测器(99.999%)。

  2. 对于50英寸的height150磅的weight的另一个测试可能会显示不同的性别,如下截图所示:

  1. 同样,这个输入从sigmoid函数中产生了一个非常低的分数(0.00000000839),表明这些特征与Female性别密切相关。

另请参阅

要了解更多关于测试、训练和验证数据集的信息,请访问以下网站:

en.wikipedia.org/wiki/Training,_test,_and_validation_sets

可视化预测分数

虽然我们可以根据特定身高和体重的个体单独预测性别,但整个数据集可以通过使用每个数据点来绘制和评分,以确定输出是女性还是男性。

准备工作

本节不需要任何依赖项。

如何做...

本节将通过步骤来可视化图表中的所有预测点。

  1. 使用以下脚本计算图表的最小和最大点:
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)
  1. 生成xy值,增量为 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]
  1. 最后,使用本章前面使用过的类似脚本来生成性别分数并填充图表,如下脚本所示:
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])

工作原理...

本节解释了如何创建数据点以生成将被绘制的预测值。

  1. 根据数组值计算图表的最小和最大值。脚本的输出可以在下面的截图中看到:

  1. 我们为每个数据点生成 x 和 y 值,在 0.05 的增量内的最小和最大值,并将每个(x,y)点运行到预测分数中以绘制这些值。女性性别分数分配为红色,男性性别分数分配为蓝色,如下截图所示:

  1. 图表显示了根据所选的heightweight之间的性别分数的分界线。

第三章:卷积神经网络的痛点

在本章中,将介绍以下内容:

  • 痛点#1:导入 MNIST 图像

  • 痛点#2:可视化 MNIST 图像

  • 痛点#3:将 MNIST 图像导出为文件

  • 痛点#4:增强 MNIST 图像

  • 痛点#5:利用训练图像的替代来源

  • 痛点#6:优先考虑用于 CNN 的高级库

介绍

卷积神经网络CNN)在过去几年中一直备受关注。在图像识别方面取得了巨大成功。随着现代智能手机的出现,任何人现在都有能力拍摄大量物体的照片并将其发布在社交媒体网站上,这在当今时代非常相关。正是由于这种现象,卷积神经网络如今需求量很大。

有几个特性使 CNN 能够最佳地执行。它们需要以下特性:

  • 大量的训练数据

  • 视觉和空间数据

  • 强调过滤(池化)、激活和卷积,而不是传统神经网络中更明显的全连接层

虽然 CNN 已经广受欢迎,但由于其计算需求以及需要大量训练数据来获得性能良好的模型,它们在使用中存在一些局限性。我们将专注于可以应用于数据的技术,这些技术最终将有助于开发卷积神经网络,并解决这些局限性。在后面的章节中,当我们为图像分类开发模型时,我们将应用其中一些技术。

痛点#1:导入 MNIST 图像

用于图像分类的最常见数据集之一是MNIST数据集,它由成千上万个手写数字样本组成。根据 Yann LeCun、Corinna Cortes 和 Christopher J.C. Burges 的说法,修改后的国家标准与技术研究所MNIST)有以下用途:

这是一个适合想要尝试在真实世界数据上学习技术和模式识别方法的人的良好数据库,同时在预处理和格式化上花费最少的精力。

在我们的 Jupyter 笔记本中导入 MNIST 图像有几种方法。在本章中,我们将介绍以下两种方法:

  1. 直接通过 TensorFlow 库

  2. 通过 MNIST 网站手动操作

需要注意的一点是,我们将主要使用 MNIST 图像作为我们如何改进卷积神经网络性能的示例。所有这些将应用于 MNIST 图像的技术都可以应用于用于训练 CNN 的任何图像。

准备工作

唯一需要的要求是安装TensorFlow。它可能不会预先安装在 anaconda3 软件包中;因此,简单的pip安装将确认TensorFlow的可用性,或者如果当前不可用,则安装它。TensorFlow可以在终端中轻松安装,如下截图所示:

如何做...

TensorFlow库中有一个方便的内置示例集,可以直接使用。其中一个示例数据集就是MNIST。本节将介绍访问这些图像的步骤。

  1. 使用以下脚本将TensorFlow导入库,并使用别名tf
import tensorflow as tf
  1. 使用以下脚本从库中下载和提取图像,并保存到本地文件夹:
from tensorflow.examples.tutorials.mnist import input_data
data = input_data.read_data_sets('MNIST/', one_hot=True)
  1. 使用以下脚本检索将用于评估图像分类准确性的训练和测试数据集的最终计数:
print('Image Inventory')
print('----------')
print('Training: ' + str(len(data.train.labels)))
print('Testing: '+ str(len(data.test.labels)))
print('----------')

工作原理...

本节解释了访问 MNIST 数据集的过程:

  1. 一旦我们收到确认TensorFlow库已正确安装,就将其导入笔记本。

  2. 我们可以确认TensorFlow的版本,并将图像提取到我们的MNIST/本地文件夹中。提取过程可在笔记本的输出中看到,如下截图所示:

  1. 提取的四个文件分别命名为:

  2. t10k-images-idx3-ubyte.gz

  3. t10k-labels-idx1-ubyte.gz

  4. train-images-idx3-ubyte.gz

  5. train-labels-idx1-ubyte.gz

  6. 它们已经下载到MNIST/子文件夹中,如下截图所示:

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

  1. 这四个文件是测试和训练图像以及相应的测试和训练标签,用于识别测试和训练数据集中的每个图像。此外,明确定义了one_hot = True特性。这表明标签使用 one-hot 编码,有助于模型中的特征选择,因为每列的值将是 0 或 1。

  2. 还导入了库的一个子类,它将 MNIST 的手写图像存储到指定的本地文件夹中。包含所有图像的文件夹应该大约为 12MB,包括 55,000 张训练图像和 10,000 张测试图像,如下截图所示:

  1. 这 10,000 张图像将用于测试我们将在 55,000 张图像上训练的模型的准确性。

还有更多...

在尝试通过TensorFlow直接访问 MNIST 数据集时,有时可能会出现错误或警告。就像在本节前面看到的那样,当导入 MNIST 时,我们收到了以下警告:

警告:从:2 读取数据集(来自 tensorflow.contrib.learn.python.learn.datasets.mnist)已被弃用,并将在将来的版本中删除。

更新说明:

请使用替代方案,例如来自 tensorflow/models 的 official/mnist/dataset.py。

数据集可能会在未来的TensorFlow版本中被弃用,因此不再直接可访问。有时,当通过TensorFlow提取 MNIST 图像时,我们可能会遇到典型的HTTP 403 错误。这可能是因为网站暂时不可用。无论哪种情况,都有一种手动方法可以使用以下链接下载这四个.gz文件:

yann.lecun.com/exdb/mnist/

这些文件位于网站上,如下截图所示:

下载这些文件并将它们保存到一个可访问的本地文件夹,类似于直接从TensorFlow获取的文件所做的操作。

另请参阅

要了解更多关于MNIST手写数字数据库的信息,请访问以下网站:yann.lecun.com/exdb/mnist/

要了解更多关于 one-hot 编码的信息,请访问以下网站:hackernoon.com/what-is-one-hot-encoding-why-and-when-do-you-have-to-use-it-e3c6186d008f.

痛点#2:可视化 MNIST 图像

在 Jupyter 笔记本中处理图形时,绘制图像通常是一个主要的痛点。显示训练数据集中的手写图像至关重要,特别是当比较与手写图像相关联的标签的实际值时。

准备工作

用于可视化手写图像的唯一 Python 库是numpymatplotlib。这两个库应该已经通过 Anaconda 中的软件包可用。如果由于某种原因它们不可用,可以在终端使用以下命令进行pip安装:

  • pip install matplotlib

  • pip install numpy

如何做...

本节将介绍在 Jupyter 笔记本中可视化 MNIST 手写图像的步骤:

  1. 导入以下库,numpymatplotlib,并使用以下脚本配置matplotlib以进行inline绘图:
import numpy as np
import matplotlib.pyplot as plt
%matplotlib inline
  1. 使用以下脚本绘制前两个样本图像:
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 笔记本中查看 MNIST 手写图像的过程:

  1. 在 Python 中生成一个循环,从训练数据集中取样两幅图像。

  2. 最初,图像只是存储在numpy数组中的 0 到 1 之间的浮点格式的一系列值。数组的值是一个名为image的标记图像。然后将image数组重塑为一个名为pixels的 28 x 28 矩阵,其中 0 处为黑色,非 0 处为灰色。值越高,灰色越浅。例如,可以在以下截图中看到数字 8 的示例:

  1. 循环的输出产生了数字 7 和 3 的两幅手写图像以及它们的标签,如下截图所示:

  1. 除了绘制图像外,还会在图像上方打印训练数据集的标签。标签是一个长度为 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 区域的中心,将图像置于 28x28 图像中心。

--来自yann.lecun.com/exdb/mnist/.的 Leun、Cortes 和 Burges

痛点#3:将 MNIST 图像导出为文件

我们经常需要直接在图像中工作,而不是作为数组向量。本节将指导我们将数组转换为.png图像。

准备工作

将向图像导出向量需要导入以下库:

  • 从 matplotlib 导入图像

如何做...

本节将介绍将 MNIST 数组样本转换为本地文件的步骤。

  1. 创建一个子文件夹,将我们的图像保存到我们的主文件夹MNIST/中,使用以下脚本:
if not os.path.exists('MNIST/images'):
   os.makedirs('MNIST/images/')
os.chdir('MNIST/images/')
  1. 循环遍历 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')
  1. 执行以下脚本以查看从image_no_1.pngimage_no_9.png的图像列表:
print(os.listdir())

它是如何工作的...

本节解释了如何将 MNIST 数组转换为图像并保存到本地文件夹中。

  1. 我们创建一个名为MNIST/images的子文件夹,以帮助我们存储临时的.png图像,并将它们与 MNIST 数组和标签分开。

  2. 再次循环遍历data.train图像,并获得可以用于取样的九个数组。然后将图像保存为.png文件到我们的本地目录,格式如下:'image_no_{}.png'.format(i), pixels, cmap = 'gray'

  3. 可以在本地目录中看到九个图像的输出,如下截图所示:

还有更多...

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

另请参阅

要了解有关matplotlibimage.imsave的更多信息,请访问以下网站:

matplotlib.org/api/_as-gen/matplotlib.pyplot.imsave.html

痛点#4:增强 MNIST 图像

在处理图像识别时的主要缺点之一是某些图像的变化不够多样化。这可能导致卷积神经网络的运行不如我们希望的那样理想,并且由于训练数据的缺乏多样性而返回不理想的结果。有一些技术可用于规避这一缺点,我们将在本节中讨论其中一种。

准备工作

再次,我们已经为我们做了大部分繁重的工作。我们将使用一个流行的 Python 包augmentor,它经常与机器学习和深度学习建模一起使用,以生成现有图像的额外版本,经过扭曲和增强以获得更多的变化。

首先必须使用以下脚本进行pip安装:pip install augmentor

然后我们应该得到确认该包已安装,如下截图所示:

然后我们需要从 augmentor 中导入 pipeline 类:

  • from Augmentor import Pipeline

操作步骤...

本节介绍了增加我们九个样本图像的频率和增强的步骤。

  1. 使用以下脚本初始化augmentor函数:
from Augmentor import Pipeline
augmentor = Pipeline('/home/asherif844/sparkNotebooks/Ch03/MNIST/images')
  1. 执行以下脚本,以便augmentor函数可以根据以下规格旋转我们的图像:
augmentor.rotate(probability=0.9, max_left_rotation=25, max_right_rotation=25)
  1. 执行以下脚本,使每个图像通过两次迭代,每次迭代 10 次增强:
for i in range(1,3):
     augmentor.sample(10)

工作原理...

本节解释了如何使用我们的九个图像创建额外的扭曲图像。

  1. 我们需要为图像变换创建一个Pipeline并指定将要使用的图像的位置。这确保了以下内容:

  2. 图像的源位置

  3. 将要转换的图像数量

  4. 图像的目标位置

  5. 我们可以看到我们的目标位置已创建一个名为/output/的子文件夹,如下截图所示:

  1. augmentor函数被配置为将每个图像向右旋转 25 度或向左旋转 25 度,概率为 90%。基本上,概率配置确定增强发生的频率。

  2. 创建一个循环,对每个图像进行两次遍历,并对每个图像应用两次变换;但是,由于我们对每个变换都添加了概率,因此有些图像可能不会被转换,而其他图像可能会被转换超过两次。变换完成后,我们应该收到一条消息,如下截图所示:

  1. 一旦我们完成增强,我们可以访问/output/子目录,并查看每个数字如何略有改变,如下截图所示:

  1. 我们可以看到我们有几个数字 3、1、8、0 和 9 的变化,都有不同程度的旋转。现在我们已经将样本数据集增加了三倍,并且增加了更多的变化,而不必去提取更多的图像进行训练和测试。

还有更多...

我们只应用了rotate变换;但是,还有几种变换和增强功能可用于图像:

  • 透视扭曲

  • 弹性变形

  • 剪切

  • 裁剪

  • 镜像

当寻求增加训练数据集的频率和多样性时,并非所有这些转换都是必要的,但使用一些特征的组合并评估模型性能可能是有益的。

另请参阅

要了解更多关于augmentor的信息,请访问以下网站:

augmentor.readthedocs.io/en/master/

痛点#5:利用训练图像的替代来源

有时,没有足够的资源来执行卷积神经网络。这些资源可能来自计算的角度或数据收集的角度。在这种情况下,我们依赖其他来源来帮助我们对图像进行分类。

准备工作

利用预训练模型作为其他数据集上测试结果的来源的技术称为迁移学习。这里的优势在于,用于训练图像的大部分 CPU 资源被外包给了预训练模型。迁移学习最近已成为深度学习的常见扩展。

如何做...

本节解释了迁移学习的工作过程。

  1. 收集一系列数据集或图像,您有兴趣对其进行分类,就像您对传统机器学习或深度学习一样。

  2. 将数据集分割为训练和测试集,例如 75/25 或 80/20。

  3. 确定将用于识别图像模式和识别您希望分类的图像的预训练模型。

  4. 构建一个深度学习管道,将训练数据连接到预训练模型,并开发识别测试数据所需的权重和参数。

  5. 最后,在测试数据上评估模型性能。

它是如何工作的...

本节解释了将迁移学习应用于 MNIST 数据集的过程。

  1. 我们在使用迁移学习时确实采取了一种捷径的方法,因为我们要么在资源、时间或两者方面受到限制,我们正在利用已经完成的先前工作,并希望它能帮助我们解决一些新问题。

  2. 由于我们正在处理图像分类问题,因此应使用曾经用于分类常见图像的预训练模型。有许多常见的模型,但其中两个突出的是:

  3. 由微软开发的 ResNet 模型。

  4. 谷歌开发的 Inception 模型。

  5. 由于微软和谷歌都拥有广泛的图像库,因此两种模型都对图像分类非常有用,可以在更详细的层面提取特征。

  6. 在 Spark 中,有能力构建深度学习管道,并调用一个名为DeepImageFeaturizer的类,并将InceptionV3模型应用于从训练数据中收集的一组特征。然后使用某种二元或多分类评估器在测试数据上评估训练数据集。

  7. 深度学习或机器学习中的管道只是用于从数据收集的初始环境到应用模型对收集的数据进行最终评估或分类的工作流程过程。

还有更多...

与一切一样,使用迁移学习有利有弊。正如我们在本节前面讨论的那样,当您在资源有限时,对大型数据集进行自己的建模时,迁移学习是理想的选择。手头的源数据可能不具备预训练模型中的许多独特特征,导致模型性能不佳。可以随时切换到另一个预训练模型并评估模型性能。再次强调,迁移学习是一种快速失败的方法,当其他选择不可用时可以采取。

另请参阅

要了解有关微软 ResNet 的更多信息,请访问以下网站:

resnet.microsoft.com/

要了解有关谷歌 Inception 的更多信息,请访问以下网站:

www.tensorflow.org/tutorials/image_recognition

要了解更多关于 InceptionV3 的信息,您可以阅读康奈尔大学的题为《重新思考计算机视觉的 Inception 架构》的论文:

arxiv.org/abs/1512.00567

痛点#6:优先考虑用于 CNN 的高级库

有许多库可用于执行卷积神经网络。其中一些被认为是低级的,比如 TensorFlow,其中许多配置和设置需要大量编码。这对于经验不足的开发人员来说可能是一个主要痛点。还有其他库,比如 Keras,它是建立在诸如 TensorFlow 之类的库之上的高级框架。这些库需要更少的代码来构建卷积神经网络。通常,刚开始构建神经网络的开发人员会尝试使用 TensorFlow 来实现模型,并在途中遇到几个问题。本节将首先建议使用 Keras 构建卷积神经网络,而不是使用 TensorFlow 来预测 MNIST 数据集中的手写图像。

准备工作

在本节中,我们将使用 Keras 训练一个模型,以识别 MNIST 中的手写图像。您可以通过在终端执行以下命令来安装 Keras:

pip install keras

如何做...

本节将介绍构建一个模型来识别 MNIST 中手写图像的步骤。

  1. 使用以下脚本基于以下变量创建测试和训练图像和标签:
xtrain = data.train.images
ytrain = np.asarray(data.train.labels)
xtest = data.test.images 
ytest = np.asarray(data.test.labels)
  1. 使用以下脚本重塑测试和训练数组:
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)
  1. keras导入以下内容以构建卷积神经网络模型:
import keras
import keras.backend as K
from keras.models import Sequential
from keras.layers import Dense, Flatten, Conv2D
  1. 使用以下脚本设置图像排序:
K.set_image_dim_ordering('th')
  1. 使用以下脚本初始化Sequential model
model = Sequential()
  1. 使用以下脚本向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'))
  1. 使用以下脚本编译model
model.compile(optimizer='adam',loss='binary_crossentropy', 
              metrics=['accuracy'])
  1. 使用以下脚本训练model
model.fit(xtrain,ytrain,batch_size=512,epochs=5,
            validation_data=(xtest, ytest))
  1. 使用以下脚本测试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 中的手写图像。

  1. 对于任何模型开发,我们需要确定我们的测试和训练数据集以及特征和标签。在我们的情况下,这相当简单,因为来自 TensorFlow 的 MNIST 数据已经被分解为data.train.images用于特征和data.train.labels用于标签。此外,我们希望将标签转换为数组,因此我们利用np.asarray()来处理ytestytrain

  2. xtrainxtestytrainytest的数组目前不是用于 Keras 中的卷积神经网络的正确形状。正如我们在本章早期确定的那样,MNIST 图像的特征表示为 28 x 28 像素图像,标签表示 0 到 9 之间的十个值中的一个。x-arrays 将被重塑为(,28,28,1),y-arrays 将被重塑为(,10)。新数组的shape如下截图所示:

  1. 如前所述,Keras 是一个高级库;因此,它不会执行张量或卷积操作,而没有低级库(如 TensorFlow)的帮助。为了配置这些操作,我们将backend设置为KKeras的图像维度排序image_dim_ordering设置为tf,表示 TensorFlow。

请注意,后端也可以设置为其他低级库,如Theano。我们将维度排序设置为th。此外,我们需要重构特征的形状。然而,在过去的几年中,Theano并没有像TensorFlow那样获得同样的采用率。

  1. 一旦我们导入构建 CNN 模型所需的必要库,我们就可以开始构建模型的序列或层,Sequential()。为了演示目的,我们将保持这个模型尽可能简单,只有 4 层,以证明我们仍然可以在最小的复杂性下获得高准确性。每一层都是使用.add()方法添加的。

  2. 第一层被设置为构建一个二维(Conv2D)卷积层,这对于空间图像如 MNIST 数据是常见的。由于这是第一层,我们必须明确定义传入数据的input_shape。此外,我们指定一个kernel_size,用于设置用于卷积的窗口滤波器的高度和宽度。通常,这是 32 个滤波器的 3x3 窗口或 5x5 窗口。此外,我们必须为这一层设置一个激活函数,对于效率目的,特别是在神经网络的早期阶段,relu是一个不错的选择。

  3. 接下来,第二层将第一层的输入展平,以获取一个分类,我们可以用来确定图像是否是可能的 10 个数字之一。

  4. 第三,我们将第二层的输出传递到具有 128 个隐藏层的另一个具有relu激活函数的dense层。密集连接层中的函数包括input_shapekernel_size以及偏差,以创建每个 128 个隐藏层的输出。

  5. 最后一层是输出层,将决定 MNIST 图像的预测值是什么。我们添加另一个具有sigmoid函数的dense层,以输出我们的 MNIST 图像可能的 10 种情况的概率。Sigmoid 函数对于二元或多类分类结果很有用。

  6. 下一步是使用adam作为optimizer编译模型,并评估accuracy作为metricsadam优化器对于 CNN 模型很常见,当处理 10 种可能结果的多类分类场景时,使用categorical_crossentropy作为损失函数也很常见,这也是我们的情况。

  7. 我们使用batch_size512的图像进行5次运行或epochs来训练模型。每个 epoch 的损失和准确性都被捕获,并可以在以下截图中看到:

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

  1. 我们的模型似乎表现良好,准确率为 98.6%,损失率为 5%。

  2. 我们在 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

第四章:循环神经网络的痛点

在本章中,我们将涵盖以下内容:

  • 前馈网络简介

  • RNN 的顺序工作

  • 痛点#1 - 梯度消失问题

  • 痛点#2 - 梯度爆炸问题

  • LSTM 的顺序工作

介绍

循环神经网络已被证明在涉及学习和预测序列数据的任务中非常高效。然而,当涉及自然语言时,长期依赖性的问题就出现了,这基本上是记住特定对话、段落或句子的上下文,以便在未来做出更好的预测。例如,考虑一个句子,说:

去年,我碰巧访问了中国。中国的食物不仅与世界上其他地方提供的中国食物不同,而且人们也非常热情好客。在这个美丽的国家呆了三年,我学会了说一口很好的......

如果将前面的句子输入到循环神经网络中以预测句子中的下一个单词(比如中国),网络会发现很难,因为它没有句子上下文的记忆。这就是我们所说的长期依赖性。为了正确预测单词“中国”,网络需要知道句子的上下文,还需要记住我碰巧去年访问中国的事实。因此,循环神经网络在执行此类任务时效率低下。然而,长短期记忆单元LSTM)可以克服这个问题,它能够记住长期依赖性并将信息存储在细胞状态中。稍后将讨论 LSTM,但本章的大部分内容将重点介绍神经网络、激活函数、循环网络、循环网络的一些主要痛点或缺点,以及如何通过使用 LSTM 来克服这些缺点。

前馈网络简介

要理解循环网络,首先必须了解前馈网络的基础知识。这两种网络都是根据它们通过网络节点执行的一系列数学运算的方式命名的。一种只通过每个节点向一个方向传递信息(永远不会两次触及给定节点),而另一种则通过循环将信息传递并将其反馈到同一节点(有点像反馈循环)。很容易理解第一种称为前馈网络,而后者是循环的。

准备就绪

理解任何神经网络图表的最重要概念是计算图的概念。计算图实际上就是相互连接的神经网络节点,每个节点执行特定的数学函数。

操作步骤...

前馈神经网络通过一组计算节点(即数学运算符和激活函数)将输入(到输入层)传递到计算网络输出的层。输出层是神经网络的最终层,通常包含线性函数。输入层和输出层之间的层称为隐藏层,通常包含非线性元素或函数:

  1. 下图(a)显示了前馈神经网络中节点如何相互连接:

前馈神经网络

  1. 前馈神经网络主要通过隐藏层节点中使用的函数(激活函数)的类型来区分彼此。它们还通过在训练期间用于优化网络的其他参数的算法来区分彼此。

  2. 在前面的图中显示的节点之间的关系不需要对每个节点进行完全填充;优化策略通常从大量的隐藏节点开始,并通过消除连接和可能的节点来调整网络,随着训练的进行。在训练过程中可能不需要利用每个节点。

工作原理...

神经元是任何神经网络的基本结构元素。神经元可以被看作是一个简单的数学函数或运算符,它对通过它流动的输入进行操作,以产生从它流出的输出。神经元的输入与节点的权重矩阵相乘,对所有输入求和,进行平移,并通过激活函数传递。这基本上是数学中的矩阵运算,如下所述:

  1. 神经元的计算图表示如前图(b)所示。

  2. 单个神经元或节点的传递函数如下所示:

这里,x[ i ]是第 i 个节点的输入,w[ i ]是与第 i 个节点相关的权重项,b是通常添加的偏差,以防止过拟合,f(⋅)是作用于流入节点的输入的激活函数,y是节点的输出。

  1. 具有 S 形激活函数的神经元通常用于神经网络的隐藏层,并且恒等函数通常用于输出层。

  2. 激活函数通常被选择为确保节点的输出严格增加、平滑(连续的一阶导数)或渐近的方式。

  3. 以下的逻辑函数被用作 S 形激活函数:

  1. 使用反向传播算法训练的神经元,如果激活函数是反对称的,即f(-x) = -f(x),可能会学习得更快,就像 S 形激活函数的情况一样。反向传播算法将在本章的后续部分中详细讨论。

  2. 逻辑函数不是反对称的,但可以通过简单的缩放和移位来使其成为反对称,从而得到具有由f(x) = 1 - f²(x)描述的一阶导数的双曲正切函数,如下数学函数所示:

  1. S 形函数及其导数的简单形式允许快速准确地计算梯度,以优化权重和偏差的选择,并进行二阶误差分析。

还有更多...

在神经网络的各层中的每个神经元/节点上执行一系列矩阵运算。下图以更数学化的方式展示了前馈网络,这将帮助您更好地理解每个节点/神经元的操作:

  1. 直观地,我们可以看到输入(向量或矩阵)首先被权重矩阵相乘。然后添加一个偏差项,然后使用激活函数(如 ReLU、tanh、sigmoid、阈值等)激活以产生输出。激活函数是确保网络能够学习线性和非线性函数的关键。

  2. 然后,这个输出作为下一个神经元的输入,然后再次执行相同的一系列操作。许多这样的神经元组合在一起形成一个层(执行输入向量的某个功能或学习某个特征),许多这样的层组合在一起形成一个前馈神经网络,可以完全学会识别输入,如下图所示:

  1. 假设我们的前馈网络已经训练好,可以对狗和猫的图像进行分类。一旦网络训练好,如下图所示,它将学会在呈现新图像时将图像标记为狗或猫:

  1. 在这样的网络中,当前输出与先前或未来的输出之间没有关系。

  2. 这意味着前馈网络基本上可以暴露给任何随机的图像集合,它暴露给的第一张图像不一定会改变它对第二张或第三张图像的分类方式。因此,我们可以说在时间步t的输出与时间步t-1的输出是独立的。

  3. 前馈网络在图像分类等情况下效果很好,其中数据不是顺序的。前馈网络在使用两个相关变量时也表现良好,比如温度和位置、身高和体重、汽车速度和品牌等。

  4. 然而,可能存在当前输出依赖于先前时间步的输出的情况(数据的顺序很重要)。

  5. 考虑阅读一本书的情景。你对书中句子的理解基于你对句子中所有单词的理解。使用前馈网络来预测句子中的下一个单词是不可能的,因为在这种情况下输出取决于先前的输出。

  6. 同样,有许多情况下,输出需要先前的输出或一些先前输出的信息(例如,股市数据、自然语言处理、语音识别等)。前馈网络可以被修改如下图所示,以捕获先前输出的信息:

  1. 在时间步t,输入t以及t-1的信息都提供给网络,以获得时间t的输出。

  2. 同样,从时间步t以及新输入都被输入到网络中的时间步t+1,以产生t+1的输出。前面图表的右侧是表示这样一个网络的一般方式,其中网络的输出会作为未来时间步的输入。这样的网络被称为循环神经网络RNN)。

另请参见

激活函数:在人工神经网络中,节点的激活函数决定了节点在给定输入或一组输入时产生的输出类型。输出y[k]由输入u[k]和偏置b[k]通过激活函数φ(.)得到,如下式所示:

有各种类型的激活函数。以下是常用的几种:

  1. 阈值函数

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

  1. Sigmoid 函数

Sigmoid 函数是一个具有下限为 0 和上限为 1 的逻辑函数,与阈值函数一样。这个激活函数是连续的,因此也是可微的。在 Sigmoid 函数中,前面函数的斜率参数由α给出。这个函数是非线性的,这对于提高性能至关重要,因为它能够容纳输入数据中的非线性,而常规线性函数不能。具有非线性能力确保权重和偏置的微小变化会导致神经元输出的显著变化。

  1. 双曲正切函数(tanh)

这个函数使激活函数的范围从 0 到 1 变为-1 到+1。

  1. 修正线性单元(ReLU)函数:ReLU 是许多逻辑单元的平滑近似,产生稀疏的活动向量。以下是该函数的方程:

ReLU 函数图

在前面的图表中,softplus (x) = log ( 1 + e^x)是整流器的平滑近似。

  1. Maxout 函数:该函数利用一种称为“dropout”的技术,并改进了快速近似模型平均的准确性,以便促进优化。

Maxout 网络不仅学习隐藏单元之间的关系,还学习每个隐藏单元的激活函数。通过主动丢弃隐藏单元,网络被迫在训练过程中找到其他路径以从给定输入到输出。以下图表是这个过程如何工作的图形描述:

Maxout 网络

前面的图表显示了具有五个可见单元、三个隐藏单元和每个隐藏单元两个神经元的 Maxout 网络。Maxout 函数由以下方程给出:

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

Maxout、ReLU 和 PReLU 函数的图形比较

RNN 的顺序工作

递归神经网络是一种人工神经网络,旨在识别和学习数据序列中的模式。此类序列数据的一些示例包括:

  • 手写

  • 诸如客户评论、书籍、源代码等文本

  • 口语/自然语言

  • 数值时间序列/传感器数据

  • 股价变动数据

准备工作

在递归神经网络中,来自上一个时间步的隐藏状态被反馈到下一个时间步的网络中,如下图所示:

基本上,进入网络的朝上箭头代表 RNN 在每个时间步的输入(矩阵/向量),而从网络中出来的朝上箭头代表每个 RNN 单元的输出。水平箭头表示在特定时间步(由特定神经元)学习的信息传递到下一个时间步。

有关使用 RNN 的更多信息,请访问:

deeplearning4j.org/usingrnns

如何做…

在递归网络的每个节点/神经元上,进行一系列矩阵乘法步骤。首先将输入向量/矩阵乘以权重向量/矩阵,然后添加偏差项,最后通过激活函数产生输出(就像前馈网络的情况一样):

  1. 以下图表显示了一种直观和数学化的方式来可视化 RNNs,以计算图的形式:

  1. 在第一个时间步骤(即t=0),使用前面图表右侧的第一个公式计算h[0]。由于h^(-1)不存在,中间项变为零。

  2. 输入矩阵x[0]乘以权重矩阵w[i],并且将偏差b[h]添加到这个项。

  3. 然后将前面的两个矩阵相加,然后通过激活函数g[h]获得h[0]

  4. 同样,y[0]使用前面图表右侧的第二个方程计算,方法是将h[0]与权重矩阵w[y]相乘,加上偏差b[y],并通过激活函数g[y]传递。

  5. 在下一个时间步(即t=1),h^((t-1))存在。它就是h[0]。这个项乘以权重矩阵w[R],也作为网络的输入与新的输入矩阵x[1]一起提供。

  6. 这个过程在多个时间步骤中重复进行,权重、矩阵和偏差在不同的时间步骤中通过整个网络流动。

  7. 整个过程在一个迭代中执行,这构成了网络的前向传递。

它是如何工作的...

训练前馈神经网络最常用的技术是通过时间的反向传播。这是一种监督学习方法,用于通过在每个时间步之后更新网络中的权重和偏差来减少损失函数。执行多个训练周期(也称为时代),其中由损失函数确定的误差通过梯度下降的技术进行反向传播。在每个训练周期结束时,网络更新其权重和偏差,以产生接近期望输出的输出,直到达到足够小的误差:

  1. 在每次迭代期间,反向传播算法基本上实现以下三个基本步骤:
  • 输入数据的前向传递和计算损失函数

  • 梯度和误差的计算

  • 通过时间的反向传播和相应地调整权重和偏差

  1. 在通过激活函数加上偏差后的输入的加权和被馈送到网络中并获得输出后,网络立即比较预测输出与实际情况(正确输出)的差异有多大。

  2. 接下来,网络计算误差。这实际上就是网络输出减去实际/正确的输出。

  3. 下一步涉及根据计算的误差在整个网络中进行反向传播。然后更新权重和偏差以观察误差是增加还是减少。

  4. 网络还记得,增加权重和偏差会增加误差,或者减少权重和偏差会减少误差。

  5. 根据前述推论,网络在每次迭代期间继续以使误差最小的方式更新权重和偏差。下面的例子将使事情更清楚。

  6. 考虑一个简单的情况,教会机器如何将一个数字加倍,如下表所示:

  1. 正如你所看到的,通过随机初始化权重(W = 3),我们得到了 0、3、6 和 9 的输出。

  2. 误差是通过将正确输出的列减去模型输出的列来计算的。平方误差实际上就是每个误差项与自身相乘。通常最好使用平方误差,因为它消除了误差项中的负值。

  3. 模型随后意识到,为了最小化误差,需要更新权重。

  4. 假设在下一次迭代中,模型将其权重更新为W = 4。这将导致以下输出:

  1. 模型现在意识到,通过增加权重到W = 4,实际上误差增加了。因此,在下一次迭代中,模型通过将权重减小到W = 2来更新权重,从而得到实际/正确的输出。

  2. 请注意,在这个简单的情况下,当增加权重时,误差增加,当减少权重时,误差减少,如下所示:

  1. 在实际的神经网络中,每次迭代期间都会执行多次这样的权重更新,直到模型收敛到实际/正确的输出。

还有更多...

如前面的情况所示,当增加权重时,误差增加,但当减少权重时,误差减少。但这并不总是成立。网络使用以下图表来确定如何更新权重以及何时停止更新它们:

  • 让权重在第一次迭代开始时初始化为零。当网络通过从点 A 到 B 增加权重时,误差率开始减少。

  • 一旦权重达到 B 点,误差率就变得最小。网络不断跟踪误差率。

  • 进一步增加从点 B 到点 C 的权重后,网络意识到错误率再次开始增加。因此,网络停止更新其权重,并恢复到点 B 的权重,因为它们是最佳的。

  • 在下一个场景中,考虑一种情况,即权重被随机初始化为某个值(比如说,点 C),如下图所示:

  • 进一步增加这些随机权重后,错误也增加了(从点 C 开始并远离点 B,图中的小箭头表示)。

  • 网络意识到错误增加,并开始从点 C 减小权重,以使错误减少(在图中从点 C 向点 B 移动的长箭头表示)。这种权重减少会一直持续,直到错误达到最小值(图中的点 B)。

  • 网络继续在达到点 B 后进一步更新其权重(在图中从点 B 远离并向点 A 移动的箭头表示)。然后它意识到错误再次增加。因此,它停止权重更新,并恢复到给出最小错误值的权重(即点 B 处的权重)。

  • 这是神经网络在反向传播后执行权重更新的方式。这种权重更新是基于动量的。它依赖于在每次迭代期间网络中每个神经元计算的梯度,如下图所示:

基本上,每次输入流入神经元时,都会针对输出计算每个输入的梯度。链式法则用于在反向传播的后向传递期间计算梯度。

另请参阅

可以在以下链接找到反向传播背后的数学详细解释:

Andrej Karpathy 的博客中有大量关于递归神经网络的有用信息。以下是一个解释它们不合理有效性的链接:

痛点#1 - 梯度消失问题

递归神经网络非常适用于涉及序列数据的任务。然而,它们也有缺点。本节将重点讨论其中一个缺点,即梯度消失问题

准备工作

梯度消失问题的名称源于在反向传播步骤中,一些梯度消失或变为零。从技术上讲,这意味着在网络的反向传播过程中没有错误项被向后传播。当网络变得更深更复杂时,这就成为了一个问题。

如何做...

本节将描述递归神经网络中梯度消失问题的发生方式:

  • 在使用反向传播时,网络首先计算错误,这只是模型输出减去实际输出的平方(如平方误差)。

  • 使用这个错误,模型然后计算错误相对于权重变化的变化(de/dw)。

  • 计算得到的导数乘以学习率  得到 w,这就是权重的变化。术语 w 被添加到原始权重上,以将它们更新为新的权重。

  • 假设 de/dw(错误相对于权重的梯度或变化率)的值远小于 1,那么该术语乘以学习率  (始终远小于 1)得到一个非常小的可忽略的数字。

  • 这是因为在反向传播过程中,权重更新仅对最近的时间步准确,而在通过以前的时间步进行反向传播时,准确性会降低,并且当权重更新通过许多时间步回溯时,这种准确性几乎变得微不足道。

  • 在某些情况下,句子可能非常长,神经网络试图预测句子中的下一个单词。它基于句子的上下文进行预测,因此需要来自许多先前时间步的信息(这些被称为长期依赖)。网络需要通过的先前时间步数随着句子长度的增加而增加。在这种情况下,循环网络无法记住过去许多时间步的信息,因此无法进行准确的预测。

  • 当出现这种情况时,网络需要进行更多复杂的计算,因此迭代次数大大增加,同时误差项的变化减少(随着时间的推移)并且权重的变化变得微不足道。因此,新的或更新的权重几乎等于先前的权重。

  • 由于没有发生权重更新,网络停止学习或无法更新其权重,这是一个问题,因为这将导致模型过度拟合数据。

  • 整个过程如下图所示:

它是如何工作的...

本节将描述梯度消失问题的一些后果:

  1. 当我们使用一些基于梯度的优化技术训练神经网络模型时,就会出现这个问题。

  2. 通常,增加更多的隐藏层倾向于使网络能够学习更复杂的任意函数,从而在预测未来结果方面做得更好。深度学习由于具有大量的隐藏层(从 10 到 200 个),因此产生了很大的影响。现在可以理解复杂的序列数据,并执行诸如语音识别、图像分类、图像字幕等任务。

  3. 由前述步骤引起的问题是,在某些情况下,梯度变得非常小,几乎消失,这反过来阻止权重在未来时间步骤中更新其值。

  4. 在最坏的情况下,这可能导致网络的训练过程停止,这意味着网络停止通过训练步骤学习不同的特征。

  5. 反向传播的主要思想是,它允许我们作为研究人员监视和理解机器学习算法如何处理和学习各种特征。当梯度消失时,就不可能解释网络中发生了什么,因此识别和调试错误变得更加具有挑战性。

还有更多...

以下是解决梯度消失问题的一些方法:

  • 一种方法在一定程度上克服这个问题是使用 ReLU 激活函数。它计算函数*f(x)=max(0,x)(即,激活函数简单地将输出的较低级别阈值设为零),并防止网络产生负梯度。

  • 另一种克服这个问题的方法是对每个层进行无监督训练,然后通过反向传播对整个网络进行微调,就像 Jürgen Schmidhuber 在他对神经网络中多层次层次结构的研究中所做的那样。该论文的链接在下一节中提供。

  • 解决这个问题的第三种方法是使用 LSTM(长短期记忆)单元或 GRUs(门控循环单元),这些是特殊类型的 RNN。

另请参阅

以下链接提供了对梯度消失问题的更深入描述,以及一些解决该问题的方法:

痛点#2 - 爆炸梯度问题

递归神经网络的另一个缺点是爆炸梯度问题。这与梯度消失问题类似,但完全相反。有时在反向传播过程中,梯度会爆炸成异常大的值。与梯度消失问题一样,爆炸梯度问题发生在网络架构变得更深时。

准备工作

爆炸梯度问题的名称源于反向传播步骤中一些梯度消失或变为零的事实。从技术上讲,这意味着在网络的反向传播过程中没有误差项向后传播。当网络变得更深更复杂时,这就成为了一个问题。

如何做...

本节将描述递归神经网络中的爆炸梯度问题:

  • 爆炸梯度问题与梯度消失问题非常相似,但完全相反。

  • 当递归神经网络中出现长期依赖时,误差项向后传播时有时会爆炸或变得非常大。

  • 这个误差项乘以学习率的结果是一个极端大的w。这导致产生的新权重看起来与以前的权重非常不同。这被称为爆炸梯度问题,因为梯度的值变得太大。

  • 爆炸梯度问题以算法方式在以下图表中进行了说明:

工作原理...

由于神经网络使用基于梯度的优化技术来学习数据中存在的特征,因此必须保留这些梯度,以便网络根据梯度的变化计算误差。本节将描述爆炸梯度问题在递归神经网络中是如何发生的:

  • 在使用反向传播时,网络首先计算误差,这只是模型输出减去实际输出的平方(如平方误差)。

  • 使用这个误差,模型然后计算了相对于权重变化的误差变化(de/dw)。

  • 计算得到的导数乘以学习率得到w,这只是权重的变化。项w 被添加到原始权重上,以将它们更新为新的权重。

  • 假设 de/dw(误差相对于权重的梯度或变化率)的值大于 1,那么该项乘以学习率将得到一个非常非常大的数字,对于网络在进一步优化权重时是毫无用处的,因为权重已不再处于相同的范围内。

  • 这是因为在反向传播过程中,权重更新仅对最近的时间步准确,而在通过以前的时间步进行反向传播时,这种准确性会降低,并且当权重更新通过许多时间步回溯时几乎变得无关紧要。

  • 网络需要通过的以前时间步数随着输入数据中序列数量的增加而增加。在这种情况下,递归网络无法记住过去许多时间步的信息,因此无法准确预测未来时间步。

  • 当出现这种情况时,网络需要进行更多复杂的计算,因此迭代次数大大增加,错误项的变化超过 1,权重(w)的变化激增。结果,与先前的权重相比,新的或更新的权重完全超出范围。

  • 由于没有发生权重更新,网络停止学习或无法在指定范围内更新其权重,这是一个问题,因为这将导致模型过度拟合数据。

还有更多...

以下是解决梯度爆炸问题的一些方法:

  • 可以应用某些梯度裁剪技术来解决梯度爆炸的问题。

  • 另一种预防方法是使用截断的时间反向传播,而不是从最后一个时间步(或输出层)开始反向传播,我们可以选择一个较小的时间步(比如 15)开始反向传播。这意味着网络将一次只反向传播最后的 15 个时间步,并且只学习与这 15 个时间步相关的信息。这类似于将小批量数据馈送到网络中,因为在大型数据集的情况下,计算每个数据集元素的梯度将变得过于昂贵。

  • 防止梯度爆炸的最后一种选择是监控它们并相应地调整学习率。

另请参阅

可以在以下链接找到有关消失和爆炸梯度问题的更详细解释:

LSTMs 的顺序工作

长短期记忆单元LSTM)单元只是相对于循环网络而言稍微更先进的架构。LSTMs 可以被认为是一种具有学习顺序数据中存在的长期依赖关系能力的特殊类型的循环神经网络。其主要原因是 LSTMs 包含内存,并且能够存储和更新其单元内的信息,而不像循环神经网络那样。

准备好了

长短期记忆单元的主要组成部分如下:

  • 输入门

  • 遗忘门

  • 更新门

这些门中的每一个都由一个 S 形层和一个逐点乘法操作组成。S 形层输出介于零和一之间的数字。这些值描述了每个组件的信息有多少被允许通过相应的门。值为零意味着门不允许任何信息通过,而值为一意味着门允许所有信息通过。

了解 LSTM 单元的最佳方法是通过计算图,就像循环神经网络的情况一样。

LSTMs 最初是由 Sepp Hochreiter 和 Jurgen Schmidhuber 于 1997 年开发的。以下是他们发表的论文链接:

如何做...

本节将描述单个 LSTM 单元的内部组件,主要是单元内部存在的三个不同门。一系列这样的单元堆叠在一起形成一个 LSTM 网络:

  1. LSTMs 也像 RNNs 一样具有链式结构。标准 RNN 基本上是重复单元的模块,如简单函数(例如 tanh)。

  2. 与 RNN 相比,由于每个单元中存在内存,LSTM 具有比 RNN 更长时间地保留信息的能力。这使它们能够在输入序列的早期阶段学习重要信息,并且还赋予了它们在每个时间步的决策中产生重要影响的能力。

  3. 通过能够从输入序列的早期阶段存储信息,LSTM 能够积极地保留可以通过时间和层进行反向传播的错误,而不是让该错误消失或爆炸。

  4. LSTM 能够在许多时间步长上学习信息,因此通过保留通过这些层进行反向传播的错误,具有更密集的层架构。

  5. 细胞结构称为“门”赋予了 LSTM 保留信息、添加信息或从细胞状态中删除信息的能力。

  6. 以下图示了 LSTM 的结构。在尝试理解 LSTM 时的关键特征在于理解 LSTM 网络架构和细胞状态,可以在这里进行可视化:

  1. 在前面的图中,x[t]h[t-1]是细胞的两个输入。x[t]是当前时间步的输入,而h[t-1]是上一个时间步的输入(即上一个时间步的细胞的输出)。除了这两个输入,我们还有h[, ],它是经过门控循环单元(LSTM)细胞对这两个输入进行操作后的当前输出(即时间步 t)。

  2. 在前面的图中,r[t]表示从输入门中出现的输出,它接受h[t-1]和x[t]的输入,将这些输入与其权重矩阵W[z]相乘,并通过 S 形激活函数传递。

  3. 类似地,术语z[t]表示从遗忘门中出现的输出。这个门有一组权重矩阵(由W[r]表示),这些权重矩阵特定于这个特定的门,并控制门的功能。

  4. 最后,还有[t],它是从更新门中出现的输出。在这种情况下,有两个部分。第一部分是一个称为输入门层的 S 形层,其主要功能是决定要更新哪些值。下一层是一个 tanh 层。这一层的主要功能是创建一个包含可以添加到细胞状态中的新值的向量或数组。

它是如何工作的...

一系列 LSTM 细胞/单元的组合形成了 LSTM 网络。这种网络的架构如下图所示:

  1. 在前面的图中,完整的 LSTM 细胞由“A”表示。细胞接受输入序列的当前输入(x**[i]),并产生(h**[i]),这实际上就是当前隐藏状态的输出。然后将此输出作为下一个 LSTM 细胞的输入。

  2. LSTM 细胞比 RNN 细胞稍微复杂一些。RNN 细胞只有一个作用于当前输入的功能/层,而 LSTM 细胞有三个层,即控制细胞在任何给定时间点流动的三个门。

  3. 细胞的行为很像计算机中的硬盘内存。因此,细胞具有允许在其细胞状态内写入、读取和存储信息的能力。细胞还会决定存储哪些信息,以及何时允许读取、写入和擦除信息。这是通过相应地打开或关闭门来实现的。

  4. LSTM 细胞中的门是模拟的,与当今计算机中的数字存储系统形成对比。这意味着门只能通过 S 形函数的逐元素乘法来控制,产生介于 0 和 1 之间的概率值。高值将导致门保持打开,而低值将导致门保持关闭。

  5. 模拟系统在神经网络操作方面比数字系统更具优势,因为它们是可微分的。这使得模拟系统更适合像反向传播这样主要依赖于梯度的任务。

  6. 门传递信息或阻止信息,或者只让部分信息根据其强度和重要性流过它们。每一次时间步骤,信息都会通过特定于每个门的权重矩阵集合进行过滤。因此,每个门都完全控制如何对接收到的信息进行操作。

  7. 与每个门相关的权重矩阵,如调制输入和隐藏状态的权重,都是根据递归网络的学习过程和梯度下降进行调整的。

  8. 第一个门被称为“遗忘门”,它控制从上一个状态中保留哪些信息。该门将上一个细胞的输出(h[t] - 1)作为其输入,以及当前输入(x**[t]),并应用 sigmoid 激活()以产生每个隐藏单元的 0 到 1 之间的输出值。然后进行与当前状态的逐元素乘法(在前面图表中的第一个操作中说明)。

  9. 第二个门被称为“更新门”,其主要功能是根据当前输入更新细胞状态。该门将与遗忘门的输入相同的输入(h**[t-1]x**[t])传递到一个 sigmoid 激活层(),然后经过 tanh 激活层,并对这两个结果进行逐元素乘法。接下来,将结果与当前状态进行逐元素加法(在前面图表中的第二个操作中说明)。

  10. 最后,有一个输出门,它控制传递到相邻细胞的信息和信息量,以作为下一个时间步骤的输入。当前细胞状态通过 tanh 激活层传递,并在通过 sigmoid 层()进行此操作后,与细胞输入(h**[t-1]x**[t])进行逐元素乘法。

  11. 更新门的行为就像细胞决定输出到下一个细胞的过滤器。这个输出 h[t]然后传递给下一个 LSTM 细胞作为它的输入,并且如果许多 LSTM 细胞堆叠在一起,也传递给上面的层。

还有更多...

与前馈网络和循环神经网络相比,LSTM 是一个重大的飞跃。人们可能会想知道未来的下一个重大进步是什么,甚至可能是什么。许多研究人员认为,“注意力”是人工智能领域的下一个重大进步。随着每天数据量的急剧增长,处理每一位数据变得不可能。这就是注意力可能成为潜在的游戏改变者的地方,使网络只关注高优先级或感兴趣的数据或区域,并忽略无用的信息。例如,如果一个 RNN 被用来创建图像字幕引擎,它将只选择图像的一部分来关注,以便输出每个单词。

徐等人在 2015 年的最新论文正是这样做的。他们探索了在 LSTM 细胞中添加注意力。阅读这篇论文可以是学习神经网络中使用注意力的好起点。在各种任务中使用注意力已经取得了一些良好的结果,目前正在对该主题进行更多的研究。徐等人的论文可以通过以下链接找到:

arxiv.org/pdf/1502.03044v2.pdf

注意力并不是 LSTM 的唯一变体。一些其他活跃的研究是基于格子 LSTM 的利用,正如 Kalchbrenner 等人在其论文中使用的那样,链接在:arxiv.org/pdf/1507.01526v1.pdf

另请参阅

关于生成网络中的 RNN 和 LSTM 的其他有用信息和论文可以通过访问以下链接找到:

第五章:使用 Spark ML 预测消防部门呼叫

在本章中,将涵盖以下内容:

  • 下载旧金山消防部门呼叫数据集

  • 识别逻辑回归模型的目标变量

  • 为逻辑回归模型准备特征变量

  • 应用逻辑回归模型

  • 评估逻辑回归模型的准确性

介绍

分类模型是预测定义的分类结果的一种流行方式。我们经常使用分类模型的输出。每当我们去电影院看电影时,我们都想知道这部电影是否被认为是正确的?数据科学社区中最流行的分类模型之一是逻辑回归。逻辑回归模型产生的响应由 S 形函数激活。S 形函数使用模型的输入并产生一个在 0 和 1 之间的输出。该输出通常以概率分数的形式呈现。许多深度学习模型也用于分类目的。通常会发现逻辑回归模型与深度学习模型一起执行,以帮助建立深度学习模型的基线。S 形激活函数是深度学习中使用的许多激活函数之一,用于产生概率输出。我们将利用 Spark 内置的机器学习库构建一个逻辑回归模型,该模型将预测旧金山消防部门的呼叫是否实际与火灾有关,而不是其他事件。

下载旧金山消防部门呼叫数据集

旧金山市在整个地区收集消防部门的服务呼叫记录做得非常好。正如他们的网站上所述,每条记录包括呼叫编号、事件编号、地址、单位标识符、呼叫类型和处理结果。包含旧金山消防部门呼叫数据的官方网站可以在以下链接找到:

data.sfgov.org/Public-Safety/Fire-Department-Calls-for-Service/nuek-vuh3

有关数据集的一些一般信息,包括列数和行数,如下截图所示:

这个当前数据集,更新于 2018 年 3 月 26 日,大约有 461 万行和 34 列。

准备工作

数据集以.csv文件的形式可供下载,并可在本地机器上下载,然后导入 Spark。

操作步骤如下:

本节将介绍下载和导入.csv文件到我们的 Jupyter 笔记本的步骤。

  1. 通过选择导出然后 CSV 从网站下载数据集,如下截图所示:

  1. 如果还没有这样做,请将下载的数据集命名为Fire_Department_Calls_for_Service.csv

  2. 将数据集保存到任何本地目录,尽管理想情况下应该保存到包含本章中将使用的 Spark 笔记本的相同文件夹中,如下截图所示:

  1. 一旦数据集已保存到与笔记本相同的目录中,执行以下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 会话中。

  1. 通过从pyspark.sql导入SparkSession来初始化本地pyspark会话。

  2. 通过使用选项header='true'inferschema='true'读取 CSV 文件创建一个名为df的数据框。

  3. 最后,始终最好运行一个脚本来显示已通过数据框导入 Spark 的数据,以确认数据已传输。可以在以下截图中看到该脚本的结果,显示了来自旧金山消防局呼叫的数据集的前两行:

请注意,当我们将文件读入 spark 时,我们使用.load().csv文件拉入 Jupyter 笔记本。对于我们的目的来说,这是可以的,因为我们使用的是本地集群,但如果我们要利用 Hadoop 中的集群,这种方法就行不通了。

还有更多...

数据集附带有数据字典,定义了 34 列的标题。可以通过以下链接从同一网站访问此数据字典:

data.sfgov.org/api/views/nuek-vuh3/files/ddb7f3a9-0160-4f07-bb1e-2af744909294?download=true&filename=FIR-0002_DataDictionary_fire-calls-for-service.xlsx

另请参阅

旧金山政府网站允许在线可视化数据,可用于进行一些快速数据概要分析。可以通过选择可视化下拉菜单在网站上访问可视化应用程序,如下截图所示:

识别逻辑回归模型的目标变量

逻辑回归模型作为分类算法运行,旨在预测二进制结果。在本节中,我们将指定数据集中用于预测运营商呼入电话是否与火灾或非火灾事件相关的最佳列。

准备就绪

在本节中,我们将可视化许多数据点,这将需要以下操作:

  1. 通过在命令行中执行pip install matplotlib来确保安装了matplotlib

  2. 运行import matplotlib.pyplot as plt,并确保通过运行%matplotlib inline在单元格中查看图形。

此外,将对pyspark.sql中的函数进行一些操作,需要importing functions as F

如何做...

本节将介绍如何可视化来自旧金山消防局的数据。

  1. 执行以下脚本以对Call Type Group列中唯一值进行快速识别:
df.select('Call Type Group').distinct().show()
  1. 有五个主要类别:

  2. 警报

  3. 潜在危及生命

  4. 非危及生命

  5. null

  6. 不幸的是,其中一个类别是null值。有必要获取每个唯一值的行计数,以确定数据集中有多少null值。执行以下脚本以生成Call Type Group列的每个唯一值的行计数:

df.groupBy('Call Type Group').count().show()
  1. 不幸的是,有超过 280 万行数据没有与之关联的呼叫类型组。这超过了 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()
  1. 可能需要选择另一个指标来确定目标变量。相反,我们可以对Call Type进行概要分析,以识别与火灾相关的呼叫与所有其他呼叫。执行以下脚本以对Call Type进行概要分析:
df.groupBy('Call Type').count().orderBy('count', ascending=False).show(100)
  1. Call Type Group一样,似乎没有任何null值。Call Type有 32 个唯一类别;因此,它将被用作火灾事件的目标变量。执行以下脚本以标记包含FireCall Type列:
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()
  1. 执行以下脚本以检索Fire Indicator的不同计数:
fireIndicator.groupBy('Fire Indicator').count().show()
  1. 执行以下脚本以将Fire Indicator列添加到原始数据框df中:
df = df.withColumn("fireIndicator",\ 
F.when(df["Call Type"].like("%Fire%"),1).otherwise(0))
  1. 最后,将fireIndicator列添加到数据框df中,并通过执行以下脚本进行确认:
df.printSchema()

它是如何工作的...

建立成功的逻辑回归模型的关键步骤之一是建立一个二元目标变量,该变量将用作预测结果。本节将介绍选择目标变量背后的逻辑:

  1. 通过识别Call Type Group的唯一列值来执行潜在目标列的数据概要分析。我们可以查看Call Type Group列的唯一值,如下截图所示:

  1. 目标是确定Call Type Group列中是否存在缺失值,以及如何处理这些缺失值。有时,可以直接删除列中的缺失值,而其他时候可以对其进行处理以填充值。

  2. 以下截图显示了存在多少空值:

  1. 此外,我们还可以绘制存在多少null值,以更好地直观感受值的丰富程度,如下截图所示:

  1. 由于Call Type Group中有超过 280 万行缺失,如df.groupBy脚本和条形图所示,删除所有这些值是没有意义的,因为这超过了数据集的总行数的 60%。因此,需要选择另一列作为目标指示器。

  2. 在对Call Type列进行数据概要分析时,我们发现 32 个可能值中没有空行。这使得Call Type成为逻辑回归模型的更好目标变量候选项。以下是Call Type列的数据概要分析截图:

  1. 由于逻辑回归在有二元结果时效果最佳,因此使用withColumn()操作符在df数据框中创建了一个新列,以捕获与火灾相关事件或非火灾相关事件相关的指示器(0 或 1)。新列名为fireIndicator,如下截图所示:

  1. 我们可以通过执行groupBy().count()来确定火警呼叫与其他呼叫的普遍程度,如下截图所示:

  1. 最佳实践是通过执行新修改的数据框的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 TypefireIndicator两列如下所示:

另请参阅

要了解更多关于 Spark 中可用的pyspark.sql模块的信息,请访问以下网站:

spark.apache.org/docs/2.2.0/api/python/pyspark.sql.html

为逻辑回归模型准备特征变量

在上一节中,我们确定了将用作逻辑回归模型预测结果的目标变量。本节将重点关注确定所有最有助于模型确定目标的特征。这被称为特征选择

准备工作

本节将需要从pyspark.ml.feature中导入StringIndexer。为了确保正确的特征选择,我们需要将字符串列映射到索引列。这将有助于为分类变量生成不同的数值,从而为机器学习模型提供独立变量的计算便利,用于预测目标结果。

如何操作...

本节将逐步介绍为我们的模型准备特征变量的步骤。

  1. 执行以下脚本来更新数据框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)
  1. 下一步是识别数据框中的任何空值并在存在时删除它们。执行以下脚本来识别具有任何空值的行数:
print('Total Rows')
df.count()
print('Rows without Null values')
df.dropna().count()
print('Row with Null Values')
df.count()-df.dropna().count()
  1. 有 16,551 行具有缺失值。执行以下脚本来更新数据框以删除所有具有空值的行:
df = df.dropna()
  1. 执行以下脚本来检索fireIndicator的更新目标计数:
df.groupBy('fireIndicator').count().orderBy('count', ascending = False).show()
  1. pyspark.ml.feature中导入StringIndexer类,为特征分配数值,如下脚本所示:
from pyspark.ml.feature import StringIndexer
  1. 使用以下脚本为模型创建所有特征变量的 Python 列表:
column_names = df.columns[1:]
  1. 执行以下脚本来指定输出列格式outputcol,它将从输入列inputcol的特征列表中进行stringIndexed
categoricalColumns = column_names
indexers = []
for categoricalCol in categoricalColumns:
    stringIndexer = StringIndexer(inputCol=categoricalCol, outputCol=categoricalCol+"_Index")
    indexers += [stringIndexer]
  1. 执行以下脚本创建一个model,用于fit输入列并为现有数据框df生成新定义的输出列:
models = []
for model in indexers:
    indexer_model = model.fit(df)
    models+=[indexer_model]

for i in models:
    df = i.transform(df)
  1. 执行以下脚本来定义数据框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')

工作原理...

本节将解释为我们的模型准备特征变量的步骤背后的逻辑。

  1. 只选择数据框中真正与火灾指示无关的指标,以贡献于预测结果的逻辑回归模型。执行此操作的原因是为了消除数据集中可能已经显示预测结果的任何潜在偏见。这最小化了人为干预最终结果。更新后的数据框的输出可以在下面的截图中看到:

请注意,列邻里-分析边界在我们提取的数据中原本拼写错误。出于连续性目的,我们将继续使用拼写错误。但是,可以使用 Spark 中的withColumnRenamed()函数来重命名列名。

  1. 最终选择的列如下所示:
  • 火灾指示

  • 事故邮政编码

  • 大队

  • 站点区域

  • 警报数量

  • 呼叫调度中的单位序列

  • 邻里-分析边界

  • 消防预防区

  • 监管区

  1. 选择这些列是为了避免我们建模中的数据泄漏。数据泄漏在建模中很常见,可能导致无效的预测模型,因为它可能包含直接由我们试图预测的结果产生的特征。理想情况下,我们希望包含真正与结果无关的特征。有几列似乎是有泄漏的,因此从我们的数据框和模型中删除了这些列。

  2. 识别并删除所有具有缺失或空值的行,以便在不夸大或低估关键特征的情况下获得模型的最佳性能。可以计算并显示具有缺失值的行的清单,如下脚本所示,数量为 16,551:

  1. 我们可以看一下与火灾相关的呼叫频率与非火灾相关的呼叫频率,如下截图所示:

  1. 导入StringIndexer以帮助将几个分类或字符串特征转换为数字值,以便在逻辑回归模型中进行计算。特征的输入需要以向量或数组格式,这对于数字值是理想的。可以在以下屏幕截图中看到将在模型中使用的所有特征的列表:

  1. 为每个分类变量构建了一个索引器,指定了模型中将使用的输入(inputCol)和输出(outputCol)列。数据框中的每一列都会被调整或转换,以重新构建一个具有更新索引的新输出,范围从 0 到该特定列的唯一计数的最大值。新列在末尾附加了_Index。在创建更新的列的同时,原始列仍然可在数据框中使用,如下屏幕截图所示:

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

  1. 然后,数据框被修剪以仅包含数字值,并删除了转换的原始分类变量。非数字值从建模的角度来看不再有意义,并且从数据框中删除。

  2. 打印出新列以确认数据框的每个值类型都是双精度或整数,如下屏幕截图所示:

还有更多...

最终查看新修改的数据框将只显示数字值,如下屏幕截图所示:

另请参阅

要了解更多关于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

如何做...

本节将介绍应用我们的模型和评估结果步骤。

  1. 执行以下脚本,将数据框中的所有特征变量汇总到名为features的列表中:
features = df.columns[1:]
  1. 执行以下操作以导入VectorAssembler并配置将被分配给特征向量的字段,通过分配inputColsoutputCol
from pyspark.ml.feature import VectorAssembler
feature_vectors = VectorAssembler(
    inputCols = features,
    outputCol = "features")
  1. 执行以下脚本,将VectorAssembler应用于数据框,并使用transform函数:
df = feature_vectors.transform(df)
  1. 修改数据框,删除除fireIndicatorfeatures之外的所有列,如下脚本所示:
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')
  1. 修改数据框,将fireIndicator重命名为label,如下脚本所示:
df = df.withColumnRenamed('fireIndicator', 'label')
  1. 将整个数据框df分割为 75:25 的训练和测试集,随机种子设置为12345,如下脚本所示:
(trainDF, testDF) = df.randomSplit([0.75, 0.25], seed = 12345)
  1. pyspark.ml.classification中导入LogisticRegression库,并配置以将数据框中的labelfeatures合并,然后在训练数据集trainDF上拟合,如下脚本所示:
from pyspark.ml.classification import LogisticRegression
logreg = LogisticRegression(labelCol="label", featuresCol="features", maxIter=10)
LogisticRegressionModel = logreg.fit(trainDF)
  1. 转换测试数据框testDF以应用逻辑回归模型。具有预测得分的新数据框称为df_predicted,如下脚本所示:
df_predicted = LogisticRegressionModel.transform(testDF)

它是如何工作的...

本节将解释应用我们的模型和评估结果步骤背后的逻辑。

  1. 当所有特征被合并为单个向量进行训练时,分类模型的效果最佳。因此,我们通过将所有特征收集到一个名为features的列表中开始向量化过程。由于我们的标签是数据框的第一列,我们将其排除,并将其后的每一列作为特征列或特征变量引入。

  2. 向量化过程继续,将features列表中的所有变量转换为名为features的单个向量输出到列中。此过程需要从pyspark.ml.feature导入VectorAssembler

  3. 应用VectorAssembler转换数据框,创建一个名为features的新添加列,如下截图所示:

  1. 在这一点上,我们在模型中需要使用的唯一列是标签列fireIndicatorfeatures列。数据框中的所有其他列都可以删除,因为它们在建模过程中将不再需要。

  2. 此外,为了帮助逻辑回归模型,我们将名为fireIndicator的列更改为label。可以在以下截图中看到df.show()脚本的输出,其中包含新命名的列:

  1. 为了最小化过拟合模型,数据框将被拆分为测试和训练数据集,以在训练数据集trainDF上拟合模型,并在测试数据集testDF上进行测试。设置随机种子为12345,以确保每次执行单元格时随机性保持一致。可以在以下截图中看到数据拆分的行数:

  1. 然后,从pyspark.ml.classification导入逻辑回归模型LogisticRegression,并配置以从与特征和标签相关的数据框中输入适当的列名。此外,逻辑回归模型分配给一个名为logreg的变量,然后拟合以训练我们的数据集trainDF

  2. 基于测试数据框testDF的转换,创建一个名为predicted_df的新数据框,一旦逻辑回归模型对其进行评分。该模型为predicted_df创建了三个额外的列,基于评分。这三个额外的列是rawPredictionprobabilityprediction,如下截图所示:

  1. 最后,可以对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

如何做...

本节将逐步介绍评估模型性能的步骤。

  1. 使用.crosstab()函数创建混淆矩阵,如下脚本所示:
df_predicted.crosstab('label', 'prediction').show()
  1. sklearn导入metrics以帮助使用以下脚本衡量准确性:
from sklearn import metrics
  1. 为了衡量准确性,从数据框中创建actualpredicted列的两个变量,使用以下脚本:
actual = df_predicted.select('label').toPandas()
predicted = df_predicted.select('prediction').toPandas()
  1. 使用以下脚本计算准确度预测分数:
metrics.accuracy_score(actual, predicted)

它是如何工作的...

本节解释了如何评估模型性能。

  1. 为了计算我们模型的准确度,重要的是能够确定我们的预测有多准确。通常,最好使用混淆矩阵交叉表来可视化,显示正确和错误的预测分数。我们使用df_predicted数据框的crosstab()函数创建一个混淆矩阵,它显示我们对标签为 0 的有 964,980 个真负预测,对标签为 1 的有 48,034 个真正预测,如下截图所示:

  1. 我们从本节前面知道testDF数据框中共有 1,145,589 行;因此,我们可以使用以下公式计算模型的准确度:(TP + TN) / 总数。准确度为 88.4%。

  2. 需要注意的是,并非所有的假分数都是相等的。例如,将一个呼叫分类为与火灾无关,最终却与火灾有关,比相反的情况对火灾安全的影响更大。这被称为假阴性。有一个考虑假阴性FN)的指标,称为召回率

  3. 虽然我们可以手动计算准确度,如最后一步所示,但最好是自动计算准确度。这可以通过导入sklearn.metrics来轻松实现,这是一个常用于评分和模型评估的模块。

  4. sklearn.metrics接受两个参数,我们拥有标签的实际结果和从逻辑回归模型中得出的预测值。因此,创建了两个变量actualpredicted,并使用accuracy_score()函数计算准确度分数,如下截图所示:

  1. 准确度分数与我们手动计算的相同,为 88.4%。

还有更多...

现在我们知道我们的模型能够准确预测呼叫是否与火灾相关的比率为 88.4%。起初,这可能听起来是一个强有力的预测;然而,将其与一个基准分数进行比较总是很重要,其中每个呼叫都被预测为非火灾呼叫。预测的数据框df_predicted中标签10的分布如下截图所示:

我们可以对同一数据框运行一些统计,使用df_predicted.describe('label').show()脚本得到值为1的标签出现的平均值。该脚本的输出如下截图所示:

基础模型的预测值为1的比率为 14.94%,换句话说,它对值为 0 的预测率为100 - 14.94%,即 85.06%。因此,由于 85.06%小于模型的预测率 88.4%,这个模型相比于盲目猜测呼叫是否与火灾相关提供了改进。

另请参阅

要了解更多关于准确度与精确度的信息,请访问以下网站:

www.mathsisfun.com/accuracy-precision.html

第六章:在生成网络中使用 LSTMs

阅读完本章后,您将能够完成以下任务:

  • 下载将用作输入文本的小说/书籍

  • 准备和清理数据

  • 对句子进行标记化

  • 训练并保存 LSTM 模型

  • 使用模型生成类似的文本

介绍

由于循环神经网络RNNs)在反向传播时存在一些缺点,长短期记忆单元LSTMs)和门控循环单元GRUs)在学习顺序输入数据时近来变得越来越受欢迎,因为它们更适合解决梯度消失和梯度爆炸的问题。

下载将用作输入文本的小说/书籍

在本示例中,我们将介绍下载小说/书籍所需的步骤,这些将作为本示例的输入文本进行执行。

准备工作

  • 将输入数据以.txt文件的形式放在工作目录中。

  • 输入可以是任何类型的文本,如歌词、小说、杂志文章和源代码。

  • 大多数经典文本不再受版权保护,可以免费下载并用于实验。获取免费书籍的最佳途径是 Project Gutenberg

  • 在本章中,我们将使用 Rudyard Kipling 的《丛林之书》作为输入来训练我们的模型,并生成统计上类似的文本作为输出。下面的截图显示了如何以.txt格式下载必要的文件:

  • 访问网站并搜索所需的书籍后,点击“Plain Text UTF-8”并下载。UTF-8 基本上指定了编码的类型。可以通过点击链接将文本复制粘贴或直接保存到工作目录中。

操作步骤...

在开始之前,先看一下数据并进行分析总是有帮助的。查看数据后,我们可以看到有很多标点符号、空格、引号以及大写和小写字母。在对其进行任何分析或将其馈送到 LSTM 网络之前,我们需要先准备好数据。我们需要一些能够更轻松处理数据的库:

  1. 通过以下命令导入必要的库:
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
  1. 前面命令的输出如下截屏所示:

  1. 始终要仔细检查当前工作目录,并选择所需的文件夹作为工作目录。在我们的案例中,.txt文件名为junglebook.txt,保存在名为Chapter 8的文件夹中。因此,我们将选择该文件夹作为整个章节的工作目录。可以按照下面的截图所示进行操作:

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

上述代码的输出如下截屏所示:

下面的截图是前面输出的延续:

  1. 如前面的截图所示,打印了.txt文件中的前 2000 个字符。在执行任何预处理之前,始终先分析数据是个好主意。这将更好地指导我们如何进行预处理步骤。

工作原理...

  1. array函数将用于处理数组形式的数据。numpy库提供了这个函数。

  2. 由于我们的数据只是文本数据,我们将需要字符串库来处理所有输入数据作为字符串,然后将单词编码为整数,以便进行馈送。

  3. tokenizer函数将用于将所有句子拆分为标记,其中每个标记代表一个单词。

  4. pickle 库将被需要,以便使用dump函数将字典保存到 pickle 文件中。

  5. keras库中的to_categorical函数将类向量(整数)转换为二进制类矩阵,例如,用于categorical_crossentropy,我们以后将需要将标记映射到唯一整数,反之亦然。

  6. 本章中所需的其他 Keras 层包括 LSTM 层、密集层、dropout 层和嵌入层。模型将被顺序定义,因此我们需要keras库中的顺序模型。

还有更多...

  • 您还可以使用相同的模型处理不同类型的文本,例如网站上的客户评论、推文、结构化文本(如源代码、数学理论等)等。

  • 本章的目的是了解 LSTM 如何学习长期依赖关系,以及与循环神经网络相比,它们在处理序列数据时表现更好的方式。

  • 另一个好主意是将* Pokémon 名称输入模型,并尝试生成自己的 Pokémon *名称。

另请参阅

有关使用的不同库的更多信息可以在以下链接找到:

准备和清理数据

本章的这一部分将讨论在将其作为输入馈送到模型之前涉及的各种数据准备和文本预处理步骤。我们准备数据的具体方式取决于我们打算对其进行建模的方式,这又取决于我们打算如何使用它。

准备工作

语言模型将基于统计数据,并预测给定文本输入序列的每个单词的概率。预测的单词将被馈送到模型中,以便生成下一个单词。

一个关键决定是输入序列应该有多长。它们需要足够长,以使模型能够学习单词的上下文以进行预测。此输入长度还将定义用于生成新序列的种子文本的长度,当我们使用模型时。

为了简单起见,我们将任意选择长度为 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
  1. 先前定义的函数基本上会将加载的文档/文件作为其参数,并返回一个干净的标记数组,如下面的屏幕截图所示:

  1. 接下来,打印出一些标记和统计数据,以更好地了解clean_document函数的作用。通过以下命令完成此步骤:
tokens = clean_document(doc)
print(tokens[:200])
print('Total Tokens: %d' % len(tokens))
print('Total Unique Tokens: %d' % len(set(tokens)))
  1. 上述一系列命令的输出打印了前两百个标记,如下面的屏幕截图所示:

  1. 接下来,使用以下命令将所有这些标记组织成序列,每个序列包含 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))

可以通过打印输出文档形成的序列的总数来查看,如下面的屏幕截图所示:

  1. 通过以下命令定义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)
  1. 该过程如下屏幕截图所示:

  1. 接下来,加载保存的文档,其中包含所有保存的标记和序列,到内存中使用定义如下的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')

工作原理...

  1. clean_document函数删除所有空格、标点、大写文本和引号,并将整个文档分割成标记,其中每个标记都是一个单词。

  2. 通过打印文档中的标记总数和唯一标记总数,我们会注意到clean_document函数生成了 51,473 个标记,其中 5,027 个标记(或单词)是唯一的。

  3. 然后,save_document函数保存所有这些标记,以及生成我们每个 50 个单词的序列所需的唯一标记。请注意,通过循环遍历所有生成的标记,我们能够生成一个包含 51,422 个序列的长列表。这些序列将用作训练语言模型的输入。

  4. 在对所有 51,422 个序列进行模型训练之前,将标记以及序列保存到文件中始终是一个良好的做法。一旦保存,可以使用定义的load_document函数将文件加载回内存。

  5. 这些序列组织为 50 个输入标记和一个输出标记(这意味着每个序列有 51 个标记)。为了预测每个输出标记,将使用前 50 个标记作为模型的输入。我们可以通过迭代从第 51 个标记开始的标记列表,并将前 50 个标记作为一个序列,然后重复此过程直到所有标记列表的末尾来实现这一点。

另请参阅

访问以下链接,以更好地了解使用各种函数进行数据准备:

对句子进行标记

在定义和输入数据到 LSTM 网络之前,重要的是将数据转换为神经网络可以理解的形式。计算机理解的一切都是二进制代码(0 和 1),因此,文本或字符串格式的数据需要转换为独热编码变量。

准备工作

要了解独热编码的工作原理,请访问以下链接:

如何做...

经过上一节的学习,您应该能够清理整个语料库并拆分句子。接下来涉及独热编码和标记化句子的步骤可以按以下方式完成:

  1. 一旦标记和序列被保存到文件并加载到内存中,它们必须被编码为整数,因为模型中的词嵌入层期望输入序列由整数而不是字符串组成。

  2. 这是通过将词汇表中的每个单词映射到唯一的整数并对输入序列进行编码来完成的。稍后,在进行预测时,可以将预测转换(或映射)回数字,以查找它们在相同映射中关联的单词,并从整数到单词的反向映射。

  3. 为了执行这种编码,利用 Keras API 中的 Tokenizer 类。在编码之前,必须对整个数据集进行训练,以便找到所有唯一的标记,并为每个标记分配一个唯一的整数。要这样做的命令如下:

tokenizer = Tokenizer()
tokenizer.fit_on_texts(lines)
sequences = tokenizer.texts_to_sequences(lines)
  1. 在后面定义嵌入层之前,还需要计算词汇表的大小。这是通过计算映射字典的大小来确定的。

  2. 因此,在向嵌入层指定词汇表大小时,将其指定为实际词汇表大小加 1。因此,词汇表大小定义如下:

vocab_size = len(tokenizer.word_index) + 1
print('Vocabulary size : %d' % vocab_size)
  1. 现在,一旦输入序列已经被编码,它们需要被分成输入和输出元素,这可以通过数组切片来完成。

  2. 分离后,对输出单词进行独热编码。这意味着将其从整数转换为 n 维向量,其中每个词汇表中的单词都有一个 0 值,用 1 表示单词的整数值的索引处的特定单词。Keras 提供了to_categorical()函数,可用于为每个输入-输出序列对独热编码输出单词。

  3. 最后,指定嵌入层输入序列的长度。我们知道有 50 个单词,因为模型是通过将序列长度指定为 50 来设计的,但指定序列长度的一个好的通用方法是使用输入数据形状的第二维(列数)。

  4. 可以通过发出以下命令来完成:

sequences = array(sequences)
Input, Output = sequences[:,:-1], sequences[:,-1]
Output = to_categorical(Output, num_classes=vocab_size)
sequence_length = Input.shape[1]

工作原理...

本节将描述在执行上一节中的命令时必须看到的输出:

  1. 在对句子进行标记化和计算词汇表长度的命令运行后,您应该看到如下屏幕截图所示的输出:

  1. 单词被分配值,从 1 开始,直到单词的总数(例如,在这种情况下为 5,027)。嵌入层需要为词汇表中从索引 1 到最大索引的每个单词分配一个向量表示。词汇表末尾的单词的索引将是 5,027;这意味着数组的长度必须是 5,027 + 1。

  2. 数组切片和将句子分隔成每个序列 50 个单词的序列后,输出应该如下截图所示:

  1. 使用to_categorical()函数,使模型学习预测下一个单词的概率分布。

还有更多...

有关在 Python 中重新整形数组的更多信息,请访问以下链接:

训练和保存 LSTM 模型

现在可以从准备好的数据中训练统计语言模型。

将要训练的模型是神经语言模型。它具有一些独特的特点:

  • 它使用分布式表示来表示单词,使得具有相似含义的不同单词具有相似的表示

  • 它在学习模型的同时学习表示

  • 它学会使用前 50 个单词的上下文来预测下一个单词的概率

具体来说,您将使用嵌入层来学习单词的表示,以及长短期记忆LSTM)递归神经网络来学习根据上下文预测单词。

准备工作

如前所述,学习的嵌入需要知道词汇表的大小和输入序列的长度。它还有一个参数,用于指定将用于表示每个单词的维度的数量。这就是嵌入向量空间的大小。

常见值为 50、100 和 300。我们将在这里使用 100,但考虑测试更小或更大的值,并评估这些值的指标。

网络将由以下组成:

  • 两个具有 200 个记忆单元的 LSTM 隐藏层。更多的记忆单元和更深的网络可能会取得更好的结果。

  • 一个 dropout 层,dropout 率为 0.3 或 30%,这将帮助网络减少对每个神经元/单元的依赖,并减少过拟合数据。

  • 一个具有 200 个神经元的全连接层连接到 LSTM 隐藏层,以解释从序列中提取的特征。

  • 输出层预测下一个单词,作为词汇表大小的单个向量,其中每个单词在词汇表中都有一个概率。

  • 在第二个密集或全连接层中使用 softmax 分类器,以确保输出具有归一化概率的特性(例如在 0 和 1 之间)。

如何做...

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

  1. 打印模型摘要,以确保模型按预期构建。

  2. 编译模型,指定需要拟合模型的分类交叉熵损失。将 epochs 数设置为 75,并使用批量大小为 250 的小批量训练模型。使用以下命令完成:

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

 model.fit(Input, Output, batch_size=250, epochs=75)
  1. 上述命令的输出在以下截图中进行说明:

  1. 模型编译完成后,使用以下命令保存:
model.save('junglebook_trained.h5')

dump(tokenizer, open('tokenizer.pkl', 'wb'))

它是如何工作的...

  1. 模型是使用 Keras 框架中的Sequential()函数构建的。模型中的第一层是一个嵌入层,它以词汇量、向量维度和输入序列长度作为参数。

  2. 接下来的两层是每个具有 200 个内存单元的 LSTM 层。可以尝试使用更多内存单元和更深的网络来检查是否可以提高准确性。

  3. 接下来的一层是一个丢弃层,丢弃概率为 30%,这意味着在训练过程中某个记忆单元不被使用的概率为 30%。这可以防止数据过拟合。同样,可以调整和调优丢弃概率。

  4. 最后两层是两个全连接层。第一个具有relu激活函数,第二个具有 softmax 分类器。打印模型摘要以检查模型是否按要求构建。

  5. 请注意,在这种情况下,可训练参数的总数为 2,115,228。模型摘要还显示了模型中每个层将被训练的参数数量。

  6. 在我们的案例中,模型是在 75 个时期的小批量中训练的,以最小化训练时间。将时期数增加到 100 以上,并在训练时使用更小的批量,可以大大提高模型的准确性,同时减少损失。

  7. 在训练过程中,您将看到性能摘要,包括每个批次更新结束时从训练数据评估的损失和准确性。在我们的案例中,运行了 75 个时期后,我们获得了接近 40%的准确性。

  8. 模型的目标不是以 100%的准确性记住文本,而是捕捉输入文本的属性,如自然语言和句子中存在的长期依赖关系和结构。

  9. 在训练完成后,模型将保存在名为junglebook_trained.h5的工作目录中。

  10. 当模型稍后加载到内存中进行预测时,我们还需要单词到整数的映射。这在Tokenizer对象中存在,并且也使用Pickle库中的dump()函数保存。

还有更多...

Jason Brownlee 在 Machine Learning Mastery 的博客上有很多关于开发、训练和调整自然语言处理机器学习模型的有用信息。可以在以下链接找到:

machinelearningmastery.com/deep-learning-for-nlp/

machinelearningmastery.com/lstms-with-python/

machinelearningmastery.com/blog/

另请参阅

有关本节中使用的不同 keras 层和其他函数的更多信息可以在以下链接找到:

使用模型生成类似的文本

现在您有了一个经过训练的语言模型,可以使用它。在这种情况下,您可以使用它来生成具有与源文本相同统计特性的新文本序列。至少对于这个例子来说,这并不实际,但它给出了语言模型学到了什么的一个具体例子。

准备工作

  1. 首先重新加载训练序列。您可以使用我们最初开发的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')

上述代码的输出如下截图所示:

  1. 请注意,输入文件名现在是'junglebook_sequences.txt',这将把保存的训练序列加载到内存中。我们需要文本,以便我们可以选择一个源序列作为模型的输入,以生成新的文本序列。

  2. 模型将需要 50 个单词作为输入。

随后,需要指定输入的预期长度。这可以通过计算加载的数据的一行的长度并减去 1 来从输入序列中确定,因为预期的输出单词也在同一行上,如下所示:

sequence_length = len(lines[0].split()) - 1

  1. 接下来,通过执行以下命令将训练和保存的模型加载到内存中:
 from keras.models import load_model
 model = load_model('junglebook.h5')
  1. 生成文本的第一步是准备种子输入。为此目的,从输入文本中随机选择一行文本。一旦选择,打印它以便您对使用的内容有一些了解。操作如下:
from random import randint
seed_text = lines[randint(0,len(lines))]
print(seed_text + '\n')

如何做...

  1. 现在,您可以逐个生成新单词。首先,使用训练模型时使用的相同标记器将种子文本编码为整数,操作如下:

encoded = tokenizer.texts_to_sequences([seed_text])[0]

  1. 模型可以通过调用model.predict_classes()直接预测下一个单词,这将返回具有最高概率的单词的索引:
 prediction = model.predict_classes(encoded, verbose=0)

  1. 查找标记器映射中的索引以获取相关联的单词,如下所示:
 out_word = ''
 for word, index in tokenizer.word_index.items():
         if index == prediction:
                 out_word = word
                 break
  1. 将这个单词附加到种子文本中并重复这个过程。重要的是,输入序列将变得太长。在将输入序列编码为整数后,我们可以将其截断为所需的长度。Keras 提供了pad_sequences()函数,我们可以使用它来执行这种截断,如下所示:
 encoded = pad_sequences([encoded], maxlen=seq_length, truncating='pre')

  1. 将所有这些封装到一个名为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

工作原理...

现在,我们准备生成一系列新单词,假设我们有一些种子文本:

  1. 首先使用以下命令将模型重新加载到内存中:
 model = load_model('junglebook.h5')
  1. 接下来,通过输入以下命令加载标记器:
 tokenizer = load(open('tokenizer.pkl', 'rb'))
  1. 通过使用以下命令随机选择一个种子文本:
 seed_text = lines[randint(0,len(lines))]
 print(seed_text + '\n')
  1. 最后,通过使用以下命令生成一个新序列:
 generated = generate_sequence(model, tokenizer, sequence_length,             seed_text, 50)
 print(generated)
  1. 在打印生成的序列时,您将看到类似于以下屏幕截图的输出:

  1. 模型首先打印随机种子文本的 50 个单词,然后打印生成文本的 50 个单词。在这种情况下,随机种子文本如下:

篮子里装满了干草,放入蚱蜢,或者捉两只螳螂让它们打架,或者串一串红色和黑色的丛林果仁做成项链,或者看蜥蜴在岩石上晒太阳,或者蛇在泥坑附近捕捉青蛙,然后它们唱着长长的歌

在这种情况下,模型生成的 50 个单词如下:

在评论结束时有奇怪的本地颤音,他看到的鬣狗,他们感到被拉到他周围的噪音,为了峡谷末端的画面,嗅着被咬的和最好的公牛在黎明时是本地人

  1. 请注意模型输出了一系列随机单词,这些单词是根据它从输入文本中学到的内容生成的。您还会注意到,模型在模仿输入文本并生成自己的故事方面做得相当不错。尽管文本没有太多意义,但它为我们提供了宝贵的见解,即模型如何学习将统计上相似的单词放在一起。

还有更多...

  • 更改设置的随机种子后,网络生成的输出也会发生变化。您可能无法获得与前面示例完全相同的输出文本,但它将与用于训练模型的输入非常相似。

  • 以下是通过多次运行生成文本片段获得的不同结果的一些屏幕截图:

  • 模型甚至生成了自己版本的项目古腾堡许可证,如下屏幕截图所示:

  • 模型的准确性可以通过将时代数量从大约 100 增加到 200 来提高到约 60%。另一种增加学习的方法是通过以大约 50 和 100 的小批量训练模型。尝试玩弄不同的超参数和激活函数,以查看以最佳方式影响结果的方法。

  • 还可以通过在定义模型时包含更多的 LSTM 和丢失层来使模型更加密集。但是,请注意,如果模型更复杂并且运行的时代更长,它只会增加训练时间。

  • 经过大量实验,发现理想的批处理大小在 50 到 100 之间,并且确定了训练模型的理想时代数量在 100 到 200 之间。

  • 执行前述任务的确切方法并不存在。您还可以尝试使用不同的文本输入到模型,例如推文,客户评论或 HTML 代码。

  • 还可以执行一些其他任务,包括使用简化的词汇表(例如删除所有停用词)以进一步增强字典中的唯一单词;调整嵌入层的大小和隐藏层中的记忆单元数量;并扩展模型以使用预训练模型,例如 Google 的 Word2Vec(预训练词模型),以查看是否会产生更好的模型。

另请参阅

有关本章最后一节中使用的各种函数和库的更多信息,请访问以下链接:

第七章:使用 TF-IDF 进行自然语言处理

在本章中,将涵盖以下内容:

  • 下载治疗机器人会话文本数据集

  • 分析治疗机器人会话数据集

  • 可视化数据集中的词频

  • 计算文本的情感分析

  • 从文本中删除停用词

  • 训练 TF-IDF 模型

  • 评估 TF-IDF 模型性能

  • 将模型性能与基准分数进行比较

介绍

自然语言处理(NLP)最近成为新闻的焦点,如果你问五个不同的人,你会得到十个不同的定义。最近,NLP 已被用于帮助识别互联网上试图传播假新闻或更糟的是欺凌行为的机器人或喷子。事实上,最近在西班牙发生了一起案件,一所学校的学生通过社交媒体账户遭到网络欺凌,这对学生的健康产生了严重影响,老师们开始介入。学校联系了研究人员,他们能够帮助识别使用 TF-IDF 等 NLP 方法的潜在喷子。最终,潜在的学生名单被提交给学校,当面对时,实际嫌疑人承认了自己的行为。这个故事发表在一篇名为《Twitter 社交网络中喷子档案检测的监督机器学习:网络欺凌的真实案例应用》的论文中,作者是 Patxi Galan-Garcıa、Jose Gaviria de la Puerta、Carlos Laorden Gomez、Igor Santos 和 Pablo Garcıa Bringas。

本文重点介绍了利用多种不同方法分析文本和开发类似人类语言处理的能力。正是这种方法将自然语言处理(NLP)融入到机器学习、深度学习和人工智能中。让机器能够摄取文本数据并可能从同样的文本数据中做出决策是自然语言处理的核心。有许多用于 NLP 的算法,例如以下内容:

  • TF-IDF

  • Word2Vec

  • N-gram

  • 潜在狄利克雷分配(LDA)

  • 长短期记忆(LSTM)

本章将专注于一个包含个人与在线治疗网站聊天机器人之间对话的数据集。聊天机器人的目的是识别需要立即引起个人关注而不是继续与聊天机器人讨论的对话。最终,我们将专注于使用 TF-IDF 算法对数据集进行文本分析,以确定聊天对话是否需要被升级到个人的分类。TF-IDF 代表词项频率-逆文档频率。这是一种常用的算法技术,用于识别文档中单词的重要性。此外,TF-IDF 在处理文档中的高词频时易于计算,并且能够衡量单词的独特性。在处理聊天机器人数据时,这非常有用。主要目标是快速识别一个唯一的单词,触发升级到个人以提供即时支持。

下载治疗机器人会话文本数据集

本节将重点介绍下载和设置本章中用于 NLP 的数据集。

准备工作

本章将使用基于治疗机器人与在线治疗网站访客之间的互动的数据集。它包含 100 个互动,每个互动都被标记为“升级”或“不升级”。如果讨论需要更严肃的对话,机器人将会将讨论标记为“升级”给个人。否则,机器人将继续与用户讨论。

它是如何工作的...

本节将介绍下载聊天机器人数据的步骤。

  1. 从以下 GitHub 存储库访问数据集:github.com/asherif844/ApacheSparkDeepLearningCookbook/tree/master/CH07/data

  2. 一旦您到达存储库,右键单击以下截图中看到的文件:

  1. 下载 TherapyBotSession.csv 并保存到与 Jupyter 笔记本 SparkSession 相同的本地目录中。

  2. 通过以下脚本在 Jupyter 笔记本中访问数据集,构建名为 sparkSparkSession,并将数据集分配给 Spark 中的数据框 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 笔记本。

  1. 数据集的内容可以通过点击存储库中的 TherapyBotSession.csv 查看,如下截图所示:

  1. 一旦数据集被下载,它可以被上传并转换为一个名为 df 的数据框。可以通过执行 df.show() 来查看数据框,如下截图所示:

  1. 有 3 个主要字段对我们来说特别感兴趣:

  2. id:网站访问者和聊天机器人之间每笔交易的唯一标识。

  3. label:由于这是一种监督建模方法,我们知道我们要预测的结果,每个交易都被分类为 escalatedo_not_escalate。在建模过程中,将使用该字段来训练文本以识别属于这两种情况之一的单词。

  4. chat:最后我们有来自网站访问者的 chat 文本,我们的模型将对其进行分类。

还有更多...

数据框 df 还有一些额外的列 _c3_c4_c5_c6,这些列将不会在模型中使用,因此可以使用以下脚本从数据集中排除。

df = df.select('id', 'label', 'chat')
df.show()

脚本的输出可以在以下截图中看到:

分析治疗机器人会话数据

在应用模型之前,始终先分析任何数据集是很重要的

准备工作

这一部分将需要从 pyspark.sql 导入 functions 来在我们的数据框上执行。

import pyspark.sql.functions as F

如何做...

以下部分将介绍对文本数据进行分析的步骤。

  1. 执行以下脚本来对 label 列进行分组并生成计数分布:
df.groupBy("label") \
   .count() \
   .orderBy("count", ascending = False) \
   .show()
  1. 使用以下脚本向数据框 df 添加一个新列 word_count
import pyspark.sql.functions as F
df = df.withColumn('word_count', F.size(F.split(F.col('response_text'),' ')))
  1. 使用以下脚本按 label 聚合平均单词计数 avg_word_count
df.groupBy('label')\
  .agg(F.avg('word_count').alias('avg_word_count'))\
  .orderBy('avg_word_count', ascending = False) \
  .show()

它是如何工作的...

以下部分解释了分析文本数据所获得的反馈。

  1. 收集跨多行的数据并按维度对结果进行分组是很有用的。在这种情况下,维度是 label。使用 df.groupby() 函数来测量按 label 分布的 100 笔在线治疗交易的计数。我们可以看到 do_not_escalateescalate 的分布是 6535,如下截图所示:

  1. 创建一个新列 word_count,用于计算聊天机器人和在线访问者之间的 100 笔交易中每笔交易使用了多少单词。新创建的列 word_count 可以在以下截图中看到:

  1. 由于现在在数据框中添加了 word_count,可以对其进行聚合以计算按 label 的平均单词计数。一旦执行了这个操作,我们可以看到 escalate 对话的平均长度是 do_not_escalate 对话的两倍多,如下截图所示:

可视化数据集中的单词计数

一张图片胜过千言万语,本节将证明这一点。不幸的是,截至版本 2.2,Spark 没有任何内在的绘图能力。为了在数据框中绘制值,我们必须转换为 pandas

准备工作

本节将需要导入matplotlib进行绘图:

import matplotlib.pyplot as plt
%matplotlib inline

如何做...

本节将介绍将 Spark 数据框转换为可以在 Jupyter 笔记本中查看的可视化的步骤。

  1. 使用以下脚本将 Spark 数据框转换为pandas数据框:
df_plot = df.select('id', 'word_count').toPandas()
  1. 使用以下脚本绘制数据框:
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,然后绘制。

  1. 从 Spark 中收集数据框的子集,并使用 Spark 中的toPandas()方法转换为pandas

  2. 然后使用 matplotlib 绘制数据的子集,将 y 值设置为word_count,将 x 值设置为id,如下面的屏幕截图所示:

另请参阅

Python 中除了matplotlib之外还有其他绘图功能,例如bokehplotlyseaborn

要了解有关bokeh的更多信息,请访问以下网站:

bokeh.pydata.org/en/latest/

要了解有关plotly的更多信息,请访问以下网站:

plot.ly/

要了解有关seaborn的更多信息,请访问以下网站:

seaborn.pydata.org/

计算文本的情感分析

情感分析是从单词或一系列单词中推导出语气和感觉的能力。本节将利用 Python 技术从数据集中的 100 个交易中计算情感分析分数。

准备工作

本节将需要在 PySpark 中使用函数和数据类型。此外,我们还将导入TextBlob库进行情感分析。为了在 PySpark 中使用 SQL 和数据类型函数,必须导入以下内容:

from pyspark.sql.types import FloatType 

此外,为了使用TextBlob,必须导入以下库:

from textblob import TextBlob

如何做...

以下部分将介绍将情感分数应用于数据集的步骤。

  1. 使用以下脚本创建情感分数函数sentiment_score
from textblob import TextBlob
def sentiment_score(chat):
    return TextBlob(chat).sentiment.polarity
  1. 使用以下脚本在数据框中的每个对话响应上应用sentiment_score

  2. 创建一个名为sentiment_score_udflambda函数,将sentiment_score映射到 Spark 中的用户定义函数udf,并指定FloatType()的输出类型,如下脚本所示:

from pyspark.sql.types import FloatType
sentiment_score_udf = F.udf(lambda x: sentiment_score(x), FloatType())
  1. 在数据框中的每个chat列上应用函数sentiment_score_udf,如下脚本所示:
df = df.select('id', 'label', 'chat','word_count',
                   sentiment_score_udf('chat').alias('sentiment_score'))
  1. 使用以下脚本计算按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,以将情感分析分数应用于数据框中的每一列。

  1. Textblob是 Python 中的情感分析库。它可以从名为sentiment.polarity的方法中计算情感分数,该方法的得分范围为-1(非常负面)到+1(非常正面),0 为中性。此外,Textblob还可以从 0(非常客观)到 1(非常主观)测量主观性;尽管在本章中我们不会测量主观性。

  2. 将 Python 函数应用于 Spark 数据框有几个步骤:

  3. 导入Textblob并将名为sentiment_score的函数应用于chat列,以生成每个机器人对话的情感极性,并在新列中生成情感分数,也称为sentiment_score

  4. Python 函数不能直接应用于 Spark 数据框,而必须先经过用户定义函数转换udf,然后在 Spark 中应用。

  5. 此外,函数的输出也必须明确说明,无论是整数还是浮点数据类型。在我们的情况下,我们明确说明函数的输出将使用FloatType() from pyspark.sql.types。最后,使用udf情感分数函数内的lambda函数在每行上应用情感。

  6. 通过执行df.show(),可以看到具有新创建字段情感分数的更新后的数据框,如下截屏所示:

  1. 现在,对于聊天对话中的每个响应计算了sentiment_score之后,我们可以为每行指定-1(非常负面的极性)到+1(非常正面的极性)的值范围。就像我们对计数和平均词数所做的那样,我们可以比较升级对话在情感上是否比不升级对话更积极或更消极。我们可以通过label计算平均情感分数avg_sentiment_score,如下截屏所示:

  1. 最初,假设升级对话的极性得分会比不升级更负面是有道理的。实际上,我们发现升级在极性上比不升级稍微更积极;但是,两者都相当中性,因为它们接近 0。

另请参阅

要了解有关TextBlob库的更多信息,请访问以下网站:

textblob.readthedocs.io/en/dev/

从文本中删除停用词

停用词是英语中非常常见的单词,通常会从常见的 NLP 技术中删除,因为它们可能会分散注意力。常见的停用词可能是诸如theand之类的单词。

准备工作

本节需要导入以下库:

from pyspark.ml.feature import StopWordsRemover 
from pyspark.ml import Pipeline

操作步骤...

本节介绍了删除停用词的步骤。

  1. 执行以下脚本,将chat中的每个单词提取为数组中的字符串:
df = df.withColumn('words',F.split(F.col('chat'),' '))
  1. 使用以下脚本将一组常见单词分配给变量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']
  1. 执行以下脚本,从 PySpark 导入StopWordsRemover函数,并配置输入和输出列wordsword without stop
from pyspark.ml.feature import StopWordsRemover 

stopwordsRemovalFeature = StopWordsRemover(inputCol="words", 
                   outputCol="words without stop").setStopWords(stop_words)
  1. 执行以下脚本以导入 Pipeline 并为将应用于数据框的停用词转换过程定义stages
from pyspark.ml import Pipeline

stopWordRemovalPipeline = Pipeline(stages=[stopwordsRemovalFeature])
pipelineFitRemoveStopWords = stopWordRemovalPipeline.fit(df)
  1. 最后,使用以下脚本将停用词移除转换pipelineFitRemoveStopWords应用于数据框df
df = pipelineFitRemoveStopWords.transform(df)

工作原理...

本节解释了如何从文本中删除停用词。

  1. 就像我们在对chat数据进行分析时一样,我们也可以调整chat对话的文本,并将每个单词分解为单独的数组。这将用于隔离停用词并将其删除。

  2. 将每个单词提取为字符串的新列称为words,可以在以下截屏中看到:

  1. 有许多方法可以将一组单词分配给停用词列表。其中一些单词可以使用适当的 Python 库nltk(自然语言工具包)自动下载和更新。对于我们的目的,我们将利用一个常见的 124 个停用词列表来生成我们自己的列表。可以轻松地手动添加或从列表中删除其他单词。

  2. 停用词不会为文本增添任何价值,并且将通过指定outputCol="words without stop"从新创建的列中删除。此外,通过指定inputCol = "words"来设置将用作转换源的列。

  3. 我们创建一个管道,stopWordRemovalPipeline,来定义将转换数据的步骤或阶段的顺序。在这种情况下,唯一用于转换数据的阶段是特征stopwordsRemover

  4. 管道中的每个阶段都可以具有转换角色和估计角色。估计角色pipeline.fit(df)用于生成名为pipelineFitRemoveStopWords的转换器函数。最后,在数据框上调用transform(df)函数,以生成具有名为words without stop的新列的更新后的数据框。我们可以将两列并排比较以查看差异,如下截屏所示:

  1. 新列words without stop不包含原始列words中被视为停用词的任何字符串。

另请参阅

要了解有关nltk的停用词的更多信息,请访问以下网站:

www.nltk.org/data.html

要了解更多关于 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 模型,并查看是否可以将这些交易分类为升级不升级

准备工作

本节将需要从spark.ml.featurespark.ml.classification导入。

操作步骤...

以下部分将逐步介绍训练 TF-IDF 模型的步骤。

  1. 创建一个新的用户定义函数udf,使用以下脚本为label列定义数值:
label = F.udf(lambda x: 1.0 if x == 'escalate' else 0.0, FloatType())
df = df.withColumn('label', label('label'))
  1. 执行以下脚本以设置单词向量化的 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")
  1. 使用以下脚本设置管道pipelineTFIDF,以设置TF_IDF_的阶段顺序:
pipelineTFIDF = Pipeline(stages=[TF_, IDF_])
  1. 使用以下脚本将 IDF 估计器拟合到数据框df上:
pipelineFit = pipelineTFIDF.fit(df)
df = pipelineFit.transform(df)
  1. 使用以下脚本将数据框拆分为 75:25 的比例,用于模型评估目的:
(trainingDF, testDF) = df.randomSplit([0.75, 0.25], seed = 1234)
  1. 使用以下脚本导入和配置分类模型LogisticRegression
from pyspark.ml.classification import LogisticRegression
logreg = LogisticRegression(regParam=0.25)
  1. 将逻辑回归模型logreg拟合到训练数据框trainingDF上。基于逻辑回归模型的transform()方法,创建一个新的数据框predictionDF,如下脚本所示:
logregModel = logreg.fit(trainingDF)
predictionDF = logregModel.transform(testDF)

工作原理...

以下部分解释了如何有效地训练 TF-IDF NLP 模型。

  1. 最好将标签以数值格式而不是分类形式呈现,因为模型能够在将输出分类为 0 和 1 之间时解释数值。因此,label列下的所有标签都转换为 0.0 或 1.0 的数值label,如下截图所示:

  1. TF-IDF 模型需要通过从pyspark.ml.feature导入HashingTFIDF来进行两步处理,以处理不同的任务。第一个任务仅涉及导入HashingTFIDF并为输入和随后的输出列分配值。numfeatures参数设置为 100,000,以确保它大于数据框中单词的不同数量。如果numfeatures小于不同的单词计数,模型将不准确。

  2. 如前所述,管道的每个步骤都包含一个转换过程和一个估计器过程。管道pipelineTFIDF被配置为按顺序排列步骤,其中IDF将跟随HashingTF

  3. HashingTF用于将words without stop转换为新列rawFeatures中的向量。随后,rawFeatures将被IDF消耗,以估算大小并适应数据框以生成名为features的新列,如下截图所示:

  1. 为了培训目的,我们的数据框将以75:25的比例保守地拆分,随机种子设置为1234

  2. 由于我们的主要目标是将每个对话分类为升级以进行升级或不升级以进行继续的机器人聊天,因此我们可以使用 PySpark 库中的传统分类算法,如逻辑回归模型。逻辑回归模型配置了正则化参数regParam为 0.025。我们使用该参数略微改进模型,以最小化过度拟合,代价是略微偏差。

  3. 逻辑回归模型在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 模型性能

此时,我们已准备好评估我们模型的性能

准备工作

本节将需要导入以下库:

  • 来自sklearnmetrics

  • pyspark.ml.evaluation中的BinaryClassificationEvaluator

如何做...

本节介绍了评估 TF-IDF NLP 模型的步骤。

  1. 使用以下脚本创建混淆矩阵:
predictionDF.crosstab('label', 'prediction').show()
  1. 使用以下脚本从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))
  1. 使用以下脚本计算 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))

它是如何工作的...

本节解释了我们如何使用评估计算来确定模型的准确性。

  1. 混淆矩阵有助于快速总结实际结果和预测结果之间的准确性数字。由于我们有 75:25 的分割,我们应该从训练数据集中看到 25 个预测。我们可以使用以下脚本构建混淆矩阵:predictionDF.crosstab('label', 'prediction').show()。脚本的输出可以在以下截图中看到:

  1. 我们现在处于通过比较prediction值和实际label值来评估模型准确度的阶段。sklearn.metrics接受两个参数,与label列相关联的actual值,以及从逻辑回归模型派生的predicted值。

请注意,我们再次将 Spark 数据框的列值转换为 pandas 数据框,使用toPandas()方法。

  1. 创建了两个变量actualpredicted,并使用metrics.accuracy_score()函数计算了 91.7%的准确度分数,如下截图所示:

  1. ROC(接收器操作特性)通常与测量真正率相对于假正率的曲线相关联。曲线下面积越大,越好。与曲线相关的 ROC 分数是另一个指标,可用于衡量模型的性能。我们可以使用BinaryClassificationEvaluator计算ROC,如下截图所示:

另请参阅

要了解有关 PySpark 中的BinaryClassificationEvaluator的更多信息,请访问以下网站:

spark.apache.org/docs/2.2.0/api/java/index.html?org/apache/spark/ml/evaluation/BinaryClassificationEvaluator.html

将模型性能与基线分数进行比较

虽然我们的模型具有 91.7%的高准确度分数,这很好,但将其与基线分数进行比较也很重要。我们在本节中深入探讨了这个概念。

如何做...

本节介绍了计算基线准确度的步骤。

  1. 执行以下脚本以从describe()方法中检索平均值:
predictionDF.describe('label').show()
  1. 减去1-平均值分数以计算基线准确度。

它是如何工作的...

本节解释了基线准确度背后的概念,以及我们如何使用它来理解模型的有效性。

  1. 如果每个chat对话都被标记为do_not_escalate或反之亦然,我们是否会有高于 91.7%的基准准确率?找出这一点最简单的方法是使用以下脚本在predictionDFlabel列上运行describe()方法:predictionDF.describe('label').show()

  2. 可以在以下截图中看到脚本的输出:

  1. label的平均值为 0.2083 或约 21%,这意味着label为 1 的情况仅发生了 21%的时间。因此,如果我们将每个对话标记为do_not_escalate,我们将有大约 79%的准确率,这低于我们的模型准确率 91.7%。

  2. 因此,我们可以说我们的模型表现比盲目基准性能模型更好。

另请参阅

要了解 PySpark 数据框中describe()方法的更多信息,请访问以下网站:

spark.apache.org/docs/2.2.0/api/python/pyspark.sql.html#pyspark.sql.DataFrame.describe

第八章:使用 XGBoost 进行房地产价值预测

房地产市场是定价最具竞争力的市场之一。这往往会根据诸多因素而显著变化,如位置、物业年龄、大小等。因此,准确预测房地产价格(特别是房地产市场中的价格)已成为一个现代挑战,以便做出更好的投资决策。本章将处理这个问题。

阅读完本章后,您将能够:

  • 下载金县房屋销售数据集

  • 进行探索性分析和可视化

  • 绘制价格与其他特征之间的相关性

  • 预测房屋价格

下载金县房屋销售数据集

在没有数据集的情况下,我们无法构建模型。我们将在本节中下载我们的数据。

准备工作

Kaggle (www.kaggle.com/)是一个用于预测建模和分析竞赛的平台,统计学家和数据挖掘者在这里竞争,以产生最佳的模型来预测和描述由公司和用户上传的数据集。金县房屋销售数据集包含了在 1900 年至 2015 年间在纽约金县出售的 21,613 套房屋的记录。数据集还包含了每套房屋的 21 个不同变量,如位置、邮政编码、卧室数量、生活空间面积等。

如何做...

  1. 可以从以下网站访问数据集:www.kaggle.com/harlfoxem/housesalesprediction。数据集来自金县的公共记录,可以免费下载和在任何分析中使用。

  2. 一旦您到达网站,您可以点击下载按钮,如下图所示:

金县房屋销售数据集

  1. 从压缩下载的文件housesalesprediction.zip中出现一个名为kc_house_data.csv的文件。

  2. 将名为kc_house_data.csv的文件保存在当前工作目录中,因为这将是我们的数据集。这将被加载到 IPython 笔记本中进行分析和预测。

它是如何工作的...

  1. 使用以下代码安装本章所需的必要库:
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
  1. 前面的步骤应该会产生一个输出,如下面的屏幕截图所示:

  1. 检查当前工作目录并将其设置为存储数据集的目录是一个好主意。如下图所示:

在我们的案例中,名为Chapter 10的文件夹被设置为当前工作目录。

  1. 使用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 平方英尺。

如何做到...

  1. 让我们绘制整个数据集中卧室数量的计数,以了解三居室房屋与两居室或一居室房屋的情况。使用以下代码完成:
dataframe['bedrooms'].value_counts().plot(kind='bar') plt.title('No. of bedrooms')
plt.xlabel('Bedrooms')
plt.ylabel('Count')
sns.despine
  1. 我们也可以使用以下命令绘制相同数据的饼图:
 dataframe['bedrooms'].value_counts().plot(kind='pie')
plt.title('No. of bedrooms')
  1. 接下来,让我们尝试看看金县最常售出的房屋有多少层。这可以通过使用以下命令绘制条形图来完成:
dataframe['floors'].value_counts().plot(kind='bar') plt.title('Number of floors')
plt.xlabel('No. of floors')
plt.ylabel('Count')
sns.despine
  1. 接下来,我们需要了解哪些地点售出的房屋数量最多。我们可以使用数据集中的latitudelongitude变量来做到这一点,如下代码所示:
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()
  1. 让我们也看看不同卧室数量的房屋价格如何相比,执行以下命令:
 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()
  1. 使用以下命令获得房屋价格与卧室数量的图:
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()
  1. 同样,让我们看看价格与所有售出房屋的居住面积的比较。这可以通过使用以下命令来完成:
plt.figure(figsize=(8,8))
plt.scatter(dataframe.price, dataframe.sqft_living)
plt.xlabel('Price')
plt.ylabel('Square feet')
plt.show()
  1. 售出的房屋的条件也给了我们一些重要的信息。让我们将其与价格绘制在一起,以更好地了解一般趋势。使用以下命令完成:
plt.figure(figsize=(5,5))
plt.bar(dataframe.condition, dataframe.price)
plt.xlabel('Condition')
plt.ylabel('Price')
plt.show()
  1. 我们可以使用以下命令查看哪些邮政编码在金县有最多的房屋销售:
plt.figure(figsize=(8,8))
plt.scatter(dataframe.zipcode, dataframe.price)
plt.xlabel('Zipcode')
plt.ylabel('Price')
plt.show()
  1. 最后,绘制每个房屋的等级与价格的关系,以了解基于每个房屋的等级的房屋销售趋势,使用以下命令:
plt.figure(figsize=(10,10))
plt.scatter(dataframe.grade, dataframe.price)
plt.xlabel('Grade')
plt.ylabel('Price')
plt.show()

工作原理...

  1. 卧室数量的图必须给出输出,如下屏幕截图所示:

  1. 很明显,三居室的房屋销售最多,其次是四居室,然后是两居室,然后是令人惊讶的五居室和六居室。

  2. 卧室数量的饼图输出如下屏幕截图所示:

  1. 您会注意到,三居室房屋大约占金县所有售出房屋的 50%。大约 25%是四居室房屋,其余 25%由两居室、五居室、六居室等房屋组成。

  2. 运行脚本以查看按楼层分类的最常售出的房屋时,我们注意到以下输出:

  1. 很明显,单层房屋销售量最大,其次是两层房屋。超过两层的房屋数量相当少,这可能是家庭规模和居住在金县的居民收入的指示。

  2. 检查不同位置出售房屋的密度后,我们得到了一个输出,如下面的屏幕截图所示。很明显,一些地方的房屋销售密度比其他地方要高:

  1. 从前述图表中观察到的趋势,很容易注意到在纬度-122.2 和-122.4 之间销售的房屋数量更多。同样,在经度 47.5 和 47.8 之间销售的房屋密度比其他经度更高。这可能是其他社区相比,更安全、更宜居社区的指示。

  2. 在绘制房屋价格与房屋卧室数量的关系时,我们意识到房屋卧室数量与价格之间的趋势与价格成正比,直到六个卧室,然后变为反比,如下面的屏幕截图所示:

  1. 将每个房屋的居住面积与价格进行对比,我们发现价格随着房屋面积的增加而增加的趋势。最昂贵的房屋似乎有 12000 平方英尺的居住面积,如下面的屏幕截图所示:

还有更多...

  1. 在绘制房屋状况与价格的关系时,我们再次注意到了一个预期的趋势,即随着房屋状况评分的提高,价格也在增加,如下面的屏幕截图所示。有趣的是,五卧室房屋的平均价格比四卧室房屋要低,这可能是因为对这么大的房子的购买者较少:

  1. 房屋邮政编码与价格的图表显示了不同邮政编码地区房屋价格的趋势。您可能已经注意到,某些邮政编码,如 98100 至 98125 之间的邮政编码,比其他地区有更多的房屋销售密度,而 98040 邮政编码地区的房屋价格高于平均价格,可能表明这是一个更富裕的社区,如下面的屏幕截图所示:

  1. 房屋等级与价格的图表显示,随着等级的提高,价格呈一致增长的趋势。两者之间似乎存在明显的线性关系,如下面的屏幕截图所示:

另请参阅

在对数据进行任何模型运行之前,以下链接很好地解释了为什么数据可视化如此重要:

绘制价格与其他特征之间的相关性

现在初步的探索性分析已经完成,我们对不同变量如何影响每个房屋的价格有了更好的了解。然而,我们不知道每个变量在预测价格时的重要性。由于我们有 21 个变量,如果将所有变量合并到一个模型中,建模就会变得困难。因此,一些变量可能需要被丢弃或忽略,如果它们的重要性不如其他变量。

准备工作

相关系数在统计学中用于衡量两个变量之间的关系强度。特别是,在进行线性回归时,皮尔逊相关系数是最常用的系数。相关系数通常取-1 到+1 之间的值:

  • 相关系数为 1 意味着对于一个变量的正增加,另一个变量也会以固定比例正增加。例如,鞋子尺寸几乎与脚长完美相关。

  • 相关系数为-1 意味着对于一个变量的正增加,另一个变量会以固定比例负减少。例如,油箱中的汽油量几乎与加速度或齿轮机构完美相关(在一档行驶的时间较长时,与四档相比,汽油的使用量减少)。

  • 零意味着对于每次增加,没有正面或负面的增加。两者之间没有关系。

如何做...

  1. 通过使用以下命令从数据集中删除iddate特征开始。在我们的预测中,我们不会使用它们,因为 ID 变量都是唯一的,在我们的分析中没有价值,而日期需要使用不同的函数来正确处理它们。这留给读者自己练习:
 x_df = dataframe.drop(['id','date',], axis = 1)
 x_df
  1. 使用以下命令将因变量(在本例中为房价)复制到新的dataframe中:
 y = dataframe[['price']].copy()
 y_df = pd.DataFrame(y)
 y_df
  1. 价格和其他每个变量之间的相关性可以通过以下脚本手动找到:
 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']))
  1. 除了前面的方法,还可以通过以下方式使用一个命令在一个dataframe中找到一个变量与所有其他变量(或列)之间的相关性:

x_df.corr().iloc[:,-19]

  1. 相关变量可以使用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)

它是如何工作的...

  1. 删除iddate变量后,新的名为x_dfdataframe包含 19 个变量或列,如以下截图所示。对于本书的目的,只打印出前十个条目:

输出的前 10 个条目

  1. 创建一个只包含因变量(价格)的新dataframe,您将看到以下输出。这个新的dataframe名为y_df。同样,为了说明,只打印价格列的前十个条目:

  1. 价格和其他变量之间的相关性显示在以下截图中:

  1. 您可能已经注意到,sqft_living变量与价格的相关性最高,相关系数为 0.702035。其次是grade,相关系数为 0.667434,其次是sqft_above,相关系数为 0.605567。Zipcode与价格的相关性最低,相关系数为-0.053202。

还有更多...

  • 使用简化代码找到的相关系数给出了完全相同的值,但也给出了价格与自身的相关性,结果是 1.0000,这是预期的。如以下截图所示:

  • 使用seaborn库绘制的相关系数在以下截图中呈现。请注意,每个图中价格都在 x 轴上:

相关系数的绘制

另请参阅

以下链接提供了对皮尔逊相关系数的出色解释以及如何手动计算它:

en.wikipedia.org/wiki/Pearson_correlation_coefficient

www.statisticshowto.com/probability-and-statistics/correlation-coefficient-formula/

预测房价

本节将使用当前数据框中的所有特征构建一个简单的线性模型来预测房价。然后,我们将评估模型,并尝试在本节的后半部分使用更复杂的模型来提高准确性。

准备工作

访问以下链接以了解线性回归的工作原理以及如何在 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

如何做...

  1. 使用以下脚本从x_df数据框中删除Price列,并将其保存到名为x_df2的新数据框中:
 x_df2 = x_df.drop(['price'], axis = 1)
  1. 声明一个名为reg的变量,并使用以下脚本将其等于 Scikit Learn 库中的LinearRegression()函数:
 reg=linear_model.LinearRegression()
  1. 使用以下脚本将数据集分割为测试集和训练集:
 x_train,x_test,y_train,y_test = train_test_split(x_df2,y_df,test_size=0.4,random_state=4)
  1. 使用以下脚本在训练集上拟合模型:
 reg.fit(x_train,y_train)
  1. 通过使用reg.coef_命令打印应用线性回归到训练集和测试集生成的系数。

  2. 使用以下脚本生成的模型预测列进行查看:

 predictions=reg.predict(x_test)
 predictions
  1. 使用以下命令打印模型的准确性:
 reg.score(x_test,y_test)

它是如何工作的...

  1. 将回归模型拟合到训练集后的输出必须如下截屏所示:

  1. reg.coeff_命令生成了 18 个系数,每个系数对应数据集中的一个变量,如下截屏所示:

  1. 具有最正值的特征/变量的系数在价格预测中具有更高的重要性,与具有负值的特征/变量的系数相比。这是回归系数的主要重要性。

  2. 打印预测时,您必须看到一个输出,其中包含从 1 到 21,612 的值数组,数据集中的每一行都有一个值,如下截屏所示:

  1. 最后,打印模型的准确性,我们获得了 70.37%的准确性,对于线性模型来说并不差。如下截屏所示:

还有更多...

线性模型在第一次尝试时表现不错,但如果我们希望模型更准确,我们将不得不使用一个带有一些非线性的更复杂模型,以便很好地拟合所有数据点。XGBoost 是我们将在本节中使用的模型,以尝试通过线性回归提高准确性。这是以下方式完成的:

  1. 使用import xgboost命令导入XGBoost库。

  2. 如果出现错误,您将不得不通过终端进行库的 pip 安装。这可以通过打开一个新的终端窗口并发出以下命令来完成:

/usr/bin/ruby -e "$(curl -fsSL https://raw.githubusercontent.com/Homebrew/install/master/install)"
  1. 在这个阶段,您必须看到一个输出,其外观如下截屏所示:

  1. 在这个阶段,您将被提示输入密码。安装 Homebrew 后,您将看到如下截屏所示的输出:

  1. 接下来,使用以下命令安装 Python:

brew install python

  1. 使用brew doctor命令检查您的安装并遵循 homebrew 的建议。

  2. 一旦安装了Homebrew,请使用以下命令 pip 安装 XGBoost:

pip install xgboost

  1. 安装完成后,您应该能够将 XGBoost 导入到 IPython 环境中。

一旦 XGBoost 成功导入到 Jupyter 环境中,您就可以使用库中的函数声明和存储模型。可以按以下步骤完成:

  1. 声明一个名为new_model的变量来存储模型,并使用以下命令声明所有超参数:
new_model = xgboost.XGBRegressor(n_estimators=750, learning_rate=0.09,         gamma=0, subsample=0.65, colsample_bytree=1, max_depth=7)
  1. 上述命令的输出必须看起来像以下截图中显示的那样:

  1. 将数据分割为测试集和训练集,并使用以下命令将新模型拟合到拆分数据中:
 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)
  1. 在这一点上,您将看到类似以下截图中显示的输出:

  1. 最后,使用新拟合的模型预测房屋价格,并使用以下命令评估新模型:
 from sklearn.metrics import explained_variance_score
 predictions = new_model.predict(x_test)
 print(explained_variance_score(predictions,y_test))
  1. 执行上述命令时,您必须看到类似以下截图中显示的输出:

  1. 请注意,新模型的准确性现在为 87.79%,约为 88%。这被认为是最佳的。

  2. 在这种情况下,估计器数量设置为 750。在 100 到 1,000 之间进行实验后,确定 750 个估计器给出了最佳准确性。学习率设置为 0.09。子采样率设置为 65%。最大深度设置为 7。max_depth对模型准确性似乎没有太大影响。然而,使用较慢的学习率确实提高了准确性。通过尝试各种超参数,我们能够进一步将准确性提高到 89%。

  3. 未来的步骤涉及对诸如卧室、浴室、楼层、邮政编码等变量进行独热编码,并在模型拟合之前对所有变量进行归一化。尝试调整超参数,如学习率、XGBoost 模型中的估计器数量、子采样率等,以查看它们如何影响模型准确性。这留给读者作为练习。

  4. 此外,您可能希望尝试结合 XGBoost 和交叉验证,以找出模型中树的最佳数量,从而进一步提高准确性。

  5. 可以进行的另一个练习是使用不同大小的测试和训练数据集,以及在训练过程中合并date变量。在我们的情况下,我们将其分割为 80%的训练数据和 20%的测试数据。尝试将测试集增加到 40%,看看模型准确性如何变化。

另请参阅

访问以下链接以了解如何调整 XGBoost 模型中的超参数以及如何在 XGBoost 中实现交叉验证:

xgboost.readthedocs.io/en/latest/python/index.html

xgboost.readthedocs.io/en/latest/get_started/

www.kaggle.com/cast42/xg-cv

第九章:使用 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()

如何做到...

以下部分将介绍下载苹果的历史股票市场数据的步骤。

  1. 访问以下网站,跟踪苹果的每日历史调整收盘股票价值,其股票代码为 AAPL:finance.yahoo.com/quote/AAPL/history

  2. 设置并应用以下参数到历史数据选项卡:

  3. 时间段:2000 年 1 月 1 日至 2018 年 4 月 30 日。

  4. 显示:历史价格。

  5. 频率:每日。

  6. 通过单击下载数据链接,使用指定参数将数据集下载到.csv文件中,如下屏幕截图所示:

  1. 下载文件AAPL.csv,然后使用以下脚本将相同的数据集上传到 Spark 数据框中:
df =spark.read.format('com.databricks.spark.csv')\
   .options(header='true', inferschema='true')\
   .load('AAPL.csv')

工作原理...

以下部分解释了如何将股票市场数据纳入 Jupyter 笔记本。

  1. Yahoo! Finance 是公开交易公司股票市场报价的重要来源。苹果的股票报价 AAPL 在纳斯达克交易,可以捕获历史报价以进行模型开发和分析。Yahoo! Finance 提供了在每日、每周或每月快照上捕获股票报价的选项。

  2. 本章的目的是在每日级别预测股票,因为这将为我们的训练模型带来最多的数据。我们可以通过追溯数据到 2000 年 1 月 1 日,一直到 2018 年 4 月 30 日来实现这一点。

  3. 一旦我们设置了下载参数,我们就会从 Yahoo! Finance 收到一个格式良好的逗号分隔值文件,可以很容易地转换为具有最少问题的 Spark 数据框。

  4. 数据框将允许我们每天查看股票的日期、开盘价、最高价、最低价、收盘价、调整收盘价和成交量。数据框中的列跟踪开盘和收盘股票价值,以及当天交易的最高和最低价值。还捕获了当天交易的股票数量。Spark 数据框的输出df可以通过执行df.show()来显示,如下面的屏幕截图所示:

还有更多...

Python 有股票市场 API,允许您自动连接并拉取公开交易公司(如苹果)的股票市场报价。您需要输入参数并检索可以存储在数据框中的数据。然而,截至 2018 年 4 月,Yahoo! Finance API 不再运作,因此不是提取本章数据的可靠解决方案。

另请参阅

Pandas_datareader 是一个非常强大的库,可以从网站上提取数据,例如 Yahoo! Finance。要了解更多关于该库以及它如何在恢复在线后与 Yahoo! Finance 连接的信息,请访问以下网站:

github.com/pydata/pandas-datareader

探索和可视化苹果股票市场数据

在对数据进行任何建模和预测之前,首先探索和可视化手头的数据是很重要的,以发现任何隐藏的宝藏。

准备工作

在本节中,我们将对数据框进行转换和可视化。这将需要在 Python 中导入以下库:

  • pyspark.sql.functions

  • matplotlib

如何做...

以下部分将介绍探索和可视化股票市场数据的步骤。

  1. 使用以下脚本通过删除时间戳来转换数据框中的 Date 列:
import pyspark.sql.functions as f
df = df.withColumn('date', f.to_date('Date'))
  1. 创建一个循环来向数据框添加三个额外的列。循环将把 date 字段分解为 yearmonth 和 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])
  1. 使用以下脚本将 Spark 数据框的子集保存到名为 df_plot 的 pandas 数据框中:df_plot = df.select('year', 'Adj Close').toPandas().

  2. 使用以下脚本在笔记本中绘制和可视化 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()
  1. 使用以下脚本计算我们的 Spark 数据框的行和列数:df.toPandas().shape

  2. 执行以下脚本来确定数据框中的空值:df.dropna().count()

  3. 执行以下脚本来获取 OpenHighLowClose 和 Adj Close 的统计数据:

df.select('Open', 'High', 'Low', 'Close', 'Adj Close').describe().show()

它是如何工作的...

以下部分解释了探索性数据分析所使用的技术和获得的见解。

  1. 数据框中的日期列更像是一个带有时间值的日期时间列,所有时间值都以 00:00:00 结尾。这对于我们建模过程中的需求是不必要的,因此可以从数据集中删除。幸运的是,PySpark 有一个 to_date 函数可以很容易地做到这一点。数据框 df 使用 withColumn() 函数进行转换,现在只显示日期列而没有时间戳,如下面的屏幕截图所示:

  1. 为了分析目的,我们想要从日期列中提取 daymonth 和 year。我们可以通过枚举一个自定义列表 date_breakdown 来实现这一点,通过 - 分割日期,然后使用 withColumn() 函数为年、月和日添加新列。更新后的数据框中可以看到新添加的列,如下面的屏幕截图所示:

一个重要的要点是,PySpark 也有一个用于日期的 SQL 函数,可以从日期时间戳中提取日、月或年。例如,如果我们要向数据框添加一个月份列,我们将使用以下脚本:df.withColumn("month",f.month("date")).show()。这是为了突出在 Spark 中有多种方法可以转换数据。

  1. Spark 数据框的可视化功能比 pandas 数据框更有限。因此,我们将从 Spark 数据框 df 中提取两列,并将它们转换为 pandas 数据框,以绘制线形或时间序列图。y 轴将是股票的调整收盘价,x 轴将是日期的年份。

  2. 准备好的 pandas 数据框 df_plot 可以在设置一些格式特性后使用 matplotlib 进行绘制,例如网格可见性、绘图的图形大小以及标题和轴的标签。此外,我们明确指出数据框的索引需要指向年份列。否则,默认索引将出现在 x 轴上而不是年份。最终的时间序列图可以在下面的屏幕截图中看到:

  1. 在过去的 18 年中,苹果经历了广泛的增长。虽然有几年出现了一些下跌,但总体趋势是稳步上升,过去几年的股票报价在 150 美元和 175 美元之间徘徊。

  2. 到目前为止,我们对数据框进行了一些更改,因此重要的是要对行和列的总数进行清点,因为这将影响后面在本章中对数据集进行测试和训练的方式。如下截图所示,我们总共有 10 列和 4,610 行:

  1. 当执行df.dropna().count()时,我们可以看到行数仍然是 4,610,与上一步的行数相同,表明没有任何行具有空值。

  2. 最后,我们可以得到每个将用于模型的列的行数、均值、标准差、最小值和最大值的良好读数。这可以帮助确定数据中是否存在异常。需要注意的一点是,将用于模型的五个字段的标准差都高于均值,表明数据更分散,而不是围绕均值聚集。可以在以下截图中看到 Open、High、Low、Close 和 Adj Close 的统计数据:

还有更多...

虽然 Spark 中的数据框没有pandas数据框中的本地可视化功能,但有些公司可以通过笔记本提供高级可视化功能,而无需使用诸如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()

如何做...

本节将介绍为我们的模型准备股票市场数据的步骤。

  1. 执行以下脚本将年份列按Adj Close计数分组:
df.groupBy(['year']).agg({'Adj Close':'count'})\
     .withColumnRenamed('count(Adj Close)', 'Row Count')\
     .orderBy(["year"],ascending=False)\
     .show()
  1. 执行以下脚本创建两个新的用于训练和测试的数据框:
trainDF = df[df.year < 2017]
testDF = df[df.year > 2016]
  1. 使用以下脚本将两个新数据框转换为pandas数据框,以获取行和列计数:
trainDF.toPandas().shape
testDF.toPandas().shape
  1. df之前所做的一样,我们使用以下脚本可视化trainDFtestDF
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()
  1. 我们根据以下脚本创建两个新数组trainArraytestArray,除了日期列以外的数据框的数据:
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())
  1. 为了将数组缩放到 0 到 1 之间,从sklearn导入MinMaxScaler并创建一个函数调用MinMaxScale,使用以下脚本:
from sklearn.preprocessing import MinMaxScaler
minMaxScale = MinMaxScaler()
  1. 然后在trainArray上拟合MinMaxScaler并使用以下脚本创建两个新数组,以便进行缩放:
minMaxScale.fit(trainArray)

testingArray = minMaxScale.transform(testArray)
trainingArray = minMaxScale.transform(trainArray)
  1. 使用以下脚本将testingArraytrainingArray拆分为特征x和标签y
xtrain = trainingArray[:, 0:-1]
xtest = testingArray[:, 0:-1]
ytrain = trainingArray[:, -1:]
ytest = testingArray[:, -1:]
  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))
  1. 执行以下脚本来绘制报价openhighlowclose的训练数组:
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()
  1. 此外,我们使用以下脚本绘制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()

工作原理...

本节将解释数据在模型中使用时所需的转换。

  1. 建立模型的第一步之一是将数据分割为训练和测试数据集,以进行模型评估。我们的目标是使用 2000 年至 2016 年的所有股票报价来预测 2017 年至 2018 年的股票趋势。我们知道从前面的部分我们有总共 4,610 天的股票报价,但我们不知道每年有多少。我们可以使用数据框中的groupBy()函数来获取每年股票报价的唯一计数,如下图所示:

  1. 2016 年和 2017 年的合并数据大约占总数据的 7%,这对于测试数据集来说有点小。但是,对于这个模型的目的来说,应该是足够的。剩下的 93%的数据将用于 2000 年至 2016 年的训练。因此,使用筛选器创建了两个数据框,以确定是否包括或排除 2016 年之前或之后的行。

  2. 我们现在可以看到测试数据集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中进行可视化。

  1. 一旦将数据的子集转换为toPandas()以利用pandas的内置绘图功能,就可以使用matplotlib可视化测试和训练数据框。将数据框并排可视化展示了当未缩放调整收盘价的 y 轴时,图表看起来相似。实际上,我们可以看到trainDF_plot从 0 开始,而testDF_plot从 110 开始,如下两个截图所示。

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

  1. 有许多方法可以将数组值缩放到 0 到 1 之间的范围。它涉及使用以下公式:缩放后的数组值 = (数组值 - 最小数组值) / (最大数组值 - 最小数组值)。幸运的是,我们不需要手动计算数组的值。我们可以利用sklearn中的MinMaxScaler()函数来缩放这两个数组。

  2. MinMaxScaler()函数适用于训练数组trainArray,然后应用于创建两个全新的数组trainingArraytestingArray,它们的值在 0 到 1 之间进行了缩放。每个数组的第一行可以在下面的截图中看到:

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

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

  1. 训练数组xtrain的 open、low、high 和 close 的值可以使用新调整的 0 到 1 之间的标度绘制报价,如下截图所示:

  1. 此外,volume也可以使用 0 到 1 之间的缩放体积得分绘制,如下截图所示:

还有更多...

虽然我们使用了来自sklearnMinMaxScaler,但也很重要的是要了解,pyspark.ml.feature中也有一个MinMaxScaler函数可供使用。它的工作方式与sklearn完全相同,通过将每个特征重新缩放为 0 到 1 之间的值。如果我们在本章中使用了 PySpark 中的机器学习库来进行预测,我们将使用pyspark.ml.feature中的MinMaxScaler

另请参阅

要了解来自sklearnMinMaxScaler的更多信息,请访问以下网站:

scikit-learn.org/stable/modules/generated/sklearn.preprocessing.MinMaxScaler.html.

要了解来自pysparkMinMaxScaler的更多信息,请访问以下网站:

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 模型的步骤。

  1. 使用以下脚本从keras导入以下库:
from keras import models, layers
  1. 使用以下脚本构建一个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')
  1. 使用以下脚本将测试和训练数据集转换为三维数组:
xtrain = xtrain.reshape((xtrain.shape[0], 1, xtrain.shape[1]))
xtest = xtest.reshape((xtest.shape[0], 1, xtest.shape[1]))
  1. 使用以下脚本使用名为loss的变量来fit模型:
loss = model.fit(xtrain, ytrain, batch_size=10, epochs=100)
  1. 使用以下脚本创建一个新数组predicted
predicted = model.predict(xtest)
  1. 使用以下脚本将predictedytest数组合并成一个统一的数组combined_array
combined_array = np.concatenate((ytest, predicted), axis = 1)

它是如何工作的...

本节解释了如何配置 LSTM 神经网络模型以在我们的数据集上进行训练。

  1. 大部分用于构建 LSTM 模型的keras功能将来自modelslayers

  2. 构建的LSTM模型将使用Sequential类进行定义,该类与依赖于序列的时间序列非常匹配。LSTM 模型的input_shape = (1,5),表示我们的训练数据集中有一个因变量和五个自变量。只使用一个Dense层来定义神经网络,因为我们希望保持模型简单。在 keras 中编译模型时需要一个损失函数,由于我们正在对递归神经网络进行操作,因此最好使用mean_squared_error计算来确定预测值与实际值的接近程度。最后,在编译模型时还需要定义一个优化器来调整神经网络中的权重。adam在递归神经网络中表现良好,尤其是在使用时。

  3. 我们当前的数组xtrainxtest目前是二维数组;然而,为了将它们纳入 LSTM 模型中,它们需要使用reshape()转换为三维数组,如下面的屏幕截图所示:

  1. LSTM 模型使用xtrainytrain进行拟合,批量大小设置为 10,时期数设置为 100。批量大小是定义一起训练的对象数量的设置。我们可以根据需要设置批量大小的大小,但要记住,批量数量越低,需要的内存就越多。此外,时期是模型遍历整个数据集的次数的度量。最终,这些参数可以根据时间和内存分配进行调整。

每个时期的均方误差损失都被捕获并可视化。在第五或第六个时期之后,我们可以看到损失逐渐减小,如下面的屏幕截图所示:

  1. 我们现在可以创建一个新数组predicted,基于应用于xtest的拟合模型,然后将其与ytest结合在一起,以便进行准确性比较。

另请参阅

要了解更多关于 keras 中参数调整模型的信息,请访问以下网站:keras.io/models/model/

评估模型

现在到了关键时刻:我们将看看我们的模型是否能够为 2017 年和 2018 年的 AAPL 股票提供良好的预测。

准备工作

我们将使用均方误差进行模型评估。因此,我们需要导入以下库:

import sklearn.metrics as metrics

如何做...

本节介绍了可视化和计算 2017 年和 2018 年苹果公司预测与实际股票报价的过程。

  1. 绘制ActualPredicted股票的并排比较图,使用以下脚本:
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()
  1. 使用以下脚本计算实际ytestpredicted股票之间的均方误差:
import sklearn.metrics as metrics
np.sqrt(metrics.mean_squared_error(ytest,predicted))

工作原理...

本节解释了 LSTM 模型评估的结果。

  1. 从图形上看,我们可以看到我们的预测与 2017 年至 2018 年的实际股票报价非常接近,如下面的屏幕截图所示:

  1. 我们的模型显示,对于 2017 年和 2018 年的前几天,预测值与实际值更接近。总的来说,虽然我们的预测值和实际得分似乎非常接近,但最好还是进行均方误差计算,以了解两者之间的偏差有多大。正如我们所看到的,我们的均方误差为 0.05841,约为 5.8%。

另请参阅

要了解更多关于 sklearn 中如何计算均方误差的信息,请访问以下网站:

scikit-learn.org/stable/modules/generated/sklearn.metrics.mean_squared_error.html

第十章:使用深度卷积网络进行人脸识别

在本章中,我们将涵盖以下配方:

  • 下载并将 MIT-CBCL 数据集加载到内存中

  • 从目录中绘制和可视化图像

  • 图像预处理

  • 模型构建、训练和分析

介绍

在当今世界,维护信息安全的需求变得越来越重要,同时也变得越来越困难。有各种方法可以强制执行此安全性(密码、指纹 ID、PIN 码等)。然而,就易用性、准确性和低侵入性而言,人脸识别算法一直表现得非常出色。随着高速计算的可用性和深度卷积网络的发展,进一步增加了这些算法的稳健性成为可能。它们已经变得如此先进,以至于现在它们被用作许多电子设备(例如 iPhoneX)甚至银行应用程序中的主要安全功能。本章的目标是开发一个稳健的、姿势不变的人脸识别算法,用于安全系统。为了本章的目的,我们将使用公开可用的MIT-CBCL数据集,其中包含 10 个不同主题的人脸图像。

下载并将 MIT-CBCL 数据集加载到内存中

在这个配方中,我们将了解如何下载 MIT-CBCL 数据集并将其加载到内存中。

到 2025 年,生物识别行业的预测价值将达到 150 亿美元,这意味着它将前所未有地增长。用于生物识别认证的一些生理特征的例子包括指纹、DNA、面部、视网膜或耳朵特征和声音。虽然 DNA 认证和指纹等技术相当先进,但人脸识别也带来了自己的优势。

由于深度学习模型的最新发展,易用性和稳健性是人脸识别算法如此受欢迎的驱动因素之一。

准备工作

对于这个配方,需要考虑以下关键点:

  • MIT-CBCL数据集由 3,240 张图像组成(每个主题 324 张图像)。在我们的模型中,我们将安排增加数据以增加模型的稳健性。我们将采用诸如移动主题、旋转、缩放和剪切主题等技术来获得这些增强的数据。

  • 我们将使用数据集的 20%(648 张图像)来测试我们的模型,通过从数据集中随机选择这些图像。同样,我们随机选择数据集中 80%的图像,并将其用作我们的训练数据集(2,592 张图像)。

  • 最大的挑战是裁剪图像到完全相同的大小,以便它们可以被馈送到神经网络中。

  • 众所周知,当所有输入图像的大小相同时,设计网络要容易得多。然而,由于这些图像中的一些主题具有侧面轮廓或旋转/倾斜轮廓,我们必须使我们的网络适应接受不同大小的输入图像。

操作方法...

步骤如下。

  1. 通过访问人脸识别主页下载MIT-CBCL数据集,该主页包含用于人脸识别实验的多个数据库。链接以及主页的屏幕截图如下所示:

www.face-rec.org/databases/

  1. 向下导航到名为 MIT-CBCL 人脸识别数据库的链接,并单击它,如下面的屏幕截图所示:

  1. 一旦你点击它,它会带你到一个许可页面,在这个页面上你需要接受许可协议并转到下载页面。一旦在下载页面上,点击立即下载。这将下载一个大约 116MB 的 zip 文件。继续提取内容到工作目录中。

工作原理...

功能如下:

  1. 许可协议要求在任何项目中使用数据库时进行适当引用。该数据库是由麻省理工学院的研究团队开发的。

  2. 特此感谢麻省理工学院和生物计算学习中心提供面部图像数据库。许可证还要求提及题为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, and V. Blanz 的论文。

  3. 以下截图描述了许可协议以及下载数据集的链接:

面部识别数据库主页

  1. 数据集下载并提取后,您将看到一个名为 MIT-CBCL-facerec-database 的文件夹。

  2. 为本章的目的,我们将仅使用training-synthetic文件夹中的图像,该文件夹包含所有 3,240 张图像,如下截图所示:

还有更多...

对于本章,您将需要 Python 导入以下库:

  • os

  • matplotlib

  • numpy

  • keras

  • TensorFlow

本章的以下部分将涉及导入必要的库和预处理图像,然后构建神经网络模型并将其加载到其中。

另请参阅

有关本章中使用的软件包的完整信息,请访问以下链接:

绘制和可视化目录中的图像

本节将描述如何在对图像进行预处理并输入到神经网络进行训练之前,如何读取和可视化下载的图像。这是本章中的重要步骤,因为需要可视化图像以更好地了解图像尺寸,以便可以准确裁剪以省略背景并仅保留必要的面部特征。

准备工作

在开始之前,完成导入必要库和函数以及设置工作目录路径的初始设置。

如何做...

步骤如下:

  1. 使用以下代码行下载必要的库。输出必须产生一行,显示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

导入库如下所示:

  1. 按照以下截图中所示的方式打印并设置当前工作目录。在我们的案例中,桌面被设置为工作目录:

  1. 通过使用以下截图中说明的命令直接从文件夹中读取所有图像:

  1. 使用plt.imshow(images[])命令从数据集中打印一些随机图像,如下截图所示,以更好地了解图像中的面部轮廓。这也将给出图像的大小的概念,这将在后期需要:

  1. 这里显示了来自第一张图像的不同测试对象的图像。

工作原理...

功能如下:

  1. mypath变量设置要从中读取所有文件的路径。在此步骤中指定了training-synthetic文件夹,因为本章仅使用该文件夹中的文件。

  2. onlyfiles变量用于通过循环遍历文件夹中包含的所有文件来计算文件夹中的所有文件的数量。这将在下一步中用于读取和存储图像。

  3. images变量用于创建一个大小为 3,240 的空数组,以存储所有尺寸为 200 x 200 像素的图像。

  4. 接下来,通过在 for 循环中使用onlyfiles变量作为参数来循环遍历所有文件,将文件夹中包含的每个图像读取并存储到先前定义的images数组中,使用matplotlib.image函数。

  5. 最后,通过指定不同索引的图像来打印随机选择的图像,您将注意到每个图像都是一个 200 x 200 像素的数组,每个主题可能是面向前方,也可能在两侧之间旋转零至十五度。

还有更多...

以下几点值得注意:

  • 该数据库的一个有趣特点是,每个文件名的第四个数字描述了相应图像中的主题是谁。

  • 图像的名称在某种意义上是唯一的,第四个数字代表了相应图像中的个体。图像名称的两个示例是0001_-4_0_0_60_45_1.pgm0006_-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 Optimizer 及其用例的更多信息,请访问以下链接:

图像预处理

在前一节中,您可能已经注意到所有图像都不是脸部正面视图,还有些略微旋转的侧面轮廓。您可能还注意到每个图像中都有一些不必要的背景区域需要去除。本节将描述如何预处理和处理图像,使其准备好被馈送到网络进行训练。

准备工作

考虑以下内容:

  • 许多算法被设计用来裁剪图像的重要部分;例如 SIFT、LBP、Haar-cascade 滤波器等。

  • 然而,我们将用一个非常简单的天真代码来解决这个问题,从图像中裁剪出面部部分。这是该算法的一个新颖之处。

  • 我们发现不必要的背景部分的像素强度为 28。

  • 请记住,每个图像都是一个三通道的 200 x 200 像素矩阵。这意味着每个图像包含三个矩阵或张量,红色、绿色和蓝色像素的强度范围从 0 到 255。

  • 因此,我们将丢弃图像中仅包含像素强度为 28 的行或列。

  • 我们还将确保所有图像在裁剪操作后具有相同的像素大小,以实现卷积神经网络的最高并行性。

操作步骤如下:

步骤如下:

  1. 定义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]
  1. 使用以下代码循环遍历文件夹中的每个图像并使用前面定义的函数进行裁剪:
#cropping all the images
 image = np.empty([3240,150,128],dtype=int)
 for n in range(0, len(images)):
     image[n]=crop(images[n])
  1. 接下来,随机选择一幅图像并打印它,以检查它是否已从 200 x 200 大小的图像裁剪到不同的大小。在我们的案例中,我们选择了图像 23。可以使用以下代码完成:
 print (image[22])
 print (image[22].shape)
  1. 接下来,使用文件夹中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)
  1. 一旦数据完成拆分,使用以下命令将训练和测试图像分开:
 # slicing the training and test images 
 y1_train=y[train_ind]
 x_test=image[test_ind]
 y1_test=y[test_ind]
  1. 接下来,将所有裁剪后的图像重塑为 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)
  1. 一旦数据完成重塑,将其转换为float32类型,这将使其在下一步中更容易处理。可以使用以下命令从 int 转换为 float32:
 #converting data to float32
 x_train = x_train.astype('float32')
 x_test = x_test.astype('float32')
  1. 在重塑和将数据转换为 float32 类型后,必须对其进行归一化,以调整所有值到相似的范围。这是防止数据冗余的重要步骤。使用以下命令执行归一化:
 #normalizing data
 x_train/=255
 x_test/=255
 #10 digits represent the 10 classes
 number_of_persons = 10
  1. 最后一步是将重塑、归一化的图像转换为向量,因为这是神经网络理解的唯一输入形式。使用以下命令将图像转换为向量:
 #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)

工作原理如下:

功能如下:

  1. crop()函数执行以下任务:

  2. 将所有像素强度为 28 的像素乘以一个 numpy 数组 1,并存储在变量a中。

  3. 检查所有实例,其中整列仅由像素强度为 28 的像素组成,并存储在变量b中。

  4. 删除所有列(或Y轴)中像素强度为 28 的整列。

  5. 绘制生成的图像。

    1. 转置图像,以便对所有行(或X轴)执行类似的操作。
    1. 将所有像素强度为 28 的像素乘以一个numpy数组 1,并存储在变量d中。
  6. 检查所有实例,其中整列仅由像素强度为 28 的像素组成,并存储在变量e中。

  7. 删除所有列(从转置图像中)中像素强度为 28 的整列。

  8. 转置图像以恢复原始图像。

  9. 打印图像的形状。

  10. 在发现像素强度小于 29 的地方,将这些像素强度替换为零,这将导致通过使它们变白来裁剪所有这些像素。

  11. 绘制生成的图像。

  12. 将生成的图像重塑为 150 x 128 像素的大小。

crop()函数的输出,如在 Jupyter 笔记本执行期间所见,如下截图所示:

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

输出如下:

注意,仅保留了相关的面部特征,并且所有裁剪后的图像的形状都小于 200 x 200,这是初始大小。

  1. 打印任意图像的图像和形状,您会注意到每个图像现在都被调整为一个 150 x 128 像素的数组,并且您将看到以下输出:

  2. 将图像分割为测试集和训练集,并将它们分隔为名为x_trainy1_trainx_testy1_test的变量,将导致以下截图中看到的输出:

  3. 数据的分离如下进行:

  1. 对训练和测试图像进行重塑并将数据类型转换为 float32 将导致以下截图中看到的输出:

还有更多...

考虑以下内容:

  • 一旦图像完成预处理,它们仍然需要被规范化并转换为向量(在本例中是张量),然后才能被输入到网络中。

  • 在最简单的情况下,规范化意味着调整在不同尺度上测量的值到一个概念上的共同尺度,通常是在平均之前。规范化数据总是一个好主意,以防止梯度在梯度下降过程中爆炸或消失,如梯度消失和爆炸问题所示。规范化还确保没有数据冗余。

  • 通过将每个图像中的每个像素除以255来对数据进行规范化,因为像素值的范围在 0 和255之间。这将导致以下截图中看到的输出:

  • 接下来,使用numpy_utils中的to_categorical()函数将图像转换为具有十个不同类的输入向量,如下截图所示:

另请参阅

以下是其他资源:

  • 有关数据规范化的更多信息,请查看以下链接:

www.quora.com/What-is-normalization-in-machine-learning

  • 有关过拟合以及为什么数据被分成测试集和训练集的信息,请访问以下链接:

towardsdatascience.com/train-test-split-and-cross-validation-in-python-80b61beca4b6

  • 有关编码变量及其重要性的更多信息,请访问以下链接:

pbpython.com/categorical-encoding.html

模型构建、训练和分析

我们将使用keras库中的标准顺序模型来构建 CNN。该网络将包括三个卷积层,两个最大池化层和四个全连接层。输入层和随后的隐藏层有 16 个神经元,而最大池化层包含(2,2)的池大小。四个全连接层包括两个密集层和一个扁平层和一个 dropout 层。使用 0.25 的 dropout 来减少过拟合问题。该算法的另一个新颖之处是使用数据增强来对抗过拟合现象。数据增强通过旋转、移位、剪切和缩放图像到不同程度来适应模型。

在输入和隐藏层中,使用relu函数作为激活函数,而在输出层中使用softmax分类器来根据预测的输出对测试图像进行分类。

准备工作

将构建的网络可视化如下图所示:

如何做...

步骤如下:

  1. 使用以下命令在 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')) 
  1. 打印模型的摘要以更好地了解模型的构建方式,并确保它是根据前述规格构建的。这可以通过使用model.summary()命令来完成。

  2. 接下来,使用以下命令编译模型:

model.compile(loss='categorical_crossentropy', optimizer=Adam(), metrics=        ['accuracy'])
  1. 为了防止过拟合并进一步提高模型的准确性,实现某种形式的数据增强。在这一步中,图像将被剪切、水平和垂直轴上移动、放大和旋转。模型学习和识别这些异常的能力将决定模型的鲁棒性。使用以下命令增强数据:
# 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)
  1. 最后,使用以下命令进行数据增强后拟合和评估模型:
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))

工作原理...

功能如下:

  1. 通过使用 sequential 函数,定义了一个九层卷积神经网络,每一层执行以下功能:

  2. 第一层是一个具有 16 个神经元的卷积层,并对输入张量/矩阵进行卷积。特征图的大小被定义为一个 3 x 3 的矩阵。由于神经网络需要知道期望的输入类型,因此需要为第一层指定输入形状。由于所有图像都被裁剪为 128 x 150 像素的大小,这也将是网络第一层定义的输入形状。在这一层中使用的激活函数是修正线性单元relu)。

  3. 网络的第二层(第一个隐藏层)是另一个具有 16 个神经元的卷积层。同样,这一层的激活函数将使用relu

  4. 网络的第三层(第二个隐藏层)是一个具有 2 x 2 池大小的最大池化层。这一层的功能是提取通过前两层卷积学习到的所有有效特征,并减小包含所有学习到的特征的矩阵的大小。卷积无非是特征图和输入矩阵(在我们的情况下是图像)之间的矩阵乘法。网络将存储卷积过程中产生的结果值。这些存储的值中的最大值将定义输入图像中的某个特征。这些最大值将由最大池化层保留,该层将省略不相关的特征。

  5. 网络的第四层(第三个隐藏层)是另一个 3 x 3 的特征图的卷积层。在这一层中使用的激活函数将再次是relu函数。

  6. 网络的第五层(第四个隐藏层)是一个具有 2 x 2 池大小的最大池化层。

  7. 网络的第六层(第五个隐藏层)是一个扁平化层,它将包含所有学习到的特征(以数字形式存储)的矩阵转换为单行,而不是多维矩阵。

    1. 网络中的第七层(第六个隐藏层)是一个具有 512 个神经元和relu激活的密集层。每个神经元基本上会处理特定的权重和偏差,这无非是对特定图像中所有学习到的特征的表示。这是为了通过在密集层上使用softmax分类器轻松对图像进行分类。
  8. 网络中的第八层(第七个隐藏层)是一个具有 0.25 或 25%的丢弃概率的丢弃层。这一层将在训练过程中随机丢弃 25%的神经元,并通过鼓励网络使用许多替代路径来防止过拟合。

  9. 网络中的最后一层是一个只有 10 个神经元和softmax分类器的密集层。这是第八个隐藏层,也将作为网络的输出层。

  10. 在定义模型后的输出必须如下截图所示:

  1. 在打印model.summary()函数时,必须看到如下截图中的输出:

  2. 该模型使用分类交叉熵进行编译,这是一个函数,用于在将信息从一个层传输到后续层时测量和计算网络的损失。模型将使用 Keras 框架中的Adam()优化器函数,它基本上会指导网络在学习特征时如何优化权重和偏差。model.compile()函数的输出必须如下截图所示:

  1. 由于神经网络非常密集,总图像数量仅为 3,240,因此我们设计了一种方法来防止过拟合。这是通过执行数据增强从训练集生成更多图像来完成的。在这一步中,图像是通过ImageDataGenerator()函数生成的。该函数通过以下方式对训练和测试集进行图像增强:
  • 旋转它们

  • 剪切它们

  • 移动宽度,基本上是扩大图像

  • 在水平轴上移动图像

  • 在垂直轴上移动图像

前述函数的输出必须如下截图所示:

  1. 最后,模型在训练 5 个时期后适应数据并进行评估。我们获得的输出如下截图所示:

  1. 如您所见,我们获得了 98.46%的准确性,导致错误率为 1.54%。这相当不错,但是卷积网络已经进步了很多,我们可以通过调整一些超参数或使用更深的网络来改进这个错误率。

还有更多...

使用 12 层更深的 CNN(一个额外的卷积和一个额外的最大池化层)将准确性提高到 99.07%,如下截图所示:

在模型构建过程中每两层之后使用数据归一化后,我们进一步将准确性提高到 99.85%,如下截图所示:

您可能会得到不同的结果,但可以随意运行几次训练步骤。以下是您可以采取的一些步骤,以便在将来实验网络以更好地了解它:

  • 尝试更好地调整超参数,并实施更高的丢失百分比,看看网络的响应如何。

  • 当我们尝试使用不同的激活函数或更小(不太密集)的网络时,准确性大大降低。

  • 此外,更改特征图和最大池化层的大小,并查看这如何影响训练时间和模型准确性。

  • 尝试在不太密集的 CNN 中包含更多的神经元并进行调整以提高准确性。这也可能导致更快的网络,训练时间更短。

  • 使用更多的训练数据。探索其他在线存储库,找到更大的数据库来训练网络。当训练数据的大小增加时,卷积神经网络通常表现更好。

另请参见

以下已发表的论文是了解卷积神经网络的更好资源。它们可以作为进一步阅读,以更多地了解卷积神经网络的各种应用:

第十一章:使用 Word2Vec 创建和可视化单词向量

在本章中,我们将涵盖以下内容:

  • 获取数据

  • 导入必要的库

  • 准备数据

  • 构建和训练模型

  • 进一步可视化

  • 进一步分析

介绍

在对文本数据进行神经网络训练并使用 LSTM 单元生成文本之前,重要的是要了解文本数据(如单词、句子、客户评论或故事)在输入神经网络之前是如何转换为单词向量的。本章将描述如何将文本转换为语料库,并从语料库生成单词向量,这样就可以使用欧几里得距离计算或余弦距离计算等技术轻松地对相似单词进行分组。

获取数据

第一步是获取一些要处理的数据。在本章中,我们需要大量的文本数据,将其转换为标记并进行可视化,以了解神经网络如何根据欧几里得距离和余弦距离对单词向量进行排名。这是了解不同单词如何相互关联的重要步骤。反过来,这可以用于设计更好、更高效的语言和文本处理模型。

准备工作

考虑以下内容:

  • 模型的文本数据需要以.txt格式的文件存在,并且您必须确保文件放置在当前工作目录中。文本数据可以是来自 Twitter 动态、新闻动态、客户评论、计算机代码或以.txt格式保存在工作目录中的整本书。在我们的案例中,我们已经将《权力的游戏》书籍作为模型的输入文本。然而,任何文本都可以替换书籍,并且相同的模型也会起作用。

  • 许多经典文本已不再受版权保护。这意味着您可以免费下载这些书籍的所有文本,并将它们用于实验,比如创建生成模型。获取不再受版权保护的免费书籍的最佳途径是 Project Gutenberg(www.gutenberg.org/)。

操作方法...

步骤如下:

  1. 首先访问 Project Gutenberg 网站并浏览您感兴趣的书籍。单击书籍,然后单击 UTF-8,这样您就可以以纯文本格式下载书籍。链接如下截图所示:

Project Gutenberg 数据集下载页面

  1. 点击纯文本 UTF-8 后,您应该会看到一个类似以下截图的页面。右键单击页面,然后单击“另存为...”接下来,将文件重命名为您选择的任何名称,并保存在您的工作目录中:

  1. 现在,您应该在当前工作目录中看到一个带有指定文件名的.txt文件。

  2. Project Gutenberg 为每本书添加了标准的页眉和页脚;这不是原始文本的一部分。在文本编辑器中打开文件,然后删除页眉和页脚。

工作原理...

功能如下:

  1. 使用以下命令检查当前工作目录:pwd

  2. 可以使用cd命令更改工作目录,如下截图所示:

  1. 请注意,在我们的案例中,文本文件包含在名为USF的文件夹中,因此这被设置为工作目录。您可以类似地将一个或多个.txt文件存储在工作目录中,以便作为模型的输入。

  2. UTF-8 指定了文本文件中字符的编码类型。UTF-8代表Unicode 转换格式8表示它使用8 位块来表示一个字符。

  3. UTF-8 是一种折衷的字符编码,可以像 ASCII 一样紧凑(如果文件只是纯英文文本),但也可以包含任何 Unicode 字符(文件大小会略有增加)。

  4. 不需要文本文件以 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 环境中。这些库将使我们的任务变得更加容易,因为它们具有现成的可用函数和模型,可以代替我们自己进行操作。这也使得代码更加简洁和可读。

做好准备

以下库和依赖项将需要创建单词向量和绘图,并在 2D 空间中可视化 n 维单词向量:

  • 未来

  • codecs

  • glob

  • multiprocessing

  • os

  • pprint

  • re

  • nltk

  • Word2Vec

  • sklearn

  • numpy

  • matplotlib

  • pandas

  • seaborn

如何做...

步骤如下:

  1. 在 Jupyter 笔记本中键入以下命令以导入所有所需的库:
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
  1. 您应该看到一个类似以下截图的输出:

  1. 接下来,使用以下命令导入stopwordspunkt库:
nltk.download("punkt")
nltk.download("stopwords")
  1. 您看到的输出必须看起来像以下截图:

它是如何工作的...

本节将描述用于此配方的每个库的目的。

  1. future库是 Python 2 和 Python 3 之间的缺失链接。它充当两个版本之间的桥梁,并允许我们使用两个版本的语法。

  2. codecs库将用于对文本文件中所有单词进行编码。这构成了我们的数据集。

  3. Regex 是用于快速查找文件的库。glob函数允许快速高效地在大型数据库中搜索所需的文件。

  4. multiprocessing库允许我们执行并发,这是一种运行多个线程并使每个线程运行不同进程的方式。这是一种通过并行化使程序运行更快的方式。

  5. os库允许与操作系统进行简单交互,如 Mac、Windows 等,并执行诸如读取文件之类的功能。

  6. pprint库提供了一种能够以可用作解释器输入的形式对任意 Python 数据结构进行漂亮打印的功能。

  7. re模块提供了类似于 Perl 中的正则表达式匹配操作。

  8. NLTK 是一个自然语言工具包,能够在非常简短的代码中对单词进行标记。当输入整个句子时,nltk函数会分解句子并输出每个单词的标记。基于这些标记,单词可以被组织成不同的类别。NLTK 通过将每个单词与一个名为词汇表的巨大预训练单词数据库进行比较来实现这一点。

  9. Word2Vec是 Google 的模型,它在一个巨大的单词向量数据集上进行了训练。它将语义上相似的单词归为一类。这将是本节中最重要的库。

  10. sklearn.manifold允许使用t-分布随机邻居嵌入t-SNE)技术对数据集进行降维。由于每个单词向量是多维的,我们需要某种形式的降维技术,将这些单词的维度降低到一个较低的维度空间,以便在 2D 空间中进行可视化。

还有更多...

Numpy是常用的math库。Matplotlib是我们将利用的plotting库,而pandas通过允许轻松重塑、切片、索引、子集和操纵数据,提供了很大的灵活性。

Seaborn库是另一个统计数据可视化库,我们需要与matplotlib一起使用。PunktStopwords是两个数据处理库,简化了诸如将语料库中的文本拆分为标记(即通过标记化)和删除stopwords等任务。

另请参阅

有关使用的一些库的更多信息,请访问以下链接:

准备数据

在将数据馈送到模型之前,需要执行一些数据预处理步骤。本节将描述如何清理数据并准备数据,以便将其馈送到模型中。

准备就绪

首先将所有.txt文件中的文本转换为一个大的语料库。这是通过从每个文件中读取每个句子并将其添加到一个空语料库中来完成的。然后执行一些预处理步骤,以删除诸如空格、拼写错误、stopwords等不规则性。然后必须对清理后的文本数据进行标记化,并通过循环将标记化的句子添加到一个空数组中。

如何做...

步骤如下:

  1. 键入以下命令以在工作目录中搜索.txt文件并打印找到的文件的名称:
book_names = sorted(glob.glob("./*.txt"))
print("Found books:")
book_names

在我们的案例中,工作目录中保存了五本名为got1got2got3got4got5的书籍。

  1. 创建一个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()
  1. 执行前面步骤中的代码,应该会产生以下截图中的输出:

  1. 使用以下命令从punkt加载英语 pickletokenizer
tokenizer = nltk.data.load('tokenizers/punkt/english.pickle')
  1. 使用以下命令将整个corpus标记化为句子:
raw_sentences = tokenizer.tokenize(corpus)
  1. 以以下方式定义将句子拆分为其组成单词并删除不必要字符的函数:
def sentence_to_wordlist(raw):
     clean = re.sub("[^a-zA-Z]"," ", raw)
     words = clean.split()
     return words
  1. 将每个句子的每个单词标记化的原始句子全部添加到一个新的句子数组中。使用以下代码完成:
sentences = []
for raw_sentence in raw_sentences:
  if len(raw_sentence) > 0:
  sentences.append(sentence_to_wordlist(raw_sentence))
  1. 从语料库中打印一个随机句子,以直观地查看tokenizer如何拆分句子并从结果创建一个单词列表。使用以下命令完成:
print(raw_sentences[50])
print(sentence_to_wordlist(raw_sentences[50]))
  1. 使用以下命令计算数据集中的总标记数:
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 个标记。这在以下截图中有所说明:

功能如下:

  1. 使用 NLTK 中的预训练tokenizer通过将每个句子计为一个标记来标记整个语料库。每个标记化的句子都被添加到变量raw_sentences中,该变量存储了标记化的句子。

  2. 接下来,常见的停用词被移除,并且通过将每个句子分割成单词来清理文本。

  3. 打印一个随机句子以及其单词列表,以了解其工作原理。在我们的案例中,我们选择打印raw_sentences数组中的第 50 个句子。

  4. 计算并打印句子数组中的总标记数(在我们的案例中是句子)。在我们的案例中,我们看到tokenizer创建了 1,110,288 个标记。

还有...

有关将段落和句子标记化的更多信息,请访问以下链接:

另请参阅

有关正则表达式工作原理的更多信息,请访问以下链接:

stackoverflow.com/questions/13090806/clean-line-of-punctuation-and-split-into-words-python

构建和训练模型

一旦我们将文本数据以数组形式的标记输入到模型中,我们就能够为模型定义一些超参数。本节将描述如何执行以下操作:

  • 声明模型超参数

  • 使用Word2Vec构建模型

  • 在准备好的数据集上训练模型

  • 保存和检查点训练好的模型

准备工作

需要声明的一些模型超参数包括以下内容:

  • 生成的单词向量的维度

  • 最小词数阈值

  • 在训练模型时运行的并行线程数

  • 上下文窗口长度

  • 降采样(对于频繁出现的单词)

  • 设置种子

一旦前面提到的超参数被声明,就可以使用Gensim库中的Word2Vec函数构建模型。

如何做...

步骤如下:

  1. 使用以下命令声明模型的超参数:
num_features = 300
min_word_count = 3
num_workers = multiprocessing.cpu_count()
context_size = 7
downsampling = 1e-3
seed = 1
  1. 使用声明的超参数,使用以下代码行构建模型:
got2vec = w2v.Word2Vec(
    sg=1,
    seed=seed,
    workers=num_workers,
    size=num_features,
    min_count=min_word_count,
    window=context_size,
    sample=downsampling
)
  1. 使用标记化的句子构建模型的词汇表,并通过所有标记进行迭代。这是使用以下方式的build_vocab函数完成的:
got2vec.build_vocab(sentences,progress_per=10000, keep_raw_vocab=False, trim_rule=None)
  1. 使用以下命令训练模型:
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)
  1. 如果尚不存在,请创建一个名为 trained 的目录。使用以下命令保存和检查点trained模型:
if not os.path.exists("trained"):
     os.makedirs("trained")
got2vec.wv.save(os.path.join("trained", "got2vec.w2v"), ignore=[])
  1. 要在任何时候加载保存的模型,请使用以下命令:
got2vec = w2v.KeyedVectors.load(os.path.join("trained", "got2vec.w2v"))

它是如何工作的...

功能如下:

  1. 模型参数的声明不会产生任何输出。它只是在内存中留出空间来存储变量作为模型参数。以下截图描述了这个过程:

  2. 模型是使用前述超参数构建的。在我们的案例中,我们将模型命名为got2vec,但模型可以根据您的喜好进行命名。模型定义如下截图所示:

  3. 在模型上运行build_vocab命令应该会产生如下截图所示的输出:

  4. 通过定义以下截图中所见的参数来训练模型:

  5. 上述命令产生如下截图所示的输出:

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

还有更多...

考虑以下内容:

  • 在我们的案例中,我们注意到build_vocab函数从 1,110,288 个单词的列表中识别出 23,960 个不同的单词类型。然而,对于不同的文本语料库,这个数字会有所不同。

  • 每个单词都由一个 300 维向量表示,因为我们已经声明维度为 300。增加这个数字会增加模型的训练时间,但也会确保模型很容易地泛化到新数据。

  • 发现 1e3 的下采样率是一个很好的比率。这是为了让模型知道何时对频繁出现的单词进行下采样,因为它们在分析时并不重要。这些单词的例子包括 this, that, those, them 等。

  • 设置一个种子以使结果可重现。设置种子也使得调试变得更容易。

  • 由于模型不是很复杂,使用常规 CPU 计算训练模型大约需要 30 秒。

  • 当检查点被检查时,模型被保存在工作目录内的trained文件夹下。

另请参阅

有关Word2Vec模型和 Gensim 库的更多信息,请访问以下链接:radimrehurek.com/gensim/models/word2vec.html

radimrehurek.com/gensim/models/word2vec.html

进一步可视化

本节将描述如何压缩所有训练过的单词的维度,并将其全部放入一个巨大的矩阵以进行可视化。由于每个单词都是一个 300 维的向量,所以需要将其降低到更低的维度,以便我们在 2D 空间中进行可视化。

准备工作

一旦模型在训练后保存和检查点后,开始将其加载到内存中,就像在上一节中所做的那样。在本节中将使用的库和模块有:

  • tSNE

  • pandas

  • Seaborn

  • numpy

如何做...

步骤如下:

  1. 使用以下命令压缩 300 维单词向量的维度:
 tsne = sklearn.manifold.TSNE(n_components=2, random_state=0)
  1. 将所有单词向量放入一个巨大的矩阵(命名为all_word_vectors_matrix),并使用以下命令查看它:
 all_word_vectors_matrix = got2vec.wv.syn0
 print (all_word_vectors_matrix)
  1. 使用以下命令将所有学习到的表示拟合到二维空间中:
 all_word_vectors_matrix_2d =  tsne.fit_transform(all_word_vectors_matrix)
  1. 使用以下代码收集所有单词向量及其相关单词:
 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"]
)
  1. 使用以下命令可以获取前十个点的XY坐标以及相关单词:
points.head(10)
  1. 使用以下命令绘制所有点:
sns.set_context("poster")
points.plot.scatter("x", "y", s=10, figsize=(15, 15))
  1. 可以放大绘图图表的选定区域以进行更仔细的检查。通过使用以下函数对原始数据进行切片来实现这一点:
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)
  1. 使用以下命令绘制切片数据。切片数据可以被视为原始所有数据点的放大区域:
plot_region(x_bounds=(20.0, 25.0), y_bounds=(15.5, 20.0))

工作原理...

功能如下:

  1. t-SNE 算法是一种非线性降维技术。计算机在计算过程中很容易解释和处理许多维度。然而,人类一次只能可视化两到三个维度。因此,当试图从数据中得出见解时,这些降维技术非常有用。

  2. 将 300 维向量应用 t-SNE 后,我们能够将其压缩为只有两个维度来绘制和查看。

  3. 通过将 n_components 指定为 2,我们让算法知道它必须将数据压缩到二维空间。完成此操作后,我们将所有压缩后的向量添加到一个名为 all_word_vectors_matrix 的巨大矩阵中,如下图所示:

  4. t-SNE 算法需要对所有这些单词向量进行训练。在常规 CPU 上,训练大约需要五分钟。

  5. 一旦 t-SNE 完成对所有单词向量的训练,它会为每个单词输出 2D 向量。可以通过将它们全部转换为数据框架来将这些向量绘制为点。如下图所示完成此操作:

  6. 我们看到上述代码生成了许多点,其中每个点代表一个单词及其 X 和 Y 坐标。检查数据框架的前二十个点时,我们看到如下图所示的输出:

  1. 通过使用 all_word_vectors_2D 变量绘制所有点,您应该会看到类似以下截图的输出:

  2. 上述命令将生成从整个文本生成的所有标记或单词的绘图,如下图所示:

  3. 我们可以使用 plot_region 函数来放大绘图中的某个区域,以便我们能够实际看到单词及其坐标。这一步骤如下图所示:

  4. 通过设置 x_boundsy_bounds 的值,可以可视化绘图的放大区域,如下图所示:

  5. 可以通过改变 x_boundsy_bounds 的值来可视化相同绘图的不同区域,如下两个截图所示:

另请参阅

还有以下额外的要点:

code.google.com/archive/p/word2vec/

  • 使用以下链接来探索 Seaborn 库的不同功能:

seaborn.pydata.org/

进一步分析

本节将描述可在可视化后对数据执行的进一步分析。例如,探索不同单词向量之间的余弦距离相似性。

准备工作

以下链接是关于余弦距离相似性工作原理的出色博客,并讨论了一些涉及的数学内容:

blog.christianperone.com/2013/09/machine-learning-cosine-similarity-for-vector-space-models-part-iii/

如何做...

考虑以下内容:

  • 可以使用 Word2Vec 的不同功能执行各种自然语言处理任务。其中之一是在给定某个单词时找到最语义相似的单词(即具有高余弦相似性或它们之间的欧几里德距离较短的单词向量)。可以使用 Word2Vecmost_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")
  • 上述过程如下截图所示:

  • 结果如下:

  • 如本节所示,词向量是所有自然语言处理任务的基础。在深入研究更复杂的自然语言处理模型(如循环神经网络和长短期记忆(LSTM)单元)之前,了解它们以及构建这些模型所涉及的数学是很重要的。

另请参阅

可以进一步阅读有关使用余弦距离相似性、聚类和其他机器学习技术在排名词向量中的应用的内容,以更好地理解。以下是一些有用的关于这个主题的已发表论文的链接:

第十二章:使用 Keras 创建电影推荐引擎

本章将涵盖以下配方:

  • 下载 MovieLens 数据集

  • 操作和合并 MovieLens 数据集

  • 探索 MovieLens 数据集

  • 为深度学习流水线准备数据集

  • 使用 Keras 应用深度学习流水线

  • 评估推荐引擎的准确性

介绍

2006 年,一家小型 DVD 租赁公司着手使他们的推荐引擎提高 10%。那家公司是 Netflix,Netflix 奖值 100 万美元。这场比赛吸引了来自世界各地一些最大科技公司的许多工程师和科学家。获胜参与者的推荐引擎是通过机器学习构建的。Netflix 现在是流媒体数据和向其客户推荐下一步应该观看的内容方面的领先科技巨头之一。

如今,评分随处可见,无论你在做什么。如果你正在寻找去新餐馆吃饭的建议,在线订购服装,观看当地影院的新电影,或者在电视或在线上观看新系列,很可能有一个网站或移动应用会给你一些类型的评分以及对你要购买的产品或服务的反馈。正是因为这种反馈的迅速增加,推荐算法在过去几年变得更加受欢迎。本章将专注于使用深度学习库 Keras 为用户构建电影推荐引擎。

下载 MovieLens 数据集

有一个伟大的研究实验室,它始于 1992 年,位于明尼阿波利斯,明尼苏达州,名为GroupLens,专注于推荐引擎,并且慷慨地从 MovieLens 网站上收集了数百万行数据。我们将使用它的数据集作为我们推荐引擎模型的数据来源。

准备工作

MovieLens 数据集存放在 GroupLens 的以下网站上:

grouplens.org/datasets/movielens/.

重要的是要注意,我们将使用的数据集将直接来自他们的网站,而不是来自第三方中介或存储库。此外,有两个不同的数据集可供我们查询:

  • 推荐用于新研究

  • 推荐用于教育和开发

使用这个数据集的目的纯粹是为了教育目的,因此我们将从网站的教育和开发部分下载数据。教育数据仍然包含大量行数,因为它包含了 10 万个评分,如下截图所示:

此外,该数据集包含了在 1995 年 1 月 9 日至 2015 年 3 月 31 日期间收集的 600 多个匿名用户的信息。该数据集最后更新于 2017 年 10 月。

F Maxwell Harper 和 Joseph A Konstan,2015 年。The MovieLens Datasets: History and Context。ACM 交互智能系统交易 (TiiS) 5, 4, Article 19 (2015 年 12 月),19 页。DOI: dx.doi.org/10.1145/2827872

如何做...

本节将涵盖下载和解压 MovieLens 数据集:

  1. 下载较小的 MovieLens 数据集的研究版本,可在以下网站公开下载:grouplens.org/datasets/movielens/latest/.

  2. 下载名为ml-latest-small.zipZIP文件到我们的一个本地文件夹中,如下截图所示:

  1. 当下载并解压ml-latest-small.zip后,应提取以下四个文件:

  2. links.csv

  3. movies.csv

  4. ratings.csv

  5. tags.csv

  6. 执行以下脚本开始我们的SparkSession

spark = SparkSession.builder \
         .master("local") \
         .appName("RecommendationEngine") \
         .config("spark.executor.memory", "6gb") \
         .getOrCreate()
  1. 通过执行以下脚本确认以下六个文件可供访问:
import os
os.listdir('ml-latest-small/')
  1. 使用以下脚本将每个数据集加载到 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')
  1. 通过执行以下脚本来确认每个数据集的行数:
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 数据集中每个数据集中的字段。请看以下步骤:

  1. 这些数据集都包含在压缩文件ml-latest-small.zip中,其中ratings.csv数据集将作为我们的数据的伪事实表,因为它包含了每部电影的交易。数据集ratings中有四个列名,如下截图所示:

  1. 该数据集显示了每个用户在其时间内选择的评分,从最早的评分到最新的评分。评分的范围可以从 0.5 到 5.0 星,如下截图中的userId = 1所示:

  1. tags数据集包含一个标签列,其中包含用户用于描述特定电影 ID 的特定单词或短语。如下截图所示,用户 15 对桑德拉·布洛克在她的一部电影中并不特别喜欢:

  1. movies数据集主要是电影类型的查找表。有 19 种唯一的类型可以与电影相关联;但是,重要的是要注意,一部电影可以同时与多种类型相关联,如下截图所示:

  1. 最后一个数据集是links数据集,它也充当查找表。它将 MovieLens 中的电影与流行电影数据库网站(如www.imdb.comwww.themoviedb.org)上可用的数据连接起来。IMDB 的链接在名为 imdbId 的列下,而 MovieDB 的链接在名为 tmdbId 的列下,如下截图所示:

  1. 在完成之前,确认我们确实从所有数据集中获得了预期的行数总是一个好主意。这有助于确保我们在将文件上传到笔记本时没有遇到任何问题。我们应该期望在ratings数据集中看到大约 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 奖的更多信息,请访问以下网站:

www.netflixprize.com/

操作和合并 MovieLens 数据集

我们目前有四个不同的数据集,但最终我们希望将其减少到一个数据集。本章将重点介绍如何将我们的数据集减少到一个。

准备工作

本节不需要导入 PySpark 库,但了解 SQL 连接将很有帮助,因为我们将探索多种连接数据框的方法。

如何做...

本节将介绍在 PySpark 中连接数据框的以下步骤:

  1. 执行以下脚本将ratings中的所有字段名重命名,将_1附加到名称的末尾:
for i in ratings.columns:
     ratings = ratings.withColumnRenamed(i, i+'_1') 
  1. 执行以下脚本将movies数据集与ratings数据集进行内连接,创建一个名为temp1的新表:
temp1 = ratings.join(movies, ratings.movieId_1 == movies.movieId, how = 'inner')
  1. 执行以下脚本将temp1数据集与links数据集进行内连接,创建一个名为temp2的新表:
temp2 = temp1.join(links, temp1.movieId_1 == links.movieId, how = 'inner')
  1. 通过左连接temp2tags,创建我们的最终组合数据集mainDF,使用以下脚本:
mainDF = temp2.join(tags, (temp2.userId_1 == tags.userId) & (temp2.movieId_1 == tags.movieId), how = 'left')
  1. 通过执行以下脚本,仅选择我们最终mainDF数据集所需的列:
mainDF = mainDF.select('userId_1',
                       'movieId_1',
                       'rating_1',
                       'title', 
                       'genres', 
                       'imdbId',
                       'tmdbId', 
                       'timestamp_1').distinct()

工作原理...

本节将介绍我们连接表的设计过程,以及将保留哪些最终列:

  1. 如前一节所述,评分数据框将作为我们的事实表,因为它包含每个用户随时间的所有主要评分交易。评分中的列将在与其他三个表的每个后续连接中使用,并且为了保持列的唯一性,我们将在每个列名的末尾附加“_1”,如下图所示:

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

在处理数据集之间的连接时,通常会遇到三种类型的连接:内连接、左连接和右连接。内连接只在数据集 1 和数据集 2 的连接键都可用时才产生结果集。左连接将产生数据集 1 的所有行,以及数据集 2 中匹配键的行。右连接将产生数据集 2 的所有行,以及数据集 1 中匹配键的行。在本节的后面,我们将探讨 Spark 中的 SQL 连接。

  1. 有趣的是,我们新创建的数据集 mainDF 有 100,441 行,而不是原始评分数据集中的 100,004 行,以及 temp1 和 temp2。有 437 个评分有多个标签与之关联。此外,我们可以看到大多数 ratings_1 都有一个空的 tag 值与之关联,如下图所示:

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

  1. 另外,我们已经确定 tags 字段相对无用,因为有超过 99k 个空值。因此,我们将使用数据框架上的select()函数,只提取我们推荐引擎所需的八列。然后,我们可以确认我们的最终新数据框mainDF有正确的行数,即 100,004 行,如下截图所示:

还有更多...

虽然我们使用 PySpark 在 Spark 数据框架中使用函数进行了连接,但我们也可以通过将数据框注册为临时表,然后使用sqlContext.sql()进行连接:

  1. 首先,我们将使用creatorReplaceTempView()将每个数据集注册为临时视图,如下脚本所示:
movies.createOrReplaceTempView('movies_')
links.createOrReplaceTempView('links_')
ratings.createOrReplaceTempView('ratings_')
  1. 接下来,我们将编写我们的 SQL 脚本,就像我们在任何其他关系数据库中所做的那样,使用sqlContext.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
"""
)
  1. 最后,我们可以对新数据框 mainDF_SQL 进行分析,并观察它看起来与我们的其他数据框 mainDF 相同,同时保持完全相同的行数,如下图所示:

另请参阅

要了解有关 Spark 中 SQL 编程的更多信息,请访问以下网站:

spark.apache.org/docs/latest/sql-programming-guide.html

探索 MovieLens 数据集

在进行任何建模之前,熟悉源数据集并进行一些探索性数据分析是很重要的。

准备工作

我们将导入以下库来帮助可视化和探索 MovieLens 数据集:matplotlib

如何做...

本节将介绍分析 MovieLens 数据库中电影评分的步骤:

  1. 通过执行以下脚本,检索rating_1列的一些摘要统计:
mainDF.describe('rating_1').show
  1. 通过执行以下脚本构建评分分布的直方图:
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()
  1. 执行以下脚本以在电子表格数据框中查看直方图的值:
mainDF.groupBy(['rating_1']).agg({'rating_1':'count'})\
 .withColumnRenamed('count(rating_1)', 'Row Count').orderBy(["Row Count"],ascending=False)\
 .show()
  1. 通过执行以下脚本,可以将用户对评分的唯一计数存储为数据框userId_frequency
userId_frequency = mainDF.groupBy(['userId_1']).agg({'rating_1':'count'})\
         .withColumnRenamed('count(rating_1)', '# of Reviews').orderBy(["# of             Reviews"],ascending=False)
  1. 使用以下脚本绘制userID_frequency的直方图:
userId_frequency.select('# of Reviews').toPandas().hist(figsize=(16, 6), grid=True)
plt.title('Histogram of User Ratings')
plt.show()

工作原理...

本节将讨论 MovieLens 数据库中评分和用户活动的分布。请查看以下步骤:

  1. 我们可以看到用户平均电影评分约为 3.5,如下截图所示:

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

  1. 直方图背后的数据再次显示,用户最频繁选择 4.0,其次是 3.0,然后是 5.0。此外,有趣的是用户更有可能给出 0.0 级别的评分,而不是 0.5 级别的评分,如下截图所示:

  1. 我们可以查看用户对评分的分布,并且看到一些用户非常活跃地表达对他们所看电影的意见。例如,匿名用户 547 发布了 2391 条评分,如下截图所示:

  1. 然而,当我们查看用户进行评分选择的分布时,我们确实看到,虽然有一些用户单独进行了一千多次选择,但绝大多数用户的选择次数少于 250 次,如下截图所示:

  1. 在上一个截图中直方图的分布呈现长尾格式,表明大多数发生在直方图中心之外。这表明绝大多数评分是由少数用户定义的。

还有更多...

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

如何做...

本节将介绍准备数据集用于深度学习流水线的以下步骤:

  1. 执行以下脚本清理列名:
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')
  1. “评分”列目前被分为 0.5 的增量。使用以下脚本调整评分,使其四舍五入为整数:
import pyspark.sql.functions as F
mainDF = mainDF.withColumn("rating", F.round(mainDF["rating"], 0))
  1. 根据“流派”标签的频率,将“流派”列从字符串转换为名为 genreCount 的索引,如下脚本所示:
from pyspark.ml.feature import StringIndexer
string_indexer = StringIndexer(inputCol="genres", outputCol="genreCount")
mainDF = string_indexer.fit(mainDF).transform(mainDF)
  1. 使用以下脚本简化我们的数据框:
mainDF = mainDF.select('rating', 'userid', 'movieid', 'imdbid', 'tmdbid', 'timestamp', 'genreCount')
  1. 使用以下脚本将 mainDF 分割为用于模型训练的训练集和测试集:
trainDF, testDF = mainDF.randomSplit([0.8, 0.2], seed=1234)
  1. 使用以下脚本将我们的两个 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()
  1. 使用以下脚本将 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)

它是如何工作的...

本节将解释我们如何准备数据集用于深度学习流水线:

  1. 在深度学习流水线内使用时,最好在流水线接收数据之前清理列名和列的顺序。重命名列标题后,我们可以查看更新后的列,如下脚本所示:

  1. 对“评分”列进行一些操作,将 0.5 增量的值四舍五入为下一个最接近的整数。这将有助于我们在 Keras 中进行多类分类时,将“评分”分为六个类别,而不是 11 个类别。

  2. 为了在深度学习模型中使用电影流派类型,我们需要将“流派”的字符串值转换为数字标签。最常见的流派类型将获得值 0,下一个最常见的类型将增加值。在以下截图中,我们可以看到 Good Will Hunting 有两种与之关联的流派(Drama | Romance),这是第四种最常见的 genreCount,值为 3.0:

  1. 流派列对于深度模型不再需要,因为它将被 genreCount 列替换,如下截图所示:

  1. 我们的主数据框 mainDF 被分成 trainDF 和 testDF,用于建模、训练和评估,采用 80/20 的分割。所有三个数据框的行数可以在以下截图中看到:

  1. 数据被传递到 Keras 深度学习模型中,使用矩阵而不是数据框。因此,我们的训练和测试数据框被转换为 numpy 数组,并分为 x 和 y。选择用于 xtrain_array 和 xtest_array 的特征是 userid、movieid 和 genreCount。这些是我们用来确定用户可能评分的唯一特征。我们放弃了 imdbid 和 tmdbid,因为它们直接与 movieid 相关,因此不会提供任何额外的价值。时间戳将被删除以过滤掉与投票频率相关的任何偏见。最后,ytest_array 和 ytrain_array 将包含评分的标签值。所有四个数组的形状可以在以下截图中看到:

还有更多...

虽然ytrain_arrayytest_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 进行深度学习模型:

  1. keras中导入以下库以构建Sequential模型,使用以下脚本:
from keras.models import Sequential
from keras.layers import Dense, Activation
  1. 使用以下脚本配置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'])
  1. 我们fit和训练模型,并将结果存储到名为accuracy_history的变量中,使用以下脚本:
accuracy_history = model.fit(xtrain_array, ytrain_OHE, epochs=20, batch_size=32)

工作原理...

本节介绍了应用于数据集的 Keras 模型的配置,以基于所选特征预测评分。

  1. 在 Keras 中,Sequential模型只是层的线性组合,包括以下内容:Dense用于在深度神经网络中定义全连接层的层类型。最后,Activation用于将特征的输入转换为可以用作预测的输出。在神经网络中可以使用许多类型的激活函数;但是,在本章中,我们将使用relusoftmax

  2. Sequential模型配置为包括三个Dense层:

  3. 第一层的input_dim设置为xtrain_array中的特征数量。shape特征拉取值为 3,使用xtrain_array.shape[1]。此外,第一层设置为神经网络的第一层有32个神经元。最后,三个输入参数使用relu激活函数激活。只有第一层需要显式定义输入维度。在后续层中不需要,因为它们将能够从前一层推断出维度的数量。

  4. Sequential模型中的第二层在神经网络中有10个神经元,并且激活函数设置为relu。在神经网络过程中早期使用修正线性单元是有效的,因为它们在训练过程中是有效的。这是因为方程的简单性,任何小于 0 的值都被舍弃,而其他激活函数则不是这样。

  5. Sequential模型的第三层需要根据从 0 到 5 的每种可能的评分情况生成六个输出。这需要将输出设置为ytrain_OHE.shape[1]的值。输出使用softmax函数生成,这在神经网络的末端通常是这样,因为它对分类非常有用。此时,我们正在寻找对 0 到 5 之间的值进行分类。

  6. 一旦层被指定,我们必须compile模型。

  7. 我们使用adam来优化模型,它代表自适应矩估计。优化器非常适合配置模型用于调整和更新神经网络权重的梯度下降的学习率。adam是一种流行的优化器,据说它结合了其他常见优化器的一些最佳特性。

  8. 我们的损失函数设置为categorical_crossentroy,通常用于预测多类分类时使用。损失函数评估模型在训练过程中的性能。

  9. 我们使用训练特征xtrain_array和训练标签ytrain_OHE来训练模型。模型在 20 个时期内进行训练,每次批量大小设置为 32。每个时期的模型输出accuracyloss都被记录在一个名为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()

脚本的输出可以在以下截图中看到:

看起来在第二个时期之后,模型的损失和准确度都稳定下来了。

另请参阅

要了解有关使用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 指标的模型性能的更多信息,请访问以下网站:

keras.io/metrics/

第十三章:在 Spark 上使用 TensorFlow 进行图像分类

本章将涵盖以下内容:

  • 下载 30 张梅西和罗纳尔多的图像

  • 配置 PySpark 安装与深度学习包

  • 将图像加载到 PySpark 数据框中

  • 理解迁移学习

  • 创建图像分类训练管道

  • 评估模型性能

  • 微调模型参数

介绍

在过去的几年里,图像识别软件需求不断增加。这种需求与大数据存储的进步不是巧合。Google Photos、Facebook 和 Apple 都利用图像分类软件为用户标记照片。这些公司使用的许多图像识别软件都是基于 TensorFlow 等流行库构建的深度学习模型。本章将通过利用一组图像的训练来学习或识别另一组图像,从而扩展了深度学习的技术。这个概念被称为迁移学习。在本章中,我们将专注于利用迁移学习来识别世界上排名前两位的足球运动员:

  1. 利昂内尔·梅西

  2. 克里斯蒂亚诺·罗纳尔多

看一下这张照片:

下载 30 张梅西和罗纳尔多的图像

在对图像进行任何分类之前,我们必须先从网络上下载我们的足球运动员的图像。

准备工作

浏览器有几个可以批量下载图像的插件。由于 Ubuntu 预装了 Mozilla Firefox 作为浏览器,我们将使用它作为我们首选的浏览器来安装批量图像下载扩展。

如何做...

以下部分解释了如何批量下载图像。看一下这些步骤:

  1. 访问以下网站下载和安装 Firefox 插件:

addons.mozilla.org/en-US/firefox/

  1. 搜索并选择“下载所有图像”插件,如下截图所示:

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

  1. 确认您的安装,因为此插件将需要权限访问您的浏览器下载历史记录,访问所有网站的数据,并向您发送通知。

  2. 完成后,您应该在浏览器右上角看到一个小图片图标,用于下载所有图像,如下截图所示:

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

  1. 接下来,点击“下载所有图像”图标,并按照以下截图中显示的设置下载图像:

  1. 点击保存,然后您将有选项将所有图片下载为.zip文件到本地目录。然后您可以将文件解压缩到一个文件夹中,并浏览所有图像。在我们的示例中,所有图像都已提取到/Home/sparkNotebooks/Ch13/football/ronaldo/,如下截图所示:

  1. 在文件夹中的所有图像中,选择 30 张罗纳尔多的图像,并将它们命名为ronaldo1.jpgronaldo2.jpg....ronaldo30.jpg,如下截图所示:

  1. 重复上述步骤,这次为梅西获取 30 张图像。最终的文件夹结构应该如下所示:

工作原理...

本节解释了插件如何批量下载图像到我们想要的位置的过程:

  1. 批量图像下载软件现在已经在浏览器中集成。我们将使用 Firefox 的 Download all Images 附加组件快速下载 Messi 和 Ronaldo 的图片。

  2. 我们希望在应用程序中指定设置以下载质量较低的图片,因此我们设置了最小阈值为 0 字节,最大阈值为 500 字节,图像类型为jpgjpeg

  3. 最后,我们希望精选出最能代表每个球员的 30 张图片,其中 20 张将作为我们的训练数据集,剩下的 10 张将作为我们的测试数据集。其他所有图片都可以删除。

  4. 所有图片都将通过它们的姓氏和 1 到 30 之间的数字进行标记或标签,用于训练目的。例如,Messi1.jpgMessi2.jpgRonaldo1.jpgRonaldo2.jpg等。

还有更多...

虽然您可以使用您自己下载的图片,使用 Download all Images 下载 Ronaldo 和 Messi 的相同图片,这些图片将在本章用于训练目的。

对于 Messi:

github.com/asherif844/ApacheSparkDeepLearningCookbook/tree/master/CH13/football/messi

对于 Ronaldo:

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 以使用深度学习包:

  1. 打开终端应用程序,输入以下命令:
nano .bashrc.
  1. 滚动到文档底部,查找我们在第一章中创建的sparknotebook()函数。

  2. 更新函数的最后一行。它目前应该看起来像下面这样:

$SPARK_HOME/bin/pyspark.

将其更改为以下内容:

$SPARK_HOME/bin/pyspark --packages databricks:spark-deep-learning:0.1.0-spark2.1-s_2.11.
  1. 一旦配置更改完成,退出文档并执行以下脚本以确认所有必要的更改已保存:
source .bashrc.

它是如何工作的...

以下部分解释了如何修改 PySpark 以整合深度学习包,请查看这些步骤:

  1. 访问 bash 允许我们在命令行上进行配置,如下截图所示:

  1. 在我们的文档末尾,我们可以看到我们的原始函数sparknotebook()仍然完整;但是,我们需要修改它以整合spark-deep-learning包。

  2. 由于这个修改是直接针对 PySpark 的,而不是针对 Python 库的,我们无法使用典型的pip安装将其合并到我们的框架中。相反,我们将修改我们的 PySpark 配置,使其显示如下截图所示:

  1. 我们现在已经配置了我们的 PySpark 安装,以整合包含帮助构建各种解决方案模型的深度学习库。

还有更多...

这个包spark-deep-learningDatabricks管理。 Databricks 是由 Spark 的共同创始人之一 Ali Ghodsi 创立的,并且用于通过统一平台提供托管的 Spark 产品。

另请参阅

要了解为 Spark 开发的其他第三方包,请访问以下网站:

spark-packages.org/

将图像加载到 PySpark 数据框中

现在我们已经准备好开始将图像导入我们的笔记本进行分类。

准备工作

在本节中,我们将使用几个库及其依赖项,这将要求我们在 Ubuntu Desktop 的终端上通过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 数据框:

  1. 使用以下脚本启动spark会话:
spark = SparkSession.builder \
      .master("local") \
      .appName("ImageClassification") \
      .config("spark.executor.memory", "6gb") \
      .getOrCreate()
  1. 从 PySpark 导入以下库以创建数据框,使用以下脚本:
import pyspark.sql.functions as f
import sparkdl as dl
  1. 执行以下脚本,为 Messi 和 Ronaldo 创建两个数据框,使用每个球员的主文件夹位置:
dfMessi = dl.readImages('football/messi/').withColumn('label', f.lit(0))
dfRonaldo = dl.readImages('football/ronaldo/').withColumn('label',             f.lit(1))
  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)
  1. 最后,使用以下脚本将训练数据框和测试数据框合并成一个新的数据框trainDFtestDF
trainDF = trainDFmessi.unionAll(trainDFronaldo)
testDF = testDFmessi.unionAll(testDFronaldo)

它是如何工作的...

以下部分解释了如何加载图像并将其读入 Jupyter 笔记本。看看这些步骤:

  1. 我们总是通过启动 Spark 会话来开始一个 Spark 项目,以设置应用程序名称以及设置 Spark 执行器内存。

  2. 我们导入pyspark.sql.functionssparkdl来帮助基于编码图像构建数据框。当导入sparkdl时,我们看到它在后台使用 TensorFlow,如下图所示:

  1. 使用sparkdl创建数据框,包括三列:文件路径、图像和标签。 Sparkdl 用于导入每个图像并按颜色和形状对其进行编码。此外,使用lit函数将文字值(0 或 1)标记到两个数据框的标签列下,以供训练目的,如下图所示:

  1. 由于每个足球运动员有 30 张图像,因此使用 66.7/33.3 的拆分比例创建 18 张训练图像和 12 张测试图像,如下图所示:

请注意,在使用深度学习时,训练过程中使用的图像越多越好。然而,我们将在本章中尝试证明的一点是,通过将迁移学习作为深度学习的扩展实现,我们可以使用更少的训练样本对图像进行分类,就像本章中 Ronaldo 和 Messi 各只有 30 张图像一样。

  1. 为了构建我们的模型,我们只对创建一个包含 36 张图像的训练数据框感兴趣,以及一个包含剩余 24 张图像的测试数据框。一旦合并数据框,我们可以确认它们的大小是否正确,如下图所示:

还有更多...

在这个过程中可能会丢失,但重要的是要注意,将图像加载到数据框中很容易,只需使用sparkdl.readImages几行代码即可。这展示了使用 Spark 提供的机器学习管道的强大功能。

另请参阅

要了解有关sparkdl包的更多信息,请访问以下存储库:

databricks.github.io/spark-deep-learning/site/api/python/sparkdl.html

理解迁移学习

本章的其余部分将涉及迁移学习技术;因此,我们将在本节中解释迁移学习在我们的架构中的工作原理。

准备工作

本节不需要任何依赖项。

如何做到...

本节将介绍迁移学习的工作步骤:

  1. 确定一个预先训练的模型,将其用作将转移到我们选择的任务的训练方法。在我们的情况下,任务将是识别梅西和罗纳尔多的图像。

  2. 有几种可用的预训练模型可以使用。最受欢迎的是以下几种:

  3. Xception

  4. InceptionV3

  5. ResNet50

  6. VGG16

  7. VGG19

  8. 从预先训练的卷积神经网络中提取并保存一定数量的图像的特征,经过多层的过滤和池化。

  9. 预先训练的卷积神经网络的最后一层被替换为我们要基于数据集分类的特定特征。

工作原理...

本节解释了迁移学习的方法:

  1. 在早期章节中,我们讨论了机器学习模型,尤其是深度学习模型,如何在训练目的上最适合较大的样本。事实上,深度学习的一般座右铭是越多越好。

  2. 然而,有时候高数量的数据或图像并不可用于训练模型。在这种情况下,我们希望将一个领域的学习转移到预测不同领域的结果。已经由开发许多预先训练的模型的机构执行了从卷积神经网络中提取特征和过滤层的繁重工作,例如 InceptionV3 和 ResNet50:

  3. InceptionV3 是在 Google 开发的,比 ResNet50 和 VGG 的权重要小

  4. ResNet50 使用 50 个权重层

  5. VGG16 和 VGG19 分别具有 16 和 19 个权重层

  6. 一些更高级的深度学习库,如 Keras,现在预先构建了这些预先训练的网络,通过指定模型名称来更简化应用。

还有更多...

确定哪个预先训练的模型最适合所涉及的数据或图像集将取决于使用的图像类型。最好尝试不同的预先训练集,并确定哪一个提供了最佳的准确性。

另请参阅

要了解有关 Inception V3 预训练模型的更多信息,请阅读以下论文:

arxiv.org/abs/1409.4842

要了解有关 VGG 预训练模型的更多信息,请阅读以下论文:

arxiv.org/abs/1409.1556

创建图像分类训练管道

我们现在准备构建用于训练数据集的深度学习管道。

准备工作

将导入以下库以帮助管道开发:

  • LogisticRegression

  • Pipeline

如何操作...

以下部分将介绍创建图像分类管道的以下步骤:

  1. 执行以下脚本以开始深度学习管道,并配置分类参数:
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)
  1. 创建一个新的数据框predictDF,其中包含原始测试标签以及新的预测分数,使用以下脚本:
predictDF = pipeline_model.transform(testDF)
predictDF.select('prediction', 'label').show(n = testDF.toPandas().shape[0], truncate=False)

工作原理...

以下部分解释了如何配置图像分类管道以实现最佳性能:

  1. LogisticRegression被导入,因为它将是用于区分梅西和罗纳尔多图像的主要分类算法。DeepImageFeaturizersparkdl中导入,以根据图像创建特征,这将作为逻辑回归算法的最终输入。

重要的是要注意,从DeepImageFeaturizer创建的特征将使用基于InceptionV3的预训练模型,并分配一个vectorizer变量。

逻辑回归模型被调整为最多运行 30 次迭代。最后,管道将vectorizerLogisticRegression变量一起输入并将其拟合到训练数据框trainDF中。vectorizer用于从图像中创建数值。DeepImageFeaturizer的输出可以在以下截图中看到:

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

还有更多...

InceptionV3是我们用于分类图像的图像分类器模型;然而,我们也可以很容易地选择其他预训练模型,并在我们的管道中比较准确度。

另请参阅

要了解更多关于迁移学习的信息,请阅读威斯康星大学的以下文章:

ftp.cs.wisc.edu/machine-learning/shavlik-group/torrey.handbook09.pdf

评估模型性能

我们准备好评估我们的模型,并看看我们能多好地区分梅西和罗纳尔多。

准备工作

由于我们将进行一些模型评估,我们需要导入以下库:

  • MulticlassClassificationEvaluator

如何做...

以下部分将介绍以下步骤来评估模型性能:

  1. 执行以下脚本,从predictDF数据框中创建混淆矩阵:
predictDF.crosstab('prediction', 'label').show().
  1. 通过执行以下脚本,基于我们的 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))).

它是如何工作的...

以下部分解释了我们如何评估模型性能。看看这些图片:

  1. 我们可以将我们的数据框predictDF转换为交叉表,以创建一个混淆矩阵。这样可以让我们了解我们的模型中有多少真正阳性、假阳性、真正阴性和假阴性,如下面的屏幕截图所示:

  1. 此时,我们准备好计算我们使用 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

微调模型参数

任何模型的准确度都有改进的空间。在本节中,我们将讨论一些可以调整以提高我们模型准确度得分的参数。

准备工作

本节不需要任何新的先决条件。

如何做...

本节将介绍微调模型的步骤。

  1. 使用以下脚本定义一个新的逻辑回归模型,其中包括regParamelasticNetParam的额外参数:
logregFT = LogisticRegression(
 regParam=0.05, 
 elasticNetParam=0.3,
 maxIter=15,labelCol = "label", featuresCol="features")
  1. 使用以下脚本创建一个为新创建的模型配置的新管道:
pipelineFT = Pipeline(stages=[vectorizer, logregFT])
  1. 使用以下脚本将管道拟合到训练数据集trainDF
pipeline_model_FT = pipelineFT.fit(trainDF)
  1. 将模型转换应用于测试数据集testDF,以便使用以下脚本比较实际与预测分数:
predictDF_FT = pipeline_model_FT.transform(testDF)
predictDF_FT.crosstab('prediction', 'label').show()
  1. 最后,使用以下脚本评估新模型的准确率binary_rate_FT
binary_rate_FT = binaryevaluator.evaluate(predictDF_FT)*100
print("accuracy: {}%" .format(round(binary_rate_FT,2)))

工作原理...

本节解释了模型如何进行微调:

  1. 逻辑回归模型logregFT使用regParamelasticNetParam参数进行微调。这两个参数对应于逻辑回归模型的γ和α参数。正则化参数或regParam用于在最小化损失函数和最小化模型过拟合之间找到平衡。我们使模型越复杂,它就越可能过拟合并且不被泛化,但我们也可能会得到更低的训练误差。此外,我们使模型越简单,它就越不容易过拟合,但训练误差可能会更高。

  2. 弹性网参数或elasticNetParam是另一种正则化技术,用于结合多个正则化器 L1 和 L2,以最小化模型的过拟合。此外,我们将迭代次数从 20 降低到 15,以查看是否可以通过包括正则化和减少运行次数同时获得更好的准确度分数。

  3. 再次,就像我们在本章中之前所做的那样,我们创建了一个流水线,其中包括从图像生成的数值特征vectorizer,以及我们的逻辑回归模型logregFT

  4. 然后在训练数据trainDF上拟合模型,并将模型的转换应用于测试数据testDF

  5. 我们可以再次通过交叉表比较模型结果的实际与预测结果,如下截图所示:

  1. 与上一节相比,我们现在只有 1 张错分的图像,而不是 3 张。我们通过将maxIter降低到15次运行,并将regParam设置为0.05elasticNetParam设置为0.3来实现这一点。

  2. 我们的新准确率现在为95.83%,如下截图所示:

还有更多...

当然,我们通过将特定参数纳入我们的模型,将准确率从 87.5%提高到 95.83%。可以进行额外的微调和调整参数,以确定是否可以达到 100%的图像分类模型准确度。

另请参阅

要了解有关逻辑回归中正则化和弹性网参数的更多信息,请访问以下网站:

spark.apache.org/docs/2.2.0/mllib-linear-methods.html#logistic-regression

posted @ 2024-05-21 12:53  绝不原创的飞龙  阅读(18)  评论(0编辑  收藏  举报