谷歌云平台机器学习实用指南-全-

谷歌云平台机器学习实用指南(全)

原文:annas-archive.org/md5/0c231e89c375d109731af45617713934

译者:飞龙

协议:CC BY-NC-SA 4.0

前言

谷歌云机器学习引擎结合了谷歌云平台的服务与 TensorFlow 的强大功能和灵活性。通过本书,您不仅将学习如何以规模构建和训练不同复杂性的机器学习模型,还将学习如何在云端托管这些模型以进行预测。

本书专注于充分利用谷歌机器学习平台处理大型数据集和复杂问题。您将通过利用谷歌云平台的不同数据服务,从头开始创建强大的基于机器学习的应用程序,解决各种问题。应用包括自然语言处理、语音转文本、强化学习、时间序列、推荐系统、图像分类、视频内容推理等。我们将实现各种深度学习用例,并广泛使用构成谷歌云平台生态系统的数据相关服务,如 Firebase、存储 API、Datalab 等。这将使您能够将机器学习和数据处理功能集成到您的 Web 和移动应用程序中。

在本书结束时,您将了解您可能遇到的主要困难,并熟悉克服这些困难并构建高效系统的适当策略。

本书面向读者

本书面向数据科学家、机器学习开发者和希望学习如何使用谷歌云平台服务构建机器学习应用的人工智能开发者。由于与谷歌机器学习平台的大部分交互都是通过命令行完成的,因此读者应熟悉 bash shell 和 Python 脚本。对机器学习和数据科学概念的理解也将很有帮助。

本书涵盖内容

第一章,介绍谷歌云平台,探讨了可能对基于 GCP 构建机器学习管道有用的不同服务。

第二章,谷歌计算引擎,帮助您通过在线控制台和命令行工具创建并完全管理您的虚拟机,以及如何实现数据科学工作流程和 Jupyter Notebook 工作空间。

第三章,谷歌云存储,展示了如何使用谷歌云平台提供的服务上传数据和管理数据。

第四章,使用 BigQuery 查询您的数据,展示了如何从谷歌存储中查询数据,并使用谷歌数据工作室进行可视化。

第五章,转换您的数据,介绍了 Dataprep,这是一个用于数据预处理、提取特征和清理记录的有用服务。我们还探讨了 Dataflow,这是一个用于实现流式和批量处理的服务。

第六章,机器学习基础,开始了我们对机器学习和深度学习的探索之旅;我们学习何时应用每一种。

第七章,Google 机器学习 API,教导我们如何使用 Google Cloud 机器学习 API 进行图像分析、文本和语音处理、翻译以及视频推理。

第八章,使用 Firebase 创建机器学习应用,展示了如何整合不同的 GCP 服务来构建一个无缝的基于机器学习的应用,无论是移动端还是基于 Web 的应用。

第九章,使用 TensorFlow 和 Keras 的神经网络,让我们对前馈网络的结构和关键元素有了良好的理解,如何构建一个架构,以及如何调整和实验不同的参数。

第十章,使用 TensorBoard 评估结果,展示了不同参数和函数的选择如何影响模型的表现。

第十一章,通过超参数调整优化模型,教导我们如何在 TensorFlow 应用代码中使用超参数调整,并解释结果以选择表现最佳的模型。

第十二章,通过正则化防止过拟合,展示了如何通过设置正确的参数和定义适当的架构来识别过拟合,并使我们的模型对之前未见过的数据更加鲁棒。

第十三章,超越前馈网络——CNN 和 RNN,教导我们针对不同问题应用哪种类型的神经网络,以及如何在 GCP 上定义和实现它们。

第十四章,使用 LSTMs 进行时间序列分析,展示了如何创建 LSTMs 并将它们应用于时间序列预测。我们还将了解何时 LSTMs 优于更标准的方法。

第十五章,强化学习,介绍了强化学习的力量,并展示了如何在 GCP 上实现一个简单的用例。

第十六章,生成型神经网络,教导我们如何使用不同类型的内容——文本、图像和声音——从神经网络中提取生成的内容。

第十七章,聊天机器人,展示了如何在实现真实移动应用的同时训练一个上下文聊天机器人。

为了充分利用这本书

本书在 Google Cloud Platform 上实现了机器学习算法。为了重现本书中的许多示例,您需要在 GCP 上拥有一个有效的账户。我们使用了 Python 2.7 及以上版本来构建各种应用程序。本着这个精神,我们尽量使所有代码都尽可能友好和易于阅读。我们相信这将使我们的读者能够轻松理解代码,并在不同场景中轻松使用它。

下载示例代码文件

您可以从www.packtpub.com的账户下载本书的示例代码文件。如果您在其他地方购买了此书,您可以访问www.packtpub.com/support并注册,以便将文件直接通过电子邮件发送给您。

您可以通过以下步骤下载代码文件:

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

  2. 选择支持选项卡。

  3. 点击代码下载与勘误。

  4. 在搜索框中输入书籍名称,并遵循屏幕上的说明。

文件下载完成后,请确保使用最新版本的以下软件解压或提取文件夹:

  • Windows 上的 WinRAR/7-Zip

  • Mac 上的 Zipeg/iZip/UnRarX

  • Linux 上的 7-Zip/PeaZip

本书代码包也托管在 GitHub 上,网址为github.com/PacktPublishing/Hands-On-Machine-Learning-on-Google-Cloud-Platform。如果代码有更新,它将在现有的 GitHub 仓库中更新。

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

下载彩色图像

我们还提供了一份包含本书中使用的截图/图表彩色图像的 PDF 文件。您可以从这里下载:www.packtpub.com/sites/default/files/downloads/HandsOnMachineLearningonGoogleCloudPlatform_ColorImages.pdf

使用的约定

本书使用了多种文本约定。

CodeInText:表示文本中的代码单词、数据库表名、文件夹名、文件名、文件扩展名、路径名、虚拟 URL、用户输入和 Twitter 昵称。以下是一个示例:“其中GROUP是一个服务或账户元素,COMMAND是要发送到GROUP的命令。”

代码块设置如下:

import matplotlib.patches as patches
import numpy as np
fig,ax = plt.subplots(1)

当我们希望您注意代码块中的特定部分时,相关的行或项目将以粗体显示:

text="this is a good text"
from google.cloud.language_v1 import types
document = types.Document(
        content=text,
        type='PLAIN_TEXT')

任何命令行输入或输出都应如下所示:

$ gcloud compute instances list

粗体:表示新术语、重要单词或屏幕上看到的单词。例如,菜单或对话框中的单词在文本中显示如下。以下是一个示例:“点击创建新项目。”

警告或重要注意事项如下所示。

小贴士和技巧如下所示。

联系我们

我们始终欢迎读者的反馈。

一般反馈:请发送电子邮件至feedback@packtpub.com,并在邮件主题中提及书籍标题。如果您对本书的任何方面有疑问,请发送电子邮件至questions@packtpub.com

勘误:尽管我们已经尽最大努力确保内容的准确性,但错误仍然可能发生。如果您在这本书中发现了错误,我们将非常感激您能向我们报告。请访问www.packtpub.com/submit-errata,选择您的书籍,点击勘误提交表单链接,并输入详细信息。

盗版:如果您在互联网上发现我们作品的任何非法副本,我们将非常感激您能提供位置地址或网站名称。请通过发送链接至copyright@packtpub.com与我们联系。

如果您有兴趣成为作者:如果您在某个领域有专业知识,并且您有兴趣撰写或为书籍做出贡献,请访问authors.packtpub.com

评价

请留下您的评价。一旦您阅读并使用过这本书,为何不在购买它的网站上留下评价呢?潜在读者可以查看并使用您的客观意见来做出购买决定,我们 Packt 公司可以了解您对我们产品的看法,并且我们的作者可以查看他们对书籍的反馈。谢谢!

如需了解更多关于 Packt 的信息,请访问packtpub.com

第一章:介绍谷歌云平台

本章的第一个介绍性目标是为您提供一个关于谷歌云平台GCP)的概述。我们首先解释为什么机器学习ML)和云计算是相辅相成的,因为对今天机器学习应用日益增长的计算资源需求。然后,我们全面介绍平台的数据相关服务。账户和项目的创建以及角色分配本章结束。

数据科学项目遵循一系列常规步骤:在提取数据、探索、清洗数据、提取信息、训练和评估模型,最后构建机器学习应用。对于数据科学流程的每个步骤,GCP 中都有一个或多个适当的服务。

但是,在我们展示 GCP 数据相关服务的整体映射之前,了解为什么机器学习和云计算真正是天生一对是很重要的。

在本章中,我们将涵盖以下主题:

  • 机器学习和云计算

  • 介绍 GCP

  • 谷歌平台的数据服务

机器学习和云计算

简而言之,人工智能AI)需要大量的计算资源。云计算解决了这些担忧。

机器学习是一种新型的显微镜和望远镜,使我们每个人都能推动人类知识和人类活动的边界。随着越来越强大的机器学习平台和开源工具,我们能够征服新的知识领域,发展新的商业模式。在家中、办公室,甚至笔记本电脑的舒适环境中,我们能够更好地理解和预测人类在广泛领域的各种行为。想想医疗保健、交通、能源、金融市场、人类沟通、人机交互、社交网络动态、经济行为和自然(天文学、全球变暖或地震活动)。受人工智能爆炸影响的领域名单实际上是无限的。对社会的影响?令人震惊。

随着如此多的资源对任何有在线连接的人开放,加入人工智能革命的门槛从未像现在这样低。书籍、教程、MOOCs、聚会,以及各种语言的开放源代码库,对资深和初出茅庐的数据科学家都是免费提供的。

如资深数据科学家所熟知,数据科学总是渴望更多的计算资源。在鸢尾花或 MINST 图像数据集上的分类,或者在泰坦尼克号乘客上的预测建模,并不能反映现实世界的数据。现实世界的数据本质上是不干净的、不完整的、有噪声的、多源头的,而且往往数量庞大。利用这些大型数据集需要计算能力、存储、CPU、GPU 和快速的 I/O。

然而,更强大的机器本身并不足以构建有意义的机器学习应用。基于科学,数据科学需要一种科学思维,包括可重复性和审查等概念。通过使用在线可访问的资源,这两个方面都变得更容易。当数据存储在个人电脑上时,共享数据集和模型以及展示结果总是更加困难。使用新数据重现结果和维护模型也需要轻松访问资源。随着我们处理越来越个性化且至关重要的数据(例如在医疗保健领域),隐私和安全问题对于项目利益相关者来说变得更加重要。

这就是云发挥作用的地方,它通过提供可扩展性和可访问性,同时提供足够的保障。

在深入探讨 GCP 之前,让我们先了解一下云。

云的本质

机器学习项目资源密集。从存储到计算能力,训练模型有时需要无法在简单独立计算机上找到的资源。近年来,在存储方面的物理限制已经缩小。我们现在可以以较低的价格享受可靠的千兆存储,对于大多数不在大数据领域的数据项目来说,存储不再是问题。计算能力也大幅提升,几年前需要昂贵工作站的任务现在可以在笔记本电脑上运行。

然而,尽管所有这些惊人的快速演变,独立个人电脑的能力是有限的。你可以在机器上存储的数据量以及你愿意等待模型训练的时间都是有限的。人工智能的新领域,如语音转文本、实时视频字幕、自动驾驶汽车、音乐生成或能够欺骗人类并通过图灵测试的聊天机器人,需要越来越多的资源。这对于深度学习模型来说尤其如此,它们在标准 CPU 上运行得太慢,需要基于 GPU 的机器在合理的时间内进行训练。

云中的机器学习不受这些限制。云计算提供的是直接访问高性能计算HPC)。在云出现之前(大约在 2006 年 AWS 推出其弹性计算云EC2)服务之前),HPC 只能通过超级计算机获得,如克雷计算机。克雷是一家自 1960 年代以来建造了一些最强大超级计算机的美国公司。中国的天河二号现在是世界上最强大的超级计算机,其计算能力为 100,000 petaflops(即 10² x 10¹⁵,或每秒 10 的 17 次方浮点运算!)。

超级计算机不仅成本数百万美元,还需要自己的物理基础设施,并且维护成本高昂。对于个人和大多数公司来说,它都遥不可及。渴望高性能计算(HPC)的工程师和研究人员现在转向按需云基础设施。云服务提供民主化了对 HPC 的访问。

云计算建立在分布式架构之上。处理器分布在不同的服务器上,而不是集中在一台机器上。只需几点击或命令行,任何人都可以在几分钟内注册大量复杂的服务器。您可支配的功率可能会令人震惊。

云计算不仅能处理最苛刻的优化任务,还能在小型数据集上执行简单的回归分析。云计算极其灵活。

总结一下,云计算提供以下服务:

  • 即时性:资源可以在几分钟内变得可用。

  • 按需使用:当不再需要时,实例可以被置于待机状态或退役。

  • 多样性:广泛的操作系统、存储和数据库解决方案,允许架构师创建以项目为中心的架构,从简单的移动应用程序到机器学习 API。

  • 无限资源:如果还不是无限的,你可以组装的存储、计算和网络资源量是令人震惊的。

  • GPU:大多数个人电脑基于 CPU(除了为游戏优化的机器)。深度学习需要 GPU 以实现与人类兼容的训练模型速度。云计算以极低的成本提供 GPU,这比购买 GPU 机器的成本要低得多。

  • 可控的可访问性和安全性:通过细粒度的角色定义、服务分区、加密连接和基于用户的访问控制,云平台大大降低了入侵和数据丢失的风险。

除了这些,市场上还有几种其他类型的云平台和产品。

公共云

根据客户需求,存在两种主要的云模型:公共云与私有云,以及多租户与单租户。这些不同的云类型提供了不同层次的管理、安全和定价。

公共云由位于互联网上的远程资源组成。在公共云中,基础设施通常是多租户的。多个客户可以共享相同的底层硬件或服务器。网络、存储、电力、冷却和计算资源都是共享的。客户通常无法看到基础设施托管的位置,除非选择一个地理位置。公共云服务的定价模式基于数据量、使用的计算能力以及其他与基础设施管理相关的服务——或者更精确地说,是 RAM、vCPU、磁盘和带宽的组合。

在私有云中,资源专门用于单个客户;架构是单租户而不是多租户。服务器位于本地或远程数据中心。客户拥有(或租赁)基础设施并负责维护它。私有云基础设施的运营成本更高,因为它们需要为单个租户提供专用硬件以确保安全。私有云的客户对其基础设施有更多的控制权,因此他们可以实现其合规性和安全要求。

混合云由公共云和私有云的混合组成。

GCP 是一个公共的多租户云平台。您与其他客户共享您使用的服务器,并让谷歌处理支持、数据中心和基础设施。

管理云与非管理云

云市场也已经分化为两个大型细分市场——管理云与非管理云。

在非管理云平台上,基础设施是自助服务。在出现故障的情况下,客户有责任实施一些机制来恢复操作。非管理云要求客户拥有构建、管理和维护云实例和基础设施的合格专家资源和资源。专注于自助服务应用程序的非管理云服务不包括其基本层支持。

在管理云平台上,提供商将通过提供监控、故障排除和全天候客户服务来支持底层基础设施。管理云立即为团队带来合格的专家资源和资源。对于许多公司来说,拥有服务提供商来处理他们的公共云可能比雇佣自己的员工来运营他们的云更容易、更经济高效。

GCP 是一个公共的、多租户的、非管理云服务。AWS 和 Azure 也是如此。另一方面,Rackspace 是一家管理云服务公司的例子。例如,Rackspace 于 2017 年 3 月刚刚开始为 GCP 提供管理服务。

IaaS 与 PaaS 与 SaaS 的比较

另一个重要区别是关于用户或云平台提供商完成的工作量。让我们借助以下服务级别来看看这个区别:

  • 基础设施即服务(IaaS):在最低级别,IaaS,云提供商处理机器、虚拟化和所需的网络。用户负责其他所有事情——操作系统、中间件、数据和应用程序软件。提供商是用户在其上构建基础设施的资源的主机。Google 计算引擎、SQL、DNS 或负载均衡是 GCP 中 IaaS 服务的例子。

  • 平台即服务(PaaS):在 PaaS 提供中,用户只需负责软件和数据。其余一切均由云提供商处理。提供商构建基础设施,而用户部署软件。PaaS 相较于 IaaS 的主要优势,除了减少工作量和对系统管理员资源的需求外,还包括为 Web 应用提供的自动扩展功能。随着需求的波动,适当数量的资源会自动分配。PaaS 服务的例子包括 Heroku 或 Google App Engine。

  • 软件即服务(SaaS):在 SaaS 中,提供商是一家提供在线服务的软件公司,而用户消费的是提供的服务。想想 Uber、Facebook 或 Gmail。

虽然 GCP 主要是一家 IaaS 提供商,但它也有一些 PaaS 提供,如 Google App Engine。而且它的 ML API(文本、语音、视频和图像)可以被视为 SaaS。

成本和定价

云服务的定价复杂且因供应商而异。云服务的基本成本结构可以分解为:

  • 计算成本:按 vCPU 数量、每 GB RAM 运行的 VM 持续时间

  • 存储成本:每 GB 的磁盘、文件和数据库

  • 网络成本:内部和外部,入站和出站流量

Google 的预留 VM(AWS Spot 实例)是基于剩余的、未使用的容量构建的 VM,其价格比正常按需 VM 低三到四倍。然而,如果 Compute Engine 需要为其他任务访问这些资源,它可能会终止(预留)这些实例。预留实例适用于批处理作业或可以承受突然中断的工作流程。它们也可能并不总是可用。在下一章中,我们将学习如何从命令行启动预留实例。

Google 云最近也推出了承诺使用量的降价。当你为长期保留实例时,你会获得折扣,通常承诺使用期限为 1 年或 3 年。

当你的基础设施快速演变并需要可扩展性和快速修改时,转移到云中的成本削减论点是成立的。如果你的应用程序非常静态且负载稳定,云可能不会导致成本降低。最终,由于云提供了更多的灵活性,并打开了快速实施新项目的大门,整体成本可能高于固定基础设施。但这种灵活性是云计算的真正好处。

请参阅cloud.google.com/compute/pricing了解当前 Google Compute Engine 的定价。

价格战

在过去几年中,云服务的成本已经大幅下降。自 2012 年以来,三大主要公共云服务提供商经历了连续的价格下降阶段,当时 AWS 大幅降低了其存储价格,以削弱竞争。2012 年,四大主要云服务提供商降低了 22 次价格,2013 年降低了 26 次。降价幅度从 6%到 30%,涉及所有类型的服务:计算、存储、带宽和数据库。截至 2014 年 1 月,亚马逊已经降低了其提供方案的价格超过 40 次。这些降价被其他主要云服务提供商匹配或超过。最近,三大主要角色进一步降低了存储价格,可能重新点燃了价格战。根据最近的一项云计算价格研究,没有多少数据表明云计算已经接近成为商品。451 研究公司表示如此,并进一步预测关系数据库很可能是下一场价格战的战场。

ML

因此,云计算的优势在于几乎即时的可用性、低成本、灵活的架构和几乎无限的资源,但代价是额外的开销和持续的成本。

在全球云计算的格局中,GCP 是一个公共未管理的 IaaS 云服务,提供一些 PaaS 和 SaaS 服务。尽管 Azure 和 GCP 在标准云服务方面,如计算(EC2、云计算等)、数据库(BigQuery、Redshift 等)、网络等方面可以直接比较,但谷歌云在机器学习方面的方法与亚马逊或 Azure 截然不同。

简而言之,AWS 提供了一站式服务,针对非常具体的应用——如人脸识别和与 Alexa 相关的应用,或者基于经典(非深度学习)模型的预测分析平台,称为 Amazon ML。微软的提供方案更侧重于 PaaS,拥有其 Cortana 智能套件。微软的机器学习服务与 AWS 相当相似,但提供了更多可用的模型。

GCP 的机器学习提供方案基于 TensorFlow,谷歌的深度学习库。谷歌提供了一系列基于预训练 TensorFlow 模型的机器学习 API,用于自然语言处理、语音转文本、翻译、图像和视频处理。它还提供了一个平台,你可以在这里训练自己的 TensorFlow 模型并评估它们(TensorBoard)。

介绍 GCP

第一项云计算服务可以追溯到 15 年前,当时在 2002 年 7 月,亚马逊推出了 AWS 平台,以展示亚马逊及其附属公司的技术和产品数据,使开发者能够构建创新和创业应用。2006 年,AWS 作为 EC2 重新推出。

AWS 的早期启动让亚马逊在云计算领域取得了领先地位,这一领先地位从未动摇。竞争对手缓慢地反击并推出他们自己的产品。2008 年 4 月,Google App Engine 作为开发和管理 Web 应用程序的 PaaS 服务推出,这是来自主要公司的 AWS 云服务的第一个替代品。因此,GCP 诞生了。微软和 IBM 也相继跟进,2010 年 2 月推出了 Windows Azure 平台,2009 年 1 月推出了 LotusLive。

Google 直到很久以后才进入 IaaS 市场。2013 年,Google 将 Compute Engine 以企业 服务级别协议SLA)的形式向公众发布。

GCP 的映射

GCP 生态系统拥有超过 40 种不同的 IaaS、PaaS 和 SaaS 服务,丰富而复杂。这些服务可以分为六个不同的类别:

  • 托管和计算

  • 存储和数据库

  • 网络连接

  • 机器学习(ML)

  • 身份和安全性

  • 资源管理和监控

在下一节中,我们将学习如何在 Google Compute Engine 上设置和管理单个虚拟机实例。但在那之前,我们需要创建我们的账户。

开始使用 GCP

在 GCP 上开始使用相当直接。您真正需要的是 Google 账户。访问 cloud.google.com/,使用您的 Google 账户登录,并按照说明操作。根据需要添加您的账单信息。这为您提供了访问 GCP 的基于 Web 的用户界面。我们将在下一章中介绍命令行和 Shell 可访问性以及相关的 SSH 密钥创建。

免费试用在撰写本文时,Google 为新账户提供了一项相当慷慨的 12 个月期限和 300 美元的信用额度免费试用服务。然而,某些服务有限制。例如,您不能启动具有超过八个 CPU 的 Google Compute Engine 虚拟机实例,您创建的项目数量也有限制,尽管您可以请求超过分配的配额。没有 SLA。不允许使用 Google Cloud 服务进行比特币挖矿等活动。一旦您升级账户,这些限制就不再适用,并且超出初始 300 美元的余额将记入您的账户。有关免费试用服务的更多信息,请参阅cloud.google.com/free/docs/frequently-asked-questions

基于项目的组织

GCP 的一个关键方面是其以项目为中心的组织。所有账单、权限、资源和设置都组在一个用户定义的项目中,这基本上充当了一个全局命名空间。如果不指定项目,就无法启动资源。

这些项目中的每一个都有:

  • 一个项目名称,由您选择。

  • 一个项目 ID,由 GCP 建议,但可编辑。项目 ID 用于 API 调用和项目内的资源。

  • 一个项目编号,由 GCP 提供。

项目 ID 和项目编号在所有 GCP 项目中都是唯一的。项目组织具有几个简单直观的好处:

  • 由于资源仅分配给单个项目,因此预算分配和计费都得到了简化。

  • 由于分配给项目的资源受相同的区域和区域规则约束,并且共享相同的元数据,因此它们之间的操作和通信无缝进行。

  • 类似地,访问管理在单个项目中是一致的,这限制了整体访问控制的复杂性。

基于项目的组织大大简化了您对资源的管理,这也是 GCP 容易使用的关键因素之一。

创建您的第一个项目

要创建一个新项目:

  1. 前往资源管理页面,console.cloud.google.com/cloud-resource-manager

  2. 点击创建项目。

  3. 记下您的项目标题,并注意 Google 如何即时生成项目 ID。根据需要编辑它。

  4. 点击创建。

  5. 您被重定向到 IAM 服务的角色部分。

角色和权限

默认情况下,当您创建一个新项目时,您的 Google 账户被设置为项目的所有者,拥有对所有项目资源和计费的全权访问权限。在 IAM 页面的角色部分 console.cloud.google.com/iam-admin/roles/,您可以向项目中添加人员并定义该人员的角色。您还可以基于每个服务创建新的自定义角色或分配按服务组织的预定义角色。

  1. 前往 IAM 页面并选择您刚刚创建的项目,如果尚未选择:console.cloud.google.com/iam-admin/iam/project。您应该看到您的 Google 账户电子邮件作为项目的所有者。

  2. 要向项目中添加新人员:

    1. 点击 + 添加。

    2. 输入人员的 Google 账户电子邮件(它必须对应一个活跃的 Google 账户)。

    3. 选择该人员的所有角色,如以下截图所示:

图片

角色菜单按服务和行政域(计费、日志和监控)组织,并且对于每个服务,按访问级别划分。尽管这取决于服务,但您可以选择以下四种角色类型之一:

  • 管理员:对资源的完全控制

  • 客户端:连接访问

  • 编辑器/创建者:除了用户管理、SSL 证书和删除实例外,拥有完全控制权

  • 查看者:只读访问

您还可以从 console.cloud.google.com/iam-admin/roles/project?project=packt-gcp 的角色 IAM 页面创建新的自定义角色。

当你为项目分配新的资源时,平台会在服务之间创建适当的和必需的角色和权限。您可以从管理资源页面右侧的信息面板或给定项目的 IAM 页面查看和管理这些访问权限和相关角色。谷歌在生成正确的访问级别方面做得很好,这使得平台用户的生活更加轻松。

我们的谷歌云项目对于这本书,我创建了packt-gcp项目。由于该名称在所有其他 GCP 项目中都是唯一的,因此项目 ID 也是packt-gcp。并且所有资源都创建在 us-central1 区域。

进一步阅读

在整本书中,我将在每一章结束时列出一些在线资源,这些资源回顾或超越了章节中讨论的内容:

摘要

在本章的介绍中,我们探讨了 GCP 的本质并探索了其服务架构。我们创建了一个新项目并理解了角色创建和分配。尽管 GCP 是云计算市场的新进入者,但它为广泛的用途提供了一套完整的服务。我们将在本书的其余部分深入研究这些服务。

我们现在可以开始使用谷歌平台上的数据科学了。在下一章中,我们将在谷歌计算引擎上创建一个虚拟机实例,并使用 Anaconda 发行版安装数据科学 Python 栈。我们将探索 Web 界面,并学习如何通过命令行和谷歌 Shell 管理实例。

第二章:Google Compute Engine

Google Cloud PlatformGCP)的核心服务是Google Compute EngineGCE)。GCE 允许您根据您的需求启动具有正确操作系统、大小、RAM 以及适当数量的 CPU 或 GPU 的虚拟机(VMs)。它与 AWS EC2 相当。使用 GCE,我们深入 GCP 的核心。

在本章中,您将学习如何:

  • 在 GCE 上创建适合您项目的 VM 实例。

  • 使用谷歌的命令行工具管理您的 VM。

  • 在 GCE 虚拟机上设置一个使用condascikit-learn的 Python 数据科学堆栈。

  • 通过密码保护的 Jupyter Notebook 访问您的 VM。我们还将涵盖与镜像、快照、预占 VM、启动脚本和 IPs 相关的更多高级主题。

到本章结束时,您将能够通过在线控制台和命令行工具创建和管理您的 VM,以及实现数据科学工作流程和 Jupyter Notebook 工作空间。

Google Compute Engine

简而言之,GCE 是一种允许您在谷歌基础设施上创建和运行 VM 的服务。GCE 允许您根据您的需求启动具有正确操作系统、大小、RAM 以及适当数量的 CPU 或 GPU 的 VM。它与 AWS EC2 相当。

GCE 于 2012 年 6 月 28 日在 Google I/O 2012 上宣布,并于 2013 年 5 月 15 日对公众开放。与 AWS EC2 等类似产品相比,GCE 是一个相对较新的服务:

以下是从发布时间线中摘录的内容,展示了 GCE 服务从简单的竞争者到云计算领域全面参与者的快速演变:

  • 2013 年 5 月 15 日:GCE 对所有用户开放。

  • 2013 年 8 月 6 日:GCE 推出负载均衡。

  • 2013 年 12 月 3 日:GCE 被宣布为生产就绪。用户现在可以放心使用 Compute Engine 来支持关键任务工作负载,提供 24/7 的支持和 99.95%的月度服务级别协议

  • 2014 年 6 月 25 日:固态硬盘SSD)持久磁盘现在普遍可用,对所有用户和项目开放。

  • 2015 年 9 月 8 日:可预占实例现在对所有用户和项目普遍可用。

  • 2016 年 3 月 30 日:大于 10 TB 的持久磁盘现在普遍可用。

  • 2016 年 7 月 1 日:关机脚本现在普遍可用,可用于计算引擎实例。

  • 2017 年 9 月 21 日:NVIDIA® Tesla® K80 GPU 现在普遍可用。

  • 2017 年 9 月 26 日:GCE 虚拟机实例的计费增量从每分钟增量减少到每秒增量。

  • 在撰写本文时最新的消息是推出了惊人的 96-vCPU 机器类型。

在过去的四年里,谷歌通过以下方式稳步提高和快速发展其 GCE 服务:

  • 扩展区域

  • 添加更强大的机器和英特尔 CPU 平台

  • 添加角色和功能

  • 持续推出针对 Windows、Suse、CentOS、Debian、Ubuntu、RHEL 或 CoreOS 的新公共镜像

如时间线所示,GCE 服务是一个年轻且动态的服务,它拥抱其客户需求的变化,并以大胆的新产品来预见它们。它反映了谷歌成为云计算业务领导者的动力,并有可能抵消亚马逊在云计算中的领先地位。

在我们启动第一个 GCE 虚拟机之前,让我们先了解一些重要概念。

虚拟机、磁盘、镜像和快照

虚拟机是一个按需虚拟服务器,您可以根据需要启动它。它位于谷歌数据中心的一个地理位置,但您只需选择区域和区域,而不是确切的位置。尽管您与其他用户共享一些基础设施资源,但这种共享对您来说是透明的。

虚拟机需要一个持久磁盘来运行,以及一个操作系统,如 Windows 或 Linux 发行版来启动。尽管在云计算环境中非常抽象,但 GCE 磁盘将指的是计算机可以启动的物理驱动器。

一个镜像位于持久磁盘之上,并包含启动实例所需的操作系统。镜像的一个典型用途是允许在许多不同的虚拟机之间共享虚拟机设置。镜像由操作系统和引导加载程序组成,可以用来启动实例。

快照是虚拟机在特定时间点的内容的反映。快照主要用于即时备份。快照作为相对于前一个快照的差异存储,而镜像则不是。

镜像和快照非常相似。可以使用快照或镜像激活实例。

当您启动一个新实例时,GCE 首先会将一个持久磁盘附加到您的虚拟机上。这提供了磁盘空间,并为实例提供了启动所需的根文件系统。磁盘使用您选择的镜像,并安装与该镜像关联的操作系统。公共镜像由谷歌提供,具有特定的操作系统,而私有镜像则是您自己的镜像。

通过对镜像进行快照,您可以将现有持久磁盘中的数据复制到新的持久磁盘。快照旨在创建即时备份。

从 Google Shell,您可以访问和管理您所有的资源和文件。

例如,让我们通过输入以下命令来列出所有现有的实例:

$ gcloud compute instances list

我们看到了我们新创建的麻雀实例。

创建虚拟机

现在我们将使用网络控制台创建我们的第一个虚拟机实例。

前往 GCE 控制台,console.cloud.google.com/。选择我们在上一章中创建的项目(或者如果您还没有,可以创建一个),然后在左侧菜单中点击 Compute Engine。由于您还没有虚拟机,您将看到以下消息。点击创建,如图所示:

对于这个第一个虚拟机,我们将选择一个小的,并在进行过程中调整其大小。

在此阶段,您需要做出几个决定:

  • 您实例的名称。我将称我的为麻雀。这个名称不需要在 GCP 中是唯一的。请随意命名您的。

  • 地区和区域。通常最好选择离您最近的地域以减少延迟。然而,GCP 服务通常首先在美国开放,在其他地区的可用性要晚一些。不同地域也可能有不同的规则和法规。例如,欧洲提供比美国更强的数据相关隐私法律。根据您的需要选择区域。您总是可以在以后更改区域。

  • 选择正确的机器类型很重要。在撰写本书时,不同的机器被分为小型、标准、高 CPU 和高 RAM 等类别:

    • 小型:共享 CPU 和有限的 RAM

    • 标准虚拟机:3.75 GB 的 RAM

    • 高内存虚拟机:13 GB RAM

    • 高 CPU 虚拟机:1.8 GB

小型类别非常适合入门并在此平台上积累一些实践经验。对于更复杂的项目,您可能需要更多的计算能力或更多的内存。

注意,免费试用账户的 CPU 数量限制为八个。

您也可以通过设置您想要的每个 CPU 的 CPU 数量或内存来定制您需要的机器。这也是您选择机器上 GPU 数量的地方,如下面的截图所示:

截图

  • 最后,您需要为您的虚拟机选择操作系统。默认情况下提供的是 Debian Linux 发行版。您可以在几个操作系统中进行选择:Windows、CentOS、Suse、CoreOS 和 Ubuntu。尽管 Ubuntu 通常是最受欢迎的选择,但实际上 Debian 和 Ubuntu 之间几乎没有区别,我们将使用默认的 Debian 发行版。如果您更熟悉 Ubuntu 发行版,请选择它。这在本章中不应引起任何问题。

Ubuntu 还是 Debian?Debian 是第一个在 1996 年发布第一个稳定版本的 Linux 发行版。Ubuntu 于 2004 年开始作为 Debian 的分支,即 Debian 的衍生版本。这两个发行版非常相似,Ubuntu 更易于用户使用,并且具有更好的桌面/UI 体验。Debian 通常用于服务器,拥有庞大的软件包库,重点在于稳定性和开源软件。Debian 的稳定版本大约每两年发布一次。Ubuntu 的发布周期为六个月。Ubuntu 从 Debian 的不稳定分支中提取,特别在 UI 方面进行定制,然后发布。对于我们的工作,这两个发行版之间应该几乎没有区别,我们将使用 Debian 作为我们的虚拟机。

将所有其他参数保留为默认选择。我们将在几页后回到 HTTPS 流量、磁盘、网络和 ssh 密钥。

在网页控制台中,有一个非常有用的功能可以降低掌握 GCP 的学习曲线,那就是在虚拟机创建页面底部的两个链接,即“等效 REST”或“命令行”,如下图中所示:

图片

命令行链接存在于网页控制台的多个页面中。这是一个非常有用的功能,可以快速学习 GCP 命令行工具的正确语法和参数。

我们的虚拟机现在已经创建完成,正在运行中!

图片

现在我们有一个全新的闪亮的虚拟机,我们该如何访问它?这很自然地引出了 Google Shell。

Google Shell

Google Shell 是 Google 在浏览器中为你提供一个独立终端的智能方式,以便访问和管理你的资源。

你可以通过点击控制台页面右上角的 >_ 图标来激活 Google Shell:

图片

浏览器窗口分为两部分,现在下半部分是一个 shell 终端:

图片

这个终端运行在具有 Debian 操作系统的 f1-micro GCE VM 上。它是根据每个用户和每个会话创建的。当你的 Cloud Shell 会话处于活动状态时,它会持续存在,在 20 分钟的无操作后会被删除。该实例运行在具有 5GB 存储空间的持久磁盘上。磁盘和镜像都是免费的。尽管实例不会跨会话持久,但与其关联的磁盘是跨会话持久的。你通过 Google Shell 创建的所有内容都将按照你下次会话开始时的状态可用。这包括你存储的所有文件、你安装的软件以及你编辑的配置文件(例如.bashrc.vimrc)。这个磁盘是私有的,其他用户无法访问。最后,Google Shell 实例预装了 Google Cloud SDK 和其他流行的开发者工具,如 VIM。

你通过网页控制台运行的某些命令将被保存在你的 Google Shell VM 中。例如,你在 Google SQL 实例上运行的 SQL 查询,将出现在你用户目录的$HOME文件夹中的.mysql_history文件中。关于 Google Shell 的更多信息可以在你$HOME文件夹中的README-cloudshell.txt文件中找到。

从 Google Shell,你可以访问和管理所有你的资源和文件。例如,让我们通过输入以下命令来列出所有现有的实例:

$ gcloud compute instances list

我们可以看到我们新创建的麻雀实例:

图片

要访问你刚刚创建的虚拟机,请输入:

$ gcloud compute ssh sparrow

这将运行必要的ssh密钥的创建过程。现在你已经不在 Google 的 Cloud Shell VM 实例上了,而是在麻雀 VM 上了。为了检查我们在麻雀实例上运行的是哪个操作系统和版本,我们运行:

$ lsb_release -d

在麻雀机器上,我安装的是 Debian GNU/Linux 9(stretch),而在 Google Shell VM 上,则是 Debian GNU/Linux 8(jessie)。这告诉我 Google Shell 尚未升级到 Debian 发行版的最新版本。当然,您可能会看到不同的结果。

Google Cloud Platform SDK

GCP 提供了几个独立的命令行界面(CLIs)来管理和与您的 GCP 资源交互,其中gcloud是主要的。所有次要命令行工具都是通过gcloud安装的。在撰写本文时,命令行工具有:

  • gcloud:管理您的 GCP 资源和项目的核心命令行界面:身份验证、本地配置、开发者工作流程以及与 GCP API 的交互。以下服务可以通过gcloud命令行界面处理:应用引擎、身份验证、计算引擎、容器、DataFlow、Dataproc、机器学习、SQL 数据库以及 Cloud 资源的部署、Iam 设置、与 Stackdriver 和 Web 资源(如 DNS、域或 Firebase)的日志记录。

Gcloud 还负责其他命令行工具:

  • gsutil:这是 Google Storage 的命令行界面。您使用gsutil创建和删除存储桶、上传、下载和移动文件、设置权限等。

  • bq:这是与 BigQuery 交互的命令行界面。

  • datalab:Datalab 命令行界面。

所有这些命令行工具都是 Python 脚本,需要在您的系统上安装 Python 2.7。

要安装gcloud,最佳方式是遵循 Cloud DSK 页面上的说明cloud.google.com/sdk/downloads。下载正确的包,并运行适合您机器的相应命令。安装过程将引导您创建ssh密钥。它将在您的~/.ssh文件夹中安装三个文件。您的公钥和私钥(即google_compute_engine.pubgoogle_compute_engine)以及已知主机列表(google_compute_known_hosts)。

您可以通过在终端中运行gcloud version来验证gcloud是否正确安装。您的输出将类似于:

Google Cloud SDK 173.0.0
core 2017.09.25
gsutil 4.27

如我们所见,gcloud不是一个万能工具。gcloud内置了组件。这些组件可以是其他独立的 CLIs,如gsutilsbqdatalab,或者是gcloud扩展(如app-engine-python),以及 Alpha 和 Beta 发布级别。要查看您的gcloud中安装了哪些组件,请在终端中运行以下命令:

$ gcloud components list

您将获得以下结果:

图片

要在当前 SDK 版本(173.0.0)中安装或删除组件,请使用以下命令:

$ gcloud components install COMPONENT_ID 
$ gcloud components remove COMPONENT_ID

要将 SDK 安装更新到最新版本(175.0.0),请运行:

$ gcloud components update

Gcloud

让我们通过几个命令来了解gcloud命令行界面的语法:

  • 要列出所有项目,请使用以下命令:
$ gcloud projects list
  • 要列出packt-gcp项目中的所有实例,请使用以下命令:
$ gcloud compute instances list --project packt-gcp

gcloud的全局通用语法也适用于其他 CLI 工具:

$ gcloud GROUP | COMMAND parameters

其中 GROUP 是服务或账户元素,COMMAND 是要发送到 GROUP 的命令。例如,在 gcloud projects list 中,projectsGROUP,您的账户的一个元素,而 listCOMMAND。在 gcloud compute instances list --project packt-gcp 中,GROUPcompute,后面跟着子组 instances,而 listCOMMAND--project packt-gcp 是必需的参数。

gcloud 参数包括账户设置(例如实例的密钥和区域)、CLI 设置(详细程度、格式或特定配置)以及命令所需的参数。例如,要启动我们的实例,我们需要指定两个参数——区域和实例 ID:

$ gcloud compute instances start sparrow  --project packt-gcp --zone us-east1-d

Gcloud 配置

为了避免在 config 中指定区域或其他参数,您可以在其中设置它们:

$ gcloud config set compute/zone us-east1-d

要在 config 中取消设置它们,您可以使用以下命令:

$ gcloud config unset compute/zone

要列出 config 中所有可用的设置,请运行 gcloud config set --help

区域和区域也可以存储在环境变量 CLOUDSDK_COMPUTE_ZONECLOUDSDK_COMPUTE_REGION 中。环境变量会覆盖您使用 gcloud config 命令设置的默认属性,但不会覆盖像 --zone--region 这样的显式标志。

要设置环境变量 CLOUDSDK_COMPUTE_ZONE,请在您的 .bashrc 文件中运行或添加以下行:

$ export CLOUDSDK_COMPUTE_ZONE=us-east1-c

更多详情,请参阅 cloud.google.com/compute/docs/gcloud-compute/#set_default_zone_and_region_in_your_local_client

使用 gcloud 访问您的实例

从一开始,您想要做的有两件重要的事情:

  • 访问实例

  • 在您的实例和另一台机器之间移动文件。要使用 ssh 连接到您的实例,请运行:

$ gcloud compute ssh <instance_name>

在我们的案例中:

$ gcloud compute ssh sparrow

第一次从您的本地系统访问您的实例时,平台将传播您的密钥到实例,这可能需要几分钟。一旦连接,您可以通过检查您的本地公钥(cat ~/.ssh/google_compute_engine.pub)是否包含在实例的 authorized_keys 列表中(cat ~/.ssh/authorized_keys)来验证。

使用 gcloud 传输文件

通过 Gcloud 的 .csp 命令版本在您的机器(或任何其他位置)和实例之间来回传输文件完成:

  • 要将本地文件发送到您的实例 $HOME 文件夹:
$ gcloud compute scp ~/LOCAL-FILE :~/
  • 例如,要将名为 hello_world.txt 的文件发送到 sparrow,您将运行以下命令:
$ gcloud compute scp ~/hello_world.txt  sparrow:~/
  • 类似地,要从实例下载文件到您的本地机器 $HOME 文件夹:
$ gcloud compute scp  <instance-name>:~/REMOTE-FILE ~/

我们将在下一章中探讨 gsutilbq 命令行工具,在第四章中探讨 Datalab CLI(使用 BigQuery 查询您的数据)。

管理虚拟机

当你开始使用 Google Compute 上的虚拟机时,你将想要执行几个操作,例如启动实例、停止实例、调整磁盘大小和修改磁盘、以及创建快照。我们将介绍其中最重要的:

  1. 启动和关闭虚拟机:
$ gcloud compute instances start sparrow --project packt-gcp 
$ gcloud compute instances stop sparrow --project packt-gcp
  1. 检查虚拟机状态:
$ gcloud compute instances list --project packt-gcp

我们开始时使用的实例是一个 f1-micro,对于实际的数据科学项目来说,CPU、RAM 或磁盘空间都不够。我们想更改底层机器并增加其磁盘空间。但在那之前,我们应该对我们的当前机器进行快照作为备份。如果出了问题,我们能够从快照中恢复实例:

  1. 对虚拟机进行快照:
$ gcloud compute disks snapshot [DISK_NAME]
  1. 在我们的例子中,让我们在运行时将我们的磁盘命名为 sparrow-backup
$ gcloud compute disks snapshot sparrow-backup --project packt-gcp
  1. 更改机器类型时,你首先需要停止你的实例,使用命令 $ gcloud compute instances stop sparrow --project packt-gcp。一旦完成,就可以使用通用命令更改机器类型:
$ gcloud compute instances set-machine-type INSTANCE --machine-type MACHINE-TYPE
  1. 在我们的例子中,如果我们想将类型更改为 n1-standard-1(3.75 GB 内存和 1 个 vCPU),我们应该运行以下命令:
$ gcloud compute instances set-machine-type sparrow --machine-type n1-standard-1
  1. 当我们处理这些时,我们还想将底层磁盘从 10 GB 调整到 100 GB:
$ gcloud compute disks resize sparrow --size 100
  1. 另一个重要的设置是确保在删除实例时不会删除磁盘:
$ gcloud compute instances set-disk-auto-delete

这是一个重要的参数,也可以在计算引擎控制台中设置,通过在创建或编辑实例时取消选择“实例删除时删除启动磁盘”:

图片

  1. 实例配置:整个实例配置可以通过 $ gcloud compute instances describe sparrow 获取。

  2. 从头创建正确的虚拟机:在这个例子中,当你从头创建虚拟机时,所有这些参数都是可用的。运行以下命令将在 europe-west1-c 区域创建一个新的 n1-standard-1 实例,名为 hummingbird,在 Ubuntu 17.04 上运行,带有 100 GB 的磁盘,也命名为 hummingbird。请注意,这个实例是可抢占的(--preemptible),并且磁盘在实例删除后将持续存在(--no-boot-disk-auto-delete):

$ gcloud compute --project packt-gcp instances create hummingbird \
--zone europe-west1-c --machine-type n1-standard-1 \
--image ubuntu-1704-zesty-v20171011 --image-project ubuntu-os-cloud \
--boot-disk-size 100  --boot-disk-type "pd-standard"  \
--boot-disk-device-name hummingbird \
--preemptible --no-boot-disk-auto-delete

我们可以验证,现在我们的项目中已经有了两个实例:

为了控制我们的资源,我们应该使用以下命令删除这个新实例:

$ gcloud compute instances stop hummingbird --zone europe-west1-c  --project packt-gcp

注意,如果你在 config 设置或作为环境变量中设置了不同的默认区域,在删除实例之前,你需要指定实例的区域;否则,将生成一个 资源未找到 的错误信息。

IP 地址

您一定注意到了与我们的 VM 关联的内部和外部 IP 的存在。每个 GCP 项目都附带一个虚拟专用云VPC)网络,该网络与项目自动创建。VPC 基本上是一个私有和隔离的虚拟网络分区,它使您的资源能够在给定项目内相互通信,同时允许控制对 VPC 的外部访问。创建时,每个实例都会分配一个内部 IP 地址,以便项目 VPC 内的其他资源能够与实例通信。要与 VPC 之外的对象通信,包括与互联网的连接,实例需要外部 IP 地址。

IP 地址,无论是内部还是外部,可以是短暂的或静态的。短暂的 IP 地址仅在实例运行期间与实例关联。当实例停止或被终止时,IP 地址将在全局 GCP IP 地址池中释放。为了使实例有一个稳定的 IP 地址,IP 地址需要变为静态。静态地址会产生额外的费用。

将 IP 地址的性质从短暂更改为静态可以通过控制台完成。停止 VM 并编辑它。在网络接口部分,选择 VM 的内部和外部 IP 地址的正确类型:

图片

可以从VPC 网络控制台访问 IP 地址和 VPC 的管理。

您可以直接从外部 IP 地址页面创建一个新的静态 IP 地址并将其附加到您的实例:

图片

点击“预留静态地址”,选择区域类型为“区域”,设置区域为您的实例所在区域,并将其附加到您的麻雀实例:

图片

创建静态 IP 并将其添加到实例的命令行等效操作是:

$ gcloud compute --project=packt-gcp addresses create sparrow-notebook --region=us-east1
$ gcloud compute --project=packt-gcp instances add-access-config sparrow --zone=us-east1-d --address=IP_OF_THE_NEWLY_CREATED_STATIC_ADDRESS

由于静态 IP 地址即使在未使用时也会计费,因此在不需用时释放它们是很重要的。

在 VM 上设置数据科学栈

因此,现在我们有一个正在运行的虚拟机,我们可以向它发送文件,连接到它,并修改它。一切准备就绪,我们可以为数据科学设置它!

我们将从 continuum 安装 Python Miniconda 栈,它比完整的 Conda 发行版小得多。通过 SSH 连接到您的实例。

  1. 使用以下命令安装 mini sudo apt-get update sudo apt-get install bzip2 wget from repo.continuum.io/miniconda/Miniconda2-latest-Linux-x86_64.sh
bash Miniconda2-latest-Linux-x86_64.sh
  1. 然后使用conda安装 Python 栈:
$ conda install scikit-learn pandas jupyter ipython

不要忘记做这件事:

$ source .bashrc

将 ipython 控制台框起来

要在您的实例中启动 Jupyter Notebook 并通过 Web 访问它,您需要将默认提供的虚拟机的短暂外部 IP 地址提升为静态外部 IP。

您还需要确保您的实例正在接受 HTTP 和 HTTPS 流量。为此,前往您的虚拟机页面,编辑它,并检查以下复选框:

由于您的 Jupyter Notebook 对网络上的所有流量都是开放的,您需要通过密码保护它:

  1. 生成配置:
$ jupyter notebook --generate-config
  1. 使用以下方式添加密码:
$ jupyter notebook password

更多关于如何保护您的公共 Notebook 的信息,包括使用 ssh 密钥和添加加密,可在jupyter-notebook.readthedocs.io/en/latest/public_server.html找到。

  1. 使用以下命令启动 Jupyter Notebook:
$ jupyter notebook --ip=0.0.0.0 --port=8888 --no-browser &

这将生成一个令牌和以下消息:

The Jupyter Notebook is running at: http://0.0.0.0:8888/?token=7b1deb1b1467a3b3c9c23946e2f2efa12d9dc2c258353660
and access it in your browser via http://104.196.129.173:8888/?token=7b1deb1b1467a3b3c9c23946e2f2efa12d9dc2c258353660

故障排除

如果您在访问 Notebook 时遇到问题,有一个替代解决方案。想法是使用 IP 0.0.0.0 启动 Jupyter Notebook,而无需先设置静态 IP:

jupyter notebook --ip=0.0.0.0 --port=8888 --no-browser

这将生成一个令牌。然后在另一个终端中 SSH 进入,添加以下标志 --ssh-flag="-L" --ssh-flag="2222:localhost:8888"

$ gcloud compute ssh sparrow --ssh-flag="-L" --ssh-flag="2222:localhost:8888"

这就是如何将 URL localhost:2222 与 Jupyter Notebook URL localhost:8888 关联起来。然后您可以在 http://localhost:2222 访问您的 Notebook。您还需要输入刚才给出的令牌。

这个替代方案是由 Jeff Delaney 在以下博客文章的评论中给出的,在 Google Cloud Engine 上运行 Python Jupyter Notebookjeffdelaney.me/blog/running-jupyter-notebook-google-cloud-platform/

将 GPU 添加到实例

检查并请求增加您的 GPU 配额:

在 Google Compute 上使用 GPU 时有一些限制。GPU 不可用于共享或可抢占的机器。GPU 实例会在常规(每周)维护事件中终止。有关限制的最新信息,请参阅cloud.google.com/compute/docs/gpus/。有关根据您所需的 GPU 数量了解可用机器类型的信息,请参阅cloud.google.com/compute/docs/gpus#introduction

从控制台创建带有 GPU 的虚拟机:

  1. 前往虚拟机控制台,点击创建实例

  2. 选择一个兼容 GPU 的区域

  3. 点击自定义机器类型,然后再次点击 GPU 链接

  4. 选择所需的 GPU 数量和关联的类型:

类似地,您可以使用以下命令使用 gcloud 创建启用 GPU 的实例:

$ gcloud compute instances create [INSTANCE_NAME] \
--machine-type [MACHINE_TYPE] --zone [ZONE] \
--accelerator type=[ACCELERATOR_TYPE],count=[ACCELERATOR_COUNT] \
--image-family [IMAGE_FAMILY] --image-project [IMAGE_PROJECT] \
--maintenance-policy TERMINATE --restart-on-failure \
--metadata startup-script='[STARTUP_SCRIPT]'

其中 --accelerator type= 指定 GPU 类型,count= 指定 GPU 数量。

例如,此命令将在us-east1-d区域创建一个带有 NVIDIA® Tesla® K80 GPU 和两个 vCPU 的 Ubuntu 1604 实例。启动脚本元数据指示实例安装 CUDA 工具包及其推荐的驱动程序版本:

$ gcloud compute instances create gpu-instance-1 \
--machine-type n1-standard-2 --zone us-east1-d \
--accelerator type=nvidia-tesla-k80,count=1 \
--image-family ubuntu-1604-lts --image-project ubuntu-os-cloud \
--maintenance-policy TERMINATE --restart-on-failure \
--metadata startup-script='#!/bin/bash
echo "Checking for CUDA and installing."
# Check for CUDA and try to install.if ! dpkg-query -W cud
a-8-0; then
  curl -O http://developer.download.nvidia.com/compute/cuda/repos/ubuntu1604/x86_64/cuda-repo-ubuntu1604_8.0.61-1_amd64.deb
  dpkg -i ./cuda-repo-ubuntu1604_8.0.61-1_amd64.deb
  apt-get update
  apt-get install cuda-8-0 -y
fi'

启动脚本为 Ubuntu 安装正确的 CUDA 驱动程序。对于其他驱动程序和操作系统,请按照developer.nvidia.com/cuda-downloads上的说明操作。

一旦驱动程序安装完成,您可以验证它是否已正确安装:

  1. 通过ssh连接到您的实例

  2. 输入nvidia-smi以查看您的驱动程序版本以及您有多少 GPU 内存

命令nvcc --version显示当前的 CUDA 版本。

启动脚本和停止脚本

启动脚本允许您在启动或创建实例时运行脚本。例如,要始终在创建新实例时安装miniconda和相关数据科学包,只需在您的本地机器上的文件中编写以下脚本:

#! /bin/bash
apt-get install bzip2
wget https://repo.continuum.io/miniconda/Miniconda2-latest-Linux-x86_64.sh
sudo bash Miniconda2-latest-Linux-x86_64.sh
conda install scikit-learn
conda install pandas
conda install jupyter
conda install ipython

然后在启动新实例时,提供--metadata-from-file标志,后跟startup-script=PATH/TO/FILE,其中PATH/TO/FILE是启动脚本相对于当前目录的路径:

$ gcloud compute instances create example-instance --metadata-from-file startup-script=examples/scripts/install.sh

您也可以在命令行或从存储在 Google 存储中的文件中使用扩展启动脚本。有关启动脚本更多信息,请访问cloud.google.com/compute/docs/startupscript

停止脚本是在实例终止或重启时自动运行的脚本。与启动脚本类似,您可以在创建实例时通过添加--metadata-from-file标志,后跟shutdown-script=PATH/TO/FILE来关联一个停止脚本:

$ gcloud compute instances create example-instance --metadata-from-file shutdown-script=examples/scripts/shutdown.sh

资源和进一步阅读

以下是一些关于在 GCE 上设置 TensorFlow 的有趣文章:

一些与 Docker 相关的资源:

摘要

GCE 是 GCP 的核心服务,提供基于不同操作系统的各种可扩展虚拟机。可用的操作系统种类繁多,机器的 CPU、GPU 范围,从小到巨大的磁盘空间和 RAM,使 GCE 成为一个强大的云环境,适用于各种项目和场景。

在本章中,你学习了:

  • 如何创建、启动、备份、修改和访问多个虚拟机 (VM)

  • 与虚拟机 (VM) 相关的不同参数和变量

  • 如何访问和使用 Google Shell

  • 如何使用 gcloud CLI 在 GCP 中执行相同的操作

  • 如何安装数据科学 Python 栈

  • 如何启动 Jupyter Notebook

GCE 提供了更多由其强大和灵活的特性能带来的可能性,我们尚未涉及。希望到本章结束时,你应该能够在一个以数据为中心的背景下,舒适地使用适合你项目的实例。

在下一章中,我们将学习如何使用 Google Storage 和 Google SQL 在 GCP 上存储数据。

第三章:Google Cloud Storage

在本章中,我们将探讨来自 Google Cloud 平台的两项数据存储服务:用于文件的 Google Storage 和用于结构化数据的 Google SQL。Google Storage 是一种文件托管服务,允许您在云端存储文件。非常简单直接,它与 Amazon S3 服务非常相似。为了超越基本使用,我们将探讨一些高级主题,例如签名 URL、协作存储桶设置、优化上传速度以及传输大型数据集。

Google SQL 是 Google 的简单 SQL 数据库服务。在 SQL 类型方面,目前不如 Amazon SQL 服务发达。它提供 MySQL 作为主要数据库。PostgreSQL 的 beta 版本也可用。我们将探讨两者在各自功能方面的差异。

在使用基于云的解决方案进行数据科学项目时,让服务相互通信,或者更确切地说,允许不同数据存储和数据消费者之间的数据传输,通常是困难所在。

诸如容量、延迟和吞吐量等问题可能会影响您脚本的运行速度和您工作的效率。设置适当的权限级别,以便您的数据可以在项目环境中访问,同时仍然受到外部访问的保护,这也是关键。

本章中,您将学习:

  • 如何使用 gsutil 在 Google 存储上存储文件

  • 如何在 Google SQL 上创建数据库并使用数据填充它们

  • 如何访问您的 Google 存储文件和您的 Google SQL 数据库

在本章结束时,您将能够使用 Google Cloud 平台提供的服务上传数据并管理它,并了解 Google Cloud SQL 在创建关系型数据库管理系统方面的潜力。

Google Cloud Storage

云存储是一种在网络上可用的计算机上存储数据的模型,其中数据存储在多个服务器上,真实或虚拟,通常托管在第三方设施或专用服务器上。通过这种模型,无论身在何处,使用任何合适的设备,都可以访问个人或商业信息,无论是视频、音乐、照片、数据库还是文件,而无需知道数据的物理位置。这种方法的优势众多:内存空间无限,只需支付实际使用的内存量,文件可以从世界任何地方访问,维护成本大幅降低,安全性更高,因为文件受到盗窃、火灾或本地计算机可能发生的损坏的保护。

Google Cloud Storage 是 Google 提出的云存储方案:它是由 Google 提供的一项开发者服务,允许您直接在 Google 的基础设施上保存和操作数据。更详细地说,Google Cloud Storage 提供了一个编程接口,它使用简单的 HTTP 请求在其基础设施上执行操作。使用 Google Cloud Storage,您可以执行以下操作:上传文件、下载文件、删除文件、获取文件列表或获取给定文件的大小。每个这些 HTTP 请求都包含有关使用的方法和请求的资源的信息。因此,可以创建一个应用程序,通过这些 HTTP 请求提供一项服务,在该服务中,应用程序可以远程保存数据,通常通过第三方服务器。

Google 存储平台是一个企业存储解决方案,提供三种不同访问需求和相关定价的存储级别:

  • 标准存储用于快速访问大量数据。在$0.026/GB 的价格下,它提供了对请求的高响应速度。

  • 持久性降低可用性DRA)适用于长期数据存储和偶尔访问。它的价格较低,为$0.02/GB。

  • 最后,近线存储适用于更不频繁的访问。它是该服务的最便宜版本,响应时间更长。它是目前价格最低的选项,目前价格为$0.01/GB。

盒式存储与驱动器

Google Drive 用于存储个人文件。在所有不同的个人服务(如电子邮件、照片等)中,它提供高达 15 GB 的免费存储空间。它还提供每月付费的额外数据存储量(例如,每月 9.99 美元用于 1 TB)。但是,与 Google Storage 相比,没有数据压缩或数据加密。它不是由 Google 作为具有相关功能、支持和可靠性的企业平台提供的。没有高级管理功能,如元数据,也没有通过存储桶进行的数据组织管理。也没有高级存储层(冷数据与热数据)。

Google Drive 服务的特点如下:

  • 没有基于层的数据存储

  • 它不是一个商业解决方案

  • 它不支持高级数据管理功能

  • 存储计划限制在 30 TB

要了解 Google Cloud Storage 提供的潜力,请参阅以下内容:

  • 它提供无限空间

  • 由于使用了 OAuth,它比 Google Drive 具有更高的安全性

  • 它提供高级存储级别

  • 它为高级企业服务和应用程序的集成提供 REST API 支持

  • 它在出错后有可能恢复数据传输

通过分析 Google Cloud Storage 提供的一些功能,其优越性显而易见,这证明了其成本是合理的。

访问控制列表

文档中说得最好,访问控制列表ACLs)允许您控制谁可以读取和写入您的数据,以及谁可以读取和写入 ACLs 本身。

如果在对象上传时未指定(例如,通过gsutil cp -a选项),

对象将使用存储桶上设置的默认对象 ACL 创建(参见gsutil help

defacl cloud.Google.com/storage/docs/gsutil/commands/defacl]。你可以

使用gsutil acl set命令替换对象或存储桶的访问控制列表(ACL),或使用gsutil acl ch命令修改现有的 ACL(参见gsutil help aclhttps://cloud.Google.com/storage/docs/gsutil/commands/acl))。

ACL 分配给对象(文件)或存储桶。默认情况下,存储桶中的所有文件都具有相同的

与它们所在的存储桶相同的 ACL。

有几点需要注意:

  • 对象没有写访问权限;尝试设置带有写

    权限将导致错误

  • 对象 ACL 决定了独立于存储桶 ACL 读取设置的读取访问权限。(文件夹权限不覆盖文件访问。)

可用几个预设。可用的预定义 ACL 有:

  • 项目私有:根据其角色给予项目团队权限。

    团队中的任何人都拥有读取权限,以及项目所有者和

    项目编辑者拥有所有者权限。这是新创建

    存储桶。这也是新创建对象的默认 ACL,除非默认

    该存储桶的对象 ACL 已更改。有关更多详细信息,请参阅 gsutil 帮助

    项目。

  • 私有:给予请求者(以及只有请求者)对对象的拥有者权限

    存储桶或对象。

  • 公共读取:给予所有用户(无论是否登录或匿名)读取权限。

    当你将此应用于对象时,互联网上的任何人都可以读取该对象

    无需认证即可访问。

  • 公共读写:给予所有用户读写权限。此 ACL 适用于

    仅限于存储桶。将存储桶设置为公共读写将允许互联网上的任何人

    互联网上传任何内容到您的存储桶。您将对此负责

    内容。还有其他设置,请查看文档。

通过 Web 控制台进行访问和管理

在前面的章节中,我们介绍了 GCP 提供的服务。现在,让我们看看如何访问该服务以及如何管理它。为此,Google 创建了一个基于 Web 的界面,用于完全在线服务管理,以及 gsutil,这是一个命令行工具,允许你在服务上执行所有需要的操作。

gsutil

gsutil 是命令行工具,是 gcloud 脚本子集的一部分,允许您管理 Google Storage 上的存储桶和对象(文件)。通过 gsutil 可用的操作从创建存储桶和移动文件等简单直接命令,到管理设置、存储类和权限的管理命令。更高级的命令包括通过 ACL 设置访问控制、定义生命周期规则(1 年后删除所有文件)、日志记录、创建通知和通过 perfdiag 进行故障排除。

让我们从简单的命令开始。我们将创建一个名为 packt-gcp 的存储桶,上传一些文件,获取这些文件的信息,移动它们,并更改存储类。

gsutil 快速参考

以下是我们可以通过 gsutil 发出的最常用命令列表:

  • 创建名为 packt-gcp 的存储桶:

    • gsutil mb gs://packt-gcp
  • 将文件上传到存储桶:

    • gsutil cp gs://packt-gcp/
  • 在存储桶中创建子目录:

    • gsutil cp your-file gs://packt-gcp/
  • 列出文件夹:

    • gsutil ls gs://packt-gcp/
  • 获取 gsutil 命令的帮助:

    • gsutil help
  • 我们使用了多少存储空间(-h 使其可读):

    • gsutil du -h gs://packt-gcp/
  • 将整个文件夹复制到存储桶中:

    • gsutil cp -r gs://packt-gcp/

例如,我有一个包含一些图片的本地 ./img 目录。我可以使用以下命令复制整个目录并同时在存储桶中创建子目录:

gsutil cp -r ./img gs://packt-gcp/

让我们分析一个特别有用的选项:我指的是 -m 标志。如果您正在执行一系列 gsutil 操作,那么如果您使用 gsutil -m -o(这意味着并行运行),它可能会运行得更快。如果您在合理快速的网络连接上对大量文件执行操作,这可以显著提高性能,但如果您使用较慢的网络,可能会降低性能。

-m 标志特别适合以下命令:lsmbmvrbdu

高级 gsutil

之前,我们看到了一些简单的基本命令,我们可以使用 gsutil 导入。但使用这个工具,我们还可以做其他事情:

  • gsutil 支持通配符(和 ?)并将通配符限制在文件上。要包括文件夹在通配符目标中,请将符号加倍:gsutil ll gs:///**.txt 将列出子目录中的所有文本文件。

  • gsutil 需要使用 UTF-8 字符编码。对于 Windows:要使用 Unicode 字符,您需要在第一次在该命令壳中使用 gsutil 之前运行此命令:chcp 65001

  • 您本地机器用户路径中的 .boto 文件是 gsutil CLI 的配置文件。您可以直接编辑它或通过 gsutil config 命令进行编辑。.boto 文件中的一些有趣参数如下:

    • parallel_composite_upload_threshold:此参数用于指定单个流中要上传的文件的最大大小。大于此阈值的文件将并行上传。parallel_composite_upload_threshold参数目前默认禁用。

    • check_hashes:此参数用于在下载数据时执行完整性检查,始终、从不或条件性。

    • prefer_api:此参数值是 JSON 或 XML。

    • aws_access_key_id:此参数用于与 S3 的互操作性。

    • aws_secret_access_key:此参数用于与 S3 的互操作性。

签名 URL

签名 URL 是用于桶和对象的查询字符串认证机制。签名 URL 提供了一种方式,无论持有人是否有 Google 账户,都可以给予时间有限的读取或写入访问权限。

为什么使用签名 URL?有时,需要控制没有 Google 账户的用户访问权限。为了授予这些用户访问 Google Cloud Storage 的权限,我们可以向他们提供签名 URL,这允许用户在有限的时间内对该资源进行读取、写入或删除访问。在 URL 过期之前,资源访问权限允许给 URL 的所有者。

创建签名 URL 有两种方式:

  • 使用gsutil创建签名 URL

  • 使用程序创建签名 URL。

创建签名 URL 最简单的方法是使用gsutil signurl命令。为此,您首先需要生成一个私钥或使用现有的私钥。要创建私钥,您首先需要为服务账户创建一个 OAuth 客户端 ID。

要创建新的私钥,请访问:console.cloud.Google.com/apis/credentials?project=packt-gcp

按照说明操作并下载文件。有两种格式可供选择:

  • JSON:在生产环境中使用应用默认凭证且不在 Google Cloud 平台之外时,此参数是必需的。

  • PKCS12:被许多不同的编程语言和库支持。

要限制可以访问 GCP 的角色:将 JSON 文件下载到您的计算机上。

您现在可以使用以下命令使用gsutil为您的文件之一创建签名 URL:

gsutil signurl -d 10m -m GET Desktop/privatekey1.json gs://packt-gcp/file1.csv

请记住,签名 URL 在目录上不工作。如果您想给多个文件提供访问权限,可以使用通配符,如下所示:

gsutil signurl -d 10m -m GET Desktop/privatekey1.json gs://packt-gcp/img/.png

但这将为img/文件夹中的每个.png文件生成一个签名 URL。

签名 URL 也可以用于上传文件(-m PUTPOST),并使用 -c text/plain-c image/jpg 指定内容类型。

在 Google Cloud Storage 中创建存储桶

正如我们之前所说的,存储桶是一个容器,用于存放您的数据。在 Google Storage 中保存的所有内容都必须包含在存储桶中,对应于文件夹。您可以使用它们来组织和控制对数据的访问,但与文件夹不同,您不能创建子存储桶。在 Google Storage 中,单个数据以对象的形式保存。这样的对象可以是任何类型、扩展和尺寸的文件;使用 BigQuery 创建的表也被视为对象,正如我们将在第四章中看到的,使用 BigQuery 查询您的数据。与单个作业相关的所有对象都必须包含在存储桶中。

对象是不可变的,因此不能直接在 Google Storage 中编辑对象。重要的是要说明,不可能对对象的任何内容进行任何更改;如果您想修改存储在 Google Storage 中的对象,它只能被替换。如果您不想删除它并重新加载它,您可以通过覆盖它来执行相同的操作,只需一个操作即可。

Google Storage 命名空间

Google Storage 使用分层结构来存储存储桶和对象。所有的存储桶都位于这个单一的环境中,而所有的对象也都在某个存储桶内的层次结构中。基于这种分层结构,存储桶名称在整个现有的 Google Storage 存储桶中必须是唯一的。每个存储桶名称在整个云存储命名空间中都必须是唯一的。

给存储桶命名时,我们不能使用已被其他用户选中的名称。虽然对象必须在给定的存储桶内具有唯一名称,但因此我们可以有多个存储桶,这些存储桶始终包含具有相同名称的对象。

存储桶命名

给存储桶命名时,我们必须遵循由事实强加的一些规则;正如我们预期的,所有的存储桶都位于唯一的 Google Storage 命名空间中。存储桶名称必须遵守以下规则:

  • 它们的长度必须是 3 到 63 个字符

  • 它们必须以数字或字母开头和结尾

  • 它们必须只包含小写字母、数字、点和破折号

  • 它们不能表示为点分十进制格式的 IP 地址

  • 它们不能以 GOOG 前缀开头

  • 它们不能包含两个相邻的点或点前后的破折号

最后,应该注意的是,一旦您创建了存储桶的名称,您就不能更改它。要更改它,您应该创建一个新的存储桶,并使用所需的名称,然后将旧存储桶的内容移动到新存储桶中。

对象命名

在 Google Storage 中,单个数据以对象的形式保存。对象名称必须遵守以下规则:

  • 它们可以包含任何有效的 Unicode 字符序列,当以 UTF-8 编码时,长度为 1-1,024 字节

  • 它们不能包含回车或换行符

  • 它们不能以知名/acme-challenge 开头

为了消除扁平层次结构带来的限制,我们可以在对象名称中使用斜杠(/)字符。例如,如果我们必须为两个不同的对象使用相同的名称,我们可以将一个对象命名为 /NewYork/Stadium.jpg,另一个命名为 /Boston/Stadium.jpg。这样就可以像在目录内一样组织对象,并按此方式管理它们。实际上,Google Storage 将对象视为独立的对象,它们之间没有层次关系。

创建存储桶

要在 Google Cloud Storage 中操作,我们可以使用两个工具:控制台和 gsutil。第一个工具使用典型的图形界面,而第二个使用命令行窗口。

Google Cloud Storage 控制台

首先,我们将了解如何使用 Google Cloud Storage 控制台创建存储桶。首先,当我们访问它时,我们可以通过在主 Google Cloud Platform 的资源部分左侧点击进入 Cloud Storage,从而进入该控制台。以下窗口将会打开:

图片

在打开的窗口中,所有与账户相关的存储桶都会列出。对于每一个,都有以下特性:

  • 名称:存储桶名称。

  • 默认存储类:默认分配给存储桶中添加的对象的存储类。

  • 位置:数据存储的位置。

  • 生命周期:允许您设置自动删除对象或降低其存储类别的规则。

  • 标签:帮助组织您的存储桶。标签也会包含在您的账单中,因此您可以看到标签间成本分布。

  • 请求者付费:如果开启,对存储桶数据的请求将计费到请求者的项目中。

要创建一个新的存储桶,只需在 Google Cloud Storage 浏览器顶部点击 CREATE BUCKET 按钮。随后会打开以下页面:

图片

在打开的页面上,您需要指定以下信息:

  • 名称:记住,它必须在 Cloud Storage 中是唯一的

  • 默认存储类:有四种选项可供选择:多区域、区域、近线和冷线

  • 位置:此下拉菜单允许我们选择位置

在做出这些选择后,只需点击 CREATE 按钮,就会在 Google Cloud Storage 浏览器中添加一个新的存储桶。

现在我们有了容器,是时候用内容来丰富它了。为此,我们首先需要通过点击其名称来访问简单的存储桶。以下页面将会打开:

图片

在打开的页面上,所有存储桶的内容都会列出。对于每个对象,都会提供一系列信息:

  • 名称:对象名称

  • 大小:对象尺寸

  • 类型:对象类型

  • 存储类:分配给存储桶中添加的对象的存储类

  • 最后修改:对象的最后修改日期

要将另一个对象添加到存储桶中:

  1. 简单地单击 Google Cloud Storage 浏览器顶部的“上传文件”按钮。将打开一个对话框窗口。

  2. 在对话框窗口中,选择您想要上传到存储桶的文件或文件夹。

  3. 点击打开。

如果我们使用 Chrome 作为浏览器,则可以进行文件夹上传。这样,我们可以将整个文件夹的内容加载到存储桶中。否则,我们可以首先使用“创建文件夹”按钮创建一个文件夹,然后单独上传所有对象。

要重命名、复制和移动对象,只需单击与对象关联的更多选项按钮(三个垂直点)。将打开一个上下文菜单。在其中我们可以执行以下操作:

  • 编辑权限

  • 编辑元数据

  • 复制

  • 移动

  • 重命名

对于每个选项,都会打开一个相对窗口,它将指导我们进行操作。

Google Cloud Storage gsutil

我们已经看到,使用 Google Cloud Storage 浏览器简单直观。但在某些情况下,有必要通过命令窗口进行操作。在这种情况下,正如我们在前面的章节中看到的,我们可以使用gsutil。之前,我们已经通过gsutil看到了一系列命令。

要使用gsutil创建存储桶,请使用mb命令。例如,使用以下命令:

gsutil mb gs://NameBucket1

这将创建一个名为NameBucket1的存储桶。重要的是要记住,存储桶名称必须在整个 Google Storage 命名空间内是唯一的。如果其他用户已经创建了一个我们想要使用的名称的存储桶,我们必须选择另一个名称。

一旦创建了存储桶,我们就可以导入其中的对象。使用cp命令,我们可以将文件从我们的计算机复制到 Google Storage:

gsutil cp figure.jpg gs://NameBucket1/fig.jpg

这将把图像figure.jpg从我们在 shell 中定位的文件夹复制到NameBucket1存储桶中,并将对象重命名为fig.jpg

要列出我们的存储桶或其中包含的对象,您必须使用ls命令。

ls命令在类 Unix 操作系统中用于显示有关文件和目录的信息。如果我们打开一个终端并输入ls,我们将得到当前目录中文件和目录的列表。

以下命令列出了我们创建的所有存储桶:

gsutil ls

当此命令列出NameBucket1中包含的对象时:

gsutil ls gs: // NameBucket1

您还可以使用ls命令的-L选项来获取有关对象和存储桶的更多信息。以下命令提供了关于对象大小、最后修改日期、数据类型以及NameBucket1存储桶中包含的所有对象的访问控制列表(ACL)的信息:

gsutil ls -L gs: // NameBucket1

而以下命令提供了关于我们的存储桶的信息,例如包含的对象数量、总大小以及所有存储桶的 ACL:

gsutil ls -L

最后,要将对象从一个存储桶转移到另一个存储桶,请使用mv命令,此命令也可以用于重命名对象。例如,以下命令将图像figure.pngNameBucket1存储桶转移到NameBucket2存储桶:

gsutil mv gs://NameBucket1/figure.png gs:// NameBucket2/

当此命令将图像figure.png重命名为fig.png时:

gsutil mv gs://NameBucket1/figure.png gs://NameBucket1/fig.png

生命周期管理

使用 Google Cloud Storage,对象的生命周期管理变得简单直接。对象的生命周期是从对象创建到其被销毁的时间。对象持续时间的规则在编程平台之间差异很大。在归档对象的情况下,我们指的是对象存储在其分配空间中的时间。

桶的生命周期可以通过其配置进行管理。配置包含一组应用于桶中所有对象的规则。当对象满足某个规则的准则时,就会执行对象上指定的操作。

要使用 Google Cloud Console 为桶启用生命周期:

  1. 前往打开 Google Cloud Platform 控制台,并点击云存储浏览器;以下窗口将打开:

图片

  1. 在此窗口中,检查生命周期列,选择桶,并点击条目;将打开一个新页面。

  2. 在此窗口中,点击添加规则。

  3. 在打开的页面中,我们可以通过选择条件和操作来指定配置。

  4. 最后,点击保存按钮。

可以执行类似的程序来检查在桶上设置的生命周期配置,以及禁用桶的生命周期管理。

Google Cloud SQL

Google Cloud SQL 是 Google Cloud Platform 的一个数据库服务,允许在云中为应用程序创建、管理和管理关系数据库。该服务得益于数据库将托管在其上的 Google 基础设施,保证了卓越的性能和可扩展性。与平台上的每个服务一样,使用的简便性是基础:无需安装任何软件,手动进行更新,或处理数据库的备份和复制。Google 平台将负责所有这些,并确保 99.95%的可用性。数据在保存到数据库和备份之前被加密,以保证基础设施的安全性。

您还可以为每个数据库实例设置网络防火墙,以直接调节对实例的访问。该服务可以与任何兼容 MySQL 或 PostgreSQL 的基于云的应用程序或网站一起使用,实例可以通过应用引擎、计算引擎和平台工作站轻松访问。

服务的费用不需要任何初始支付,而是按分钟计算整个使用期间的费用,并且只对实际分配的资源收费。在 MySQL 数据库的情况下,Cloud SQL 实例的标准价格范围是每小时$0.0150 – $4.0240(最高可达 208 GB 的 RAM),而对于存储空间,备份的价格为每 GB/月$0.08,HDD 存储的价格为每 GB/月$0.09,SSD 存储的价格为每 GB/月$0.17。此外,还需要加上通过计算引擎管理网络的成本。

支持的数据库

使用 Google Cloud SQL,可以创建两种类型的数据库:

  • MySQL

  • PostgreSQL

MySQL 是一个开源的关系型数据库管理系统RDBMS),可以通过结构化查询语言SQL)免费使用。SQL 是用于在数据库中添加、访问和管理内容的最流行语言。它以其快速处理、可靠的可靠性、易用性和使用灵活性而闻名。MySQL 几乎是所有开源 PHP 应用程序的必要组成部分。

PostgreSQL 是一个具有悠久历史的先进数据库系统。它在广泛的平台上可用,既用于微小的嵌入式系统,也用于巨大的多 TB 系统。多年来,PostgreSQL 凭借其创新功能、数据完整性、安全性和可靠性赢得了卓越的声誉。

PostgreSQL 支持目前仍处于测试阶段,因此复制或高可用性等特性不可用。它们支持的云平台实例支持 MySQL 5.7 或 PostgreSQL 9.6 数据库,并允许最高达到 208 GB 的 RAM、32 个 CPU 和 10 TB 的存储。数据在 Google 网络上以及表和备份中加密,可以通过通过应用引擎(用 Java、Python、PHP、Node.js、Go 或 Ruby 编写)创建的应用程序访问,通过 MySQL/PostgreSQL 客户端使用标准协议,甚至可以通过外部应用程序访问,并可以使用如 SSL 等安全连接协议。

通常,标准 MySQL/PostgreSQL 实例在本地提供的功能与 Cloud SQL 实例提供的功能之间没有明显的差异。特别是,我们提醒您,使用CREATE FUNCTION语句在 MySQL 中创建新功能是不可能的,并且缺少SUPER(MySQL)和SUPERUSER(PostgreSQL)权限。

Google Cloud SQL 性能和可扩展性

Cloud SQL 提供卓越的性能和可扩展性。如前所述,最大存储容量为 10 TB(将在 HDD 和 SSD 之间分配),而可选择的最高 RAM 为 208 GB,最大 CPU 数量为 32。Google 还保证最大 IOPS(每秒输入/输出操作)为 25,000,即每秒可以进行 25,000 次输入/输出操作。因此,选择这样的资源使我们能够使用 Cloud SQL 来满足各种类型的应用需求,从负载较低的应用到需要强大性能的应用。每秒查询数(QPS)没有限制,但有关连接有一些限制。实际上,在第二代 MySQL 实例的情况下,最多可以支持 4,000 个并发连接,而其他情况下的限制则更为严格。

对于第一代 MySQL 数据库,只有在建立连接之前,传入请求会短暂排队,此时同时存在的排队连接数限制为 100。数据库也易于垂直扩展,增加或减少可用资源(存储空间、CPU 和 RAM),以及水平扩展同时工作的服务器和实例数量。一旦创建了实例,您可以直接从控制台更改为其分配的资源。通常,更改会立即生效,但实例的重启将关闭现有连接,并需要几秒钟(在第一代 MySQL 的情况下)或几分钟(在第二代的情况下)才能重新上线。

Google Cloud SQL 的安全性和架构

关于安全性,Cloud SQL 上的数据在表中、临时文件中、备份副本中以及在 Google 基础设施内从一台服务器到另一台服务器使用安全网络协议移动时都进行了加密。因此,平台基础设施旨在确保在信息管理的每个阶段都保证其数据的安全性。用户无需采取任何行动:平台通过计算引擎自动加密数据,因此它提供的所有服务都将符合这些要求。

从设计角度来看,全球范围内的基础设施安全性是按照分层架构组织的。这种架构允许安全地使用服务、数据和用户、管理员之间的通信。

从最底层开始,即硬件层面,我们拥有谷歌自主设计和建设的数据中心,仅对少数员工开放,并由金属探测器、摄像头或生物识别技术等设备进行监控。单个数据中心由多个连接到单个本地网络的服务器组成:始终由谷歌负责验证其自行设计和供应商提供的组件是否符合公司的安全标准。单个服务器机器,通过数字签名在各个低级组件上进行单独标识,以便易于追踪,并在每次启动时进行验证。

转到软件层面,每个编写并用于在平台上运行的应用程序都会在多台机器上以多个副本运行,以使用每个工作负载所需的资源量。软件架构是多租户的,其中软件的单个实例运行在服务器上,并为多个租户(即共享实例访问权限的用户)提供服务。应用程序的任务是为每个租户提供实例的专用部分。这一概念与多实例架构截然相反,在多实例架构中,有多个软件实例,每个实例都专门为单个客户端服务。

创建 Google Cloud SQL 实例

Google Cloud SQL 实例是一组管理数据库文件的记忆结构。实例管理其关联的数据并为数据库用户提供服务。每个运行的 Google Cloud SQL 数据库都至少关联到一个数据库实例。

Cloud SQL 实例是完全管理的,关系型 MySQL 和 PostgreSQL 数据库。谷歌负责复制、补丁管理和数据库管理,以确保可用性和性能。创建实例时,请选择适合您应用程序的大小和计费计划。

要创建 Google Cloud SQL 实例:

  1. 请访问以下网址查看 Google Cloud SQL 实例页面:console.cloud.google.com/sql/instances.

  2. 点击页面中间的创建实例按钮。一个引导序列将引导我们创建实例。

  3. 选择数据库类型;有两个选项可供选择:MySQL 和 PostgreSQL(MySQL 是默认选择)。做出选择后,点击下一步按钮。

  4. 选择 MySQL 实例类型;有两个选项可供选择:MySQL 第二代(推荐)和 MySQL 第一代。MySQL 第二代提供高性能、高存储容量和低成本。MySQL 第一代是 Cloud SQL 的较旧版本,提供基本性能和存储容量。要做出选择,请点击选择第二/第一代按钮。

  5. 新页面被打开。在这个页面上,必须做出一系列选择,如下面的截图所示:

截图

首先的选择涉及实例的名称。这是一个从外部可见的名称,因此建议不要在该名称中包含敏感或个人信息。此外,在实例名称中包含项目 ID 也不是必要的;如果需要,它将自动添加。

  1. 设置'root'@'%'用户的密码。

  2. 设置区域。

  3. 设置配置选项。

  4. 最后,点击“创建”按钮。

这样,一个新的实例就已经创建好了。

摘要

在本章中,我们发现了 Google Cloud Platform 提供的两项额外服务:Google Cloud Storage 和 Google Cloud SQL。Google Cloud Storage 是一个文件托管服务,允许您在云端存储文件。我们看到了如何存储视频、音乐、照片、数据库或文件,而无需知道数据的物理位置,无论在世界上的任何地方,使用任何合适的设备。我们通过浏览器或命令行执行了文件上传操作。

之后,我们了解了 Google Cloud SQL 在创建关系型数据库管理系统方面的潜力。使用 Google Cloud SQL,您可以创建、管理和管理关系型数据库。最初我们发现了该服务的特点,后来通过一个实际示例学习了如何创建新的数据库实例。

第四章:使用 BigQuery 查询您的数据

技术的演变在过去的几年中导致了能够自动化许多操作的电子设备数量的显著增加,无论是在商业世界还是在家庭中。这些设备每天产生大量数据,其数量在近年来呈指数级增长。

这些数据代表了一个巨大的资源,在过去,这种资源并没有被充分表达。今天,大型公司意识到,他们的活动成功至少部分地取决于从处理这些数据中获取的信息。但处理大量数据需要公司付出大量的努力——无论是技术上的还是人力上的。谷歌是第一批理解数据管理重要性的公司之一,随着时间的推移,它创造了一个今天可供公司和个人使用的科技背景。

在本章中,我们将介绍 BigQuery 和 Data Studio 平台,以管理和查看数据。首先,我们将探讨大数据以及管理大量数据所面临的问题。然后,我们将分析如何组织数据以及用于正确查询数据库的工具。考虑到这一点,我们将提出 SQL 语言的介绍。接着,我们将分析 Google BigQuery,这是一个能够实现大规模数据集交互式分析的网络服务。最后,我们将分析如何使用 Google Data Studio 从我们的数据中创建报告。

涵盖的主题包括:

  • 大数据

  • 查询数据库

  • SQL 语言

  • Google BigQuery

  • Google Data Studio

在本章结束时,读者将能够应用这些工具来分析他们的数据,而无需技术支持。我们将处理几个示例,以使这些工具在实际情况中的使用尽可能现实。

接近大数据

社交网络的爆炸式增长,加上智能手机不可阻挡的传播,证明了在创新、营销和信息技术的世界中,近年来反复出现的术语之一就是大数据。这个术语指的是以大量、高速和最多样化的格式产生的大量数据,其处理需要的技术和资源远远超出了传统数据管理和存储系统。但这个术语究竟包含了什么?

在一篇广为引用的文章《大数据时代》中,纽约时报的技术记者 Steve Lohr 这样解释大数据:

“什么是大数据?当然是一个迷因和营销术语,但也是对技术发展趋势的简写,这些趋势为理解世界和做出决策开辟了新的途径。”

对于整篇文章的详细阅读,请参考以下 URL:www.nytimes.com/2012/02/12/sunday-review/big-datas-impact-in-the-world.html

“大数据”这个术语不应误导我们;实际上,乍一看,我们可能会认为这种现象只与数据大小有关。尽管维度确实代表了问题的要素之一,但还有其他方面或大数据的其他属性,它们不一定与它们相关。

“大数据有三个维度——体积、种类和速度,”迈克尔·米内利说。“并且在这三个维度中的每一个维度都包含着广泛的变量。”

让我们更深入地了解与大数据相关的三个维度:

  • 体积:大数据意味着巨大的数据量。以前,数据是由人创造的。现在,数据由机器、网络和社交媒体生成,需要分析的数据量是巨大的。然而,体积并不是唯一需要解决的问题。

  • 种类:数据的多样性是由于存储这些数据的许多来源和类型,包括结构化和非结构化数据。在过去,数据存储在电子表格和数据库中。现在,它以照片、视频、音频、电子邮件等形式存在。这种非结构化数据的多样性给数据的存储、提取和分析带来了问题。

  • 速度:最后,速度指的是数据从工业流程、机器、网络、社交媒体、移动设备等来源到达的顺序。因此,数据流是巨大且连续的。这种实时数据可以帮助研究人员和公司做出重要决策,如果他们能够管理速度,这将提供战略性的竞争优势。

公司正在产生越来越多的数据,记录了数万亿关于客户、供应商和运营的信息字节。这么大的信息量是由于数据大量来自以下来源:

  • 收集不同类型数据的传感器

  • 映射潜在客户位置的移动电话 GPRS 套餐

  • 社交媒体上的内容

  • 图片——数字和视频

  • 线上购买交易记录

  • 任何其他可以产生我们感兴趣信息的数据来源

这在以下图表中展示:

图片

从功能上讲,收集如此大量的结构和非结构化数据可以帮助组织:

  • 降低成本

  • 提高运营效率和生产力

  • 改善客户关系

  • 以更明智的方式开发新产品

  • 加速和同步交付

  • 制定和回应更深入的需求

  • 改进并简化决策过程

所有这些对于许多大型公司来说已经是现实。未来的挑战是确保即使是小型公司和个人也能获得资源,使他们能够以简单和实用的方式处理数据。

多亏了数据存储和云计算,记忆、聚合和组合数据(因此使用结果进行深入分析)的能力逐渐变得更加容易获得。换句话说,这些服务继续降低其成本和其他技术障碍,以面对日益高效和高效的服务。例如,通过云计算,可以通过互联网访问高度可扩展的计算资源,通常比在他们的计算机上安装所需的成本要低,因为资源被许多用户共享。

数据结构化

每天在世界各地,人类的各种活动都会产生大量数据。最初,这些数据片段并没有结构化,因为它们来自不同性质的资源。因此,它们需要组织起来以便使用。因此,收集的无结构信息必须根据特定要求进行处理,然后作为结构化数据存储。数据结构的形式多种多样,从基本到高级和复杂,它们在数据结构化过程中是必不可少的。

数据结构化包括对作为输入的看似随机和无结构的数据执行的一系列线性或非线性操作。这些操作旨在分析数据的性质及其重要性。然后,系统根据分析结果将数据划分为广泛的信息类别,并将它们存储或发送进行进一步分析。这种附加分析可用于将数据进一步细分为嵌套的子类别。在分析过程中,某些数据也可以被认为是无用的,并最终被丢弃。

这个过程的成果是结构化数据,它可以进一步分析或直接用于提取以前未知的信息。从非结构化数据到有用信息的转变是数据结构化和处理周期的基础,它们的成功往往决定了数据在特定应用领域的价值。

数据结构化是一种组织和存档数据的方法,以便可以高效地访问和修改。特别是,数据结构由一组数据值、它们之间的关系以及可以应用于数据的函数或操作组成,如下面的图所示:

图片

随着时间的推移,数据以不同的方式被组织起来,从编程语言中常用的非常基本的结构,如数组,到可以采取复杂形式的现代数据结构。现代数据结构是不同类型的数据库,支持广泛的扩展操作,允许以许多不同的方式轻松地操作、分类和排序数据。

关系数据库是许多人首选的数据结构,因为它们已经被广泛使用了多年。术语数据库指的是在特定信息系统、商业、科学、行政或其他类型中使用的数据集。数据库由两种不同类型的信息组成,属于不同的抽象级别:

  • 数据,代表要建模的系统实体。这些实体的属性用值(数值、字母数字等)来描述。数据片段也根据它们的共同结构(例如,书籍、作者等)进行分组或分类。

  • 结构(元数据),用于描述各种数据类别(如属性值的名字和类型)的共同特征。

数据库必须代表现实的各个方面,特别是除了实际数据外,还必须表示数据之间的关系,即各种类别之间的逻辑连接。例如,将每位作者与其书籍以及反之亦然关联的关联必须被表示。数据库还必须满足以下要求:

  • 数据必须以最小冗余组织,也就是说,不应不必要地重复。这一条件源于避免不仅是不必要地使用存储资源,而且更重要的是避免管理多个副本的负担;此外,如果与某一类数据相关的信息被重复,那么在其中一个副本上执行更新而未在其他副本上显示,可能会对所有数据的完整性和可靠性产生负面影响。

  • 数据必须能够同时被多个用户使用。这一要求源于前一点;应避免每个用户(或用户类别)在自己的数据副本上工作的情形,而必须有一个单一的数据版本,所有用户都可以访问;这暗示了每种类型的用户都需要对数据有一个特定的视图和特定的数据访问权限。此外,还需要技术来防止各种用户的活动在同时使用相同数据时产生冲突。

  • 数据必须是永久的。这不仅仅意味着使用大容量存储器,还意味着在系统任何组件出现故障的情况下,必须应用技术来保存数据集。

表是关系数据库的基本数据结构。表代表概念模式中的实体和关系。它由记录(行或元组)和字段(列或属性)组成:

  • 每条记录代表实体/关系的实例(或发生或元组)。

  • 每个字段代表实体/关系的属性。

对于每个字段,都确定了其域(数据类型):字母数字、数值、日期、布尔值等。

唯一标识表中记录的值的一组字段称为主键。当主键仅由一个字段组成时,它被称为键字段。以下图显示了数据库中主键的一个示例:

图片

当在实体的属性之间找不到键字段时,定义一个自动增加(计数器)的数字 ID 字段。

引用完整性是关系模型的一组规则,它通过外键将关系相互关联时保证数据完整性:这些规则用于验证表之间的关联,并消除插入、删除或修改链接数据时的错误。

索引在数据库中是相关的。索引是一种旨在提高数据搜索时间的数据结构。对于需要搜索或连接操作的字段可以建立索引。在没有索引的情况下,对字段值的搜索是在表中的记录上顺序进行的。索引自动从数据库为定义为键的字段生成。

查询数据库

到目前为止,我们已经看到了如何从非结构化信息移动到结构化信息。特别是,我们已经了解到这项操作需要大量资源。对正确结构化数据的需要源于在数据库中搜索信息以提取知识的需求。

任何数据库管理系统DBMS)都提供了一个非常强大的工具来咨询数据库表的内容:查询。

数据库管理系统(DBMS)是一种软件系统,旨在允许创建、操作(由数据库管理员(DBA)管理员操作)和高效查询(由一个或多个客户端用户操作)数据库,因此也称为数据库管理器引擎,并托管在专用硬件架构(服务器)或简单计算机上。

查询是查询数据库的方法,即显示从表中提取的信息。为此,可以执行以下操作:

  • 根据各种标准过滤表中的数据

  • 重新关联不同表中的数据

  • 选择要查看的字段

  • 根据某些字段的值对结果进行排序

  • 组(聚合)记录在某个字段具有相同值

查询可以执行以仅显示一次结果,也可以保存到数据库中以便多次执行。如果您在更改表后再次使用查询,查询结果将返回新的表内容。

例如,假设您有一个包含两个表的图书馆数据库:

  • 书籍表表示目录中的书籍,包含字段ID_Book(主键)、标题、作者、年份、价格、出版社

  • 出版社表表示图书馆有联系的出版社,包括字段出版社(主键)、城市、电话

想象一下,想要定义一个查询,显示所有书籍的标题和价格。这个查询需要访问书籍表,并向用户展示仅选择的两个字段(标题和价格)。如果书籍表包含 80 卷,查询将显示 80 个标题和价格对的结果。如果从书籍表中删除了一条记录,重新运行已定义的查询将只显示 79 个标题和价格对,而无需干预查询的定义。

SQL 基础

在这一点上,提出以下问题是合法的:使用哪种语言来编写数据库查询?我们需要一种查询语言,这种语言是用户用来在数据库和信息系统中创建查询的语言。它用于通过相关的数据库管理系统(DBMS)查询数据库,然后与用户及其服务请求进行接口。普遍使用的查询语言是 SQL。

SQL 是一种定义和操作数据的语言。作为操作语言,SQL 允许从数据库中选择感兴趣的数据并更新其内容。查询既用于数据定义的 SQL 构造中,也用于数据库更新的构造中。

SQL 是一种声明性语言:它允许你指定要查找的内容,而不必说明如何查找。当查询处理器执行查询时,它会在系统内部被转换成一种过程性语言,这种语言允许指定如何访问数据。通常,一个 SQL 查询会被转换成多种过程性语言。查询优化器的任务是选择最有效的执行计划。

在数据库上执行一个 SQL 查询,然后在一个通过外键机制相互连接的表集上执行。查询的结果是一个表。我们将通过示例来介绍 SQL,即展示越来越丰富和复杂的查询示例。

要理解 SQL 是如何工作的,我们分析包含意大利博物馆两年游客数量的表格,如下截图所示:

图片

这个表名为Museum;可以编写的最简单的查询如下:

select *
from museum

结果是整个博物馆表。查询的第一行称为select语句,用于从数据库中检索数据。*运算符允许我们选择所有列。查询的第二行称为from语句,用于指示要使用哪些表。在查询中,select语句和from语句是强制性的。请密切关注每个查询中 SQL 关键字的组成部分的大小写、空格和逻辑分隔。

如果我们只对博物馆的名称和所在城市感兴趣,我们可以这样选择它们:

select Museum, City
from museum

结果是以下表格:

博物馆 城市
科洛西奥和罗马广场 罗马
庞贝考古遗址 庞贝
乌菲齐美术馆 佛罗伦萨
佛罗伦萨学院美术馆 福尔尼兹
圣安杰洛城堡 罗马
文尼亚雷亚莱 文尼亚雷亚莱
都灵埃及博物馆 都灵
博洛尼亚博物馆… 福尔尼兹
卡塞塔王宫 卡塞塔
博尔盖塞美术馆 罗马

为了阐明数据库表和 SQL 表之间的区别,我们看到了一个生成具有相同名称两列的表的简单查询:

select Museum, Museum
from museum

结果如下表所示:

博物馆 博物馆
科洛塞奥和罗马广场 科洛塞奥和罗马广场
庞贝考古遗址 庞贝考古遗址
乌菲齐美术馆 乌菲齐美术馆
佛罗伦萨学院美术馆 佛罗伦萨学院美术馆
圣安杰洛城堡 圣安杰洛城堡
文尼亚雷亚莱 文尼亚雷亚莱
都灵埃及博物馆 都灵埃及博物馆
博洛尼亚博物馆… 博洛尼亚博物馆…
卡塞塔王宫 卡塞塔王宫
博尔盖塞美术馆 博尔盖塞美术馆

一个 SQL 表可以包含具有相同名称的重复行和列。列通过其位置唯一标识。这对于查询结果表也是正确的。数据库表,即数据库的一部分,不能有具有相同名称的列。

此外,我们展示了一个生成具有相等行的简单查询:

select city
from museum

结果如下表所示:

城市
罗马
庞贝
福尔尼兹
福尔尼兹
罗马
文尼亚雷亚莱
都灵
福尔尼兹
卡塞塔
罗马

此外,你可以在select关键字之后指定distinct关键字以消除重复项:

select distinct city
from museum

结果如下表所示:

城市
罗马
庞贝
福尔尼兹
文尼亚雷亚莱
都灵
卡塞塔

现在让我们介绍where子句:

select Museum,City
from museum
where City = 'Rome'

结果如下表所示:

博物馆 城市
科洛塞奥和罗马广场 罗马
圣安杰洛城堡 罗马
博尔盖塞美术馆 罗马

如果你想要在数据库中查找特定的项目或项目组,你需要一个或多个条件。条件包含在where子句中。例如,要查找 2016 年注册的游客数量超过一百万的博物馆,我们必须编写:

select Museum, City, Visitors_2016
from museum
where Visitors_2016 >= 1000000

结果如下表所示:

博物馆 城市 Visitors_2016
科洛塞奥和罗马广场 罗马 6408852
庞贝考古遗址 庞贝 3283740
乌菲齐美术馆 福尔尼兹 2010631
佛罗伦萨学院美术馆 福尔尼兹 1461185
圣安杰洛城堡 罗马 1234443
文尼亚雷亚莱 文尼亚雷亚莱 1012033

在这一系列示例中,我们学习了如何使用 SQL 语言正确地提出数据库查询。正如我们所看到的,使用 SQL 非常简单。在以下表中列出了一些最重要的 SQL 命令:

命令 简要描述
SELECT 从数据库中提取数据
UPDATE 更新数据库中的数据
DELETE 从数据库中删除数据
INSERT INTO 将新数据插入到数据库中
CREATE DATABASE 创建一个新的数据库
ALTER DATABASE 修改一个数据库
CREATE TABLE 创建一个新的表
ALTER TABLE 修改一个表
DROP TABLE 删除一个表
CREATE INDEX 创建一个索引(搜索键)
DROP INDEX 删除一个索引

我们现在将讨论其他 SQL 查询,以便我们能够获得更多的技能。

Google BigQuery

数据是公司管理和增长的基本因素。确保数据得到保护、可用且易于访问是任何 IT 部门的基本要求。更重要的是,另一个要求是确保数据被正确使用:管理流程、向决策者提供信息,以及在不断变化的环境中智能干预。

公司确保数据可用性的方式正在迅速变化。云计算近年来在概念和实践的 IT 基础设施组成部分上都取得了令人印象深刻的增长。

云计算是一种技术,允许通过远程服务器使用软件和硬件资源(如用于数据存储的大容量存储),其使用由提供商提供,具体来说是通过订阅提供。

一个特别有趣的云计算解决方案是谷歌 BigQuery。BigQuery 是一个旨在允许你对大型数据集进行查询的 Web 服务;例如,它能够在几秒钟内对包含数十亿记录的表执行选择和聚合查询,因此,以交互式方式获取以前需要几天才能计算的信息将是一个很好的进步。

BigQuery 使全球的公司和开发者能够实时管理大量数据,无需任何硬件或软件投资。例如,如果一家大型跨国公司需要根据销售和广告数据优化其日常支出,或者即使是一家小型在线零售商需要根据用户点击更改产品展示,谷歌提供的服务都是有用的。正如生产商自己所说的,该系统还旨在帮助许多公司应对当前的世界经济危机。

通过将 BigQuery 作为一个公共服务,谷歌声称在使大数据分析通过云服务对所有企业可访问的努力中达到了一个重要的里程碑。BigQuery 通过一个简单的用户界面提供访问,允许你利用谷歌提供的计算能力。收集的数据在多个安全级别得到保护,在多个服务器上复制,并且可以轻松导出。开发者和企业可以在线订阅该服务,并免费获得每月 100 GB 的数据。

BigQuery 的主要特点包括:

  • 可扩展性:云计算的一个固有优势是能够根据需求扩展基础设施,确保应用容量根据需求的增加实现动态可扩展性。这在托管应用程序的峰值使用水平随着时间的推移而持续变化时尤其有用。

  • 交互性:能够在几秒钟内对数十亿条记录执行选择或分组查询。

  • 熟悉性:使用 SQL 语法编写查询。

它还允许良好的数据共享,使用 Google Storage 允许您创建一个协作中心。每当需要与其他用户共享数据时,可以通过设置访问控制列表ACL)来允许那些希望适当访问信息的人访问信息。

ACL 是一种机制,用于表达复杂的规则,这些规则决定了 IT 系统的一些资源是否被其用户访问。

BigQuery 包含允许创建、填充和删除表以及在其上查询的方法。使用 SQL 语法在 BigQuery 中编写查询是可能的;在这个方言中,一些 SQL 方法已被修改以加快某些查询的执行速度;在这些情况下,如果结果精度不是关键,它们基于统计估计并返回一个指示性值。

BigQuery 基础

BigQuery 是 Google Analytics 的数据仓库。它基本上是一个完全托管、PB 级别且低成本的工具。BigQuery 是 NoOps:术语 NoOps(代表 no operation)用于标识一个 IT 环境,该环境自动化程度高,从底层基础设施中抽象出来,不需要专门的团队来管理内部软件。实际上,在 BigQuery 中没有要管理的基础设施,也不需要数据库管理员。节省的时间可以用来分析数据,以找到有意义的信息。在 BigQuery 中形成查询时,我们可以使用熟悉的 SQL 语法,非常容易学习,且非常有效。但使用这项技术的真正便利之处来自于实际节省,这是由于按需付费的模型,它允许我们只为执行特定分析所需的资源实际使用量付费。

要访问 BigQuery,我们基本上可以使用三种模式:

  • 使用图形网页界面

  • 使用命令行工具

  • 使用 API 或客户端库

在本章中,我们将仅通过图形界面分析 BigQuery 的操作。

使用图形网页界面

BigQuery 可以通过一个网页图形用户界面进行访问,该界面可用于加载数据、导出数据、在浏览器中执行查询以及执行其他用户和管理任务。网页用户界面可以在任何浏览器中运行,尽管谷歌推荐使用 Chrome 网页浏览器,因为它能产生最佳性能。

要通过网页界面访问 BigQuery,请访问以下链接:

bigquery.cloud.google.com

一旦登录,你将看到如下所示的 BigQuery 控制台:

在 BigQuery 控制台中,会显示一条欢迎信息。在其中,提出了各种活动供我们选择:

  • 阅读 BigQuery 快速入门指南

  • 通过点击“编写查询”来运行一个查询——在已提供的样本数据上练习——

  • 使用左侧菜单创建一个新的数据集并将数据加载到表中

  • 发现 BigQuery 中的成本控制选项

  • 最后,参考 BigQuery 网络用户界面指南以获取有关用户界面的更多信息

要详细分析 BigQuery 控制台,我们将选择第二个选项,然后点击左上角的“编写查询”按钮。这样,就会显示如下所示的窗口:

在前面的屏幕截图中,两个主要部分被突出显示:

  • 导航栏:从顶部开始,它包含一个元素列表,描述了你想要执行的操作:编写查询、查看查询历史或查看作业历史。继续向下,我们可以识别出当前项目中具有读取访问权限的数据集列表,以及一个名为公共数据的公开数据集。此列表显示了一系列公开数据库,这些数据库为我们提供了练习的机会。要使用这些数据,只需点击任何数据集旁边的数据集展开图标或数据集名称,这样链接就会扩展,我们就可以查看该数据集中的表。

  • 查询框:表示一个可以在其中使用 SQL 语法编写查询的框。这是窗口的主要部分,因为它代表了我们的查询形成的地方。正如预期的那样,为了形成查询,我们需要使用我们在前几节中至少部分看到的 SQL 语法。当然,在我们将要提出的例子中,我们将有机会深入探讨这个话题。在正确地形成要执行的查询后,只需点击查询框底部的“运行查询”按钮即可。

  • 数据集详情:表示包含我们在导航栏中选择的数据的摘要部分。提出了数据集的名称、其内容的简要描述、一系列细节,最后是该数据集中的表。要预览单个表的内容,只需点击表名。

让我们通过一个例子来了解 BigQuery 控制台的使用便捷性。我们使用以下一个公开数据集:

bigquery-public-data.new_york.tlc_yellow_trips_2015.

此数据集由纽约市出租车和豪华轿车委员会TLC)收集,包括从 2009 年至今所有黄色和绿色出租车在纽约市完成的行程记录,以及从 2015 年至今所有出租车FHV)的行程记录。记录包括捕获接车和下车日期/时间、接车和下车地点、行程距离、详细费用、费率类型、支付类型和司机报告的乘客数量等字段。

以下表格列出了数据集中包含的几个字段及其内容的简要描述:

字段名称 简要描述
vendor_id 一个代码,表示提供记录的 TPEP 提供商。1=Creative Mobile Technologies, LLC2=VeriFone Inc
pickup_datetime 车载计价器启动的日期和时间。
dropoff_datetime 车载计价器断开连接的日期和时间。
passenger_count 车辆中的乘客数量。这是一个司机输入的值。
trip_distance 由计价器报告的行程距离(英里)。
pickup_longitude 车载计价器启动时的经度。
pickup_latitude 车载计价器启动时的纬度。
rate_code 行程结束时的最终费率代码。1=标准费率2=JFK3=纽瓦克4=纳萨乌或韦斯特切斯特5=协商费用6=团体行程
store_and_fwd_flag 此标志指示行程记录在发送到供应商之前是否保留在车辆内存中,也称为存储和转发,因为车辆没有连接到服务器。Y=存储和转发行程N=非存储和转发行程
dropoff_longitude 车载计价器断开连接时的经度。
dropoff_latitude 车载计价器断开连接时的纬度。
payment_type 一个数字代码,表示乘客如何支付行程费用。1=信用卡2=现金3=免费4=争议5=未知6=作废行程
fare_amount 车载计价器计算的时间-距离费率。
extra 杂项额外费用和附加费。目前,这仅包括$0.50 和$1 的拥堵时段和夜间费用。
mta_tax 基于使用的计价器费率的自动触发的$0.50 MTA 税。
tip_amount 小费金额——此字段自动填充信用卡小费。不包括现金小费。
tolls_amount 行程中支付的所有过路费总额。
imp_surcharge 在标志放下时对行程征收的$0.30 改善附加费。改善附加费始于 2015 年开始征收。
total_amount 向乘客收取的总金额。不包括现金小费。

首先,让我们向数据库提出一个简单的查询。2015 年,黄色出租车每个月的行程数是多少? 这个查询必须返回 2015 年所有黄色出租车的月度行程总数。有人可能会认为我们开始得太简单了;实际上,对于大多数小城市来说,计算每个月的出租车呼叫似乎微不足道。这对于像纽约这样的大都市来说并非如此。事实上,我们正在谈论一个 18.1 GB 的数据库,包含 146,112,989 个观测值。

然后让我们看看要插入查询框的 SQL 代码:

#standardSQL
SELECT
 TIMESTAMP_TRUNC(pickup_datetime,
    MONTH) month,
  COUNT(*) trips
FROM
  `bigquery-public-data.new_york.tlc_yellow_trips_2015`
GROUP BY
  1
ORDER BY
  1

让我们逐行解释,以理解每个命令的含义。让我们从第一行开始:

#standardSQL

第一行是为了让 BigQuery 知道您想使用 SQL 标准。我们可以为查询启用标准 SQL,这样您就不必在 SQL 中插入此标记。要做到这一点,只需点击查询框下方立即显示的“显示选项”按钮。在打开的选项中,取消选中“使用旧版 SQL”复选框。让我们继续分析查询:

SELECT
 TIMESTAMP_TRUNC(pickup_datetime, MONTH) month,
  COUNT(*) trips

在这段代码中,我们使用了SELECT语句,正如在前几节中已经预料的,它从数据库中检索数据。在语句的第一部分,您指定要检索哪些数据。我们说过,每个月的行程数在 us 中。为此,我们将计算每个月的行数。回想一下,数据库中的每一行对应一次行程。包含此信息的字段是pickup_datetime。为了以易于阅读的格式返回数据,我们使用了TIMESTAMP_TRUNC函数,该函数截断到TIMESTAMP值(返回值类型为TIMESTAMP)。然后,我们使用了count()函数,该函数返回符合指定标准的行数。让我们继续前进:

FROM
  `bigquery-public-data.new_york.tlc_yellow_trips_2015`

使用FROM子句,我们选择要搜索的表。最后两行:

GROUP BY
  1
ORDER BY
  1

这些行被插入以分组和排序数据。结果如下表所示:

月份 行程
1 2015-01-01 00:00:00.000 UTC 12748986
2 2015-02-01 00:00:00.000 UTC 12450521
3 2015-03-01 00:00:00.000 UTC 13351609
4 2015-04-01 00:00:00.000 UTC 13071789
5 2015-05-01 00:00:00.000 UTC 13158262
6 2015-06-01 00:00:00.000 UTC 12324935
7 2015-07-01 00:00:00.000 UTC 11562783
8 2015-08-01 00:00:00.000 UTC 11130304
9 2015-09-01 00:00:00.000 UTC 11225063
10 2015-10-01 00:00:00.000 UTC 12315488
11 2015-11-01 00:00:00.000 UTC 11312676
12 2015-12-01 00:00:00.000 UTC 11460573

如您所见,每个月的行程数量从最低的 11,130,304 到最高的 13,351,609 不等。以下截图显示了 BigQuery 控制台中的结果:

图片

在之前的屏幕截图中,有两个细节被突出显示:

  • 关于计算时间和处理数据的报告

  • 一系列结果存储选项

在第一种情况下,BigQuery 告诉我们,为了完成操作,Google 资源已经投入了 4.5 秒,在此期间处理了 1.09GB 的数据。这些信息将有助于计算交易的成本。

在上一张图像中,我们强调了可用的存储选项。提供了四种选项供我们选择:

  • 下载为 CSV

  • 下载为 JSON

  • 以表格形式保存

  • 保存到 Google Sheets

如果你还记得,JSON 语法是 JavaScript 语法的子集,而 Google Sheets 是一个在线电子表格应用程序,允许用户创建和格式化电子表格,并与其他人同时工作。

在上一张截图上,你可以注意到在结果的最上方有两个标签页:结果和详情。到目前为止,我们已经看到了结果标签页返回的内容;现在让我们看看点击详情标签页会看到什么。一系列关于执行操作的统计数据返回给我们,如下所示:

Job ID   progetto-1-191608:bquijob_1d181029_1614bc7198f
Creation Time  Jan 31, 2018, 11:33:14 AM
Start Time     Jan 31, 2018, 11:33:15 AM
End Time Jan 31, 2018, 11:33:18 AM
User     xxxxxx@gmail.com
Bytes Processed      1.09 GB
Bytes Billed   1.09 GB
Slot Time (ms) 153 K
Destination Table    progetto-1-191608:_b6e2bd761c7590ee099d343a7b87889c01400431.anond9ac14f20bd65f3658af2aa65b7b8847b7d677be
Use Legacy SQL false

这条信息再次提到了我们进行的查询和获得的结果。

使用 Google Data Studio 可视化数据

Google Data Studio 是一个免费的工具,它允许我们快速轻松地创建吸引人的报告。我们终于可以告别冗余和令人困惑的数据表了。实际上,使用 Data Studio,除了能够插入简单的表格外,还可以附加可定制的图形,具有各种颜色和字体,易于理解。由于 Google Data Studio 是 Google Drive 应用程序,因此共享报告变得更加容易,它的工作方式与 Google Drive 类似。

对公司来说最重要的新闻(但同样,如果不是最重要的,也是对客户来说)在于其极其直观的界面。此外,为了保持一切在控制之下,Google 还提供了更改日期范围(在报告或图表级别)的可能性。

实际上,只需几秒钟,你就可以几乎实时地了解网站的进展情况。Data Studio 对两种类型的用户都很有用:

  • 谁创建报告:分析和营销员工

  • 谁会阅读报告:公司的客户和 CEO

目前处于测试阶段的 Google Data Studio 允许你创建具有强烈视觉冲击力的动态报告和仪表板。使用 Data Studio,你可以做到:

  • 轻松连接到不同的数据源

  • 使用动态、交互式和引人注目的报告和仪表板查看你的数据

  • 按照在 Google Drive 上使用的逻辑与他人共享和协作

要使用 Data Studio 查看报告,必须满足以下要求:

  • 你只需要一个网络浏览器(已测试的包括:Chrome、Firefox 和 Safari)。

  • 你不需要 Google 账户。

而在创建报告和数据源时,必须满足以下要求:

  • 你需要登录到 Google 账户

  • 你需要位于受支持的任何国家(换句话说,除了以下国家之外的所有国家:中华人民共和国、俄罗斯、斯瓦尔巴群岛和扬马延岛、伊朗、伊拉克、克里米亚、朝鲜、叙利亚、古巴)

  • 你需要能够使用 Google Drive

数据源使用管道来确保名为连接器的数据集。当数据工作室首次连接到特定类型的数据集,例如 Google Analytics 或 AdWords 时,你会被要求授权连接,这可以在任何时候取消。此时,要连接到某种类型的数据集,例如 Google Analytics,你可以连接到一个账户、一个属性和一个视图;对于 AdWords,你连接到一个管理员或标准账户;对于 BigQuery,你提供一个项目和一个表或一个自定义查询,等等。

在数据工作室中创建报告

首先,让我们看看立即在数据工作室中创建报告需要采取的第一步。为了使用数据工作室,不需要填写任何注册表单;你只需要有一个活跃的 Google 账户。如果你已经登录了你的 Google 账户,只需在浏览器中输入以下 URL:datastudio.google.com/

这将直接带您到数据工作室主页,如下面的截图所示:

图片

一旦您查看了数据工作室的主页,我们就可以识别出一些有助于我们创建报告的部分(在之前的截图中被突出显示)。为了开始熟悉数据工作室界面,我们可以咨询一些内置模板以开始使用。你可以选择一个现有的模型并对其进行修改,或者简单地从一个空白报告开始。

点击“所有模型”下拉菜单会显示所有内置模板。这样我们就可以选择任何符合我们需求的模型,并使用可用的编辑器对其进行修改,以便适应我们的需求。预定义模板对于开始使用或那些没有太多时间花在格式设置上的人来说很有用。实际上,在这些模型中,功能已经准备好供使用,从而节省了大量时间。

为了有效地了解如何使用数据工作室,我们将从一个空报告开始,以便解释创建报告所需的所有步骤。有两种方法可以从新报告开始:

  • 点击水平标题栏上的空加号(空白)

  • 点击右下角的(+)图标

两种选项在上一个图中都清楚地突出显示,并且在两种情况下,以下屏幕中显示的窗口都会出现:

图片

这会显示报告编辑器,其中包含添加图表和数据以及选择报告风格所需的所有控件和元素。最初,新报告在屏幕左上角有一个默认名称(“未命名报告”),所以只需点击它,我们就会为新报告输入一个新名称(“第一报告”)。

首先要做的事情是将数据源添加到报告中。为此,我们又有两种选择:

  • 已存在的数据源

  • 创建新的数据源

在前面的屏幕截图中,两个可以激活选项的区域被突出显示。可以注意到,练习中提出的资源已经出现在数据源选择器的底部。在此场合,我们将参考这些资源。例如,要选择第一个与 [样本] 世界人口数据 2005-2014 相关的项目,只需点击它。此数据集包含 2005 年至 2014 年的世界人口数据。在这种情况下,以下屏幕截图所示的窗口将显示:

点击“添加到报告”按钮后,Data Studio 的主窗口将增加新的组件:

  • 菜单栏:这允许您通过在组件上右键单击来访问许多其他菜单功能。

  • 工具栏:这允许您在各种工具、页面和控制选项中进行选择。此工具栏分为五个部分。

  • 布局和主题属性面板:我们可以通过此面板中的选项来控制报告在查看器屏幕上的显示方式。这是默认属性面板;当没有选择其他组件时,它会显示出来。

在以下屏幕截图中,您可以查看报告图表的新外观:

要将图表添加到我们空报告的页面,只需单击工具栏中的一个图标即可。例如,要添加从 2005 年到 2014 年的世界人口条形图,只需这样做:

  1. 点击工具栏第三区域的第二个图标(条形图)

  2. 将视图定位器放置在页面上您想要显示图表的点

  3. 在标题下绘制图表,要移动它,只需单击并拖动,或者选择它,并使用键盘上的箭头键

完成此操作后,我们的数据条形图将出现在主区域,而右侧的布局和主题属性面板将让位于条形图属性面板,如下面的屏幕截图所示:

条形图属性面板对于修改刚刚添加到我们报告中的图表至关重要。实际上,我们可以更改图表类型,更改数据源,添加另一个维度,添加新的度量值,并更改数据的排序。

Data Studio 不允许整个仪表板或多个报告以 .pdf 或其他格式导出。尽管可以导出单个表格或图表的 CSV 格式。要导出图表或表格:

  • 点击右上角的“视图”选项卡以查看报告的查看模式

  • 将鼠标悬停在您想要导出的图表或报告上

  • 右键单击并选择导出 CSV

要将整个仪表板导出为 PDF,目前还没有添加任何内部功能来帮助你将报告导出为.pdf格式。但是,通过使用 Chrome 扩展程序,你可以非常容易地完成这项操作(Google Data Studio PDF Export)。另一种将报告导出为 PDF 的方法是通过浏览器打印到 PDF。你将不得不为每个报告页面这样做。

摘要

在本章中,我们提出了 BigQuery 和 Data Studio 平台的管理和查看数据的介绍。首先,我们探讨了大数据及其与大量数据管理相关的问题。然后,我们分析了如何以结构化格式组织数据以正确查询数据库。

在此之后,你被引入了 SQL。SQL 是一种用于定义和操作数据的语言。作为一种操作语言,SQL 允许从数据库中选择感兴趣的数据并更新其内容。查询在数据定义的 SQL 构造和更新数据库的构造中都被使用。

然后进行了 Google BigQuery 的介绍。BigQuery 是一种网络服务,它使对大量数据集的交互式分析成为可能。BigQuery 使全球的公司和开发者能够在不依赖任何硬件或软件投资的情况下,实时管理大量数据。

最后,我们分析了如何使用 Google Data Studio 从我们的数据中创建报告。Google Data Studio 是一个免费工具,允许我们快速轻松地创建引人入胜的报告。使用 Data Studio,除了能够插入简单的表格外,还可以附加可定制的图形,具有各种颜色和字体,易于理解。

在下一章中,我们将介绍 Dataprep 服务,这是一个用于预处理数据、提取特征和清理记录的有用服务,以及 Dataflow 服务,这是一个用于实现流式和批量处理的服务。

第五章:转换您的数据

实际世界的数据集非常多样:变量可以是文本的、数值的或分类的,观察值可以是缺失的、错误的或错误的(异常值)。为了进行适当的数据分析,我们将了解如何正确解析数据、清理数据并创建一个针对机器学习分析优化构建的输出矩阵。为了提取知识,读者能够使用不同的数据分析和技术创建一个观察矩阵是至关重要的。

在本章中,我们将介绍 Cloud Dataprep,这是一种用于预处理数据、提取特征和清理记录的有用服务。我们还将介绍 Cloud Dataflow,这是一种实现流式和批量处理的服务。我们将通过一些实际案例研究深入探讨一些细节。我们将从发现转换数据的不同方法和数据清理的程度开始。我们将分析可用于准备最适合分析和建模的数据的技术,这包括缺失数据的插补、异常值的检测和消除以及派生变量的添加。然后我们将学习如何归一化数据,其中数据单位被消除,使我们能够轻松比较来自不同位置的数据。

在本章中,我们将涵盖以下主题:

  • 转换数据的不同方法

  • 如何组织数据

  • 处理缺失数据

  • 检测异常值

  • 数据归一化

在本章结束时,我们将能够进行数据准备,以便其信息内容能够最好地暴露给回归工具。我们将学习如何将转换方法应用于我们的数据,以及这些技术是如何工作的。我们将发现如何清理数据,识别缺失数据,以及处理异常值和缺失条目。我们还将学习如何使用归一化技术来比较来自不同位置的数据。

如何清理和准备数据

初学者可能会认为,一旦我们完成数据的收集并将其导入 Google Cloud,就终于可以开始分析过程了。相反,我们必须首先进行数据的准备工作(数据整理)。

数据整理是将数据转换和映射的过程,将原始数据转换为格式化数据,目的是使其更适合后续的分析操作。

这个过程可能需要很长时间,而且非常繁琐,在某些情况下,可能占整个数据分析过程的约 80%。

然而,这是数据分析工作流程中的基本先决条件;因此,掌握这些技术的最佳实践至关重要。在将我们的数据提交给任何机器学习算法之前,我们必须能够评估我们观察的质量和准确性。如果我们不知道如何从原始数据转换成可分析的数据,我们就无法继续前进。

Google Cloud Dataprep

为了正确准备我们的数据,有必要执行一系列涉及使用不同算法的操作。正如我们预料的,这项工作可能需要很长时间,并且会使用很多资源。在云服务中,Google 提供了一种简单直接的方式来完成这项工作:Google Cloud Dataprep。

它是一种智能数据服务,允许您直观地探索、清理和准备结构化和非结构化数据分析。Google Cloud Dataprep 是无服务器的,并且可以在任何规模上工作。不需要分发或管理任何基础设施。

Google Cloud Dataprep 有助于快速准备数据以进行即时分析或用于训练机器学习模型。通常,数据需要手动清理;然而,Google Cloud Dataprep 通过自动检测模式、类型、连接和异常(如缺失值)使这个过程变得极为简单。至于机器学习,建议了不同的数据清理方式,可以使数据准备过程更快且更少出错。

在 Google Cloud Dataprep 中,您可以通过与数据样本的交互来定义数据准备规则。应用程序的使用是免费的。一旦定义了数据准备流程,您可以免费导出样本或将其作为 Cloud Dataprep 作业运行,这将产生额外的费用。

使用 Google Cloud Dataprep,您可以执行以下操作:

  • 从不同来源导入数据

  • 识别并删除或修改缺失数据

  • 识别异常值(离群值)

  • 在数据集中执行搜索

  • 标准化数据集中字段中的值

  • 通过连接合并数据集

  • 通过合并操作将一个数据集添加到另一个数据集中

这些操作可以在非常短的时间内无需技术基础设施的情况下执行。

探索 Dataprep 控制台

第一次登录 Google Cloud Dataprep 控制台时,您将被要求接受服务条款、登录您的 Google 账户并选择一个用于 Cloud Dataprep 的云存储桶。您还将被要求允许第三方应用程序托管商 Trifacta 访问项目数据。完成这些步骤后,您将获得一个包含打开的 Flows 屏幕的 Cloud Dataprep 主页,如下面的截图所示:

目前,控制台看起来是空的,因为我们还没有执行任何操作;它将在以后被我们的操作填充。在 Google Cloud Dataprep 控制台顶部,您可以找到三个链接,它们可以打开以下页面:

  • FLOWS:此页面显示了您可访问的流程,并允许您创建、审查和管理它们。流程是一个对象,允许我们收集和组织数据集以生成结果。

  • DATASETS:在此页面上,我们可以审查我们可访问的导入和参考数据集。

  • 作业:在这个页面上,我们可以跟踪所有正在运行、完成或失败的作业的状态。

默认情况下,Flows 页面是打开的。要创建一个新的流程,请点击创建流程按钮。以下窗口将打开,我们可以为新流程设置名称和描述:

图片

在我们完成这些后,在 flows 页面上,刚刚创建的新流程将显示,并建议可以添加到此流程的数据集以开始整理。为了分析 Google Cloud Dataprep 示例的工作方式,我们将使用一个专门设计的文件,该文件包含一小部分观测数据的数据;它列出了测试的结果。我们将获取CleaningData.csv,这是一个包含我们刚刚列出的一些问题的电子表格。在正确识别此文件并上传到我们的计算机后,将显示以下窗口(Transformer 页面):

图片

在这个页面上,我们可以轻松地识别我们已导入的数据。在这个时候,我们可以编写我们想要应用到此数据的转换。一旦我们确定了我们要做什么,我们就可以立即预览结果,然后再对整个最终数据集进行更改。

在 Transformer 页面上,有两个面板可用:网格和列。默认情况下,Transformer 页面显示网格面板,其中以列网格的形式显示列的预览。在列面板中,返回有关单个列的附加统计信息。此面板特别适用于管理异常值,以审查平均值、最小值和最大值。

从对 Transformer 页面的第一眼观察中,我们可以了解我们已导入的数据。实际上,我们可以查看所有字段和所有观测值。

在这里,我们上传了一个只有几个字段和观测值的文件。更普遍地说,在左下角,我们得到了行数、列数和数据类型的总结。

从返回窗口的分析中,我们可以提取有用的信息。从每个列的名称开始,其左侧出现一个符号,用于标识自动分配给它的数据类型。这样我们就可以验证 Dataprep 已将变量编目如下:

  • name: 名称(字符串)

  • gender: 性别(性别)

  • age: 年龄(整数)

  • right1: 正确答案的百分比(整数)

  • wrong: 错误答案的百分比(整数)

数据类型的分配似乎正确。这些信息是通过光标返回给我们的。实际上,当你将光标在页面上的选定数据元素上移动时,光标会改变。在列标题下方立即有有用的摘要图表。在列的数据质量条上,显示了值的类别。以下三种类型的值可用:

  • 有效

  • 不匹配

  • 缺失

然后,显示直方图,为我们提供有关每个列中包含的数据的统计信息。进一步的信息通过将光标移过它返回给我们。

由于值很少,我们还可以预览所有数据。让我们看一下,以视觉上识别异常。已经可以看到,年龄变量有一个缺失值。任何类型的变量的缺失值都由 NA 代码表示,这意味着不可用。另一方面,非数字NaN)代码表示无效的数值,例如零除以数值。如果一个变量包含缺失值,GCP 无法对其应用某些函数。因此,有必要提前处理缺失值。

在转换页面上,你可以看到当你将鼠标悬停在区域和相关面板上时,光标旁边会出现一个灯泡图标,以指示有建议可用。

在每个列标题的左侧,你可以看到一个下拉列菜单。借助此菜单,我们可以对列数据进行多项操作,例如根据列数据类型更改其数据类型。以下操作可用:

  • 重命名:重命名列

  • 更改类型:更改列的数据类型

  • 移动:将列移动到开始或结束位置,或移动到数据集中的指定位置

  • 编辑列:对列执行一系列编辑

  • 列详情:探索列详情的交互式配置文件

  • 显示相关步骤:在配方面板中突出显示引用所选列的步骤

  • 查找:查找并替换特定值或从列中提取模式值

  • 过滤:根据列的文本书面值或计算值过滤数据集的行

  • 清理:清理列中的不匹配或缺失值,用固定或计算值替换值或删除整个行

  • 公式:根据所选函数从源列生成包含计算值的新的列

  • 聚合:根据跨组的计算生成汇总表,或将汇总数据作为新列添加到当前表中

  • 重新结构化:根据列的值更改数据集的结构

  • 查找:在另一个数据集的另一列的值集中查找列值

  • 删除:从数据集中删除列

以下截图显示了性别列的列菜单:

图片

当选择一列时,Transformer 页面右侧会打开一个新的建议面板。此面板显示一系列与特定列中包含的数据类型相关的建议。建议根据所选数据而变化。通过悬停在任何一个建议上,你可以在数据网格中预览结果,以确保所提出的转换适用于数据集。在下一节中,我们将能够扩展建议面板的使用。

删除空单元格

从导入数据的视觉分析中,我们检测到第三行第二列有一个空单元格;这表明存在缺失值。在分析数据集之前,有必要消除这个异常。

在这种情况下,由于数据量小,识别空单元格特别容易;在大型数据集的情况下,视觉分析不起作用。因此,为了识别缺失值,我们可以分析数据质量条。在这里,缺失值用黑色标识。

要预览缺失数据的数量,我们可以将鼠标移到数据质量条的黑色部分;会返回缺失数据的数量,如下面的截图所示:

截图

我们已经确认性别字段存在缺失值。回想一下,缺失值是指不包含内容或不存在的内容的值。这些缺失值可能是由一系列事件引起的:

  • 数据集创建中的错误;值输入错误,留下了空单元格

  • 数据集包含自动创建的字段,其中单元格不包含值

  • 不可能计算的后果

现在,如果我们选择性别列,一个新建议面板会在转换器页面右侧打开。如前所述,此面板显示一系列与特定列中包含的数据类型相关的建议。提出了几个建议:

  • 删除列

  • 重命名

  • 聚合和分组数据

  • 创建新列

  • 设置

  • 将值添加到列中

当我们悬停在不同的建议上时,每个建议的左侧都会出现一个迷你预览,如下面的截图所示:

截图

在建议面板中,出现一个特定的文本“缺失值”,这意味着在列中识别出的缺失值可以被其他内容替换。通过点击“设置”项,会出现两个按钮:编辑和添加。点击编辑按钮会打开一个新框;在这个框中提出了以下新公式:

ifmissing($col, '')

缺失函数会在源值是空或缺失值时写入指定的值。在引号之间插入NA字符串后,每次在列中识别到缺失值时,它将被替换为NA值。修改公式后,我们将能够实时预览列,这是由应用公式修改的;如下面的截图所示:

截图

现在,要应用这个建议,只需在建议面板中点击“添加”按钮。这样,就会在配方面板中添加一个新的步骤。

注意,配方面板允许我们回顾和修改迄今为止创建的配方步骤,同时,它还允许我们添加新的步骤。如果面板没有显示,要使其出现,只需点击运行作业按钮左上角的图标(配方)。

要为不同的列生成一组新的建议,请点击取消。然后,选择列中的一组不同的列或值;这样,就会打开一个新的建议面板。

替换不正确的值

下一步将允许我们替换不正确的值指示符。如果我们再次查看数据,我们会看到在年龄列中显示的值是-19。这显然是一个不正确的值,因为对于这个变量,允许的值大于零(这是一个年龄):

24 -19 32 15 18 21 28 30 26 100 22 NA

我们可以用缺失值指示符NA替换这个值。为此,我们将在设置建议中写下以下公式:

IF($col<0, 'NA',$col)

在上一节创建的步骤之后,将立即在配方面板中添加一个新步骤。同时,我们可以看到对数据集所做的更改预览。不正确的值不再存在;取而代之的是进一步的NA值。

可以通过直接在配方面板中操作来获得相同的结果:

  1. 记住,要打开配方面板,只需点击运行作业按钮左上角的图标(配方)。

  2. 点击新建步骤按钮;转换构建器被打开

  3. 在转换下拉菜单中,从可用的转换中选择应用公式

  4. 指定列(年龄)

  5. 在公式框中编辑所需的公式

  6. 点击添加按钮

  7. 在配方面板中添加新步骤

在下面的截图中,我们可以看到转换构建器:

图片

获得的结果与我们通过在设置建议中编写公式获得的结果相同;在这种情况下,年龄列中也不再存在负值。

不匹配的值

任何看起来与列指定的数据类型不同的值都被视为不匹配的值。在我们目前分析的这个数据库中,有几个值似乎偏离了分配给列的类型。例如,在右侧列中有一个包含点的单元格;很明显,这是在填充数据库阶段的一个错误。同样明显的是,这样的值在分析阶段可能会引起很多问题,因此必须适当地处理。

如同处理缺失值一样,不匹配的值也显示在每列顶部的数据质量条上。在数据质量条中,不匹配的值用红色标识,如下面的截图所示:

图片

要修复不匹配的数据,有几种选项可用:

  • 更改数据类型

  • 用常数值替换值

  • 使用其他列的值设置值

  • 使用函数转换数据

  • 删除行

  • 暂时隐藏该列

  • 删除列

在这种情况下,由于选中的两行在其他列中也存在不匹配的数据,我们将删除所有三列。为此,只需在建议面板的删除行区域点击添加按钮。

分析数据集,我们可以看到不匹配的数据仍然存在。实际上,年龄列中的数据质量条在红色区域。我们试图解决这个问题。这次不适宜删除整行。实际上,通过分析第一列,我们可以看到NA值指的是一个名字明显指代女性的行(Olivia)。所以最合适的解决方案是将这个值替换为已知值,在这种情况下是'F'

要做到这一点,我们将在建议面板的集合项中写下以下公式:

ifmismatched($col, ['Gender'], 'F')

将在配方面板中添加一个新步骤。再次,我们可以看到对数据集所做的更改的预览。实际上,我们可以看到错误值不再存在;取而代之的是'F'

我们已经调整了几个方面,但乍一看,还有一些事情要做。如果我们注意右 1 列,它代表提供的正确答案的百分比,我们会注意到值的范围如下:-19 到 98。但-19 显然是一个错误值,因为这个变量的允许值在 0 到 100 之间(这是一个百分比)。我们可以假设在创建数据集时错误地添加了负号。然后我们可以修改这个值,只留下值 19。

要做到这一点,我们执行以下步骤:

  1. 打开配方面板。只需点击运行作业按钮左上角的图标(配方)。

  2. 点击“新建步骤”按钮。打开转换构建器。

  3. 在转换下拉菜单中,从可用转换列表中选择应用公式。

  4. 指定列(右侧)。

  5. 在公式框中编辑以下公式:

  6. IF($col==-19,19,$col)

  7. 点击“添加”按钮。

  8. 在配方面板中添加了一个新步骤。

我们添加到配方面板中的操作有五个,如本截图所示:

图片

通过分析配方面板,我们随后对数据集上计划采取的行动有一个总结。此外,对数据集预览所做的更改的视觉分析没有显示任何需要修复的异常。在每列顶部的数据质量条上,没有红色/黑色区域被突出显示,尽管现在还为时尚早!数据准备的工作还远未完成。

在数据中寻找异常值

异常值是与其他值相比特别极端的值(一个明显远离其他可用观察值的值)。异常值的存在造成阻碍,因为它们往往会扭曲数据分析的结果,特别是在描述性统计和相关性中。在数据清洗阶段本身识别这些异常值是理想的;然而,它们也可以在数据分析的下一步中处理。异常值可以是单变量的,当它们对于单个变量具有极端值时,或者多变量的,当它们对于多个变量具有不寻常的值组合时。

异常值是分布的极端值,与分布中的其他值相比,其极端高或极端低,因此相对于其他分布而言是孤立案例。

检测异常值有不同的方法。Google Cloud Dataprep 使用 Tukey 的方法,该方法采用 四分位距IQR)方法。这种方法不依赖于数据的分布,并忽略了受异常值影响的平均值和标准差。

如前所述,为了确定异常值,参考由第 25 百分位数和第 75 百分位数之间的差异给出的 IQR,即它所在的范围的幅度。这 50% 的观测值占据了有序数据系列中的中心位置。异常值是相对于其他分布而言,偏离第 75 百分位数(正值)大于两倍 IQR 的值,或者对称地,是偏离第 25 百分位数(绝对值)大于两倍 IQR 的值。

实际上,异常值可以是以下两种情况之一:

< (25th percentile) - (2 * IQR)
> (75th percentile) + (2 * IQR)

要在单个列中识别异常值,Google Cloud Dataprep 提供了视觉功能和统计信息。

视觉功能

此前,我们已讨论过每列顶部的直方图。此图显示了列中检测到的每个值的计数(对于字符串数据)或数值范围内的值的计数(对于数值数据)。

Google Cloud Dataprep 提供的视觉功能正是使用这些直方图来识别异常值或异常值,这些值在执行整个数据集上的任何分析之前应被移除或修正。

返回的直方图类型取决于列中包含的数据类型。实际上,对于数值数据,每个条形图代表一个值范围,条形图按数值顺序排序。对于分类类型,每个垂直条形图覆盖单个值,按出现频率最高的值排序。

将鼠标移至直方图的条形图上,会返回一系列信息。这样,我们可以突出显示特定值,获取该值的计数以及该值在列中所有值总计数中的百分比。我们还可以选择特定的条形图;在这种情况下,包含它们的行会被突出显示,并显示建议面板以管理这些值。

要同时选择不同的条形图:

  • 使用 Ctrl + 点击来选择多个条形图

  • 点击并拖动选择一系列条形图

为了更好地理解这种视觉功能的有用性,让我们参考一个例子。特别是,我们将分析在前面章节中已经使用过的数据集,我们已经有机会对其进行修改。让我们看一下年龄列,因为它目前在预览报价中呈现,并且已经生效了更改。我们可以看到直方图有四个条形在左侧分组,只有一个条形在右侧孤立。这种直方图形式表明存在异常值,如下面的截图所示:

图片

在这种情况下,异常值的出现是明显的;更普遍地说,图表两端孤立条形的存在必须引起我们的注意。

如果数据集包含多个异常值实例,则有必要进一步调查。通常,如果数据集包含大量异常值,则在执行修改或删除这些行的操作之前,有必要审查这些值及其在其他列中的数据,因为删除这些值可能变得具有统计学意义。

统计信息

我们到目前为止所进行的视觉分析在某些情况下并不允许我们轻松地识别异常值的存在。为了获得更多信息,我们可以检查当前选定列的详细统计数据,包括在列详细信息面板中可用的异常值数据。要打开列详细信息面板,只需从列的下拉菜单中选择列详细信息;以下面板将被打开:

图片

在这个面板中,有很多信息可用,以下是一些信息:

  • 概述统计:有效(唯一和异常)值的计数、不匹配值和缺失值

  • 统计意味着你可以获得许多选项,如最小值、最大值、平均值、最低和最高四分位数、中位数和标准差

  • 值直方图

  • 最高值

  • 异常值

在这种情况下,任何异常值都被清楚地识别并标记出来。此外,直方图现在更加精确,清楚地指示条形的排列。

删除异常值

到目前为止,我们已经看到了识别可能异常值的不同技术。在识别它们之后我们应该做什么?在确定列中的值是异常值之后,需要确定这些值对于数据集是否有效或无效。

如果这些值无效是由于数据集人口统计阶段的错误导致的,那么我们必须纠正它们。这个操作可能涉及用可能有效的值替换这个值,或者删除整行。在后一种情况下,我们必须注意这个操作对整个数据集可能产生的影响。

为了替换看起来在所有方面都无效的值100(可能原本是10,多加了一个零),我们可以插入以下公式:

IF(($col == 100),10, $col)

相反,如果删除此记录在统计上没有显著的重要性,我们可以采用简单的擦除指令,如下所示:

DELETE row: age == 100

如果数据看起来有效(实际上,人类可能有一百年的寿命),我们可以保持原样。或者我们可以将其转换为对我们来说在统计上更有意义的值。例如,我们可以决定用整个列的平均值替换这个值,以保留至少从其他列中得到的这个观察结果的信息。要用列的平均值替换 100,请使用以下公式:

if($col > 80, average($col), $col)

最终我们选择了第一个选项,所以年龄列中的100将被替换为10。为此,我们(像往常一样)在新的步骤中插入刚刚提出的公式,如下面的屏幕截图所示:

图片

要查看更改的预览,请点击“添加”按钮,新的步骤将被添加到我们的配方面板中。在屏幕截图中,我们还可以验证现在值的范围已经显著减少:从 10 到 32,而不是从 15 到 100。不仅如此,直方图也不再在末端有孤立的长条。

运行作业

我们已经在数据库上计划了几个操作:现在是时候做出这些改变了。为此,只需在 Transformer 页面上点击“运行作业”。这样,就会打开“运行作业”页面,在那里我们可以为当前加载的数据集指定转换和概要分析作业。可用的选项包括输出格式和输出目的地。

“概要结果”选项允许我们生成可视化的结果概要。可视化概要对于检查我们的配方问题并进行迭代非常有用,即使这是一个需要大量资源的流程。如果我们正在处理的数据集很大,禁用结果概要可以提高作业的整体执行速度。

在正确设置可用选项后,我们可以通过简单地点击“运行作业”来排队指定的作业以供执行。一旦完成,作业就会排队等待处理。在处理结束时,您可以使用数据集详细信息页面查看成功运行的作业结果。

流处理时间取决于服务器可用性和数据集的大小。在我们的案例中,数据集确实很小,所以我们只需等待服务可用。点击“查看结果”以在作业结果页面上打开作业,如下面的屏幕截图所示:

图片

在之前的屏幕截图(作业结果页面)中,可以查看转换配方对整个数据集的影响。提供了统计信息和数据直方图,这些信息可以提供对我们转换配方质量的总体可见性。

在截图左上角,你可以找到关于生成数据集中包含的数据的一系列总结信息。特别是,显示了有效值的计数、不匹配值和缺失值的数量。这些值在整个数据集中以完整形式显示。

在截图的上部,总是会出现一系列关于所执行工作的总结信息,但这次你可以在右侧找到它。

在截图的下部,我们可以可视化对单个列进行的变换细节。根据列的数据类型,会显示变量的信息。

最后,在截图右上角的上部,有两个按钮:

  • 查看依赖项:要查看作业所依赖的配方和数据集

  • 导出结果:要导出结果

特征缩放

数据缩放是一种通常在特征选择和分类之前使用的预处理技术。许多基于人工智能的系统使用由许多不同的特征提取算法生成的特征,这些算法来自不同的来源。这些特征可能具有不同的动态范围。流行的距离度量,如欧几里得距离,隐含地给范围较大的特征比范围较小的特征更多的权重。因此,特征缩放是必要的,以大致相等化特征的范围,并使它们在相似度计算中具有大约相同的影响。

此外,在具有大量特征和较大动态范围的数据挖掘应用中,特征缩放可能会提高拟合模型的性能。然而,选择这些技术是重要的问题。这是因为对输入进行缩放可能会改变数据的结构,从而影响数据挖掘中使用的多元分析的结果。

到目前为止,我们已经对数据进行处理,纠正了任何错误或遗漏。我们可以这样说,在这个阶段,数据集中包含的所有变量都是完整的,并且数据是一致的。那么,具有不同范围和单位的变量呢?在数据框中可能存在变量,其中一个特征的值可能从 1 到 10,而另一个特征的值可能从 1 到 1,000。

In data frames such as these, owing to mere greater numeric range, the impact on response variables by the feature having greater numeric range could be more than the one having less numeric range, and this could, in turn, impact prediction accuracy. Our goal is to improve predictive accuracy and not allow a particular feature to impact the prediction due to a large numeric value range. Thus, we may need to scale values under different features such that they fall under a common range. Through this statistical procedure, it is possible to compare identical variables belonging to different distributions, but also different variables, or variables expressed in different units. Two methods are usually well known for rescaling data: normalization and standardization.

Remember, it is good practice to rescale the data before training a machine learning algorithm. With rescaling, data units are eliminated, allowing you to easily compare data from different locations.

Min–max normalization

Min-max normalization (usually called feature scaling) performs a linear transformation on the original data. This technique gets all the scaled data in the range (0, 1). The formula to achieve this is the following:

Min-max normalization preserves the relationships among the original data values. The cost of having this bounded range is that we will end up with smaller standard deviations, which can suppress the effect of outliers.

To better understand how to perform a min-max normalization, just analyze an example. We will use a dataset contained in the Airquality.csv file.

This dataset is available at the UCI machine learning repository, a large collection of data, at the following link: archive.ics.uci.edu/ml/index.php.

S. De Vito, E. Massera, M. Piga, L. Martinotto, G. Di Francia, On field calibration of an electronic nose for benzene estimation in an urban pollution monitoring scenario, Sensors and Actuators B: Chemical, Volume 129, Issue 2, 22 February 2008, Pages 750-757, ISSN 0925-4005 at: www.sciencedirect.com/science/article/pii/S0925400507007691.

These are the daily readings of the following air quality values for May 1, 1973 (a Tuesday) to September 30, 1973:

  • 臭氧:罗斯福岛 1300 至 1500 小时的平均臭氧浓度,每十亿分之一

  • 太阳辐射:中央公园 0800 至 1200 小时在 4000–7700 埃频率范围内的太阳辐射,单位为朗格利

  • 风速:拉瓜迪亚机场 0700 和 1000 小时的平均风速,单位为每小时英里

  • 温度:拉瓜迪亚机场每日最高温度,单位为华氏度

The data was obtained from the New York State Department of Conservation (ozone data) and the National Weather Service or NWS (meteorological data).

A data frame consists of 154 observations on 6 variables:

名称 类型 单位
臭氧 数值 ppb
太阳 数值 lang
风速 数值 mph
温度 数值 华氏度
月份 数值 1 到 12
数值 1 到 31

如所示,六个变量具有不同的度量单位。正如我们在前面的章节中所做的那样,即使在这种情况下,我们也从创建新的流程开始准备数据,然后我们将.csv文件导入 Google Cloud Dataprep。以下窗口将打开:

正如我们所预期的,四个变量有不同的度量单位,这意味着值范围差异很大。实际上,通过分析前一个截图中的每一列的上部,我们可以获得以下范围:

  • 臭氧:1 到 168

  • 太阳辐射强度:7 到 334

  • 风速:2 到 21

  • 温度:56 到 97

在进行标准化之前,我们消除数据质量栏中突出显示的一些问题。已识别不匹配的值。为了更精确:

  • 臭氧:37 个不匹配的值

  • 太阳辐射强度:7 个不匹配的值

首先,我们将尝试修复这些问题,就像我们在上一节中学到的那样。

我们从臭氧列开始;我们点击数据质量栏的红色区域。这样,所有 37 个不匹配的值都会被突出显示。同时,窗口右侧打开建议面板。特别是,第一个建议建议我们删除臭氧中不匹配值的行。我们只需点击添加按钮。以下行将被添加到“配方”面板中:

Delete rows where ISMISMATCHED(Ozone, ['Integer'])

我们对Solar_R列执行相同的操作;以下行将被添加到“配方”面板中:

Delete rows where ISMISMATCHED(Solar_R, ['Integer'])

在这两种情况下,预览窗口显示我们数据中现在没有不匹配的值。此时,我们可以处理归一化。正如我们之前指定的,变量的范围非常广泛。我们希望通过最小-最大归一化消除这一特征。正如之前所述,要应用此程序,我们必须计算每个变量的最小值和最大值。为此,我们可以应用 Google Cloud Dataprep 中可用的两个函数:MINMAX

Google Cloud Dataprep 支持在大多数桌面电子表格软件中常见的支持函数。可以使用这些函数创建公式来操作列中的数据。

之前,我们提出了归一化的公式;要将它应用于一列,我们执行以下步骤:

  1. 在“配方”面板中,点击“新建步骤”按钮。还记得吗?要打开“配方”面板,只需点击运行作业按钮左上角的图标(配方)。

  2. 从“转换”下拉菜单中选择“应用公式”项(此项目将一列或多列的值设置为公式的结果)。

  3. 从列框中选择臭氧列,例如(相同的步骤可以应用于数据集的所有变量)。

  4. 在“公式”框中,编辑以下公式:(Ozone-MIN(Ozone))/(MAX(Ozone)-MIN(Ozone))

  5. 只需点击“添加”按钮。

在“配方”面板中添加以下语句:

Set Ozone to (Ozone-MIN(Ozone))/(MAX(Ozone)-MIN(Ozone))

我们对其他三个变量(Solar_RWindTemp)遵循相同的程序。最后,我们将添加以下行到“配方”面板中:

Set Solar_R to (Solar_R -MIN(Solar_R))/(MAX(Solar_R)-MIN(Solar_R))
Set Wind to (Wind -MIN(Wind))/(MAX(Wind)-MIN(Wind))
Set Temp to (Temp -MIN(Temp))/(MAX(Temp)-MIN(Temp))

变换页面已更改如下:

图片

显然,现在数据都在零和一之间;这是针对数据集的每一列以及每个变量发生的。因此,由于不同测量单位的差异导致的缩放差异已被消除。

z 分数标准化

这种技术包括从列的每个值中减去列的平均值,然后将结果除以列的标准差。实现这一目标的公式如下:

图片

标准化的结果是,特征将被重新缩放,以便它们具有标准正态分布的性质,如下所示:

  • μ=0

  • σ=1

μ是平均值,σ是平均值的标准差。

总结来说,z 分数(也称为标准分数)表示观测点或数据值与观测或测量的平均值相比的标准差数。高于平均值的值具有正的 z 分数,而低于平均值的值具有负的 z 分数。z 分数是一个无量纲的量,通过从单个粗略分数中减去总体平均值,然后除以总体标准差得到。

再次,为了标准化数据,我们将使用与 min-max 归一化相同的程序。这次,两个函数更改如下:

  • 平均数:计算每列的平均值

  • 标准差:计算每列的标准差

要执行 z 分数标准化,只需分析用于 min-max 归一化的相同数据集。我指的是名为 Airquality.csv 的数据集,它包含了 1973 年 5 月 1 日(星期二)至 1973 年 9 月 30 日的每日空气质量值。

要将 z 分数标准化应用于数据集列,请执行以下步骤:

  1. 在“配方”面板中,点击“新建步骤”按钮。再次,要打开“配方”面板,只需点击运行作业按钮左上角的图标(配方)。

  2. 从“转换”下拉菜单中选择“应用公式”(此选项将一个或多个列的值设置为公式的结果)。

  3. 从“列”框中选择 Ozone 列(相同的步骤可以应用于所有数据集变量)。

  4. 在“公式”框中,编辑以下公式:

  5. (Ozone- AVERAGE(Ozone))/STDEV(Ozone)

  6. 只需点击“添加”按钮。

在“配方”面板中添加以下语句:

Set Ozone to (Ozone- AVERAGE(Ozone))/STDEV(Ozone)

我们对其他三个变量 Solar_RWindTemp 也遵循相同的程序。最后,我们将以下行添加到配方面板中:

Set Solar_R to (Solar_R - AVERAGE(Solar_R))/STDEV(Solar_R)
Set Wind to (Wind - AVERAGE(Wind))/STDEV(Wind)
Set Temp to (Temp - AVERAGE(Temp))/STDEV(Temp)

变压器页面已经发生了如下变化:

z 分数标准化所做的修改是明显的:数据范围相当相似。这发生在数据集的每一列中,然后是每个变量。因此,由于不同的测量单位导致的规模差异已被消除。

根据假设,所有变量都必须满足 平均= 0标准差 =1。让我们来验证这一点。为此,只需使用之前章节中已经使用过的列详细信息面板中的统计信息。要打开列详细信息面板,从列的下拉菜单中选择列详细信息;以下面板将被打开:

因此,我们已经验证了臭氧变量具有零平均值和一的标准差。可以对其他变量执行相同的检查。

Google Cloud Dataflow

Google Cloud Dataflow 是一个全托管服务,用于创建在批量模式和流式模式下转换、丰富和分析数据的数据管道。Google Cloud Dataflow 从数据中提取有用信息,降低运营成本,无需实施、维护或调整数据基础设施,无需麻烦。

管道是一组按顺序连接的数据处理元素,其中前一个元素的输出是下一个元素的输入。数据管道被实现以提高吞吐量,即给定时间内执行的指令数量,并行化多个指令的处理流程。

通过适当地定义进程管理流程,可以从数据中提取知识时节省大量资源。由于采用无服务器方法来提供和管理资源,Dataflow 为解决最严重的数据处理问题提供了几乎无限的容量,但您只需为使用的部分付费。

Google Cloud Dataflow 自动提供和管理处理资源,以减少延迟时间并优化利用率。不再需要手动激活实例或预留它们。自动和优化的分区允许待处理的作业动态重新分配。您无需使用键盘快捷键或预处理您的输入数据。Cloud Dataflow 支持使用 Apache Beam SDK 中的表达性 Java 和 Python API 快速和简化管道开发。

Google Cloud Dataflow 作业按分钟计费,基于批量模式或 Cloud Dataflow 流式传输中实际使用的工人的数量。使用其他 GCP 资源(如 Cloud Storage 或 Cloud Pub/Sub)的作业将根据相应服务的价格计费。

摘要

在本章中,我们探讨了 Google Cloud Dataprep,这是一种用于数据预处理、提取特征和清理记录的有用服务。我们通过实际案例的例子深入了解了实践细节。我们首先查看云应用程序界面,以获取访问平台所需的一些初步信息。然后,我们分析了为准备最适合分析和建模的数据所提供的各种技术,这包括缺失数据的插补、检测和消除异常值以及处理不匹配值。我们发现了不同的数据转换方法以及数据清理的程度。接着,我们学习了如何规范化我们的数据,其中数据单位被消除,使我们能够轻松比较来自不同位置的数据。

第六章:机器学习基础知识

到目前为止,在之前的章节中,我们介绍了 GCP 中可用的各种 ETL 流程。在本章中,我们将通过以下主题开始我们的机器学习和深度学习之旅:

  • 机器学习应用

  • 监督学习和无监督学习

  • 主要机器学习技术的概述

  • 数据拆分

  • 测量模型的准确性

  • 机器学习和深度学习之间的区别

  • 深度学习应用

机器学习应用

机器学习涵盖了一系列从历史数据中学习的技巧。基于从历史数据中学习到的模式,机器学习技术预测未来数据集中事件发生的概率。鉴于机器学习的工作方式,这一系列技巧有多个应用。以下几节中,我们将探讨其中的一些。

金融服务业

在金融领域的某些应用如下:

  • 识别贷款/信用卡申请人的风险程度

  • 估计给定客户的信用额度

  • 预测卡交易是否为欺诈交易

  • 识别需要针对活动进行定位的客户细分市场

  • 预测客户在接下来的几个月内可能违约的可能性

  • 推荐客户应该购买的正确金融产品

零售行业

以下是一些机器学习不同技巧在零售行业中的应用:

  • 预测客户可能购买的下一种产品

  • 估计给定产品的最佳价格点

  • 预测产品随时间推移将销售的单元数量

  • 通过捆绑产品进行促销以定位客户

  • 估计客户终身价值

电信行业

以下是机器学习在电信行业中的几个应用:

  • 预测在通话开始前通话掉话的可能性

  • 预测客户在接下来的几个月内可能流失的可能性

  • 识别可以出售给客户的附加月度使用量

  • 识别不太可能为后付费服务付款的客户

  • 为现场销售人员优化劳动力

监督学习和无监督学习

监督学习构成了旨在构建一个近似函数的模型的一组技巧。该函数接受一组输入变量,这些变量也被称为独立变量,并试图将输入变量映射到输出变量,这些变量也被称为依赖变量或标签。

由于我们知道我们正在尝试预测的标签(或值),对于一组输入变量,该技术变成了一个监督学习问题。

以类似的方式,在无监督学习问题中,我们没有必须预测的输出变量。然而,在无监督学习中,我们试图将数据点分组,以便它们形成逻辑组。

可以通过以下图示获得监督学习和无监督学习的高层次区别:

图片

在前面的图中,监督学习方法可以区分两个类别,如下所示:

图片

在监督学习中,有两个主要目标可以实现:

  • 预测事件发生的概率——分类

  • 估计连续因变量的值——回归

帮助分类的主要方法如下:

  • 逻辑回归

  • 决策树

  • 随机森林

  • 梯度提升

  • 神经网络

除了逻辑回归之外,线性回归也有助于估计连续变量(回归)。

虽然这些技术有助于估计连续变量或预测事件发生的概率(离散变量预测),但无监督学习有助于分组。分组可以是行(这是一种典型的聚类技术)或列(一种降维技术)。行分组的主要方法包括:

  • K-means 聚类

  • 层次聚类

  • 基于密度的聚类

列分组的主要方法包括:

  • 主成分分析

  • t-Distributed Stochastic Neighbor Embedding (t-SNE)

列分组可以识别出数据集中存在的客户(观测值)的段。

列分组可以减少列的数量。当独立变量的数量很高时,这一点很有用。通常情况下,如果出现这种情况,构建模型可能会遇到问题,因为需要估计的权重数量可能很高。此外,在解释模型时也可能出现问题,因为一些独立变量之间可能高度相关。在这种情况下,主成分分析或 t-SNE 很有用,因为我们可以在不丢失太多数据集中现有信息的情况下减少独立变量的数量。

在下一节中,我们将概述所有主要的机器学习算法。

机器学习技术概述

在了解主要机器学习技术概述之前,让我们先看看在回归技术或分类技术中我们想要优化的函数。

回归中的目标函数

在回归练习中,我们估计连续变量的值。在这种情况下,我们的预测可能低于实际值或高于实际值;也就是说,误差值可以是正的也可以是负的。在这种情况下,目标函数转化为最小化数据集中每个观测值实际值和预测值之间差异的平方和。

用数学术语来说,前面的内容可以写成如下形式:

图片

在给定的方程中:

  • SSE 代表平方误差和

  • y 指的是因变量的实际值

  • y' 指的是因变量的估计值

  • ∑ 表示数据集中所有观测值的平方误差之和

给定目标函数,让我们从高层次了解线性回归是如何工作的。

线性回归

在线性回归中,我们假设自变量和因变量之间存在线性关系。线性回归表示如下:

在给定的方程中:

  • Y 是因变量

  • W 是与自变量 X 相关的权重

  • b 是截距值

如果有多个自变量(比如说两个自变量,x1x2),方程如下:

在给定的方程中:

  • w1 是与变量 x1 相关的权重

  • w2 是与变量 x2 相关的权重

典型的线性回归看起来如下,其中 x 轴是自变量,y 轴是因变量:

直线(具有特定的斜率和截距)是线性回归的方程。

注意图中线是使整体平方误差最小的线。

决策树

决策树是一种帮助我们从数据中推导规则的技巧。基于规则的技巧在解释模型如何估计因变量值时非常有帮助。

典型的决策树如下所示:

以下图示解释如下:

  • 根节点:这代表整个总体或样本,并进一步分割成两个或更多的节点。

  • 分割:根据一定规则将节点分割成两个或更多子节点的过程。

  • 决策节点:当一个子节点进一步分割成子节点时,它被称为决策节点

  • 叶节点/终端节点:决策树中的最后一个节点是叶节点或终端节点。

  • 剪枝:当我们移除决策节点的子节点时,这个过程称为剪枝。可以说它是分割过程的相反过程。

  • 分支/子树:整个树的子部分称为分支子树

  • 父节点和子节点:被分割成子节点的节点称为子节点的父节点,而子节点是父节点的子节点。

给定一个因变量和一个自变量值,我们将通过以下数据集了解决策树是如何工作的:

var2 response
0.1 1996
0.3 839
0.44 2229
0.51 2309
0.75 815
0.78 2295
0.84 1590

在前面的数据集中,变量 var2 是输入变量,而 response 变量是因变量。

在决策树的第一个步骤中,我们将输入变量从低到高排序,并逐一测试多个规则。

在第一种情况下,所有var2值小于0.3的观测值属于决策树的左节点,其他观测值属于决策树的右节点。

在回归练习中,左节点的预测值是所有属于左节点的观测值的response变量的平均值。同样,右节点的预测值是所有属于右节点的观测值的response的平均值。

给定左节点的预测值和属于右节点的观测值的不同的预测值,可以计算每个左节点和右节点的平方误差。一个可能规则的总体误差是左右节点平方误差的总和。

实施的决策规则是在所有可能的规则中具有最小平方误差的规则。

随机森林

随机森林是决策树的扩展。它是一个森林,因为它是由多个树组合而成的,并且是随机的,因为我们为每个决策树随机采样不同的观测值。

随机森林通过平均每个决策树的预测值(这些决策树在原始数据集的样本上工作)来工作。

通常,随机森林比单个决策树表现更好,因为在其中异常值的影响被减少了(因为在某些样本中,异常值可能没有出现),而在决策树中,异常值肯定会出现(如果原始数据集中包含异常值)。

梯度提升

当随机森林在一个构建多个并行树的框架中工作时,梯度提升采取了一种不同的方法——构建一个深度框架。

梯度提升中的梯度指的是实际值和预测值之间的差异,而提升指的是改进,即在不同迭代中改进误差。

梯度提升也利用了决策树工作的以下方式:

  • 构建决策树以估计因变量

  • 计算误差,即实际值和预测值之间的差异

  • 构建另一个预测误差的决策树

  • 通过考虑前一个决策树的预测误差来更新预测

这样,梯度提升持续构建一个预测前一个决策树误差的决策树,从而在梯度提升中构建了一个基于深度的框架。

神经网络

神经网络提供了一种近似非线性函数的方法。通过在加权输入变量的和上应用激活函数来实现非线性。

神经网络看起来是这样的:

图片

输入层包含输入,隐藏层包含加权输入值的总和,其中每个连接都与一个权重相关。

非线性应用于隐藏层。典型的非线性激活函数可以是 sigmoid、tanh 或修正线性单元。

输出层与每个隐藏单元相关的权重之和有关。通过调整权重以使总的平方误差值最小化,获得每个连接相关的权重的最优值。关于神经网络如何工作的更多细节将在后面的章节中提供。

逻辑回归

如前所述,逻辑回归用于根据输入数据集将预测分类为一类或另一类。逻辑回归使用 sigmoid 函数来获得事件发生的概率。

Sigmoid 曲线看起来是这样的:

图片

注意,当 x 轴的值大于 3 时,输出是一个高概率,而当 x 轴的值小于 3 时,输出是一个非常低的概率。

逻辑回归与线性回归的不同之处在于激活函数的使用。线性回归方程将是 Y = a + b * X,而逻辑回归方程将是:

图片

分类中的目标函数

在回归技术中,我们最小化总的平方误差。然而,在分类技术中,我们最小化总的交叉熵误差。

二元交叉熵误差如下:

图片

在给定的方程中:

  • y 是实际的因变量

  • p 是事件发生的概率

对于分类练习,所有前面的算法都适用;只是目标函数变为交叉熵误差最小化,而不是平方误差。

在决策树的情况下,属于根节点的变量是与其他所有独立变量相比提供最高信息增益的变量。信息增益定义为当树被给定变量分割时,整体熵的改善与未分割时相比。

数据分割

在处理任何机器学习模型时,需要解决的一个关键问题是:一旦在未来的数据集上实施,这个模型能达到多高的准确度?

无法立即回答这个问题。然而,从最终从模型构建中受益的商业团队那里获得认可非常重要。在这种情况下,将数据集分为训练集和测试集非常有用。

训练数据集是用于构建模型的数据。测试数据集是模型未见过的数据集;也就是说,数据点没有被用于构建模型。本质上,可以将测试数据集视为未来可能出现的数据集。因此,我们在测试数据集上看到的准确率可能是模型在未来数据集上的准确率。

通常,在回归中,我们处理的是泛化/过拟合的问题。当模型变得如此复杂以至于它完美地拟合所有数据点时,过拟合问题就会出现——从而产生最小的可能错误率。一个典型的过拟合数据集的例子如下所示:

图片

从图数据集中,我们可以观察到黑色曲线并不完美地拟合所有数据点,而蓝色曲线完美地拟合了这些点,因此它在训练数据点上的错误率最小。

然而,与新的数据集上的曲线相比,直线有更大的可能性具有更好的泛化能力。因此,在实践中,回归/分类是在模型的泛化能力和复杂性之间的一种权衡。

模型的泛化能力越低,未见数据点的错误率就越高。

这种现象可以在以下图表中观察到。随着模型复杂性的增加,未见数据点的错误率持续降低,直到达到一个点,之后又开始增加:

图片

蓝色曲线表示训练数据集上的错误率,红色曲线表示测试数据集上的错误率。

验证数据集用于获取模型的最佳超参数。例如,在随机森林或 GBM 等技术中,构建所需的树的数量或树的深度是一个超参数。随着我们不断改变超参数,未见数据集上的准确率也会发生变化。

然而,我们不能继续改变超参数直到测试数据集的准确率最高,因为在这样的情况下,我们已经看到了实际上的未来数据集(测试数据集)。

在这种情况下,验证数据集非常有用,我们不断在训练数据集上改变超参数,直到我们看到验证数据集上的准确率最高。这样就会形成模型的最佳超参数组合。

测量模型的准确度

评估模型准确性的方法在监督学习和无监督学习之间有所不同。

在典型的线性回归(预测连续值的情况下),有几种方法可以衡量模型的误差。通常,误差是在验证集和测试集上测量的,因为在一个训练集(用于构建模型的集合)上测量误差是有误导性的。因此,误差总是在未用于构建模型的集合上测量的。

绝对误差

绝对误差定义为预测值与实际值之间差异的绝对值。让我们想象以下场景:

实际值 预测值 误差 绝对误差
数据点 1 100 120 20 20
数据点 2 100 80 -20 20
总体 200 200 0 40

在先前的场景中,我们看到总体误差是 0(因为一个误差是+20,另一个是-20)。如果我们假设模型的总体误差为 0,我们就会忽略模型在个别数据点上表现不佳的事实。

因此,为了避免正误差和负误差相互抵消从而产生最小误差的问题,我们考虑模型的绝对误差,在这种情况下是 40;并且绝对误差率是 40/200 = 20%。

均方根误差

解决误差符号不一致问题的另一种方法是平方误差(负数的平方是正数)。之前讨论的场景可以翻译如下:

实际值 预测值 误差 平方误差
数据点 1 100 120 20 400
数据点 2 100 80 -20 400
总体 200 200 0 800

在这种情况下,总体平方误差是 800,均方根误差是(800/2)的平方根,即 20。

在分类练习的情况下,准确度如下测量:绝对误差和 RMSE 适用于预测连续变量。然而,预测具有离散结果的事件的流程是不同的。离散事件预测以概率的形式发生;也就是说,模型的结果是某个事件发生的概率。在这种情况下,尽管绝对误差和 RMSE 在理论上可以使用,但还有其他相关的指标。

混淆矩阵计算模型预测事件结果的数量,并将其与实际值进行比较,如下所示:

预测欺诈 预测非欺诈
实际欺诈 真阳性TP 假阴性FN
实际非欺诈 误报FP 真阴性TN

灵敏度或 TP 率或召回率 = TP/(总正数)= TP/(TP+FN) 特异性或 TN 率 = TN/(总负数)= TP/(FP + TN)精确度或正预测值 = TP/(TP + FP)准确度 = (TP + TN)/(TP + FN + FP + TN)F1 分数 = 2TP/(2TP + FP + FN)

接收者操作特征ROC)曲线给出了各种截止点的真正例率和假正例率之间的关系。假设模型预测值大于 0.8。我们假设我们应该将预测分类为阳性。这里的 0.8 是截止点。在这里,截止点变得重要,因为模型的预测始终是一个概率数——一个介于 0 和 1 之间的值。因此,分析师需要将自己的判断纳入确定最佳截止点。

ROC 曲线是一个曲线,其中(1-特异性)位于 x 轴上,敏感性位于 y 轴上。曲线是通过改变截止点(决定预测值应该是 1 还是 0)来生成敏感性(1-特异性)的各种组合而绘制的。

在一个理想场景中,数据可以清楚地分割,准确率为 100%,存在一个概率的截止点,在此之后预测值属于一个类别;低于截止点的值属于另一个类别。在这种情况下,对于某些截止点的值,ROC 曲线将仅在 y 轴上,即特异性=1。对于其余长度,曲线将与 x 轴平行。

一个典型的 ROC 曲线看起来是这样的:

图片

ROC 曲线是衡量模型性能相对于随机猜测有多好的一个指标。随机猜测是在 5% 的客户流失的情况下,随机猜测者猜测在每二十个客户中,将有一个被标记为潜在流失者。在这种情况下,随机猜测将在随机标记 20% 的所有客户后捕获 20% 的所有流失者。

模型的预测能力在于尽可能接近 100% 的准确性,即尽可能远离随机猜测。

曲线下的面积AUC)是模型曲线与随机猜测曲线之间面积的一个度量。AUC 越高,模型的预测准确性就越高。

机器学习和深度学习之间的区别

到目前为止,我们已经从高层次上了解了各种机器学习算法是如何工作的。在本节中,我们将了解深度学习与机器学习的区别。

机器学习任务的一个关键属性是输入由分析师或数据科学家提供。通常,特征工程在提高模型准确性方面起着关键作用。此外,如果输入数据集是非结构化的,特征工程就会变得非常复杂。往往,这归结为个人在推导相关特征以构建更准确模型方面的知识。

例如,让我们想象一个场景,在这个场景中,给定一个句子中的单词集合,我们试图预测下一个单词。在这种情况下,传统的机器学习算法工作如下:

  • 对句子中的每个单词进行独热编码

  • 使用 one-hot 编码向量表示单词的输入序列

  • 使用 one-hot 编码向量表示输出单词

  • 通过优化相关损失函数来构建一个模型,以预测给定输入单词集的输出单词向量

虽然上述方法有效,但在构建模型时我们面临三个主要挑战:

  • One-hot 编码向量的维度:

    • 一段文本可能包含数百或数千个独特的单词

    • 高维数据可能会引起多个问题——例如多重共线性以及构建模型所需的时间

  • 输入数据集中缺少单词的顺序

  • 两个单词之间的距离相同,无论这两个单词是否相似:

    • 例如,在一个 one-hot 编码的向量场景中,king 和 prince 之间的距离将与 king 和 cheese 之间的距离相同

在这种情况下,深度学习非常有用。通过使用深度学习中的某些技术(例如 Word2vec),我们能够解决刚才列出的问题中的以下问题:

  • 以一种方式在低维空间中表示每个单词,使得相似的单词具有相似的单词向量,而不相似的单词不具有相似的向量

  • 此外,通过在低维空间(比如说 100 维)中表示一个单词,我们就解决了数据高维的问题

Word2vec 技术有多种变体,例如连续词袋模型和连续跳字模型。

CBOW 模型的架构如下:

注意,输入向量是 one-hot 编码版本(正如我们在典型的机器学习模型中会使用的那样)。隐藏层神经元确保我们将 10,000 维的输入向量表示为 300 维的单词向量。

输出层中的实际值代表周围单词(构成上下文)的 one-hot 编码版本。

深度学习中另一种有助于解决上述问题的技术是 循环神经网络RNN)。RNN 致力于解决传统机器学习在之前场景中面临的单词序列问题。

RNN 提供每个单词向量以预测序列中的下一个单词。关于 RNN 如何工作的更多细节将在另一章中提供。RNN 技术的流行变体包括 长短期记忆LSTM)和 门控循环单元GRU):

上述图表示了一个典型的循环神经网络(RNN),其中 x[(t-1)]x[(t)]x[(t+1)] 代表每个时间段的单词,W 是用于预测下一个单词的前一个单词的权重,而 O[(t)] 是时间 t 的输出。

当需要与序列中很久以前出现的单词相关联的权重很高时,LSTM 非常有用。

Word2vec 和 RNN 的组合,它们是神经网络的不同变体,有助于避免在给定的文本数据中特征工程带来的挑战。

为了巩固我们对机器学习和深度学习之间差异的理解,让我们通过另一个示例:预测图像的标签。

我们将使用一个经典示例——MNIST 数据集(我们将在未来的章节中更多地使用 MNIST)。

MNIST 数据集包含从零到九的各种数字的图像。每个图像的大小为 28 x 28 像素。任务是通过对各种像素值进行分析来预测图像的标签。MNIST 数据集中的一个示例图像如下所示:

传统机器学习解决前面问题的方式如下:

  • 将每个像素视为一个单独的变量;也就是说,我们总共有 784 个变量

  • 对标签列进行 one-hot 编码

  • 预测标签发生的概率

解决前面问题的挑战如下:

  • 模型不会考虑像素的相邻关系

  • 模型不会考虑图像的平移或旋转

例如,当图像被适当地移动时,一个零可能看起来像六,反之亦然。同样,如果所有图像都是使用一个数据集进行训练,该数据集中的所有数字都集中在图像中,但测试数据集有一个图像稍微向右或向左移动,预测很可能是准确的。这是因为模型会给每个像素分配权重。

为了解决前面的问题,一种名为卷积神经网络CNN)的深度学习技术非常有用。CNN 的工作方式是它在区域级别而不是像素级别分配权重。本质上,这形成了卷积神经网络中的卷积部分。通过这种方式,使用深度学习考虑了像素的相邻关系。

同样,图像的平移是通过在 CNN 中使用的一种称为最大池化的技术来考虑的。

CNN 的典型架构如下,其更多细节将在后面的章节中解释:

在前面的图中,输入是我们考虑的图像。conv1是应用卷积于滤波器和输入时的输出。鉴于我们应用了多个滤波器,我们将有多个卷积,pool1是应用池化于卷积输出时的输出。卷积和池化的过程会反复应用,直到我们获得最终的完全连接单元,然后将其连接到输出。

深度学习的应用

在上一节中,我们了解了为什么在某些应用中深度学习比机器学习更出色。让我们来看看深度学习的一些应用:

  • 从一种语言翻译到另一种语言

  • 语音转文本转换

  • 多个行业中的图像分析

  • 识别图像中的文本

  • 图像和音频合成

  • 个性化预测用户可能观看/购买的下部电影/产品

  • 时间序列分析

  • 检测罕见事件

摘要

在本章中,我们了解了监督学习和无监督学习之间的主要区别,并对主要的机器学习算法有了概述。我们还通过文本和图像分析的例子,了解了深度学习算法在哪些领域比传统的机器学习算法更出色。

第七章:Google 机器学习 API

如前一章所示,机器学习被广泛应用于各种应用中。然而,一些应用容易构建,而一些则非常难以构建,尤其是对于不太熟悉机器学习的用户。我们将在本章讨论的一些应用属于难以构建的类别,因为这些应用的机器学习模型构建过程数据密集、资源密集,并且需要该领域的大量知识。

在本章中,我们将介绍谷歌(截至 2018 年 3 月)提供的五个机器学习 API。这些 API 旨在作为 RESTful API 直接使用。对于以下提到的每个服务,我们将展示哪些类型的应用程序可以从中受益,以及如何解释返回的结果:

  • 视觉具有标签检测、OCR、面部检测、情感、标志和地标

  • 语音意味着语音转文本

  • NLP 有实体、情感和 POS

  • 翻译

  • 视频智能

视觉 API

视觉 API 允许我们构建许多与视觉相关的应用程序:

  • 在图像中检测标签

  • 在图像中检测文本

  • 面部检测

  • 情感检测

  • 标志检测

  • 地标检测

在我们深入构建应用程序之前,让我们快速了解它们可能如何构建,以面部情感检测为例。

情感检测的过程包括:

  1. 收集大量图像

  2. 使用图像中可能表示的情感人工标记图像

  3. 训练一个卷积神经网络CNN)(将在未来章节中讨论)来根据图像输入分类情感

虽然前述步骤资源消耗很大(因为我们需要很多人来收集和人工标记图像),但还有多种其他方式可以获得面部情感检测。我们不确定谷歌是如何收集和标记图像的,但我们现在将考虑谷歌为我们构建的 API,这样,如果我们想将图像分类为它们所代表的情感,我们可以利用该 API。

启用 API

在我们开始构建应用程序之前,我们首先必须启用 API,如下所示:

  1. 搜索 Google Cloud Vision API:

图片

  1. 启用 Google Cloud Vision API:

图片

  1. 一旦点击 ENABLE,API 将为项目(即我的第一个项目)启用,如前一张截图所示。

  2. 获取 API 凭据:

图片

  1. 点击创建凭据后点击服务帐户密钥:

图片

  1. 点击新建服务帐户:

图片

  1. 输入服务帐户名称(在我的情况下,kish-gcp)并选择项目所有者角色:

图片

  1. 点击创建以保存密钥的 JSON 文件。

打开实例

为了打开一个实例,点击 VM 实例,如下面的截图所示,然后点击激活 Google Cloud Shell 图标:

使用 Cloud Shell 创建实例

点击云壳图标后,我们创建了一个如下所示的实例:

  1. 通过指定以下代码创建一个实例:
datalab create --no-create-repository <instance name>
  1. 在 Cloud Shell 中,前面的代码如下所示:

  1. 一旦你为所有提示输入了响应,你需要将端口更改为8081以访问 Datalab,操作如下:

  1. 点击更改端口后,你会看到一个如下所示的窗口。输入8081并点击“更改并预览”以打开 Datalab:

  1. 这将打开 Datalab,它具有使我们能够编写所有类型命令的功能:bashbigquerypython等等。

现在需求已经设置好,让我们获取/安装 API 的需求:

  1. 在上一节中访问 API 密钥,我们已经下载了所需的密钥。现在,让我们通过点击上传按钮将.json文件上传到 Datalab:

  1. 一旦.json文件上传,你应该能够从这里通过 Datalab 访问它:

  1. 打开一个笔记本;你可以在 Datalab 中通过点击笔记本标签来打开一个笔记本,如下所示:

  1. 要安装google-cloud,一旦你打开笔记本,将内核从 python2 更改为 python3:

  1. 按照以下方式安装google-cloud包:
%bash
pip install google-cloud
  1. 一旦安装了google-cloud,确保之前上传的.json文件在当前 Python 环境中可访问,如下所示:
import os
os.environ["GOOGLE_APPLICATION_CREDENTIALS"] = "/content/datalab/google-
api.json"
  1. 为了上传感兴趣的画面,我们将查看从本地机器到存储桶,以及从存储桶到 Datalab 的文件传输。

  2. 在 Google Cloud 中搜索bucket

  1. 现在,命名存储桶并创建它:

  1. 点击上传文件将相关文件从本地机器上传到存储桶。

  1. 一旦文件上传到存储桶,按照以下方式从 Datalab 获取它:

  1. 现在,你应该注意到11.jpg在 Datalab 中可访问。

现在要分析的画面在 Datalab 中可访问,让我们了解如何利用 Cloud Vision API 更好地理解图像:

  1. 导入相关包:
from google.cloud import vision

前面的代码片段确保了 Vision 中可用的方法在当前会话中可访问。

  1. 调用在客户端图像上执行 Google Cloud Vision API 检测任务(如人脸、地标、标志、标签和文本检测)的服务 - ImageAnnotator
client = vision.ImageAnnotatorClient()
  1. 确认图像已按预期上传:
import matplotlib.pyplot as plt
import matplotlib.image as mpimg
%matplotlib inline
img=mpimg.imread('/content/datalab/11.jpg')
plt.axis('off')
plt.imshow(img)

图片

  1. 调用face_detection方法以获取图像的相关详细信息,如下所示:
response = client.face_detection({'source' : {'image_uri': "gs://kish-
bucket/11.jpg"},})
  1. 图像注释的响应如下:

图片图片

  1. 现在我们已经运行了我们的方法来检测图像中的面部,让我们看看输出 - responseresponse的输出是一组属性,如前所述:
response

图片

以下是一些详细解释的额外点:

  • 边界多边形:边界多边形围绕人脸。边界框的坐标以原始图像的比例为单位,如ImageParams中返回的。边界框是根据人类预期计算出来的,以框定人脸。它基于人脸标记器的结果。注意,如果图像中只出现部分人脸,则BoundingPoly中可能不会生成一个或多个x和/或y坐标(多边形将是不封闭的)。

  • 人脸检测边界多边形fd_bounding_poly边界多边形比BoundingPoly更紧密,仅包围人脸的皮肤部分。通常,它用于从任何检测图像中可见皮肤数量的图像分析中消除人脸。

  • 地标:检测到的人脸关键点。

在以下要点中解释了几个更多术语:

  • roll_angle:翻滚角,表示人脸相对于图像的顺时针/逆时针旋转量。范围是[-180,180]。

  • pan_angle:偏航角,表示人脸相对于垂直于图像的平面的左/右角度。范围是[-180,180]。

  • tilt_angle:俯仰角,表示人脸相对于图像水平平面的向上/向下角度。范围是[-180,180]。

  • detection_confidence:与检测相关的置信度。

  • landmarking_confidence:与标记相关的置信度。

  • joy_likelihood:与快乐相关的似然性。

  • sorrow_likelihood:与悲伤相关的似然性。

  • anger_likelihood:与愤怒相关的似然性。

  • surprise_likelihood:与惊讶相关的似然性。

  • under_exposed_likelihood:与曝光相关的似然性。

  • blurred_likelihood:与模糊相关的似然性。

  • headwear_likelihood:与头部佩戴物相关的似然性。

人脸关键点将进一步提供眼睛、鼻子、嘴唇、耳朵等位置。

我们应该能够围绕识别出的人脸绘制一个边界框。

face_annotations的输出如下:

图片

从前面的代码中,我们应该能够理解边框的坐标。在接下来的代码中,我们计算边框的起始点,以及边框的相应宽度和高度。一旦计算完成,我们就在原始图像上叠加矩形:

import matplotlib.patches as patches
import numpy as np
fig,ax = plt.subplots(1)

# Display the image
ax.imshow(img)

# Create a Rectangle patch
x_width = np.abs(response.face_annotations[0].bounding_poly.vertices[1].x-
  response.face_annotations[0].bounding_poly.vertices[0].x)
y_height = np.abs(response.face_annotations[0].bounding_poly.vertices[1].y-
  response.face_annotations[0].bounding_poly.vertices[3].y)

rect =
 patches.Rectangle((response.face_annotations[0].bounding_poly.vertices[0].x,
 response.face_annotations[0].bounding_poly.vertices[0].y),
                         x_width,y_height,linewidth=5,edgecolor='y',facecolor='none')

# Add the patch to the Axes
ax.add_patch(rect)
plt.axis('off')
plt.show()

前述代码的输出是带有面部边框的图像,如下所示:

图片

标签检测

在前面的代码片段中,我们使用了face_detection方法来获取各种坐标。

为了理解图像的标签,我们将使用label_detection方法代替face_detection,如下所示:

response_label = client.label_detection({'source' : {'image_uri': "gs://kish-
bucket/11.jpg"},})

图片

标签检测的输出是一系列标签,以及与每个标签相关的分数。

文本检测

可以通过使用text_detection方法来识别图像中的文本,如下所示:

response_text = client.text_detection({'source' : {'image_uri': "gs://kish-
bucket/11.jpg"},})

response_text的输出如下:

图片

注意,text_detection方法的输出是图像中存在的各种文本的边框。

此外,请注意,text_annotations的描述提供了图像中检测到的文本。

标志检测

视觉服务还使我们能够通过使用logo_detection方法来识别图像中的标志。

在下面的代码中,你可以看到我们能够通过传递图像位置的 URL 来检测wikipedia的标志,如下所示:

response = client.logo_detection({'source' : {'image_uri':
"https://upload.wikimedia.org/wikipedia/commons/thumb/b/b3/Wikipedia-logo-v2-
en.svg/135px-Wikipedia-logo-v2-en.svg.png"},})

logo_detection方法的输出如下:

图片

地标检测

注意,在前面的代码行中,我们在logo_detection方法中指定了图像位置的 URL,这导致了预测的标志描述,以及与其相关的置信度分数。

同样,任何位于图像中的地标都可以通过使用landmark_detection方法进行检测,如下所示:

response = client.landmark_detection({'source' : {'image_uri': 
 "https://upload.wikimedia.org/wikipedia/commons/thumb/1/1d/
  Taj_Mahal_%28Edited%29.jpeg/250px-Taj_Mahal_%28Edited%29.jpeg"},})

landmark_detection方法的输出如下:

图片

云翻译 API

云翻译 API 提供了一个简单、程序化的接口,用于将任意字符串翻译成任何支持的语言,使用最先进的神经机器翻译。翻译 API 响应速度快,因此网站和应用程序可以集成翻译 API,以实现从源语言到目标语言的快速、动态翻译(例如,从法语翻译成英语)。对于源语言未知的情况,也提供了语言检测功能。

启用 API

为了我们能够使用 Google 云翻译服务,我们需要启用,这可以通过以下步骤完成:

  1. 为了启用 Google Cloud Translation API,在控制台中搜索该 API:

图片

  1. 启用 Google Cloud Translation API:

  1. 一旦启用翻译 API,下一步就是创建访问 API 的凭证。然而,请注意,如果您已经为某个 API 创建了凭证,则它们可以用于任何其他 API。让我们继续使用 Cloud Shell 初始化我们的实例:

  1. 一旦实例启动,我们将打开端口8081上的 Datalab。我们提供以下路径到api-key文件的位置:
import os
os.environ["GOOGLE_APPLICATION_CREDENTIALS"] = "/content/datalab/google-api.json"
  1. 使用以下语句导入translate的各种方法:
from google.cloud import translate
  1. 创建一个client对象,用于连接到云翻译服务,如下所示:
client = translate.Client()

Google Cloud 翻译 API 有三个支持的方法,分别是get_languages()detect_language()translate()

  • client.get_languages()方法为我们提供了一个所有可用语言的列表,以及它们的缩写符号,如下所示:

  • client.detect_language()方法检测文本所使用的语言:

注意,在上述方法中,我们提供了两个文本——一个是西班牙语,另一个是英语。上述输出表示文本的语言,以及与语言检测相关的置信度。

  • client.translate()方法检测源语言并将文本翻译成英语(默认),如下所示:

  • client.translate()方法还提供了指定需要翻译成哪种目标语言的选项,如下所示:

自然语言 API

Google Cloud 自然语言 API 通过提供易于使用的 REST API 中的强大机器学习模型来揭示文本的结构和含义。您可以使用它从文本文档、新闻文章或博客文章中提取有关人物、地点、事件等信息,还可以用它来了解社交媒体上对您产品的看法,或从呼叫中心或消息应用中的客户对话中解析意图。您还可以分析请求中上传的文本,或将其与 Google Cloud 存储上的文档存储集成。

您可以通过在控制台中搜索它来找到云自然语言 API,如下所示:

云自然语言 API 已在生成的页面中启用:

与翻译 API 类似,如果至少已启用了一个 API,则无需为该 API 创建凭证。

自然语言处理在提取与各种文本相关的情感方面可能很有用。

情感分析检查给定的文本,并识别文本中的主导情感意见,以确定作者的立场是积极、消极还是中立。情感分析是通过 analyzeSentiment 方法进行的。

在以下示例中,让我们了解如何识别语句的情感:

  1. 导入相关包:
from google.cloud import language
  1. 初始化与语言服务对应的类:
client = language.LanguageServiceClient()

Google 自然语言 API 有以下支持的方法:

  • analyzeEntities

  • analyzeSentiment

  • analyzeEntitySentiment

  • annotateText

  • classifyText

每个方法都使用 Document 来表示文本。以下示例中,让我们探索 analyzeSentiment 方法:

text="this is a good text"
from google.cloud.language_v1 import types
document = types.Document(
        content=text,
        type='PLAIN_TEXT')
sentiment = client.analyze_sentiment(document).document_sentiment
sentiment.score

注意,我们已经将输入文本转换为 Document 类型,然后分析了文档的情感。

情感得分的输出反映了文本为正面的概率;得分越接近一,陈述就越积极。

类似地,可以传递一个 HTML 文件,如下所示:

存储在 Google Cloud 存储桶中的文件也可以通过将内容更改为 gcs_content_uri 来引用,如下所示:

analyze_entities() 方法在文本中找到命名实体(即专有名称)。此方法返回 AnalyzeEntitiesResponse

document = language.types.Document(content='Michelangelo Caravaggio, Italian    painter, is known for "The Calling of Saint Matthew".'
                                   ,type='PLAIN_TEXT') 
response = client.analyze_entities(document=document)

for entity in response.entities:
  print('name: {0}'.format(entity.name)) 

上述循环的输出是文档内容中存在的命名实体,如下所示:

我们还可以通过使用 analyze_syntax 方法提取给定文本中每个单词的词性,如下所示:

  1. 将文档标记化成构成文本的相应单词:
tokens = client.analyze_syntax(document).tokens
tokens[0].text.content
# The preceding output is u'Michelangelo'
  1. 然后,可以提取 token 的词性,如下所示:
pos_tag = ('UNKNOWN', 'ADJ', 'ADP', 'ADV', 'CONJ', 'DET', 'NOUN', 'NUM','PRON', 'PRT', 'PUNCT', 'VERB', 'X', 'AFFIX')
for token in tokens:print(u'{}: {}'.format(pos_tag[token.part_of_speech.tag],
                               token.text.content))

上述代码的输出如下:

注意,大多数单词都被正确地分类为词性。

语音转文本 API

Google Cloud Speech API 允许开发者通过应用易于使用的 API 中的强大神经网络模型将音频转换为文本。该 API 识别超过 110 种语言和变体。可以将用户对应用程序麦克风的语音指令转录成文本,通过语音实现命令和控制,或者转录音频文件,等等。

为了启用语音转文本 API,在控制台中搜索它,如下所示:

在生成的网页中,启用 API,如下所示:

与前几节中提到的 API 类似,为其中一个 API 获得的凭证可以复制用于其他 Google API。因此,我们不需要为语音转文本 API 分别创建凭证。

一旦启用 API,让我们启动 Cloud Shell 和 Datalab,就像前几节中做的那样。

在以下代码中,我们将一个小音频文件转录成文本:

  1. 导入相关包和 API 密钥:
from google.cloud import speech
import os
os.environ["GOOGLE_APPLICATION_CREDENTIALS"] = "/content/datalab/google-api.json"
from google.cloud.speech import enums
from google.cloud.speech import types
  1. 按照以下步骤调用语音服务:
client = speech.SpeechClient()
  1. 按以下方式指定我们想要转换的音频:
audio = types.RecognitionAudio(uri='gs://kish-bucket/how_are_you.flac')

注意无损音频编解码器FLAC)。

可以使用位于audio.online-convert.com/convert-to-flac的转换器将音频文件(.wav)转换为.flac文件。

文件位于我们之前创建的存储桶中。我们指定音频配置,如下所示:

config = types.RecognitionConfig(
encoding=enums.RecognitionConfig.AudioEncoding.FLAC,
sample_rate_hertz=16000,
language_code='en-US')

通过传递audio内容和指定的配置来获得响应:

response = client.recognize(config, audio)

现在可以按照以下方式访问结果:

for result in response.results: 
  print(result)

这的输出如下:

图片

当输入音频文件是短(<1 分钟)持续时间音频时,recognize方法才会工作。

如果音频文件持续时间更长,则应使用long_running_recognize方法:

operation = client.long_running_recognize(config, audio)

可以通过指定以下内容来访问result

response = operation.result(timeout=90)

最后,可以通过打印响应结果来获取转录和置信度,就像之前做的那样。

视频智能 API

云视频智能 API 通过使用易于使用的 REST API 提取元数据,使视频可搜索和可发现。现在,您可以搜索目录中每个视频文件的每一刻。它快速标注存储在 Google Cloud 存储中的视频,并帮助您识别视频中的关键实体(名词)及其出现时间。

可以按照以下方式搜索和启用云视频智能 API:

图片图片

按以下方式导入所需的包并添加api-key的路径:

from google.cloud import videointelligence
import os
os.environ["GOOGLE_APPLICATION_CREDENTIALS"] = "/content/datalab/google-api.json"
from google.cloud.speech import enums
from google.cloud.speech import types

features方法使我们能够指定我们想要在视频中检测的内容类型。可用的功能如下:

图片

让我们继续检测我们感兴趣的视频中标签:

features = [videointelligence.enums.Feature.LABEL_DETECTION]

按以下方式指定视频的config和上下文:

mode = videointelligence.enums.LabelDetectionMode.SHOT_AND_FRAME_MODE
config = videointelligence.types.LabelDetectionConfig(
    label_detection_mode=mode)
context = videointelligence.types.VideoContext(
    label_detection_config=config)

然后,需要按照以下方式将视频从云存储传递过来:

path="gs://kish-bucket/Hemanvi_video.mp4"
operation = video_client.annotate_video(
        path, features=features, video_context=context)

按以下方式访问annotate_video方法的输出:

result = operation.result(timeout=90)

可以在以下级别获得视频的注释结果:

  • 视频段级别

  • 视频镜头级别

  • 帧级别

在遍历每个不同段标签注释后,可以在以下方式获得段级别结果:

segment_labels = result.annotation_results[0].segment_label_annotations
for i, segment_label in enumerate(segment_labels):
    print('Video label description: {}'.format(
        segment_label.entity.description))
    for category_entity in segment_label.category_entities:
        print('\tLabel category description: {}'.format(
            category_entity.description))

    for i, segment in enumerate(segment_label.segments):
        start_time = (segment.segment.start_time_offset.seconds +
                      segment.segment.start_time_offset.nanos / 1e9)
        end_time = (segment.segment.end_time_offset.seconds +
                    segment.segment.end_time_offset.nanos / 1e9)
        positions = '{}s to {}s'.format(start_time, end_time)
        confidence = segment.confidence
        print('\tSegment {}: {}'.format(i, positions))
        print('\tConfidence: {}'.format(confidence))
    print('\n')

上述代码的输出如下:

图片

同样,可以按照以下方式获得镜头级别的结果:

shot_labels = result.annotation_results[0].shot_label_annotations
for i, shot_label in enumerate(shot_labels):
    print('Shot label description: {}'.format(
        shot_label.entity.description))
    for category_entity in shot_label.category_entities:
        print('\tLabel category description: {}'.format(
            category_entity.description))

    for i, shot in enumerate(shot_label.segments):
        start_time = (shot.segment.start_time_offset.seconds +
                      shot.segment.start_time_offset.nanos / 1e9)
        end_time = (shot.segment.end_time_offset.seconds +
                    shot.segment.end_time_offset.nanos / 1e9)
        positions = '{}s to {}s'.format(start_time, end_time)
        confidence = shot.confidence
        print('\tSegment {}: {}'.format(i, positions))
        print('\tConfidence: {}'.format(confidence))
    print('\n')

上述代码行的输出如下:

图片

最后,可以按照以下方式获得帧级别的结果:

frame_labels = result.annotation_results[0].frame_label_annotations
for i, frame_label in enumerate(frame_labels):
    print('Frame label description: {}'.format(
        frame_label.entity.description))
    for category_entity in frame_label.category_entities:
        print('\tLabel category description: {}'.format(
            category_entity.description))

    # Each frame_label_annotation has many frames,
    # here we print information only about the first frame.
    frame = frame_label.frames[0]
    time_offset = (frame.time_offset.seconds +
                   frame.time_offset.nanos / 1e9)
    print('\tFirst frame time offset: {}s'.format(time_offset))
    print('\tFirst frame confidence: {}'.format(frame.confidence))
    print('\n')

上述代码行的输出如下:

图片

摘要

在本章中,我们了解了谷歌提供的主要机器学习 API:视觉、翻译、自然语言处理、语音和视频智能。我们学习了每个 API 中的各种方法如何使我们能够复制深度学习结果,而无需从头编写代码。

第八章:使用 Firebase 创建机器学习应用程序

在上一章中,我们学习了如何使用一些 Google 机器学习 API 来预测/分类事件。然而,我们在 Datalab 中完成了所有工作。在实际场景中,我们可能希望将机器学习 API 集成到我们构建的网络应用程序或移动应用程序中。在这种情况下,Firebase 非常有用。Firebase 是一个平台,允许我们构建无需服务器端编程的 Web 和移动应用程序。Firebase 提供了多个功能,确保开发者专注于构建应用程序,而后端由 Firebase 负责。Firebase 提供的一些功能包括:

  • 实时数据库

  • 文件存储

  • 云函数

  • 主机服务

  • 性能监控

  • 分析

  • 身份验证

在本章中,我们将了解 Firebase 提供的各种功能。此外,为了了解 Firebase 如何帮助构建具有机器学习功能的应用程序,我们将构建一个网络应用程序和一个移动应用程序,该应用程序使用 Google 翻译 API 将任何给定语言的文本翻译成英语,并提供最常翻译的文本。

Firebase 的功能

Firebase 提供的一些功能如下:

  • 实时数据库:使我们能够在毫秒内存储和同步应用程序数据

  • 云 Firestore:使我们能够在全球范围内存储和同步数据

  • 云函数:使我们能够在不管理服务器的情况下运行后端代码

  • 主机服务:以速度和安全交付网络应用程序资产

  • 性能监控:帮助我们深入了解应用程序的性能

  • 崩溃分析:使我们能够通过强大的实时崩溃报告来优先处理和修复问题

  • 身份验证:帮助我们简单且安全地验证用户

  • 云存储:使我们能够在 Google 规模上存储和提供文件

  • 预测:使我们能够根据预测的行为定义动态用户组

  • 远程配置:使我们能够在不部署新版本的情况下修改我们的应用程序

  • 应用索引:使我们能够将搜索流量引导到移动应用程序

  • 云消息服务:使我们能够发送有针对性的消息和通知

  • 动态链接:使我们能够通过使用带有归因的深度链接来驱动增长

  • 邀请:使我们能够通过使用带有归因的深度链接来驱动增长

在本章中,我们将构建一个应用程序,该应用程序可以将输入文本翻译成英语——首先是一个网络应用程序,然后是一个移动应用程序。

构建网络应用程序

为了构建网络应用程序,我们使用了 Node.js。

下载并安装 Node.js:

  1. node.js可以从以下链接下载:nodejs.org/en/

    对于我们现在正在构建的版本,我们将在 Windows 64 位机器上使用 Node.js 的 8.11.1 LTS 版本。

  2. 下载可执行文件 nodejs.org/dist/v8.11.1/node-v8.11.1-x64.msi 后,请确保使用默认参数安装 Node.js。

创建 Firebase 项目:

  1. 可以通过登录 Firebase 控制台在这里创建 Firebase 项目:console.firebase.google.com

  2. 在控制台中,点击添加项目:

  1. 输入项目名称(用红色突出显示)并获取项目 ID(用黑色突出显示),如下所示:

  1. 使用 Node.js 包管理器安装 Firebase 工具,如下所示:

    • 将目录更改为需要存储 firebase 函数文件的文件夹。在以下屏幕截图中,我们在 E 驱动器中创建了一个名为 firebase 的文件夹:

  1. 我们使用以下代码片段安装 Firebase 工具:

登录并初始化 Firebase:

  1. 通过指定以下内容登录 Firebase:
firebase login --reauth
  1. 之前的代码片段将允许我们使用我们的凭据登录。请确保允许 Firebase CLI 访问您的 Google 账户。

  2. 一旦我们登录到 Firebase,我们就可以如下初始化 firebase

firebase init
  1. 你将看到以下屏幕:

  1. Y 初始化 Firebase。

  2. 通过按空格键选择当前应用程序所需的特性,选择完成后按 Enter 键:

  1. 选中后,对于我们这里使用的版本,让我们指定我们的函数使用 JavaScript 部署,如下所示:

  1. 选中后,我们使用项目目录设置项目:

注意 mytranslator 是我们在 步骤 2 中创建的项目。还请注意,一旦我们初始化了 Firebase,文件夹结构如下所示:

  1. 在命令提示符中,初始化 Firebase 后按 Enter 键以获取各种提示。初始化完成后,你应该会收到以下确认信息:

在滚动到 functions 文件夹后,使用 Node.js 包管理器安装 Google Translate:

我们指定了我们用例所需的所有功能(公共 API 方法)。这些函数处理所有服务器编程:

  1. 为了指定这些,让我们用以下代码片段覆盖 functions 文件夹中现有的 index.js 文件。
const functions = require('firebase-functions');
const Translate = require('@google-cloud/translate');
const admin = require("firebase-admin")

//setting connection to db
admin.initializeApp();

const translate = new Translate({
    projectId: 'mytranslator-c656d'
});
//Extract the most searched term

exports.getMessageStats=functions.https.onRequest((req,res) =>
 {
 var output;
 var db = admin.database();
 var ref = db.ref("/translateMessageStats");

// Attach an asynchronous callback to read the data at our posts reference
 ref.orderByChild("count").limitToLast(1).on("value", function(snapshot) {

console.log(snapshot.forEach(element => {
 output=element.key+" : "+element.val().count + 'times'
 }))
 res.header("Access-Control-Allow-Origin", "*");
 return res.send(JSON.stringify(output));
 }, function (errorObject) {
 console.log("The read failed: " + errorObject.code);
 });
})

// create a public API method of name "translateMessage"

exports.translateMessage=functions.https.onRequest((req,res) =>
 {
 const input = req.query.text;

translate.translate(input,'en').then(results =>
 {
 const output = results[0];
 console.log(output);

const db = admin.database();
var ref = db.ref("/translateMessageStats");

//update database
 var dataRef= ref.child(input);

dataRef.once('value', function(snapshot) {
 if (snapshot.exists()) {
 dataRef.update({"count":snapshot.val().count+1});
 console.log("data exists")
 }
 else
 {
 console.log("data does not exist")
 dataRef.update({"count":1});
 }
 });

res.header("Access-Control-Allow-Origin", "*");
 return res.send(JSON.stringify(output));
 })
});
  1. 在此代码中,我们通过以下代码导入所需的 Node.js 包:
const functions = require('firebase-functions');
const Translate = require('@google-cloud/translate');
const admin = require("firebase-admin")
  1. 我们通过指定以下内容初始化与数据库的连接:
admin.initializeApp();
  1. 我们创建translate对象,并将项目 ID 作为参数传递给它,如下所示:
const translate = new Translate({
    projectId: 'mytranslator-c656d'
});
  1. 然后,我们创建一个名为translateMessage的公开 API,如下所示:
exports.translateMessage=functions.https.onRequest((req,res) =>
  1. 用户输入通过以下行获取:
const input = req.query.text;
  1. 输入文本的翻译和相应存储在输出中的翻译文本是通过此代码完成的:
translate.translate(input,'en').then(results =>
{
    const output = results[0];
  1. 我们创建一个数据库实例,如下所示:
const db = admin.database();
 var ref = db.ref("/translateMessageStats");
  1. 输入在数据库中更新:
var dataRef= ref.child(input);
  1. 如果给出新的输入,则count初始化为1;否则,count增加1
dataRef.once('value', function(snapshot) {
    if (snapshot.exists()) {
        dataRef.update({"count":snapshot.val().count+1});
        console.log("data exists")
    }
    else
    {
        console.log("data does not exist")
        dataRef.update({"count":1});
    }
  });

启用Google Cloud Translation API,如下所示:

部署firebase函数:

  1. 我们可以按照以下截图所示部署firebase函数:

  1. 函数部署后,检查项目概述的开发部分中的函数:

  1. 点击函数后,我们应该能够看到一个包含我们刚刚创建的函数translateMessage的仪表板:

注意,前面的事件为我们提供了一个 URL,使用该 URL 我们应该能够翻译输入文本,如下所示:

注意 URL 中?text=的使用,这是输入。

如果执行过程中出现问题,我们应该能够在函数仪表板的日志标签中理解它们。

此外,我们搜索过的所有输入都存储在数据库中:

注意,计数值初始化为搜索术语hola

public文件夹中的index.html文件的内容替换为以下代码片段。以下代码片段的输出将创建一个文本框,允许我们输入文本,翻译文本,并生成翻译后的输出。

  1. 在您的执行中,将项目 ID mytranslator-c656d替换为您自己的项目 ID:
<html>
  <script src="img/jquery.min.js">  </script>
  <script>

    $(document).ready(
      getMessageStats()
    );
<!-- The following code extracts the most searched term from the database that we create in the next function -->

    function getMessageStats(){
      var xhr = new XMLHttpRequest();
      xhr.open('GET', "https://us-central1-mytranslator-c656d.cloudfunctions.net/getMessageStats", true);
      xhr.send();

      xhr.onreadystatechange = processRequest;

      function processRequest(e) {
        if (xhr.readyState == 4) {
          var response = JSON.parse(xhr.responseText);
          document.getElementById("mostSearched").innerHTML=response;
        }
      }
    }
<!-- the following function translates the input value into english -->
    function translateText()
    {
      var textInput= document.getElementById("input").value;
      var xhr = new XMLHttpRequest();
      xhr.open('GET', "https://us-central1-mytranslator-c656d.cloudfunctions.net/translateMessage?text="+textInput, true);
      xhr.send();

      xhr.onreadystatechange = processRequest;
      function processRequest(e) {

        if (xhr.readyState == 4) {
          var response = JSON.parse(xhr.responseText);
          document.getElementById("output").innerHTML=response;
          getMessageStats();
        }
      }
    }
  </script>
<!-- the following code creates the layout of web application, with input text box and output-->
  <body>
    <label>Enter Input Text</label>
    <input id="input" type="text-area"></input>
    <button onclick="translateText()" id="btnTrans">Translate</button>
    <label id="output"></label>
    <br/>
    <div>
      <h1>Most Searched element</h1>
      <label id="mostSearched"></label>
    </div>
  </body>
</html>

我们部署 Firebase,以便上传指定最终 URL 结构的 HTML 文件:

现在,我们应该能够访问显示的链接,这有助于我们翻译文本,如下所示:

从这个例子中,我们看到我们能够创建一个翻译任何给定输入文本的 Web 应用程序。请注意,Web 应用程序使用了函数创建的 API 端点,而前端代码将根据我们使用的框架而有所不同——当我们使用 Angular 而不是 HTML 时,它可能会有所不同,但服务器端代码将保持不变。

构建移动应用程序

在上一节中,我们了解了将为我们翻译输入的 HTML 页面的前端。在本节中,我们将构建利用我们为函数生成的端点返回翻译文本的 Android 应用程序的前端。

我们创建应用的布局如下:

<?xml version="1.0" encoding="utf-8"?>
 <android.support.constraint.ConstraintLayout 

     android:layout_width="match_parent"
     android:layout_height="match_parent"
     tools:context=".MainActivity">

     <Button
         android:id="@+id/button"
         android:layout_width="wrap_content"
         android:layout_height="wrap_content"
         android:layout_marginStart="148dp"
         android:layout_marginTop="56dp"
         android:text="Translate"
         app:layout_constraintStart_toStartOf="parent"
         app:layout_constraintTop_toBottomOf="@+id/input" />

     <EditText
         android:id="@+id/input"
         android:layout_width="wrap_content"
         android:layout_height="wrap_content"
         android:layout_marginStart="84dp"
         android:layout_marginTop="84dp"
         android:ems="10"
         android:inputType="textPersonName"
         app:layout_constraintStart_toStartOf="parent"
         app:layout_constraintTop_toTopOf="parent" />

     <TextView
         android:id="@+id/out"
         android:layout_width="197dp"
         android:layout_height="80dp"
         android:layout_marginStart="92dp"
         android:layout_marginTop="56dp"
         app:layout_constraintStart_toStartOf="parent"
         app:layout_constraintTop_toBottomOf="@+id/button" />

 </android.support.constraint.ConstraintLayout>

前面的代码会输出应用的布局:

注意,我们有一个用于接收输入的 EditText 视图。

按钮用于执行翻译,而 out 是用于显示输出的 TextView。

此外,请注意,在前面的代码中,我们确保了组件对齐到屏幕。

在主活动(main activity)中,我们执行以下代码:

package com.example.admin.translateapp;

 import android.os.AsyncTask;
 import android.support.v7.app.AppCompatActivity;
 import android.os.Bundle;
 import android.view.View;
 import android.widget.Button;
 import android.widget.EditText;
 import android.widget.TextView;

 import java.io.BufferedReader;
 import java.io.IOException;
 import java.io.InputStreamReader;
 import java.net.HttpURLConnection;
 import java.net.URL;

 import javax.net.ssl.HttpsURLConnection;

 public class MainActivity extends AppCompatActivity {
     public String myurl;
     public String result;
     public String response;
     public EditText inp;
     public TextView out;
     public Button btn;

     @Override
     protected void onCreate(Bundle savedInstanceState) {
         super.onCreate(savedInstanceState);
         setContentView(R.layout.activity_main);
         inp = (EditText) findViewById(R.id.input);
         out = (TextView) findViewById(R.id.out);
         btn = (Button) findViewById(R.id.button);
         myurl = "http://us-central1-mytranslator-c656d.cloudfunctions.net/translateMessage?text=";

         btn.setOnClickListener(new View.OnClickListener() {
             public void onClick(View v) {
                 RequestTask task = new RequestTask();
                 task.execute(inp.getText().toString());
             }
         });
     }

     private class RequestTask extends AsyncTask<String, String, String> {

         @Override
         protected String doInBackground(String... uri) {
             try {
                 URL url = new URL(myurl+uri[0].toString());
                 HttpURLConnection conn = (HttpURLConnection) url.openConnection();
                 conn.setRequestMethod("GET");
                 conn.connect();
                 if (conn.getResponseCode() == HttpURLConnection.HTTP_OK) {
                     InputStreamReader streamReader = new
                             InputStreamReader(conn.getInputStream());
                     //Create a new buffered reader and String Builder
                     BufferedReader reader = new BufferedReader(streamReader);
                     StringBuilder stringBuilder = new StringBuilder();
                     //Check if the line we are reading is not null
                     String inputLine;
                     while((inputLine = reader.readLine()) != null){
                         stringBuilder.append(inputLine);
                     }
                     //Close our InputStream and Buffered reader
                     reader.close();
                     streamReader.close();
                     //Set our result equal to our stringBuilder
                     result = stringBuilder.toString();
                     //result = conn.getResponseMessage();
                 } else {
                 }
             } catch (IOException e) {
                 //TODO Handle problems..
             }
             return result;
         }

         @Override
         protected void onPostExecute(String result1) {
             super.onPostExecute(result1);
             out.setText(result1); }
     }
 }

让我们理解前面的代码。

导入相关包:

import android.os.AsyncTask;
 import android.support.v7.app.AppCompatActivity;
 import android.os.Bundle;
 import android.view.View;
 import android.widget.Button;
 import android.widget.EditText;
 import android.widget.TextView;

 import java.io.BufferedReader;
 import java.io.IOException;
 import java.io.InputStreamReader;
 import java.net.HttpURLConnection;
 import java.net.URL;

初始化我们在MainActivity类中使用的对象:

public String myurl;
 public String result;
 public String response;
 public EditText inp;
 public TextView out;
 public Button btn;

此外,使用以下代码初始化视图:

inp = (EditText) findViewById(R.id.*input*);
 out = (TextView) findViewById(R.id.*out*);
 btn = (Button) findViewById(R.id.*button*);

设置点击监听器:

btn.setOnClickListener(new View.OnClickListener() {
     public void onClick(View v) {
         RequestTask task = new RequestTask();
         task.execute(inp.getText().toString());

指定点击按钮时需要执行的任务:

URL url = new URL(myurl+uri[0].toString());
 HttpURLConnection conn = (HttpURLConnection) url.openConnection();
 conn.setRequestMethod("GET");
 conn.connect();
 if (conn.getResponseCode() == HttpURLConnection.*HTTP_OK*) {
     InputStreamReader streamReader = new
             InputStreamReader(conn.getInputStream());
     //Create a new buffered reader and String Builder
     BufferedReader reader = new BufferedReader(streamReader);
     StringBuilder stringBuilder = new StringBuilder();
     //Check if the line we are reading is not null
     String inputLine;
     while((inputLine = reader.readLine()) != null){
         stringBuilder.append(inputLine);
     }
     //Close our InputStream and Buffered reader
     reader.close();
     streamReader.close();
     //Set our result equal to our stringBuilder
     result = stringBuilder.toString();

从前面的代码中,URL 被评估为我们在之前网络应用部分看到的 URL。

前面代码的输出如下:

注意,点击按钮时,我们应该能够翻译我们的文本。

概述

在本章中,我们了解了 Firebase 的各种功能,并使用firebase函数构建网络和移动应用的底层。我们还使用firebase函数实时更新数据库,并从数据库中检索历史上搜索最多的术语。

第九章:使用 TensorFlow 和 Keras 的神经网络

神经网络是一种受大脑功能启发的不严格监督学习算法。类似于大脑中神经元相互连接的方式,神经网络接收输入并通过一个函数传递它,基于此,某些后续神经元被激活,从而产生输出。

在本章中,我们将关注使用 TensorFlow 和 Keras 的神经网络的实际实现。TensorFlow 提供了一个低级框架来创建神经网络模型。Keras 是一个高级神经网络 API,它显著简化了定义神经网络模型的任务。我们将展示如何在 TensorFlow 上使用 Keras 来定义和训练 GCP 上的模型。我们将以 Python 中的 Keras API 展示,并使用经典 MNIST 数据集上的简单前馈网络进行操作。此外,我们还将介绍神经网络的各个组成部分:

  • 初始化

  • 指标和损失函数

  • 激活函数

  • 网络深度

神经网络概述

神经网络的起源在于每个函数都不能用线性/逻辑回归来近似——数据中可能存在只能由复杂函数近似的潜在复杂形状。

函数越复杂(有某种处理过拟合的方法),预测精度就越好。

以下图像解释了神经网络如何将数据拟合到模型中的方式。

神经网络的典型结构如下:

在此图中,输入层通常由用于预测输出(因变量)层或层的独立变量组成。

隐藏层用于将输入变量转换成高阶函数。隐藏层转换输出的方式如下:

在前面的图中,x[1]x[2]、...、x[n] 是独立变量,而 x[0] 是偏置项(类似于我们在线性/逻辑回归中有一个偏置的方式)。

w[1]w[2]、...、w[n] 是分配给每个输入变量的权重。如果 a 是隐藏层中的一个神经元,它将等于:

在此方程中我们看到的是我们在求和之上应用的激活函数,以便我们获得非线性。我们需要非线性,以便我们的模型能够学习复杂的模式。

此外,拥有多个隐藏层有助于实现高非线性。

下面的章节将提供可以调整的神经网络各种参数的详细信息。

设置 Google Cloud Datalab

为了设置 Google Cloud Datalab,我们点击 Cloud Shell 图标:

在 Cloud Shell 中,设置需要工作的项目,如下所示:

gcloud config set core/project gcp-test-196204

一旦设置了项目,按照以下方式配置区域:

gcloud config set compute/zone us-west1-b

最后,通过指定以下内容创建一个 Datalab 实例:

  • 对于 CPU 版本:
datalab create --no-create-repository mlgcp
  • 对于 GPU 版本:

首先,您需要通过配额页面请求 GPU 版本,如下所示:

图片

提交配额请求,您应该很快就会收到在指定区域使用 GPU 的权限。

注意,在构建神经网络模型时,GPU 版本更好,因为 GPU 中的多个处理器可以并行更新神经网络中的多个权重。

将端口更改为 8081 以打开 Datalab 并因此打开笔记本。

安装和导入所需的包

TensorFlow 作为包,是为了执行神经网络计算而构建的。它使用懒加载评估概念,即在执行代码之前,需要指定神经网络连接的各种元素。

另一个名为 Keras 的 API 使得构建神经网络变得容易得多。在本章中,我们将首先利用 TensorFlow 后端运行的 Keras 包,然后我们将展示如何使用 TensorFlow 中的预制估计器和自定义估计器构建神经网络。

在前面的章节中,我们了解了如何设置 Datalab 笔记本。在本章中,我们将看到如何将所需的包安装并导入到 Datalab 笔记本中。

默认情况下,Datalab 预装了 TensorFlow 包。然而,它默认不包含 Keras。让我们看看如何安装 keras 包:

!pip install keras

一旦安装了 Keras,让我们导入所需的两个包:

import keras as K
import tensorflow as tf

简单神经网络的详细工作原理

为了理解神经网络是如何工作的,我们将构建一个非常简单的网络。输入和预期的输出如下:

import numpy as np
x=np.array([[1,2],[3,4]])
y=np.array([0,1])

注意,x 是包含两个变量的输入数据集,每个行有两个变量。y 是两个输入的预期输出。

实质上,我们已经建立了输入和输出层。

例如,对于前面数据点中的一个,网络的输入和输出值将如下所示:

图片

在传统的机器学习中,您会直接在输入和输出值之间找到关系。然而,神经网络架构的工作原理如下:

"输入值可以表示在一个更丰富(更高)的维度空间中。输入值所表示的维度越多,输入数据集中的复杂性就越大。"

基于前面的直觉,让我们在神经网络中构建一个包含三个单元的隐藏层:

图片

现在层已经构建,让我们按照以下方式在每个单元之间建立连接:

图片

现在已经建立了每个单元之间的连接,每个连接将会有一个与之相关的权重。在下面的图中,我们将初始化每个连接所代表的权重:

注意,权重W代表连接的强度。

现在我们已经构建了一个简单的神经网络。让我们随机初始化输入层和隐藏层之间的权重值,以了解隐藏层值是如何计算的:

隐藏层值是输入值和与之相关的权重的乘积,如下所示:

h1 = 11 + 2(2) = 5

h2 = 10.5 + 2(-1) = -1.5

h3 = 1(-0.2) + 20.1 = 0

现在隐藏值已经计算出来,我们将它们通过一个激活函数。激活函数的直觉如下:

"我们之前呈现的神经网络状态(没有激活函数)是输入变量的一个大线性组合。非线性只能通过对隐藏层值进行激活来获得。"

为了简化,目前我们将假设我们要应用的非线性是 sigmoid 函数。

Sigmoid 函数的工作原理如下:

  • 它接受一个输入值x,并将其转换成新的值1/(1+exp(-x))

Sigmoid 曲线的非线性对于各种x的值看起来是这样的:

因此,隐藏层值,即 5,-1.5 和 0,被转换成0.990.180.5

现在隐藏层值已经计算出来,让我们初始化连接隐藏层到输出层的权重。

注意,权重再次是随机初始化的:

现在权重已经初始化,让我们计算与输出层相关的值:

0.991 + 0.18(-1) + 0.50.2 = 0.91*

输出层的预期值是0.91,而实际值是 0。

因此,在这种情况下相关的损失是(0.91 - 0)² = 0.83

到目前为止的过程,即计算与权重值相对应的损失,被称为前向过程

到目前为止,在本节中,我们已经理解了:

  • 权重

  • 激活函数

  • 损失计算

在先前的场景中,虽然对于我们要尝试解决的给定目标,损失函数保持不变,但权重初始化和激活函数可以针对不同的网络架构而变化。

对于刚才提出的这个问题,目标是通过迭代地改变权重来最小化与网络架构相对应的损失。

例如,在先前的架构中,可以通过将隐藏层到输出层连接的最终权重从0.2更改为0.1来减少损失。一旦权重改变,损失从0.83减少到0.74

通过迭代改变权重以最小化损失值的过程称为反向传播

每个给定数据集中权重变化发生的次数称为epoch。本质上,一个 epoch 由前向传播和反向传播组成。

智能地达到最佳权重值的一种技术称为梯度下降——关于各种权重优化器将在后面的章节中详细介绍。

反向传播

在上一节中,我们看到了反向传播中权重如何更新的直觉。在本节中,我们将看到权重更新过程的详细细节:

在反向传播过程中,我们从神经网络末端的权重开始,向后工作。

在先前的图(1)中,我们通过迭代地以小量(0.01)改变每个连接隐藏层到输出层的权重值:

原始权重 变化后的权重 误差 误差减少量
1 1.01 0.84261 -1.811
-1 -0.99 0.849 -0.32
0.2 0.21 0.837 -0.91

从前面的表中,我们注意到,为了提高误差,应该减少权重值,而不是增加它们:

原始权重 变化后的权重 误差 误差减少量
1 0.99 0.8108 1.792
-1 -1.01 0.8248 0.327
0.2 0.19 0.819 0.9075

现在我们注意到,对于某些权重更新,误差的改进很高,而对于其他一些权重更新,误差的改进较低。

这表明,对于某些误差改进很大的权重,权重更新可以更快;而对于误差改进相对较低的权重,权重更新可以更慢。

对于值为 1 的权重,其变化后的权重可能是:

变化后的权重 = 原始权重 + 学习率 X 误差减少量

现在,让我们假设学习率为0.05;那么:

变化后的权重 = 1 + 0.05(1.792) = 1.089*

其他权重将使用相同的公式进行更改。

直觉上,学习率帮助我们建立对算法的信任。例如,在决定权重更新的幅度时,我们可能会选择不是一次性改变所有内容,而是采取更谨慎的方法,更慢地更新权重。

一旦使用所描述的过程更新了所有权重,反向传播过程就完成了,然后我们再次进行前向传播。

前向传播和反向传播步骤合在一起称为一个 epoch

注意,我们一次计算预测一个数据点的误差值,从而形成一个大小为 1 的批次。在实践中,我们计算一组数据点的误差值,然后使用一个数据批次而不是单个数据点来更新权重。

在 Keras 中实现简单的神经网络

从前面的讨论中,我们已经看到神经网络中的关键组件是:

  • 隐藏层

  • 隐藏层的激活

  • 损失函数

除了这些,神经网络中还有一些其他关键组件。然而,我们将在后面的章节中学习它们。

现在,我们将使用我们到目前为止所获得的知识,在 Keras 中构建一个具有给定玩具数据集的神经网络模型:

导入相关函数:

图片

序列模型是层(输入、隐藏和输出)的线性堆叠。

在每一层中,dense 帮助实现网络中指定的操作。

让我们继续构建网络,如下所示:

图片

在我们的数据中,我们首先将二维的输入数据集转换为三维的隐藏层单元。

一旦计算了隐藏层的值,我们将在第二步通过 sigmoid 激活函数传递它们。

前两个步骤在模型指定的第二行中得到了体现。

从隐藏层,我们将其连接到一个一维的输出层,因此第三行代码有 Dense(1)

让我们看看我们指定的模型摘要:

图片

让我们了解前面总结中的输出形状列:(None, 3)

None 表示输出与输入数量无关(不要与输入维度混淆)。3 表示隐藏层中的单元数量。

同样地,第二层中的 (None,1) 代表输出层的维度(输出层只有一个单元)。

Param # 表示与网络相关的参数数量。

注意,输入层和隐藏层之间的连接总共有九个参数,因为有六个权重值(如前一小节中的图所示),以及与隐藏层中每个单元相关的三个偏置项。

同样地,隐藏层和输出层之间有四个参数,因为隐藏层和输出层之间有三个权重值,还有一个与输出层相关的偏置项。

现在既然已经指定了网络架构,让我们编译模型,如下所示:

图片

在上一行代码中,我们指定损失是基于均方误差计算的,这是实际值与预测值之间平方差的平均值,涉及输入数据集中的所有数据点。

类似地,我们指定优化技术基于随机梯度下降。

现在我们已经指定了模型结构、我们正在计算的损失函数以及我们正在使用的优化技术,让我们将模型拟合到输入和输出值。

在拟合模型时需要指定的附加指标是:

  • 输入和输出值

  • 在模型上运行的 epoch 数量:

图片

注意,我们指定的输入和输出变量是xy

还应该注意到,随着权重值调整以尽可能在 10 个 epoch 内最小化损失,损失值在不同的 epoch 中会降低。

现在我们已经建立了模型,让我们看看如何获得每一层的权重值:

图片

现在可以按照以下方式计算对应于新输入值的值:

图片

在前面的代码片段中,我们初始化了一个新的输入,并使用通过运行模型获得的最佳权重预测了对应于这个新输入的输出。

让我们了解如何获得输出。

获得隐藏层中三个单元的值:

h1 = 2(-0.985) + 5(-0.3587) + 0.00195 = -3.76

h2 = 20.537 + 5(-0.8225) + 0.0011 = -3.025

h3 = 2(-0.24) + 50.98 - 0.0027 = 4.421

一旦计算了隐藏层值,我们就将它们通过模型架构中指定的 sigmoid 激活函数传递。

final h1 = sigmoid(h1) = 0.0226

final h2 = sigmoid(h2) = 0.0462

final h3 = sigmoid(h3) = 0.988

一旦获得了最终的隐藏层单元值,我们将它们与连接隐藏层到输出层的权重相乘,如下所示:

Output = 0.0226 * 0.834 + 0.0462 * 0.6618 + (-0.401) * 0.988 + 0.14615 = -0.20051

注意,我们获得的是与model.predict函数中获得相同的值。这证明了我们迄今为止所学的架构功能。

现在我们已经建立了模型,让我们重新执行我们的代码,看看结果是否保持不变:

图片

注意,损失值与我们之前迭代中获得的不同。这是因为神经网络运行的第一个 epoch 中权重是随机初始化的。一种修复方法是设置一个种子。种子有助于在每次神经网络运行时初始化相同的随机值集合。

注意,每次重建模型时都应该运行种子。设置种子的代码片段如下:

图片

理解各种损失函数

如前一章所述,存在两种类型的因变量——连续变量和分类变量。在连续变量预测的情况下,损失(误差)函数可以通过计算所有预测的平方误差值之和来计算。

在因变量是与其相关联的唯一两个不同值的分类变量的情况下,损失是通过使用此公式的二进制交叉熵误差来计算的:

ylogp + (1-y)log(1-p)

在因变量是具有多个不同值的分类变量的情况下,损失是通过使用分类交叉熵误差来计算的:

∑ ylogp*

其中 p 是事件为 1 的概率。

在实践中,分类变量通常按以下方式进行 one-hot 编码:

假设三个不同行的输出为[1,2,3];则输出值表示为[[1,0,0], [0,1,0], [0,0,1]]。其中每个索引值表示是否存在一个不同的值。在上面的例子中,零索引对应于 1,因此只有第一行在零索引处有值为 1,其余的值为 0。

Keras 中可用的其他损失函数包括:

  • 均方绝对误差

  • 均方绝对百分比误差

  • 均方对数误差

  • 平方铰链

  • 铰链

  • 分类铰链

  • Logcosh

Softmax 激活

从前面的章节中,我们应该注意到,在分类变量预测的情况下,输出层的单元数将与因变量中不同值的数量相同。

此外,请注意,输出层的任何单元的预测值都不能大于 1 或小于 0。同时,输出层中所有节点值的总和应等于 1。

例如,假设两个输出节点的输出值分别为-1 和 5。鉴于输出值的期望值应在 0 到 1 之间(即事件发生的概率),我们将输出值通过 softmax 激活函数传递,如下所示:

  • 通过指数函数传递值:

exp(-1) = 0.367

exp(5) = 148

  • 将输出值归一化以获得介于 0 到 1 之间的概率,并确保两个输出节点之间的概率总和为 1:

0.367/(0.367+148) =0.001

148/(0.367+148) = 0.999

因此,softmax 激活帮助我们将输出值转换为概率数。

在 Keras 中构建更复杂的网络

到目前为止,我们已经构建了一个相当简单的神经网络。传统的神经网络会有更多可变的参数,以实现更好的预测能力。

让我们通过使用经典的 MNIST 数据集来理解它们。MNIST 是一个包含 28 x 28 像素大小的手写数字数据集,这些图像表示为 28 x 28 维度的 NumPy 数组。

每个图像都是一个数字,当前的挑战是预测图像对应的数字。

让我们下载并探索 MNIST 数据集中的一些图像,如下所示:

图片

在前面的代码片段中,我们正在导入 MNIST 对象并使用load_data函数下载 MNIST 数据集。

还要注意,load_data函数有助于自动将 MNIST 数据集分割成训练集和测试集。

让我们可视化训练集中的其中一个图像:

图片

注意,前面的数字是 5,而我们看到的网格大小为 28 x 28。

让我们进一步了解输入和输出的形状,以更好地理解数据集:

图片

由于每个输入图像的大小为 28 x 28,让我们将其展平以获取784个像素值的分数:

图片

输出层需要预测图像是否对应于 0 到 9 之间的数字之一。因此,输出层由 10 个单元组成,分别对应于 10 个不同的数字:

图片

在前面的代码中,to_categorical提供了标签的一热编码版本。

现在我们已经准备好了训练集和测试集,接下来让我们在下一节中构建神经网络的架构:

图片

注意,前面的截图中的batch_size指的是用于更新权重的数据点的数量。批量大小的直觉是:

"如果在包含 1,000 个数据点的数据集中,批量大小为 100,那么在整个数据中遍历时会有 10 次权重更新"。

注意,在预测测试集上的标签时,准确率约为 91%。

当达到 300 个 epoch 时,准确率提高至 94.9%。请注意,对于测试集上的 94.9%准确率,训练集上的准确率约为 99%。

这是一个过度拟合的经典案例,处理方法将在后续章节中讨论。

激活函数

到目前为止,我们只考虑了隐藏层中的 Sigmoid 激活函数。然而,还有许多其他激活函数在构建神经网络时非常有用。此图表提供了各种激活函数的详细信息:

图片

最常用的激活函数是 ReLU、Tanh 和逻辑或 Sigmoid 激活。

让我们探索各种激活函数在测试集上的准确率:

图片

注意,当使用 ReLU 激活时,测试集上的准确率仅为 29.75%。

然而,在进行 ReLU 激活时,在拟合模型之前对数据进行缩放总是一个好主意。缩放是一种减少输入数据集中所有值幅度的方法。

让我们先对输入进行缩放,如下所示:

图片

现在,让我们重新运行模型并查看测试数据集上的准确率:

注意,运行 10 次迭代后,测试数据集的准确率为 88.1%。现在,让我们运行模型 300 个 epoch,以便比较 sigmoid 激活和 ReLU 激活的输出。

你会注意到测试数据集的准确率为 95.76%,略高于 sigmoid 激活的准确率。然而,训练数据集的准确率为 96%,这表明它不太可能在该数据集上过拟合;因此,更多的 epoch 可能会进一步提高测试数据集的准确率。

让我们使用 Tanh 激活函数重新运行模型,首先不进行缩放,然后进行缩放。

当模型在未缩放的数据上运行时,10 个 epoch 后的准确率为 92.89%,300 个 epoch 后为 94.6%。

缩放输入数据集后,测试数据集的准确率在 10 个 epoch 后为 88%,300 个 epoch 后为 93%。

注意,当数据集缩放时,无论使用哪种激活函数,都不会出现过拟合的问题(训练数据集的准确率远高于测试数据集的准确率)。

优化器

在前一节中,我们探讨了各种激活函数,并注意到 ReLU 激活函数在高 epoch 数运行时能给出更好的结果。

在本节中,我们将探讨在激活函数保持为 ReLU 的情况下,改变优化器对缩放数据集的影响。

当运行 10 个 epoch 时,各种损失函数及其在测试数据集上的准确率如下:

优化器 测试数据集准确率
SGD 88%
RMSprop 98.44%
Adam 98.4%

现在我们已经看到 RMSprop 和 Adam 优化器比随机梯度下降优化器表现更好;让我们看看优化器内部可以修改以改进模型准确率的另一个参数——学习率。

优化器的学习率可以通过以下方式指定:

在前面的代码片段中,lr代表学习率。学习率的典型值介于 0.001 和 0.1 之间。

在 MNIST 数据集上,当我们改变学习率时,准确率没有进一步提高;然而,通常对于较低的学习率,需要更多的 epoch 才能达到相同的准确率。

增加网络的深度

增加隐藏层的深度等同于增加神经网络中隐藏层的数量。

通常,对于隐藏层中隐藏单元数量更多和/或隐藏层数量更多的情况,预测的准确率更高。

由于 Adam 优化器或 RMSprop 在经过一定数量的 epoch 后准确率会饱和,让我们切换回随机梯度下降,以了解模型运行 300 个 epoch 时的准确率;但这次我们在隐藏层中使用了更多的单元:

注意,通过在隐藏层中使用 2,000 个单元,我们的准确率在 300 个 epoch 结束时增加到 95.76%。这可能是因为输入现在可以在更高维的空间中表示,因此与 1,000 维空间场景相比,可以学习到更好的表示。

现在,我们将增加隐藏层的数量,以了解构建深度神经网络对准确率的影响:

图片

注意,当网络深度增加,有两个隐藏层而不是一个时,300 个 epoch 后的准确率为 97.24%,与单隐藏层网络相比,这是一个明显的改进。

类似地,当一层中的隐藏单元数量增加时,网络学会更复杂的数据表示,当隐藏层的数量增加时,网络也会学会更复杂的数据表示。

批量大小变化的影响

如前所述,批量大小越小,给定神经网络中权重的更新频率就越高。这导致达到网络一定准确率所需的 epoch 数量更少。同时,如果批量大小过低,网络结构可能会导致模型不稳定。

让我们在一种场景中将之前构建的网络与较小的批量大小进行比较,在下一个场景中与较大的批量大小进行比较:

图片

注意,在前面的场景中,批量大小非常高,300 个 epoch 结束时的测试数据集准确率仅为 89.91%。

这是因为批量大小为 1,024 的网络会比批量大小为 30,000 的网络更快地学习权重,因为当批量大小较小时,权重更新的数量要高得多。

在下一个场景中,我们将批量大小减少到非常小的数值,以观察对网络准确率的影响:

图片

注意,虽然准确率在 10 个 epoch 内迅速提高到 97.77%,但要产生结果需要相当长的时间,因为每个 epoch 的权重更新数量很高。这导致更多的计算,因此执行时间更长。

在 TensorFlow 中实现神经网络

在前面的章节中,我们已经了解了神经网络的工作原理以及如何在 Keras 中构建神经网络模型。在本节中,我们将致力于在 TensorFlow 中构建神经网络模型。在 TensorFlow 中构建模型有两种方式:

  • 使用预制的估计器

  • 定义自定义估计器

使用预制的估计器

预制估计器类似于 scikit-learn 等包中可用的方法,其中指定了输入特征和输出标签,以及各种超参数。然后,可以通过传递不同的函数作为参数来优化解决预定义为默认值的损失函数。

让我们探索在代码中构建训练和测试数据集:

  1. 导入相关包:

  1. 导入数据集。我们将在这个练习中使用 MNIST 数据集:

图像和标签的形状如下:

预制函数在标签值上工作,而不是在独热编码版本上。让我们将独热编码标签转换为值,如下所示:

让我们了解数据点的样子:

  1. 将数据集输入到消耗自变量(x)和因变量(y)的函数中:

注意,我们将自变量命名为 x2,因变量命名为 y

此外,请注意,我们已经传递了形成自变量和因变量值的数组。

batch_size 表示用于计算损失函数的训练示例数量,而 num_epochs = None 表示稍后将会提供要运行的 epoch 数量。

train_input_fn 返回特征和标签,如下所示:

同样,我们传递测试数据集:

注意,在测试数据集的情况下,num_epochs = 1,因为我们只通过前馈传递一次测试数据集,一旦从训练中推导出模型权重。

一个数据集可能包含多个列,因此让我们指定特征列及其类型,如下所示:

如果有多个列,我们将在列表中指定所有列,如下所示:

feature_columns = [feature_x1, feature_x2]

feature_x1 是一个特征,而 feature_x2 是另一个特征。

现在,我们将指定隐藏层的数量以及每层的隐藏单元数:

注意,通过以上方式指定隐藏单元数,我们已经指定了有三个隐藏层,其中第一个隐藏层有 512 个单元,第二个隐藏层有 256 个单元,最后一个隐藏层有 128 个单元。

既然我们已经指定了特征和隐藏层,让我们指定神经网络架构,如下所示:

既然我们已经指定了模型架构,我们可以继续训练模型。如果您想进一步更改函数中可用的超参数,您可以通过使用 help 函数查看可用的超参数调节器,如下所示:

以下代码运行神经网络模型总共 2,000 个 epoch:

图片

现在我们已经运行了模型,让我们评估测试数据集上的准确率,如下所示:

图片

我们可以看到,模型在测试数据集上的准确率为 97.2%。

到目前为止,我们一直在使用预制评估器实现模型;在接下来的章节中,我们将探讨在不使用预制评估器的情况下定义模型。

创建自定义评估器

预制评估器限制了 TensorFlow 可以发挥的全面潜力;例如,我们无法在各个层之后有不同的 dropout 值。在这方面,让我们继续创建一个我们自己的函数,如下所示:

图片

让我们详细探索前面代码片段的每个部分:

图片

该函数接受特征(自变量)和标签(因变量)作为输入。mode表示我们想要训练、预测还是评估给定数据。

params为我们提供了提供有关参数信息的功能;例如,学习率:

图片

前面的代码片段类似于我们在 Keras 中定义模型架构的方式,其中我们指定了输入、隐藏层激活函数和隐藏层中的单元数:

图片

如果我们的模式是预测类别,我们就不需要训练模型,只需传递预测的类别,因此在这种情况下评估器规范只需计算y_pred_cls值,因此以下代码:

图片

如果模式是训练或测试模型,我们就必须计算损失,因此以下代码:

图片

在前面的代码中,第一行用于定义交叉熵计算。第二行计算所有行交叉熵的平均值。

optimizer指定我们感兴趣的优化器和学习率。train_op指定我们感兴趣的是最小化损失,而global_step参数记录模型当前所在的步(epoch)。metrics指定我们感兴趣计算的指标,最终计算的spec将是之前定义的所有参数的组合。

一旦定义了模型架构和需要返回的评估器规范,我们定义参数和模式如下:

图片

从前面的代码中,函数学习需要更改的参数以及需要工作的模型架构(model_fn):

图片

我们通过指定模式(在这种情况下为train)和一定的训练轮数(在这种情况下为2000)来运行模型。

运行模型后,我们评估模型在测试数据集上的准确度,如下所示:

图片

摘要

在本章中,我们学习了如何设置 Datalab 在 Google Cloud 上执行神经网络。我们还学习了神经网络的架构以及各种参数,如深度、隐藏单元数量、激活函数、优化器、批量大小和训练轮数如何影响模型的准确度。我们还看到了如何在 Keras 和 TensorFlow 中实现神经网络。本章还涵盖了使用预制的估计器和在 TensorFlow 中创建自定义估计器等主题。

第十章:使用 TensorBoard 评估结果

在上一章中,我们了解了神经网络的工作原理,神经网络中的各种超参数是什么,以及如何进一步调整它们以提高我们模型的准确性。

Google 提供了 TensorBoard,它是模型训练日志的可视化。在本章中,我们展示了如何使用 TensorBoard 进行 TensorFlow 和 Keras。我们解释 TensorBoard 生成的可视化,以了解我们模型的性能,并了解 TensorBoard 中的其他功能,这些功能可以帮助我们更好地可视化数据集。

如前一章所述,Keras 作为一个框架,是 TensorFlow 或 Theano 之上的包装器。你将使用 TensorFlow 进行的一些计算,例如训练一个大规模的深度神经网络,可能很复杂且令人困惑。为了使其更容易理解、调试和优化 TensorFlow 程序,TensorFlow 的创建者包括了一套名为 TensorBoard 的可视化工具。

你可以使用 TensorBoard 来可视化你的 TensorFlow 图,绘制关于图执行的定量指标,还可以看到输入的图像等额外数据。当 TensorBoard 完全配置后,它看起来像这样:

图片

从这张截图,你可以注意到图表显示了随着 epoch 数量的增加,平均交叉熵误差的减少。在章节的后续部分,我们将介绍以下内容:

  • 安装 TensorBoard

  • TensorBoard 捕获的各种总结操作的概述

  • 调试代码的方法

设置 TensorBoard

在上一章中,我们了解了如何设置 Datalab。在 Datalab 中安装 TensorBoard 与指定以下代码一样简单:

图片

注意,我们不需要为 TensorBoard 进行任何单独的安装,它包含在google.datalab.ml包中预构建的。

一旦导入包,我们需要通过指定包含模型拟合过程写入的总结的日志位置来启动 TensorBoard。

tb.start方法的工作原理如下:

图片

注意,在第一步中,它检查用户是否有权执行计算。接下来,它选择一个未使用的端口来打开 TensorBoard,最后它启动 TensorBoard 并打印打开 TensorBoard 的链接。

我们将在下一节中学习更多关于写入日志的内容。

总结操作的概述

总结提供了一种导出关于模型压缩信息的方法,然后可以在 TensorBoard 等工具中访问。

一些常用的总结函数包括:

  • 标量

  • 直方图

  • 音频

  • 图像

  • 合并

  • merge_all

一个标量总结操作返回一个标量,即随着 epoch 数量的增加,某个度量值的值。

histogram 汇总操作返回各种值的直方图——可能是每一层的权重和偏差。

imageaudio 汇总操作返回图像和音频,可以在 TensorBoard 中分别进行可视化和播放。

merge 操作返回所有输入汇总值的并集,而 merge_all 返回模型规范中包含的所有汇总的并集。

下一个部分将提供一些讨论的汇总的可视化。

调试代码的方法

为了理解 TensorBoard 如何帮助,让我们初始化一个如下所示的模式结构,一个注定不会工作的结构:

图片

注意,在这段代码片段中,验证准确率仅为约 19%。

验证准确率如此低的原因是输入数据集未进行缩放,并且我们在未缩放的数据集上执行了 ReLU 激活。

注意,在前面的代码中,我们将在目录 logs/tensor_new6 中存储模型运行的日志(子目录可以命名为任何名称)。

一旦日志存储在这个位置,我们就可以按照以下步骤启动 TensorBoard:

图片

前面的代码启动了 TensorBoard,其外观如下:

图片

注意,默认情况下,输出给出标量的度量,即训练和测试数据集的准确率和损失值。

可以使用正则表达式 .*Filter 标签中相邻可视化输出,如下所示:

图片

注意,这张截图中的前两个图表表示训练数据集的准确率和损失,而接下来的两个图表表示验证数据集的准确率和损失。

当我们查看各个层的权重和偏差的直方图时,我们了解到权重和偏差在各个时期没有变化:

图片

这表明网络架构中没有发生学习。

当我们在不同的标签页查看权重和偏差在各个时期的分布时,也可以注意到相同的情况:

图片

从这张截图,我们可以得出模型准确率如此低的原因;这是因为模型无法更新权重。

现在,通过点击“GRAPHS”标签,让我们探索模型是否初始化正确:

图片

你应该注意到训练块连接到图中的每个其他块。这是因为,为了计算梯度,需要连接到图中的每个变量(因为每个变量都包含需要调整的权重)。

现在,让我们从图中删除训练块。这可以通过如下方式右键单击训练块来完成:

图片

移除训练块后的结果图如下:

注意,输入层连接到隐藏层,隐藏层又连接到输出层,从输出层计算指标和损失。让我们通过双击各个块来探索这些连接,如下所示:

放大这些连接有助于我们理解各个块中的形状:

输入层在维度上是(784),因为可能有任意数量的输入样本,但每个样本都是 784 维的。同样,内核(权重矩阵)是 784 x 784 维的,偏差将有 784 个初始化值,依此类推。

注意,在上面的图中,我们使用输入层的值并使用random_normal初始化的内核进行矩阵乘法。同时注意,random_normal初始化与训练块没有连接,而内核块与训练块相连。

让我们也找出输出层是否按照预期连接到所有相关块。鉴于图看起来非常复杂,我们可以使用 TensorBoard 提供的另一个功能:跟踪输入。跟踪输入有助于突出显示仅与任何感兴趣的块相连的块。通过在左侧面板中选择感兴趣的块并切换开关来激活,如下所示:

现在所有连接看起来都很好,但梯度仍然没有得到更新;让我们将激活函数更改为 sigmoid,然后检查权重直方图:

我们以下面的方式构建一个具有 sigmoid 激活的神经网络:

一旦定义并编译了神经网络结构,让我们按照以下步骤拟合模型:

为了打开 TensorBoard,我们将执行以下代码:

from google.datalab.ml import TensorBoard as tb
tb.start('./logs/tensor_neww3')

我们将收到以下输出:

同时,我们应该注意到准确率和损失指标有了相当大的改进:

也可以通过在 TensorBoard 函数中指定 write_grads=True 来可视化梯度的直方图。输出将如下所示:

从 TensorFlow 设置 TensorBoard

在上一章中,我们了解到在 TensorFlow 中定义模型有两种方式:

  • 预制评估器

  • 构建自定义评估器

在以下代码中,我们将考虑一个额外的代码片段,它将使我们能够可视化各种摘要操作:

注意,我们只需要在预制的估计器中指定 model_dir 来存储由 TensorFlow 操作生成的各种日志文件。

然后,可以通过引用模型目录来初始化 TensorBoard,如下所示:

图片

上述代码将导致 TensorBoard 可视化,其中将包含所有内置的摘要。

自定义估计器的摘要

在上一节中,我们探讨了在 TensorBoard 中从预制的估计器获取预定义摘要的方法。在本节中,我们将了解如何在自定义估计器中获取摘要,以便它们可以在 TensorBoard 中进行可视化。

需要捕获的摘要操作应在自定义估计器函数中指定,如下所示:

图片

注意,模型函数与我们之前在了解自定义估计器时定义的非常相似;然而,添加了一些将摘要写入日志文件的代码行。

tf.summary.scalar 添加了准确度指标。同样,我们可能还想将损失(另一个标量)添加到日志中;然而,它默认添加(注意,当训练模型时损失会被显示)。

tf.summary.histogram 提供了网络中权重的分布。

一旦模型训练完成,我们应该注意 TensorBoard 输出中的标量和直方图/分布。训练模型并启动 TensorBoard 的代码如下:

图片

在上述代码片段中,我们指定了模型函数和参数以及日志文件将被写入的目录:

model.train(input_fn=train_input_fn, steps=1000)

上述代码片段训练了包含 1,024(批大小)数据点的 1,000 批次的模型:

from google.datalab.ml import TensorBoard as tb
tb.start('/content/datalab/docs/log10/')

此代码片段通过使用给定文件夹中编写的日志文件启动 TensorBoard。

摘要

在本章中,我们了解了在 TensorBoard 中可视化神经网络模型的方法,包括从 Keras 和 TensorFlow 中进行可视化。我们还考虑了如何在预制的估计器和自定义定义的估计器中可视化模型、权重分布以及损失/准确度指标。以及神经网络中的各种指标。

第十一章:通过超参数调整优化模型

神经网络包含多个参数,这些参数可以影响预测事件或标签的最终准确度。典型的参数包括:

  • 训练时使用的批量大小

  • 周期数量

  • 学习率

  • 隐藏层数量

  • 每个隐藏层中的隐藏单元数量

  • 隐藏层中应用的激活函数

  • 使用的优化器

从前面的列表中,我们可以看到可以调整的参数数量非常高。这使得找到最佳的超参数组合具有挑战性。在这种情况下,Cloud ML Engine 提供的超参数调整服务非常有用。

在本章中,我们将介绍:

  • 为什么需要超参数调整

  • 超参数调整工作概述

  • 在云端实现超参数调整

超参数调整的直觉

为了获得超参数调整的实用直觉,让我们通过以下场景来预测给定神经网络架构在 MNIST 数据集上的准确率:

  • 场景 1:大量周期和低学习率

  • 场景 2:少量周期和高的学习率

让我们在 Google Cloud 环境中创建训练集和测试集,如下所示:

  1. 下载数据集:
mkdir data
curl -O https://s3.amazonaws.com/img-datasets/mnist.pkl.gz
gzip -d mnist.pkl.gz
mv mnist.pkl data/          

前面的代码创建了一个名为 data 的新文件夹,下载了 MNIST 数据集,并将其移动到 data 文件夹中。

  1. 在终端中打开 Python 并导入所需的包:
from __future__ import print_function 
import tensorflow as tf
import pickle # for handling the new data source
import numpy as np
from datetime import datetime # for filename conventions
from tensorflow.python.lib.io import file_io # for better file I/O
import sys
  1. 导入 MNIST 数据集:
f = file_io.FileIO('data/mnist.pkl', mode='r')
data = pickle.load(f)
  1. 提取训练集和测试集:
(x_train, y_train), (x_test, y_test) = data
# Converting the data from a 28 x 28 shape to 784 columns
x_train = x_train.reshape(60000, 784)
x_train = x_train.astype('float32')
# Scaling the train dataset
x_train /= 255
# Reshaping the test dataset
x_test = x_test.reshape(10000, 784)
x_test = x_test.astype('float32')
# Scaling the test dataset
x_test /= 255
# Specifying the type of labels
y_train = y_train.astype(np.int32)
y_test = y_test.astype(np.int32)
  1. 创建估算函数:
# Creating the estimator input functions for train and test datasets 
train_input_fn = tf.estimator.inputs.numpy_input_fn(
 x={"x2": np.array(x_train)},
 y=np.array(y_train),
 num_epochs=None,
 batch_size=1024,
 shuffle=True)
test_input_fn = tf.estimator.inputs.numpy_input_fn(
 x={"x2": np.array(x_test)},
 y=np.array(y_test),
 num_epochs=1,
 shuffle=False)
  1. 指定列的类型:
feature_x = tf.feature_column.numeric_column("x2", shape=(784))
feature_columns = [feature_x]
  1. 使用场景 1 中的参数构建 DNN 分类器;即学习率为 0.1,步数为 200
num_hidden_units = [1000]
lr=0.1
num_steps=200
# Building the estimator using DNN classifier
# This is where the learning rate hyper parameter is passed
model = tf.estimator.DNNClassifier(feature_columns=feature_columns,
                 hidden_units=num_hidden_units,
                 activation_fn=tf.nn.relu,
                 n_classes=10,
                 optimizer=tf.train.AdagradOptimizer(learning_rate = lr))
model.train(input_fn=train_input_fn, steps=num_steps) 
# Fetching the model results
result = model.evaluate(input_fn=test_input_fn)
print('Test loss:', result['average_loss'])
print('Test accuracy:', result['accuracy'])

在这种情况下,测试准确率达到 96.49%。

在场景 2 中,我们将使用不同的参数构建另一个 DNN 分类器;现在学习率为 0.01,步数为 2000

num_hidden_units = [1000]
lr=0.01
num_steps=2000
# Building the estimator using DNN classifier
# This is where the learning rate hyper parameter is passed
model = tf.estimator.DNNClassifier(feature_columns=feature_columns,
 hidden_units=num_hidden_units,
 activation_fn=tf.nn.relu,
 n_classes=10,
 optimizer=tf.train.AdagradOptimizer(learning_rate = lr))
model.train(input_fn=train_input_fn, steps=num_steps) 
# Fetching the model results
result = model.evaluate(input_fn=test_input_fn)
print('Test loss:', result['average_loss'])
print('Test accuracy:', result['accuracy']) 

场景 2 中测试集上的准确率接近 98.2%。

前面的两个场景表明了不同超参数值如何影响最终结果的重要性。

在这种情况下,Google Cloud ML 引擎非常有用,我们可以更智能地选择更优的超参数集。

超参数调整概述

超参数调整通过在一个训练作业中运行多个试验来实现。每个试验是您训练应用程序的完整执行,其中选择的超参数值在您指定的范围内设置。Cloud ML Engine 训练服务会跟踪每个试验的结果,并为后续试验进行调整。作业完成后,您可以获取所有试验的摘要,以及根据您指定的标准确定的最有效配置值。

我们希望选择那些能给出最佳性能的超参数。这相当于一个优化问题,具体来说,是优化一个函数 f(x)(即性能作为超参数值的函数)在紧致集 A 上的问题。我们可以用数学公式表示为:

图片

让我们以函数 (1-x)^(ex) 为例,它在 x = 0 处达到最大值 f(x) = 1,因此 arg max0

许多优化设置,如这个例子,假设目标函数 f(x) 有已知的数学形式,是凸的,或者容易评估。但这些特征不适用于寻找超参数的问题,其中函数是未知的且难以评估。这就是贝叶斯优化发挥作用的地方。

为了实现超参数调整,Google 使用了一种称为高斯过程探险家的算法,这是一种贝叶斯优化的形式。

当函数的数学形式未知或计算成本高昂时,贝叶斯优化是一种极其强大的技术。其背后的主要思想是基于数据(使用著名的贝叶斯定理)计算目标函数的后验分布,然后根据这个分布选择好的点进行尝试。

要使用贝叶斯优化,我们需要一种灵活地建模目标函数分布的方法。这比建模实数分布要复杂一些,因为我们需要一个这样的分布来表示我们对每个 xf(x) 的信念。如果 x 包含连续超参数,那么将会有无限多个 x 需要建模 f(x),即为其构建一个分布。对于这个问题,高斯过程是一种特别优雅的技术。实际上,它们推广了多维高斯分布,并且确实存在足够灵活以建模任何目标函数的版本。

上述过程通常如以下图所示:

图片

我们通过迭代的结果更新高斯模型,这有助于进一步确定要测试的正确下一组超参数集;结果进一步提高了我们的高斯模型,以识别要选择正确的超参数集。

高斯分布的细节超出了本书的范围,但为了这个练习,我们将采用 Google 的方法(作为一个黑盒)并使用 Google Cloud 实现超参数调整。

Google Cloud 中的超参数调整

为了让刚刚设置的高斯过程运行,我们必须允许我们的模型构建在 Google Cloud 上运行,以便进行超参数调整。

为了运行超参数调整,以下组件是必不可少的:

  • 数据文件及其位置

  • 模型文件

  • 超参数配置文件

  • 设置文件

  • __init__ 文件

由于我们正在 Google Cloud ML 引擎上运行模型,数据应位于云存储桶中,以便 ML 引擎可以访问。

这可以通过在云壳中执行以下操作来完成:

gsutil mb gs://my-mnist-bucket
gsutil cp -r data/mnist.pkl gs://my-mnist-bucket/data/mnist.pkl

注意,使用前面的步骤,我们已经创建了一个名为my-mnist-bucket的存储桶,并将我们的数据复制到该存储桶中。前面的代码应该会在该目录中创建一个名为data的目录和该目录中的mnist.pkl文件:

图片

模型文件

模型文件应位于包含__init__.py文件的文件夹中。

让我们创建一个名为trainer的文件夹,其中包含模型文件和__init__文件:

mkdir trainer
cd trainer

之前的代码创建了trainer文件夹并将目录更改为新创建的文件夹。

让我们继续创建模型文件,如下所示:

vim mnist_mlp_lr_numsteps.py

将以下代码插入到之前创建的文件中:

from __future__ import print_function

import argparse
import pickle 
from datetime import datetime 
import numpy as np
from tensorflow.python.lib.io import file_io # for better file I/O
import sys
import tensorflow as tf

def train_model(train_file='data/mnist.pkl',job_dir='./tmp/mnist_mlp', num_steps = 1, lr=0.1, **args):
  # logs_path gives access to the logs that are generated by the previous epochs of model
  logs_path = job_dir + '/logs/' + str(datetime.now().isoformat())
  print('Using logs_path located at {}'.format(logs_path))
  # by default floats are considered as string
  # Good idea to convert them back into floats
  lr=float(lr)
  num_steps=float(num_steps)
  batch_size = 1024
  num_classes = 10
  # Reading in the pickle file. Pickle works differently with Python 2 vs 3
  # In Python 2 the following code would be:
  # f = file_io.FileIO(train_file, mode='r')
  # data = pickle.load(f)
  f = file_io.FileIO(train_file, mode='rb') 
  data = pickle.load(f,encoding='bytes') 
  (x_train, y_train), (x_test, y_test) = data
  # Converting the data from a 28X28 shape to 784 columns
  x_train = x_train.reshape(60000, 784)
  x_train = x_train.astype('float32')
  x_test = x_test.reshape(10000, 784)
  x_test = x_test.astype('float32')
  x_train /= 255
  x_test /= 255
  # Specifying the type of following labels
  y_train = y_train.astype(np.int32)
  y_test = y_test.astype(np.int32)

  # Creating the estimator following input functions 
  train_input_fn = tf.estimator.inputs.numpy_input_fn(
    x={"x2": np.array(x_train)},
    y=np.array(y_train),
    num_epochs=None,
    batch_size=batch_size,
    shuffle=True)
  test_input_fn = tf.estimator.inputs.numpy_input_fn(
    x={"x2": np.array(x_test)},
    y=np.array(y_test),
    num_epochs=1,
    shuffle=False)
  # Specifying the columns as numeric columns
  feature_x = tf.feature_column.numeric_column("x2", shape=(784))
  feature_columns = [feature_x]
  num_hidden_units = [1000]
  # Building the estimator using DNN classifier
  # This is where the learning rate hyper parameter is passed
  model = tf.estimator.DNNClassifier(feature_columns=feature_columns,
                                   hidden_units=num_hidden_units,
                                   activation_fn=tf.nn.relu,
                                   n_classes=num_classes,
                   optimizer=tf.train.AdagradOptimizer(learning_rate = lr))
  # Passing the other parameter: num_steps
  model.train(input_fn=train_input_fn, steps=num_steps) 
  # Fetching the model results
  result = model.evaluate(input_fn=test_input_fn)
  print('Test loss:', result['average_loss'])
  print('Test accuracy:', result['accuracy'])

if __name__ == '__main__':
  # Parse the input arguments for common Cloud ML Engine options
  # There are 4 arguments that we need to give, as per the preceding model specification 
  # training file location, job directory, number of steps and learning rate
  parser = argparse.ArgumentParser()
  parser.add_argument(
    '--train-file',
    help='Cloud Storage bucket or local path to training data')
  parser.add_argument(
    '--job-dir',
    help='Cloud storage bucket to export the model and store temp files')
  parser.add_argument(
    '--num-steps',
    help='number of steps')
  parser.add_argument(
    '--lr',
    help='learning rate') 

  args = parser.parse_args()
  arguments = args.__dict__
  train_model(**arguments)

配置文件

一旦模型文件设置好,我们需要在同一个trainer文件夹中提供配置文件,以便 ML 引擎知道需要调整的参数以及参数的典型最小和最大值。

我们在trainer文件夹中创建配置文件如下:

vim hptune.yaml

以下代码被插入到前面的文件中:

trainingInput:
  pythonVersion: "3.5"
  scaleTier: CUSTOM
  masterType: standard_gpu
  hyperparameters:
    goal: MAXIMIZE
    hyperparameterMetricTag: accuracy
    maxTrials: 10
    maxParallelTrials: 1
    params:
      - parameterName: num-steps
        type: INTEGER
        minValue: 200
        maxValue: 10000
        scaleType: UNIT_LINEAR_SCALE
      - parameterName: lr
        type: DOUBLE
        minValue: 0.001
        maxValue: 0.1
        scaleType: UNIT_LOG_SCALE

在前面的代码块中,我们指定了要运行的 Python 版本,并指定了它是在 CPU 上运行还是在 GPU 上运行。

hyperparameters部分,我们指定了需要优化的指标是准确率(请注意,model.evaluate的输出是accuracylossaverage lossglobal step);目标是最大化它。

此外,我们还指定了要运行的试验的最大次数以及可以并行运行的试验的最大次数(当云配置与多个核心相关联时,此值会发生变化)。

params部分包含需要修改的参数、变量的类型以及最小和最大值。

ScaleType指示将对参数应用哪种缩放类型:

Value 描述
UNIT_LINEAR_SCALE 线性地将可行空间缩放到(0,1)。
UNIT_LOG_SCALE 对数地将可行空间缩放到(0,1)。整个可行空间必须是严格正的。
UNIT_REVERSE_LOG_SCALE 将可行空间反向对数缩放到(0,1)。结果是,接近可行空间顶部的值比接近底部的点分散得更多。整个可行空间必须是严格正的。

设置文件

在某些情况下,我们可能需要安装未预构建的包。在这种情况下,setup.py文件很有用:

from setuptools import setup, find_packages
setup(name='mnist_mlp_lr_numsteps',
      version='1.0',
      packages=find_packages(),
      include_package_data=True,
      install_requires=[
          'keras',
          'h5py'],
      zip_safe=False)

在前面的代码中,可以包含运行模型文件所需的额外包。

init 文件

为了让 Cloud ML 引擎为我们正在构建的模块创建一个包,它需要在trainer文件夹中创建一个__init__.py文件。

为了做到这一点,我们将运行以下代码:

touch trainer/__init__.py

现在一切设置就绪,我们按照以下方式运行作业:

export BUCKET_NAME=my-mnist-bucket
export JOB_NAME="mnist_mlp_hpt_train_$(date +%Y%m%d_%H%M%S)"
export JOB_DIR=gs://$BUCKET_NAME/$JOB_NAME
export REGION=us-east1
export HPTUNING_CONFIG=hptune.yaml
gcloud ml-engine jobs submit training $JOB_NAME \
 --job-dir $JOB_DIR \
 --runtime-version 1.6 \
 --config $HPTUNING_CONFIG \
 --module-name trainer.mnist_mlp_lr_numsteps \
 --package-path ./trainer \
 --region $REGION \
 -- \
 --train-file gs://$BUCKET_NAME/data/mnist.pkl \
 --num-steps 100 \
 --lr 0.01

注意,我们指定了数据存在的数据桶名称,以及需要存储日志的作业名称和目录。需要设置区域,并指定配置文件。

此外,使用你的包的命名空间点表示法将--module-name参数设置为应用程序主模块的名称。

注意,在指定区域后,我们有一个空格,表示现在开始是参数(它们是训练文件位置、步数和学习率)。

在前面的代码中指定的步数和学习率是默认版本,一旦传递给 ML 引擎作业,就会进行更改。

代码的输出可以在我们运行的作业的训练输出中可视化,如下所示:

图片

最优的超参数可以从前面的输出中选择。我们可以看到,学习率设置为0.0149,步数设置为7658,比我们之前测试的两个场景在测试数据集上的准确率更高。

摘要

在本章中,我们了解了不同的参数组合如何影响最终的准确度度量,以及如何使用 Cloud ML 引擎进行超参数调整来进一步提高准确度。

在下一章中,我们将学习如何通过设置正确的参数和定义适当的架构来识别过拟合,并使我们的模型对之前未见过的数据更加鲁棒。

第十二章:使用正则化防止过拟合

到目前为止,在前面的章节中,我们了解了构建神经网络、评估 TensorBoard 结果以及调整神经网络模型的超参数以提高模型准确率。

虽然超参数通常有助于提高模型的准确率,但某些超参数的配置会导致模型过度拟合训练数据,而无法泛化测试数据,这就是过拟合训练数据的问题。

一个关键参数可以帮助我们在泛化未见数据集的同时避免过拟合,那就是正则化技术。以下是一些关键的正则化技术:

  • L2 正则化

  • L1 正则化

  • Dropout

  • 缩放

  • 批标准化

  • 权重初始化

在本章中,我们将讨论以下内容:

  • 过度/欠拟合的直觉

  • 使用正则化减少过拟合

  • 改善欠拟合场景

过度/欠拟合的直觉

在我们了解前面技术如何有用之前,让我们构建一个场景,以便我们了解过拟合的现象。

场景 1:在未见数据集上未泛化的案例

在这个场景中,我们将创建一个数据集,其中输入和输出之间存在清晰的线性可分映射。例如,每当独立变量为正时,输出为 [1,0],而当输入变量为负时,输出为 [0,1]

图片

为了那个数据集,我们将通过添加一些遵循先前模式相反的数据点来添加少量噪声(前一个数据集的 10%),即当输入变量为正时,输出为 [0,1],而当输入变量为负时,输出为 [1,0]

图片

将前两个步骤获得的数据集附加在一起,就得到了训练数据集,如下所示:

图片

在下一步中,我们将创建测试数据集,其中它遵循大多数训练数据集的准则,即当输入为正时,输出为 [1,0]

图片

现在我们已经创建了数据集,让我们继续构建一个模型来预测给定的输入的输出。

这里的直觉是,如果训练准确率提高了超过 90.91%,那么它就是一个典型的过拟合案例,因为模型试图拟合那些不适用于未见数据集的少数观察结果。

为了检查这一点——让我们首先导入所有相关的包来在 keras 中构建一个模型:

我们构建了一个具有三个层的模型,其中每个隐藏层有 1,000、500 和 100 个单元:

图片图片

下面是训练和测试数据集上损失和准确性的 TensorBoard 可视化:

图片

从前两个图中,我们可以看到,随着训练数据集上的损失减少,其准确性提高。

此外,请注意,训练损失并没有平滑地减少——这可能会向我们表明它正在过度拟合训练数据。

你应该观察到,随着训练数据集准确性的提高,验证准确性(测试准确性)开始下降——再次向我们表明该模型对未见数据集的泛化能力不佳。

这种现象通常发生在模型过于复杂,试图拟合最后几个错误分类以减少训练损失时。

减少过拟合

通常,过拟合会导致一些权重相对于其他权重非常高。为了理解这一点,让我们看看在 场景 1 中通过运行模型在人工创建的数据集上获得的权重直方图:

图片

我们可以看到,有一些权重值很高(> 0.1),而大多数权重则集中在零附近。

现在我们来探索通过 L1 和 L2 正则化对具有高权重值进行惩罚的影响。

正则化的直觉如下:

  • 如果将权重值缩小到尽可能小,那么这些权重中的一些对微调我们的模型以适应少数异常情况贡献更大的可能性就较小。

实现 L2 正则化

既然我们已经看到了我们的数据集上过拟合的发生,我们将探讨 L2 正则化在减少数据集上过拟合的影响。

数据集上的 L2 正则化可以定义为如下:

图片

注意,损失函数是传统的损失函数,其中 y 是因变量,x 是自变量,W 是核(权重矩阵)。

正则化项被添加到损失函数中。注意正则化值是权重矩阵所有维度的权重值的平方和。鉴于我们是在最小化权重值的平方和以及损失函数,成本函数确保没有权重值过大——从而确保过拟合现象减少。

Lambda 参数是一个超参数,用于调整我们对正则化项赋予的权重。

让我们探索在 场景 1 中定义的模型中添加 L2 正则化的影响:

图片

注意,我们修改了在 场景 1 中看到的代码,通过添加 kernel_regularizer,在这种情况下,是具有 0.01 Lambda 值的 L2 正则化器。

注意 TensorBoard 的输出,正如我们训练前面的模型:

图片

训练损失持续下降,验证准确率保持稳定,而训练准确率为 90.9%,没有考虑到过拟合的情况。

让我们探索权重的分布,以了解在执行 L2 正则化和没有正则化时权重分布的差异:

图片

你应该注意到,与没有正则化的情况相比,在 L2 正则化的场景下,核(主要是dense_2dense_3层的核)在零点有一个更尖锐的峰值。

为了进一步了解峰值分布,我们将修改 lambda 值,并将正则化的权重提高至 0.1 而不是 0.001,看看权重看起来如何:

图片

注意,由于给正则化项更高的权重,权重在中心(值为 0)周围的分布要尖锐得多。

还应该注意到,核是dense_4,并且变化不大,因为我们没有在这个层应用正则化。

从前面的点我们可以得出结论,通过实现 L2 正则化,我们可以减少在没有正则化时看到的过拟合问题。

实现 L1 正则化

L1 正则化与 L2 正则化类似;然而,L1 正则化的成本函数与 L2 正则化不同,如下所示:

图片

注意,在上述方程中,所有项都保持不变;只是正则化项是权重绝对值的总和,而不是权重平方值的总和。

让我们在代码中实现 L1 正则化;现在我们看到相应的输出如下:

图片

注意,由于 L1 正则化项不涉及平方,我们可能需要在 L1 中降低 lambda 值,与 L2 相比(考虑到大多数权重小于一,平方它们会使权重值更小)。

在定义模型(这次带有正则化)后,我们拟合它,如下所示:

图片

上述代码拟合结果在训练和测试数据集上的准确率,正如我们所预期,如下所示:

图片

让我们也看看在直方图标签页中各层的权重分布:

图片

我们应该注意,这里的核分布与 L2 正则化 lambda 值高时的核分布相似。

实现 dropout

另一种减少过拟合的方法是实现 dropout 技术。在执行典型反向传播中的权重更新时,我们确保在给定的一轮中,一些随机部分的权重被排除在权重更新之外——因此得名 dropout。

Dropout 作为一种技术,也可以帮助减少过拟合,因为减少单个 epoch 中需要更新的权重数量,从而减少了输出依赖于少数输入值的机会。

Dropout 可以这样实现:

模型拟合的结果如下:

应该注意,与没有正则化的场景相比,给定配置中的 dropout 导致权重分布略宽:

减少欠拟合

当以下情况发生时,通常会出现欠拟合:

  • 模型极其复杂,并且运行了较少的 epoch

  • 数据未进行归一化

场景 2:MNIST 数据集上的欠拟合实际操作

在以下场景中,我们看到 MNIST 数据集上欠拟合的实际案例:

注意,在前面的代码中,我们没有缩放我们的数据——训练和测试数据集的列值范围在 0 到 255 之间:

前面模型的训练和测试数据集上的 TensorBoard 准确率和损失可视化如下:

注意,在前面的图表中,训练数据集的损失和准确率几乎没有变化(注意两个图表的y轴值)。

这种情况(损失几乎没有变化)通常发生在输入有非常高的数字(通常>5)时。

前面的情况可以通过执行以下任何一项来纠正:

  • 数据缩放

  • 批标准化

数据缩放就像重复前面的架构一样简单,但需要稍作修改,即缩放训练和测试数据集:

批标准化可以在以下方式下执行(甚至可以在未缩放的 MNIST 数据集上):

训练和测试准确率的可视化如下:

在前面的场景中,我们看到即使在未缩放的数据集上,测试准确率也相当高。

场景 3:错误的权重初始化

就像前面的场景一样,如果权重没有正确初始化(即使数据集是正确缩放的),我们很可能遇到欠拟合的场景。例如,在以下代码中,我们将所有权重(核)初始化为零,然后注意到测试数据集上的准确率:

前述代码的输出结果导致以下 TensorBoard 可视化:

图片

场景 2类似,前述图表表明,通过先前定义的架构没有发生学习。

由于权重被初始化为零,没有发生学习。

建议将权重初始化为正态初始化。其他可以尝试的初始化方法,以测试是否可以提高准确率的有:

  • glorot_normal

  • lecun_uniform

  • glorot_uniform

  • he_normal

摘要

在本章中,我们看到了过拟合的特征以及如何通过 L1 和 L2 正则化以及 dropout 来处理它们。同样,我们也看到了存在大量欠拟合的场景,以及如何通过缩放或批量归一化来帮助我们改善欠拟合的情况。

第十三章:超越前馈网络——CNN 和 RNN

人工神经网络ANNs)现在在各种技术中极为广泛地被用作工具。在最简单的应用中,ANNs 为神经元之间的连接提供了一种前馈架构。前馈神经网络是设计出的第一种也是最简单的人工神经网络。在存在与某些问题相互作用的假设的情况下,前馈网络的内向结构具有强烈的限制性。然而,可以从它开始,创建一个计算一个单元的结果会影响另一个单元计算过程的网络。显然,管理这些网络动态的算法必须满足新的收敛标准。

在本章中,我们将介绍主要的人工神经网络架构,例如卷积神经网络(CNN)、循环神经网络(RNN)和长短期记忆LSTM)。我们将解释每种类型 NN 背后的概念,并告诉您它们应该应用于哪些问题。每种类型的 NN 都使用 TensorFlow 在真实数据集上实现。

涵盖的主题包括:

  • 卷积网络及其应用

  • 循环网络

  • LSTM 架构

在本章结束时,我们将了解训练、测试和评估卷积神经网络CNN)。我们将学习如何在 Google Cloud Platform 上训练和测试 CNN 模型。我们将涵盖 CNN 和 RNN 架构的概念。我们还将能够训练一个 LSTM 模型。读者将学习将哪种类型的神经网络应用于不同的问题,以及如何在 Google Cloud Platform 上定义和实现它们。

卷积神经网络

ANN 是从生物神经网络(人脑)中受到启发的模型系列,它从调节自然神经网络的机制开始,计划模拟人类思维。它们用于估计或近似可能依赖于大量输入的函数,其中许多通常是未知的。ANN 通常表示为相互连接的神经元系统,其中发生消息交换。每个连接都有一个相关的权重;权重的值可以根据经验进行调整,这使得神经网络成为适应各种类型输入并具有学习能力的工具。

ANNs 将神经元定义为中央处理单元,它通过对一组输入执行数学运算来生成一个输出。神经元的输出是输入加权总和加上偏差的函数。每个神经元执行一个非常简单的操作,如果接收到的总信号量超过激活阈值,则涉及激活。在以下图中,显示了一个简单的 ANN 架构:

图片

实质上,卷积神经网络(CNN)是人工神经网络。事实上,就像后者一样,CNN 由通过加权分支(权重)相互连接的神经元组成;网络的训练参数再次是权重和偏差。

在 CNN 中,神经元之间的连接模式受到动物世界中视觉皮层结构的启发。大脑的这一部分(视觉皮层)中的单个神经元对观察到的狭窄区域内的某些刺激做出反应,该区域称为感受野。不同神经元的感受野部分重叠,以覆盖整个视野。单个神经元对其感受野内发生的刺激的反应可以通过卷积运算进行数学近似。

与神经网络训练相关的所有内容,即前向/反向传播和权重的更新,也适用于此上下文;此外,整个 CNN 总是使用一个可微分的成本函数。然而,CNN 假设它们的输入具有精确的数据结构,例如图像,这使得它们可以在其架构中采用特定的属性以更好地处理此类数据。

使用 FC 架构分层的一般神经网络——其中每一层的每个神经元都与前一层的所有神经元(除偏置神经元外)相连——在输入数据规模增加时通常无法很好地扩展。

让我们举一个实际例子:假设我们想要分析一张图像以检测对象。首先,让我们看看图像是如何处理的。正如我们所知,在图像编码中,它被分成一个小方格的网格,每个小方格代表一个像素。在这个阶段,为了编码彩色图像,我们只需要为每个方格识别一定数量的色调和不同的颜色渐变。然后我们通过适当的位序列对每个方格进行编码。以下是一个简单的图像编码示例:

图片

网格中的方块数量定义了图像的分辨率。例如,宽度为 1,600 像素、高度为 800 像素(1,600 x 800)的图像包含(相乘)1,280,000 个像素,或 1.2 兆像素。因此,我们必须乘以三个颜色通道,最终得到 1,600 x 800 x 3 = 3,840,000。这意味着第一个隐藏层中完全连接的每个神经元都会有 3,840,000 个权重。这只是一个神经元的例子;考虑到整个网络,事情肯定会变得难以管理!

CNN 被设计成直接在由像素表示的图像中识别视觉模式,并且需要零或非常有限的预处理。它们能够识别极其变化的模式,例如自由手写和代表现实世界的图像。

通常,CNN 由几个交替的卷积和子采样级别(池化)组成,在分类的情况下,后面跟着一个或多个 FC 最终级别。以下图显示了经典的图像处理流程:

图片

为了解决现实世界中的问题,这些步骤可以按需组合和堆叠。例如,你可以有两层、三层,甚至更多层的卷积。你可以输入所有你想要的池化来减少数据的大小。

如前所述,在 CNN 中通常使用不同类型的层。在以下章节中,将介绍主要的一些。

卷积层

这是主要类型的层;在 CNN 中使用一个或多个这些层是必不可少的。实际上,卷积层的参数与一组可操作的滤波器相关。每个滤波器在宽度和高度维度上是空间上小的,但它扩展到它所应用的输入体积的整个深度。

与普通神经网络不同,卷积层中的神经元以三维组织:宽度高度深度。它们在以下图中展示:

图片

在前向传播过程中,每个滤波器沿着输入体积的宽度和高度进行平移——或者更准确地说,进行卷积——产生该滤波器的二维激活图(或特征图)。当滤波器在输入区域移动时,滤波器的值与它所应用的输入部分的值之间执行标量积操作。

直观地讲,网络的目标是学习在输入的特定空间区域出现某种特定类型的特征时被激活的滤波器。所有这些特征图(对于所有滤波器)沿着深度维度的排队形成了卷积层的输出体积。这个体积的每个元素都可以解释为观察输入的小区域并与其他特征图中的神经元共享其参数的神经元的输出。这是因为这些值都来自应用相同的滤波器。

总结一下,让我们关注以下要点:

  • 局部感受野:层的每个神经元都与输入的一个小区域(称为局部感受野)完全连接;每个连接学习一个权重。

  • 共享权重:由于有趣的特征(边缘、块等)可以在图像的任何地方找到,同一层的神经元共享权重。这意味着同一层的所有神经元都将识别相同的位置在不同输入点处的特征。

  • 卷积:相同的权重图应用于不同的位置。卷积输出称为特征图

每个滤波器捕捉前一层中存在的特征。因此,为了提取不同的特征,我们需要训练多个卷积滤波器。每个滤波器返回一个突出不同特性的特征图。

矩形线性单元

ReLU(修正线性单元)在神经网络中扮演神经元激活函数的角色。ReLU 层由应用函数 f(x) = max (0, x) 的神经元组成。这些层增加了网络的非线性,同时不修改卷积层的接收域。与双曲正切或 Sigmoid 等其他函数相比,ReLU 函数的功能更受青睐,因为它在比较中导致训练过程更快,同时不会显著影响泛化精度。

池化层

这些层定期插入到网络中,以减少当前表示的空间尺寸(宽度和高度)以及特定网络阶段中的体积;这有助于减少网络参数数量和计算时间。它还监控过拟合。池化层独立地对输入体积的每个深度切片进行操作,以在空间上调整其大小。

例如,这种技术将输入图像分割成一系列正方形,并对每个生成的区域,返回最大值作为输出。

CNNs 还使用位于卷积层之后的池化层。池化层将输入划分为区域,并选择单个代表性值(最大池化和平均池化)。使用池化层:

  • 减少了后续层的计算量

  • 增强了特征对空间位置的抗干扰性

它基于这样的概念,一旦某个特征被识别,其在输入中的精确位置并不像其相对于其他特征的近似位置那样重要。在典型的 CNN 架构中,卷积层和池化层反复交替。

完全连接层

这种类型的层与具有完全连接FC)架构的任何经典 ANN 层完全相同。简单地说,在 FC 层中,每个神经元都连接到前一层中的所有神经元,特别是它们的激活。

与 CNN 中迄今为止所看到的不同,这种类型的层不使用局部连接性属性。FC 层连接到整个输入体积,因此,正如你可以想象的那样,会有很多连接。这种类型层的唯一可设置参数是构成它的 K 个神经元的数量。基本上定义 FC 层的是以下内容:将其 K 个神经元与所有输入体积连接,并计算其 K 个神经元中的每个神经元的激活。

实际上,其输出将是一个 1 x 1 x K 的单个向量,包含计算出的激活值。使用单个全连接层后,从三维组织输入体积(在单维中组织单个输出向量)切换,这表明在应用全连接层之后,不能再使用卷积层。在 CNN 的上下文中,全连接层的主要功能是对到目前为止获得的信息进行某种分组,用一个单一的数字(其神经元之一的活动)来表示,这将在后续的计算中用于最终的分类。

CNN 的结构

在详细分析了 CNN 的每个组件之后,现在是时候看看 CNN 的整体结构了。例如,从图像作为输入层开始,将会有一系列的卷积层,其中穿插着 ReLU 层,并在必要时使用标准化和池化层。最后,在输出层之前,将有一系列的全连接层。以下是一个 CNN 架构的示例:

图片

基本思想是从一个大的图像开始,逐步减少数据,直到得到一个单一的结果。卷积通道越多,神经网络就越能够理解和处理复杂函数。

TensorFlow 概述

TensorFlow 是由谷歌提供的开源数值计算库,用于机器智能。它隐藏了构建深度学习模型所需的全部编程,并为开发者提供了一个黑盒接口来编程。

在 TensorFlow 中,图中的节点代表数学运算,而图边代表它们之间传递的多维数据数组(张量)。TensorFlow 最初是由谷歌大脑团队在谷歌机器智能研究内部为机器学习和深度神经网络研究开发的,但现在它已进入公共领域。当配置适当的时候,TensorFlow 会利用 GPU 处理。

TensorFlow 的通用用例如下:

  • 图像识别

  • 计算机视觉

  • 语音/声音识别

  • 时间序列分析

  • 语言检测

  • 语言翻译

  • 基于文本的处理

  • 手写识别

  • 许多其他

要使用 TensorFlow,我们首先必须安装 Python。如果你机器上没有 Python 安装,那么是时候安装它了。Python 是一种动态的面向对象编程OOP)语言,可用于许多类型的软件开发。它提供了与其他语言和程序集成的强大支持,附带一个大型标准库,可以在几天内学会。许多 Python 程序员可以证实生产力的显著提高,并觉得它鼓励开发更高质量的代码和可维护性。

Python 运行在 Windows、Linux/Unix、macOS X、OS/2、Amiga、Palm 手持设备和诺基亚手机上。它还适用于 Java 和.NET 虚拟机。Python 遵循 OSI 批准的开源许可;其使用是免费的,包括商业产品。

Python 是在 20 世纪 90 年代初由荷兰 Stichting Mathematisch Centrum 的 Guido van Rossum 创建的,作为名为ABC的语言的继任者。尽管 Python 包括许多其他人的贡献,但 Guido 仍然是 Python 的主要作者。

如果你不知道要使用哪个版本,有一份英文文档可以帮助你选择。原则上,如果你必须从头开始,我们建议选择 Python 3.6。有关可用版本和如何安装 Python 的所有信息,请参阅www.python.org/

在正确安装我们的机器的 Python 版本后,我们必须担心安装 TensorFlow。我们可以从以下链接检索所有库信息和操作系统的可用版本:www.tensorflow.org/

此外,在安装部分,我们可以找到一系列指南,解释如何安装允许我们用 Python 编写应用程序的 TensorFlow 版本。以下操作系统的指南可用:

  • Ubuntu

  • macOS X

  • Windows

例如,要在 Windows 上安装 TensorFlow,我们必须选择以下类型之一:

  • 仅支持 CPU 的 TensorFlow

  • 支持 GPU 的 TensorFlow

要安装 TensorFlow,以管理员权限启动终端。然后在终端中发出适当的pip3 install命令。要安装仅 CPU 版本,请输入以下命令:

C:\> pip3 install --upgrade tensorflow

一系列代码行将在视频中显示,以使我们了解安装过程的执行情况,如图所示:

图片

在过程结束时,将显示以下代码:

Successfully installed absl-py-0.1.10 markdown-2.6.11 numpy-1.14.0 protobuf-3.5.1 setuptools-38.5.1 tensorflow-1.5.0 tensorflow-tensorboard-1.5.1 werkzeug-0.14.1

要验证安装,从 shell 中调用python,如下所示:

python

在 Python 交互式外壳中输入以下简短程序:

>>> import tensorflow as tf
>>> hello = tf.constant('Hello, TensorFlow!')
>>> sess = tf.Session()
>>> print(sess.run(hello))

如果系统输出以下内容,那么你就可以开始编写 TensorFlow 程序了:

Hello, TensorFlow!

在这种情况下,你将确认库在你的计算机上正确安装。现在你只需要使用它。

使用 CNN 和 TensorFlow 进行手写识别

手写识别HWR)是现代技术中非常常用的程序。通过光学扫描(光学字符识别OCR)或智能文字识别,可以从一张纸上离线检测到书写文本的图像。或者,可以在线检测笔尖移动(例如,从笔计算机表面,由于有更多线索,这通常更容易)。

从技术上来说,手写识别是计算机接收和解释来自纸张文件、照片、触摸屏和其他设备等来源的手写可识别输入的能力。HWR 通过需要 OCR 的各种技术来完成。然而,一个完整的脚本识别系统还管理格式,执行正确的字符分割,并找到最可能的单词。

修改后的国家标准与技术研究院MNIST)是一个包含手写数字的大型数据库。它包含 70,000 个数据示例。它是 NIST 更大数据集的一个子集。这些数字的分辨率为 28 x 28 像素,存储在一个 70,000 行和 785 列的矩阵中;784 列形成 28 x 28 矩阵中的每个像素值,一个值是实际的数字。这些数字已经被尺寸归一化,并居中在一个固定大小的图像中。

MNIST 集中的数字图像最初是由 Chris Burges 和 Corinna Cortes 使用边界框归一化和居中技术选定的。Yann LeCun 的版本使用更大窗口内的质心居中。数据可在 Yann LeCun 的网站上找到:

yann.lecun.com/exdb/mnist/.

每个图像都是 28 x 28 像素创建的。以下图显示了 MNIST 数据集中 0-8 的图像样本:

MNIST 包含几个手写数字的样本。这个数据集可以被输入到我们的 Python 程序中,我们的代码可以识别任何作为预测数据呈现的新手写数字。这是一个神经网络架构作为计算机视觉系统在 AI 应用中起作用的案例。以下表格显示了 LeCun 网站上可用的 MNIST 数据集的分布:

数字 数量
0 5923
1 6742
2 5958
3 6131
4 5842
5 5421
6 5918
7 6265
8 5851
9 5949

我们将使用 TensorFlow 库来训练和测试 MNIST 数据集。我们将 70,000 行的数据集分为 60,000 个训练行和 10,000 个测试行。接下来,我们将找到模型的准确率。然后,该模型可以用来预测任何包含 0 到 9 数字的 28 x 28 像素手写数字的输入数据集。在我们的示例 Python 代码中,我们使用一个 100 行的训练数据集和一个 10 行的测试数据集。在这个例子中,我们将学习如何使用 TensorFlow 层模块,它提供了一个高级 API,使得构建神经网络变得容易。它提供了创建密集(FC)层和卷积层、添加激活函数和应用 dropout 正则化的方法。

首先,我们将逐行分析代码,然后我们将了解如何使用 Google Cloud Platform 提供的工具来处理它。现在,让我们从头开始分析代码,学习如何应用卷积神经网络(CNN)来解决手写识别(HWR)问题:

from __future__ import absolute_import
from __future__ import division
from __future__ import print_function

这三条线被添加以编写兼容 Python 2/3 的代码库。所以让我们继续导入模块:

import numpy as np
import tensorflow as tf

这样,我们已经导入了numpytensorflow模块。让我们分析下一行代码:

tf.logging.set_verbosity(tf.logging.INFO)

这段代码设置了将记录的消息的阈值。在初始阶段之后,我们将定义一个函数,该函数将允许我们构建一个 CNN 模型:

def cnn_model_fn(features, labels, mode):

因此,我们已经定义了函数。现在让我们继续:

input_layer = tf.reshape(features["x"], [-1, 28, 28, 1])

在这一行代码中,我们以(batch_size,image_width,image_height,channels)的形式传递了输入张量,这是从层模块中的方法期望得到的,用于创建二维图像数据的卷积和池化层。让我们继续到第一个卷积层:

conv1 = tf.layers.conv2d(
      inputs=input_layer,
      filters=32,
      kernel_size=[5, 5],
      padding="same",
      activation=tf.nn.relu)

这一层创建了一个卷积核,该核与层输入卷积以产生输出张量。卷积中的过滤器数量为 32,2D 卷积窗口的高度和宽度为[5,5],激活函数是 ReLU 函数。为此,我们使用了层模块中的conv2d()方法。接下来,我们将我们的第一个池化层连接到我们刚刚创建的卷积层:

pool1 = tf.layers.max_pooling2d(inputs=conv1,
                       pool_size=[2, 2], strides=2)

我们在层中使用了max_pooling2d()方法来构建一个执行 2x2 过滤器最大池化和步长为2的层。现在我们将第二个卷积层连接到我们的 CNN:

conv2 = tf.layers.conv2d(
    inputs=pool1,
    filters=64,
    kernel_size=[5, 5],
    padding="same",
    activation=tf.nn.relu)

现在我们将连接第二个池化层到我们的卷积神经网络(CNN):

pool2 = tf.layers.max_pooling2d(inputs=conv2,
                   pool_size=[2, 2], strides=2)
pool2_flat = tf.reshape(pool2, [-1, 7 * 7 * 64])

接下来,我们将添加一个密集层:

dense = tf.layers.dense(inputs=pool2_flat,
                units=1024, activation=tf.nn.relu)

通过这段代码,我们在 CNN 中添加了一个包含 1,024 个神经元的密集层和 ReLU 激活函数,以对卷积/池化层提取的特征进行分类。

记住,ReLU 层由应用函数f(x) = max (0, x)的神经元组成。这些层增加了网络的非线性,同时它们不修改卷积层的接收域。

为了提高结果,我们将在密集层上应用 dropout 正则化:

dropout = tf.layers.dropout(inputs=dense,
            rate=0.4, training=mode ==
                      tf.estimator.ModeKeys.TRAIN)

为了做到这一点,我们在层中使用了 dropout 方法。接下来,我们将添加最终的层到我们的神经网络:

logits = tf.layers.dense(inputs=dropout, units=10)

这是logits层,它将返回我们的预测的原始值。通过前面的代码,我们创建了一个包含10个神经元的密集层(每个目标类 0-9 一个),具有线性激活。我们只需生成预测:

predictions = {
      "classes": tf.argmax(input=logits, axis=1),
       "probabilities": tf.nn.softmax(logits, name="softmax_tensor")
  }
  if mode == tf.estimator.ModeKeys.PREDICT:
        return tf.estimator.EstimatorSpec(mode=mode,
                           predictions=predictions)

我们将预测生成的原始值转换为两种不同的格式,我们的模型函数可以返回:一个从 0 到 9 的数字以及示例是零、是一、是二等的概率。我们在字典中编译我们的预测并返回一个EstimatorSpec对象。现在,我们将传递定义一个loss函数:

loss = tf.losses.sparse_softmax_cross_entropy(labels=labels, logits=logits)

loss函数衡量模型的预测与目标类之间的匹配程度。此函数用于训练和评估。我们将配置我们的模型,在训练期间优化此损失值:

if mode == tf.estimator.ModeKeys.TRAIN:
    optimizer = tf.train.GradientDescentOptimizer(learning_rate=0.001)
    train_op = optimizer.minimize(
        loss=loss,
        global_step=tf.train.get_global_step())
    return tf.estimator.EstimatorSpec(mode=mode, loss=loss, train_op=train_op)

我们使用0.001的学习率和随机梯度下降作为优化算法。现在,我们将在模型中添加一个准确度指标:

  eval_metric_ops = {
      "accuracy": tf.metrics.accuracy(
          labels=labels, predictions=predictions["classes"])}
  return tf.estimator.EstimatorSpec(
      mode=mode, loss=loss, eval_metric_ops=eval_metric_ops)

为了做到这一点,我们在EVAL模式下定义了eval_metric_ops字典。因此,我们已经定义了网络的架构;现在有必要定义训练和测试网络的代码。为此,我们将在 Python 代码中添加一个main()函数:

def main(unused_argv):

然后我们将加载训练和评估数据:

  mnist = tf.contrib.learn.datasets.load_dataset("mnist")
  train_data = mnist.train.images 
  train_labels = np.asarray(mnist.train.labels, dtype=np.int32)
  eval_data = mnist.test.images 
  eval_labels = np.asarray(mnist.test.labels, dtype=np.int32)

在这段代码中,我们将训练特征数据和训练标签分别存储在train_datatrain_labels中的numpy数组中。同样,我们将评估特征数据和评估标签分别存储在eval_dataeval_labels中。接下来,我们将为我们的模型创建一个Estimator

  mnist_classifier = tf.estimator.Estimator(
      model_fn=cnn_model_fn, model_dir="/tmp/mnist_convnet_model")

Estimator是 TensorFlow 的一个类,用于执行高级模型训练、评估和推理。以下代码设置了预测的日志记录:

  tensors_to_log = {"probabilities": "softmax_tensor"}
  logging_hook = tf.train.LoggingTensorHook(
      tensors=tensors_to_log, every_n_iter=50)

现在我们已经准备好训练我们的模型:

    train_input_fn = tf.estimator.inputs.numpy_input_fn(
      x={"x": train_data},
      y=train_labels,
      batch_size=100,
      num_epochs=None,
      shuffle=True)
  mnist_classifier.train(
      input_fn=train_input_fn,
      steps=15000,
      hooks=[logging_hook])

为了做到这一点,我们创建了train_input_fn并在mnist_classifier上调用train()。在前面的代码中,我们固定了steps=15000,这意味着模型将总共训练 15,000 步。

执行此训练所需的时间取决于我们机器上安装的处理器,但无论如何,它可能将超过 1 小时。为了在更短的时间内进行此类训练,您可以减少传递给train()函数的步骤数;很明显,这种变化将对算法的准确性产生负面影响。

最后,我们将评估模型并打印结果:

    eval_input_fn = tf.estimator.inputs.numpy_input_fn(
      x={"x": eval_data},
      y=eval_labels,
      num_epochs=1,
      shuffle=False)
  eval_results = mnist_classifier.evaluate(input_fn=eval_input_fn)
  print(eval_results)

我们调用了evaluate方法,该方法评估了在model_fneval_metriced_ops参数中指定的度量标准。我们的 Python 代码以以下行结束:

if __name__ == "__main__":
  tf.app.run()

这些行只是一个非常快速的包装器,用于处理标志解析,然后转发到您自己的主函数。在这个阶段,我们只需将整个代码复制到一个具有.py扩展名的文件中,并在安装了 Python 和 TensorFlow 的机器上运行它。

在 Google Cloud Shell 上运行 Python 代码

Google Cloud Shell 直接从您的浏览器提供对云资源的命令行访问。您可以轻松管理项目和服务,无需在系统中安装 Google Cloud SDK 或其他工具。使用 Cloud Shell,当您需要时,Cloud SDK 的gcloud命令行工具和其他必要的实用工具始终可用,更新并完全认证。

以下是一些 Google Cloud Shell 的功能:

  • 这是一个用于管理托管在 Google Cloud Platform 上的资源的 shell 环境。

  • 我们可以使用 Linux shell 的灵活性来管理我们的 GCP 资源。Cloud Shell 通过在网页控制台中打开的终端窗口,提供了对虚拟机实例的命令行访问。

  • 它为访问托管在 Google Cloud Platform 上的项目和资源提供了集成授权。

  • 您最喜欢的许多命令行工具,从 bash 和 sh 到 emacs 和 vim,都已经预先安装和更新。如 MySQL 客户端、Kubernetes 和 Docker 等管理工具已配置并就绪。您不再需要担心安装最新版本及其所有依赖项。只需连接到 Cloud Shell。

开发者将能够访问所有喜欢的预配置开发工具。您将找到 Java、Go、Python、Node.js、PHP 和 Ruby 的开发和实现工具。在 Cloud Shell 实例中运行您的网络应用程序,并在浏览器中预览它们。然后使用预配置的 Git 和 Mercurial 客户端再次提交到存储库。

Cloud Shell 为 Cloud Shell 实例分配了 5 GB 的永久磁盘存储空间,挂载为 $ HOME 目录。存储在 $ HOME 目录中的所有文件,包括用户配置脚本和 bashrcvimrc 等文件,从一个会话持续到另一个会话。

要启动 Cloud Shell,只需在控制台窗口顶部点击激活 Google Cloud Shell 按钮,如下面的截图所示:

在控制台底部的新的框架中打开一个 Cloud Shell 会话,并显示命令行提示符。初始化 shell 会话可能需要几秒钟。现在,我们的 Cloud Shell 会话已准备好使用,如下面的截图所示:

在这一点上,我们需要将包含 Python 代码的 cnn_hwr.py 文件传输到 Google Cloud Platform。我们已看到,为此,我们可以使用 Google Cloud Storage 提供的资源。然后我们打开 Google Cloud Storage 浏览器并创建一个新的存储桶。

记住,存储桶是基本容器,用于存储您的数据。您存储在 Cloud Storage 中的所有内容都必须包含在存储桶中。您可以使用存储桶来组织您的数据并控制对数据的访问,但与目录和文件夹不同,您不能嵌套存储桶。

要将 cnn_hwr.py 文件传输到 Google Storage,请执行以下步骤:

  1. 只需点击创建存储桶图标

  2. 在创建存储桶窗口中输入新存储桶的名称(cnn-hwr

  3. 之后,在存储桶列表中会出现一个新的存储桶

  4. 点击 cnn-hwr 存储桶

  5. 在打开的窗口中点击上传文件图标

  6. 在打开的对话框窗口中选择文件

  7. 点击打开

在这一点上,我们的文件将出现在新的存储桶中,如下面的图所示:

现在,我们可以从 Cloud Shell 访问文件。为此,我们在 shell 中创建一个新的文件夹。在 shell 提示符中输入以下命令:

mkdir CNN-HWR

现在,要将文件从 Google Storage 存储桶复制到 CNN-HWR 文件夹,只需在 shell 提示符中输入以下命令:

gsutil cp gs://cnn-hwr-mlengine/cnn_hwr.py CNN-HWR

以下代码将显示:

giuseppe_ciaburro@progetto-1-191608:~$ gsutil cp gs://cnn-hwr/cnn_hwr.py CNN-HWR
Copying gs://cnn-hwr/cnn_hwr.py...
/ [1 files][ 5.7 KiB/ 5.7 KiB]
Operation completed over 1 objects/5.7 KiB.

现在,让我们进入文件夹并验证文件的存在:

$cd CNN-HWR
$ls
cnn_hwr.py

我们只需运行该文件:

$ python cnn_hwr.py

显示了一系列初步指令:

Successfully downloaded train-images-idx3-ubyte.gz 9912422 bytes.
Extracting MNIST-data/train-images-idx3-ubyte.gz
Successfully downloaded train-labels-idx1-ubyte.gz 28881 bytes.
Extracting MNIST-data/train-labels-idx1-ubyte.gz
Successfully downloaded t10k-images-idx3-ubyte.gz 1648877 bytes.
Extracting MNIST-data/t10k-images-idx3-ubyte.gz
Successfully downloaded t10k-labels-idx1-ubyte.gz 4542 bytes.
Extracting MNIST-data/t10k-labels-idx1-ubyte.gz
INFO:tensorflow:Using default config.

它们表明数据下载成功,TensorFlow 库的调用也成功了。从这一点开始,网络的训练开始,正如我们预料的,这可能相当长。算法执行结束后,将返回以下信息:

INFO:tensorflow:Saving checkpoints for 15000 into /tmp/mnist_convnet_model/model.ckpt.
INFO:tensorflow:Loss for final step: 2.2751274.INFO:tensorflow:Starting evaluation at 2018-02-19-08:47:04
INFO:tensorflow:Restoring parameters from /tmp/mnist_convnet_model/model.ckpt-15000
INFO:tensorflow:Finished evaluation at 2018-02-19-08:47:56
INFO:tensorflow:Saving dict for global step 15000: accuracy = 0.9723, global_step = 15000, loss = 0.098432
{'loss': 0.098432, 'global_step': 15000, 'accuracy': 0.9723}

在这种情况下,我们在测试数据集上达到了97.2的准确率。

循环神经网络

前馈神经网络基于输入数据,这些数据被提供给网络并转换为输出。如果是一个监督学习算法,输出是一个可以识别输入的标签。基本上,这些算法通过识别模式将原始数据连接到特定的类别。另一方面,循环网络不仅接受被提供给网络的当前输入数据,还包括它们随时间经历过的内容。

一个循环神经网络RNN)是一种信息双向流动的神经网络模型。换句话说,在前馈网络中,信号的传播仅以连续的方式从输入到输出方向进行,而循环网络则不同。在这些网络中,这种传播也可以从跟随前一个神经层的神经层发生,或者在同一层的神经元之间发生,甚至可以发生在同一个神经元自身之间。

一个循环网络在特定时刻做出的决策会影响它接下来将做出的决策。因此,循环网络有两个输入来源——当前和最近过去——它们结合起来决定如何对新数据进行响应,就像人们在日常生活中所做的那样。

循环网络之所以与前馈网络不同,是因为它们与过去决策相关的反馈回路,因此暂时将它们的输出作为输入接受。这一点可以通过说循环网络具有记忆来强调。将记忆添加到神经网络中有其目的:序列本身包含信息,循环网络使用这些信息执行前馈网络无法执行的任务。

访问记忆是通过内容而不是通过地址或位置来实现的。一种方法是认为记忆内容是 RNN 节点上的激活模式。想法是使用一个激活方案来启动网络,这个方案是请求的记忆内容的部分或噪声表示,并且网络稳定在所需的内容上。

RNN 是一类神经网络,其中至少存在一个神经元之间的反馈连接,形成一个有向循环。以下图示了一个典型的 RNN,其输出层和隐藏层之间存在连接:

图片

在图所示的循环网络中,既使用输入层又使用输出层来定义隐藏层的权重。

最终,我们可以将 RNN 视为 ANN 的一种变体:这些变体可以在不同的隐藏层数量和数据流的不同趋势上进行表征。RNN 的特点是数据流的不同趋势,实际上神经元之间的连接形成了一个循环。与前馈网络不同,RNN 可以使用内部记忆进行其处理。RNN 是一类 ANN,其特征是隐藏层之间的连接,这些连接通过时间传播以学习序列。

数据在内存中保持的方式以及在不同时间周期中的流动使得 RNN 强大且成功。RNN 的应用案例包括以下领域:

  • 股市预测

  • 图像标题

  • 天气预报

  • 基于时间序列的预测

  • 语言翻译

  • 语音识别

  • 手写识别

  • 音频或视频处理

  • 机器人动作序列

循环神经网络被设计成通过数据序列识别模式,并在预测和预测方面非常有用。它们可以在文本、图像、语音和时间序列数据上工作。RNN 是功能强大的 ANN 之一,代表了生物大脑,包括具有处理能力的记忆。

循环神经网络从当前输入(类似于前馈网络)以及之前计算得出的输出中获取输入。在下面的图中,我们比较了前馈神经网络和循环神经网络的单个神经元工作原理:

图片

正如我们在简单、刚刚提出的单个神经元方案中看到的那样,反馈信号被添加到 RNN 的输入信号中。反馈是一个相当重要和显著的特征。与仅限于从输入到输出的单向信号的简单网络相比,反馈网络更有可能更新,并且具有更多的计算能力。反馈网络显示出单向网络未揭示的现象和过程。

为了理解 ANN 和 RNN 之间的差异,我们将 RNN 视为神经网络的网络,并且循环性质以下列方式展开:考虑神经元在不同时间周期(t-1tt+1,等等)的状态,直到收敛或达到总 epoch 数。

网络学习阶段可以使用类似于导致前馈网络反向传播算法的梯度下降过程进行。至少在简单架构和确定性激活函数的情况下是有效的。当激活是随机的,模拟退火方法可能更合适。

RNN 架构可以有多种不同的形式。在数据向后流动的方式上有更多变体:

  • 完全循环

  • 递归

  • 跳频

  • Elman 网络

  • LSTM

  • 门控循环单元

  • 双向

  • 循环多层感知器

在接下来的几页中,我们将分析这些网络中的一些架构。

完全循环神经网络

一个全递归神经网络是一个神经元网络,每个神经元都有一个指向其他每个神经元的定向(单向)连接。每个神经元都有一个时变的、实值的激活度。每个连接都有一个可修改的实值权重。期望有输入神经元、输出神经元和隐藏神经元。这种网络是一种多层感知器,其中前一组隐藏单元的激活度与输入一起反馈到网络中,如下面的图所示:

图片

在每一步中,每个非输入单元将其当前激活度计算为连接到它的所有单元激活度的加权和的非线性函数。

递归神经网络

递归网络是循环网络的推广。在循环网络中,权重是共享的,维度在序列长度上保持不变。在递归网络中,权重是共享的,维度在节点上保持不变。以下图显示了递归神经网络的外观:

图片

递归神经网络可用于学习树状结构。它们在解析自然场景和语言方面非常有用。

霍普菲尔德循环神经网络

1982 年,物理学家约翰·J·霍普菲尔德发表了一篇基础文章,在其中引入了一个数学模型,通常被称为霍普菲尔德网络。这个网络突出了从大量简单处理元素的集体行为中产生的新计算能力。霍普菲尔德网络是一种循环人工神经网络(ANN)的形式。

根据霍普菲尔德的观点,每个物理系统都可以被认为是一个潜在的存储设备,如果它具有一定数量的稳定状态,这些状态作为系统本身的吸引子。基于这一考虑,他提出了这样的吸引子的稳定性和位置代表了由大量相互作用的神经元组成的系统的自发性属性的观点。

结构上,霍普菲尔德网络构成一个循环对称神经网络(因此具有对称的突触权重矩阵),它是完全连接的,其中每个神经元都与所有其他神经元相连,如下面的图所示:

图片

如前所述,循环神经网络是一种包含双向信息流的神经网络;换句话说,在前馈网络中,信号的传播仅以连续的方式在从输入到输出的方向上进行,而在循环网络中,这种传播也可以从跟随前一个神经层的神经层或同一层中的神经元(霍普菲尔德网络)之间发生,甚至可以发生在神经元与其自身之间。

跳频网络的动力学由一个非线性微分方程组描述,神经元更新机制可以是:

  • 异步:一次更新一个神经元

  • 同步:所有神经元同时更新

  • 连续:所有神经元持续更新

Elman 神经网络

Elman 神经网络是一种前馈网络,其中除了与输出层相连外,隐藏层还分支到另一个相同的层,称为上下文层,它与权重等于一的上下文层相连。在每一个时刻(每次数据传递到输入层的神经元时),上下文层的神经元保持前一个值并将它们传递给相应的隐藏层神经元。以下图显示了 Elman 网络方案:

图片

与前馈网络类似,Elman 的 RNN 可以使用称为时间反向传播BPTT)的算法进行训练,这是专门为 RNN 创建的反向传播的变体。实质上,该算法将神经网络展开,将其转换为前馈网络,层数等于要学习的序列长度;随后,应用经典的反向传播算法。或者,可以使用全局优化方法,如遗传算法,特别是在无法应用 BPTT 的 RNN 拓扑结构上。

长短期记忆网络

LSTM 是 RNN 的一种特定架构,最初由 Hochreiter 和 Schmidhuber 在 1997 年构思。这种类型的神经网络最近在深度学习背景下被重新发现,因为它不受梯度消失问题的影响,并且在实践中提供了出色的结果和性能。

梯度消失问题影响了基于梯度的学习方法的 ANN 训练。在梯度方法,如反向传播中,权重按误差梯度的比例进行调整。由于上述梯度的计算方式,我们得到一个效果,即它们的模量呈指数下降,朝着最深的层前进。问题是,在某些情况下,梯度会变得极其小,实际上阻止了权重改变其值。在最坏的情况下,这可能会完全阻止神经网络进一步训练。

基于 LSTM 的网络非常适合预测和分类时间序列,它们正在取代许多经典机器学习方法。事实上,在 2012 年,谷歌替换了其语音识别模型,从表示了 30 多年标准的隐马尔可夫模型(HMM)过渡到深度学习神经网络。在 2015 年,它切换到结合连接主义时间分类CTC)的 RNN LSTM。

CTC 是一种神经网络输出和相关评分函数,用于训练 RNN。

这是因为 LSTM 网络能够考虑数据之间的长期依赖关系,在语音识别的情况下,这意味着管理句子中的上下文以提高识别能力。

LSTM 网络由相互连接的细胞(LSTM 块)组成。每个细胞又由三种类型的端口组成:输入门输出门遗忘门。它们分别实现了对细胞内存的写入、读取和重置功能。端口不是二进制而是模拟的(通常由一个映射到范围(0, 1)的 sigmoid 激活函数管理,其中零表示完全抑制,1 表示完全激活),并且是乘法的。这些端口的存 在使得 LSTM 细胞能够记住信息一段时间。实际上,如果输入门低于激活阈值,细胞将保持之前的状态,而如果它被激活,当前状态将与输入值相结合。正如其名所示,遗忘门将细胞当前状态重置(当其值被带到零时),输出门决定细胞内的值是否必须取出。

下图显示了 LSTM 单元:

基于神经网络的这些方法非常强大,因为它们能够捕捉数据之间的特征和关系。特别是,在实践中,也发现 LSTM 网络提供了高性能和优秀的识别率。一个缺点是神经网络是黑盒模型,因此它们的行为是不可预测的,并且无法追踪它们处理数据的逻辑。

使用 RNN 和 TensorFlow 进行手写识别

为了练习 RNN,我们将使用之前用于构建 CNN 的数据集。我指的是 MNIST 数据集,这是一个包含大量手写数字的大型数据库。它包含 70,000 个数据示例。它是 NIST 更大数据集的一个子集。28 x 28 像素分辨率的图像存储在一个 70,000 行和 785 列的矩阵中;28 x 28 矩阵中的每个像素值和一个值是实际的数字。在固定大小的图像中,数字已经被尺寸归一化。

在此情况下,我们将使用 TensorFlow 库实现一个 RNN(LSTM)来对图像进行分类。我们将把每行图像视为像素序列。由于 MNIST 图像的形状是 28 x 28,我们将为每个样本处理 28 个时间步长的 28 个序列。

首先,我们将逐行分析代码;然后我们将看到如何使用 Google Cloud Platform 提供的工具来处理它。现在,让我们通过代码学习如何将 RNN(LSTM)应用于解决 HWR 问题。让我们从代码的开始部分开始:

from __future__ import absolute_import
from __future__ import division
from __future__ import print_function

这三行代码是为了编写一个兼容 Python 2/3 的代码库而添加的。因此,让我们继续导入模块:

import tensorflow as tf
from tensorflow.contrib import rnn

这样,我们已经导入了tensorflow模块,以及来自tensorflow.contribrnn模块。《tensorflow.contrib》包含易变或实验性代码。《rnn`模块是一个用于构建 RNN 单元和额外的 RNN 操作的模块。让我们分析下一行代码:

from tensorflow.examples.tutorials.mnist import input_data
mnist = input_data.read_data_sets("/tmp/data/", one_hot=True)

第一行用于从 TensorFlow 库导入mnist数据集;实际上,minist数据集已经作为示例存在于库中。第二行从本地目录读取数据。让我们继续设置训练参数:

learning_rate = 0.001
training_steps = 20000
batch_size = 128
display_step = 1000

学习率是学习算法用来确定权重调整速度的值。它决定了使用该算法训练的权重神经元的获取时间。《训练步数》设置训练过程执行次数。《批量大小》是你输入网络的样本数量。《显示步数》决定显示训练部分结果的步数。现在让我们设置网络参数:

num_input = 28
timesteps = 28
num_hidden = 128
num_classes = 10

第一个参数(num_input)设置 MNIST 数据输入(图像形状:28 x 28)。timesteps参数相当于你运行 RNN 的时间步数。《num_hidden参数设置神经网络隐藏层的数量。最后,《num_classes参数设置 MNIST 总类别(0-9 数字)。让我们分析以下代码行:

X = tf.placeholder("float", [None, timesteps, num_input])
Y = tf.placeholder("float", [None, num_classes])

在这些代码行中,我们使用了tf.placeholder()函数。占位符简单地说是一个我们将在以后日期分配数据的变量。它允许我们创建操作和构建计算图,而无需数据。这样,我们已经设置了tf.Graph输入。《tf.Graph》包含两种相关信息:图结构和图集合。TensorFlow 使用数据流图来表示你的计算,以单个操作之间的依赖关系为依据。这导致了一种低级编程模型,其中你首先定义数据流图,然后创建 TensorFlow 会话,在一系列本地和远程设备上运行图的一部分。让我们继续定义权重

weights = {
    'out': tf.Variable(tf.random_normal([num_hidden, num_classes]))
}
biases = {
    'out': tf.Variable(tf.random_normal([num_classes]))
}

网络中的权重是将输入转换为影响输出的最重要的因素。这与线性回归中的斜率类似,其中权重乘以输入以形成输出。权重是决定每个神经元如何影响其他神经元的数值参数。偏差类似于线性方程中添加的截距。它是一个额外的参数,用于调整输出,以及神经元输入的加权总和。现在我们必须通过创建一个新的函数来定义RNN

def RNN(x, weights, biases):
    x = tf.unstack(x, timesteps, 1)
    lstm_cell = rnn.BasicLSTMCell(num_hidden, forget_bias=1.0)
    outputs, states = rnn.static_rnn(lstm_cell, x, dtype=tf.float32)
    return tf.matmul(outputs[-1], weights['out']) + biases['out']

使用 unstack() 函数来获取形状为 (batch_size, n_input) 的 timesteps 张量列表。然后我们使用 TensorFlow 定义了一个 lstm 单元,并得到了 lstm 单元输出。最后,我们在内循环和最后一个输出处放置了一个线性激活,使用 RNN。接下来,让我们继续:

logits = RNN(X, weights, biases)
prediction = tf.nn.softmax(logits)

第一行代码使用新定义的 RNN 函数构建网络,而第二行代码使用 tf.nn.softmax() 函数进行预测,该函数计算 softmax 激活。接下来,我们将定义 lossoptimizer

loss_op = tf.reduce_mean(tf.nn.softmax_cross_entropy_with_logits_v2(
    logits=logits, labels=Y))
optimizer = tf.train.GradientDescentOptimizer(learning_rate=learning_rate)
train_op = optimizer.minimize(loss_op)

loss 函数将事件或一个或多个变量的值映射到一个实数,直观地表示与事件相关的一些 cost。我们使用了 tf.reduce_mean() 函数,该函数计算张量维度的元素均值。optimizer 基类提供了计算损失梯度并将梯度应用于变量的方法。一系列子类实现了经典的优化算法,如梯度下降和 AdaGrad。让我们继续评估模型:

correct_pred = tf.equal(tf.argmax(prediction, 1), tf.argmax(Y, 1))
accuracy = tf.reduce_mean(tf.cast(correct_pred, tf.float32))

然后,我们将通过分配它们的默认值来初始化变量:

init = tf.global_variables_initializer()

现在,我们可以开始训练网络:

with tf.Session() as sess:
    sess.run(init)
    for step in range(1, training_steps+1):
        batch_x, batch_y = mnist.train.next_batch(batch_size)
        batch_x = batch_x.reshape((batch_size, timesteps, num_input))
        sess.run(train_op, feed_dict={X: batch_x, Y: batch_y})
        if step % display_step == 0 or step == 1:
            loss, acc = sess.run([loss_op, accuracy], feed_dict={X: batch_x,
                                                                 Y: batch_y})
            print("Step " + str(step) + ", Minibatch Loss= " + \
                  "{:.4f}".format(loss) + ", Training Accuracy= " + \
                  "{:.3f}".format(acc))
    print("End of the optimization process ")

最后,我们将计算 128 个 mnist 测试图像的准确率:

    test_len = 128
    test_data = mnist.test.images[:test_len].reshape((-1, timesteps, num_input))
    test_label = mnist.test.labels[:test_len]
    print("Testing Accuracy:", \
        sess.run(accuracy, feed_dict={X: test_data, Y: test_label}))

到目前为止,我们只需将整个代码复制到一个以 .py 扩展名的文件中,并在安装了 Python 和 TensorFlow 的机器上运行它。

在 Google Cloud Shell 上使用 LSTM

在彻底分析 Python 代码之后,是时候运行它来对数据集中的图像进行分类了。为此,我们以与 CNN 示例类似的方式进行操作。因此,我们将使用 Google Cloud Shell。Google Cloud Shell 直接从您的浏览器提供对云资源的命令行访问。您可以轻松管理项目和服务,而无需在系统中安装 Google Cloud SDK 或其他工具。使用 Cloud Shell,当您需要时,Cloud SDK 的 gcloud 命令行工具和其他必要的实用工具总是可用、更新并完全认证。

要启动 Cloud Shell,只需在控制窗口顶部单击“激活 Google Cloud Shell”按钮,如下面的截图所示:

Cloud Shell 会话在控制窗口底部的新的框架中打开,并显示一个命令行提示符。初始化 shell 会话可能需要几秒钟。现在,我们的 Cloud Shell 会话已准备好使用,如下面的截图所示:

到目前为止,我们需要将包含 Python 代码的 rnn_hwr.py 文件传输到 Google Cloud Platform。我们已经看到,为此,我们可以使用 Google Cloud Storage 提供的资源。然后我们打开 Google Cloud Storage 浏览器并创建一个新的存储桶。

要将 cnn_hwr.py 文件传输到 Google 存储,请按照以下步骤操作:

  1. 只需单击 CREATE BUCKET 图标

  2. 在创建存储桶窗口中输入新存储桶的名称(rnn-hwr

  3. 之后,在存储桶列表中会出现一个新的存储桶

  4. 点击rnn-hwr存储桶

  5. 点击打开的窗口中的“上传文件”图标

  6. 在弹出的对话框中选择文件

  7. 点击“打开”

在这一点上,我们的文件将出现在新的存储桶中,如下面的截图所示:

现在我们可以从 Cloud Shell 访问该文件。为此,我们在 shell 中创建一个新的文件夹。在 shell 提示符中输入以下命令:

mkdir RNN-HWR

现在,要将文件从 Google Storage 存储桶复制到CNN-HWR文件夹,只需在 shell 提示符中输入以下命令:

gsutil cp gs://rnn-hwr-mlengine/rnn_hwr.py RNN-HWR

显示以下代码:

giuseppe_ciaburro@progetto-1-191608:~$ gsutil cp gs://rnn-hwr/rnn_hwr.py RNN-HWR
Copying gs://rnn-hwr/rnn_hwr.py...
/ [1 files][ 4.0 KiB/ 4.0 KiB]
Operation completed over 1 objects/4.0 KiB.

现在,让我们进入文件夹并验证文件的存在:

$cd RNN-HWR
$ls
rnn_hwr.py

我们只需运行文件:

$ python rnn_hwr.py

显示一系列初步指令:

Extracting /tmp/data/train-images-idx3-ubyte.gz
Extracting /tmp/data/train-labels-idx1-ubyte.gz
Extracting /tmp/data/t10k-images-idx3-ubyte.gz
Extracting /tmp/data/t10k-labels-idx1-ubyte.gz

它们表明数据下载成功,TensorFlow 库的调用也成功。从这一点开始,网络的训练开始,正如我们预期的,可能相当长。算法执行结束后,将返回以下信息:

Step 1, Minibatch Loss= 2.9727, Training Accuracy= 0.117
Step 1000, Minibatch Loss= 1.8381, Training Accuracy= 0.430
Step 2000, Minibatch Loss= 1.4021, Training Accuracy= 0.602
Step 3000, Minibatch Loss= 1.1560, Training Accuracy= 0.672
Step 4000, Minibatch Loss= 0.9748, Training Accuracy= 0.727
Step 5000, Minibatch Loss= 0.8156, Training Accuracy= 0.750
Step 6000, Minibatch Loss= 0.7572, Training Accuracy= 0.758
Step 7000, Minibatch Loss= 0.5930, Training Accuracy= 0.812
Step 8000, Minibatch Loss= 0.5583, Training Accuracy= 0.805
Step 9000, Minibatch Loss= 0.4324, Training Accuracy= 0.914
Step 10000, Minibatch Loss= 0.4227, Training Accuracy= 0.844
Step 11000, Minibatch Loss= 0.2818, Training Accuracy= 0.906
Step 12000, Minibatch Loss= 0.3205, Training Accuracy= 0.922
Step 13000, Minibatch Loss= 0.4042, Training Accuracy= 0.891
Step 14000, Minibatch Loss= 0.2918, Training Accuracy= 0.914
Step 15000, Minibatch Loss= 0.1991, Training Accuracy= 0.938
Step 16000, Minibatch Loss= 0.2815, Training Accuracy= 0.930
Step 17000, Minibatch Loss= 0.1790, Training Accuracy= 0.953
Step 18000, Minibatch Loss= 0.2627, Training Accuracy= 0.906
Step 19000, Minibatch Loss= 0.1616, Training Accuracy= 0.945
Step 20000, Minibatch Loss= 0.1017, Training Accuracy= 0.992
Optimization Finished!
Testing Accuracy: 0.9765625

在这种情况下,我们在测试数据集上达到了97.6的准确率。

摘要

在本章中,我们通过添加功能来扩展标准神经网络的底层概念,以解决更复杂的问题。首先,我们发现了 CNN 的架构。CNN 是一种 ANN,其隐藏层通常由卷积层、池化层、全连接层和归一化层组成。CNN 的底层概念得到了覆盖。

我们通过分析一个真实案例来理解 CNN 的训练、测试和评估。为此,在 Google Cloud Platform 中解决了一个 HWR 问题。

然后,我们探讨了 RNN。循环神经网络不仅接受当前输入数据作为网络输入,还接受它们在一段时间内经历过的数据。分析了几个 RNN 架构。特别是,我们专注于 LSTM 网络。

第十四章:使用 LSTM 的时间序列

在涉及多个现实生活领域的许多情况下,需要规划未来的行动。预测是有效规划的重要工具。此外,这个工具使决策者对意外事件的影响降低,因为它要求对操作环境的知识采取更科学的方法。通常,未来行动的规划源于对随时间积累的数据的分析,以提取对观察现象特征化的信息。

事件的按时间顺序记录产生了新的行为,这恰好被称为时间序列。时间序列是由在连续瞬间或时间间隔内对现象进行的观察序列组成的。通常,即使不是必然的,它们也是均匀分布的或长度相同。时间序列预测需要神经网络对数据序列有一定的记忆。称为长短期记忆LSTM)的特定架构非常适合时间序列分析。在本章中,我们将展示如何使用 Keras 在 GCD 上创建和训练自己的 LSTM,并将它们应用于预测金融时间序列。我们将发现最常用的建模方法:自回归AR)、移动平均MA)、自回归移动平均ARMA)和自回归积分移动平均ARIMA)。

本章涵盖的主题包括:

  • 时间序列的经典方法

  • 时间序列分解

  • 时间序列模型

  • LSTM 用于时间序列分析

在本章结束时,我们将能够处理与时间序列相关的问题。我们将了解如何识别时间序列的不同组成部分,包括趋势、季节性和残差,以及消除季节性以使预测更容易理解。最后,我们将通过一个实际例子了解如何实现具有重复功能的 LSTM 网络。

介绍时间序列

时间序列是由在连续瞬间或时间间隔内对现象 y 进行的观察序列组成的,这些瞬间或时间间隔通常是均匀分布的,即使不是必然的,长度也相同。商品价格趋势、股票市场指数、BTP/BUND 利差和失业率只是时间序列的几个例子。

与经典统计学中假设独立观察来自单个随机变量相反,在时间序列中,假设有 n 个观察来自许多相关随机变量。因此,时间序列的推断被配置为一个试图将时间序列恢复到其生成过程的程序。

时间序列可以分为两种类型:

  • 确定性:如果变量的值可以根据先前值精确确定

  • 随机性:如果变量的值只能根据先前值部分确定

大多数时间序列都是随机的,因此没有错误地绘制预测是不可能的。通常假设观察到的时序是这两个组成部分组成的。这两个序列不能单独观察,但必须基于样本来确定。

我们将这个序列表示为这两个贡献的总和:

Y[t] = f(t) + w(t)

根据时间序列的经典方法,假设存在现象的时间演化规律,由f(t)表示。随机成分w(t)被假设代表我们不想或不能在Y[t]中考虑的、每个都微不足道的情形集合。

因此,Y[t]的残差部分,即未被f(t)解释的部分,被归因于偶然误差的情况,并同化为一系列偶然误差。这相当于假设随机成分w(t)是由一个白噪声过程生成的,即由一系列独立同分布的随机变量序列,均值为零,方差恒定。总之,在经典方法中,注意力集中在f(t)上,而w(t)被认为是一个具有不相关成分的过程,因此可以忽略不计。

t = 1…. T表示时间,我们将指示这个序列y[t];时间是决定事件序列的参数,不能被忽略,因此我们还需要知道观察在时间维度上的位置。通常,它用于在笛卡尔图上表示值对(t, y[t]),以连续线图的形式,好像现象是连续检测到的。这种图表称为时间序列图。在下面的图表中,我们看到的是从 1871 年到 1970 年阿斯旺尼罗河流量的时间序列图:

时间序列图可以立即揭示趋势或规律性振荡,以及随时间出现的其他系统性趋势。前一个图表显示了长期内系统性地下降的年度数据。特别是,它有一个锯齿状模式;由于数据是按月度计算的,存在称为季节性的现象。可以注意到,在预期有雨的那些月份总是记录到高峰值。

时间序列的单变量分析旨在解释生成序列的动态机制,并预测现象的未来实现。在这些操作中,所利用的信息仅限于(t; Y[t])这对,其中t = 1,…, T。基本点是过去和现在都包含与预测现象未来演化相关的信息。

可以认为单变量分析过于限制;我们通常有与要预测的现象相关的信息,这些信息应该适当融入,以提高修订模型的性能。尽管如此,它是一个有用的基准,允许验证更复杂的替代方案。

在时间序列图中,可以根据时间识别出四种类型的模式:

  • 水平模式:在这种情况下,系列围绕一个常数值(系列平均值)波动。这种系列在平均意义上被称为平稳的。这是在质量控制中,当过程相对于平均值保持控制时出现的典型情况。

  • 季节性模式:当系列受季节性因素影响时存在(例如,每月、半年、季度等)。冰淇淋、软饮料、电力消耗等产品都受到季节性现象的影响。受季节性影响的系列也被称为周期性系列,因为季节周期在固定周期内重复。在年度数据中,季节性不存在。

  • 周期模式:当系列有非固定周期的增加和减少时,这种趋势类型就会出现。这是周期性和季节性波动之间的主要区别。此外,周期性振荡的幅度通常大于季节性引起的幅度。在经济系列中,周期模式由经济因猜测现象的扩张和收缩所决定。

  • 趋势或潜在趋势:它以长期增加或减少的趋势为特征。世界居民人口系列就是一个增加趋势的例子;另一方面,月度啤酒销售系列没有显示出任何趋势。它有一个水平的背景模式。

许多系列强调了这些模式的组合。正是这种复杂性使得预测操作极其有趣。实际上,预测方法必须能够识别系列的各个组成部分,以便在未来的假设下,在它们的进化特征中继续重复过去的模式。

时间序列的经典方法基于将系列的确定性部分分解成一系列信号成分(这些成分表达系列的结构性信息)相对于可忽略的噪声部分。在实践中,我们将尝试在时间序列趋势中识别我们之前列出的一些模式。以下图显示了一个时间序列,其中已识别出一些成分:

在之前的图中,我们已识别的组成部分是:

  • 趋势:这是所考虑现象的潜在趋势,指的是一个长期的时间段。

  • 季节性:这包括现象在一年中的运动。由于气候和社会因素的影响,它们往往在相同时期(例如,月份、季度等)以类似的方式重复出现。

  • 残差:在时间序列模型中,观察变量与不同成分之间从未有过完美的关系。偶然成分考虑了这一点,以及经济主体、社会等方面的不可预测行为。

最后,我们可以说,采用这种方法,时间序列可以被视为分析过的三个成分的总和(加法方法)。

时间序列的经典方法

到目前为止,我们根据经典方法处理时间序列。在这个视角下,试图模拟现象的经典模型可以分为两种类型:

  • 组合模型:已知基本组成部分,通过假设某种聚合形式,得到结果序列

  • 分解模型:从观察序列中假设存在一些基本趋势,我们想要确定其特征

分解模型在实践中最常使用,因此我们将详细分析它们。

时间序列的成分可以根据不同类型的方法进行聚合:

  • 加法方法Y(t) = τ(t) + C(t) + S(t) + r(t)

  • 乘法方法Y(t) = τ(t) * C(t) * S(t) * r(t)

  • 混合方法Y(t) = τ(t) * C(t) + S(t) * r(t)

在这些公式中,因子定义为以下:

  • Y(t) 代表时间序列

  • τ(t) 代表趋势成分

  • C(t) 代表周期成分

  • S(t) 代表季节性成分

  • r(t) 代表残差成分

通过对时间序列成分的对数变换,乘法模型可以追溯到加法模型:

Y(t) = τ(t) * C(t) * S(t) * r(t)

通过对所有因子应用对数函数,这个公式变为:

lnY(t) = lnτ(t) + lnC(t) + lnS(t) + lnr(t)

趋势成分的估计

趋势成分的估计可以有两种不同的模式,这取决于线性/非线性特征。

如果序列趋势在参数上通过对数变换是线性的或可线性化的,那么这些趋势可以通过从线性回归中推导出的程序进行估计。我们可以假设一个可以用以下方程表示的多项式趋势:

τ (t) = α[0] + α[1] t + α[2] t[2] + ... + α[q] t[q] + εt

在这个公式中,q 代表多项式的次数。

根据假设的 q 的值,可以表示以下情况:

q 情况
0 获得一个常数趋势
1 我们得到一个线性趋势
2 我们得到一个抛物线趋势

相反,非线性行为的存在使得难以,如果不是不可能的话,识别出一个已知的函数形式 f(t) 来表达趋势成分。

在这些情况下,使用 MA 工具。MA 是一个移动到每个新迭代(在任何时间 t)从数据序列的开始到结束的算术平均值(简单或加权)。

假设我们有 n 个数据项:

a1, a2, a3, ..., a^((n-1)), a^n

以下程序被采用:

  1. 首先,我们计算前三个数据的平均值,并用平均值替换中间数据

  2. 然后,我们用第二组三个数据重复该程序

  3. 当没有更多数据可用时,程序将耗尽

在考虑的情况下,MA 由仅三个数据组成。MA 的阶数可以扩展到 5,7,9,等等。为了使 MA 与可用数据居中,阶数必须是奇数。

估计季节性成分

研究历史序列的季节性可以有以下目的:

  • 简单地估计季节性成分

  • 一旦估计出来,就从一般课程中消除它

如果必须比较具有不同季节性的几个时间序列,唯一的方法是对它们进行季节性调整。

有几种方法可以估计季节性成分。其中之一是使用二元辅助变量(虚拟变量)的回归模型。

假设存在一个没有趋势成分的加性模型:

Y(t) = S(t) + r(t)

假设我们按月度测量了该序列。虚拟变量可以按以下方式定义:

  • dj:如果观察值 t 是相对于年份的第 j 个月,则为 1

  • dj:否则为 0

一旦创建了周期性虚拟变量,就可以使用以下回归模型来估计季节性成分:

Y(t) = β[1]D[1] + β[2]D[2] + ... + β[n]D[n] + ε(t)

模型中剩余的 ε(t) 部分代表序列中未被季节性解释的部分。如果序列中存在趋势成分,它将与 ε(t) 完全一致。

时间序列模型

在前面的章节中,我们探讨了时间序列的基本原理。为了根据过去发生的事件正确预测未来事件,有必要构建一个适当的数值模拟模型。选择一个合适的模型非常重要,因为它反映了序列的潜在结构。在实践中,有两种类型的模型可用:线性或非线性(取决于序列的当前值是否是过去观察值的线性或非线性函数)。

以下是最广泛用于预测时间序列数据的模型:

  • AR

  • MA

  • ARMA

  • ARIMA

自回归模型

AR 模型是解决与时间序列相关的预测问题的非常有用的工具。一个序列连续值之间的强相关性通常会被观察到。在这种情况下,当我们考虑相邻值时,我们谈论一阶自相关;如果我们指的是两个周期后序列值之间的关系,我们谈论二阶自相关;在一般情况下,如果考虑的值之间有 p 个周期,我们谈论 p 阶自相关。AR 模型允许利用这些联系来获得序列未来行为的有用预测。

AR 是一种线性预测建模技术。该模型试图根据使用 AR 参数作为系数的先前值来预测时间序列。用于预测的样本数量决定了模型的阶数(p)。正如其名称所示,这是一个变量的自回归;也就是说,使用变量的过去值的线性组合来预测未来值。阶数为 p 的 AR 模型定义为:

图片

在前面的公式中,术语定义如下:

  • Y[t] 是时间周期 t 的实际值

  • c 是一个常数

  • ϕ[i] (i = 1,2,..., p) 是模型参数

  • Y[t-i] 是时间周期 t-i 的过去值

  • ε[t] 是时间周期 t 的随机误差(白噪声)

可能会发生常数项被省略的情况;这样做是为了使模型尽可能简单。

移动平均模型

MA 模型规定输出变量线性依赖于随机项(不可完全预测)的过去和当前过去值。MA 模型不应与我们在前几节中看到的 MA 混淆。这是一个本质上不同的概念,尽管有一些相似之处。与 AR 模型不同,完成的 MA 模型总是平稳的。

正如 AR (p) 模型相对于序列的过去值进行回归一样,MA (q) 模型使用过去误差作为解释变量。

阶数为 q 的 MA 模型定义为:

图片

在前面的公式中,术语定义如下:

  • Y[t] 是时间周期 t 的实际值

  • μ 是序列的均值

  • θ[i] (i = 1,2,..., q) 是模型参数

  • ε[t-i] 是时间周期 t-i 的过去随机误差

  • ε[t] 是时间周期 t 的随机误差(白噪声)

MA 模型本质上是对白噪声应用有限脉冲响应滤波器,并对它附加了一些额外的解释。

自回归移动平均模型

ARMA 是一种线性数学模型,它基于先前输入和输出值即时提供输出值。该系统被视为一个实体,它即时接收输入值(输入)并生成输出(输出),基于内部参数,这些参数根据线性规律变化。因此,每个内部参数在每个瞬间等于前一个瞬间的所有内部参数的线性组合和输入值。输出值反过来又将是内部参数的线性组合,在罕见情况下,也可能是输入值。

更简单地说,ARMA 可以看作是 AR 和 MA 模型的有效组合,形成了一类通用且有用的时序模型。

该模型通常定义为 ARMA 模型 (p, q),其中 p 是自回归部分的阶数,q 是移动平均部分的阶数。ARMA 模型由以下公式定义:

图片

术语定义如下:

  • Y[t] 是时间周期 t 的实际值

  • c 仍然是一个常数

  • ϕ[i] (i = 1,2,..., p) 是自回归模型参数

  • Y[t-i] 是时间周期 t-i 的过去值

  • θ[i] (i = 1,2,..., q) 是移动平均模型参数

  • ε[t-i] 是时间周期 t-i 的过去随机误差

  • ε[t] 是时间周期 t 的随机误差(白噪声)

通常,一旦选择了阶数 (p, q),ARMA 模型 (p, q) 的参数可以通过最大似然估计器进行估计,例如。至于 AR 模型,模型阶数的选择必须满足对数据良好适应和参数估计数量简约性的对立需求。

自回归积分移动平均模型

ARIMA 模型是 ARMA 模型的一种推广。ARIMA 模型适用于数据表现出明显的非平稳趋势的情况。在这些情况下,为了消除非平稳性,在 ARMA 算法(对应于模型的积分部分)中添加了一个初始微分步骤,该步骤应用一次或多次。

因此,该算法本质上由三个部分组成:

  • 自回归部分决定了对其自身延迟(即先前)值进行回归的演变变量。

  • MA 部分。它表示回归误差实际上是过去同时发生并在不同时间出现的误差项的线性组合。

  • 积分部分;它表示数据值已被替换为它们当前值与先前值之间的差值(并且这种微分过程可能已经执行过多次)。

每个这些特征的目的都是为了以最佳方式使模型适合数据。

为了制定 ARIMA 模型的代表性方程,我们从 ARMA 模型方程开始:

图片

简单地将 AR 部分移到方程的右侧,得到以下方程(小于常数 c):

通过引入滞后算子 (L),我们可以将这个方程重写如下:

记住:滞后算子 (L) 对时间序列的一个元素进行操作,以产生前一个元素,其含义是 LY[t] = Y[t-1]

假设如下:

这精确地表达了之前为了消除非平稳性而进行的 d 阶因式分解过程。基于这个假设并设置 p = p'-d,我们可以写出以下方程来表示 ARIMA (p,d,q) 模型的数学公式,使用滞后多项式:

d 参数控制着区分的程度。通常情况下,d=1 就足够了。

从时间序列中去除季节性

在经济和金融分析中,这些分析通常基于众多指标,使用季节性调整形式(即净去季节性波动)的数据被广泛使用,以便更清楚地把握所考虑现象的短期演变。

在时间序列的动态中,季节性是每年以固定间隔重复出现的成分,在连续年份的同一时期(月份、季度、学期等)中强度变化或多或少相似;在同年中强度不同。这种类型的典型例子是 8 月份在许多公司假期关闭后工业生产的下降,以及 12 月份由于假日季节零售销售的上升。

季节性波动,伪装其他感兴趣的运动(通常是周期性波动),在分析经济周期时通常被视为一种干扰。季节性的存在会在例如分析历史序列中两个连续时期(月份和季度)之间观察到的变化时造成问题——所谓的经济波动。这些波动通常在很大程度上受到季节性波动的影响,而不是其他原因(例如,经济周期)的影响。另一方面,可以通过在季节性调整后的数据上计算经济波动来正确地突出显示后者。此外,由于每个时间序列都有其特定的季节性特征,使用季节性调整后的数据使得比较不同时间序列的演变成为可能,并且在联合使用不同国家产生的统计数据时得到了广泛应用。

分析时间序列数据集

要了解如何在时间序列上执行季节性移除操作,我们将使用关于月度牛奶产量(每头牛的磅数;1962 年 1 月 – 1975 年 12 月)的数据集。以下是关于此数据集的一些有用信息:

  • 单位:每头牛的磅数

  • 数据集度量:一个时间序列中的 168 个事实值

  • 时间粒度:月份

  • 时间范围:1962 年 1 月 – 1975 年 12 月

  • 来源:时间序列数据库

时间序列数据库TSDL)是由澳大利亚莫纳什大学统计学教授 Rob Hyndman 创建的。

数据存储在一个名为 milk-production-pounds.csv.csv 文件中。首先,让我们看看如何将数据导入 Python,然后如何显示它以识别可能存在的季节性。首先要做的事情是导入我们将要使用的库:

import pandas as pd
import matplotlib.pyplot as plt

在第一行中,我们导入了 pandas 库,在第二行中,我们导入了来自 matplotlib 库的 pyplot 模块。

pandas 是一个开源的 BSD 许可库,为 Python 编程语言提供高性能、易于使用的数据结构和数据分析工具。特别是,它提供了用于操作数值表和时间序列的数据结构和操作。

Matplotlib 是一个 Python 2D 绘图库,可以在各种硬拷贝格式和跨平台的交互式环境中生成出版物质量的图形。Matplotlib 可以用于 Python 脚本、Python 和 IPython Shell、Jupyter Notebook、Web 应用服务器以及四个图形用户界面工具包。matplotlib.pyplot 模块包含允许您快速生成许多类型图表的函数。现在让我们看看如何在 Python 中导入数据集中包含的数据:

data = pd.read_csv('milk-production-pounds.csv',
               parse_dates=True,index_col='DateTime',
               names=['DateTime', 'Milk'], header=None)

要导入数据集,我们使用了 pandas 库的 read_csv 模块。read_csv 方法将数据加载到我们命名为 data 的 Pandas DataFrame 中。为了显示视频导入的 DataFrame 的前五行,我们可以使用 head() 函数如下:

print(data.head())

以下结果返回:

DateTime     Milk
1962-01-01   589
1962-02-01   561
1962-03-01   640
1962-04-01   656
1962-05-01   727

head() 函数不带任何参数时,从 DataFrame 中获取前五行数据。现在时间序列已在我们的 Python 环境中可用;为了预览其中包含的数据,我们可以计算一系列基本统计量。为此,我们将使用以下方式的 describe() 函数:

print(data.describe())

以下结果返回:

 Milk
count  168.000000
mean   754.708333
std    102.204524
min    553.000000
25%    677.750000
50%    761.000000
75%    824.500000
max    969.000000

describe() 函数生成描述性统计量,用于总结数据集分布的中心趋势、离散程度和形状,排除 NaN 值。它分析数值和对象序列,以及混合数据类型的 DataFrame 列集。输出将根据提供的内容而变化。为了提取更多信息,我们可以按照以下方式调用 info() 函数:

print(data.info())

以下结果返回:

<class 'pandas.core.frame.DataFrame'>
DatetimeIndex: 168 entries, 1962-01-01 to 1975-12-01
Data columns (total 1 columns):
Milk    168 non-null int64
dtypes: int64(1)
memory usage: 2.6 KB
None

在查看数据集的内容之后,我们将进行初步的视觉探索性分析。Pandas 内置了相对广泛的绘图功能,可用于探索性图表——特别是在数据分析中非常有用。Pandas 的 .plot() 命令本身提供了大量的功能:

data.plot()
plt.show()

data.plot() 命令使用 matplotlib/pylab 绘制 DataFrame 的图表。为了在视频中显示刚刚创建的图表,我们必须使用 plt.show() 函数,如下面的图表所示:

图片

从前一个图表的分析中,我们可以肯定地认识到牛奶产量正在增长(我们注意到一个正向趋势),但同时也表现出一定的可变性(围绕假设趋势线的波动)。这种状态几乎随着时间的推移而持续不变。

识别时间序列中的趋势

如果我们想要尝试预测下一个月的牛奶产量,我们可以这样思考:有了获取到的数据,我们可以追踪趋势线并将其延伸到下一个月。这样,我们就可以对即将到来的未来牛奶产量有一个粗略的估计。

但追踪趋势线意味着追踪回归线。线性回归方法包括精确地确定一条能够代表二维平面上点分布的线。正如容易想象的那样,如果对应于观察点的点靠近这条线,那么选定的模型将能够有效地描述变量之间的联系。在理论上,有无限多条可能近似观察点的线。在实践中,只有一个数学模型能够优化数据的表示。

要拟合线性回归模型,我们首先需要导入两个额外的库:

import numpy
from sklearn.linear_model import LinearRegression

NumPy 是 Python 科学计算的基础包。它包含,但不仅限于:

  • 一个强大的 N 维数组对象

  • 复杂的(广播)功能

  • 用于集成 C/C++ 和 FORTRAN 代码的工具

  • 有用的线性代数、傅里叶变换和随机数功能

除了其明显的科学用途外,NumPy 还可以用作高效的多维通用数据容器。可以定义任意数据类型。这使得 NumPy 能够无缝且快速地与各种数据库集成。

sklearn 是一个用于 Python 编程语言的免费软件机器学习库。它包含各种分类、回归和聚类算法,包括支持向量机、随机森林、梯度提升、k-means 和 DBSCAN,并且设计为与 Python 的数值和科学库 NumPy 和 SciPy 兼容。

记住,要导入 Python 初始分布中不存在的库,你必须使用pip install命令后跟库的名称。这个命令应该只使用一次,而不是每次运行代码时都使用。

我们开始准备数据:

X = [i for i in range(0, len(data))]
X = numpy.reshape(X, (len(X), 1))
y = data.values

首先,我们统计了数据;然后我们使用reshape()函数给一个数组赋予新的形状而不改变其数据。最后,我们将时间序列值插入到y变量中。现在我们可以构建线性回归模型:

LModel = LinearRegression()

LinearRegression()函数执行普通最小二乘线性回归。普通最小二乘法是一种优化技术(或回归),它允许我们找到一个函数,该函数由一个最优曲线(或回归曲线)表示,该曲线尽可能接近一组数据。特别是,找到的函数必须是最小化观测数据与代表该函数本身的曲线之间的距离平方和的函数。

给定观察群体中的 n 个点(x[1], y[1]), (x[2], y[2]), ... (x[n], y[n]),最小二乘回归线定义为方程线:

y=αx+β*

对于以下量是最小的:

这个量表示每个实验数据(x[i], y[i])到对应直线上的点(x[i], αx[i]+β)的距离平方和,如下所示:

现在,我们必须应用fit方法来拟合线性模型:

LModel.fit(X, y)

线性回归模型基本上找到截距和斜率的最佳值,从而得到一条最佳拟合数据的直线。为了查看线性回归算法为我们数据集计算出的截距和斜率的值,执行以下代码:

print(LModel.intercept_,LModel.coef_)

返回以下结果:

[613.37496478] [[1.69261519]]

第一个是截距;第二个是回归线的系数。现在我们已经训练了我们的算法,是时候进行一些预测了。为了做到这一点,我们将使用全部数据并查看我们的算法预测百分比分数的准确性。记住,我们的目标是定位时间序列趋势。要对全部数据进行预测,执行以下代码:

trend = LModel.predict(X)

是时候可视化我们所取得的成果了:

plt.plot(y)
plt.plot(trend)
plt.show()

使用这段代码,我们首先追踪了时间序列。因此,我们添加了代表数据趋势的回归线,最后我们将整个图表打印出来,如下所示:

我们回顾一下,这代表了一种长期单调趋势运动,突出了由于在相同方面系统作用的原因而产生的现象的结构演变。从前图的分析中,我们可以注意到这一点:基于表示时间序列趋势的线条对精确时期牛奶产量进行估计,在某些情况下可能是灾难性的。这是因为季节性高点和低点与回归线的距离很重要。很明显,不能使用这条线来估计牛奶产量。

时间序列分解

时间序列经典分析的一个基本目的是将序列分解为其组成部分,以便更好地研究它们。此外,为了能够将随机方法应用于时间序列,通常几乎总是需要消除趋势和季节性,以获得稳定的过程。正如我们在前面的章节中指定的那样,时间序列的组成部分通常是以下这些:趋势、季节性、周期和残差。

如前所述,它们可以通过加法方式分解:

Y(t) = τ(t) + S(t) + r(t)

它们也可以通过乘法方法分解:

Y(t) = τ(t) * S(t) * r(t)

在接下来的章节中,我们将探讨如何使用这两种方法推导出这些成分。

加法方法

要进行时间序列分解,我们可以使用自动化程序。stats模型库提供了一个名为seasonal_decompose()的函数,实现了朴素或经典分解方法的实现。加法或乘法方法都是可用的。

我们开始导入stats模型库:

from statsmodels.tsa.seasonal import seasonal_decompose

尤其是我们导入了seasonal_decompose模块,使用移动平均(MAs)进行季节分解。我们通过应用加法方法进行分解:

DecompDataAdd = seasonal_decompose(data, model='additive', freq=1)

通过对数据进行卷积滤波器处理,首先移除季节成分。每个周期的平滑序列的平均值即为返回的季节成分。让我们通过识别成分的可视化来看看发生了什么:

DecompDataAdd.plot()
plt.show()

下图显示了加法方法的分解结果:

图片

在此图中,时间序列的三个成分被清晰地表示出来:趋势、季节和残差。这些属性包含在seasonal_decompose()方法返回的对象中。这意味着我们可以使用该对象的内容来从时间序列中去除季节性影响。让我们看看如何:

SeasRemov= data-DecompDataAdd.seasonal

通过这一行代码,我们已经简化了seasonal_decompose()方法从数据返回的季节性属性。此时,我们只需可视化结果:

SeasRemov.plot()
plt.show()

下图显示了去除季节性后的月度牛奶产量(1962 年 1 月至 1975 年 12 月每头牛的磅数):

图片

在获得的图表中,由于季节性而产生的成分已被清楚地去除,而由于趋势而产生的成分则清晰可见。

乘法方法

正如我们所说的,seasonal_decompose()执行加法和乘法分解。要运行乘法方法,只需键入以下命令:

DecompDataMult = seasonal_decompose(data, model='multiplicative')

到目前为止,我们只需可视化结果:

DecompDataMult.plot()
plt.show()

下面的图表显示了乘法方法的分解结果:

在上一幅图中,我们可以注意到从时间序列中提取的趋势和季节性信息似乎相当合理。残差显示出有趣的变异;在时间序列的早期和后期,高变异性时期被清楚地识别出来。

用于时间序列分析的 LSTM

LSTM 是一种由 Hochreiter 和 Schmidhuber 在 1997 年最初构思的循环神经网络特定架构。这种类型的神经网络最近在深度学习背景下被重新发现,因为它摆脱了梯度消失的问题,并且在实践中提供了出色的结果和性能。

基于 LSTM 的网络非常适合时间序列的预测和分类,并且正在取代许多经典的机器学习方法。这是因为 LSTM 网络能够考虑数据之间的长期依赖关系,在语音识别的情况下,这意味着管理句子中的上下文以提高识别能力。

时间序列数据集概述

来自美国国家海洋和大气管理局NOAA)的科学家们从 1965 年到 1980 年在夏威夷莫纳罗亚火山锥顶部(顶部)测量了大气二氧化碳。该数据集覆盖了 317.25 到 341.19 百万分之一ppm)的二氧化碳浓度,并包含 192 个月的记录。以下是关于此数据集的一些有用信息:

  • 单位:ppm

  • 数据集指标:一个时间序列中有 192 个事实值

  • 时间粒度:月

  • 时间范围:1965 年 1 月-1980 年 12 月

来源:TSDL,由澳大利亚莫纳什大学统计学教授 Rob Hyndman 创建。

数据可在名为co2-ppm-mauna-loa-19651980.csv.csv文件中找到。首先,让我们看看如何将数据导入 Python,然后如何显示它们以识别可能存在的季节性。首先要做的事情是导入我们将要使用的库:

import pandas as pd
import matplotlib.pyplot as plt

在第一行中,我们导入了pandas,在第二行中,我们导入了来自matplotlib库的pyplot模块。现在让我们看看如何在 Python 中导入数据集中的数据:

dataset = pd.read_csv(' co2-ppm-mauna-loa-19651980.csv',
               parse_dates=True,index_col='DateTime',
               names=['DateTime', 'CO2'], header=None)

要导入数据集,我们使用了pandas库的read_csv模块。read_csv方法将数据加载到我们命名为dataset的 Pandas DataFrame 中。为了在视频中显示导入的 DataFrame 的前五行,我们可以使用以下head()函数:

print(dataset.head())

返回以下结果:

DateTime    CO2
1965-01-01  319.32
1965-02-01  320.36
1965-03-01  320.82
1965-04-01  322.06
1965-05-01  322.17

head()函数不带参数时,从 DataFrame 中获取前五行数据。现在时间序列已经存在于我们的 Python 环境中;为了预览其中的数据,我们可以计算一系列基本统计信息。为此,我们将使用以下方式的describe()函数:

print(dataset.describe())

返回以下结果:

 CO2
count  192.000000
mean   328.463958
std      5.962682
min    317.250000
25%    323.397500
50%    328.295000
75%    333.095000
max    341.190000

describe()函数生成描述性统计信息,总结数据集分布的中心趋势、离散度和形状,排除 NaN 值。它分析数值和对象序列,以及混合数据类型的 DataFrame 列集。输出将根据提供的内容而变化。为了提取更多信息,我们可以调用info()函数:

print(data.info())

返回以下结果:

<class 'pandas.core.frame.DataFrame'>
DatetimeIndex: 192 entries, 1965-01-01 to 1980-12-01
Data columns (total 1 columns):
CO2    192 non-null float64
dtypes: float64(1)
memory usage: 3.0 KB

在查看数据集的内容之后,我们将进行初步的视觉探索性分析。Pandas 内置了相对广泛的绘图功能,可用于探索性图表;这在数据分析中特别有用。Pandas 的.plot()命令提供了大量的功能:

dataset.plot()
plt.show()

dataset.plot()命令使用matplotlib/pylab绘制 DataFrame 的图表。为了在视频中显示刚刚创建的图表,我们必须使用plt.show()函数,如下面的图表所示:

图片

从前一个图的分析中,我们可以肯定地认识到大气二氧化碳正在增长。我们注意到一个正向趋势。但这也表明了一定的可变性(围绕假设趋势线的振荡),这种可变性几乎随着时间的推移而持续保持。

数据缩放

数据缩放是一种预处理技术,通常在特征选择和分类之前使用。许多基于人工智能的系统使用由许多不同的特征提取算法生成的特征,这些算法来自不同的来源。这些特征可能具有不同的动态范围。

此外,在具有大量特征和较大动态范围的数据挖掘应用中,特征缩放可能会提高拟合模型的性能。然而,选择这些技术是重要的问题,因为对输入数据进行缩放可能会改变数据的结构,从而影响数据挖掘中使用的多元分析结果。

为了对数据进行缩放,我们将使用最小-最大归一化(通常称为特征缩放);它对原始数据进行线性变换。这种技术将所有缩放数据都转换到(0,1)的范围内。实现这一目标的公式是:

图片

最小-最大归一化保留了原始数据值之间的关系。这种有界范围的代价是我们最终会得到较小的标准差,这可能会抑制异常值的影响。

为了执行最小-最大归一化,我们将使用sklearn.preprocessing类的MinMaxScaler()模块。此模块通过将每个特征缩放到给定范围来转换特征。这个估计器将每个特征单独缩放和转换,使其在训练集上位于给定的范围内,即零到一之间。以下代码显示了如何将此模块应用于我们的数据:

scaler = MinMaxScaler()
dataset = scaler.fit_transform(dataset)

首先,我们使用了MinMaxScaler()函数来设置归一化区间(默认为(0,1))。在代码的第二行中,我们应用了fit_transform()函数;它将转换器拟合到数据集,并返回数据的转换版本。这个函数特别有用,因为它存储了使用的转换参数。这些参数在做出预测后,我们将需要将这些数据以初始形式(归一化之前)报告出来,以便与实际数据进行比较。

数据拆分

现在我们来拆分用于训练和测试模型的数据。训练和测试模型是进一步使用模型进行预测分析的基础。给定 192 行数据的数据集,我们将其拆分为一个方便的比例(比如说 70:30),并将 134 行分配给训练,58 行分配给测试。

通常,在基于人工神经网络的算法中,拆分是通过随机选择行来进行的,以减少偏差。对于时间序列数据,值的顺序很重要,因此这个程序不可行。我们可以使用的一个简单方法是按照顺序将数据集分为训练集和测试集。正如我们所预期的,以下代码计算了分割点索引,并在训练数据集中分离数据,其中 70%的观测值用于训练我们的模型;这留下了剩余的 30%用于测试模型:

train_len = int(len(dataset) * 0.70)
test_len  = len(dataset) - train_len
train = dataset[0:train_len,:]
test  = dataset[train_len:len(dataset),:]

代码的前两行设置了两组数据长度。接下来的两行将数据集分为两部分:从第 1 行到train_len -1行用于训练集,从train_len行到最后一行用于测试集。为了确认数据的正确拆分,我们可以打印两个数据集的长度:

print(len(train), len(test))

这给出了以下结果:

134 58

正如我们所预期的,这个操作将数据集分为134行(训练集)和58行(测试集)。

构建模型

我们的目标是利用数据集中的数据来进行预测。具体来说,我们希望根据.csv文件中可用的数据预测空气中二氧化碳的存在。我们需要输入和输出数据来训练和测试我们的网络。很明显,输入由数据集中存在的数据表示。然后我们必须构建我们的输出;我们将通过假设我们想要预测时间t + 1时大气中存在的 CO2 相对于时间t时测量的值来做到这一点。所以我们将有:

输入 = data(t)

输出 = data(t + 1)

我们已经说过,循环网络具有记忆功能,并且通过固定所谓的时间步来维持。时间步与在训练期间计算权重更新梯度的反向传播中回溯的时间步数有关。这样,我们设置 时间步 = 1。然后我们定义一个函数,它接受一个数据集和一个时间步返回输入和输出数据:

def dataset_creating(dataset):
   Xdata, Ydata = [], []
   for i in range(len(dataset)-1):
         Xdata.append(dataset[i, 0])
         Ydata.append(dataset[i + 1, 0])
   return numpy.array(Xdata), numpy.array(Ydata)

在此函数中,Xdata=Input= data(t) 是输入变量,Ydata=output= data(t + 1) 是下一个时间段的预测值。让我们使用这个函数来设置下一阶段(网络建模)中我们将使用的训练和测试数据集:

trainX, trainY = create_dataset(train)
testX, testY = create_dataset(test)

以这种方式,我们创建了网络训练和测试所需的所有数据。此函数将值数组转换为数据集矩阵。现在我们必须准备两个输入数据集(trainXtestX),以符合我们打算使用的机器学习算法(LSTM)所需的形式。为此,有必要深化这一概念。

在一个经典的正向传播网络中,如前几章已分析的,输入包含每个观测变量所假设的值。这意味着输入具有以下形状:

(观测数量,特征数量)

在 LSTM/RNN 网络中,每个 LSTM 层的输入必须包含以下信息:

  • 观测:收集到的观测数量

  • 时间步:样本中的一个观测点

  • 特征:每个步骤一个特征

因此,有必要为那些经典网络预见的添加一个时间维度。因此,输入形状变为:

(观测数量,时间步数,每步特征数量)

以这种方式,每个 LSTM 层的输入变为三维。为了将输入数据集转换为 3D 形式,我们将使用以下 numpy.reshape() 函数:

trainX = numpy.reshape(trainX, (trainX.shape[0], 1, 1))
testX = numpy.reshape(testX, (testX.shape[0], 1, 1))

numpy.reshape() 函数在不改变其数据的情况下,为数组赋予新的形状。所使用的函数参数包括:

  • trainXtestX:需要重塑的数组

  • (trainX.shape[0], 1, 1)(testX.shape[0], 1, 1):新形状

新形状应该与原始形状兼容。在我们的例子中,新形状是 trainX 的 (133,1,1) 和 testX 的 (57,1,1)。现在数据已经以正确的格式,是时候创建模型了:

timesteps = 1
model = Sequential()

我们开始定义时间步;然后我们使用一个顺序模型,即层的线性堆叠。要创建一个顺序模型,我们必须将层实例的列表传递给构造函数。我们也可以通过 .add() 方法简单地添加层:

model.add(LSTM(4, input_shape=(1, timesteps)))
model.add(Dense(1))

第一层是一个 LSTM 层,包含四个 LSTM 块的隐藏层。模型需要知道它应该期望的输入形状。因此,我们向这个层传递了一个 input_shape 参数。在下一行,我们添加了一个实现默认 sigmoid 激活函数的密集层。现在,我们必须为训练配置模型:

model.compile(loss='mean_squared_error', optimizer='adam')

为了做到这一点,我们使用了编译模块。传递的参数是一个损失函数mean_squared_error和随机梯度下降optimizer。最后,我们可以拟合模型:

model.fit(trainX, trainY, epochs=1000, batch_size=1, verbose=2)

在训练阶段,使用trainXtrainY数据,共 1,000 个 epoch(在训练集上的完整训练周期)。传递一个批大小为 1(batch_size = 每个梯度更新中的样本数)。最后verbose=2(verbose 参数提供了关于计算机正在做什么的额外细节)打印出每个 epoch 的损失值。

进行预测

我们的模式现在已准备好使用。因此,我们可以用它来执行我们的预测:

trainPred = model.predict(trainX)
testPred = model.predict(testX)

使用了predict()模块,它为输入样本生成输出预测。计算是在批处理中完成的。返回一个预测的 Numpy 数组。之前,当执行数据缩放时,我们使用了fit_transform()函数。正如我们所说的,这个函数特别有用,因为它存储了使用的转换参数。这些参数在做出预测后,当我们必须将数据报告为初始形式(在归一化之前),以便与实际数据进行比较时将是有用的。事实上,现在必须以原始形式报告预测,以便与实际值进行比较:

trainPred = scaler.inverse_transform(trainPred)
trainY = scaler.inverse_transform([trainY])
testPred = scaler.inverse_transform(testPred)
testY = scaler.inverse_transform([testY])

这个代码块仅用于取消归一化的影响,并将数据集恢复到初始形式。为了估计算法的性能,我们将计算均方根误差:

trainScore = math.sqrt(mean_squared_error(trainY[0], trainPred[:,0]))
print('Train Score: %.2f RMSE' % (trainScore))
testScore = math.sqrt(mean_squared_error(testY[0], testPred[:,0]))
print('Test Score: %.2f RMSE' % (testScore))

均方根误差RMSE)衡量两个数据集之间的误差程度。换句话说,它比较了一个预测值和一个观察值。

返回以下结果:

Train Score: 1.12 RMSE
Test Score: 1.35 RMSE

在评估了方法性能之后,我们现在可以通过绘制适当的图表来可视化结果。为了正确显示时间序列,需要进行预测偏移。这个操作必须在训练集和测试集上执行:

trainPredPlot = numpy.empty_like(dataset)
trainPredPlot[:,:] = numpy.nan
trainPredPlot[1:len(trainPred)+1,:] = trainPred

然后在测试集上执行相同的操作:

testPredPlot = numpy.empty_like(dataset)
testPredPlot[:,:] = numpy.nan
testPredPlot[len(trainPred)+2:len(dataset),:] = testPred

最后,我们必须绘制实际数据和预测:

plt.plot(scaler.inverse_transform(dataset))
plt.plot(trainPredPlot)
plt.plot(testPredPlot)
plt.show()

在以下图表中显示了实际数据和预测:

图片

从前一个图表的分析中,我们可以看到 RMSE 报告的内容得到了图表的证实。事实上,我们可以看到模型在训练集和测试集的拟合方面做得非常出色。

摘要

在本章中,我们探讨了时间序列数据。时间序列构成了一系列现象的观察序列。在一个时间序列中,我们可以识别出几个组成部分:趋势、季节性、周期和残差。我们通过一个实际例子学习了如何从一个时间序列中去除季节性。

然后讨论了表示时间序列的最常用的模型:AR、MA、ARMA 和 ARIMA。对于每一个,我们分析了基本概念,然后提供了模型的数学公式。

最后,提出了一种用于时间序列分析的长短期记忆(LSTM)模型。通过一个实际例子,我们可以看到如何使用 LSTM 类型的循环神经网络模型来处理时间序列回归问题。

第十五章:强化学习

现在,大多数计算机都是基于符号化处理。问题是首先编码在一系列变量中,然后使用一个显式算法进行处理,该算法为问题的每个可能输入提供适当的输出。然而,有些问题通过显式算法的解决既低效甚至不自然,例如,语音识别器;用经典方法处理这类问题是不高效的。这类问题和其他类似问题,如机器人的自主导航或操作中的语音助手,是可以通过基于强化学习的解决方案直接解决的一个非常多样化的问题集。

强化学习基于心理学理论,该理论是在对动物进行一系列实验之后详细阐述的。定义一个要实现的目标,强化学习试图最大化执行动作或动作集以使我们达到指定目标所获得的奖励。强化学习是机器学习的一个非常激动人心的领域,它被用于从自动驾驶汽车到视频游戏的各种应用中。它的目标是创建能够学习和适应环境变化的算法。

本章涵盖的主题包括:

  • 强化学习

  • 马尔可夫决策过程MDP

  • Q 学习

  • 时间差分TD)学习

  • 深度 Q 学习网络

在本章结束时,你将全面了解强化学习的力量,并学习这种技术的不同方法。将涵盖几种强化学习方法。

强化学习简介

强化学习的目标是创建能够学习和适应环境变化的算法。这种编程技术基于根据算法选择接收外部刺激的概念。正确的选择将涉及奖励,而错误的选择将导致惩罚。当然,系统的目标是实现最佳可能的结果。

在监督学习中,有一个教师告诉系统哪个是正确的输出(有教师的学习)。这并不总是可能的。通常我们只有定性信息(有时是二元的,对/错,或成功/失败)。可用的信息被称为强化信号。但系统不会提供任何关于如何更新代理行为(即权重)的信息。你不能定义一个成本函数或梯度。系统的目标是创建能够从经验中学习的智能代理。

下图是一个流程图,显示了强化学习与环境之间的交互:

图片

科学文献对强化学习的分类持不确定的态度,将其视为一种范例。事实上,在最初阶段,它被视为监督学习的特殊情况,然后被完全提升为机器学习算法的第三范式。它在监督学习效率低下的不同环境中得到应用;与环境交互的问题是很明显的例子。

以下流程显示了正确应用强化学习算法的步骤:

  1. 代理的准备

  2. 监测环境

  3. 选择最佳策略

  4. 执行动作

  5. 计算相应的奖励(或惩罚)

  6. 更新策略的开发(如果需要)

  7. 重复步骤 2-5,直到代理学会最佳策略

强化学习基于一些心理学理论,这些理论是在一系列对动物进行的实验之后详细阐述的。特别是,美国心理学家爱德华·桑代克指出,如果一只猫在执行被认为正确的行为后立即得到奖励,那么这种行为再次发生的概率会增加。而面对不受欢迎的行为时,应用惩罚会降低错误重复的概率。

在此理论的基础上,并定义了一个要实现的目标,强化学习试图最大化执行动作或动作集以实现指定目标的奖励。

代理-环境接口

强化学习可以看作是达到目标交互问题的特殊情况。必须达到目标的是被称为代理的实体。与代理必须交互的实体被称为环境,它对应于代理外部的一切。

到目前为止,我们更多地关注了“代理”这个术语,但它究竟代表了什么?代理是一种代表其他程序执行服务的软件实体,通常自动且无形。这些软件也被称为智能代理

以下是一个代理最重要的特征:

  • 它可以在连续集和离散集之间选择对环境采取的行动

  • 行动取决于情况。情况总结在系统状态中

  • 代理持续监控环境(输入)并持续改变状态

  • 行动选择并非易事,需要一定程度的智能

  • 代理具有智能记忆

代理具有目标导向的行为,但在一个事先未知或部分已知的不确定环境中行动。代理通过与环境的交互来学习。在通过代理自身进行的测量中了解环境的同时,可以制定计划。策略接近试错理论。

尝试和错误是解决问题的基本方法。它以重复、变化的尝试为特征,直到成功,或者直到智能体停止尝试。

智能体-环境交互是连续的;智能体选择一个要采取的动作,然后环境改变状态,呈现一个新的情况来面对。

在强化学习的特定情况下,环境为智能体提供奖励;确保奖励来源是环境,以避免在智能体内部形成会损害学习的个人强化机制。

奖励的价值与动作在达到目标时产生的影响成比例;因此,在正确动作的情况下是正的或高的,在错误动作的情况下是负的或低的。

以下是一些现实生活中智能体和环境之间交互以解决问题的例子:

  • 国际象棋选手,对于每一个移动,都有关于可以创造和对手可能的反击动作的棋子配置的信息

  • 一只小长颈鹿在几小时内学会以 50 公里/小时的速度站起来和奔跑

  • 一个真正自主的机器人学会在房间里移动以走出房间

  • 炼油厂的参数(如油压、流量等)在实时设置,以便获得最大产量或最大质量

我们所分析的所有例子都具有以下共同特征:

  • 与环境的交互

  • 智能体的目标

  • 环境的不确定性或部分知识

从这些例子的分析中,可以得出以下观察:

  • 智能体从自己的经验中学习。

  • 当动作改变状态(情况)时,未来选择的可能性会改变(延迟奖励)。

  • 动作的效果不能完全预测。

  • 智能体对其行为的整体评估。

  • 它必须利用这些信息来改善其选择。选择随着经验而提高。

  • 问题可以有一个有限或无限的时间范围。

马尔可夫决策过程

为了避免负载问题和计算困难,智能体-环境交互被视为一个 MDP。MDP 是一个离散时间随机控制过程。在每一个时间步,过程处于一个状态 s,决策者可以选择在状态 s 中可用的任何动作 a。在下一个时间步,过程通过随机移动到新的状态 s' 并给决策者一个相应的奖励,r(s,s') 来响应。

在这些假设下,智能体-环境交互可以概括如下:

  • 智能体和环境在时间上的离散间隔内进行交互,t = 0, 1, 2, … n

  • 在每个间隔,智能体接收环境状态 st 的表示。

  • S 集合中每个元素 s[t],其中 S 是可能状态集合。

  • 一旦识别出状态,智能体必须采取 A(s[t]) 中的行动 a[t],其中 A(s[t]) 是状态 s[t] 中的可能行动集合。

  • 要采取的行动的选择取决于要实现的目标,并通过带有符号 π(折现累积奖励)的策略来映射,该策略将行动与 A(s) 中的 a[t] 相关联,对于每个状态 s。术语 πt 表示在状态 s 中执行行动 a 的概率。

  • 在下一个时间间隔 t + 1 中,作为行动后果的一部分,智能体收到一个数值奖励 r[t] + 1 R,对应于之前采取的行动 a[t]

  • 行动的后果代表的是新的状态 s[t]。在这个时候,智能体必须再次编码状态并选择行动。

  • 这种迭代会一直重复,直到智能体达到目标。

状态 s[t] + 1 的定义取决于之前的状态和采取的行动 MDP,即:

s[t] + 1 = δ (s[t],a[t])

这里 δ 代表状态函数。

总结来说:

  • 在一个 MDP 中,智能体可以感知到他所处的状态 s S,并且有一个 A 集合的行动可供选择。

  • 在每个离散的时间间隔 t 中,智能体检测到当前状态 st 并决定在 A 实施行动。

  • 环境通过提供奖励(强化)r[t] = r (s[t], a[t]) 并移动到状态 s[t] + 1 = δ (s[t], a[t]) 来做出反应。

  • rδ 函数是环境的一部分;它们只依赖于当前状态和行动(不是之前的),并且不一定为智能体所知。

  • 强化学习的目标是学习一个策略,对于系统所处的每个状态 s,指示智能体采取一个行动以最大化在整个行动序列中收到的总强化。

让我们深入探讨一些使用的术语:

  • 奖励函数定义了强化学习问题中的目标。它将环境检测到的状态映射到一个单一的数字,从而定义了一个奖励。如前所述,唯一的目标是在长期内最大化它所收到的总奖励。奖励函数定义了对于智能体来说什么是好事和坏事。奖励函数需要是正确的,并且可以用作改变策略的基础。例如,如果策略选择的行动导致低奖励,策略可以在下一步改变以选择其他行动。

  • 策略定义了学习智能体在给定时间的行为。它将环境检测到的状态和在这些状态下采取的行动进行映射。对应于心理学中所谓的规则集或刺激反应的关联。策略是强化学习智能体的基本部分,因为它单独就足以确定行为。

  • 价值函数表示一个状态对智能体有多好。它等于智能体从状态 s 预期得到的总奖励。价值函数取决于智能体选择执行的动作的策略。

折扣累积奖励

在上一节中,我们提到这一点:强化学习的目标是学习一个策略,对于系统所处的每个状态 s,指示智能体一个动作,以在整个动作序列中最大化收到的总强化。但如何最大化整个动作序列中收到的总强化呢?

根据策略计算得到的总强化如下:

图片

在这里,r[T] 表示将环境驱动到终端状态 s[T] 的动作的奖励。

解决这个问题的可能方法是将提供最高奖励的动作与每个个体状态关联起来;也就是说,我们必须确定一个最优策略,使得之前的数量最大化。

对于在有限步数内无法达到目标或终端状态的问题(持续任务),R[t] 趋向于无穷大。

在这些情况下,想要最大化的奖励总和在无穷大处发散,因此这种方法不适用。那么,就需要开发一种替代的强化技术。

适合强化学习范式的技术最终证明是折扣累积奖励,它试图最大化以下数量:

图片

这里,γ 被称为折扣因子,它表示对未来奖励的重要性。此参数可以取值 0 ≤ γ ≤ 1,具有以下含义:

  • 如果 γ < 1,序列 r[t] 将收敛到一个有限值。

  • 如果 γ = 0,智能体将不会对未来的奖励感兴趣,但会尝试仅最大化当前状态的奖励。

  • 如果 γ = 1,智能体将试图增加未来的奖励,即使是以牺牲当前的奖励为代价。

折扣因子可以在学习过程中修改,以突出或忽略特定的动作或状态。一个最优策略可以使执行单个动作获得的强化甚至很低(或甚至为负),只要这总体上导致更大的强化。

探索与利用

理想情况下,智能体必须将每个动作与相应的奖励 r 关联起来,然后选择最被奖励的行为以实现目标。因此,这种方法对于复杂问题来说不切实际,在这些问题中,状态的数量特别高,因此可能的关联呈指数增长。

这个问题被称为探索-利用困境。理想情况下,智能体必须探索每个状态的所有可能动作,找到实际最被奖励的动作,以利用它来实现其目标。

因此,决策涉及一个基本的选择:

  • 利用:根据当前信息做出最佳决策

  • 探索:收集更多信息

在这个过程中,最佳长期策略可能会在短期内带来相当大的牺牲。因此,有必要收集足够的信息来做出最佳决策。

这里有一些采用这种技术解决现实生活案例的例子:

选择商店

  • 利用:前往你最喜欢的商店

  • 探索:尝试一个新的商店

选择路线

  • 利用:选择迄今为止的最佳路线

  • 探索:尝试一条新路线

在实践中,对于非常复杂的问题,收敛到非常好的策略会太慢。

解决这个问题的好方法是在探索和利用之间找到平衡:

  • 一个仅限于探索的代理将始终在每个状态下以随意的方式行事,显然收敛到最优策略是不可能的

  • 如果一个代理很少探索,它将始终使用常规动作,这些动作可能不是最优的

强化学习技术

正如我们在前几节中看到的,强化学习是一种编程哲学,旨在开发能够学习和适应环境变化的算法。这种编程技术基于能够根据算法的选择从外部接收刺激的假设。因此,正确的选择将带来奖励,而错误的选择将导致系统的惩罚。系统的目标是实现尽可能高的奖励,从而获得最佳结果。与强化学习相关的技术分为两大类:

  • 连续学习算法:这些技术从假设有一个简单的机制来评估算法的选择,然后根据结果奖励或惩罚算法。这些技术也可以适应环境中的重大变化。例如,语音识别程序或 OCR 程序在使用过程中提高其性能。

  • 预防性训练算法:这些算法从观察出发,认为不断评估算法的行为可能是一个无法自动化或非常昂贵的流程。在这种情况下,应用一个第一阶段,在这个阶段中,算法被教授;当系统被认为可靠时,它被固定下来,就不再可编辑。许多电子组件在其内部使用神经网络,这些网络的突触权重是不可变的,因为它们在电路构建过程中是固定的。

应该注意的是,前面提到的类别是实现选择而不是算法概念上的差异。因此,一个算法可以根据设计者的实现方式经常位于第一类或第二类。

Q 学习

Q-learning 是最常用的强化学习算法之一。这得益于它能够在不要求环境模型的情况下比较可用动作的预期效用。

通过这项技术,我们可以找到在完成 MDP 中每个给定状态的优化动作。

强化学习问题的一般解决方案是,通过学习过程估计一个评估函数。这个函数必须能够通过奖励的总和评估特定策略的便利性或不利性。实际上,Q-learning 试图最大化 Q 函数(动作值函数)的值,它代表我们在状态 s 执行动作 a 时的最大折现未来奖励,如下所示:

Q(S[t],a[t]) = max(R[t+1])

知道 Q 函数,状态 s 中的最佳动作 a 是具有最高 Q 值的动作。在这个点上,我们可以定义一个策略 π(s),它为我们提供任何状态下的最佳动作。回忆一下,策略 π(s; a) 对与在状态 s 中执行动作的概率 (s; a) 相关联,我们可以写出以下内容:

问题被简化为评估 Q 函数。然后我们可以通过递归过程,用下一个点的 Q 函数来估计过渡点的 Q 函数。以下是在过程的单步中使用的方程。这个方程被称为 贝尔曼方程,代表了 Q-learning 的转换规则:

术语定义如下:

  • Q(s[t],a[t]) 是从状态 s 出发的动作 a 的当前策略。

  • r 是动作的奖励。

  • maxt+1) 定义了最大未来奖励。我们执行了 a[t] 动作,从状态 s[t] 到达状态 s[t+1]。从这里,我们可能有多个动作,每个动作对应一些奖励。计算这些奖励中的最大值。

  • γ 是折扣因子。γ 的值在 0 到 1 之间变化;如果值接近 0,则优先考虑即时奖励。如果它接近 1,则未来奖励的重要性增加,直到 1,此时它被认为与即时奖励相等。

在前面的方程的基础上,评估函数 Q 是即时奖励和从下一个状态开始可获得的最高奖励的总和。

应用前面的公式,我们试图将延迟奖励转化为即时奖励。我们之前提到,Q 函数的评估代表一个递归过程。然后我们可以将这个过程中获得的价值输入到一个表格中,当然,我们将这个表格称为 Q 表。在这个表格中,行是状态,列是动作。作为一个起始的 Q 表,我们可以使用一个包含所有零的矩阵(我们已经初始化了 Q 表),如下面的图所示:

此表 Q 的元素(单元格)是在给定行状态和列动作的情况下获得的奖励。在任何状态下采取的最佳动作是具有最高奖励的动作。我们的任务是使用新值更新此表。为此,我们可以采用以下算法:

  1. 解码状态 s[t]

  2. 选择动作 a[t]

  3. 执行动作 a[t] 并获得奖励 r

  4. Q(s[t]; a[t]) 的元素根据 Bellman 方程提供的训练规则进行更新。

  5. 执行动作 a 将环境状态移动到 s[t+1]

  6. 将下一个状态设置为当前状态 (s[t] = s[t+1])

  7. 从点 1 重新开始并重复该过程,直到达到终端状态。

在更复杂和高效的公式中,可以用神经网络代替迭代效率仍然不高的表,学习过程将改变突触连接的权重。

时间差分学习

TD 学习算法基于减少代理在不同时间做出的估计之间的差异。在前一节中看到的 Q-learning 是 TD 算法,但它基于相邻瞬间状态之间的差异。TD 更通用,可能考虑更远的时间和状态。

它是蒙特卡洛MC)方法和动态规划DP)思想的结合。

MC 方法允许基于获得结果的平均值解决强化学习问题。

DP 代表一组算法,可以在给定环境完美模型(以 MDP 形式)的情况下计算最优策略。

TD 算法可以直接从原始数据中学习,而无需环境动态模型(如 MC)。该算法基于先前学习的估计部分更新估计,而不需要等待最终结果(如 DP 中的自举)。它适用于没有动态环境模型的场景。如果时间步长足够小,或者随着时间的推移而减少,则使用固定策略收敛。

如前节所述,Q-learning 根据以下公式计算其值:

图片

通过采用一步前瞻。

前瞻是尝试预测选择一个分支变量以评估其值的影响的通用术语。前瞻的两个主要目标是选择下一个要评估的变量以及分配给它的值的顺序。

很明显,也可以使用两步公式,如下所示:

图片

更一般地,使用 n 步前瞻,我们得到以下公式:

图片

动态规划

DP 代表一组算法,可以用来在给定环境完美模型(以 MDP 形式)的情况下计算最优策略。DP 的基本思想,以及一般强化学习,是使用状态值和动作来寻找好的策略。

DP 方法通过迭代两个称为政策评估政策改进的过程来接近解决马尔可夫决策过程。

  • 政策评估算法在于将迭代方法应用于 Bellman 方程的求解。由于只有当k → ∞时我们才能保证收敛,我们必须满足于通过设置停止条件来获得良好的近似值。

  • 政策改进算法基于当前值来改进策略。

政策迭代算法的一个缺点是我们必须在每一步评估策略。这涉及一个迭代过程,其收敛时间我们事先不知道。这将取决于许多其他因素,包括起始策略的选择。

克服这种缺点的一种方法是在特定步骤中断策略的评估。这种操作不会改变收敛到最优值的保证。一种特殊情况是,通过状态(也称为sweep)的步骤阻止策略评估,定义了值迭代算法。在值迭代算法中,在策略改进的每一步之间执行一次值的计算迭代。

因此,DP 算法基本上基于两个并行发生的过程:政策评估和政策改进。这两个过程的重复执行使整个过程收敛到最优解。在策略迭代算法中,这两个阶段交替进行,每个阶段都在另一个开始之前结束。

DP 方法在整个环境可能假设的状态集中操作,在每个迭代中对每个状态进行完全备份。备份操作中执行的每次更新操作都是基于所有可能的后继状态值,这些值根据它们发生的概率加权,并受到选择策略和环境动态的影响。完全备份与 Bellman 方程密切相关;它们不过是将方程转换为赋值指令。

当完整的备份迭代不会对状态值带来任何变化时,就达到了收敛;因此,最终状态值完全满足 Bellman 方程。DP 方法仅在存在交替器的完美模型时适用,该模型必须等同于 MDP。

正是因为这个原因,动态规划算法在强化学习中用处不大,这不仅是因为它们假设了一个完美的环境模型,还因为计算成本高且昂贵。但仍然有必要提及它们,因为它们代表了强化学习的理论基础。实际上,所有强化学习方法都试图实现动态规划方法相同的目标,只是计算成本更低,且不假设一个完美的环境模型。

动态规划方法相对于状态数量 n 和动作数量 m 的多项式操作次数收敛到最优解,而基于直接搜索的方法则需要指数操作次数 mn*。

动态规划方法基于后续状态的值估计来更新状态的值估计;或者基于过去的估计来更新。这代表了一种特殊属性,称为自助法。几种强化学习方法执行自助法,甚至是不需要像动态规划方法那样要求完美环境模型的方法。

蒙特卡洛方法

用于估计值函数和发现优秀策略的蒙特卡洛方法不需要环境模型的存在。它们能够仅通过代理的经验或从代理与环境交互中获得的状态序列、动作和奖励的样本来学习。经验可以通过代理在符合学习过程的情况下获得,或者通过预先填充的数据集来模拟。在学习过程中获得经验(在线学习)的可能性很有趣,因为它即使在缺乏对环境动力学先验知识的情况下,也能获得优秀的行为。即使通过已经填充的经验数据集进行学习也可能很有趣,因为如果与在线学习相结合,它使得通过他人的经验引起的自动策略改进成为可能。

为了解决强化学习问题,蒙特卡洛方法基于过去回合中获得的总奖励的平均值来估计值函数。这假设经验被划分为回合,并且所有回合都由有限数量的转换组成。这是因为,在蒙特卡洛方法中,只有当回合完成后,才会进行新值的估计和策略的修改。蒙特卡洛方法迭代地估计策略和值函数。然而,在这种情况下,每个迭代周期相当于完成一个回合——策略和值函数的新估计是逐个回合发生的。

通常,MC 术语用于估计方法,其中涉及随机成分的操作;在这种情况下,MC 指的是基于总奖励平均值的强化学习方法。与计算每个状态值的 DP 方法不同,MC 方法计算每个状态-动作对的值,因为在没有模型的情况下,只有状态值不足以决定在某个状态下执行哪种动作最好。

深度 Q 网络

深度 Q 网络DQN)算法结合了强化学习和深度学习的方法。DQN 通过自我学习,以经验方式学习,而不需要针对特定目标(如赢得棋类游戏)的严格编程。

DQN 代表了一种使用深度学习来近似评估函数的 Q 学习的应用。DQN 是由 Mnih 等人于 2015 年 2 月 26 日在《自然》杂志上发表的文章中提出的。因此,许多研究机构加入了这个领域,因为深度神经网络可以使强化学习算法能够直接处理高维状态。

深度神经网络的使用是由于研究人员注意到了以下事实:使用神经网络来近似强化学习算法中的Q 评估函数使得系统不稳定或发散。实际上,可以注意到对Q的小更新可以显著改变策略、数据分布以及Q和目标值之间的相关性。这些相关性存在于观察序列中,是算法不稳定的原因。

要将一个普通的 Q 网络转换为 DQN,需要采取以下预防措施:

  • 用多层卷积网络替换单层神经网络来近似 Q 函数评估

  • 实现经验回放

  • 在更新期间使用第二个网络来计算目标 Q 值

“经验回放”这个术语是什么意思?这意味着,不是在模拟或实际经验中发生时运行 Q 学习在状态/动作对上,系统会存储发现的数据,通常在一个大表中。这样,我们的网络可以使用存储的经验记忆来自我训练。

OpenAI Gym

OpenAI Gym是一个帮助我们实现基于强化学习算法的库。它包括一个不断增长的基准问题集合,这些问题提供了一个公共接口,以及一个网站,人们可以在那里分享他们的结果并比较算法性能。

OpenAI Gym 专注于强化学习的场景设置。换句话说,代理的经验被划分为一系列的场景。代理的初始状态由一个分布随机采样,直到环境达到终端状态,交互过程才继续。这个程序为每个场景重复进行,目的是最大化每个场景的总奖励期望,并在尽可能少的场景中实现高水平的表现。

Gym 是开发比较强化学习算法的工具包。它支持教代理从走路到玩像 Pong 或弹球这样的游戏的一切。该库可在以下 URL 获取:

gym.openai.com/

下图显示了 OpenAI Gym 项目网站的首页:

图片

OpenAI Gym 是更大胆的项目——OpenAI 项目的一部分。OpenAI 是由埃隆·马斯克和山姆·奥特曼共同创立的人工智能研究公司。这是一个非营利项目,旨在以造福全人类的方式促进和开发友好的人工智能。该组织旨在通过使他们的专利和研究对公众开放,自由地与其他机构和研究人员合作。创始人决定承担这个项目,因为他们对来自人工智能无差别使用的存在风险感到担忧。

OpenAI Gym 是一个程序库,允许你开发人工智能,衡量它们的智力能力,并增强它们的学习能力。简而言之,这是一个以算法形式存在的 Gym,它训练当前的数字大脑,并将它们投射到 OpenAI Gym 项目中。

但还有一个目标。OpenAI 希望通过资助那些即使在经济上没有回报也能让人类进步的项目来刺激人工智能领域的研究。另一方面,Gym 旨在标准化人工智能的测量,以便研究人员可以在平等的基础上竞争,了解他们的同事已经取得了哪些进展,但最重要的是,关注真正对所有人都有用的结果。

可用的工具很多。从玩像 Pong 这样的老式视频游戏到在围棋中战斗,再到控制机器人,我们只需将我们的算法输入这个数字空间,看看它是如何工作的。第二步是将获得的基准与其他基准进行比较,看看我们与其他人的差距,也许我们可以与他们合作,实现互利共赢。

OpenAI Gym 对我们的代理结构没有假设,并且与任何数值计算库兼容,例如 TensorFlow 或 Theano。Gym 库是我们可以使用来测试我们的强化学习算法的测试问题——环境。这些环境有一个共享的接口,允许你编写通用算法。

要安装 OpenAI Gym,请确保您之前已安装了 Python 3.5+ 版本;然后只需输入以下命令:

pip install gym

完成此操作后,我们将能够以简单直接的方式插入库提供的工具。

Cart-Pole 系统

Cart-Pole 系统 是强化学习的一个经典问题。该系统由一个杆(类似于倒立摆)通过一个关节连接到车上,如图所示:

该系统通过向车上施加 +1 或 -1 的力来控制。可以控制施加到车上的力,目标是使杆向上摆动并稳定它。这必须在不让车掉到地面上完成。在每一步,智能体可以选择将车向左或向右移动,并且每当杆平衡时,它都会收到 1 的奖励。如果杆偏离垂直方向超过 15 度,则程序结束。

要使用 OpenAI Gym 库运行 Cart-Pole 示例,只需输入以下代码:

import gym
env = gym.make('CartPole-v0')
env.reset()
for i in range(1000):
    env.render()
    env.step(env.action_space.sample())

和往常一样,我们将详细解释每一行代码的含义。第一行用于导入 gym 库:

import gym

然后我们继续通过调用 make 方法创建环境:

env = gym.make('CartPole-v0')

此方法创建我们的智能体将运行的虚拟环境。环境是一个具有最小接口的问题,智能体可以与之交互。OpenAI Gym 中的环境设计是为了允许对智能体能力的客观测试和基准测试。Gym 库附带了一系列从简单到困难、涉及多种不同类型数据的环境。

要获取可用环境的列表,请参阅以下链接:

gym.openai.com/envs

最常用的环境列在这里:

  • 经典控制和玩具文本:完成小规模任务,主要来自强化学习文献。它们在这里是为了让您开始。

  • 算法:执行加多位数和反转序列等计算。

  • Atari:玩经典 Atari 游戏。

  • 2D 和 3D 机器人:在模拟中控制机器人。

在我们的案例中,我们已将 CartPole-v0 环境命名为。make 方法返回一个 env 对象,我们将使用它来与游戏交互。但让我们回到分析代码。现在我们必须使用 reset() 方法初始化系统:

env.reset()

此方法将环境置于其初始状态,返回描述它的数组。在此阶段,我们将使用 for 循环运行 CartPole-v0 环境的实例 1000 次时间步,并在每一步渲染环境:

for i in range(1000):
    env.render()
    env.step(env.action_space.sample())

调用 render() 方法将可视化显示当前状态,而后续对 env.step() 的调用将允许我们与环境交互,并返回对调用它的动作的响应的新状态。

这样,我们在每一步都采用了随机动作。在这个时候,了解我们对环境所采取的动作以决定未来的动作是非常有用的。step() 方法正是返回这个信息。实际上,这个方法返回以下四个值:

  • observation (object): 代表你对环境观察的环境特定对象。

  • reward (float): 上一个动作获得的奖励量。这个量在环境中变化,但目标始终是增加你的总奖励。

  • done (boolean): 是否是时候重新设置环境了。大多数(但不是所有)任务被划分为定义良好的剧集,doneTrue 表示剧集已结束。

  • info (dict): 用于调试的诊断信息。有时它对学习很有用。

要运行这个简单的示例,将代码保存到名为 cart.py 的文件中,并在 bash 窗口中输入以下命令:

python cart.py

这样,就会显示一个包含我们系统(不稳定且很快会超出屏幕)的窗口。这是因为对小车施加的推力是随机的,没有考虑到杆的位置。

为了解决问题,即平衡杆,因此必须将推力设置在杆倾斜的相反方向。所以,我们只需要设置两种动作,-1 或 +1,将小车推向左边或右边。但为了做到这一点,我们需要随时了解来自环境观察的数据。正如我们之前所说的,这些数据是由 step() 方法返回的,特别是它们包含在观察对象中。

此对象包含以下参数:

  • 小车位置

  • 小车速度

  • 杆的角度

  • 杆尖的速度

这四个值成为我们问题的输入。正如我们之前预料的,通过向小车施加推力来平衡系统。有两种可能的选择:

  • 向左推小车(0)

  • 向右推(1)

很明显,这是一个二元分类问题:四个输入和一个单一的二元输出。

让我们首先考虑如何提取作为输入的值。为了提取这些参数,我们只需更改前面提出的代码:

import gym
env = gym.make('CartPole-v0')
observation = env.reset()
for i in range(1000):
    env.render()
    print(observation)
    observation, reward, done, info = env.step(env.action_space.sample())

通过运行代码,我们可以看到观察对象中包含的值现在被打印在屏幕上。所有这些很快就会变得有用。

使用从环境观察返回的值,智能体必须决定采取两种可能动作之一:将小车向左或向右移动。

学习阶段

现在,我们必须面对最具挑战性的阶段,即我们系统的训练。在前一节中,我们提到 Gym 库专注于强化学习的周期性设置。智能体的经验被划分为一系列的周期。智能体的初始状态由一个分布随机采样,交互过程一直进行到环境达到终端状态。这个程序为每个周期重复进行,目的是最大化每个周期的总奖励期望值,并在尽可能少的周期内达到高水平的表现。

在学习阶段,我们必须估计一个评估函数。这个函数必须能够通过奖励的总和来评估特定策略的便利性或其他方面。换句话说,我们必须近似评估函数。我们如何做?一个解决方案是使用人工神经网络作为函数近似器。

回想一下,神经网络训练的目的是识别神经元之间连接的权重。在这种情况下,我们将为每个周期选择随机的权重值。最后,我们将选择收集到最大奖励的权重组合。

在某一时刻的系统状态由观察对象返回给我们。为了从实际状态中选择一个动作,我们可以使用权重和观察的线性组合。这是函数近似的最重要特殊情况之一,其中近似函数是权重向量 w 的线性函数。对于每个状态 s,都有一个与 w 具有相同数量的分量的实值向量 x(s)。线性方法通过 wx(s) 的内积来近似状态值函数。

以这种方式,我们已经指定了我们打算采用的方法来解决问题。现在,为了使整个训练阶段易于理解,我们报告整个代码块,然后逐行详细注释:

import gym
import numpy as np
env = gym.make('CartPole-v0')
HighReward = 0
BestWeights = None
for i in range(200):
  observation = env.reset()
  Weights = np.random.uniform(-1,1,4)
  SumReward = 0
  for j in range(1000):
    env.render()
    action = 0 if np.matmul(Weights,observation) < 0 else 1
    observation, reward, done, info = env.step(action)
    SumReward += reward
    print( i, j, Weights, observation, action, SumReward, BestWeights)

  if SumReward > HighReward:
    HighReward = SumReward
    BestWeights = Weights

代码的第一部分处理导入库:

import gym
import numpy as np

然后我们继续通过调用 make 方法创建环境:

env = gym.make('CartPole-v0')

这种方法创建了我们的智能体将运行的 环境。现在让我们初始化我们将使用的参数:

HighReward = 0
BestWeights = None

HighReward 将包含到目前为止获得的最高奖励;这个值将用作比较值。BestWeights 将包含将记录最高奖励的权重序列。我们现在可以通过对每个周期的迭代过程实现最佳权重序列搜索:

for i in range(200):

我们决定执行该过程 200 次,因此我们使用 reset() 方法初始化系统:

  observation = env.reset() 

在每个周期中,我们使用与环境的观察数量相等的权重序列,正如之前所说的,这是四个(小车位置、小车速度、杆角度和杆尖端速度):

Weights = np.random.uniform(-1,1,4)

为了固定权重,我们使用了np.random.uniform()函数。此函数从均匀分布中抽取样本。样本在半开区间(低和高)上均匀分布。它包括低但不包括高。

换句话说,在给定的区间内,任何值都有可能被均匀分布抽取。已经传递了三个参数:输出区间的下界,其上界,以及输出形状。在我们的情况下,我们请求在区间(-1,1)内生成四个随机值。完成此操作后,我们初始化奖励的总和:

SumReward = 0

在这一点上,我们实现另一个迭代周期,以确定使用这些权重可以获得的最大奖励:

for j in range(1000):

调用render()方法将可视化显示当前状态:

env.render()

现在,我们必须决定action

action = 0 if np.matmul(Weights,observation) < 0 else 1

正如我们所说的,为了决定动作,我们使用了两个向量的线性组合:weightsobservation。为了执行线性组合,我们使用了np.matmul()函数;它实现了两个数组的矩阵乘积。因此,如果这个乘积是<0,则action是 0(向左移动);否则,action是 1(向右移动)。

应该注意的是,负乘积意味着杆倾斜到左边,因此为了平衡这种趋势,有必要将小车推向左边。正乘积意味着杆倾斜到右边,因此为了平衡这种趋势,有必要将小车推向右边。

现在我们使用step()方法来返回我们调用它时采取的动作对应的新状态。显然,我们传递给方法的动作是我们刚刚决定的:

observation, reward, done, info = env.step(action)

正如我们所说的,此方法返回以下四个值:

  • observation对象):一个特定于环境的对象,代表你对环境的观察。

  • reward浮点数):前一个动作获得的奖励量。在不同的环境中,其比例不同,但目标始终是增加你的总奖励。

  • done布尔值):是否是时候再次重置环境了。大多数(但不是所有)任务被划分为定义良好的剧集,doneTrue表示剧集已结束。

  • info字典):用于调试的诊断信息。有时它对学习很有用。

然后,我们可以使用刚刚获得的奖励更新奖励的总和。记住,对于每次我们保持杆直立的时间步,我们都会获得+1 的reward

SumReward += reward

我们只需打印出在此步骤中获得的价值:

print( i, j, Weights, observation, action, SumReward, BestWeights)

在当前迭代的末尾,我们可以进行比较,以检查获得的总奖励是否是迄今为止获得的最高的:

if SumReward > HighReward:

如果这是迄今为止获得的最大奖励,则使用此值更新HighReward参数:

HighReward = SumReward

一旦完成,将当前步骤的Weights序列固定为最佳序列:

BestWeights = Weights

通过这条指令,训练阶段结束,这将给我们提供最佳逼近评估函数的权重序列。我们现在可以测试系统。

测试阶段

当训练阶段完成时,在实践中这意味着我们已经找到了最佳逼近该函数的权重序列,即返回了可实现的最佳奖励的那个。现在我们必须用这些值测试系统,以检查杆是否能在至少100个时间步内保持平衡。

现在,因为我们已经完成了训练阶段,为了使整个测试阶段易于理解,我们报告整个代码块,然后逐行详细注释:

observation = env.reset()
for j in range(100):
  env.render()
  action = 0 if np.matmul(BestWeights,observation) < 0 else 1
  observation, reward, done, info = env.step(action)
  print( j, action)

首先,我们必须再次使用reset()方法初始化系统:

observation = env.reset() 

然后,我们必须运行一个迭代周期来应用训练阶段获得的结果:

for j in range(100):

对于每一步,我们将调用render()方法来可视化显示当前状态:

env.render()

现在,我们必须根据训练阶段获得的最佳权重和当前状态下的观察结果来决定对系统执行的动作:

action = 0 if np.matmul(BestWeights,observation) < 0 else 1

现在我们使用返回对所调用动作的新状态的step()方法。传递给方法的行为是我们刚刚决定的:

observation, reward, done, info = env.step(action)

最后,我们打印出步数和决定执行的动作,以便进行流程的可视化控制。

通过运行提出的代码,我们可以验证在训练阶段之后,系统能够在 100 个时间步内保持杆的平衡。

摘要

强化学习旨在创建能够学习和适应环境变化的算法。这种编程技术基于根据算法选择接收外部刺激的概念。正确的选择将涉及奖励,而错误的选择将导致惩罚。系统的目标是实现最佳可能的结果。在本章中,我们讨论了强化学习的基础。

首先,我们了解到,强化学习的目标是创建能够从经验中学习的智能代理。因此,我们分析了正确应用强化学习算法的步骤。后来,我们探讨了代理-环境接口。必须实现目标的是被称为代理的实体。代理必须与之交互的实体被称为环境,它对应于代理之外的一切。

为了避免加载问题和计算困难,将代理-环境交互视为马尔可夫决策过程(MDP)。MDP 是一个随机控制过程。然后引入了折扣因子概念。折扣因子可以在学习过程中修改,以突出或忽略特定的动作或状态。一个最优策略可以使执行单个动作获得的强化甚至很低(或负值),只要这总体上导致更大的强化。

在本章的核心部分,我们专注于分析最常用的强化学习技术。涵盖了 Q 学习、TD 学习和深度 Q 学习网络。最后,我们探讨了 OpenAI Gym 库,并分析了强化学习的一个实际案例。

第十六章:生成神经网络

近年来,神经网络已被用作生成模型:能够复制输入数据的分布,然后能够从这个分布中生成新值的算法。通常,分析图像数据集,并尝试学习与图像像素相关的分布,以产生与原始图像相似的形状。正在进行大量工作,以使神经网络能够创建小说、文章、艺术和音乐。

人工智能AI)研究人员对生成模型感兴趣,因为它们代表了一个跳板,可以构建能够使用世界原始数据并自动提取知识的 AI 系统。这些模型似乎是一种训练计算机理解概念的方法,无需研究人员事先教授这些概念。与当前系统相比,这将是一个巨大的进步,因为当前系统只能从由有能力的自然人准确标记的训练数据中学习。

在本章中,我们将触及生成模型中最激动人心的研究途径之一。首先,我们将介绍无监督学习算法;然后提出生成模型的概述。我们还将发现最常见的生成模型,并展示如何实现一些示例。最后,我们将向读者介绍 Nsynth 数据集和 Google Magenta 项目。

涵盖的主题是:

  • 无监督学习

  • 生成模型介绍

  • 受限玻尔兹曼机

  • 深度玻尔兹曼机

  • 自动编码器

  • 变分自动编码器

  • 生成对抗网络

  • 对抗性自动编码器

在本章结束时,读者将学习如何从神经网络中提取不同类型的内容生成的内容。

无监督学习

无监督学习是一种机器学习技术,它从一系列输入(系统经验)开始,能够根据共同特征重新分类和组织,以尝试对后续输入进行预测。与监督学习不同,在学习过程中,只向学习者提供未标记的示例,因为类别不是事先已知的,而是必须自动学习。

下面的图表显示了从原始数据中标记的三个组:

图表

从这张图中,我们可以注意到系统基于相似性识别了三个组,在这个例子中,这种相似性是由于邻近性。一般来说,无监督学习试图识别数据的内部结构以重现它。

这些算法的典型例子是搜索引擎。这些程序,给定一个或多个关键词,能够创建一个链接列表,这些链接指向搜索算法认为与所进行的研究相关的页面。这些算法的有效性取决于它们可以从数据库中提取的信息的有用性。

无监督学习技术通过比较数据和寻找相似性或差异来工作。众所周知,机器学习算法试图模仿动物神经系统的功能。为此,我们可以假设神经过程是由优化他们追求的未知目标的机制所引导的。每个过程都从与刺激相关联的初始情况发展到终端,其中有一个答案,这是过程本身的结果。直观地讲,在这个过程中,存在信息传递。事实上,刺激提供了获得所需响应所需的信息。因此,在过程完成之前,尽可能忠实地传输这些信息是很重要的。因此,解释神经系统发生的过程的合理标准是,将它们视为信息传递,同时最大限度地保留相同的信息。

无监督学习算法基于这些概念。这是一个使用学习理论技术来衡量在传输过程中发生的信息损失的问题。考虑的过程被视为通过通信领域开发出的已知技术来传输信号的噪声信道。然而,也可以遵循基于过程几何表示的不同方法。实际上,刺激和响应都由适当数量的组件表征,这些组件在空间中对应于一个点。因此,这个过程可以解释为输入空间到输出空间的几何变换。输出空间的大小小于输入空间,因为刺激包含了激活许多同时进行的过程所需的信息。与只有一个相比,它是冗余的。这意味着在考虑的变换中始终存在冗余减少操作。

在输入和输出空间中,形成了典型的区域,信息与之相关联。因此,控制信息传输的自然机制必须以某种方式识别考虑过程中的这些重要区域,并确保它们在变换中相对应。因此,在所讨论的过程中存在数据分组操作;这个操作可以与经验的获得相等同。前两个分组和冗余减少操作是典型信号处理中的操作,有生物学证据表明它们存在于神经系统的功能中。值得注意的是,这两种操作在基于实验原则的非监督学习中是自动实现的,例如竞争学习。

生成模型

生成模型旨在生成现象的所有值,包括可观察到的(输入)和可以从观察到的值中计算出的(目标)。我们试图通过提出生成模型和判别模型之间的第一个区别来理解这种模型如何实现这一目标。

在机器学习中,我们通常需要根据输入 x 向量的值预测目标向量 y 的值。从概率的角度来看,目标是找到条件概率分布 p(y|x)

事件 y 关于事件 x 的条件概率是在已知 x 已验证的情况下 y 发生的概率。这个概率,用 p(y|x) 表示,表达了由 x 的观察所决定的 y 的期望修正。

解决这个问题的最常见方法是用参数模型表示条件分布,然后使用由包含输入变量的值和相应输出的相对向量的对 (xn, yn) 组成的训练集来确定参数。得到的条件分布可以用来对新输入值 (x) 的目标 (y) 进行预测。这被称为判别方法,因为条件分布直接区分了 y 的不同值。

作为这种方法的替代方案,我们可以寻找联合概率分布 p(x∩ y),然后使用这个联合分布来评估条件概率 p(y | x) 以便对新值 xy 进行预测。这被称为生成方法,因为通过从联合分布中采样,可以生成特征向量 x 的合成示例。

联合概率分布 p(x, y) 是一个概率分布,它给出了 xy 向量中的每一个落在为该变量指定的任何特定范围或离散值集中的概率。

生成方法,无论数据类型和使用的理论模型如何,都分为两个基本步骤:

  1. 第一步涉及生成模型的构建。输入数据被处理,目的是推导它们的分布。为此,输入数据可以简单地重新组织成不同的结构,或者它可以代表从输入数据中提取的新信息,这些信息来自特定的算法。生成模型构建的结果是根据其近似分布呈现数据。

  2. 一旦在输入数据上构建了生成模型,这允许采样,从而导致形成与输入数据具有相同分布的新数据。

生成模型的构建允许突出显示初始数据中隐含的特征和属性。然后,根据对数据进行解释以说明这些特征的类型以及因此获得的近似数据分布的变量类型,区分不同的方法。

为什么人工智能研究人员对生成模型如此兴奋?让我们举一个简单的例子:假设我们向系统提供一系列猫的图片。假设在这些图片看过之后,计算机能够以完全独立的方式生成新的猫的照片。如果计算机能够做到这一点,并且产生的图像具有正确的腿、尾巴、耳朵等数量,那么很容易证明计算机知道哪些部分构成了猫,即使没有人向它解释过猫的解剖结构。因此,从某种意义上说,一个好的生成模型是计算机对概念基本知识的证明。

这就是为什么研究人员对构建生成模型如此热情的原因。这些模型似乎是一种训练计算机理解概念的方法,无需研究人员事先教授它们概念。

限制性玻尔兹曼机

玻尔兹曼机是一种概率图模型,可以解释为随机神经网络。玻尔兹曼机首次由杰弗里·辛顿和特里·谢诺夫斯基于 1985 年提出。"随机"一词源于神经元的行为;在它们内部,在激活函数中,它们将具有一个概率值,这将影响神经元的激活。

在实践中,玻尔兹曼机是一个模型(包括一定数量的参数),当应用于数据分布时,能够提供一种表示。该模型可以用来从目标分布(目标分布)的样本中提取未知分布的重要方面。玻尔兹曼机所引用的数据样本也称为训练数据。以下图显示了玻尔兹曼机的架构:

图片

训练玻尔兹曼机意味着调整其参数,以便它所表示的概率分布尽可能好地插值训练数据。从计算角度来看,玻尔兹曼机的训练是一项相当繁重的工作。然而,通过在工作网络的拓扑结构上施加限制,可以简化这个问题;这定义了限制性玻尔兹曼机RBM)。

在玻尔兹曼机中,有两种类型的单元:

  • 可见单元(或神经元,因为正如我们所说,玻尔兹曼机可以解释为神经网络)

  • 隐藏单元(或神经元)

即使在 RBMs 中,也存在这两种类型的单元,我们可以想象它们被安排在两个层面上:

  • 可见单元是观察的组成部分(例如,如果我们的数据由图像组成,我们可以将一个可见单元与每个像素关联)

  • 隐藏单元为我们提供了一个关于观察(例如,图像像素之间的依赖关系)的组件之间存在的依赖关系的模型

因此,隐藏单元可以被视为数据特征的检测器。在 RBM 图中,每个神经元都与另一层的所有神经元相连,而同一层的神经元之间没有连接;正是这种限制使得 RBM 得名,如下面的图所示:

图片

在成功训练后,RBM 提供了对训练数据下分布的非常好的表示。它是一个生成模型,允许从学习到的分布中采样新的数据;例如,可以从研究过的图像生成新的图像结构。拥有生成模型使得有用的应用成为可能。例如,你可以考虑整合一些对应于部分观察的可见单元(即,固定观察变量的值并认为它们是常数)然后产生剩余的可见单元以完成观察;在图像分析示例中,这可以用于图像补全任务。

作为生成模型,RBM 也可以用作分类器。考虑这种类型的应用:

  • RBM 被训练来学习输入数据(解释变量)和相应的标签(响应/输出变量)的联合概率分布,这两个变量都在网络图中表示,从 RBM 的可见单元中学习。

  • 随后,可以链接一个新的输入模式,这次没有标签,到可见变量。相应的标签可以通过直接从霍尔兹曼机采样来预测。

霍尔兹曼机能够完成可见单元上的部分数据模式。如果我们把可见单元分为输入单元和输出单元,给定输入模式,霍尔兹曼机通过产生输出(分类)来完成它。否则,它作为关联记忆工作,返回学习到的模式中最相似的模式到(部分)数据。

霍尔兹曼机架构

霍尔兹曼机架构基于输入、输出和隐藏节点。连接权重是对称的:

图片

基于这个假设,霍尔兹曼机高度递归,这种递归消除了输入节点和输出节点之间的任何基本差异,在需要时可以被视为输入或输出。霍尔兹曼机是一个为整个网络定义了能量的单元网络。其单元产生二元结果((1,0)值)。输出是概率性地计算的,并依赖于温度变量T

玻尔兹曼机的共识函数由以下公式给出:

图片

在前面的公式中,项的定义如下:

  • S[i]是单元i(1,0)的状态

  • w[ij]是单元j和单元i之间的连接强度

  • u[j]是单元j的输出

计算在机器中以随机方式进行,以便增加一致性。因此,如果w[ij]是正的,那么单元ij同时激活或同时失活的趋势会增加,而如果权重是负的,那么它们具有不同激活(一个激活,另一个不激活)的趋势。当一个权重是正的时,它被称为兴奋性;否则,它被称为抑制性

每个二元单元都会随机决定是 1(概率p[i])或 0(概率1- p[i])。这个概率由以下公式给出:

图片

在网络的平衡状态下,似然被定义为指数化的负能量,称为玻尔兹曼分布。你可以想象,通过施加能量,你可以使系统摆脱局部最小值。这必须缓慢进行,因为剧烈的冲击可能会使系统远离全局最小值。最佳方法是先施加能量,然后缓慢减少。这个概念在冶金学中得到了应用,首先通过熔化获得金属的有序状态,然后缓慢降低温度。在过程进行中的温度降低被称为模拟退火

这种方法可以通过向 Hopfield 网络添加概率更新规则来重现(参见第十三章,超越前馈网络 - CNN 和 RNN);重现它的网络被称为玻尔兹曼机。将有一个参数会变化:温度。因此,在高温T下,跃迁到更高能量的概率远大于在低温下。

当温度下降时,假设正确最小能量状态的概率接近 1,网络达到热平衡。网络中的每个单元都会根据以下公式进行能量跃迁:

图片

系统根据以下概率规则(转换函数)转变为更低能量的状态:

图片

可以看到,在高温T下,向更高能量状态的转换概率高于低温T。网络可以根据以下玻尔兹曼分布假设稳定状态配置:

图片

即,它取决于状态的能量和系统的温度。低能量状态更可能;事实上,如果 E[a] < E[b],则 P[a]/P[b] > 1,因此 P[a]>P[b]。所以系统倾向于向最低能量状态转变。

玻尔兹曼机缺点

基于玻尔兹曼机的算法在使用过程中出现了许多问题。以下是一些遇到的问题:

  • 权重调整

  • 收集统计信息以计算概率所需的时间,

  • 一次改变多少权重

  • 如何在模拟退火过程中调整温度

  • 如何决定网络何时达到平衡温度。

主要缺点是玻尔兹曼学习比反向传播慢得多。

深度玻尔兹曼机

另一种类型的玻尔兹曼机是深度玻尔兹曼机DBM)。这是一个类似于 RBM 的神经网络,但它不仅仅只有一个隐藏层节点,DBM 有很多。每个神经层只与相邻层(立即前一个和立即后一个)连接;在这里,同一层的神经元也不相互连接。这种结构使得每个层都能产生特定的统计信息,从而能够捕捉新的数据特征。以下图显示了具有一个可见层和两个隐藏层的 DBM 模型:

图片

如我们所见,连接仅存在于相邻层之间的单元之间。像 RBM 和 DBM 只包含二进制单元。

DBMs 模型为可见向量 v 分配以下概率:

图片

在前面的公式中,术语定义如下:

  • v 是可见向量

  • θ = (W(1),W(2)) 是模型参数,代表可见到隐藏和隐藏到隐藏的对称交互项

  • h^((1))h^((2)) 是隐藏的随机二进制变量

  • Z(θ) 是配分函数

在识别对象或单词的情况下,DBMs 特别有用。这是由于使用少量标记输入数据学习复杂和抽象的内部表示的强大能力,而不是利用大量未标记的输入数据。然而,与深度卷积神经网络不同,DBMs 在双向推理和训练过程中都采用,以更好地检测输入结构的表示。

自编码器

自编码器是一种神经网络,其目的是将输入编码成小维度,并得到的结果能够重建输入本身。自编码器由以下两个子网组成:

  • 编码器,它计算以下函数:

z = ϕ(x)

给定一个输入 x,编码器将其编码到变量 z 中,也称为潜在变量z通常比 x 的维度小得多。

  • 解码器,它计算以下函数:

x' = ψ(z)

由于 z 是编码器产生的 x 的代码,解码器必须将其解码,以便 x'x 相似。

自编码器的训练旨在最小化输入和结果之间的均方误差。

均方误差MSE)是输出和目标之间的平均平方差。较低的值表示更好的结果。零表示没有错误。

对于 n 个观测值,MSE 由以下公式给出:

最后,我们可以总结说,编码器将输入编码为压缩表示,解码器从它返回输入的重建,如下面的图所示:

让我们定义以下术语:

  • W: 输入 → 隐藏权重

  • V: 隐藏 → 输出权重

之前的公式变为:

z = ϕ(W x)*

并且它们也变为:

x' = ψ(VW1* x)*

最后,自编码器的训练旨在最小化以下量:

自编码器的目的不仅仅是执行一种对输入的压缩或寻找恒等函数的近似。有一些技术可以从一个降低维度的隐藏层开始,指导模型给予某些数据属性更大的重要性,从而基于相同的数据产生不同的表示。

变分自编码器

变分自编码器VAE)受自编码器概念的影响:由两个称为编码器解码器的神经网络组成的模型。正如我们所见,编码器网络试图以压缩的形式编码其输入,而解码器网络则试图从编码器返回的代码开始重建初始输入。

然而,变分自编码器(VAE)的功能与简单的自编码器非常不同。VAE 不仅允许对输入进行编码/解码,还可以生成新的数据。为此,它们将代码 z 和重建/生成 x' 视为属于某个概率分布的一部分。特别是,VAE 是深度学习和贝叶斯推理相结合的结果,因为它们由一个使用称为重参数化技术的反向传播算法修改后的神经网络训练而成。虽然深度学习已被证明在复杂函数逼近方面非常有效,但贝叶斯统计允许以概率的形式管理随机生成的不确定性。

VAE 使用与训练集相似的相同结构来生成新的图像。在这种情况下,编码器不会直接为给定的输入生成一个代码,而是计算正态分布的均值和方差。从这个分布中取一个值,然后由解码器进行解码。训练包括修改编码器和解码器参数,以便解码的结果尽可能接近起始图像。训练结束时,我们有从编码器产生的均值和方差的正态分布开始;解码器将能够生成与训练集相似的图像。

让我们定义以下术语:

  • X: 输入数据向量

  • z: 潜在变量

  • P(X): 数据的概率分布

  • P(z): 潜在变量的概率分布

  • P(X|z): 后验概率,即给定潜在变量生成数据的分布

后验概率 P(X|z) 是在证据 zX 的概率。

我们的目标是根据潜在变量中包含的特征生成数据,因此我们想要找到 P(X)。为此,我们可以使用以下公式的全概率定律:

为了理解我们是如何得到这个公式的,我们逐步进行推理。在定义模型的第一项任务是从观察数据开始推断潜在变量的良好值,或者计算后验 p(z|X)。为此,我们可以使用贝叶斯定理:

在前面的公式中,出现了 P(X) 项。在贝叶斯统计的背景下,它也可能被称为证据或模型证据。证据可以通过对潜在变量进行边缘化来计算。这使我们回到了起始公式:

这个积分的计算估计需要指数级的时间,因为它必须在所有潜在变量的配置上进行评估。为了降低计算成本,我们被迫对后验概率的估计进行近似。

在变分自编码器(VAE)中,正如其名所示,我们使用一种称为变分推断VI)的方法来推断 p(z | X)。VI 是贝叶斯推理中最常用的方法之一。这种技术将推断视为一个优化问题。在这样做的时候,我们使用一个简单且易于评估的分布(例如高斯分布),并使用 Kullback-Leibler 散度度量来最小化这两个分布之间的差异。

Kullback-Leibler 散度度量是两个概率分布 PQ 之间非对称差异的度量。特别地,从 PQ 的 Kullback-Leibler 散度,记为 DKL (P ||Q),是当使用 Q 来近似 P 时损失的信息的度量。

对于离散概率分布PQ,从QP的 Kullback-Leibler 散度定义为以下:

分析公式可以清楚地看出,Kullback-Leibler 散度是概率PQ之间对数差异的期望,这里的期望是使用概率P来计算的。

生成对抗网络

生成对抗网络GAN)是由两个共同训练的网络组成的生成模型,称为生成器判别器

这两个网络之间的动力学类似于伪造者和调查者之间的关系。伪造者试图制作对真实艺术作品的忠实模仿,而调查者则试图区分赝品和真品。在这个类比中,伪造者代表生成器,调查者代表判别器。生成器接受属于固定分布的输入值,并试图生成与数据集相似的图像。判别器试图区分生成器创建的数据与属于数据集的数据。这两个网络是共同训练的:

  • 如果输入属于数据集,判别器试图返回输出=1,如果其输入是由生成器生成的,则返回 0

  • 生成器则试图最大化判别器犯错的概率

生成器获取随机输入噪声并试图创建数据样本,而判别器从现实世界的示例或生成器获取输入,如下面的图所示:

为了简单起见,这两个对抗网络是多层感知器类型;然而,可以使用深度网络来模拟相同的结构。例如,为了生成新的图像,而不是从复杂的分布中采样数据,这些网络中使用的方法是从属于简单分布的值或从随机值开始。随后,它们通过将在训练过程中学习的第二个分布进行映射。

在这样的系统中,训练会导致生成器和判别器之间持续的竞争。在这些条件下,优化过程可以在双方独立进行。将生成器命名为G(z),判别器命名为D(x),模型的训练目标是最大化判别器将 1 分配给来自训练集的值的概率,而不是将 0 分配给生成器产生的值。另一方面,我们希望教会生成器最小化以下量:

训练是通过将梯度下降技术应用于以下表达式来进行的:

这种方法源于博弈论,特别是称为两人最小-最大博弈的方法。这类算法采用最小化玩家选择可能造成的最大损失的策略。在训练过程中,判别器可能无法对由真实数据生成的示例进行分类。

对抗自编码器

对抗自编码器AAE)是 VAE 和 GAN 结合产生的生成模型。为了解释该模型,我们首先定义以下术语:

  • x:自编码器输入

  • z:从 x 生成的代码,

  • p(z):我们想要施加的分布

  • q(z|x):从编码器学习到的分布

  • p(x|z):从解码器学习到的分布

  • pdata:数据的分布

  • p(x):模型的分布

我们将函数 q(z|x) 视为 q(z) 的后验分布,其定义如下:

图片

我们试图将等式 q(z)=p(z) 强加于模型。与 VAE 的不同之处在于,驱动 q (z)p(z) 靠近的是对抗网络。VAE 的编码器被视为 GAN 的生成器,对于该生成器可以使用判别器。这试图区分属于 q(z) 的数据与来自 p(z) 的数据。以下图显示了 AAE 架构:

图片

对抗网络和自编码器的训练是联合进行的,使用随机梯度下降。

使用 RBM 进行特征提取

最近,几种类型的人工神经网络ANNs)已被应用于对特定数据集进行分类。然而,这些模型中的大多数只使用有限数量的特征作为输入,在这种情况下,由于起始数据集的复杂性,可能没有足够的信息来做出预测。如果你有更多特征,训练的运行时间会增加,并且由于维度诅咒,泛化性能会下降。在这些情况下,一个用于提取特征的工具会特别有用。RBM 是一种具有强大表示能力的机器学习工具,通常用作各种分类问题中的特征提取器。

乳腺癌数据集

乳房由一组腺体和脂肪组织组成,位于皮肤和胸壁之间。实际上,它不是一个单一的腺体,而是一组腺体结构,称为小叶,它们联合起来形成一个叶。在乳房中,有 15 到 20 个小叶。牛奶通过称为乳管的小管道从小叶流向乳头。

乳腺癌如果未被发现和治疗,可能是一种严重的疾病。它是由乳腺中某些细胞不受控制地增殖并转化为恶性细胞引起的。这意味着它们具有从产生它们的组织中脱离并侵犯周围组织以及最终侵犯身体其他器官的能力。理论上,癌症可以由所有类型的乳腺组织形成,但最常见的是由腺细胞或形成导管壁的细胞形成。

本例的目标是识别多个良性或恶性类别中的每一个。为此,我们将使用名为“乳腺癌”(威斯康星乳腺癌数据库)的数据集中的数据。这些数据是从 UCI 机器学习数据库仓库中获取的,因为 DNA 样本定期到达,正如 Wolberg 博士报告他的临床病例一样。因此,数据库反映了这种数据的按时间顺序分组。这种分组信息立即出现,因为已经被从数据本身中移除。除了第一个变量之外,每个变量都被转换成了 11 个原始数值属性,其值从零到十不等。

为了获取数据,我们借鉴了以下链接中 UCI 机器学习仓库的大量数据集:archive.ics.uci.edu/ml

为了加载数据集,我们将使用sklearn.datasets模块。它包括用于加载数据集的实用工具,包括加载和检索流行参考数据集的方法。它还提供了一些人工数据生成器。

乳腺癌数据集是一个经典且非常容易的二分类数据集。以下表格提供了一些关于数据集的信息:

类别 2
每类样本数 212(M), 357(B)
样本总数 569
维度 30
特征 实数和正数

数据准备

在介绍了乳腺癌数据集之后,我们可以分析代码,这将使我们能够逐行对输入数据进行分类。在代码的第一部分,我们导入稍后将要使用的库:

from sklearn import linear_model, datasets, preprocessing
from sklearn.cross_validation import train_test_split
from sklearn.pipeline import Pipeline
from sklearn.neural_network import BernoulliRBM
from pandas_ml import ConfusionMatrix
import numpy as np
import pandas as pd

目前,让我们仅限于导入;我们将在使用时进一步深入研究。首先,我们必须导入数据集;我们将使用sklearn.datasets包来完成:

BC = datasets.load_breast_cancer()

此命令加载并返回乳腺癌wisconsin数据集。sklearn.datasets包嵌入了一些小型玩具数据集。为了评估数据集的规模(n_samplesn_features)对数据统计属性(通常是特征的相关性和信息性)的影响,同时生成合成数据也是可能的。此包还提供了一些辅助工具,用于获取机器学习社区常用的大型数据集,以在来自真实世界的数据上基准测试算法。数据集是一个类似字典的对象,包含所有数据和有关数据的元数据。这些数据存储在数据成员中,它是一个n_samplesn_features数组。在监督问题的情况下,一个或多个响应变量存储在目标成员中。

数据以Bunch对象的形式返回,这是一个类似字典的对象,包含以下属性:

  • data: 要学习的数据

  • target: 分类标签

  • target_names: 标签的含义

  • feature_names: 特征的含义

  • DESCR: 数据集的完整描述

为了确认数据的内容,让我们提取维度:

print(BC.data.shape)
print(BC.target.shape)

结果如下所示:

(569, 30)
(569,)

为了更好地理解操作,我们将这些数据分为X(预测变量)和Y(目标):

X = BC.data
Y = BC.target

在这一点上,我们使用提供给我们pandas库的工具从预测变量中提取一系列统计信息。

pandas是一个开源的、BSD 许可的库,为 Python 编程语言提供高性能、易于使用的数据结构和数据分析工具。

要使用此功能,我们必须将输入数据从numpy.darray转换为pandas数据框:

Xdata=pd.DataFrame(X)
print(Xdata.describe())

结果如下所示:

               0           1           2            3           4   \
count  569.000000  569.000000  569.000000   569.000000  569.000000
mean    14.127292   19.289649   91.969033   654.889104    0.096360
std      3.524049    4.301036   24.298981   351.914129    0.014064
min      6.981000    9.710000   43.790000   143.500000    0.052630
25%     11.700000   16.170000   75.170000   420.300000    0.086370
50%     13.370000   18.840000   86.240000   551.100000    0.095870
75%     15.780000   21.800000  104.100000   782.700000    0.105300
max     28.110000   39.280000  188.500000  2501.000000    0.163400

由于空间限制,我们只报告了前五个预测变量的结果。正如我们所看到的,变量有不同的范围。当预测变量有不同的范围时,具有较大数值范围的特征的响应变量的影响可能比具有较小数值范围的特征的影响更大,这反过来又可能影响预测精度。我们的目标是提高预测精度,不允许某个特征由于较大的数值范围而影响预测。因此,我们可能需要将不同特征下的值缩放到一个共同的范围内。通过这个统计过程,可以比较属于不同分布的相同变量,也可以比较不同的变量或以不同单位表示的变量。

记住,在训练机器学习算法之前对数据进行缩放是一种良好的实践。通过缩放,消除了数据单位,使得您能够轻松地比较来自不同位置的数据。

在这个例子中,我们将使用最小-最大方法(通常称为特征缩放)来获取所有缩放数据在范围(0,1)内。实现此目的的公式是:

图片

以下命令执行特征缩放:

X = (X - np.min(X, 0)) / (np.max(X, 0) - np.min(X, 0))

numpy.min()numpy.max() 用于计算每个数据库列的最小值和最大值。

现在让我们将数据分割成训练和测试模型。训练和测试模型是进一步使用模型进行预测分析的基础。给定一个包含预测变量和响应变量的 100 行数据集,我们将数据集分割成方便的比例(比如说 80:20),并分配 80 行用于训练,20 行用于测试。行是随机选择的,以减少偏差。一旦有了训练数据,数据就被输入到机器学习算法中,以获得大规模的通用函数。为了分割数据集,我们将使用 sklearn.model_selection.train_test_split() 函数:

X_train, X_test, Y_train, Y_test = train_test_split(X, Y, test_size=0.2, random_state=1)

train_test_split() 函数将数组或矩阵分割成随机的训练和测试子集。前两个参数是 X(预测变量)和 Y(目标)numpy 数组。允许的输入包括列表、numpy 数组、scipy 稀疏矩阵或 pandas 数据框。然后添加了两个选项:

  • test_size:这应该在 0.0 和 1.0 之间,并代表要包含在测试分割中的数据集比例

  • random_state:这是随机数生成器使用的种子

模型拟合

我们之前提到 RBM 经常被用作各种分类问题中的特征。现在是时候看看如何做了。首先要做的是使用 sklearn.neural_network 模块的 BernoulliRBM 函数。

sklearn 是一个用于 Python 编程语言的免费机器学习库。它具有各种分类、回归和聚类算法,包括支持向量机、随机森林、梯度提升、k-means 和 DBSCAN。它旨在与 Python 数值和科学库 NumPy 和 SciPy 交互操作。

sklearn 库中,sklearn.neural_network 模块包括基于神经网络的模型。在这个模块中,BernoulliRBM 函数拟合一个伯努利 RBM。返回一个具有二元可见单元和二元隐藏单元的 RBM。参数使用随机最大似然SML),也称为持久对比散度PCD)进行估计。首先,我们将设置模型的架构:

RbmModel = BernoulliRBM(random_state=0, verbose=True)

然后,我们将使用训练数据拟合模型:

FitRbmModel = RbmModel.fit_transform(X_train, Y_train)

fit_transform 方法将转换器拟合到 X_trainY_train 上,并可选地使用参数 fit_params,然后返回 X_train 的转换版本。在这种情况下,没有使用可选参数。

如果你还记得,我们的目的是使用Rbm模型提取特征,然后这些特征将被逻辑回归模型用于分类数据。所以,第一部分已经完成——我们已经在FitRbmModel变量中提取了特征。现在是时候创建逻辑回归模型了。为此,我们将使用sklearn.linear_model模块中的LogisticRegression函数,如下所示:

LogModel = linear_model.LogisticRegression()

现在我们将决策函数中特征系数设置为从rbm模型中提取的特征:

LogModel.coef_ = FitRbmModel

现在我们可以构建分类器了。为此,我们将使用sklearn.pipeline模块中的Pipeline函数:

Classifier = Pipeline(steps=[('RbmModel', RbmModel), ('LogModel', LogModel)])

pipeline的目的在于组装可以一起进行交叉验证的多个步骤,同时设置不同的参数。为此,它允许使用步骤的名称来设置各个步骤的参数,就像之前的代码中那样。可以通过将参数名称设置为另一个估计器来完全替换一个步骤的估计器,或者通过将其设置为None来移除一个转换器。现在分类器已经准备好了;我们只需要对其进行训练:

LogModel.fit(X_train, Y_train)
Classifier.fit(X_train, Y_train)

首先,训练逻辑回归模型,然后训练分类器。我们只需要进行预测。回想一下,为了做到这一点,我们有一个未使用的数据集可用:X_testY_test。为了检查分类器的性能,我们将预测与真实数据进行比较:

print ("The RBM model:")
print ("Predict: ", Classifier.predict(X_test))
print ("Real:    ", Y_test)

下面的截图显示了返回的结果:

图片

最后,为了更好地理解模型性能,我们将计算混淆矩阵。在混淆矩阵中,我们的分类结果与真实数据进行比较。混淆矩阵的优势在于它不仅识别了分类错误的性质,还识别了它们的数量。在这个矩阵中,对角线单元格显示了正确分类的案例数量;所有其他单元格显示了错误分类的案例。要计算混淆矩阵,我们可以使用 pandas 库中包含的ConfusionMatrix()函数,如下所示:

CM = ConfusionMatrix(Y_test, Classifier.predict(X_test))
CM.print_stats()

在下面的代码中,展示了ConfusionMatrix()函数返回的结果:

population: 114
P: 72
N: 42
PositiveTest: 87
NegativeTest: 27
TP: 71
TN: 26
FP: 16
FN: 1
TPR: 0.9861111111111112
TNR: 0.6190476190476191
PPV: 0.8160919540229885
NPV: 0.9629629629629629
FPR: 0.38095238095238093
FDR: 0.1839080459770115
FNR: 0.013888888888888888
ACC: 0.8508771929824561
F1_score: 0.8930817610062893
MCC: 0.6866235389841608
informedness: 0.6051587301587302
markedness: 0.7790549169859515
prevalence: 0.631578947368421
LRP: 2.588541666666667
LRN: 0.022435897435897433
DOR: 115.37500000000003
FOR: 0.037037037037037035

返回了一些信息;特别是,我们可以注意到模型的准确率为 0.85。

Keras 自编码器

正如我们之前所说的,自编码器是一种神经网络,其目的是将输入编码成小维度,并能够重建输入本身。自编码器由以下两个子网络的并集组成:编码器和解码器。此外,还有一个损失函数,它是数据压缩表示和分解表示之间信息损失量的距离。编码器和解码器将与距离函数可微分,因此编码/解码函数的参数可以通过梯度随机优化来最小化重建损失。

加载数据

这是一个包含 60,000 个 28 x 28 灰度图像的手写数字数据库,这些图像是 10 个数字的,还有一个包含 10,000 个图像的测试集。这个数据集已经在 Keras 库中可用。以下图表显示了 MNIST 数据集中 0-8 的图像样本:

如往常一样,我们将逐行分析代码。在代码的第一部分,我们导入稍后将要使用的库:

from keras.layers import Input, Dense
from keras.models import Model

此代码导入了以下函数:

  • 使用 Input 函数来实例化一个 Keras 张量。Keras 张量是从底层后端(Theano、TensorFlow 或 CNTK)的张量对象。我们通过添加某些属性来增强它,这些属性允许我们仅通过知道模型的输入和输出就构建一个 Keras 模型。

  • 使用 Dense 函数实例化一个常规密集连接神经网络层。

  • 使用 Model 函数来定义模型。模型是你可以总结、拟合、评估并用于做出预测的东西。Keras 提供了一个Model类,你可以用它从创建的层中创建模型。它只需要你指定输入和输出层。

要导入数据集,只需使用此代码:

from keras.datasets import mnist
(x_train, y_train), (x_test, y_test) = mnist.load_data()

以下元组被返回:

  • x_train, x_test:一个uint8数组,包含灰度图像数据,形状为(num_samples, 28, 28)

  • y_train, y_test:一个uint8数组,包含数字标签(范围在 0-9 的整数),形状为(num_samples)

现在我们必须将所有值归一化到 0 到 1 之间。Mnist 图像以像素格式存储,其中每个像素(总共 28 x 28)存储为一个 8 位整数,其值范围从 0 到 255。通常,0 被认为是黑色,255 被认为是白色。介于两者之间的值构成了不同的灰色阴影。现在,为了将所有值归一化到 0 到 1 之间,只需将每个值除以 255。因此,包含值 255 的像素将变为 1,包含 0 的像素将保持原样;介于两者之间的是所有其他值:

x_train = x_train.astype('float32') / 255
x_test = x_test.astype('float32') / 255

通过使用astype()函数,我们已经将输入数据从float32(单精度浮点数:符号位,8 位指数,23 位尾数)转换过来。正如我们所说的,每个样本(图像)由一个 28 x 28 的矩阵组成。为了降低维度,我们将 28 x 28 的图像展平成大小为 784 的向量:

x_train = x_train.reshape((len(x_train), np.prod(x_train.shape[1:])))
x_test = x_test.reshape((len(x_test), np.prod(x_test.shape[1:])))

reshape()函数给数组赋予一个新形状,而不改变其数据。新形状应与原始形状兼容。新形状的第一个维度是len()函数返回的观测数(len(x_train)len(x_test))。第二个维度代表起始数据的最后两个维度的乘积(28 x 28 = 784)。为了更好地理解这种转换,我们首先打印起始数据集的形状,然后打印转换后数据集的形状:

print (x_train.shape)
print (x_test.shape)

以下是数据集重塑前后的结果:

(60000, 28, 28)
(10000, 28, 28)
(60000, 784)
(10000, 784)

Keras 模型概述

Keras 中有两种类型的模型可用:

  • 顺序模型

  • Keras 功能 API

让我们在以下各节中详细查看每个部分。

顺序模型

Sequential 模型是层的线性堆叠。我们可以通过将层实例列表传递给构造函数来创建一个 Sequential 模型,如下所示:

from keras.models import Sequential
from keras.layers import Dense, Activation
model = Sequential([
    Dense(32, input_shape=(784,)),
    Activation('relu'),
    Dense(10),
    Activation('softmax'),
])

我们也可以通过 .add() 方法简单地添加层:

model = Sequential()
model.add(Dense(32, input_dim=784))
model.add(Activation('relu'))

此类模型需要知道它应该期望什么输入形状。因此,Sequential 模型的第一个层需要接收有关其输入形状的信息。有几种可能的方法可以实现这一点:

  • 向第一个层传递 input_shape 参数

  • 通过 input_diminput_length 参数指定它们的输入形状

  • 向一个层传递 batch_size 参数

Keras 功能 API

定义模型的另一种方式是 Keras 功能 API。对于定义复杂模型,如多输出模型、有向无环图或具有共享层的模型,Keras 功能 API 是最佳选择。例如,要定义一个密集连接网络,只需输入以下代码:

from keras.layers import Input, Dense
from keras.models import Model
inputs = Input(shape=(784,))
x = Dense(64, activation='relu')(inputs)
x = Dense(64, activation='relu')(x)
predictions = Dense(10, activation='softmax')(x)
model = Model(inputs=inputs, outputs=predictions)
model.compile(optimizer='rmsprop',
              loss='categorical_crossentropy',
              metrics=['accuracy'])
model.fit(data, labels) 

在下一节中,我们将通过将其应用于我们的示例来深入探讨此类模型。

定义模型架构

现在我们将使用 Keras 功能 API 来构建模型。正如我们之前看到的,首先我们必须定义输入:

InputModel = Input(shape=(784,))

这将返回一个表示我们输入占位符的张量。稍后,我们将使用这个占位符来定义一个 Model。在这个阶段,我们可以向我们的模型架构中添加层:

EncodedLayer = Dense(32, activation='relu')(InputModel)

Dense 类用于定义一个全连接层。我们指定层的神经元数量作为第一个参数(32),使用激活参数(relu)指定激活函数,最后指定层的输入张量(InputModel)。

记住,给定一个输入 x,编码器将其编码到一个变量 z 中,也称为潜在变量z 通常比 x 的维度小得多;在我们的例子中,我们从 784 压缩到 32,压缩因子为 24.5。

现在,让我们添加解码层:

DecodedLayer = Dense(784, activation='sigmoid')(EncodedLayer)

此层是对输入的损失重建。又一次,我们使用了具有 784 个神经元(输出空间的维度)的 Dense 类,sigmoid 激活函数,以及 EncodedLayer 输出作为输入。现在我们必须如下实例化一个模型:

AutoencoderModel = Model(InputModel, DecodedLayer)

此模型将包括在给定 InputModel(输入)的情况下计算 DecodedLayer(输出)所需的全部层。以下是 Model 类的一些有用属性:

  • model.layers 是包含模型图的层的扁平化列表

  • model.inputs 是输入张量的列表

  • model.outputs 是输出张量的列表

因此,我们必须为训练配置模型。为此,我们将使用 compile 方法,如下所示:

AutoencoderModel.compile(optimizer='adadelta', loss='binary_crossentropy')

此方法配置模型以进行训练。仅使用两个参数:

  • optimizer:字符串(优化器名称)或优化器实例。

  • loss: 字符串(目标函数的名称)或目标函数。如果模型有多个输出,可以通过传递字典或损失列表来使用不同的损失函数。然后,模型将最小化的损失值将是所有单个损失的加和。

我们使用了 adadelta 优化器。这种方法随时间动态调整,仅使用一阶信息,并且计算开销最小,超出了传统的随机梯度下降。该方法不需要手动调整学习率,并且对噪声梯度信息、不同的模型架构选择、各种数据模态和超参数的选择具有鲁棒性。

此外,我们使用了binary_crossentropy作为loss函数。损失函数是计算上可行的函数,表示在分类问题中对预测不准确性的代价。

在这一点上,我们可以训练模型:

history = AutoencoderModel.fit(x_train, x_train,
                batch_size=256,
                epochs=100,
                shuffle=True,
                validation_data=(x_test, x_test))

fit方法用于在固定数量的 epochs(在数据集上的迭代)上训练模型。以下是对传递的参数的解释,以便更好地理解其含义:

  • x: 如果模型有一个输入,则为训练数据的 Numpy 数组,或者如果有多个输入,则为 Numpy 数组的列表。如果模型中的输入层有名称,也可以通过将输入名称映射到 Numpy 数组来传递字典。如果从框架原生张量(例如,TensorFlow 数据张量)中提供,则x可以是None(默认)。

  • y: 如果模型有一个输出,则为目标(标签)数据的 Numpy 数组,或者如果有多个输出,则为 Numpy 数组的列表。如果模型中的输出层有名称,也可以通过将输出名称映射到 Numpy 数组来传递字典。如果从框架原生张量(例如,TensorFlow 数据张量)中提供,则y可以是None(默认)。

  • batch_size: 整数None。这是每次梯度更新时的样本数量。如果没有指定,batch_size将默认为32

  • epochs: 一个整数。这是训练模型的 epoch 数量。一个 epoch 是对整个xy数据的迭代。注意,与initial_epoch结合使用时,epochs应理解为最终 epoch。模型不是根据 epoch 的数量进行多次迭代训练,而是仅仅训练到 epoch 索引为 epochs 的那个 epoch。

  • shuffle: 一个布尔值,用于决定在每个 epoch 之前是否对训练数据进行洗牌,或者str(用于batch)。batch是处理 HDF5 数据限制的特殊选项;它以批大小块进行洗牌。当steps_per_epoch不是None时,它没有效果。

  • validation_data: 一个元组(x_valy_val)或元组(x_valy_val,和val_sample_weights),用于在每个 epoch 结束时评估损失和任何模型度量。模型不会在此数据上训练。validation_data将覆盖validation_split

返回一个History对象。其history.history属性是记录在连续的 epoch 中训练损失值和指标值,以及验证损失值和验证指标值(如果适用)。

我们的模式现在已准备就绪,因此我们可以使用它来自动重建手写数字。为此,我们将使用predict方法:

DecodedDigits = AutoencoderModel.predict(x_test)

此方法为输入样本(x_test)生成输出预测。运行此示例,你应该会看到每个 100 个 epoch 的消息,打印每个 epoch 的损失和准确率,然后是对训练数据集上训练模型的最终评估。如下所示:

图片

要了解loss函数在 epoch 中的变化情况,可以创建一个在训练和验证数据集上训练 epoch 的损失图。为此,我们将使用以下Matplotlib库:

plt.plot(history.history['loss'])
plt.plot(history.history['val_loss'])
plt.title('Autoencoder Model loss')
plt.ylabel('loss')
plt.xlabel('epoch')
plt.legend(['train', 'test'], loc='upper left')
plt.show()

在以下图表中显示了训练和验证数据集上训练 epoch 的损失图:

图片

我们的工作已经完成;我们只需验证获得的结果。我们可以在屏幕上打印出原始的手写数字和从我们的模型重建的数字。当然,我们只会对数据集中包含的 60000 个数字中的某些进行操作;实际上,我们将仅显示前五个。在这种情况下,我们也将使用Matplotlib库:

n=5
plt.figure(figsize=(20, 4))
for i in range(n):
    ax = plt.subplot(2, n, i + 1)
    plt.imshow(x_test[i].reshape(28, 28))
    plt.gray()
    ax.get_xaxis().set_visible(False)
    ax.get_yaxis().set_visible(False)
    ax = plt.subplot(2, n, i + 1 + n)
    plt.imshow(DecodedDigits[i].reshape(28, 28))
    plt.gray()
    ax.get_xaxis().set_visible(False)
    ax.get_yaxis().set_visible(False)
plt.show()

结果如下所示:

图片

如您所见,结果非常接近原始数据,这意味着模型运行良好。

Magenta

2016 年 6 月 1 日,谷歌推出了 Magenta 项目,这是一个旨在通过使用人工智能以自主方式创作艺术和音乐的研究项目。基于 TensorFlow 平台,Magenta 旨在以开源模式在 GitHub 上发布代码,以便开发者能够实现越来越引人注目和先进的结果。

该项目是谷歌大脑团队的一个创意,谷歌大脑团队是谷歌的一个深度学习人工智能研究团队。它将开放式的机器学习研究、系统工程和谷歌规模的计算资源相结合。

Magenta 项目为自己设定了两个雄心勃勃的目标:开发艺术和音乐机器学习,并建立一个对这一主题感兴趣的人们的社区。机器学习长期以来在不同的环境中被使用,特别是在语音识别和语言翻译方面。Magenta 的创建是为了集中活动在之前未探索的领域,如广义上的艺术生成。为此,Magenta 希望创建一个物理场所,所有由相同兴趣(即艺术生成)联合的人可以交流思想和产品。换句话说,一个由艺术家、程序员和机器学习研究者组成的社区。

更多信息,请参考以下 URL 的项目官方网站:magenta.tensorflow.org/.

NSynth 数据集

通过阅读前面的章节,我们现在已经了解到,为了正确训练机器学习算法,需要有一个包含重要数量观察的数据集。最近,由于高质量图像数据集的可用性,生成模型在图像上的使用增加,因此这对应着一个重要的数据集。考虑到这一点,Google Brain 团队推出了 NSynth。这是一个大规模、高质量的音符集,比可比的公共数据集大一个数量级。目标是拥有一个重要的音频数据集,以便开发性能更好的生成模型。

NSynth 数据集由 Jesse Engel 等人介绍在名为《使用 WaveNet 自动编码器进行神经音频合成音符》的文章中。

NSynth 是一个包含 305,979 个音乐音符的音频数据集,每个音符都有独特的音调、音色和包络。对于来自商业样本库的 1,006 个工具,Google Brain 团队生成了 4 秒的 16 kHz 单声道音频片段,称为音符,跨越标准 MIDI 钢琴的每个步骤(21-108)和五种不同的速度(25、50、75、100 和 127)。音符保持前 3 秒,并在最后 1 秒允许其衰减。

Google Brain 团队还根据人类评估和启发式算法的组合,为每个音符标注了三条额外信息:

  • 来源:音符乐器的声音产生方法。这可以是声学或电子,对于分别从声学或电子乐器录制乐器的乐器,或合成,对于合成乐器。

  • 家族:音符所属的高级别家族。每个乐器恰好属于一个家族。

  • 品质:音符的音质。每个音符都标注了零个或多个品质。

NSynth 数据集可以以下两种格式下载:

  • TensorFlow 示例协议缓冲区的序列化 TFRecord 文件,每个 note 包含一个 Example proto

  • 包含非音频特征和 16 位 PCM WAV 音频文件的 JSON 文件

整个数据集分为三个集合:

  • 训练:包含 289,205 个示例的训练集。乐器与有效集或测试集不重叠。

  • 有效:包含 12,678 个示例的验证集。乐器与训练集不重叠。

  • 测试:包含 4,096 个示例的测试集。乐器与训练集不重叠。

更多信息和下载数据集,请参考以下 URL 的项目官方网站:magenta.tensorflow.org/datasets/nsynth.

摘要

在本章中,我们探讨了使用神经网络建模的最有趣的研究领域之一。首先,我们看到了无监督学习算法的介绍。无监督学习是一种机器学习技术,它从一系列输入(系统经验)开始,将能够根据共同特征重新分类和组织,以尝试对后续输入进行预测。与监督学习不同,在学习过程中,只向学习者提供未标记的示例,因为类别不是事先已知的,而是必须自动学习。

因此,我们分析了不同类型的生成模型。玻尔兹曼机是一种概率图模型,可以解释为随机神经网络。在实践中,玻尔兹曼机是一个模型(包括一定数量的参数),当应用于数据分布时,能够提供一种表示。该模型可以用来从目标分布(未知分布)的样本中提取重要方面。

自动编码器是一种神经网络,其目的是将输入编码成小维度,并能够重建输入本身。自动编码器的目的不仅仅是执行一种输入的压缩或寻找身份函数的近似;但有一些技术可以让我们指导模型(从减少维度的隐藏层开始)给予某些数据属性更大的重要性。因此,它们基于相同的数据产生不同的表示。

GAN 是由两个联合训练的网络组成的生成模型,称为 生成器判别器。这两个网络之间的动态类似于伪造者和调查者。伪造者试图制作忠实于原作的仿制品,而调查者则试图区分赝品和真品。

然后,我们展示了如何实现一些示例:使用 RBM 进行特征提取和 Keras 的自动编码器。最后,我们介绍了 Nsynth 数据集和 Google Magenta 项目。

第十七章:聊天机器人

聊天机器人的时代已经到来,这是一种新的技术现象,它帮助产生了与机器互动的新方式,从而创造了新的商业机会。聊天机器人是能够通过聊天与用户互动的机器人,能够通过执行极其有限的任务来帮助他们:提供当前账户信息、购票、接收天气预报等。

聊天机器人处理用户呈现的文本,然后根据一组复杂的算法进行响应,这些算法解释和识别用户说了什么。在扣除用户所需的内容后,它根据从上下文中提取的信息确定一组适当的响应。一些聊天机器人提供了一种非常真实的对话体验,其中很难确定代理是机器人还是人类。

聊天机器人也是人工智能带来的最令人兴奋的创新之一。在本章中,在介绍所有这些技术所基于的主要概念之后,我们将介绍构建上下文聊天机器人的方法,并在 GCP 上实现一个简单的聊天机器人端到端应用程序。

涵盖的主题:

  • 聊天机器人基础

  • 聊天机器人设计技术

  • 自然语言处理

  • Google Cloud Dialogflow

  • 在 GCP 上构建和实施聊天机器人

在本章结束时,读者将完成对聊天机器人的实战介绍,并学习如何在实施过程中训练上下文聊天机器人。

聊天机器人基础

聊天机器人,或称为 chatbots,是能够通过聊天与人类互动的程序,模拟人类的行为。随后,人类与机器人之间建立对话。自从计算机科学开始发展以来,学者们与其他学科合作,试图通过机器来重现典型的人类认知过程。它们通常用于简单且重复的活动,这些活动可能需要花费大量时间,或者不值得分配人力资源。

考虑到它们的复杂性,显然在这种情况下,我们不能谈论对人类自身行为的令人满意的模拟,但仍然可以开始提及人工智能的概念。

机器人可以执行方案并展示其操作。机器人可以做任何事情,从自动回复消息到允许在线购物。它们可以接收任何类型的新闻,发布天气状况,或展示音乐视频,所有这些都可以通过聊天完成。

有几个平台实现了使用机器人的能力。这些包括:Telegram、Skype、Messenger、Slack、SMS 和电子邮件。机器人允许您使用这些平台——用户已知并用于其他功能(如消息)的应用程序——在它们内部执行最不同的功能,从而节省用户在设备上使用和安装额外应用程序的努力。

聊天机器人历史

聊天机器人的历史比你想象的要早得多。我们回到了 20 世纪中叶的英国,当时艾伦·图灵提出了“机器能思考吗?”这个问题,并提出了一种将智能与进行对话的能力联系起来的测试。从那时起,创建能够以越来越准确的方式模拟人类语言的软件的挑战从未停止。

模仿游戏

在 20 世纪 50 年代,艾伦·图灵撰写了一篇名为计算机与智能的文章,其中讨论了确定机器是否能够思考的标准问题。这个标准基于模仿游戏,其中有一个计算机A,一个人B,以及另一个人类C(审问者)。人类C必须确定AB的身份。审问者向他们提问,AB以书面形式回答。当C在判断A的身份时出错,认为它是人类时,计算机A赢得游戏。以下图显示了模仿游戏的方案:

图片

图灵的游戏——尽管有众多批评认为这一标准不足以确定机器是否能够思考——在过去几十年中引发了一场挑战,导致了以越来越准确的方式模拟人类语言的软件的创建。

1990 年,一项名为洛布纳奖的竞赛成立,基于图灵测试,以奖励行为最接近人类思维的计算机。这项竞赛每年举行一次。以下是竞赛的官方网站:www.aisb.org.uk/events/loebner-prize

Eliza

1966 年,约瑟夫·魏岑鲍姆创建了 Eliza 程序,旨在模仿治疗师和患者在他们初始阶段之间的对话。在程序中,发生了一系列简单的问题交换,这些问题不需要深厚的情感深度。以下截图展示了一个简单的 Eliza 聊天机器人:

图片

Eliza 依赖于使用正则表达式将用户输入转换为输出,以便使对话保持连续性以及明显的连贯性。在其他时候,它不会产生任何上下文观察,而是产生与心理治疗师态度完全一致的内容,例如非常有趣继续can you elaborate on that?*

帕里

帕里继续留在医疗领域,模拟了患有偏执型精神分裂症的人的行为。它于 1972 年由精神病学家肯尼斯·科比开发,作为他在处理真实患者之前为学生提供的一种实践工具。

在图灵测试的一个轻微变化中,精神科医生被要求阅读 Parry 的记录和真实患者的记录,以区分这两者来自哪里。这在当时是一个令人难以置信的结果;精神科医生只成功地在 48%的时间里识别出行为上的差异。

1973 年 1 月,在国际计算机会议期间,计算机科学先驱 Vint Cerf 决定将机器人带到它们的逻辑结论。使用 ARPANET,他设置了 Eliza 和 Parry 之间的对话。这是一次计算机思维的跨海岸会议;Eliza 位于麻省理工学院,Parry 位于斯坦福大学。以下是从两个机器人之间的对话中摘录的一段简短对话:

  • Eliza:晚上好。告诉我你的问题。请用句号或问号结束输入。

  • Parry:有时候人们让我感到烦躁。

  • Eliza:我不确定我是否完全理解你。

  • Parry:你应该更加注意。

  • Eliza:也许你应该更加注意。

  • Parry:你有权持有自己的观点。

  • Eliza:是什么让你认为我有权持有自己的观点?

  • Parry:仅仅和普通人在一起就让我感到烦恼。

  • Eliza:请继续说。

  • Parry:我之前去过 Bay Meadows 的跑道。

  • Eliza:这对你有什么启示?

  • Parry:我去过赛马。

Jabberwacky

Jabberwacky 是由程序员 Rollo Carpenter 于 1988 年创造的。这个机器人的目标是通过图灵测试。Jabberwacky 能够以幽默的方式模拟人类的声音。目前,该机器人的开发仍在继续,旨在将系统应用于机器人或会说话的宠物,基于声音学习。

这是一个基于机器学习的机器人;实际上,为了与我们互动,它只使用学到的材料,并借用我们的一些智慧,同时增加它的知识。没有硬编码的规则,它完全基于反馈的原则。

Cleverbot 是 Jabberwacky 在 1997 年发布的一个变体,取得了显著成果;2011 年,它参加了印度 IIT Guwahati 的图灵测试,有 59.3%的概率被认为是人类。

Dr. Sbaitso

Dr. Sbaitso 也被设计成模拟能够解决用户情感问题的心理学家的行为,并且可以在使用 MS-DOS 操作系统的个人电脑上使用。它由 Creative Labs 于 1992 年开发,旨在展示声卡生成合成声音的能力。以下截图显示了 Dr. Sbaitso 中的欢迎信息所在的 MS-DOS 窗口:

大部分问题是WHY DO YOU FEEL THAT WAY?。因此避免了更复杂的互动。当他收到他无法理解的句子时,他通常会回答THAT'S NOT MY PROBLEM

ALICE

人工语言互联网计算机实体 (ALICE) 是基于 自然语言处理 (NLP) 的开源软件,由科学家理查德·S·华莱士于 1995 年设计。Alice 的解释系统基于最小化方法。句子的意义通过特定的关键词或术语(根)来阐述,避免了深入和复杂的分析。Alice 三次赢得了 Loebner 奖:2000 年、2001 年和 2004 年。

SmarterChild

SmarterChild 是一个非常成功的聊天机器人,可在 AOL 即时消息和 MSN 即时消息上使用。由 ActiveBuddy Inc. 于 2001 年开发,被超过 3000 万用户使用。从 SmarterChild 的快速成功中衍生出了面向市场营销的机器人,如 Radiohead、Austin Powers、Intel、Keebler、体育新闻等。

IBM Watson

Watson 是 IBM 在 2006 年开发的人工智能系统,能够回答以自然语言表达的问题。最初,Watson 是为了参加一档名为 Jeopardy! 的美国电视智力竞赛而创建的。然而,在第一次参赛时,它只能回答 35% 的问题。在 IBM 团队进行多次改进后,Watson 于 2011 年再次尝试,这次它成功地击败了智力竞赛的人类冠军。

在游戏中,Watson 在没有连接到互联网的情况下工作,利用了 4 个 terabytes 的磁盘空间。后来,它被用于许多其他完全不同的环境中,例如在纪念斯隆-凯特琳癌症中心管理肺癌治疗决策。

构建机器人

在早期的聊天机器人中,使用了相当简单的算法来分析输入消息并返回输出响应;这些算法旨在通过提供一致的输出响应来模拟计算机对输入中提出的内容的理解。随着时间的推移和技术的发展,越来越多的复杂人工智能方法被创造出来,使得聊天机器人能够建立越来越接近人类真实对话的对话。为了正确设计聊天机器人,从哪些基本点开始是必要的?机器人构建的基本主题包括意图、实体和上下文。在下一节中,我们将分析它们,以了解如何有效地使用它们。

意图

用户的意图是他们的目的,最终目标。例如,订购某物、想要激活用户窗口上的某物、寻找节目,或者只是说再见。聊天机器人应该能够根据从用户消息中检测到的意图执行一些操作。

假设我们想要创建一个为销售 IT 相关产品的商店设计的聊天机器人。作为一个初步程序,有必要考虑聊天机器人在被用户请求后能够执行哪些操作。例如,当用户请求查看商店销售的产品时,聊天机器人需要向用户提供适当的信息,比如:“我想买一个鼠标”。同样,当用户发送消息,如“在罗马找一个商店”时,聊天机器人应该能够定位到该特定位置附近的商店。为了执行这些操作中的每一个,聊天机器人必须能够区分用户的两种意图:搜索产品或销售点。在以下图中,用户表达了两种可能的意图:

图片

从用户消息中检测意图是机器学习领域的一个非常普遍的问题。这是称为文本分类的技术,其中程序的目标是将文档/短语分类到几个类别中,这些类别代表了用户的意图。理解用户的请求是什么是聊天机器人的智能部分,因为自然语言中表达请求的方式有很多。聊天机器人将通过识别最接近的意图来尝试解释用户的请求。当然,这种关联并不总是精确的;事实上,会返回可能的解释的排名。但从这个角度来看,可以通过提供更多相同请求的替代示例来改进答案。

实体

实体是用户消息中包含的相关主题,例如一个物体、一种颜色或一个日期。如果意图是在网页上激活某个功能,用户也可能指明是什么,比如一个按钮或一个窗口。因此,实体是聊天机器人可以识别的关键词。多亏了这些实体,聊天机器人能够识别对话的主题,从而提供有针对性的信息作为输出。在以下图中,聊天机器人识别出了两个可能的实体:

图片

假设我们有一个以下的消息作为输入:我想买一个显示器。很明显,用户想要购买一个 IT 产品,但如果聊天机器人无法识别用户试图购买的产品类型,返回的信息将关于所有类型的 IT 产品,其中许多对用户来说并不感兴趣。如果聊天机器人能够检测到用户试图购买显示器,那么它将只返回关于这种 IT 产品的信息,从而减少可用的选项。这对用户是有益的。

上下文

聊天机器人是为了简化并自动化流程而创建的。因此,如果一个聊天机器人使人类以前简单管理的过程变得复杂,那么它就失败了。例如,假设你搜索天气信息。如果你用手机请求信息,服务提供商可能会使用你的电话号码查找你的账户信息,就像你的地址一样。同样的程序应该适用于聊天机器人。如果你旨在创建一个与使用手机相当或更有效率或更无效的机器人,那么聊天机器人的上下文就极其重要。

当我们谈论聊天机器人的上下文时,我们指的是机器人能够识别它已经知道的信息,并且能够只查找它需要提供适当解决方案的未知信息。在这种情况下,它必须提供未来几天该地区的天气信息。如果这个天气信息聊天机器人适当地使用上下文,那么它就不应该询问它已经知道的信息。由于上下文的维护,使用机器人已经持有的信息,使得返回信息的程序变得更快。

一旦意图和实体被放入系统中,对话的逻辑流程就创建了,因为区分对话和简单的常见问题解答FAQs)的是上下文。多亏了上下文,我们才能将用户的当前输入与之前提到的输入联系起来。

上下文在聊天机器人和用户之间来回传递。维护对话从一回合到下一回合的上下文是聊天机器人的责任。上下文包括与每个用户对话的唯一标识符,以及每次对话回合增加的计数器。如果我们不保留上下文,每一轮输入似乎都是一次新对话的开始。

聊天机器人

我们不需要从头开始设计聊天机器人。事实上,我们可以利用程序员在开发我们之前章节中分析的应用程序过程中积累的经验。收集到的所有信息代表了一种知识,我们可以从中提取对我们应用程序有用的线索。

必要要求

首先,我们可以看看一个优秀的聊天机器人必须满足哪些要求,以确保它提供服务的成功。以下列表提到了我们必须记住的聊天机器人设计的一些关键要素:

  • 保证用户最小手动努力:这是聊天机器人设计中的起点。为了服务的成功,必须通过最小化手动干预来陪伴用户的选择。这是通过大幅减少帮助机器人确定最佳解决方案所需的触摸、按键或鼠标点击次数来实现的。为此,你需要确保大多数选项都由同一个聊天机器人提供,用户只需选择正确的选项。这样,在用户与聊天机器人的交互中可以节省大量时间。

  • 预测正确选项:为了确保系统只显示与该上下文相关的选项,必须通过一系列选择提供正确的选项。为了实现这一点,系统必须能够识别用户的需求。用户需求必须通过最少的提问和用户的手动努力来识别。

  • 聊天机器人的定制:这是根据服务用户的特点构建不同用户聊天机器人交互的可能性。例如,系统可以记住用户档案、之前的交互、系统中其他用户的交互、当前上下文和环境知识。理解用户及其当前可能需要什么,必须将这些属性与其他属性一起理解。

文本的重要性

在应用任何文本解释策略之前,必须进行一系列的加工。特别是,以下阶段很重要:

  • 文本清理:文本被清理掉所有可能改变后续分析结果的因素(例如,消息开头和结尾的空格)

  • 验证文本字符:检查文本是否包含等同于其他可能使后续分析无效的字符

  • 文本规范化:将大写字母转换为小写字母,这样用大写字母而不是小写字母写的同一个单词将被以相同的方式解释(这种方法并不总是最优的,因为有时大写字母可能具有歧视性价值)

单词转换

这种技术已被 Eliza 类型的聊天机器人广泛使用。它包括重新表述输入消息以生成相应的输出。例如,如果用户写道 you are a chatbot,聊天机器人的回答将是 so you think I'm a chatbot

使用这种技术进行的替换主要涉及人称代词(you → me)和动词(you are → I am),因此将所有第一人称形式转换为第二人称形式,反之亦然。

检查值与模式的一致性

任何使用过文字处理程序的人都必须面对在括号内搜索文本字符串的问题。也许我们并不知道,我们遇到了模式匹配问题。模式匹配是一种检查标记序列是否具有某种模式的过程,即一组遵循特定模式的字符。

词汇标记,或简称为标记,是一个具有分配和因此识别的意义的字符串。它由一个标记名称和一个可选的标记值组成的对构成。标记名称是词汇单元的类别。

至于聊天机器人对输入的解释,模式匹配对于识别某些消息集是有用的。例如,通过模式匹配,你可以对包含单词hellohi的所有消息回答Hello!;或者你可以通过检查最后一个标记是否为?来识别消息是问题类型。

正则表达式是模式识别的一个非常有用的工具,它提供了一种识别字符串集合的符号系统。

维护上下文

存储上下文是一种策略,用于跟踪之前说过的话,并能够将其用于对话。当聊天机器人的响应不能仅基于用户发送的最后一条消息,而必须从某些之前的消息中获取信息时,这变得必要。

为了更好地理解上下文管理的作用,让我们举一个例子:

  • 用户:我的名字是 Giuseppe.

  • 聊天机器人:好的,Giuseppe。

  • 用户:我的名字是什么?

  • 聊天机器人:你之前在叫你 Giuseppe 之前告诉过我。

如果用户尚未声明他的名字,聊天机器人的回答将不得不类似于:

  • 用户:我的名字是什么?

  • 聊天机器人:你仍然没有告诉我你的名字。

因此,理解如何管理上下文以形成答案是很简单的。这对于使聊天机器人看起来不那么机械而更有人性化至关重要。

记住之前的消息对于检测用户重复发送消息是有用的,或者它可以通过在选择下一个之前检查最后一条消息的值来防止相同的聊天机器人发送相同的消息。

聊天机器人架构

聊天机器人的主要模块是对话管理器。该模块控制人机交互的流程。它接收用户的请求作为输入,并决定系统的响应应该是什么。它将以某种形式记住对话上下文,例如通过键值对,来管理用户和系统之间多步骤的对话。

为了使对话管理器能够为用户提出的请求选择正确的答案,有必要理解用户意图。在最能理解人类语言的先进聊天机器人中,用户的表达将被转换为包含用户意图和实体的语义表示。这项操作将由自然语言理解模块执行。该模块必须事先经过训练,以理解开发者先前识别的一系列用户意图。该模块基于自然语言理解(NLU)组件。

在语音输入的情况下,系统还必须配备一个可以将输入转换为文本的语音识别模块,然后再将其传递到自然语言理解模块。在操作结束时,系统响应(输出)必须首先通过语音合成器模块处理,该模块将系统的文本响应转换为语音。

当用户输入被理解时,对话管理器会采取行动。为了执行操作或生成响应,对话管理器从数据源检索所需信息。之后,响应生成组件生成响应消息并发送回用户。以下是一个聊天机器人架构方案图:

图片

为了跟踪上下文,对话管理器保持对话状态,以了解请求是否与之前的对话相关,或者是否将新主题引入对话。

自然语言处理

自然语言处理(NLP)是计算语言学的一个领域,它处理计算机与自然语言之间的交互。

计算语言学通过计算机方法处理自然语言的分析和处理。它侧重于发展自然语言功能的描述性形式化,以便它们可以被转换成计算机可以执行的程序。

传统上,计算机要求你通过编程语言与之交互,因此它应该是一种精确、无歧义且高度结构化的通信方式,使用有限数量的已知命令。相反,人类语言并不精确;它通常是模糊的,语言结构可能取决于许多不同的变量,例如方言和各种社会环境。

因此,NLP 是一个极其重要的领域,因为它研究和试图解决计算机在解释或分析人类语言时遇到的全部困难。人类语言的众多歧义使得算法理解人类语言特别困难。要理解一个话语,有必要对现实和周围世界有更广泛的知识。实际上,仅仅了解每个单词的含义并不足以正确解释句子的信息;相反,它可能导致矛盾和无意义的交流。

自然语言的研究是通过一系列精确顺序的步骤进行的,这些步骤以不断增长的语义价值为特征,如下所述:

  • 发音并解码一种语言的声音,使我们能够识别声音和字母。

  • 了解一种语言中的单词,它们的结构(复数/单数)以及它们的组织(名词、动词和形容词)。词汇分析识别构成该语言的词汇并从词典中找到定义。形态分析识别复数/单数结构、动词方式和动词时间;并为每个单词分配其自身的形态类别,即形容词、名词和动词。

  • 在复杂成分(词性)中组合单词。句法识别词性作为主语、谓语、补语或具有单一意义的单词组,如热狗,或具有完整句法树派生的名词和动词部分。

  • 将意义赋予简单和复杂的语言表达。语义学试图根据上下文识别单词的意义。

  • 在适合交流目的的语境、情境和方式中使用句子。实用主义者观察语言是如何以及为什么目的被使用的,区分它是叙述、对话、隐喻等问题。

然后将获得的结果应用于 NLP 的两个主要类别:

  • 自然语言生成NLG),它涉及将数据库中的信息转换为人类可读语言

  • NLU,它将人类语言转换为程序易于操作的形式

NLP 面临许多问题:

  • 语音分割:将语音轨道转换为具有完整意义的字符和单词

  • 文本分割:识别用汉字而不是字母书写的文本中的单个单词(如中文、日语、泰语等)

  • 词性标注:识别句子中的语法元素,如名词、形容词、动词、代词

  • 词义消歧:从上下文中推断出通常用于表示多个概念的术语的意义

  • 不完整或不规则输入:识别和纠正任何地方口音、打字错误或由光学字符识别工具产生的错误

语言领域阐述的困难也可以通过考虑自然语言本身最明显的特征来解释:

  • 灵活性,因为它使用不同的方式来确认同一个事实

  • 含糊性,因为同一个陈述可以有多个含义

  • 动态性,由于新词的不断创造

正是因为这些特殊性,自然语言的理解通常被认为是一个 AI 完全问题,即一个其解决方案相当于创建 AI 本身的问题。实际上,理解文本需要理解与之相关的概念,因此需要广泛的知识和对现实的深刻理解,以及强大的操纵能力。

AI 完全被定义为最困难的问题;也就是说,它们呈现的计算问题等同于解决 AI 本身的问题——使计算机像人一样智能。因此,AI 完全这个术语表明了一个不会通过简单的特定算法解决的问题。

对于人类来说,语言理解是心理过程的结果,这个过程在机器中无法复制;此外,语言是人与人之间沟通和互动的一种形式,它反映了意义的表面,并使人们能够相互理解。然而,无论计算机的软件多么复杂,它仍然基于先验确定的程序。

自然语言理解

NLU 包括阅读用自然语言表达的文字;通过赋予其中存在的术语、句子和段落以意义来确定其含义,并通过对这些元素进行推理来揭示它们的显性或隐性属性。特别是,在建模文本表示时最突出的问题之一是捕捉概念之间的语义关系。为了解决这个任务,文献中提出了几种方法,其中一些提到了访问外部知识库。另一些方法则构建语义分布空间,分析文本集合的内容,而不使用先验知识。

在 NLU 的定义下,计算机应用的范围非常广泛,从简单的操作,如给机器人的简短命令,到复杂的操作,如对文本的全面理解。在现实世界中,现在广泛使用基于 NLU 的算法;例如,对电子邮件中的文本进行分类以分配标签,不需要对文本有彻底的理解,但它需要处理许多词汇项。

文本输入的拆解和解析过程非常复杂,因为输入中出现了未知和意外的特征,并且在输出语言时需要确定适用于它的适当句法和语义方案(这些因素是预先确定的)。在下面的图中,你可以看到 NLU 过程的流程图:

图片

NLU(自然语言理解)帮助我们分析输入文本的语义特征并从内容中提取元数据,例如类别、概念、情感、实体、关键词、元数据、关系和语义角色。通过 NLU,开发者将文本翻译成机器可读的正式表示,使其内容的相关方面明确化。

Google Cloud Dialogflow

从聊天机器人到物联网设备,广泛使用的对话式虚拟助手应用能够以最自然的方式与人类语言互动,这表明需要创建更多引人入胜的个人交互。挑战是双重的:不仅需要识别和传输优化的基本信息,还需要涉及用户并帮助他们实现目标。这要求自动系统尽可能适应其目标用户的语言,这得益于数据分析和机器学习及人工智能技术的力量。

Dialogflow 概述

Google 通过 Dialogflow 应对这一挑战,Dialogflow 是一个基于机器学习的创建语音和文本对话应用的平台。它支持 14 种语言,并且可以通过服务 API 与主要的聊天平台集成,如 Google Assistant、Facebook Messenger、Slack、Skype、Telegram 以及其自身的应用。

最近,鉴于开发者对在标准版本中添加商业功能的大量需求,Google 宣布推出 Dialogflow 企业版,目前处于测试阶段。

以下是一些 Dialogflow 提供的功能:

  • 基于机器学习的对话交互:Dialogflow 使用自然语言处理(NLP)来创建更快的对话体验并快速迭代。只需提供一些用户可能说的话的例子,Dialogflow 就会创建一个特定的模型,该模型可以学习激活哪些动作以及提取哪些数据以提供最相关和最准确的答案。

  • 一次创建,处处部署:使用 Dialogflow 创建对话应用,并将其部署到您的网站、应用或包括 Google Assistant 和其他流行消息服务在内的 32 个不同平台。Dialogflow 还包括多语言支持和多语言体验,以覆盖全球各地的用户。

  • 高级满足选项:满足意味着对用户所说内容的相应动作,例如处理食品订单或激活对用户问题的正确答案。为此,Dialogflow 允许您连接到任何 Webhook,无论它托管在公共云还是本地。Dialogflow 集成的代码编辑器允许您直接在 Dialogflow 控制台中编码、测试和实施这些动作。

  • 语音识别与语音识别:Dialogflow 使对话应用能够响应命令或语音对话。它可以通过一个 API 调用实现,该调用结合了语音识别和自然语言理解。

除了理解自然语言之外,Dialogflow 的灵活性也允许开发者超越决策结构和功能,例如与云函数的深度集成,可以直接将其界面中的基本无服务器脚本写入其中,这使得 Dialogflow 与一些竞争对手区分开来。Dialogflow 还简化了与其他应用程序的连接,无论它们托管在哪里。如果你想要将你的对话应用与你的订单和发货系统集成,这将是必需的。

Dialogflow 基本元素

在详细分析构建聊天机器人的实际案例之前,建议详细分析 Dialogflow 的基本元素。我们现在将介绍在聊天机器人构建中最常用的元素。

代理

代理是一个响应特定任务的程序。它可以是一个负责酒店房间预订的接待员。或者它可能是一个了解所有产品和价格表的在线商店的商业专家。或者它可能是一个为我们购买的家用电器提供合格技术支持的合格技术支持人员。或者它可能是汽车的机载计算机。

重要的是,一个智能代理有一个特定的目的和有限的知识库;我们并不感兴趣与那个负责航班预订的机器人下棋,而且无论如何它也无法做到这一点。

使用 Dialogflow,创建代理非常简单;只需访问服务的初始页面,点击创建代理按钮,给它起一个名字,如下面的截图所示:

图片

要访问 Dialogflow,只需使用 URL dialogflow.com/。你可以注册或使用 Google 账户并登录。

意图

意图是最终用户可以向代理提出的问题。预订酒店房间是一个意图;另一个意图是咨询午餐时间,或者取消已经做出的预订。

理解用户的请求是什么是代理的智能部分,因为请求可以用自然语言以多种方式表达,即我们人类所谈论的内容。

代理将通过识别最接近的意图来尝试解释用户的请求。自然地,这种关联并不总是精确的;事实上,会返回可能的解释的排名。但是,从这个角度来看,我们可以通过提供更多相同请求的替代示例来改进答案。此外,还可以应用机器学习算法来从之前的答案中学习。

实体

如果一个意图与请求匹配,实体对应于细节。在预订酒店房间时,您需要知道确切的日期、将入住的人或用户的请求。从代理的设计角度来看,像酒店房间这样的实体被定义,并包含所有必要的细节。

Dialogflow 有一系列已创建的系统实体,这些实体有助于管理更简单的概念(例如,日期)。开发者可以定义一系列实体(开发实体)以概括代理的行为。最后,最终用户为每个请求创建一个实体。开发实体可以具有由列表确定的值、执行映射或由其他实体组成,等等。

动作

到目前为止,我们处理的是对用户请求的解释;现在则是回答的问题。实际上,这个概念更为广泛;一旦我们理解了一个请求,我们就可以满足它,例如,预订房间、开票或与餐厅沟通。但如果我们在制作聊天机器人,答案可能将对应于动作。

一个动作对应于当用户的输入触发特定意图时,应用程序将采取的步骤。动作的名称及其参数都在意图的动作部分定义。

上下文

客人何时到达酒店?你吃了什么?这些短语在没有上下文的情况下是没有意义的,但在更广泛的对话上下文中变得可以理解;这就是上下文的作用。从上下文中收集参数的语法非常简单:

  # context_name.parameter_name

使用 Dialogflow,上下文保持时间为 10 分钟或五次请求。

使用 Dialogflow 构建聊天机器人

在分析了 Dialogflow 的主要组件之后,现在是时候关注实际应用了。实际上,我们将创建一个简单的聊天机器人,帮助用户检索关于世界上最美丽城市的天气信息。

首先要做的是创建代理,即包含您想要向用户提供的意图、实体和答案的项目。意图是收集用户请求(使用实体)并指导代理相应响应的机制。对于不包含对话外收集信息的简单答案,您可以直接在意图中定义答案。您可以使用自己的逻辑和 Webhook 来执行更复杂的响应。

Webhook 是调用执行要执行的动作的代码的 URL。与其它环境不同,Dialogflow 还允许您使用 HTTP 协议(而不仅仅是 HTTPS 协议)。

代理创建

要创建代理,请执行以下步骤:

  1. 如果您还没有 Dialogflow 账户,请注册。如果您已有账户,请登录。

  2. 点击左侧导航中的“创建代理”并填写字段。以下窗口将被打开:

图片

在此窗口中,我们需要设置一些参数:

    • 代理的名称。

    • 代理的主要语言。

    • 时区设置。日期和时间使用此时区进行解析。

    • Google 项目。这使 Cloud functions、Google 动作和权限管理成为可能。

已有一系列代理(预构建代理)可用于某些类型的请求,可以根据您的需求进行定制和丰富。可用的代理数量取决于语言;在英语中,有超过 30 个不同的代理可用。

  1. 点击保存按钮。

意图定义

如我们之前所说,用户的意图是他们的目的,最终目标。例如,订购某物、想要在用户窗口上激活某物、寻找节目,或者只是说再见。聊天机器人应该能够根据从用户消息中检测到的意图执行一些操作。

要创建一个意图,请点击“意图”旁边的加号图标;以下窗口将被打开:

图片

在此窗口中,我们需要设置一些参数:

  • 意图名称:意图的名称。

  • 上下文:这是用于管理对话流程的。

  • 事件:这是一个允许您通过事件名称而不是用户查询来调用意图的功能。事件可以被外部服务用来触发 Dialogflow 意图,例如,Google Assistant 的内置意图。

  • 训练短语:必须以自然语言报告识别意图的短语。您可以使用示例(示例模式由“图标”识别)和模板(模板模式由“@”图标识别)。还提供了其他句子的示例,越多,代理将越智能,或者他将能够更好地识别用户请求并解决歧义。

  • 动作和参数:这指定了要执行的可能动作及其可以从对话中提取的参数。

  • 响应:当识别到意图时,必须报告返回给用户的答案。为了使其更人性化,您可以输入相同答案的不同变体。答案也可以参数化,并且根据所使用的集成,它们可以由丰富的消息组成。

  • 履行:调用网络服务以连接您的后端。将意图、参数和上下文发送到您的云函数或网络服务。执行必要的逻辑并以书面、口语或视觉响应进行响应。

当插入示例句子时,它会自动标记,识别收集为实体的部分。一个代理总是包含一个默认的回退意图,收集所有没有识别到其他意图的情况。

我们想要插入的第一个意图的目的是指定与我们的天气预报相关的位置。我们说意图代表用户的意图,因此我们需要思考用户可能会提出哪些问题以获取天气预报。我们需要不同的意图,因为询问相同内容的方式有很多。识别意图的过程是将用户可以用来表达意图的所有可能方式映射出来。我们可能期望从用户那里得到的要求应在训练短语部分中指定。为了开始,让我们插入以下短语:

  • 罗马的天气怎么样

  • 罗马的天气怎么样

  • 罗马的天气

  • 罗马天气预报

要插入单个短语,只需在训练短语文本字段中添加用户表达式,然后按Enter键添加另一个短语。我们会注意到句子将被添加到我们的意图声明中。特别是,我们可以看到单词 Rome 被突出显示。这意味着它被标记为分配给现有城市实体的参数,如下面的截图所示:

图片

我们继续插入句子。在定义位置之后,还需要定义时间。很明显,用户将需要知道特定日期的预测,例如今天或明天。然后我们也包括以下短语:

  • 今天天气怎么样

  • 明天的天气预报

  • 罗马今天的天气预报

如前所述,时间参数也被突出显示,这次颜色不同。最后一句很有趣,因为它包含了一个date参数和一个geo-city参数,如下面的截图所示:

图片

要开始,我们保留其他字段不变,只关注聊天机器人将提供给用户的答案。到目前为止,我们还没有考虑任何外部参考来检索用户请求的信息。这意味着至少现在,我们不得不插入这样的模糊答案:

  • 很抱歉,我现在没有这个信息

  • 日期为$date 的预报不可用

  • 在$geo-city 的日期为$date 的天气预报不可用

在最后两个短语中,我们插入了以下参考实体:$date$geo-city。因此,当代理响应时,它会考虑收集到的参数值,并使用包含这些值的回复。

一旦完成,我们点击保存按钮。以下消息出现在窗口的右下角:

  • 意图已保存

  • 代理训练开始

  • 代理训练完成

意义很明确。既然你的代理能够理解用户的基本请求,现在就尝试一下你到目前为止所做的一切。要尝试新创建的代理,我们可以使用右上角控制台中的相应框。为此,我们只需输入一个请求。让我们通过输入与“训练短语”部分给出的示例略有不同的请求来测试我们的代理。例如,我们询问:“今天罗马的天气怎么样”。之后,我们按下Enter键,随后返回以下窗口:

图片

我们可以理解那些未能恢复搜索信息的用户的挫败感,但至少现在我们可以满意,因为代理已经正确理解了问题并提供了合理的答案。记住,至少目前,预报数据是不可用的,这就是代理所说的。此外,正如预期的那样,代理已经识别出两个参考实体,并重新使用它们来构建响应。

摘要

在本章中,我们发现了聊天机器人的神奇世界。聊天机器人是机器人,通过与用户进行聊天互动,能够通过执行极其有限的任务来帮助他们:提供当前账户的信息、购票、接收天气预报等。

首先,我们研究了该主题的基本概念,从 20 世纪 50 年代聊天机器人的历史开始,包括艾伦·图灵的努力以及各种随后的聊天机器人实现,它们完善了基本概念。Eliza、Parry、Jabberwacky、Dr. Sbaitso、ALICE、SmarterChild 和 IBM Watson 是最重要的例子。随着时间的推移和技术的发展,越来越多的复杂人工智能方法被创造出来。

在介绍基本概念之后,我们专注于聊天机器人的设计技术,然后转向分析聊天机器人的架构。我们探讨了自然语言处理(NLP)和自然语言理解(NLU)的有趣领域。

在本章的最后部分,我们介绍了基于机器学习的语音和文本对话应用平台 Google Cloud Dialogflow。它支持 14 种语言,可以与主要的聊天平台集成。最后,我们创建了一个简单的聊天机器人,帮助用户获取世界上最美城市——罗马的天气预报。这至少是一个心灵旅行的好机会。

posted @ 2025-09-04 14:10  绝不原创的飞龙  阅读(2)  评论(0)    收藏  举报