数据科学入门指南-全-

数据科学入门指南(全)

原文:Introducing Data Science

译者:飞龙

协议:CC BY-NC-SA 4.0

第一章. 大数据世界中的数据科学

本章涵盖

  • 定义数据科学和大数据

  • 识别不同类型的数据

  • 获取对数据科学过程的洞察

  • 介绍数据科学和大数据领域

  • 通过 Hadoop 的示例进行工作

大数据 是一个总称,用于描述任何数据集,其规模或复杂性如此之大或复杂,以至于使用传统的数据管理技术(例如,关系数据库管理系统 RDBMS)处理它们变得困难。广泛采用的 RDBMS 一直被视为一种万能解决方案,但处理大数据的需求已经证明并非如此。数据科学 涉及使用方法来分析大量数据并提取其中包含的知识。你可以将大数据与数据科学之间的关系比作原油与炼油厂之间的关系。数据科学和大数据起源于统计学和传统数据管理,但现在被认为是不同的学科。

大数据的特征通常被称为三个 V:

  • 体积 —有多少数据?

  • 多样性 —不同类型的数据有多多样?

  • 速度 —新数据以多快的速度生成?

通常,这些特征还会补充一个第四个 V,即 veracity:数据有多准确?这四个特性使大数据与传统数据管理工具中发现的数据不同。因此,它们带来的挑战几乎可以在各个方面感受到:数据捕获、整理、存储、搜索、共享、传输和可视化。此外,大数据需要专门的技巧来提取洞察力。

数据科学是统计学的一种进化扩展,能够处理今天产生的海量数据。它将计算机科学的方法添加到统计学的工具箱中。在 Laney 和 Kart 的研究笔记《数据科学家的新角色与数据科学的艺术》中,作者们筛选了数百个数据科学家、统计学家和 BI(商业智能)分析师的职位描述,以检测这些头衔之间的差异。将数据科学家与统计学家区分开来的主要因素是处理大数据的能力以及在机器学习、计算和算法构建方面的经验。他们的工具也往往有所不同,数据科学家的职位描述中更频繁地提到使用 Hadoop、Pig、Spark、R、Python 和 Java 等能力。如果你觉得这个列表让你感到害怕,请不要担心;尽管我们将重点关注 Python,但本书中会逐渐介绍这些工具中的大多数。Python 是数据科学的一个优秀语言,因为它拥有许多数据科学库,并且得到了专业软件的广泛支持。例如,几乎每个流行的 NoSQL 数据库都有一个特定的 Python API。由于这些特性和使用 Python 快速原型设计同时保持可接受性能的能力,它在数据科学领域的影響力正在稳步增长。

随着数据量的持续增长以及利用数据的重要性日益凸显,每位数据科学家在其职业生涯中都将遇到大数据项目。

1.1. 数据科学与大数据的益处和用途

数据科学与大数据在商业和非商业环境中几乎无处不在。用例的数量非常庞大,本书中提供的例子只是触及了可能性的表面。

几乎每个行业的商业公司都使用数据科学和大数据来深入了解他们的客户、流程、员工、完成情况和产品。许多公司使用数据科学来为客户提供更好的用户体验,以及进行交叉销售、升级销售并个性化他们的产品。Google AdSense 就是一个很好的例子,它收集互联网用户的数据,以便将相关的商业信息与浏览互联网的人匹配。MaxPoint (maxpoint.com/us)是另一个实时个性化广告的例子。人力资源专业人士使用人员分析和文本挖掘来筛选候选人、监控员工的情绪以及研究同事之间的非正式网络。人员分析是《点球成金:不公平游戏的胜利艺术》一书的核心主题。在书中(和电影)我们看到,美国棒球的传统选秀过程是随机的,用相关信号取代它则改变了所有的一切。依靠统计数据,他们能够雇佣正确的球员,并在他们具有最大优势的地方与他们对抗。金融机构使用数据科学来预测股市、确定贷款风险以及学习如何吸引新客户为其服务。在撰写本书时,全球至少 50%的交易是由基于由数据科学家(通常被称为“量化分析师”)开发的算法自动完成的,这些数据科学家专注于交易算法,并借助大数据和数据科学技术。

政府机构也意识到数据的价值。许多政府机构不仅依赖内部数据科学家来发现有价值的信息,而且还与公众共享他们的数据。您可以使用这些数据来获得洞察力或构建数据驱动的应用程序。Data.gov只是其中一个例子;它是美国政府开放数据的家园。政府机构中的数据科学家有机会参与各种项目,例如检测欺诈和其他犯罪活动或优化项目资金。爱德华·斯诺登提供了一个著名的例子,他泄露了美国国家安全局和英国政府通信总部的内部文件,清楚地展示了他们如何使用数据科学和大数据来监控数百万个人。这些机构从广泛的来源收集了 500 亿条数据记录,包括谷歌地图、愤怒的小鸟、电子邮件和短信等。然后,他们应用数据科学技术来提炼信息。

非政府组织(NGO)也不陌生于使用数据。他们使用数据来筹集资金并捍卫他们的事业。例如,世界自然基金会(WWF)雇佣数据科学家来提高他们筹款活动的有效性。许多数据科学家将他们的一部分时间投入到帮助 NGOs 中,因为 NGOs 通常缺乏收集数据和雇佣数据科学家的资源。DataKind 就是这样一组数据科学家,他们致力于造福人类。

大学在他们的研究中使用数据科学,同时也为了提高学生的学习体验。大规模开放在线课程(MOOC)的兴起产生了大量数据,这使得大学能够研究这种学习方式如何补充传统课程。如果你想要成为一名数据科学家和大数据专业人士,MOOCs 是一项无价的资产,所以一定要看看其中一些较为知名的:Coursera、Udacity 和 edX。大数据和数据科学领域变化迅速,MOOCs 允许你通过跟随顶尖大学的课程来保持最新。如果你还没有熟悉它们,现在就花时间了解一下;你会像我们一样爱上它们的。

1.2. 数据的方面

在数据科学和大数据中,你会遇到许多不同类型的数据,每种数据通常都需要不同的工具和技术。数据的主要类别如下:

  • 结构化

  • 无结构化

  • 自然语言

  • 机器生成

  • 基于图

  • 音频、视频和图像

  • 流式传输

让我们探索所有这些有趣的数据类型。

1.2.1. 结构化数据

结构化数据是依赖于数据模型并位于记录中固定字段内的数据。因此,它通常很容易在数据库中的表或 Excel 文件中存储结构化数据(图 1.1)。SQL,或结构化查询语言,是管理并查询数据库中数据的首选方式。你也可能遇到一些结构化数据,可能很难在传统的关系型数据库中存储。例如,家谱这样的层次数据就是这样一种数据。

图 1.1. Excel 表格是结构化数据的一个例子。

然而,世界并不是由结构化数据构成的;它是人类和机器强加的。更常见的是,数据是无结构的。

1.2.2. 无结构化数据

无结构化数据是难以适应数据模型的数据,因为其内容是特定于上下文或变化的。无结构化数据的一个例子是你的普通电子邮件(图 1.2)。尽管电子邮件包含结构化元素,如发件人、标题和正文文本,但找到写有关于特定员工的投诉邮件的人数是一项挑战,因为有许多方式可以指代一个人。成千上万的不同语言和方言进一步复杂化了这个问题。

图 1.2. 电子邮件既是无结构化数据的例子,也是自然语言数据的例子。

如图 1.2 所示的人写电子邮件也是一个自然语言数据的完美示例。

1.2.3. 自然语言

自然语言是一种特殊类型的非结构化数据;它难以处理,因为它需要了解特定的数据科学技术和语言学知识。

自然语言处理社区在实体识别、主题识别、摘要、文本补全和情感分析方面取得了成功,但在一个领域训练的模型并不很好地泛化到其他领域。即使是最先进的技巧也无法解读每一条文本的含义。然而,这并不令人惊讶:人类在处理自然语言上也存在困难。它本质上就是模糊的。意义本身的概念在这里也是值得怀疑的。让两个人听同一场对话,他们会得到相同的意义吗?同样的词语在不同的情绪下(如愤怒或喜悦)可能会有不同的含义。

1.2.4. 机器生成数据

机器生成数据是由计算机、过程、应用程序或其他机器在没有人类干预的情况下自动创建的信息。机器生成数据正成为主要的数据资源,并将继续如此。Wikibon 预测,到 2020 年,工业互联网(Frost & Sullivan 创造的一个术语,指复杂物理机械与网络传感器和软件的集成)的市场价值将达到约 5400 亿美元。IDC(国际数据公司)估计,到 2020 年,连接的设备数量将是人数的 26 倍。这个网络通常被称为物联网

由于机器数据量巨大且速度快,其分析依赖于高度可扩展的工具。机器数据的例子包括网络服务器日志、通话详细记录、网络事件日志和遥测数据(图 1.3)。

图 1.3. 机器生成数据的示例

图片

图 1.3 中显示的机器数据非常适合经典的表结构数据库。这不是处理高度互联或“网络化”数据(其中实体之间的关系起着重要作用)的最佳方法。

1.2.5. 基于图或网络的数据

“图数据”可能是一个令人困惑的术语,因为任何数据都可以以图表的形式展示。这里的“图”指的是数学上的图论。在图论中,图是一种数学结构,用于模拟对象之间的成对关系。简而言之,图或网络数据是关注对象关系或相邻性的数据。图结构使用节点、边和属性来表示和存储图形数据。基于图的数据是表示社交网络的自然方式,其结构允许你计算特定指标,例如一个人的影响力和两个人之间的最短路径。

图形数据的示例可以在许多社交媒体网站上找到(图 1.4)。例如,在领英上,你可以看到你在哪家公司认识的人。推特上的关注者列表是另一个基于图形数据的例子。其力量和复杂性来自于多个重叠的节点图。例如,想象这里连接的边表示“Facebook 上的朋友”。想象另一个包含相同人物的图,通过领英连接商业同事。想象一个基于 Netflix 电影兴趣的第三张图。重叠这三个看起来不同的图使得提出更有趣的问题成为可能。

图 1.4. 社交网络中的朋友是图形数据的例子。

图片

图形数据库用于存储基于图形的数据,并使用专门的查询语言(如 SPARQL)进行查询。

图形数据带来了挑战,但对于计算机解释加性和图像数据来说,可能更加困难。

1.2.6. 音频、图像和视频

音频、图像和视频是数据科学家面临特定挑战的数据类型。对于人类来说微不足道的任务,例如在图片中识别物体,对于计算机来说却很具挑战性。MLBAM(美国职业棒球大联盟高级媒体)在 2014 年宣布,他们将增加每场比赛的视频捕捉量,达到大约 7 TB,用于实时比赛分析。体育场的高速摄像机将捕捉球和运动员的动作,以实时计算,例如,防守球员相对于两条底线所走的路径。

最近,一家名为 DeepMind 的公司成功开发了一个算法,能够学习如何玩电子游戏。这个算法将视频屏幕作为输入,并通过深度学习过程的复杂过程学习解释一切。这是一个令人瞩目的成就,促使谷歌收购该公司以用于他们自己的人工智能(AI)开发计划。学习算法接受由计算机游戏产生的数据;这是流数据。

1.2.7. 流数据

虽然流数据可以几乎采取之前任何形式,但它有一个额外的属性。当事件发生时,数据流入系统,而不是批量加载到数据存储中。尽管这并不是真正不同类型的数据,但我们在这里将其视为此类,因为您需要调整您的流程来处理此类信息。

示例包括推特上的“热门趋势”、现场体育或音乐赛事,以及股市。

1.3. 数据科学流程

数据科学流程通常包括六个步骤,如你在图 1.5 中的思维导图中所见。我们在这里简要介绍它们,并在第二章中更详细地处理它们。

图 1.5. 数据科学流程

图片

1.3.1. 确定研究目标

数据科学主要在组织背景下应用。当业务要求你执行一个数据科学项目时,你首先准备一个项目章程。这个章程包含诸如你要研究什么、公司如何从中受益、你需要哪些数据和资源、时间表以及交付成果等信息。在整个书中,数据科学过程将应用于更大的案例研究,你将了解不同的可能研究目标。

1.3.2. 获取数据

第二步是收集数据。你在项目章程中已经说明了需要哪些数据以及在哪里可以找到它们。在这一步中,你确保可以使用这些数据在你的程序中,这意味着检查数据的存续性、质量和可访问性。数据也可以由第三方公司提供,形式多样,从 Excel 表格到不同类型的数据库。

1.3.3. 数据准备

数据收集是一个容易出错的过程;在这个阶段,你提高数据质量,并为后续步骤的使用做准备。这个阶段包括三个子阶段:数据清洗从数据源中移除错误值和跨数据源的不一致性,数据集成通过结合来自多个数据源的信息来丰富数据源,数据转换确保数据以适合在模型中使用的形式。

1.3.4. 数据探索

数据探索涉及对数据的更深入理解。你试图理解变量之间如何相互作用,数据的分布情况,以及是否存在异常值。为了实现这一点,你主要使用描述性统计、可视化技术和简单的建模。这一步通常被称为 EDA,即探索性数据分析。

1.3.5. 数据建模或模型构建

在这个阶段,你使用模型、领域知识和之前步骤中找到的数据的见解来回答研究问题。你从统计学、机器学习、运筹学等领域选择一种技术。构建模型是一个迭代过程,涉及选择模型变量、执行模型和模型诊断。

1.3.6. 展示和自动化

最后,你将结果展示给业务部门。这些结果可以有多种形式,从演示文稿到研究报告。有时你需要自动化执行过程,因为业务可能希望在其他项目中使用你获得的认识,或者使运营流程能够使用模型的结果。

迭代过程

之前对数据科学过程的描述可能会给你一种印象,即你以线性方式走过这个过程,但现实中你往往需要退后一步,重新审视某些发现。例如,你可能在数据探索阶段发现异常值,指向数据导入错误。作为数据科学过程的一部分,你获得渐进式洞察,这可能导致新的问题。为了防止重复工作,确保你在开始时清晰地彻底界定业务问题。

现在我们对这个过程有了更好的理解,让我们来看看相关的技术。

1.4. 大数据生态系统与数据科学

目前存在许多大数据工具和框架,由于新技术快速出现,很容易感到迷茫。一旦你意识到大数据生态系统可以按具有相似目标和功能的技术分组,这将在本节中讨论。数据科学家使用许多不同的技术,但并非所有技术;我们将用单独的一章来介绍最重要的数据科学技术类别。图 1.6 中的思维导图显示了大数据生态系统的组件以及不同技术所属的位置。

图 1.6. 大数据技术可以分为几个主要组件。

让我们来看看图中不同的工具组,看看每个工具的作用。我们将从分布式文件系统开始。

1.4.1. 分布式文件系统

一个 分布式文件系统 与普通文件系统类似,但不同的是它同时运行在多个服务器上。因为它是文件系统,所以你可以做几乎与在普通文件系统上相同的事情。诸如存储、读取、删除文件以及为文件添加安全措施等操作是每个文件系统的核心,包括分布式文件系统。分布式文件系统具有显著的优势:

  • 它们可以存储比任何单个计算机磁盘都大的文件。

  • 文件会自动在多个服务器之间进行复制,以实现冗余或并行操作,同时隐藏了这一过程的复杂性,对用户来说不可见。

  • 系统易于扩展:你不再受单个服务器内存或存储限制的约束。

在过去,通过将所有内容迁移到具有更多内存、存储和更好 CPU 的服务器(垂直扩展)来增加规模。如今,你可以添加另一个小型服务器(水平扩展)。这一原则使得扩展潜力几乎无限。

目前最知名的分布式文件系统是 Hadoop 文件系统 (HDFS)。它是对 Google 文件系统的一个开源实现。在这本书中,我们专注于 Hadoop 文件系统,因为它是最常用的。然而,还存在许多其他分布式文件系统:Red Hat 集群文件系统Ceph 文件系统Tachyon 文件系统,仅举三个例子。

1.4.2. 分布式编程框架

一旦你在分布式文件系统中存储了数据,你希望利用它。在分布式硬盘上工作的一个重要方面是,你不会将数据移动到程序中,而是将程序移动到数据中。当你从零开始使用像 C、Python 或 Java 这样的通用编程语言时,你需要处理分布式编程带来的复杂性,比如重启失败的任务、跟踪不同子进程的结果等等。幸运的是,开源社区已经为这些开发了众多框架,这些框架为你提供了与分布式数据一起工作的更好体验,并帮助你应对许多挑战。

1.4.3. 数据集成框架

一旦你建立了分布式文件系统,就需要添加数据。你需要将数据从一个来源移动到另一个来源,这正是数据集成框架如 Apache Sqoop 和 Apache Flume 大显身手的地方。这个过程类似于传统数据仓库中的提取、转换和加载过程。

1.4.4. 机器学习框架

当数据已经到位时,就是提取宝贵见解的时候了。这时,你需要依赖机器学习、统计学和应用数学等领域。在第二次世界大战之前,所有事情都需要手工计算,这严重限制了数据分析的可能性。第二次世界大战后,计算机和科学计算得到了发展。一台计算机可以完成所有的计数和计算,从而打开了无限的可能性。自从这一突破以来,人们只需要推导出数学公式,将它们写成算法,并加载他们的数据。如今,可用的数据量如此巨大,一台计算机已无法独自处理工作负载。事实上,在上一个千年中开发的几个算法,即使你能使用地球上所有的计算机,也无法在宇宙末日之前终止。这与时间复杂度(en.wikipedia.org/wiki/Time_complexity)有关。一个例子是尝试通过测试所有可能的组合来破解密码。一个例子可以在stackoverflow.com/questions/7055652/real-world-example-of-exponential-time-complexity找到。旧算法的最大问题之一是它们扩展性不好。今天我们需要分析的数据量如此之大,这变得成问题,需要专门的框架和库来处理这么大的数据量。Python 最受欢迎的机器学习库是 Scikit-learn。它是一个出色的机器学习工具箱,我们将在本书的后面使用它。当然,还有其他 Python 库:

  • PyBrain 用于神经网络 —神经网络是模仿人类大脑学习机制和复杂性的学习算法。神经网络通常被视为高级和黑盒。

  • NLTK 或自然语言工具包 —正如其名所示,它的重点是处理自然语言。这是一个功能丰富的库,附带多个文本语料库,以帮助您建模自己的数据。

  • Pylearn2 —另一个机器学习工具箱,但比 Scikit-learn 成熟度略低。

  • TensorFlow —由谷歌提供的用于深度学习的 Python 库。

当然,这个领域不仅限于 Python 库。Spark 是一个新的 Apache 许可的机器学习引擎,专注于实时机器学习。它值得一看,你可以在spark.apache.org/了解更多关于它的信息。

1.4.5. NoSQL 数据库

如果你需要存储大量数据,你需要一种专门用于管理和查询这些数据的软件。传统上,这通常是关系数据库如 Oracle SQL、MySQL、Sybase IQ 等领域的游戏规则。虽然它们仍然是许多用例的首选技术,但新的数据库类型已经在 NoSQL 数据库的范畴下出现。

这个组的名称可能会误导人,因为在这个上下文中,“No”代表“不仅”。SQL 功能不足并不是范式转变的最大原因,许多 NoSQL 数据库已经实现了 SQL 的版本。但是,传统数据库的不足之处在于它们无法很好地扩展。通过解决传统数据库的几个问题,NoSQL 数据库允许数据几乎无限增长。这些问题与大数据的每个属性相关:它们的存储或处理能力无法扩展到单个节点,并且它们没有处理流、图或非结构化数据形式的方法。

已经出现了许多不同类型的数据库,但它们可以被归类为以下几种类型:

  • 列数据库 —数据以列的形式存储,这使得算法能够执行更快的查询。较新的技术使用单元格存储。类似表的结构仍然很重要。

  • 文档存储 —文档存储不再使用表,而是将每个观察结果存储在文档中。这允许有更多灵活的数据模式。

  • 流数据 —数据不是批量收集、转换和汇总,而是在实时进行。虽然我们将其分类为数据库以帮助您选择工具,但它更是一种特定类型的问题,推动了像 Storm 这样的技术创造。

  • 键值存储 —数据不是存储在表中;相反,你为每个值分配一个键,例如 org.marketing.sales.2015: 20000. 这可以很好地扩展,但几乎所有的实现都放在了开发者身上。

  • SQL on Hadoop —Hadoop 上的批量查询使用类似于 SQL 的语言,在后台使用 map-reduce 框架。

  • 新 SQL —这个类别结合了 NoSQL 数据库的可扩展性和关系数据库的优点。它们都具有 SQL 接口和关系数据模型。

  • 图数据库 —并非每个问题都最适合存储在表中。某些问题更自然地转化为图论并存储在图数据库中。一个典型的例子是社交网络。

1.4.6. 调度工具

调度工具帮助你自动化重复性任务,并根据事件(如将新文件添加到文件夹)触发作业。这些工具类似于 Linux 上的 CRON,但专门为大数据开发。例如,你可以使用它们在目录中可用新数据集时启动 MapReduce 任务。

1.4.7. 基准测试工具

这类工具的开发是为了通过提供标准化的分析套件来优化你的大数据安装。分析套件是从一组代表性大数据作业中提取的。基准测试和优化大数据基础设施和配置通常不是数据科学家自己的工作,而是专业设置 IT 基础设施的人员的工作;因此,这些内容没有包含在这本书中。使用优化的基础设施可以产生很大的成本差异。例如,如果你可以在 100 台服务器的集群上提高 10%,你就可以节省 10 台服务器的成本。

1.4.8. 系统部署

设置大数据基础设施并不容易,而帮助工程师将新应用程序部署到大数据集群正是系统部署工具大放异彩的地方。它们在很大程度上自动化了大数据组件的安装和配置。这不是数据科学家的核心任务。

1.4.9. 服务编程

假设你已经在 Hadoop 上开发了一个世界级的足球预测应用程序,并且你想允许其他人使用你应用程序做出的预测。然而,你对所有热衷于使用你预测的人的架构或技术一无所知。服务工具在这里表现出色,通过将大数据应用程序作为服务暴露给其他应用程序。数据科学家有时需要通过服务来公开他们的模型。最著名的例子是 REST 服务;REST 代表表示状态转移。它通常用于向网站提供数据。

1.4.10. 安全性

你是否希望每个人都能够访问你所有的数据?你可能需要精细控制对数据的访问,但又不想逐个应用程序地管理。大数据安全工具允许你集中和精细地控制对数据的访问。大数据安全已经成为一个独立的话题,数据科学家通常只是作为数据消费者面对它;很少会自己实现安全。在这本书中,我们不描述如何在大数据上设置安全,因为这是一项安全专家的工作。

1.5. Hadoop 的入门级工作示例

我们将以一个大数据环境中的小型应用程序结束本章。为此,我们将使用 Hortonworks Sandbox 镜像。这是一个由 Hortonworks 创建的虚拟机,用于在本地机器上尝试一些大数据应用程序。在本书的后面部分,您将看到 Juju 如何简化在多台机器上安装 Hadoop 的过程。

我们将使用一个小型的工作薪资数据集来运行我们的第一个示例,但查询包含数十亿行的大数据集同样简单。查询语言将类似于 SQL,但在幕后将运行一个 MapReduce 作业,并生成一个简单的结果表,然后可以将其转换为条形图。这个练习的最终结果看起来像 图 1.7。

图 1.7. 最终结果:按工作描述的平均薪资

为了尽可能快地启动,我们在 VirtualBox 中使用 Hortonworks Sandbox。VirtualBox 是一种虚拟化工具,允许您在自己的操作系统内运行另一个操作系统。在这种情况下,您可以在已安装的操作系统内运行带有现有 Hadoop 安装的 CentOS。

要在 VirtualBox 上将沙盒启动并运行,需要几个步骤。注意,以下步骤是在撰写本章时(2015 年 2 月)适用的:

1. 从 hortonworks.com/products/hortonworkssandbox/#install 下载虚拟镜像。

2. 启动您的虚拟机主机。VirtualBox 可以从 www.virtualbox.org/wiki/Downloads 下载。

3. 按下 CTRL+I 并从 Hortonworks 选择虚拟镜像。

4. 点击下一步。

5. 点击导入;稍等片刻,您的镜像应该已经导入。

6. 现在选择您的虚拟机并点击运行。

7. 稍等片刻,启动带有 Hadoop 安装的 CentOS 发行版,如图 1.8 所示。注意这里的沙盒版本是 2.1。在其他版本中可能会有所不同。

图 1.8. 在 VirtualBox 中运行的 Hortonworks Sandbox

您可以直接登录到机器或使用 SSH 登录。对于此应用程序,您将使用 Web 界面。将您的浏览器指向地址 127.0.0.1:8000,您将看到 图 1.9 中所示的屏幕。

图 1.9. 可在 127.0.0.1:8000 找到的 Hortonworks Sandbox 欢迎界面

Hortonworks 上传了两个样本集,您可以在 HCatalog 中看到它们。只需点击屏幕上的 HCat 按钮,您就会看到可用的表 (图 1.10)。

图 1.10. HCatalog 中可用的表列表

要查看数据内容,请点击样本 _07 条目旁边的浏览数据按钮,以获取下一屏幕 (图 1.11)。

图 1.11。表格的内容

这看起来像是一个普通的表格,而 Hive 是一个工具,让你可以用 SQL 像普通数据库一样接近它。没错:在 Hive 中,你使用 HiveQL(一种普通的 SQL 方言)来获取结果。要打开 Beeswax HiveQL 编辑器,请点击菜单中的 Beeswax 按钮(图 1.12)。

图 1.12。您可以在 Beeswax HiveQL 编辑器中执行 HiveQL 命令。幕后,它被转换为一个 MapReduce 作业。

要获取你的结果,执行以下查询:

Select description, avg(salary) as average_salary from sample_07 group by
description order by average_salary desc.

点击执行按钮。Hive 将你的 HiveQL 转换为 MapReduce 作业,并在你的 Hadoop 环境中执行它,如图 1.13 所示。

图 1.13。日志显示你的 HiveQL 被转换为一个 MapReduce 作业。注意:这个日志是从 2015 年 2 月的 HDP 版本中来的,所以当前版本可能看起来略有不同。

然而,现在最好避免阅读日志窗口。在这个阶段,它可能会误导人。如果你这是第一次查询,那么可能需要 30 秒。Hadoop 以其预热期而闻名。不过,那个讨论留到以后再说。

过了一段时间,结果出现了。干得好!正如图 1.14 所示,结论是上医学院是一个好的投资。惊讶吗?

图 1.14。最终结果:按职业的平均工资概述

通过这个表格,我们结束了我们的 Hadoop 入门教程。

虽然这一章只是个开始,但有时可能会觉得有点令人不知所措。建议现在先放一放,等到所有概念都彻底解释清楚后再回来。数据科学是一个广泛的领域,因此它包含了一个广泛的词汇表。我们希望在我们的时间里给你一个大多数内容的概览。之后,你可以挑选和选择,并在你最感兴趣的领域磨练你的技能。这就是“介绍数据科学”的全部内容,我们希望你能和我们一起享受这段旅程。

1.6. 摘要

在本章中,你学习了以下内容:

  • 大数据是一个总称,用于任何大型或复杂的数据集集合,以至于使用传统的数据管理技术处理它们变得困难。它们的特点是四个 V:速度、多样性、体积和真实性。

  • 数据科学涉及使用方法来分析从小数据集到大数据集的所有内容。

  • 尽管数据科学流程不是线性的,但它可以被分解为以下步骤:

    1. 确定研究目标

    2. 收集数据

    3. 数据准备

    4. 数据探索

    5. 模型

    6. 展示和自动化

  • 大数据景观不仅仅是 Hadoop。它由许多不同的技术组成,可以归类为以下几类:

    • 文件系统

    • 分布式编程框架

    • 数据集成

    • 数据库

    • 机器学习

    • 安全性

    • 调度

    • 基准测试

    • 系统部署

    • 服务编程

  • 并非每个大数据类别都被数据科学家大量使用。他们主要关注文件系统、分布式编程框架、数据库和机器学习。他们确实会接触到其他组件,但这些是其他专业领域的范畴。

  • 数据可以以不同的形式出现。主要形式包括

    • 结构化数据

    • 非结构化数据

    • 自然语言数据

    • 机器数据

    • 基于图的数据

    • 流数据

第二章. 数据科学流程

本章涵盖

  • 理解数据科学流程的流程

  • 讨论数据科学流程的步骤

本章的目标是在不深入大数据之前概述数据科学流程。你将在后续章节中学习如何处理大数据集、流数据以及文本数据。

2.1. 数据科学流程概述

采取结构化的数据科学方法有助于你在最低成本的情况下最大限度地提高数据科学项目的成功率。这也使得以团队形式承担项目成为可能,每个团队成员都专注于他们最擅长的事情。但是请注意:这种方法可能不适合所有类型的项目,也不一定是做好数据科学的唯一方式。

典型的数据科学流程包括六个步骤,你将通过这些步骤进行迭代,如图 2.1 所示。

图 2.1. 数据科学流程的六个步骤

图 2.1 总结了数据科学流程,并显示了在项目期间你将采取的主要步骤和行动。以下列表是一个简要介绍;本章将更深入地讨论每个步骤。

1. 这个流程的第一步是设定一个研究目标。这里的主要目的是确保所有利益相关者都理解项目的目标方法原因。在每一个严肃的项目中,这都会导致一个项目章程的产生。

2. 第二个阶段是数据检索。你希望有可用于分析的数据,因此这一步骤包括寻找合适的数据并从数据所有者那里获取数据。结果是原始形式的数据,这可能在变成可用的之前需要抛光和转换。

3. 现在你已经拥有了原始数据,是时候对其进行准备了。这包括将数据从原始形式转换为可以直接用于你模型的格式。为了实现这一点,你需要检测和纠正数据中的不同类型错误,合并来自不同数据源的数据,并进行转换。如果你已经成功完成了这一步,你就可以继续进行数据可视化和建模。

4. 第四步是数据探索。这一步骤的目标是深入理解数据。你将基于视觉和描述性技术寻找模式、相关性和偏差。从这个阶段获得的洞察将使你能够开始建模。

5. 最后,我们来到了最吸引人的部分:模型构建(本书中通常称为“数据建模”)。现在是你尝试从项目章程中陈述的洞察或预测的时候了。现在是时候拿出重型武器了,但请记住,研究告诉我们,通常(但不总是)简单模型的组合往往比一个复杂的模型表现更好。如果你正确完成了这一阶段,你几乎就完成了。

6. 数据科学模型的最后一步是展示你的结果和自动化分析,如果需要的话。项目的一个目标就是改变流程和/或做出更好的决策。你可能仍然需要说服企业你的发现确实会按照预期改变业务流程。这就是你可以在你的影响力角色中发光的时候。这一步骤的重要性在战略和战术层面的项目中更为明显。某些项目需要你反复执行业务流程,因此自动化项目可以节省时间。

在现实中,你不会以线性方式从步骤 1 进展到步骤 6。你通常会在这不同阶段之间回退和迭代。

按照这六个步骤进行,可以提高项目成功率并增加研究结果的影響力。这个过程确保你在开始查看数据之前就有一个明确的研究计划,对商业问题的良好理解,以及清晰的交付成果。你流程的第一步是关注获取高质量数据作为你模型的输入。这样你的模型在后期将表现得更好。在数据科学中,有一句众所周知的话:垃圾输入等于垃圾输出

跟随一种结构化方法的好处之一是,你在寻找最佳模型的同时,更多地处于原型模式。在构建原型时,你可能会尝试多个模型,而不会过分关注程序速度或编写符合标准代码等问题。这让你能够专注于带来商业价值。

并非每个项目都是由企业自身发起的。在分析过程中获得的认识或新数据的到来可以催生新的项目。当数据科学团队产生一个想法时,已经进行了工作来提出建议并找到商业赞助者。

将项目划分为更小的阶段也允许员工作为一个团队一起工作。不可能在所有事情上都是专家。你需要知道如何将所有数据上传到所有不同的数据库中,找到一个既适合你的应用程序也适合公司内部其他项目的最佳数据方案,然后跟踪所有统计和数据挖掘技术,同时还要成为演示工具和商业政治的专家。这是一项艰巨的任务,这也是为什么越来越多的公司依赖于一个专家团队而不是试图找到一个人能做所有的事情。

我们在本节中描述的流程最适合只包含几个模型的数据科学项目。它并不适合所有类型的项目。例如,包含数百万个实时模型的项目需要比我们描述的流程不同的方法。尽管如此,一个初出茅庐的数据科学家按照这种方式工作可以走得很远。

2.1.1. 不要成为过程的奴隶

并非每个项目都会遵循这个蓝图,因为你的流程受数据科学家、公司以及你所从事的项目性质的影响。一些公司可能要求你遵循严格的协议,而其他公司则可能采用更为非正式的工作方式。通常情况下,当你处理复杂项目或涉及多人或资源时,你需要一个结构化的方法。

敏捷项目模型是迭代顺序流程的替代方案。随着这种方法在 IT 部门和整个公司中赢得更多支持,它也被数据科学社区所采纳。尽管敏捷方法适合数据科学项目,但许多公司政策可能会倾向于更严格的数据科学方法。

在一开始就规划数据科学流程的每一个细节并不总是可能的,而且你通常会在流程的不同步骤之间迭代。例如,在简报之后,你开始你的正常流程,直到你进入探索性数据分析阶段。你的图表显示了两组——男性和女性——之间的行为差异。你不确定,因为你没有变量来指示客户是男性还是女性。你需要检索额外的数据集来确认这一点。为此,你需要通过审批流程,这表明你(或业务)需要提供一种项目章程。在大公司中,获取完成项目所需的所有数据可能是一项艰巨的任务。

2.2. 第一步:定义研究目标和创建项目章程

项目开始于理解项目的目标原因方法(图 2.2)。公司期望你做什么?管理层为什么如此重视你的研究?它是更大战略图景的一部分,还是源于某人发现的机会的“孤狼”项目?回答这三个问题(目标、原因、方法)是第一阶段的目标,这样每个人都知道该做什么,并可以就最佳行动方案达成一致。

图 2.2. 第一步:设定研究目标

图片

结果应该是一个明确的研究目标、对背景的良好理解、定义清晰的交付成果以及一个包含时间表的行动计划。这些信息最好放在项目章程中。当然,项目章程的长度和正式程度在不同项目和公司之间可能会有所不同。在这个项目的早期阶段,人际交往能力和商业洞察力比卓越的技术能力更为重要,这就是为什么这部分工作通常由更有经验的人员指导。

2.2.1. 花时间理解你的研究目标和背景

一个基本的结果是研究目标,它以清晰和专注的方式陈述了你的作业目的。理解业务目标和背景对于项目成功至关重要。继续提问和设计例子,直到你掌握确切的业务期望,确定你的项目如何融入更大的图景,欣赏你的研究如何改变业务,以及他们如何使用你的结果。没有什么比花了几个月研究某件事,直到那一刻的灵感迸发并解决问题,但当你向组织报告你的发现时,每个人都立即意识到你误解了他们的问题更令人沮丧的了。不要轻率地跳过这个阶段。许多数据科学家在这里失败:尽管他们有数学智慧和科学才华,但他们似乎从未真正理解业务目标和背景。

2.2.2. 创建项目章程

客户喜欢事先知道他们支付了什么,所以在你对业务问题有很好的理解之后,尝试就可交付成果达成正式协议。所有这些信息最好收集在项目章程中。对于任何重大项目,这将是强制性的。

项目章程需要团队合作,你的输入至少包括以下内容:

  • 清晰的研究目标

  • 项目使命和背景

  • 你将如何进行你的分析

  • 你期望使用的资源

  • 证明这是一个可实现的项目的证据,或者概念证明

  • 可交付成果和成功的衡量标准

  • 时间表

你的客户可以使用这些信息来估算项目成本以及使你的项目成功所需的数据和人员。

2.3. 第 2 步:检索数据

数据科学的下一步是检索所需的数据(图 2.3)。有时你需要进入现场并自己设计数据收集过程,但大多数时候你不会参与这一步骤。许多公司已经为你收集和存储了数据,而且他们没有的通常可以从第三方购买。不要害怕在你的组织之外寻找数据,因为越来越多的组织甚至将高质量的数据免费提供给公众和商业使用。

图 2.3. 第 2 步:检索数据

图片

数据可以以多种形式存储,从简单的文本文件到数据库中的表格。现在的目标是获取所有你需要的数据。这可能很困难,即使你成功了,数据通常就像未经雕琢的钻石:它需要抛光才能对你有任何用处。

2.3.1. 从公司内部存储的数据开始

您的第一步应该是评估公司内部可轻松获取的数据的相关性和质量。大多数公司都有维护关键数据的计划,因此大部分清理工作可能已经完成。这些数据可以存储在由 IT 专业人员维护的官方数据存储库中,例如数据库数据集市数据仓库数据湖。数据库的主要目标是数据存储,而数据仓库旨在读取和分析这些数据。数据集市是数据仓库的子集,旨在服务于特定的业务单元。虽然数据仓库和数据集市是预加工数据的家园,但数据湖包含的是其自然或原始格式的数据。但仍然存在可能性,即您的数据仍然存储在领域专家的桌面上的 Excel 文件中。

即使在您自己的公司内部找到数据有时也可能是一项挑战。随着公司的发展,他们的数据会分散在许多地方。随着人们更换职位和离开公司,数据知识可能会分散。文档和元数据并不总是交付经理的首要任务,因此您可能需要发展一些福尔摩斯式的技能来找到所有丢失的部分。

获取数据访问权限是另一项困难的任务。组织理解数据的价值和敏感性,并通常有政策来确保每个人都能访问他们所需的数据,而无需更多。这些政策转化为被称为防火墙的物理和数字障碍。这些“墙”在大多数国家对于客户数据来说是强制性和规范化的。这也是出于好理由;想象一下,信用卡公司的每个人都能够访问您的消费习惯。获取数据访问权限可能需要时间,并可能涉及公司政治。

2.3.2. 不要害怕四处寻找

如果您所在的组织内部没有数据,那么就看看组织外部。许多公司专门从事收集有价值的信息。例如,尼尔森和 GFK 在零售行业因这一点而闻名。其他公司提供数据,以便您反过来丰富他们的服务和生态系统。推特、领英和 Facebook 就是这种情况。

虽然某些公司认为数据比石油更有价值,但越来越多的政府和组织正将他们的数据免费与世界共享。这些数据的质量可能非常优秀;这取决于创建和管理它的机构。他们分享的信息涵盖了广泛的主题,例如某个地区的意外事故数量或药物滥用情况及其人口统计。当你想要丰富专有数据时,这些数据很有帮助;同时,在家训练数据科学技能时也非常方便。表 2.1 仅展示了日益增长的开放数据提供者中的一小部分。

表 2.1. 应该帮助您开始的开放数据提供者列表
开放数据网站 描述
Data.gov 美国政府开放数据的家园
open-data.europa.eu/ 欧洲委员会开放数据的家园
Freebase.org 从维基百科、MusicBrains 和 SEC 档案等网站检索信息的开放数据库
Data.worldbank.org 世界银行开放数据倡议
Aiddata.org 国际发展开放数据
Open.fda.gov 美国食品药品监督管理局的开放数据

2.3.3. 现在进行数据质量检查,以防止以后出现问题

预计您将花费项目的大部分时间进行数据校正和清洗,有时高达 80%。数据的检索是您在数据科学流程中第一次检查数据。在数据收集阶段,您会遇到的大部分错误都很容易发现,但过于粗心大意会使您花费许多小时解决本可以在数据导入期间预防的数据问题。

您将在导入、数据准备和探索阶段调查数据。区别在于调查的目标和深度。在数据检索阶段,您检查数据是否与源文档中的数据相等,并查看您是否拥有正确的数据类型。这不应该花费太多时间;当您有足够的证据表明数据与您在源文档中找到的数据相似时,您就停止。在数据准备阶段,您进行更细致的检查。如果您在前一阶段做得很好,现在发现的错误也存在于源文档中。重点是变量的内容:您希望消除拼写错误和其他数据输入错误,并将数据带到数据集的共同标准。例如,您可能将 USQ 更正为 USA,将联合王国更正为 UK。在探索阶段,您的重点转向从数据中可以学习到什么。现在您假设数据是干净的,并查看统计属性,如分布、相关性和异常值。您通常会反复进行这些阶段。例如,当您在探索阶段发现异常值时,它们可以指向数据输入错误。现在您已经了解了数据质量在流程中是如何得到提高的,我们将更深入地探讨数据准备步骤。

2.4. 第 3 步:清洗、集成和转换数据

从数据检索阶段接收到的数据可能“像粗糙的钻石。”您的任务现在是对其进行净化和准备,以便在建模和报告阶段使用。这样做非常重要,因为您的模型将表现得更好,您将花费更少的时间来修复奇怪的输出。这几乎无法用足够多的次数来提及:垃圾输入等于垃圾输出。您的模型需要特定格式的数据,因此数据转换总是起作用。尽早纠正数据错误是一个好习惯。然而,在现实环境中,这并不总是可能的,因此您需要在程序中采取纠正措施。

图 2.4 展示了在数据清洗、集成和转换阶段最常见的行为。

图 2.4. 第 3 步:数据准备

这个思维导图现在可能看起来有点抽象,但我们在下一节中会更详细地处理所有这些点。您将看到所有这些行动之间有很大的共性。

2.4.1. 数据清洗

数据清洗是数据科学过程中的一个子过程,它专注于去除数据中的错误,以便您的数据成为其来源过程的真正和一致的表现。

通过“真正和一致的表现”,我们意味着至少存在两种类型的错误。第一种是解释错误,例如,当您理所当然地接受数据中的值时,比如说某人的年龄大于 300 岁。第二种错误指向数据源之间的不一致性或与您公司标准化的值相矛盾。这类错误的一个例子是在一个表中将“Female”放入,在另一个表中将“F”放入,尽管它们代表的是同一件事:这个人女性。另一个例子是在一个表中使用磅,在另一个表中使用美元。可能的错误太多,无法一一列举,但表 2.2 展示了可以通过简单检查检测到的错误类型概述——“低垂的果实”。

表 2.2. 常见错误的概述
一般解决方案
尝试在数据获取链的早期修复问题,或者在其他地方修复它。
错误描述
指向一个数据集中错误值的错误
数据输入错误
空白冗余
不可能的值
缺失值
异常值
指向数据集之间不一致性的错误
与代码簿的偏差
不同的度量单位
不同聚合级别

有时你会使用更高级的方法,例如简单的建模,来寻找和识别数据错误;诊断图可以特别有洞察力。例如,在图 2.5 中,我们使用一个度量来识别看起来不合适的数据点。我们进行回归以熟悉数据并检测单个观察值对回归线的影响。当一个单独的观察值有太大的影响时,这可以表明数据中存在错误,但也可能是一个有效的点。然而,在数据清洗阶段,这些高级方法很少被应用,并且通常被某些数据科学家视为过度。

图 2.5. 被圈出的点对模型有重大影响,值得调查,因为它可能指向一个数据不足的区域,或者可能表明数据中存在错误,但它也可能是一个有效的数据点。

图片

现在我们已经给出了概述,是时候更详细地解释这些错误了。

数据输入错误

数据收集和数据输入是容易出错的过程。它们通常需要人工干预,由于人类只是人类,他们可能会犯拼写错误或分心一秒钟,从而在链中引入错误。但由机器或计算机收集的数据也不是没有错误的。错误可能源于人类粗心大意,而另一些则是由于机器或硬件故障。机器产生的错误示例包括传输错误或提取、转换和加载阶段(ETL)中的错误。

对于小数据集,你可以手动检查每个值。当研究的变量没有很多类别时,可以通过计数表来检测数据错误。当你有一个只能取两个值:“好”和“坏”的变量时,你可以创建一个频率表,看看这些是否真的是唯一存在的两个值。在表 2.3 中,值“Godo”和“Bade”指出至少在 16 个案例中出了问题。

表 2.3. 使用频率表检测简单变量的异常值
数量
1598647
1354468
Godo 15
Bade 1

这种类型的错误大多数都可以通过简单的赋值语句和 if-then-else 规则轻松修复:

if x == "Godo":
    x = "Good"
if x == "Bade":
    x = "Bad"
空白字符重复

空白字符往往难以检测,但会导致与其他冗余字符一样的问题。谁没有因为字符串末尾的空白字符而丢失几天项目时间呢?你要求程序合并两个键,却发现输出文件中缺少了一些观察值。经过几天在代码中寻找,你终于找到了错误。然后是最难的部分:向项目利益相关者解释延迟。ETL 阶段的清理没有很好地执行,一个表中的键在字符串末尾包含了一个空白字符。这导致了“FR” – “FR”这样的键不匹配,丢弃了无法匹配的观察值。

如果你知道要留意它们,修复多余的空白符在大多数编程语言中都很幸运地足够简单。它们都提供了字符串函数,可以删除前导和尾随空白。例如,在 Python 中,你可以使用strip()函数来删除前导和尾随空格。

修复大写字母不匹配问题

大写字母不匹配很常见。大多数编程语言在“巴西”和“brazil”之间做出区分。在这种情况下,你可以通过应用一个返回两个字符串都为小写的函数来解决问题,例如 Python 中的.lower()"Brazil".lower() == "brazil".lower()应该返回true

不可能的值和合理性检查

合理性检查是另一种有价值的数据检查类型。在这里,你检查值是否与物理上或理论上不可能的值相匹配,例如身高超过 3 米的人或年龄为 299 岁的人。合理性检查可以直接用规则表示:

check = 0 <= age <= 120
异常值

异常值是一个似乎与其他观察值距离较远的观察值,或者更具体地说,是一个遵循与其他观察值不同的逻辑或生成过程的观察值。找到异常值的最简单方法是使用包含最小值和最大值的图表或表格。一个例子显示在图 2.6 中。

图 2.6. 分布图有助于检测异常值并帮助你理解变量。

顶部的图表显示没有异常值,而底部的图表显示在期望正态分布时,上侧可能存在异常值。正态分布,或高斯分布,是自然科学中最常见的分布。它表明大多数情况发生在分布的平均值附近,并且当距离平均值更远时,发生频率会降低。底部图表中的高值在假设正态分布时可能指向异常值。正如我们之前在回归示例中看到的,异常值可能会严重影响你的数据建模,因此首先要调查它们。

处理缺失值

缺失值不一定错误,但你仍然需要单独处理它们;某些建模技术无法处理缺失值。它们可能是数据收集过程中出现问题或 ETL 过程中发生错误的指标。数据科学家常用的常见技术列在表 2.4 中。

表 2.4. 处理缺失数据的技术概述
技术 优点 缺点
忽略值 易于执行 你会丢失观察值的信息
将值设置为 null 易于执行 并非每个建模技术/实现都可以处理 null 值
填充静态值,如 0 或平均值 易于执行 你不会从观察到的其他变量中丢失信息 可能会导致模型产生错误的估计
从估计或理论分布中插入一个值 不会太多地干扰模型 执行起来更困难
建模值(非相关) 不会太多地干扰模型 可能会导致对模型过度自信

何时使用哪种技术取决于您的具体情况。例如,如果您没有多余的观测值,省略观测值可能不是一个选择。如果变量可以用稳定的分布来描述,您可以根据这一点进行插补。然而,缺失值实际上可能意味着“零”?这在销售中可能是这种情况:如果没有促销应用于客户篮子,该客户的促销信息缺失,但很可能它也是 0,没有价格折扣。

与代码簿的偏差

通过代码簿或标准值来检测较大数据集中的错误可以使用集合操作来完成。代码簿是您数据的描述,是一种元数据。它包含诸如每个观测值的变量数量、观测值的数量以及变量中每种编码的含义等信息。(例如,“0”等于“负”,“5”表示“非常积极”)。代码簿还说明了您正在查看的数据类型:它是层次结构、图还是其他类型?

您查看那些存在于集合 A 中但不在集合 B 中的值。这些是需要纠正的值。集合是我们工作时使用的数据结构,这并非巧合。仔细考虑您的数据结构是一个好习惯;它可以节省工作并提高程序的性能。

如果您要检查多个值,最好将它们从代码簿放入表格中,并使用差异运算符来检查两个表格之间的差异。这样,您可以直接利用数据库的力量。更多关于这一点的内容请参阅第五章。

不同的测量单位

当整合两个数据集时,您必须注意它们各自的测量单位。一个例子是研究世界各地的汽油价格。为此,您需要从不同的数据提供者那里收集数据。数据集可以包含每加仑的价格,而其他数据集可以包含每升的价格。在这种情况下,简单的转换就可以解决问题。

不同的聚合级别

拥有不同的聚合级别类似于拥有不同的测量类型。一个例子是包含每周数据的数据集与包含工作周数据的数据集。这类错误通常很容易检测到,通过总结(或其逆操作,展开)数据集可以修复它。

在清理数据错误后,您会结合来自不同数据源的信息。但在我们探讨这个主题之前,我们将稍微偏离一下,强调尽早清理数据的重要性。

2.4.2. 尽早纠正错误

一个好的做法是在数据收集链中尽早调解数据错误,并在尽可能少地在你程序内部修复问题的源头的同时修复问题。检索数据是一项困难的任务,组织在它上面花费数百万美元,希望做出更好的决策。数据收集过程容易出错,在一个大组织中,它涉及许多步骤和团队。

数据应在获取时进行清理,原因有很多:

  • 并非每个人都能发现数据异常。决策者可能会基于未能纠正错误数据的应用程序提供的不正确数据做出代价高昂的错误决策。

  • 如果在流程的早期没有纠正错误,那么每个使用该数据的项目的清理工作都必须进行。

  • 数据错误可能指向一个不符合设计的工作流程。例如,两位作者过去都在一家零售商工作,他们设计了一个优惠券系统来吸引更多人并创造更高的利润。在数据科学项目中,我们发现客户滥用优惠券系统并在购买杂货时赚钱。优惠券系统的目标是刺激交叉销售,而不是免费赠送产品。这个缺陷使公司损失了金钱,公司里没有人意识到这一点。在这种情况下,数据在技术上并没有错误,但带来了意外的结果。

  • 数据错误可能指向设备故障,例如损坏的传输线和有缺陷的传感器。

  • 数据错误可能指向软件或软件集成中的错误,这些错误可能对公司至关重要。在我们为一家银行做一个小项目时,我们发现两个软件应用程序使用了不同的本地设置。这导致大于 1,000 的数字出现问题。对于一款应用程序,数字 1.000 代表一个,而对于另一款则代表一千。

在理想世界中,一旦数据被捕获就修复数据是很好的。遗憾的是,数据科学家并不总是对数据收集有发言权,仅仅告诉 IT 部门修复某些事情可能并不会让它变得如此。如果你不能在源头纠正数据,你需要在你的代码中处理它。数据操作并不随着纠正错误而结束;你仍然需要组合你的 incoming data。

最后一点:始终保留原始数据的副本(如果可能的话)。有时你开始清理数据,但会犯错误:以错误的方式估计变量,删除具有有趣额外信息的异常值,或者由于最初的误解而更改数据。如果你保留副本,你就有机会再次尝试。对于在到达时被操作的“流动数据”,这并不总是可能的,你将不得不在能够使用你捕获的数据之前接受一段时间的调整。然而,更困难的事情并不是单个数据集的数据清理,而是将不同的来源组合成一个更有意义的整体。

2.4.3. 从不同的数据源组合数据

你的数据来自几个不同的地方,在这个子步骤中,我们专注于整合这些不同的来源。数据在大小、类型和结构上有所不同,从数据库和 Excel 文件到文本文档都有。

为了简洁起见,本章我们专注于表格结构中的数据。仅就这个主题而言,完全可以写满整本书,而我们选择专注于数据科学过程,而不是为每种类型的数据提供场景。但请记住,还存在其他类型的数据源,例如键值存储、文档存储等,我们将在书中更合适的地方处理这些内容。

组合数据的不同方式

你可以执行两个操作来合并来自不同数据集的信息。第一个操作是连接:用一个表中的信息丰富另一个表中的观察结果。第二个操作是追加堆叠:将一个表中的观察结果添加到另一个表中。

当你合并数据时,你可以选择通过创建视图来创建一个新的物理表或虚拟表。视图的优势是它不会消耗更多的磁盘空间。让我们详细说明这些方法。

连接表格

连接表格允许你将一个表中找到的观察结果的信息与另一个表中找到的信息结合起来。重点是丰富单个观察结果。比如说,第一个表包含有关客户购买的信息,而另一个表包含有关客户居住区域的信

图 2.7. 在项目键和区域键上连接两个表格

图片

要连接表格,你使用代表两个表中相同对象的变量,例如日期、国家名称或社会保障号码。这些公共字段被称为键。当这些键也唯一地定义表中的记录时,它们被称为主键。一个表可能包含购买行为,而另一个表可能包含关于个人的人口统计信息。在图 2.7 中,两个表都包含客户名称,这使得丰富客户支出与客户所在的区域变得容易。熟悉 Excel 的人会注意到这与使用查找函数的相似性。

输出表中结果的行数取决于你使用的确切连接类型。我们将在本书的后面部分介绍不同类型的连接。

添加表格

附加或堆叠表实际上是向另一个表中添加一个表中的观测值。图 2.8 展示了附加表的例子。一个表包含一月份的观测值,第二个表包含二月份的观测值。这些表附加的结果是一个更大的表,包含一月份和二月份的观测值。在集合论中,这相当于并集操作,这也是关系数据库的通用语言 SQL 中的命令。在数据科学中,也使用了其他集合运算符,如集合差和交集。

图 2.8. 从表中附加数据是一个常见的操作,但需要附加的表具有相同的结构。

使用视图模拟数据连接和附加

为了避免数据重复,你实际上是通过视图来组合数据的。在先前的例子中,我们取了月度数据并在一个新的物理表中将其组合。问题是,我们重复了数据,因此需要更多的存储空间。在我们正在处理的例子中,这可能不会造成问题,但想象一下,每个表都包含数以兆字节的数据;那么重复数据就变得有问题了。因此,发明了视图的概念。视图表现得就像你在操作一个表一样,但这个表实际上只是一个为你组合表的虚拟层。图 2.9 展示了如何将不同月份的销售数据虚拟组合成年度销售表,而不是重复数据。然而,视图也有缺点。虽然表连接只执行一次,但创建视图的连接每次查询时都会被重新创建,这比预先计算的表需要更多的处理能力。

图 2.9. 视图帮助您在不复制数据的情况下组合数据。

丰富聚合度量

通过向表中添加计算信息也可以进行数据丰富,例如总销售额或某个地区已售出总库存的百分比(图 2.10)。

图 2.10. 增长、按产品类别划分的销售和排名销售是派生和聚合度量的例子。
产品类别 产品 销售额($) 上月销售额($) 增长 按产品类别划分的销售 排名销售
A B X Y (X-Y) / Y AX NX
运动 运动 1 95 98 –3.06% 215 2
运动 运动 2 120 132 –9.09% 215 1
鞋类 鞋类 1 10 6 66.67% 10 3

类似这样的额外措施可以提供新的视角。查看图 2.10,我们现在有一个汇总的数据集,这反过来可以用来计算每个产品在其类别中的参与度。这在数据探索期间可能很有用,但在创建数据模型时更有用。像往常一样,这取决于具体案例,但根据我们的经验,使用“相对度量”如%销售额(产品销售数量/总销售数量)的模型往往优于使用原始数字(销售数量)作为输入的模型。

2.4.4. 数据转换

某些模型需要其数据具有特定的形状。现在你已经清理并整合了数据,这是你接下来要执行的任务:将你的数据转换成适合数据建模的合适形式。

数据转换

输入变量和输出变量之间的关系并不总是线性的。以形式 y = ae^(bx) 的关系为例。对独立变量取对数可以极大地简化估计问题。图 2.11 展示了如何通过转换输入变量来极大地简化估计问题。有时你可能想将两个变量合并成一个新的变量。

图 2.11. 将 x 转换为 log x 使 x 和 y 之间的关系线性化(右),与未取对数的 x(左)相比。

替代图 2.11

减少变量数量

有时候你有很多变量,需要减少数量,因为它们不会向模型添加新信息。模型中变量过多会使模型难以处理,并且当输入变量过多时,某些技术表现不佳。例如,所有基于欧几里得距离的技术在变量数量不超过 10 个时表现良好。

欧几里得距离

欧几里得距离或“普通”距离是对数学中关于三角形(三角学)的第一件事的扩展:毕达哥拉斯定理。如果你知道直角三角形中 90°角相邻两边的长度,你可以轻松地推导出剩余边的长度(斜边)。这个公式是斜边 = 公式。在二维平面上的两点之间的欧几里得距离是使用类似公式计算的:距离 = 公式。如果你想将这个距离计算扩展到更多维度,将那些更高维度中点的坐标加到公式中。对于三维,我们得到距离 = 公式

数据科学家使用特殊方法来减少变量的数量,但保留最大量的数据。我们将在第三章中讨论这些方法中的几个。图 2.12 展示了减少变量数量如何使理解关键值更容易。它还展示了两个变量如何解释数据集中 50.6%的变异(component1 = 27.8% + component2 = 22.8%)。这些变量被称为“component1”和“component2”,都是原始变量的组合。它们是数据结构的基础的主成分。如果现在还不那么清楚,不要担心,主成分分析(PCA)将在第三章中更详细地解释。你还可以看到第三个(未知)变量的存在,它将观察结果分组分成两个。

图 2.12. 变量减少可以在尽可能多的信息的情况下减少变量的数量。

图片

将变量转换为虚拟变量

变量可以被转换为虚拟变量(图 2.13)。虚拟变量只能取两个值:真(1)或假(0)。它们用于表示可能解释观察结果的分类效应的缺失。在这种情况下,你将为存储在一个变量中的类别创建单独的列,如果该类别存在,则用 1 表示,否则用 0 表示。一个例子是将一个名为“工作日”的列转换为从星期一到星期日的列。你使用一个指标来显示观察结果是否在星期一;你在星期一上放置 1,在其他地方放置 0。将变量转换为虚拟变量是一种在建模中使用的技巧,它受到经济学家的欢迎,但并非仅限于经济学家。

图 2.13. 将变量转换为虚拟变量是一种数据转换,它将具有多个类别的变量分解成多个变量,每个变量只有两种可能的值:0 或 1。

图片

在本节中,我们介绍了数据科学过程中的第三步——清理、转换和整合数据,这将您的原始数据转换为建模阶段的可用输入。数据科学过程的下一步是更好地理解数据的内容以及变量和观察结果之间的关系;我们将在下一节中探讨这一点。

2.5. 第 4 步:探索性数据分析

在探索性数据分析过程中,你将深入挖掘数据(见图 2.14)。当信息以图片形式展示时,信息变得更容易理解,因此你主要使用图形技术来了解你的数据和变量之间的相互作用。这一阶段是关于探索数据,所以在探索性数据分析阶段保持开放的心态和敏锐的观察力是至关重要的。目标不是清理数据,但通常你仍然会发现之前遗漏的异常,迫使你退一步并修复它们。

图 2.14。步骤 4:数据探索

图片

在这一阶段,你使用的可视化技术从简单的折线图或直方图,如图 2.15 所示,到更复杂的图表,如桑基图和网络图。有时,从简单的图表中组合成复合图,可以更深入地了解数据。有时,图表可以动画化或交互化,使其更容易,让我们承认,更有趣。一个交互式桑基图的例子可以在bost.ocks.org/mike/sankey/找到。

图 2.15。从上到下,柱状图、折线图和分布图是探索性分析中使用的一些图表。

图片

Mike Bostock 提供了几乎所有类型图表的交互式示例。尽管他的大多数示例在数据展示方面更有用,但花时间在他的网站上还是值得的。

这些图表可以组合起来提供更多的见解,如图 2.16 所示。

图 2.16。将多个图表一起绘制可以帮助你理解多个变量中的数据结构。

图片

在多个图表上叠加是常见的做法。在图 2.17 中,我们将简单的图表组合成帕累托图,或 80-20 图。

图 2.17。帕累托图是值和累积分布的组合。从这个图中很容易看出,前 50%的国家包含略少于 80%的总金额。如果这个图代表了客户的购买力,而我们销售昂贵的产品,我们可能不需要在每个国家花费我们的营销预算;我们可以从前 50%开始。

图片

图 2.18 展示了另一种技术:刷选和链接。通过刷选和链接,你可以将不同的图表和表格(或视图)组合并链接起来,这样在一个图表中的变化会自动转移到其他图表。这种交互式数据探索有助于发现新的见解。一个详细的例子可以在第九章中找到。这一阶段的数据探索有助于发现新的见解。

图 2.18。链接和刷选允许你在一张图上选择观测值,并在其他图上突出显示相同的观测值。

图片

图 2.18 显示了每个国家的问题的平均得分。这不仅表明了答案之间的高度相关性,而且很容易看出,当你选择子图的几个点时,这些点将对应于其他图表上相似的点。在这种情况下,左图上选定的点对应于中间和右图上的点,尽管它们在中间和右图上对应得更好。

另外两个重要的图表是图 2.19 中显示的直方图和图 2.20 中显示的箱线图。

图 2.19. 示例直方图:5 年年龄间隔内的人数

图片

图 2.20. 示例箱线图:每个用户类别在摄影网站上对某一图片的喜爱程度的分布。

图片

在直方图中,一个变量被划分为离散类别,每个类别中的发生次数被汇总并显示在图表中。另一方面,箱线图不显示有多少观测值,但它确实提供了类别内分布的印象。它可以同时显示最大值、最小值、中位数和其他特征度量。

我们在本阶段描述的技巧主要是视觉的,但在实践中,它们当然不仅限于可视化技术。制表、聚类和其他建模技术也可以是探索性分析的一部分。甚至构建简单的模型也可以是这一步骤的一部分。

现在你已经完成了数据探索阶段,并且对数据有了很好的掌握,是时候进入下一个阶段:构建模型。

2.6. 步骤 5:构建模型

在数据清洁且对内容有良好理解的基础上,你准备好构建模型,目标是做出更好的预测、分类对象或理解你正在建模的系统。这一阶段比探索性分析步骤更加专注,因为你知道你在寻找什么,以及你希望的结果是什么。图 2.21 显示了模型构建的组成部分。

图 2.21. 步骤 5:数据建模

图片

你现在将使用的技巧来自机器学习、数据挖掘和/或统计学领域。在本章中,我们只探索了现有技术的一小部分,而第三章将适当地介绍它们。本书的范围超出了提供更多概念性介绍的范畴,但这对你的入门已经足够;20%的技术将帮助你解决 80%的情况,因为技术之间存在重叠,它们试图实现的目标相似但略有不同。

建立模型是一个迭代的过程。你建立模型的方式取决于你是否选择经典统计学或相对较新的机器学习学派,以及你想要使用的技巧类型。无论如何,大多数模型都包括以下主要步骤:

1. 选择建模技巧和要进入模型中的变量

2. 执行模型

3. 诊断和模型比较

2.6.1. 模型和变量选择

你需要选择你想要包含在模型中的变量和建模技巧。探索性分析的结果应该已经给你一个相当明确的想法,哪些变量将帮助你构建一个好的模型。有许多建模技巧可供选择,为问题选择正确的模型需要你自己的判断。你需要考虑模型性能以及你的项目是否满足使用模型的所有要求,以及其他因素:

  • 模型是否需要转移到生产环境,如果是的话,是否容易实现?

  • 模型的维护有多困难:如果保持不变,它将保持多久的相关性?

  • 模型是否需要易于解释?

当思考完成时,就是采取行动的时候了。

2.6.2. 模型执行

一旦你选择了一个模型,你将需要用代码实现它。

备注

这是我们第一次进入实际的 Python 代码执行,所以请确保你的虚拟环境已经启动并运行。了解如何设置这是必需的知识,但如果这是你第一次尝试,请查看附录 D。

本章的所有代码都可以从www.manning.com/books/introducing-data-science下载。本章附带一个 ipython (.ipynb) 笔记本和一个 Python (.py) 文件。

幸运的是,大多数编程语言,如 Python,已经拥有 StatsModels 或 Scikit-learn 等库。这些包使用了多种最流行的技巧。在大多数情况下,编码一个模型是一个非平凡的任务,因此拥有这些库可以加快这个过程。正如以下代码所示,使用 StatsModels 或 Scikit-learn 进行线性回归(图 2.22)相当容易。如果你自己来做,即使是简单的技巧也需要更多的努力。以下列表显示了线性预测模型的执行。

图 2.22. 线性回归试图拟合一条线,同时最小化到每个点的距离

列表 2.1. 在半随机数据上执行线性预测模型

好吧,我们在这里作弊了,相当严重。我们创建了预测值,目的是预测目标变量的行为。对于线性回归,假设每个 x(预测变量)和 y(目标变量)之间存在“线性关系”,如图 2.22 所示。

我们,然而,通过添加一点随机性,基于预测变量创建了目标变量。这给我们一个拟合良好的模型并不令人惊讶。results.summary() 输出了 图 2.23 中的表格。请注意,确切的结果取决于你得到的随机变量。

图 2.23. 线性回归模型信息输出

图片

让我们忽略这里得到的输出的大部分内容,专注于最重要的部分:

  • 模型拟合 —这里使用 R 平方或调整 R 平方。这个指标是模型捕捉数据中变化量的一个指示。调整 R 平方和 R 平方之间的差异在这里是最小的,因为调整后的 R 平方是正常的 R 平方加上对模型复杂性的惩罚。当引入许多变量(或特征)时,模型会变得复杂。如果你有一个简单的模型可用,你不需要复杂的模型,所以调整 R 平方会惩罚你过度复杂化。无论如何,0.893 是很高的,这应该是由于我们作弊了。存在一些经验法则,但对于商业中的模型,通常认为 R 平方大于 0.85 的模型是好的。如果你想赢得比赛,你需要达到 90% 以上。然而,对于研究来说,通常会发现模型拟合度非常低(甚至小于 0.2)。更重要的是,引入的预测变量对的影响。

  • 预测变量有一个系数 —对于线性模型来说,这很容易解释。在我们的例子中,如果你给 x1 加上“1”,它将使 y 变化“0.7658”。很容易看出找到一个好的预测变量如何成为你获得诺贝尔奖的途径,即使你的整体模型很糟糕。例如,如果你确定某个基因是癌症的一个显著原因,这是一个重要的知识,即使这个基因本身并不决定一个人是否会得癌症。这里的例子是分类,而不是回归,但观点是一样的:在科学研究中,检测影响比完美拟合模型(更不用说更现实)更重要。但何时我们知道一个基因有这种影响?这被称为显著性。

  • 预测变量的重要性 —系数很好,但有时没有足够的证据来显示影响的存在。这就是 p 值的作用。在这里可以长篇大论地解释类型 1 和类型 2 错误,但简短的解释将是:如果 p 值低于 0.05,大多数人认为该变量是显著的。实际上,这是一个任意数字。这意味着预测变量没有任何影响的概率是 5%。你是否接受这种 5% 的错误概率?这取决于你。一些人引入了极端显著(p<0.01)和边际显著阈值(p<0.1)。

线性回归适用于你想预测一个值的情况,但如果你想要对某物进行分类呢?那么你就转向分类模型,其中最著名的是 k-最近邻。

如图 2.24 所示,k 最近邻算法查看一个未标记点附近的标记点,并根据这个预测标签应该是什么。

图 2.24。K 最近邻技术通过查看最近的 k 个点来进行预测。

图片

让我们用 Python 代码和 Scikit learn 库来尝试,如下所示。

列表 2.2。在半随机数据上执行 k 最近邻分类

图片

和之前一样,我们构建了随机相关数据,令人惊讶的是,我们正确分类了 85%的情况。如果我们想深入了解,我们需要评估模型。不要让knn.score()欺骗你;它返回模型准确率,但当我们说“评估模型”时,我们通常是指将其应用于数据以进行预测。

prediction = knn.predict(predictors)

现在,我们可以使用预测结果,并使用混淆矩阵将其与实际情况进行比较。

metrics.confusion_matrix(target,prediction)

我们得到了一个 3x3 的矩阵,如图 2.25 所示。

图 2.25。混淆矩阵:它通过比较预测值与真实值来显示正确分类和错误分类的案例数量。备注:图中的类别(0,1,2)是为了说明而添加的。

图片

混淆矩阵显示我们正确预测了 17+405+5 个案例,所以这是好的。但这真的是一个惊喜吗?不,以下是一些原因:

  • 首先,分类器只有三个选项;通过np.around()标记与上一次的差异,将数据四舍五入到最接近的整数。在这种情况下,要么是 0,1,或 2。只有 3 个选项,即使对于像抛硬币这样的真实随机分布,在 500 次猜测中,你不可能做得比 33%正确更差。

  • 第二,我们又作弊了,将响应变量与预测变量相关联。由于我们这样做的方式,我们得到大多数观测值都是“1”。如果我们为每个案例猜测“1”,我们就会得到类似的结果。

  • 我们比较了预测值与真实值,是的,但我们从未基于新数据做出预测。预测是使用构建模型时使用的相同数据进行的。这所有的一切都很好,可以让你感觉良好,但它并不能告诉你你的模型在遇到真正的新数据时是否会工作。为此,我们需要一个保留样本,这将在下一节中讨论。

不要被欺骗。仅仅输入这段代码本身并不能创造奇迹。可能需要一段时间才能正确设置建模部分及其所有参数。

说实话,只有少数技术有在 Python 中的行业级实现。但是,借助 RPy 库,在 Python 中使用 R 中可用的模型相当容易。RPy 为 Python 提供了到 R 的接口。R是一个免费软件环境,广泛用于统计分析。如果你还没有尝试过,至少值得一试,因为 2014 年它仍然是数据科学中最受欢迎(如果不是最受欢迎)的编程语言之一。更多信息,请参阅www.kdnuggets.com/polls/2014/languages-analytics-data-mining-data-science.html

2.6.3. 模型诊断和模型比较

你将构建多个模型,然后根据多个标准从中选择最佳模型。使用保留样本可以帮助你选择表现最好的模型。保留样本是你从模型构建中排除的一部分数据,以便在之后评估模型。这里的原理很简单:模型应该在未见过的数据上工作。你只使用你数据的一部分来估计模型,而另一部分,即保留样本,则被排除在外。然后模型被释放到未见过的数据上,并计算误差度量来评估它。有多种误差度量可用,在图 2.26 中我们展示了比较模型的一般思想。示例中使用的误差度量是均方误差。

图 2.26. 均方误差公式

均方误差是一个简单的度量:检查每个预测与真实值有多远,平方这个误差,并将每个预测的误差相加。

图 2.27 比较了两个模型从价格预测订单大小的性能。第一个模型是大小 = 3 * 价格,第二个模型是大小 = 10。为了估计模型,我们从 1,000 个观测值中随机选择了 800 个(或 80%),没有向模型展示其他 20%的数据。一旦模型训练完成,我们根据已知真实值的变量预测其他 20%的变量的值,并使用误差度量计算模型误差。然后我们选择误差最低的模型。在这个例子中,我们选择了模型 1,因为它具有最低的总误差。

图 2.27. 保留样本有助于你比较模型,并确保你可以将结果推广到模型尚未见过的数据。

许多模型都做出了强烈的假设,例如输入的独立性,你必须验证这些假设确实得到了满足。这被称为模型诊断

本节简要介绍了构建有效模型所需的步骤。一旦你有了可工作的模型,你就可以进入最后一步。

2.7. 第 6 步:展示发现并在此基础上构建应用

在你成功分析了数据并构建了一个表现良好的模型之后,你准备好向世界展示你的发现(图 2.28)。这是一个令人兴奋的部分;你所有辛勤工作的小时都得到了回报,你可以向利益相关者解释你发现了什么。

图 2.28. 第 6 步:演示和自动化

图 2.28

有时候人们对你工作的兴奋程度如此之高,以至于你需要一遍又一遍地重复它,因为他们重视你模型的预测或你产生的见解。因此,你需要自动化你的模型。这并不总是意味着你必须不断地重做所有的分析。有时,仅实现模型评分就足够了;其他时候,你可能需要构建一个自动更新报告、Excel 电子表格或 PowerPoint 演示文稿的应用程序。数据科学过程的最后阶段是您软技能最能发挥作用的阶段,而且它们确实非常重要。事实上,我们建议你找到关于这个主题的专用书籍和其他信息,并努力去学习,因为如果没有人听你说话,你为什么要做所有这些艰苦的工作呢?

如果你已经正确地完成了这项工作,你现在拥有了一个工作模型和满意的利益相关者,因此我们可以在这里结束这一章节。

2.8. 摘要

在本章中,你学习了数据科学过程包括六个步骤:

  • 设定研究目标 —在项目章程中定义项目的“是什么”、“为什么”和“如何”。

  • 检索数据 —寻找并获取你项目中需要的数据。这些数据可能在公司内部找到,也可能从第三方获取。

  • 数据准备 —检查和修复数据错误,用来自其他数据源的数据丰富数据,并将其转换为适合你模型的格式。

  • 数据探索 —使用描述性统计和可视化技术深入挖掘你的数据。

  • 数据建模 —使用机器学习和统计技术来实现你的项目目标。

  • 演示和自动化 —向利益相关者展示你的结果,并将你的分析过程工业化,以便重复使用和与其他工具集成。

第三章. 机器学习

本章涵盖

  • 理解为什么数据科学家使用机器学习

  • 确定最重要的 Python 机器学习库

  • 讨论模型构建的过程

  • 使用机器学习技术

  • 获得机器学习的实践经验

你知道计算机是如何学会保护你免受恶意人员侵害的吗?计算机可以过滤掉你超过 60%的电子邮件,并且随着时间的推移,它们可以学会做得更好,以保护你。

你能明确教计算机在图片中识别人物吗?虽然可能,但将所有可能的识别方法编码进去是不切实际的,但你很快就会看到可能性几乎是无穷无尽的。为了成功,你需要给你的工具箱添加一项新技能,机器学习,这正是本章的主题。

3.1. 什么是机器学习,为什么你应该关注它?

“机器学习是一个研究领域,它赋予计算机在没有明确编程的情况下学习的能力。”

亚瑟·萨缪尔,1959^([1])

¹

尽管以下论文经常被引用为这句话的来源,但它并未出现在 1967 年的论文重印版中。作者们无法验证或找到这句话的确切来源。参见 Arthur L. Samuel,“使用国际象棋游戏进行机器学习的一些研究”,IBM 研究与发展杂志 3,第 3 期(1959 年):210–229。

亚瑟·萨缪尔提出的机器学习的定义经常被引用,其广泛性堪称天才,但它也留下了你如何让计算机学习的疑问。为了实现机器学习,专家们开发了通用的算法,这些算法可以用于解决大量学习问题。当你想要解决一个特定任务时,你只需要向算法提供更多特定数据。从某种意义上说,你是在通过示例编程。在大多数情况下,计算机将使用数据作为其信息来源,将其输出与期望输出进行比较,然后对其进行纠正。计算机获得的数据或“经验”越多,它在指定任务上的表现就越好,就像人类一样。

当机器学习被视为一个过程时,以下定义是富有洞察力的:

“机器学习是计算机在收集和学习给定数据的过程中工作更准确的过程。”

迈克·罗伯茨^([2])

²

迈克·罗伯茨是本书的技术编辑。感谢,迈克。

例如,当用户在手机上发送更多短信时,手机就会对短信的常用词汇有更多了解,并能更快、更准确地预测(自动完成)它们的单词。

在更广泛的科学领域,机器学习是人工智能的一个子领域,与应用数学和统计学密切相关。所有这些都可能听起来有些抽象,但机器学习在日常生活中有许多应用。

3.1.1. 机器学习在数据科学中的应用

回归分类对数据科学家来说至关重要。为了实现这些目标,数据科学家使用的主要工具之一是机器学习。回归和自动分类的用途非常广泛,例如以下内容:

  • 根据现有地点找到油田、金矿或考古遗址(分类和回归)

  • 在文本中找到地名或人物(分类)

  • 根据图片或声音录音识别人员(分类)

  • 根据鸟鸣声识别鸟类(分类)

  • 识别有利可图的客户(回归和分类)

  • 主动识别可能失效的汽车部件(回归)

  • 识别肿瘤和疾病(分类)

  • 预测一个人在产品 X 上花费的金额(回归)

  • 预测一段时间内火山喷发次数(回归)

  • 预测公司年度收入(回归)

  • 预测哪支足球队将赢得欧洲冠军联赛(分类)

有时数据科学家会构建一个模型(现实的抽象),它可以为现象的潜在过程提供洞察。当模型的目标不是预测而是解释时,它被称为根本原因分析。以下是一些例子:

  • 理解和优化业务流程,例如确定哪些产品为产品线增值

  • 发现糖尿病的成因

  • 确定交通拥堵的原因

这份机器学习应用列表只能被视为开胃菜,因为它在数据科学中无处不在。回归和分类是两种重要的技术,但技巧和应用的范畴并不止于此,聚类就是一个有价值的技巧的例子。机器学习技术可以在数据科学过程的各个阶段使用,我们将在下一节中讨论。

3.1.2. 机器学习在数据科学过程中的应用

尽管机器学习主要与数据科学过程中的数据建模步骤相关联,但它几乎可以在每个步骤中使用。为了回顾前几章的内容,数据科学过程在图 3.1 中展示。

图 3.1. 数据科学过程

图片

数据建模阶段只能在您拥有可以理解的定性原始数据后开始。但在那之前,数据准备阶段可以从机器学习的使用中受益。一个例子就是清洗文本字符串列表;机器学习可以将相似字符串分组在一起,使其更容易纠正拼写错误。

机器学习在探索数据时也非常有用。算法可以在数据中找出难以仅通过图表发现的潜在模式。

由于机器学习在整个数据科学过程中都很有用,因此开发了大量 Python 库来使您的生活更加轻松,这并不令人惊讶。

3.1.3. 机器学习中使用的 Python 工具

Python 有大量的包可以在机器学习环境中使用。Python 机器学习生态系统可以分为三种主要类型的包,如图 3.2 所示。

图 3.2. 机器学习阶段使用的 Python 包概述

图 3.2 中展示的第一种类型的包主要用于简单任务和数据适合内存时。第二种类型用于在原型设计完成后优化代码,遇到速度或内存问题时。第三种类型专门用于与大数据技术一起使用 Python。

用于在内存中处理数据的包

在原型设计时,以下包可以通过几行代码提供高级功能,帮助你开始:

  • SciPy 是一个库,它集成了在科学计算中经常使用的许多基本包,如 NumPy、matplotlib、Pandas 和 SymPy。

  • NumPy 给你提供了强大的数组函数和线性代数函数。

  • Matplotlib 是一个流行的 2D 绘图包,具有一些 3D 功能。

  • Pandas 是一个高性能但易于使用的数据处理包。它将数据框引入 Python,这是一种内存中的数据表类型。这个概念应该对 R 的常规用户来说很熟悉。

  • SymPy 是一个用于符号数学和计算机代数的包。

  • StatsModels 是一个用于统计方法和算法的包。

  • Scikit-learn 是一个充满机器学习算法的库。

  • RPy2 允许你在 Python 中调用 R 函数。R 是一个流行的开源统计程序。

  • NLTK(自然语言工具包)是一个专注于文本分析的 Python 工具包。

这些库是入门的好选择,但一旦你决定频繁运行某个 Python 程序,性能就会变得重要。

优化操作

一旦你的应用程序进入生产阶段,这里列出的库可以帮助你提供所需的性能。有时这涉及到连接到 Hadoop 和 Spark 等大数据基础设施。

  • Numba 和 NumbaPro — 这些使用即时编译来加速直接用 Python 编写的应用程序和一些注释。NumbaPro 还允许你使用你的图形处理器单元(GPU)的强大功能。

  • PyCUDA — 这允许你编写将在 GPU 上而不是 CPU 上执行的代码,因此非常适合计算密集型应用程序。它最适合那些适合并行化并且与所需计算周期相比输入很少的问题。一个例子是通过对单个起始状态进行数千种不同的结果计算来研究你预测的鲁棒性。

  • Cython,或 C 语言用于 Python — 这将 C 编程语言引入 Python。C 是一种低级语言,因此代码更接近计算机最终使用的(字节码)。代码与位和字节越接近,执行速度越快。当计算机知道变量的类型时(称为 静态类型),计算机也会更快。Python 并未设计用于此,Cython 帮助你克服这一不足。

  • Blaze — Blaze 提供了可以大于你计算机主内存的数据结构,使你能够处理大型数据集。

  • Dispy 和 IPCluster — 这些包允许你编写可以在计算机集群上分布的代码。

  • PP — Python 默认以单个进程执行。借助 PP,你可以在单个机器或集群上并行化计算。

  • Pydoop 和 Hadoopy — 这些将 Python 连接到 Hadoop,一个常见的大数据框架。

  • PySpark — 这将 Python 和 Spark 连接起来,Spark 是一个内存中的大数据框架。

现在你已经看到了可用的库的概述,让我们来看看模型过程本身。

3.2. 模型过程

模型阶段包括四个步骤:

1. 特征工程和模型选择

2. 训练模型

3. 模型验证和选择

4. 将训练好的模型应用于未见数据

在你找到一个好的模型之前,你可能会在前三个步骤之间迭代。

最后一步并不总是存在,因为有时目标不是预测而是解释(根本原因分析)。例如,你可能想找出物种灭绝的原因,但并不一定预测下一个即将离开我们星球的是哪一个。

可以 链式组合 多种技术。当你链式连接多个模型时,第一个模型的输出成为第二个模型的输入。当你组合多个模型时,你独立训练它们并组合它们的结果。这种最后的技术也被称为 集成学习

一个模型由称为 特征预测器 的信息构造和 目标响应变量 组成。你的模型的目标是预测目标变量,例如,明天的最高气温。帮助你做到这一点并且(通常)为你所知的变量是特征或预测变量,例如今天的温度、云的移动、当前风速等。最好的模型是那些能够准确反映现实,同时保持简洁和可解释性的模型。为了实现这一点,特征工程是建模过程中最重要且最具趣味性的部分。例如,一个试图解释过去 60,000 年澳大利亚大型陆地动物灭绝的重要特征最终被发现是人口数量和人类的扩散。

3.2.1. 特征工程和模型选择

在工程特性方面,你必须为模型提出并创建可能的预测因子。这是过程中的一个最重要的步骤,因为模型通过重新组合这些特性来实现其预测。通常,你可能需要咨询专家或查阅适当的文献来提出有意义的特性。

某些特性是从数据集中获得的变量,正如我们在练习中提供的数据集和大多数学校练习中的情况一样。在实践中,你需要自己找到这些特性,它们可能分散在不同的数据集中。在几个项目中,我们不得不在获得所需原始数据之前汇集 20 多个不同的数据源。通常,你需要在输入成为良好的预测因子之前对其进行转换,或者将多个输入组合起来。多个输入组合的一个例子是交互变量:单个变量的影响较低,但如果两者都存在,它们的影响就会变得巨大。这在化学和医学环境中尤其如此。例如,虽然醋和漂白剂本身是相对无害的常见家用产品,但混合它们会产生有毒的氯气,这种气体在第一次世界大战中杀死了成千上万的人。

在医学领域,临床药学是一门致力于研究药物相互作用效果的学科。这是一项重要的工作,甚至不需要两种药物相互作用就能产生潜在的危险结果。例如,将抗真菌药物如斯波拉诺克斯与葡萄柚混合会产生严重的副作用。

有时你必须使用建模技术来推导特性:一个模型的输出成为另一个模型的一部分。这在文本挖掘中并不罕见。文档首先可以被标注以将内容分类到不同的类别中,或者你可以计算文本中的地理地点或人物的数量。这种计数往往比听起来更困难;模型首先被应用于识别某些单词作为人物或地点。然后,所有这些新信息都被倒入你想要构建的模型中。模型构建中的一个最大错误是可用性偏差:你的特性只是你能够轻易获得的那些,因此你的模型相应地代表了一种单方面的“真相”。受到可用性偏差影响的模型在验证时往往失败,因为很明显,它们并不是对真相的有效表示。

在第二次世界大战期间,在对德国领土进行轰炸行动后,许多英国飞机在机翼、机头和机尾附近都出现了弹孔,但几乎没有飞机的驾驶舱、尾翼或发动机块上有弹孔。因此,工程师决定在机翼上增加额外的装甲板。这个想法看起来很合理,直到一位名叫亚伯拉罕·瓦尔德的数学家解释了他们错误的明显性:他们只考虑了返回的飞机。机翼上的弹孔实际上是他们最不需要担心的问题,因为至少带有这种损伤的飞机能够返回家中进行维修。因此,飞机的加固被增加到了返回飞机上未受损的部分。最初的推理受到了可用性偏差的影响:工程师忽略了一个重要的数据部分,因为它更难获得。在这种情况下,他们很幸运,因为推理可以被反转,以得到预期的结果,而无需从坠毁的飞机中获得数据。

当初始特征被创建时,模型可以被训练到数据上。

3.2.2. 训练你的模型

在确定了合适的预测因子并考虑了建模技术后,你可以进入模型训练阶段。在这个阶段,你向模型展示数据,使其能够从中学习。

最常见的建模技术几乎在每种编程语言中都有现成的实现,包括 Python。这些技术使你能够通过执行几行代码来训练你的模型。对于更先进的数据科学技术,你可能需要进行大量的数学计算,并使用现代计算机科学技术来实现。

一旦模型被训练,就需要测试它是否可以推广到现实中:模型验证。

3.2.3. 验证模型

数据科学有许多建模技术,问题是哪一种才是正确的选择。一个好的模型有两个特性:它具有良好的预测能力和良好的泛化能力,能够很好地推广到它尚未见过的数据。为了实现这一点,你需要定义一个误差度量(模型错误程度)和一个验证策略。

机器学习中两种常见的误差度量是分类问题的分类错误率和回归问题的均方误差。分类错误率是测试数据集中模型错误标记的观测值的百分比;越低越好。均方误差衡量预测的平均误差有多大。平均误差的平方有两个后果:你不能用一个方向的错误预测抵消另一个方向的错误预测。例如,下个月过度估计周转额 5,000 并不抵消下下个月低估 5,000。作为平方的第二个后果,更大的误差比它们原本的权重还要大。小误差保持不变,甚至可以缩小(如果<1),而大误差会扩大,并肯定会引起你的注意。

存在许多验证策略,包括以下常见的几种:

  • 将数据分为包含 X%观测值的训练集,并将剩余的作为保留数据集(一个永远不会用于模型创建的数据集)——这是最常用的技术。

  • K 折交叉验证 —这种策略将数据集分为 k 部分,并使用每一部分一次作为测试数据集,而将其他部分作为训练数据集。这种方法的优点是,你使用了数据集中所有可用的数据。

  • 留一法 —这种方法与 k 折交叉验证相同,但 k=1。你总是留出一个观测值,并在其余数据上训练。这种方法仅用于小型数据集,因此对评估实验室实验的人更有价值,而不是对大数据分析师。

机器学习中另一个流行的术语是正则化。在应用正则化时,你为构建模型使用的每个额外变量都会受到惩罚。使用L1 正则化时,你要求模型尽可能少地使用预测变量。这对于模型的鲁棒性很重要:简单的解决方案往往在更多情况下是正确的。L2 正则化旨在使预测变量系数之间的方差尽可能小。预测变量之间的重叠方差使得难以识别每个预测变量的实际影响。保持它们的方差不重叠将提高可解释性。简单来说:正则化主要用于防止模型使用过多的特征,从而防止过拟合。

验证非常重要,因为它决定了你的模型是否能在实际生活中工作。直白地说,就是你的模型是否值一美元。即便如此,人们时不时地会向受尊敬的科学期刊提交论文(有时甚至成功发表),但这些论文的验证存在缺陷。结果是,这些论文被拒绝或需要撤回,因为一切都错了。这种情况对你的心理健康有害,所以请始终记住:在从未见过的数据上测试你的模型,并确保这些数据是它在应用于其他人新鲜观察时可能遇到的真实表现的准确代表。对于分类模型,混淆矩阵(在第二章中介绍,但将在本章后面详细解释)是黄金工具;拥抱它们。

一旦你构建了一个好的模型,你可以(可选地)使用它来预测未来。

3.2.4. 预测新的观察结果

如果你已经成功实施了前三个步骤,你现在有一个性能良好的模型,它可以泛化到未见过的数据。将你的模型应用于新数据的过程称为模型评分。实际上,模型评分是你在验证过程中隐式完成的,但现在你不知道正确的结果。到如今,你应该足够信任你的模型,可以使用它进行实际应用。

模型评分涉及两个步骤。首先,你准备一个具有与你的模型定义完全一致的特征的数据集。这相当于重复你在建模过程的第一个步骤中进行的数据处理,但针对一个新的数据集。然后,你将模型应用于这个新的数据集,这会产生一个预测结果。

现在我们来看看不同的机器学习技术:不同的问题需要不同的方法。

3.3. 机器学习的类型

从广义上讲,我们可以根据协调它们所需的人类努力以及它们如何使用标记数据(即分配有类别或代表先前观察结果的真实数值的数据)来划分不同的机器学习方法。

  • 监督学习技术试图通过试图在标记数据集中找到模式来辨别结果并学习。需要人类交互来标记数据。

  • 无监督学习技术不依赖于标记数据,并试图在数据集中找到模式,而不需要人类交互。

  • 半监督学习技术需要标记数据,因此需要人类交互来在数据集中找到模式,但即使传递了未标记的数据,它们仍然可以朝着结果前进并学习。

在本节中,我们将探讨所有三种方法,看看每种方法更适合哪些任务,并使用前面提到的几个 Python 库中的一到两个来让你对代码有一个直观感受,并解决一个任务。在这些示例中,我们将使用一个已经清洗过的可下载数据集,因此我们将直接跳到数据科学流程中的数据建模步骤,正如本章前面所讨论的。

3.3.1. 监督学习

如前所述,监督学习是一种只能应用于标记数据的机器学习技术。一个例子就是从图像中识别数字。让我们深入研究一个关于数字识别的案例研究。

案例研究:从图像中识别数字

在网络上防止计算机黑客攻击用户账户的许多常见方法之一是验证码检查——用户必须解码并输入到表单字段中,然后将表单发送回网络服务器的文本和数字图片。类似于图 3.3 的图片应该很熟悉。

图 3.3. 简单的验证码控制可以用来防止通过在线表单发送自动垃圾邮件。

图片

在侧边栏中详细解释的简单但强大的算法朴素贝叶斯分类器的帮助下,你可以从文本图像中识别数字。这些图像与许多网站为了确保你不是试图黑客攻击用户账户的计算机而设置的验证码检查非常相似。让我们看看让计算机识别数字图片有多难。

我们的研究目标是让计算机能够识别数字图片(数据科学流程的第一步)。

我们将要处理的数据是 MNIST 数据集,它常被用于数据科学文献中的教学和基准测试。

在垃圾邮件过滤器中介绍朴素贝叶斯分类器

你收到的每一封电子邮件并不都有诚实的目的。你的收件箱可能包含未经请求的商业邮件或批量邮件,也就是垃圾邮件。垃圾邮件不仅令人烦恼,它还常被用于诈骗和病毒传播。卡巴斯基估计,世界上超过 60%的电子邮件都是垃圾邮件。为了保护用户免受垃圾邮件的侵扰,大多数电子邮件客户端都会在后台运行一个程序,将电子邮件分类为垃圾邮件或安全邮件。

³

卡巴斯基 2014 年季度垃圾邮件统计报告,usa.kaspersky.com/internet-security-center/threats/spam-statistics-report-q1-2014#.VVym9blViko

在垃圾邮件过滤中,一个流行的技术是使用邮件内部的单词作为预测器的分类器。它输出特定电子邮件由其组成的单词是垃圾邮件的概率(用数学术语,P(垃圾邮件 | 单词))。为了得出这个结论,它使用了三个计算:

  • P(垃圾邮件)—在不知道单词的情况下垃圾邮件的平均比率。根据卡巴斯基的数据,一封邮件有 60%的概率是垃圾邮件。

  • P(单词)—无论是否为垃圾邮件,这个单词组合的使用频率。

  • P(单词 | 垃圾邮件)—在训练邮件被标记为垃圾邮件时,这些单词出现的频率。

要确定一封新邮件是垃圾邮件的概率,你会使用以下公式:

P(垃圾邮件|单词) = P(垃圾邮件)P(单词|垃圾邮件) / P(单词)

这是对规则 P(B|A) = P(B) P(A|B) / P(A) 的应用,该规则被称为贝叶斯定理,并以该分类器的名字命名。其中“朴素”部分来自分类器假设一个特征的存在不会告诉你关于另一个特征的信息(特征独立性,也称为多重共线性不存在)。在现实中,特征往往是相关的,尤其是在文本中。例如,“购买”这个词通常会被“现在”跟随。尽管这个假设不切实际,但朴素分类器在实践中表现得相当出色。

在侧边栏中的理论基础上,你已经准备好进行建模本身。确保运行所有即将到来的代码都在相同的范围内,因为每一部分都需要前一部分。可以从这本书的 Manning 下载页面下载本章的 IPython 文件。

MNIST 图像可以在 Scikit-learn 的数据集包中找到,并且已经为你进行了归一化(所有都缩放到相同的大小:64x64 像素),因此我们不需要太多的数据准备(数据科学流程的第三步)。但让我们首先按照数据科学流程的第二步,使用以下列表获取我们的数据。

列表 3.1. 数据科学流程的第二步:获取数字图像数据

图片

与图像一起工作与其他数据集一起工作没有太大区别。在灰度图像的情况下,你将每个矩阵条目中的值设置为要显示的灰度值。以下代码演示了此过程,并且是数据科学流程的第四步:数据探索。

列表 3.2. 数据科学流程的第四步:使用 Scikit-learn

图片

图 3.4 展示了模糊的“0”图像如何转换为数据矩阵。

图 3.4. 数字 0 的模糊灰度表示及其对应的矩阵。数字越高,越接近白色;数字越低,越接近黑色。

图片

图 3.4 展示了实际的代码输出,但也许 图 3.5 可以稍微澄清这一点,因为它显示了向量中的每个元素是如何成为图像的一部分。

图 3.5. 我们将通过获取图像中每个像素的灰度值(如图右侧所示)并将这些值放入列表中,将图像转换为朴素贝叶斯分类器可用的格式。

图片

到目前为止,这很简单,不是吗?当然,还有一些额外的工作要做。朴素贝叶斯分类器期望一个值列表,但pl.matshow()返回一个反映图像形状的二维数组(一个矩阵)。要将它展平成一个列表,我们需要在digits.images上调用reshape()。最终结果将是一个类似这样的单维数组:

array([[ 0., 0., 5., 13., 9., 1., 0., 0., 0., 0., 13., 15., 10., 15., 5., 0.,
0., 3., 15., 2., 0., 11., 8., 0., 0., 4., 12., 0., 0., 8., 8., 0.,
0., 5., 8., 0., 0., 9., 8., 0., 0., 4., 11., 0., 1., 12., 7., 0.,
0., 2., 14., 5., 10., 12., 0., 0., 0., 0., 6., 13., 10., 0., 0., 0.]])

之前的代码片段显示了图 3.5 中的矩阵展平(维度从二维减少到一维)到一个 Python 列表。从这一点开始,它就是一个标准的分类问题,这把我们带到了数据科学过程的第五步:模型构建。

现在我们有了将图像内容传递给分类器的方法,我们需要传递一个训练数据集,这样它就可以开始学习如何预测图像中的数字。我们之前提到 Scikit-learn 包含 MNIST 数据库的子集(1,800 个图像),因此我们将使用它。每个图像也标有它实际显示的数字。这将构建一个基于图像的灰度值的概率模型,存储在内存中最可能显示的数字。

一旦程序通过了训练集并构建了模型,我们就可以传递测试数据集给它,看看它是否已经学会了如何使用模型来解释图像。

下面的列表展示了如何在代码中实现这些步骤。

列表 3.3. 数字图像数据分类问题

这段代码的最终结果被称为混淆矩阵,例如图 3.6 中所示。作为一个二维数组返回,它显示了预测的数字在主对角线以及矩阵中的(i,j)位置(j 被预测,但图像显示的是 i)出现的频率。查看图 3.6,我们可以看到模型正确预测数字 2 共 17 次(坐标 3,3),但模型也错误地预测了数字 8 共 15 次,而图像中实际显示的是数字 2(坐标 9,3)。

图 3.6. 由预测模糊图像所生成的混淆矩阵

混淆矩阵

混淆矩阵是一个矩阵,显示了模型预测错误(或正确)的情况,以及它有多少次“混淆”。在其最简单的形式中,它将是一个 2x2 的表格,用于尝试将观测值分类为 A 或 B 的模型。假设我们有一个分类模型,它预测某人是否会购买我们最新的产品:油炸樱桃布丁。我们可以预测:“是的,这个人会购买”或“不,这位顾客不会购买”。一旦我们对 100 人做出预测,我们可以将它们与他们的实际行为进行比较,这会告诉我们我们有多少次预测正确。一个例子在表 3.1 中显示。

表 3.1. 混淆矩阵示例
混淆矩阵 预测“人会购买” 预测“人不会购买”
人员 购买 油炸樱桃布丁 35 (真阳性) 10 (假阴性)
人员 未购买 油炸樱桃布丁 15 (假阳性) 40 (真阴性)

模型在 (35+40) 75 个案例中是正确的,在 (15+10) 25 个案例中是错误的,结果是一个 (75 正确/100 总观察值) 75% 的准确率。

所有正确分类的观察结果都加在对角线上(35+40),而其他所有内容(15+10)都被错误分类。当模型只预测两个类别(二元)时,我们的正确猜测分为两组:真阳性(预测购买并确实购买)和真阴性(预测不会购买且确实没有购买)。我们的错误猜测分为两组:假阳性(预测他们会购买但实际没有)和假阴性(预测不会购买但实际购买了)。矩阵有助于看到模型最容易出现问题的位置。在这种情况下,我们对自己的产品过于自信,轻易地将客户分类为未来的购买者(假阳性)。

从混淆矩阵中,我们可以推断出对于大多数图像,预测都是相当准确的。在一个好的模型中,你期望矩阵主对角线上的数字之和(也称为矩阵 )与矩阵所有条目之和相比非常高,这表明大部分预测是正确的。

假设我们想要以更易于理解的方式展示我们的结果,或者我们想要检查几个图像以及我们的程序所做的预测:我们可以使用以下代码来并排显示。然后我们可以看到程序出错的地方,需要更多的训练。如果我们对结果满意,模型构建就结束了,我们到达第六步:展示结果。

列表 3.4. 检查预测值与实际数值

图 3.7 展示了所有预测似乎都是正确的,除了数字 2,它将其标记为 8。我们应该原谅这个错误,因为这个 2 与 8 在视觉上确实有相似之处。左下角的数字是模糊的,即使是人类;它是 5 还是 3?这值得商榷,但算法认为它是 3。

图 3.7. 对于每个模糊图像,都会预测一个数字;只有数字 2 被误认为是 8。然后一个模糊的数字被预测为 3,但它也可能是 5;即使是人类眼睛也看不清楚。

通过辨别哪些图像被误解,我们可以通过将正确的数字标签添加到这些图像上,并将它们作为新的训练集反馈到模型中(数据科学过程的第 5 步)来进一步训练模型。这将使模型更准确,因此学习、预测、纠正的循环继续,预测变得更加准确。这是一个我们用于示例的受控数据集。所有示例大小相同,它们都是 16 种灰度的。扩展到 Captcha 控制中显示的变量大小、变量长度字符串和变量色调的字母数字字符,你就可以理解为什么足够准确以预测任何 Captcha 图像的模型还不存在。

在这个监督学习示例中,很明显,如果没有与每个图像关联的标签告诉程序该图像显示的数字,就无法构建模型和做出预测。相比之下,无监督学习方法不需要其数据被标记,并且可以用来为无结构化数据集提供结构。

3.3.2. 无监督学习

通常情况下,大多数大型数据集的数据上没有标签,所以除非你全部整理并为其添加标签,否则监督学习方法在数据上不会起作用。相反,我们必须采取适合这些数据的方法,因为

  • 我们可以研究数据的分布并从分布的不同部分推断关于数据的真相。

  • 我们可以研究数据的结构和值并从中推断出新的、更有意义的数据和结构。

对于这些无监督学习方法中的每一个都存在许多技术。然而,在现实世界中,你总是在数据科学过程的第一阶段定义的研究目标下工作,因此你可能需要在数据集可以标记、启用监督学习技术之前,或者甚至达到目标本身之前,结合或尝试不同的技术。

从你的数据中辨别简化的潜在结构

并非所有事物都可以衡量。当你第一次见到某人时,你可能会根据他们的行为和他们的反应来猜测他们是否喜欢你。但如果是他们今天过得不好呢?也许他们的猫被撞了,或者他们可能还在为上周参加的葬礼感到难过?重点是,某些变量可以立即获得,而其他变量只能推断出来,因此它们缺失在你的数据集中。第一种类型的变量被称为可观察变量,第二种类型被称为潜在变量。在我们的例子中,你新朋友的情绪状态是一个潜在变量。它肯定会影响他们对你的判断,但它的值并不明确。

根据数据集的实际内容推导或推断潜在变量及其值是一项非常有用的技能,因为

  • 潜在变量可以替代数据集中已经存在的几个变量。

  • 通过减少数据集中的变量数量,数据集变得更加易于管理,任何进一步运行的算法都会更快,预测可能更加准确。

  • 由于潜在变量是为定义的研究目标而设计和针对的,因此使用它们会丢失很少的关键信息。

如果我们能够将数据集从每行 14 个可观察变量简化到 5 或 6 个潜在变量,例如,由于数据集结构的简化,我们将更有可能达到我们的研究目标。从下面的例子中您将看到,这并不是将现有数据集简化到尽可能少的潜在变量的案例。您需要找到潜在变量数量带来的最大价值的“甜点”。让我们通过一个小案例研究来实践一下。

案例研究:在葡萄酒质量数据集中寻找潜在变量

在这个简短的案例研究中,您将使用一种称为主成分分析(PCA)的技术来寻找描述葡萄酒质量的数据集中的潜在变量。然后您将比较一组潜在变量在预测葡萄酒质量方面与原始可观察集相比的效果。您将学习

1. 如何识别和推导出那些潜在变量。

2. 如何通过生成和解释由 PCA 生成的碎石图来分析“甜点”在哪里——即多少新变量能带来最大的效用。(我们稍后会看到碎石图。)

让我们看看这个例子中的主要组成部分。

  • 数据集 —加州大学欧文分校(UCI)有一个在线仓库,包含 325 个用于机器学习练习的数据集,网址为archive.ics.uci.edu/ml/。我们将使用由 P. Cortez、A. Cerdeira、F. Almeida、T. Matos 和 J. Reis 创建的红葡萄酒质量数据集。该数据集有 1600 行,每行有 11 个变量,如表 3.2 所示。

    您可以在archive.ics.uci.edu/ml/datasets/Wine+Quality找到关于葡萄酒质量数据集的完整详细信息。

    表 3.2. 红葡萄酒质量数据集的前三行
    固定酸度 挥发性酸度 柠檬酸 残糖 氯化物 自由二氧化硫 总二氧化硫 密度 pH 硫酸盐 酒精 质量
    7.4 0.7 0 1.9 0.076 11 34 0.9978 3.51 0.56 9.4 5
    7.8 0.88 0 2.6 0.098 25 67 0.9968 3.2 0.68 9.8 5
    7.8 0.76 0.04 2.3 0.092 15 54 0.997 3.26 0.65 9.8 5
  • 主成分分析 —一种在尽可能保留信息的同时,寻找数据集中潜在变量的技术。

  • Scikit-learn —我们使用这个库是因为它已经为我们实现了 PCA,并且是生成碎石图的一种方式。

数据科学过程的第一步是设定我们的研究目标:我们希望利用不同的葡萄酒特性来解释主观的“葡萄酒质量”反馈。

因此,我们的第一个任务是下载数据集(第二步:获取数据),如下面的列表所示,并为其分析做准备(第三步:数据准备)。然后我们可以运行 PCA 算法并查看结果以查看我们的选项。

列表 3.5. 数据获取和变量标准化

在完成初步数据准备之后,你可以执行 PCA。生成的散点图(将在稍后解释)显示在图 3.8 中。因为 PCA 是一种探索性技术,我们现在到达数据科学过程的第四步:数据探索,如下面的列表所示。

图 3.8. PCA 散点图显示了 PCA 可以创建的每个新变量的边际信息量。前几个变量解释了数据中大约 28%的方差,第二个变量解释了另外 17%,第三个大约 15%,以此类推。

列表 3.6. 执行主成分分析

现在,让我们看看图 3.8 中的散点图。

从葡萄酒数据集生成的图表显示在图 3.8 中。你希望看到的是图表中的肘形或曲棍球棒形状。这表明几个变量可以代表数据集中大部分的信息,而其余的只增加了少许。在我们的图表中,PCA 告诉我们,将集合减少到一个变量可以捕获集合中大约 28%的总信息(图表从零开始,所以变量一在 x 轴上的位置为零),两个变量将捕获大约 17%更多或 45%的总信息,以此类推。表 3.3 显示了完整的读数。

表 3.3. PCA 的结果
变量数量 捕获的额外信息 总数据捕获
1 28% 28%
2 17% 45%
3 14% 59%
4 10% 69%
5 8% 77%
6 7% 84%
7 5% 89%
8 - 11 ... 100%

图表中出现的肘形表明,五个变量可以包含数据内部的大部分信息。你也可以争论在六个或七个变量处截断,但我们将选择一个比原始数据集方差更小的简单数据集。

在这一点上,我们可以继续查看使用五个潜在变量重新编码的原始数据集是否足够准确预测葡萄酒的质量,但在我们这样做之前,我们将看看我们如何识别它们代表的内容。

解释新变量

在决定将数据集从 11 个原始变量减少到 5 个潜在变量后,我们可以检查是否可以根据它们与原始变量的关系来解释或命名它们。实际名称比 lv1、lv2 等代码更容易使用。我们可以在以下列表中添加一行代码以生成一个表格,显示两组变量之间的相关性。

列表 3.7. 在 Pandas 数据框中显示 PCA 组件
pd.DataFrame(results.components_, columns=list(
  [u'fixed acidity', u'volatile acidity', u'citric acid', u'residual sugar',
   u'chlorides', u'free sulfur dioxide',  u'total sulfur dioxide', u'density',
   u'pH', u'sulphates',  u'alcohol']))

结果表(表 3.4)中的行显示了数学相关性。或者用英语来说,第一个潜在变量 lv1,它捕捉了大约 28%的总信息,具有以下公式。

表 3.4. PCA 如何计算 11 个原始变量与 5 个潜在变量之间的相关性
固定酸度 挥发性酸度 柠檬酸 残余糖 氯化物 自由二氧化硫 总二氧化硫 密度 pH 硫酸盐 酒精
0 0.489314 -0.238584 0.463632 0.146107 0.212247 -0.036158 0.023575 0.395353 -0.438520 0.242921 -0.113232
1 -0.110503 0.274930 -0.151791 0.272080 0.148052 0.513567 0.569487 0.233575 0.006711 -0.037554 -0.386181
2 0.123302 0.449963 -0.238247 -0.101283 0.092614 -0.428793 -0.322415 0.338871 -0.057697 -0.279786 -0.471673
3 -0.229617 0.078960 -0.079418 -0.372793 0.666195 -0.043538 -0.034577 -0.174500 -0.003788 0.550872 -0.122181
4 0.082614 -0.218735 0.058573 -0.732144 -0.246501 0.159152 0.222465 -0.157077 -0.267530 -0.225962 -0.350681
*Lv1 = (fixed acidity * 0.489314) + (volatile acidity * -0.238584) + ... +*
*(alcohol * -0.113232)*

给每个新变量起一个可用的名字有点棘手,可能需要咨询实际的葡萄酒专家以确保准确性。然而,由于我们手头没有葡萄酒专家,我们将它们称为以下内容(表 3.5)。

表 3.5. 基于 PCA 创建的葡萄酒质量变量的解释
潜在变量 可能的解释
0 持续酸度
1 硫化物
2 挥发性酸度
3 氯化物
4 缺乏残余糖

我们现在可以用只有五个潜在变量的原始数据集进行重新编码。这样做是数据准备,因此我们回顾数据科学过程中的第三步:数据准备。如第二章中所述,数据科学过程是递归的,这尤其是在第三步:数据准备和第四步:数据探索之间。

表 3.6 显示了完成此操作的前三行。

表 3.6. 在五个潜在变量中重新编码的红葡萄酒质量数据集的前三行

| | 持续酸度 | 硫化物 | 挥发性酸度 | 氯化物 | 缺乏残余糖 |
| --- | --- | --- | --- | --- | --- | --- | --- | --- | --- | --- | --- |
| 0 | -1.619530 | 0.450950 | 1.774454 | 0.043740 | -0.067014 |
| 1 | -0.799170 | 1.856553 | 0.911690 | 0.548066 | 0.018392 |
| 2 | 2.357673 | -0.269976 | -0.243489 | -0.928450 | 1.499149 |

已经可以看到,葡萄酒 0 在挥发性酸度上有很高的值,而葡萄酒 2 在持久性酸度上特别高。听起来一点也不像好酒!

比较原始数据集与潜在变量的准确性

既然我们已经决定将数据集重新编码为 5 个潜在变量而不是最初的 11 个,现在是时候看看新的数据集在预测葡萄酒质量方面与原始数据集相比表现如何了。我们将使用之前示例中看到的朴素贝叶斯分类器算法来进行监督学习以帮助。

让我们先看看原始的 11 个变量如何预测葡萄酒质量评分。以下列表展示了执行此操作的代码。

列表 3.8. 主成分分析前的葡萄酒评分预测

现在,我们将运行相同的预测测试,但开始时只有 1 个潜在变量而不是原始的 11 个。然后我们再添加另一个,看看效果如何,再添加一个,以此类推,以查看预测性能如何提高。以下列表展示了如何进行此操作。

列表 3.9. 随着主成分数量增加的葡萄酒评分预测

结果图显示在图 3.9 中。

图 3.9. 结果图显示,向模型添加更多潜在变量(x 轴)可以大大提高预测能力(y 轴),但达到一定程度后就会下降。添加变量带来的预测能力提升最终会减弱。

图 3.9 中的图表显示,只有 3 个潜在变量时,分类器在预测葡萄酒质量方面比原始的 11 个变量做得更好。此外,添加超过 5 个潜在变量并不会像最初的 5 个那样增加太多的预测能力。这表明我们选择在 5 个变量处截断是一个很好的选择,正如我们所希望的。

我们探讨了如何对相似变量进行分组,但也可以对观测值进行分组。

通过对相似观测值进行分组来从数据分布中获得洞察

假设你正在构建一个网站,该网站根据用户输入的偏好和观看的电影推荐电影。如果他们看了很多恐怖电影,他们很可能想了解新的恐怖电影,而不是那么想了解新的青少年浪漫电影。通过将观看相似电影并设置相似偏好的用户分组在一起,你可以获得很多关于他们可能还喜欢什么推荐的洞察。

我们在这里描述的一般技术被称为聚类。在这个过程中,我们试图将我们的数据集划分为观测子集,或聚类,其中观测值应该与同一聚类中的观测值相似,但与其他聚类中的观测值差异很大。图 3.10 给你一个聚类试图达到的目标的直观概念。图顶左部的圆圈彼此很近,而与其他圆圈较远。右上角的十字架也是同样的情况。

图 3.10。聚类的目标是把数据集划分为“足够不同的”子集。在这个例子中,观测值被划分为三个聚类。

图片

Scikit-learn 在其sklearn.cluster模块中实现了几个常见的聚类数据算法,包括 k-means 算法、亲和传播和谱聚类。每个算法都有一些用例,更适合某些情况,尽管 k-means 是一个很好的通用算法,可以用来入门。然而,像所有聚类算法一样,你需要提前指定所需的聚类数量,这必然会导致在得出合理结论之前进行试错的过程。它还假设所有分析所需的数据已经可用。如果数据不可用怎么办?

你可以在scikit-learn.org/stable/modules/clustering.html找到 Scikit-learn 中所有聚类算法的比较。

让我们看看通过属性(花瓣长度和宽度、花瓣长度和宽度等)对鸢尾花(一种花)进行聚类的实际案例。在这个例子中,我们将使用 k-means 算法。这是一个很好的算法,可以让你对数据有一个大致的了解,但它对起始值很敏感,所以每次运行算法时,你可能会得到不同的聚类结果,除非你通过指定一个种子(起始值生成器的常量)手动定义起始值。如果你需要检测层次结构,你最好使用层次聚类技术类别的算法。

另一个缺点是需要提前指定所需的聚类数量。这通常会导致在得出令人满意的结论之前进行试错的过程。

执行代码相当简单。它遵循所有其他分析相同的结构,除了你不需要传递一个目标变量。算法将学习有趣的模式。下面的列表使用鸢尾花数据集来查看算法是否可以将不同类型的鸢尾花分组。

列表 3.10。鸢尾花分类示例

图片

图片

图 3.11显示了鸢尾花分类的输出。

图 3.11。鸢尾花分类的输出

图片

此图显示,即使不使用标签,你也会找到与官方的鸢尾花分类相似的簇,结果是 150 个正确分类中的 134 个(50+48+36)。

你并不总是需要在监督学习和无监督学习之间做出选择;有时将它们结合起来是一个选项。

3.4. 半监督学习

当你了解到,尽管我们希望所有数据都被标记以便使用更强大的监督式机器学习技术,但在现实中,我们通常只从最少标记的数据开始,如果有的话。我们可以使用我们的无监督机器学习技术来分析我们所拥有的数据,并可能为数据集添加标签,但全部标记的成本将非常高昂。因此,我们的目标是用尽可能少的标记数据来训练我们的预测模型。这就是半监督学习技术发挥作用的地方——它是我们之前看到两种方法的混合体。

以图 3.12 中的示例来说明。在这种情况下,数据中只有两个标记的观测值;通常情况下,这太少以至于无法做出有效的预测。

图 3.12。此图只有两个标记的观测值——对于监督学习来说太少,但足以开始使用无监督或半监督方法。

图片

一种常见的半监督学习技术是标签传播。在这种技术中,你从一个标记的数据集开始,并将相同的标签赋予相似的数据点。这类似于在数据集上运行聚类算法,并根据它们包含的标签对每个簇进行标记。如果我们将这种方法应用于图 3.12 中的数据集,我们可能会得到类似于图 3.13 的结果。

图 3.13。前面的图显示,数据中只有两个标记的观测值,对于监督学习来说远远不够。此图显示了如何利用底层数据集的结构来学习比仅从标记数据更好的分类器。数据被聚类技术分为两个簇;我们只有两个标记的值,但如果我们大胆一些,可以假设该簇内的其他值具有相同的标签(买家或非买家),如图所示。这种技术并不完美;如果能得到实际的标签就更好了。

图片

值得一提的半监督学习特殊方法之一是主动学习。在主动学习中,程序根据你指定的某些标准指出它想要标记的观测值,以便进行下一轮学习。例如,你可以设置它尝试标记算法最不确定的观测值,或者你可以使用多个模型进行预测,并选择模型意见分歧最大的点。

在你掌握机器学习基础知识之后,下一章将讨论在单个计算机的约束下使用机器学习。当数据集太大而无法完全加载到内存中时,这通常具有挑战性。

3.5. 摘要

在本章中,你了解到

  • 数据科学家高度依赖统计学和机器学习的技术来进行他们的建模。机器学习在现实生活中的应用非常广泛,从分类鸟鸣声到预测火山爆发。

  • 建模过程包括四个阶段:

    1.  特征工程、数据准备和模型参数化—我们定义模型输入的参数和变量。

    2.  模型训练—模型被喂入数据,并学习数据中隐藏的模式。

    3.  模型选择与验证—一个模型可能表现良好或糟糕;根据其表现,我们选择最合理的模型。

    4.  模型评分—当我们的模型值得信赖时,它将被应用于新数据。如果我们做得好,它将为我们提供额外的见解或对我们对未来情况的预测提供良好的结果。

  • 两种主要的机器学习技术

    1.  监督学习—需要标记数据的机器学习。

    2.  无监督学习—不需要标记数据,但通常比监督学习不准确或不可靠。

  • 半监督学习介于这些技术之间,当只有一小部分数据被标记时使用。

  • 两个案例研究分别展示了监督学习和无监督学习:

    1.  我们的第一个案例研究使用了朴素贝叶斯分类器来对数字图像进行分类,以表示其代表的数字。我们还研究了混淆矩阵,作为确定我们的分类模型表现如何的手段。

    2.  我们对无监督技术的案例研究展示了我们如何使用主成分分析来减少输入变量,以便进一步构建模型,同时保持大部分信息。

第四章:在单台计算机上处理大型数据

本章涵盖

  • 在单台计算机上处理大型数据集

  • 使用适合大型数据集的 Python 库

  • 理解选择正确算法和数据结构的重要性

  • 理解如何调整算法以在数据库内部工作

如果你的数据量如此之大,以至于似乎超出了你的能力,你的技术似乎也不再足够,你会怎么办,屈服还是适应?

幸运的是,你选择了适应,因为你还在阅读。本章将向你介绍一些技术和工具,以处理更大的数据集,如果你采用正确的技术,这些数据集仍然可以通过单台计算机进行管理。

本章为你提供了在数据不再适合你计算机的 RAM(随机访问内存)中进行分类和回归的工具,而第三章则专注于内存数据集。第五章将更进一步,教你如何处理需要多台计算机来处理的数据集。当我们在本章中提到“大型数据”时,我们指的是那些在内存或速度方面引起问题,但仍然可以通过单台计算机处理的数据。

我们从本章开始,概述你在处理大型数据集时面临的问题。然后我们提供三种类型的解决方案来克服这些问题:调整你的算法、选择正确的数据结构,以及选择正确的工具。数据科学家并不是唯一需要处理大量数据的人,因此你可以应用通用的最佳实践来解决大数据问题。最后,我们将这些知识应用于两个案例研究。第一个案例展示了如何检测恶意 URL,第二个案例演示了如何在数据库内部构建推荐引擎。

4.1. 处理大型数据时面临的问题

大量的数据带来了新的挑战,例如内存过载和永不停止运行的算法。这迫使你适应并扩展你的技术储备。但即使你能够执行分析,你也应该注意诸如 I/O(输入/输出)和 CPU 饥饿等问题,因为这些可能会引起速度问题。图 4.1 展示了一个思维导图,随着我们逐步进行,它将逐渐展开:问题、解决方案和技巧。

图 4.1:处理比内存能容纳更多的数据时遇到的问题概述

计算机只有有限的 RAM。当你试图将比实际适合的更多数据挤进这个内存时,操作系统将开始将内存块交换到磁盘上,这比所有数据都在内存中要低效得多。但只有少数算法被设计来处理大数据集;其中大多数一次将整个数据集加载到内存中,这会导致内存不足错误。其他算法需要在内存中保留数据的多个副本或存储中间结果。所有这些都加剧了这个问题。

即使你解决了内存问题,你可能还需要处理另一个有限的资源:时间。虽然计算机可能认为你活了几百万年,但现实中你不会(除非你进入冷冻睡眠直到你的 PC 完成)。某些算法没有考虑到时间;它们会永远运行。其他算法在需要处理仅几兆字节的数据时,无法在合理的时间内结束。

处理大数据集时,你还会观察到第三件事,那就是你的计算机组件可能会开始形成瓶颈,而其他系统则闲置。虽然这不像无限循环的算法或内存不足错误那样严重,但它仍然会带来严重的成本。从节省人天和计算基础设施的角度来考虑 CPU 饥饿的成本。某些程序因为必须从硬盘读取数据(这是计算机上速度最慢的组件之一)而无法快速向处理器提供数据。这个问题随着固态硬盘(SSD)的引入得到了解决,但 SSD 仍然比速度较慢且更广泛使用的硬盘驱动器(HDD)技术要贵得多。

4.2. 处理大量数据的通用技术

无限循环的算法、内存不足错误和速度问题是你在处理大数据时面临的最常见挑战。在本节中,我们将探讨克服或减轻这些问题的解决方案。

解决方案可以分为三类:使用正确的算法、选择合适的数据结构和使用合适的工具(图 4.2)。

图 4.2. 处理大数据集的解决方案概述

图片

问题与解决方案之间不存在明确的单一映射,因为许多解决方案同时解决了内存不足和计算性能问题。例如,数据集压缩可以帮助你解决内存问题,因为数据集变得更小。但这也会影响计算速度,从慢速硬盘转移到快速 CPU。与 RAM(随机访问内存)不同,硬盘会在断电后存储一切,但写入硬盘比在短暂的 RAM 中更改信息花费更多时间。当不断更改信息时,RAM 因此比(更耐用的)硬盘更可取。在未打包的数据集中,会发生大量的读写操作(I/O),但 CPU 仍然大部分空闲,而压缩数据集时,CPU 可以获得其应得的工作量。在我们探索一些解决方案时,请记住这一点。

4.2.1. 选择合适的算法

选择合适的算法可以解决比增加更多或更好的硬件更多的问题。一个适合处理大量数据的算法不需要将整个数据集加载到内存中进行预测。理想情况下,该算法还支持并行计算。在本节中,我们将深入了解三种可以实现这一点的算法类型:在线算法块算法MapReduce 算法,如图 4.3 所示。

图 4.3. 适应大数据集的技术概述

在线学习算法

一些机器学习算法可以通过每次使用一个观察值来训练,而不是将所有数据都加载到内存中。当新的数据点到达时,模型被训练,观察值可以被遗忘;其影响现在已纳入模型参数。例如,用于预测天气的模型可以使用不同的参数(如大气压力或温度)在不同地区。当来自一个地区的数据被加载到算法中时,它会忘记这些原始数据,然后转向下一个地区。这种“使用并遗忘”的工作方式是解决内存问题的完美解决方案,因为单个观察值不太可能大到足以填满现代计算机的所有内存。

列表 4.1 展示了如何将此原理应用于具有在线学习的感知器。感知器是用于二元分类(0 或 1)的最简单机器学习算法之一;例如,顾客会购买还是不会购买?

列表 4.1. 通过观察训练感知器

我们将放大代码中可能不是那么容易理解的部分。我们将首先解释 train_observation() 函数的工作原理。这个函数有两个主要部分。第一部分是计算观察值的预测并将其与实际值进行比较。第二部分是如果预测似乎错误,则更改权重。

预测(y)是通过将独立变量的输入向量与其相应的权重相乘并求和项(如线性回归中所示)来计算的。然后,将此值与阈值进行比较。如果它大于阈值,算法将输出 1,如果它小于阈值,算法将输出 0。设置阈值是一个主观的事情,取决于你的业务案例。比如说,你正在预测某人是否患有某种致命疾病,其中 1 表示阳性,0 表示阴性。在这种情况下,较低的阈值更好:被发现阳性并进行第二次调查并不像忽视疾病并让患者死亡那样糟糕。计算误差,这将给出权重变化的指示。

result = np.dot(X, self.weights) > self.threshold
error = y - result

权重的改变是根据误差的符号进行的。更新是通过感知器的学习规则完成的。对于权重向量中的每一个权重,你使用以下规则更新其值:

Δwi = αexi

其中 Δw[i] 是权重需要改变的数量,α 是学习率,ε 是误差,x^i 是输入向量中的第 i 个值(第 i 个预测变量)。误差计数是一个变量,用于跟踪在这个时期内预测错误的观察数量,并将其返回给调用函数。如果原始预测错误,则将一个观察值添加到误差计数器中。一个 epoch 是一次通过所有观察值的单个训练运行。

if error != 0:
       error_count += 1
            for index, value in enumerate(X):
                self.weights[index] +=  self.learning_rate * error * value

我们将更详细地讨论的第二个函数是 train() 函数。这个函数有一个内部循环,它不断地训练感知器,直到它可以完美预测或者直到它达到了一定的训练轮数(epochs),如下所示。

列表 4.2. 使用 train 函数

大多数在线算法也可以处理小批量;这样,你可以一次性输入 10 到 1,000 个观察值的批次,同时使用滑动窗口遍历你的数据。你有三个选项:

  • 全批量学习(也称为统计学习) — 一次性将所有数据输入算法。这正是我们在第三章中做的事情。

  • 小批量学习 — 一次输入算法一小部分观察值(100,1000,...,取决于你的硬件能处理多少)。

  • 在线学习 — 一次输入算法一个观察值。

在线学习技术相关于流式算法,其中你只能看到每个数据点一次。想想 incoming Twitter 数据:它被加载到算法中,然后观察(推文)被丢弃,因为 incoming tweets 的数量可能会很快超过硬件的处理能力。在线学习算法与流式算法的不同之处在于,它们可以看到相同的观察结果多次。确实,在线学习算法和流式算法都可以逐个从观察结果中学习。它们的不同之处在于,在线算法还可以用于静态数据源以及流数据源,通过以小批量(小到单个观察结果)的形式呈现数据,这使得你可以多次遍历数据。而流式算法则不同,数据流入系统,你需要立即进行计算。它们相似之处在于,它们一次只处理少量数据。

将大矩阵分割成许多小矩阵

而在前一章中,我们几乎不需要处理算法如何精确估计参数的问题,深入探讨这个问题有时可能会有所帮助。例如,通过将大数据表切割成小矩阵,我们仍然可以进行线性回归。这种矩阵分割的逻辑以及如何使用矩阵进行线性回归的计算可以在侧边栏中找到。现在只需知道,我们即将使用的 Python 库将负责矩阵分割,线性回归的变量权重可以使用矩阵微积分来计算。

块矩阵和线性回归系数估计的矩阵公式

某些算法可以被转换成使用矩阵块而不是完整矩阵的算法。当你将矩阵分割成块矩阵时,你将完整矩阵分割成部分,并使用较小的部分而不是完整矩阵。在这种情况下,你可以将较小的矩阵加载到内存中并执行计算,从而避免内存不足错误。图 4.4 展示了如何将矩阵加法 A + B 重写为子矩阵。

图 4.4. 块矩阵可以用来计算矩阵 A 和 B 的和。

图片

图 4.4 中的公式显示,将矩阵 A 和 B 一次性相加与先加矩阵的上半部分然后加下半部分没有区别。

所有常见的矩阵和向量运算,如乘法、求逆和奇异值分解(类似于 PCA 的变量减少技术),都可以用块矩阵来表示。¹ 块矩阵运算通过将问题分割成更小的块来节省内存,并且易于并行化。

尽管大多数数值包都有高度优化的代码,但它们只处理可以放入内存的矩阵,并且在有利的条件下会在内存中使用块矩阵。对于超出内存的矩阵,它们不会为你优化这一点,你需要自己将矩阵分割成更小的矩阵,并实现块矩阵版本。

线性回归 是一种使用预测变量的线性组合来预测连续变量的方法;执行计算的最基本方法之一是使用称为 普通最小二乘法 的技术。矩阵形式的公式是

β = (XTX)(-1)X^Ty

其中 β 是你想要检索的系数,X 是预测变量,y 是目标变量。

我们用来完成任务的 Python 工具如下:^([1])

¹

对于那些想要尝试的人来说,在计算奇异值分解时,给定变换比 Householder 变换更容易实现。

  • bcolz 是一个 Python 库,可以紧凑地存储数据数组,并在数组不再适合主内存时使用硬盘。

  • Dask 是一个库,它使你能够优化计算流程,并使并行计算更容易。它不是 Anaconda 默认设置的一部分,所以在运行下面的代码之前,请确保在你的虚拟环境中使用 conda install dask。注意:当使用 64 位 Python 导入 Dask 时,已经报告了一些错误。Dask 依赖于一些其他库(如 toolz),但依赖关系应该由 pip 或 conda 自动处理。

以下列表展示了使用这些库进行块矩阵计算。

列表 4.3. 使用 bcolz 和 Dask 库进行块矩阵计算

注意,你不需要使用块矩阵求逆,因为 XTX 是一个大小为预测变量数量 * 预测变量数量的平方矩阵。这是幸运的,因为 Dask 还不支持块矩阵求逆。你可以在维基百科的矩阵算术页面en.wikipedia.org/wiki/Matrix_(mathematics)上找到更多一般信息。

MapReduce

使用类比来理解 MapReduce 算法是很容易的:想象一下,有人要求你统计全国选举的所有选票。你的国家有 25 个政党,1,500 个投票办公室,和 200 万人。你可以选择从每个办公室单独收集所有选票并集中计数,或者你可以要求当地办公室为 25 个政党统计选票,并将结果交给你,然后你可以按政党进行汇总。

Map reducers 遵循与第二种工作方式类似的过程。它们首先将值映射到键上,然后在 reduce 阶段对该键进行聚合。查看以下列表的伪代码,以更好地了解这种感觉。

列表 4.4. MapReduce 伪代码示例
For each person in voting office:
    Yield (voted_party, 1)
For each vote in voting office:
    add_vote_to_party()

MapReduce 算法的一个优点是它们易于并行化和分发。这解释了它们在 Hadoop 等分布式环境中的成功,但它们也可以在单个计算机上使用。我们将在下一章更深入地探讨它们,并在第九章中提供了一个(JavaScript)实现示例。当在 Python 中实现 MapReduce 时,您不需要从头开始。许多库已经为您做了大部分工作,例如 Hadoopy、Octopy、Disco 或 Dumbo。

4.2.2. 选择合适的数据结构

算法可以成就或毁掉您的程序,但您存储数据的方式同样重要。数据结构有不同的存储需求,但也会影响数据集上CRUD(创建、读取、更新和删除)和其他操作的性能。

图 4.5 展示了您可以选择的许多不同的数据结构,其中我们将讨论三种:稀疏数据、树数据和哈希数据。让我们首先看看稀疏数据集。

图 4.5. 在处理大数据时,数据科学中常用数据结构的概述

稀疏数据

与其条目(观测值)相比,稀疏数据集包含相对较少的信息。看看图 4.6:几乎全是“0”,仅在第二个观测值变量 9 中有一个“1”。

图 4.6. 稀疏矩阵的示例:几乎全是 0;在稀疏矩阵中,其他值是例外

这样的数据可能看起来很荒谬,但将文本数据转换为二进制数据时,这通常是您得到的结果。想象一下一组 10 万个完全不相关的 Twitter 推文。其中大多数可能少于 30 个单词,但合在一起可能有数百或数千个不同的单词。在文本挖掘章节中,我们将介绍将文本文档切割成单词并将它们存储为向量的过程。但到目前为止,想象一下如果每个单词都被转换为一个二进制变量,其中“1”代表“存在于这条推文中”,而“0”表示“不存在于这条推文中”。这将确实导致稀疏数据。结果的大矩阵即使包含很少的信息,也可能导致内存问题。

幸运的是,这样的数据可以压缩存储。在图 4.6 的情况下,它可能看起来像这样:

data = [(2,9,1)]

第 2 行第 9 列的值为 1。

在 Python 中,支持处理稀疏矩阵的功能正在增长。现在许多算法都支持或返回稀疏矩阵。

树结构

树是一种数据结构,它允许你比扫描整个表更快地检索信息。树总是有一个根值和子树,每个子树都有自己的子节点,以此类推。简单的例子可以是你的家族树或生物树以及它如何分裂成分支、枝条和叶子。简单的决策规则使得很容易找到你的数据所在的子树。查看图 4.7 以了解树结构如何帮助你快速获取相关信息。

图 4.7. 树数据结构的示例:可以使用如年龄类别这样的决策规则快速定位家族树中的人

图片

在图 4.7 中,你从顶部开始搜索,首先选择一个年龄类别,因为显然这是剔除最多替代因素的因素。这个过程一直持续到找到你想要的东西。对于不熟悉 Akinator 的人来说,我们建议访问en.akinator.com/。Akinator 是一个神奇灯中的精灵,通过询问你关于某人的几个问题来试图猜测你心中的那个人。试试看,你会感到惊讶……或者看看这种魔法是如何变成树搜索的。

树在数据库中也非常受欢迎。数据库更喜欢不从头到尾扫描表,而是使用一个称为 索引 的设备来避免这种情况。索引通常基于树和哈希表等数据结构来更快地找到观察结果。使用索引极大地加快了查找数据的过程。让我们看看这些哈希表。

哈希表

哈希表是一种数据结构,为你的数据中的每个值计算一个键,并将这些键放入桶中。这样,当你遇到数据时,可以通过查看正确的桶来快速检索信息。Python 中的字典是哈希表实现,它们是键值存储的近亲。你将在本章的最后一个例子中遇到它们,当你在一个数据库中构建推荐系统时。哈希表在数据库中广泛用作索引,以实现快速信息检索。

4.2.3. 选择合适的工具

当合适的算法和数据结构就位时,是时候选择合适的工具来完成这项工作了。合适的工具可以是 Python 库,或者至少是可以通过 Python 控制的工具,如图 4.8 所示。可用的有用工具数量巨大,所以我们只看其中的一小部分。

图 4.8. 在处理大量数据时可以使用的工具概述

图片

Python 工具

Python 有许多库可以帮助你处理大量数据。这些库从更智能的数据结构到代码优化器,再到即时编译器应有尽有。以下是我们面对大量数据时喜欢使用的库列表:

  • Cython —你越接近计算机的实际硬件,计算机知道它必须处理的数据类型就越重要。对于计算机来说,1 + 1 与 1.00 + 1.00 的加法是不同的。第一个例子由整数组成,第二个例子由浮点数组成,这些计算由 CPU 的不同部分执行。在 Python 中,你不需要指定你使用的数据类型,因此 Python 编译器必须推断它们。但是,推断数据类型是一个耗时的操作,这也是 Python 不是最快的语言之一的部分原因。Cython,Python 的超集,通过在开发程序时强制程序员指定数据类型来解决此问题。一旦编译器有了这些信息,它就会以更快的速度运行程序。有关 Cython 的更多信息,请参阅 cython.org/

  • Numexpr —Numexpr 是许多大数据包的核心,就像 NumPy 对于内存中的包一样。Numexpr 是一个用于 NumPy 的数值表达式评估器,但可以比原始 NumPy 快得多。为了实现这一点,它重写你的表达式并使用内部(即时)编译器。有关 Numexpr 的详细信息,请参阅 github.com/pydata/numexpr

  • Numba —Numba 通过在执行前编译你的代码来帮助你实现更高的速度,这被称为 即时编译。这让你能够编写高级代码,但达到与 C 代码相似的速度。使用 Numba 很简单;请参阅 numba.pydata.org/ 了解更多信息。

  • Bcolz —Bcolz 帮助你克服在使用 NumPy 时可能出现的内存不足问题。它可以以最优压缩形式存储和操作数组。它不仅减少了你的数据需求,还在后台使用 Numexpr 来减少使用 bcolz 数组进行计算时所需的计算量。请参阅 bcolz.blosc.org/ 了解更多信息。

  • Blaze —如果你想要使用数据库后端的力量,但又喜欢“Pythonic”的数据处理方式,Blaze 是理想的选择。Blaze 会将你的 Python 代码转换为 SQL,但可以处理比关系数据库更多的数据存储,如 CSV、Spark 等。Blaze 提供了一种统一的方式来处理多个数据库和数据库。尽管如此,Blaze 仍在开发中,因此许多功能尚未实现。请参阅 blaze.readthedocs.org/en/latest/index.html 了解更多信息。

  • Theano —Theano 允许你直接与图形处理单元 (GPU) 交互,并在可能的情况下进行符号化简化,它还附带了一个优秀的即时编译器。除此之外,它还是一个处理高级但有用的数学概念(张量)的出色库。请参阅 deeplearning.net/software/theano/

  • Dask — Dask 允许您优化计算流程并高效执行它们。它还允许您分发计算。请参阅 dask.pydata.org/en/latest/

这些库主要关于使用 Python 本身进行数据处理(除了 Blaze,它还可以连接到数据库)。为了实现高端性能,您可以使用 Python 与各种数据库或其他软件进行通信。

使用 Python 作为主控来控制其他工具

大多数软件和工具制造商都支持其软件的 Python 接口。这使得您能够轻松地利用 Python 的便捷性和生产力来访问专门的软件组件。这样,Python 与其他流行的数据科学语言(如 R 和 SAS)区分开来。您应该充分利用这种奢侈,最大限度地发挥专用工具的威力。第六章 介绍了一个使用 Python 连接到 NoSQL 数据库的案例研究,第七章 则介绍了与图数据相关的案例。

现在我们来看一下处理大数据时的一些更通用的有用提示。

4.3. 处理大数据集的一般编程技巧

在一般编程环境中有效的技巧在数据科学中仍然适用。其中一些可能措辞略有不同,但所有程序员的原则基本上是相同的。本节回顾了在数据科学环境中重要的这些技巧。

您可以将一般技巧分为三个部分,如图 4.9 中的思维导图所示:

图 4.9. 处理大数据时的一般编程最佳实践概述

  • 不要重复造轮子。 使用他人开发的工具和库。

  • 充分利用您的硬件。 您的机器从未被充分利用;通过简单的适配,您可以使其更努力地工作。

  • 减少计算需求。 尽可能地精简您的内存和处理需求。

当面对具体问题时,“不要重复造轮子”可能说起来容易做起来难,但您的第一个想法始终应该是,“肯定有人在我之前遇到过这个问题。”

4.3.1. 不要重复造轮子

“不要重复别人的工作”可能比“不要重复自己”更好。通过您的行动增加价值:确保它们有意义。解决已经解决的问题是一种浪费时间的行为。作为一名数据科学家,您有两个重要的规则可以帮助您处理大数据并使您的工作效率更高:

  • 利用数据库的力量。 当数据科学家处理大数据集时,他们大多数人的第一反应是在数据库内部准备他们的分析基础表。当你要准备的特征相对简单时,这种方法效果很好。当这种准备涉及到高级建模时,找出是否可以采用用户定义的函数和过程。本章的最后一个例子是关于将数据库集成到你的工作流程中。

  • 使用优化库。 创建像 Mahout、Weka 和其他机器学习算法这样的库需要时间和知识。它们高度优化,并融合了最佳实践和最先进的技术。把时间花在完成任务上,而不是重复别人的工作,除非是为了理解事物是如何工作的。

然后你必须考虑你的硬件限制。

4.3.2. 充分利用你的硬件

计算机上的资源可能处于闲置状态,而其他资源则过度使用。这会减慢程序的速度,甚至可能导致它们失败。有时(并且是必要的)可以使用以下技术将工作负载从过度使用的资源转移到未充分利用的资源:

  • 向 CPU 提供压缩数据。 避免 CPU 饥饿的一个简单技巧是向 CPU 提供压缩数据而不是膨胀(原始)数据。这将使更多的工作从硬盘转移到 CPU,这正是你想要的,因为在大多数现代计算机架构中,硬盘无法跟上 CPU 的速度。

  • 利用 GPU 的优势。 有时候,瓶颈不是你的内存,而是 CPU。如果你的计算可以并行化,你可以从切换到 GPU 中受益。GPU 在计算方面比 CPU 有更高的吞吐量。GPU 在并行化工作中非常高效,但它的缓存比 CPU 少。但是,如果你的问题是硬盘,切换到 GPU 是没有意义的。几个 Python 包,如 Theano 和 NumbaPro,可以在不费太多编程努力的情况下使用 GPU。如果这还不够,你可以使用 CUDA(计算统一设备架构)包,如 PyCUDA。如果你对创建自己的货币感兴趣,这也是比特币挖矿中的一个众所周知的小技巧。

  • 使用多线程。 在你的 CPU 上并行化计算仍然是可能的。你可以通过正常的 Python 线程来实现这一点。

4.3.3. 减少你的计算需求

“聪明工作 + 努力工作 = 成就。” 这同样适用于你编写的程序。避免出现大量数据问题的最佳方式是在一开始就尽可能多地移除工作,并让计算机只处理那些无法跳过的部分。以下列表包含了一些帮助你实现这一目标的方法:

  • 分析你的代码并修复慢速代码部分。 并非你代码的每一部分都需要优化;使用分析器来检测程序中的慢速部分,并修复这些部分。

  • 尽可能使用编译代码,特别是在涉及循环时。 无论如何可能,使用针对数值计算进行优化的包中的函数,而不是自己实现所有内容。这些包中的代码通常高度优化且已编译。

  • 否则,自己编译代码。 如果你不能使用现有的包,可以使用即时编译器,或者将代码中的最慢部分用 C 或 Fortran 等底层语言实现,并将其与代码库集成。如果你决定转向 底层语言(更接近通用计算机字节码的语言),学习使用计算库,如 LAPACK、BLAST、Intel MKL 和 ATLAS。这些库高度优化,难以达到它们的性能水平。

  • 避免将数据拉入内存。 当你处理无法装入内存的数据时,避免将所有数据都拉入内存。这样做的一个简单方法是通过分块读取数据并在飞行中解析数据。这并不适用于每个算法,但可以使对极其大型数据集的计算成为可能。

  • 使用生成器来避免中间数据存储。 生成器帮助你按观测值返回数据,而不是批量返回。这样,你就可以避免存储中间结果。

  • 尽可能少地使用数据。 如果没有可用的大规模算法,并且你不愿意自己实现此类技术,那么你仍然可以在原始数据的一个样本上训练你的数据。

  • 尽可能使用你的数学技能来简化计算。 以以下方程为例:(a + b)² = a² + 2ab + b²。方程的左侧将比方程的右侧计算得更快;即使对于这个简单的例子,在处理大量数据时也可能有所区别。

4.4. 案例研究 1:预测恶意 URL

互联网可能是现代最伟大的发明之一。它推动了人类的发展,但并非每个人都怀着崇高的意图使用这一伟大发明。许多公司(例如 Google)试图通过为我们检测恶意网站来保护我们免受欺诈。这样做并不容易,因为互联网上有数十亿个网页需要扫描。在本案例研究中,我们将展示如何处理不再适合内存的数据集。

我们将使用

  • 数据 — 本案例研究中的数据作为研究项目的一部分提供。该项目包含 120 天的数据,每个观测值大约有 3,200,000 个特征。目标变量包含 1 表示恶意网站,否则为 -1。更多信息,请参阅“超越黑名单:从可疑 URL 中学习检测恶意网站。”^([2])

    ²

    Justin Ma, Lawrence K. Saul, Stefan Savage, and Geoffrey M. Voelker, “Beyond Blacklists: Learning to Detect Malicious Web Sites from Suspicious URLs,” Proceedings of the ACM SIGKDD Conference, Paris (June 2009), 1245–53.

  • Scikit-learn 库 — 到目前为止,你应该在你的 Python 环境中安装了这个库,因为我们已经在上一章中使用过它了。

如您所见,对于这个案例,我们不需要太多,让我们深入探讨。

4.4.1. 第 1 步:定义研究目标

我们项目的目标是检测某些 URL 是否可信。由于数据量很大,我们旨在以内存友好的方式完成这项工作。在下一步中,我们将首先看看如果我们不关心内存(RAM)问题会发生什么。

4.4.2. 第 2 步:获取 URL 数据

首先从 sysnet.ucsd.edu/projects/url/#datasets 下载数据并将其放置在一个文件夹中。选择 SVMLight 格式的数据。SVMLight 是一种基于文本的格式,每行一个观测值。为了节省空间,它省略了零。

以下列表和图 4.10 展示了当你尝试读取 120 个文件中的 1 个并创建算法期望的正常矩阵时会发生什么。todense() 方法将数据从特殊文件格式转换为包含每个条目值的正常矩阵。

图 4.10. 尝试将大量数据集加载到内存时出现的内存错误

图片 04fig10_alt

列表 4.5. 生成内存不足错误

图片 104fig01_alt

惊讶,惊讶,我们得到了一个内存不足错误。除非你在超级大的机器上运行此代码。经过一些技巧后,你将不再遇到这些内存问题,并将检测到 97% 的恶意网站。

工具和技术

在加载单个文件时遇到了内存错误——还有 119 个文件要加载。幸运的是,我们有一些技巧。让我们在案例研究中尝试这些技术:

  • 使用数据的稀疏表示。

  • 用压缩数据而不是原始数据喂给算法。

  • 使用在线算法进行预测。

当我们使用它时,我们将更深入地探讨每个“技巧”。现在我们已经将数据本地化了,让我们访问它。数据科学流程的第 3 步,数据准备和清洗,在这个案例中不是必要的,因为 URL 已经预先清洗过。然而,在我们释放学习算法之前,我们需要进行某种形式的探索。

4.4.3. 第 4 步:数据探索

为了看看我们是否可以应用我们的第一个技巧(稀疏表示),我们需要找出数据是否确实包含大量零。我们可以使用以下代码片段进行检查:

print "number of non-zero entries %2.6f"  % float((X.nnz)/(float(X.shape[0]) * float(X.shape[1])))

这会输出以下内容:

number of non-zero entries 0.000033

与零相比信息量小的数据称为 稀疏数据。如果将数据存储为 [(0,0,1),(4,4,1)] 而不是

[[1,0,0,0,0],[0,0,0,0,0],[0,0,0,0,0],[0,0,0,0,0],[0,0,0,0,1]]

实现这一点的文件格式之一是 SVMLight,这正是我们下载此格式数据的原因。尽管如此,我们还没有完成,因为我们还需要了解数据内部的维度。

要获取这些信息,我们已经在检查最大观测值和变量数时保持了数据压缩。我们还需要逐个读取数据文件。这样你消耗的内存会更少。第二个技巧是向 CPU 提供压缩文件。在我们的例子中,它已经打包在 tar.gz 格式中。你只有在需要时才解压文件,而不会将其写入硬盘(你电脑中最慢的部分)。

对于我们的示例,列表 4.6 中,我们只会在前 5 个文件上工作,但你可以自由地使用所有文件。

列表 4.6. 检查数据大小

部分代码需要一些额外的解释。在这个代码中,我们遍历 tar 归档文件内的 svm 文件。我们逐个解压文件以减少所需的内存。由于这些文件是 SVM 格式,我们使用辅助函数functionload_svmlight_file()来加载特定文件。然后我们可以通过检查结果数据集的形状来查看文件包含多少观测值和变量。

带着这些信息,我们可以继续进行模型构建。

4.4.4. 步骤 5:模型构建

现在我们已经知道了数据的维度,我们可以在以下列表中应用相同的两个技巧(压缩文件的稀疏表示)并添加第三个(使用在线算法)。让我们找到那些有害网站!

列表 4.7. 创建一个模型来区分恶意 URL 和正常 URL

之前列表中的代码看起来与我们之前所做的相当相似,除了随机梯度下降分类器SGDClassifier()

在这里,我们通过使用partial_fit()函数在一个文件中呈现观测值来迭代训练算法。

只遍历前 5 个文件在这里给出了表 4.1 中显示的输出。该表显示了分类诊断指标:精确度、召回率、F1 分数和支持度。

表 4.1. 分类问题:网站是否可信?
精确度 召回率 F1 分数 支持度
-1 0.97 0.99 0.98 14045
1 0.97 0.94 0.96 5955
平均/总计 0.97 0.97 0.97 20000

只有 3%(1 - 0.97)的恶意网站没有被检测到(精确度),6%(1 - 0.94)的检测到的网站被错误地指控(召回率)。这是一个不错的结果,因此我们可以得出结论,该方法有效。如果我们重新运行分析,结果可能会有所不同,因为算法可能会收敛得略有不同。如果你不介意等待一会儿,你可以使用完整的数据集。你现在可以处理所有数据而不会出现问题。在这种情况下研究,我们不会有第六步(展示或自动化)。

现在,让我们看看我们技术的第二个应用;这次你将在数据库内构建一个推荐系统。要了解推荐系统的知名例子,请访问亚马逊网站。在浏览时,你很快就会遇到推荐:“购买此产品的用户还购买了...”

4.5. 案例研究 2:在数据库内构建推荐系统

在现实中,你处理的大部分数据都存储在关系型数据库中,但大多数数据库并不适合数据挖掘。但正如这个例子所示,我们可以调整我们的技术,以便你可以在数据库内部进行大部分分析,从而利用数据库的查询优化器为你优化代码。在这个例子中,我们将探讨如何使用哈希表数据结构以及如何使用 Python 控制其他工具。

4.5.1. 需要的工具和技术

在进入案例研究之前,我们需要快速查看所需的工具和即将进行的操作的理论背景。

工具
  • MySQL 数据库——需要 MySQL 数据库来工作。如果你还没有安装 MySQL 社区服务器,你可以从www.mysql.com下载一个。附录 C:“安装 MySQL 服务器”解释了如何设置它。

  • MySQL 数据库连接 Python 库——要从 Python 连接到这个服务器,你还需要安装 SQLAlchemy 或其他能够与 MySQL 通信的库。我们使用 MySQLdb。在 Windows 上,你不能直接使用 Conda 来安装它。首先安装 Binstar(另一个包管理服务),然后寻找适合你的 Python 设置的适当 mysql-python 包。

conda install binstar
binstar search -t conda mysql-python

在 Windows 命令行中输入的以下命令对我们有效(在激活 Python 环境之后):

conda install --channel https://conda.binstar.org/krisvanneste mysql-python

再次提醒,如果你更熟悉 SQLAlchemy 库,请随意使用。

  • 我们还需要pandas Python 库,但到现在应该已经安装好了。

基础设施就绪后,让我们深入探讨一些技术。

技术

一个简单的推荐系统会寻找与你租借过类似电影的用户,然后推荐那些其他人看过但你还没有看过的电影。在机器学习中,这种技术被称为k-最近邻

一个行为与你相似的客户并不一定是最相似的客户。你需要使用一种技术来确保你能够找到相似的客户(局部最优解),而不保证你已经找到了最佳客户(全局最优解)。解决这个问题的常用技术被称为局部敏感哈希。关于这个主题的论文概述可以在www.mit.edu/~andoni/LSH/找到。

本地敏感哈希背后的思想很简单:构建函数,将相似的客户映射到一起(它们被放入具有相同标签的桶中),并确保不同的对象被放入不同的桶中。

这个想法的核心是一个执行映射的函数。这个函数被称为哈希函数:一个将任何输入范围映射到固定输出的函数。最简单的哈希函数将来自几个随机列的值连接起来。列的数量(可扩展输入)无关紧要;它将其还原为单个列(固定输出)。

你将设置三个哈希函数来找到相似的客户。这三个函数取三部电影的值:

  • 第一个函数取电影 10、15 和 28 的值。

  • 第二个函数取电影 7、18 和 22 的值。

  • 最后一个函数取电影 16、19 和 30 的值。

这将确保处于同一桶的客户至少共享几部电影。但桶内的客户可能在未包含在哈希函数中的电影上有所不同。为了解决这个问题,你仍然需要比较桶内的客户。为此,你需要创建一个新的距离度量。

你将用来比较客户的距离称为汉明距离。汉明距离用于计算两个字符串的差异程度。距离定义为字符串中不同字符的数量。表 4.2 提供了汉明距离的一些示例。

表 4.2. 计算汉明距离的示例
字符串 1 字符串 2 汉明距离
帽子 1
帽子 狂野 2
老虎 狮子 2
巴黎 罗马 5

比较多个列是一个昂贵的操作,所以你需要一个技巧来加快这个过程。因为列包含一个二进制(0 或 1)变量来指示客户是否购买过电影,你可以将信息连接起来,使得相同的信息包含在一个新的列中。表 4.3 显示了包含所有电影列信息的“movies”变量。

表 4.3. 将不同列的信息合并到 movies 列中。这也是 DNA 的工作方式:长字符串中的所有信息。
列 1 电影 1 电影 2 电影 3 电影 4 movies
客户 1 1 0 1 1 1011
客户 2 0 0 0 1 0001

这允许你更有效地计算汉明距离。通过将此操作符作为位来处理,你可以利用 XOR 操作符。XOR 操作符(^)的结果如下:

1¹ = 0
1⁰ = 1
0¹ = 1
0⁰ = 0

在此基础上,找到相似客户的过程变得非常简单。让我们首先用伪代码来看一下:

预处理:

1. 定义 p(例如,3)个函数,从电影向量中选择 k(例如,3)个条目。这里我们取 3 个函数(p),每个函数取 3 部电影。

2.  将这些函数应用于每个点,并将它们存储在单独的列中。(在文献中,每个函数被称为哈希函数,每个列将存储一个桶。)

查询点 q:

1.  将相同的 p 函数应用于你想要查询的点(观察)q。

2.  为每个函数检索对应桶中结果的点。

当你检索到桶中的所有点或达到 2p 点(例如,如果你有 5 个函数,则为 10)时停止。

3.  计算每个点的距离,并返回距离最小的点。

让我们看看 Python 中的一个实际实现,以使这一切更加清晰。

4.5.2. 第 1 步:研究问题

假设你在一个录像店工作,经理问你是否可以使用人们租借电影的信息来预测他们可能喜欢其他哪些电影。您的老板已经将这些数据存储在 MySQL 数据库中,您需要进行分析。他所指的是推荐系统,这是一种自动学习人们偏好的系统,并为尚未尝试的客户推荐电影和其他产品。我们案例研究的目的是创建一个易于记忆的推荐系统。我们将使用数据库和一些额外的技巧来实现这一点。为了这个案例研究,我们将自己创建数据,这样我们就可以跳过数据检索步骤,直接进入数据准备阶段。然后我们可以跳过数据探索步骤,直接进入模型构建阶段。

4.5.3. 第 3 步:数据准备

您的老板收集的数据显示在表 4.4 中。为了演示,我们将自己创建这些数据。

表 4.4. 客户数据库和客户租借的电影摘要
客户 电影 1 电影 2 电影 3 ... 电影 32
Jack Dani 1 0 0 1
Wilhelmson 1 1 0 1
...
Jane Dane 0 0 1 0
Xi Liu 0 0 0 1
Eros Mazo 1 1 0 1
...

对于每位客户,你都会得到一个指示,表明他们之前是否租过这部电影(1)或者没有(0)。让我们看看你还需要什么,这样你就可以给你的老板提供他想要的推荐系统。

首先,让我们将 Python 连接到 MySQL 来创建我们的数据。使用您的用户名和密码连接到 MySQL。在下面的列表中,我们使用了一个名为“test”的数据库。将用户、密码和数据库名称替换为您的设置中的相应值,并检索连接和游标。数据库游标是一种控制结构,它记得你在数据库中的当前位置。

列表 4.8. 在数据库中创建客户

我们创建了 100 个客户,并随机分配他们是否观看过某些电影,总共有 32 部电影。数据最初是在 Pandas 数据框中创建的,但随后被转换成 SQL 代码。注意:运行此代码时可能会遇到警告。警告指出:“带有 DBAPI 连接的“mysql”风味已被弃用,将在未来版本中删除。MySQL 将通过 SQLAlchemy 引擎进一步支持”。您可以随时切换到 SQLAlchemy 或其他库。我们将在其他章节中使用 SQLAlchemy,但在这里使用 MySQLdb 来扩展示例。

为了以后高效查询我们的数据库,我们需要进行额外的数据准备,包括以下事项:

  • 创建位字符串。位字符串是列内容的压缩版本(0 和 1 值)。首先,这些二进制值被连接起来;然后,得到的位字符串被重新解释为一个数字。现在这可能听起来很抽象,但在代码中会变得清晰。

  • 定义哈希函数。实际上,哈希函数将创建位字符串。

  • 向表中添加索引,以加快数据检索。

创建位字符串

现在,你创建一个适合查询的中间表,应用哈希函数,并将位序列表示为十进制数。最后,你可以将它们放入表中。

首先,你需要创建位字符串。你需要将字符串“11111111”转换成二进制或数值,以便汉明函数能够工作。我们选择了数值表示,如下一列表所示。

列表 4.9. 创建位字符串

通过将 32 列的信息转换为 4 个数字,我们压缩了它以便后续查找。图 4.11 显示了在询问这种新格式下前两个观察结果(客户电影观看历史)时我们得到的结果。

图 4.11. 位字符串转换成数字后前两位客户的 32 部电影信息

store[0:2]

下一步是创建哈希函数,因为它们将使我们能够采样我们将使用的数据,以确定两位客户是否有相似的行为。

创建哈希函数

我们创建的哈希函数取客户的电影值。在本案例研究的理论部分,我们决定创建 3 个哈希函数:第一个函数结合了电影 10、5 和 18;第二个结合了电影 7、18 和 22;第三个结合了 16、19 和 30。如果您想选择其他函数,这可以是随机选择的。以下代码列表显示了如何实现这一点。

列表 4.10. 创建哈希函数

哈希函数将不同电影的价值连接成一个二进制值,就像在createNum()函数中之前发生的那样,但这次我们不将其转换为数字,我们只取 3 部电影而不是 8 部电影作为输入。assert 函数显示了它如何为每个观察值连接 3 个值。当客户购买了电影 10 但没有购买电影 15 和 28 时,它将为桶 1 返回 b’100’。当客户购买了电影 7 和 18 但没有购买电影 22 时,它将为桶 2 返回 b’110’。如果我们查看当前结果,我们会看到我们之前创建的 4 个变量(bit1,bit2,bit3,bit4)来自 9 个精选电影(图 4.12)。

图 4.12. 位字符串压缩和 9 个采样电影的信息

我们将要应用的最后一个技巧是索引客户表,以便查找更快。

向表中添加索引

现在您必须添加索引以加快检索速度,这在实时系统中是必需的。这将在下一个列表中显示。

列表 4.11. 创建索引

数据索引后,我们现在可以继续到“模型构建部分”。在本案例研究中,没有实现实际的机器学习或统计模型。相反,我们将使用一种更简单的技术:字符串距离计算。可以使用之前在案例研究的理论介绍中解释的汉明距离来比较两个字符串。

4.5.4. 第 5 步:模型构建

要在数据库中使用汉明距离,我们需要将其定义为函数。

创建汉明距离函数

我们将其实现为一个用户定义函数。此函数可以计算 32 位整数(实际上是 4*8)的距离,如下所示。

列表 4.12. 创建汉明距离

如果一切顺利,此代码的输出应该是 3。

现在我们已经将汉明距离函数定位好,我们可以使用它来找到与给定客户相似的客户,这正是我们希望应用程序所做的。让我们继续到最后一个部分:利用我们的设置作为一种应用程序。

4.5.5. 第 6 步:展示和自动化

现在我们已经设置好了所有内容,当面对一个给定的客户时,我们的应用程序需要执行两个步骤:

  • 寻找类似客户。

  • 根据客户已经观看的内容和类似客户的观看历史,建议客户尚未观看的电影。

首先考虑:选择一个幸运的客户。

寻找类似客户

进行实时查询的时间。在以下列表中,客户 27 是那位快乐的客户,他将得到为他选择的下一部电影。但首先我们需要选择具有类似观看历史的客户。

列表 4.13. 寻找类似客户

表 4.5 显示客户 2 和 97 与客户 27 最为相似。不要忘记数据是随机生成的,所以任何复制此示例的人可能会得到不同的结果。

表 4.5. 与客户 27 最相似的客户
cust_id distance
0 27 0
1 2 8
2 97 9

现在,我们终于可以为客户 27 选择一部电影来观看。

寻找一部新电影

我们需要查看客户 27 尚未观看但最近客户已经看过的电影,如下所示。这也是一个很好的检查,看看你的距离函数是否工作正确的机会。尽管这可能不是最近客户,但它与客户 27 非常匹配。通过使用哈希索引,你在查询大型数据库时获得了巨大的速度提升。

列表 4.14. 寻找一部未观看的电影

表 4.6 显示你可以根据客户 2 的行为推荐电影 12、15 或 31。

表 4.6. 客户 2 的电影可以作为客户 27 的建议。
0 1 2
Cust_id 2 27 97
Movie3 0 1 1
Movie9 0 1 1
Movie11 0 1 1
Movie12 1 0 0
Movie15 1 0 0
Movie16 0 1 1
Movie25 0 1 1
Movie31 1 0 0

任务完成。我们这位快乐的影迷现在可以尽情享受一部新电影,这部电影是根据他的喜好定制的。

在下一章中,我们将查看更大的数据,并看看我们如何使用我们在第一章中下载的 Horton 沙盒来处理这些数据。第一章。

4.6. 摘要

本章讨论了以下主题:

  • 在处理大数据集时,你可能会遇到的主要 问题 如下:

    • 内存不足

    • 长运行程序

    • 形成瓶颈并导致速度问题的资源

  • 解决这些问题的三种主要类型 解决方案 如下:

    • 调整你的算法。

    • 使用不同的数据结构。

    • 依赖工具和库。

  • 可以使用三种主要技术来 调整算法

    • 一次只呈现算法数据的一个观察结果,而不是一次性加载整个数据集。

    • 将矩阵划分为更小的矩阵 并使用这些矩阵进行计算。

    • 实现 MapReduce 算法(使用 Python 库如 Hadoopy、Octopy、Disco 或 Dumbo)。

  • 数据科学中使用了三种主要 数据结构。第一种是包含相对较少信息的一种矩阵类型,即 稀疏矩阵。第二种和第三种是数据结构,使你能够在大型数据集中快速检索信息:哈希函数树结构

  • Python 有许多可以帮助你处理大型数据集的 工具。几个工具可以帮助你处理数据量的大小,其他工具可以帮助你并行化计算,还有一些工具可以克服 Python 本身的相对较慢的速度。Python 也易于用作控制其他数据科学工具的工具,因为 Python 通常被选为实现 API 的语言。

  • 计算机科学中的 最佳实践 也在数据科学环境中有效,因此应用它们可以帮助你克服在大数据环境中遇到的问题。

第五章. 大数据的第一步

本章涵盖

  • 使用两个大数据应用程序:Hadoop 和 Spark 的第一步

  • 使用 Python 编写大数据作业

  • 构建一个连接到存储在大数据数据库中的数据的交互式仪表板

在过去的两章中,我们稳步增加了数据的大小。在第三章(kindle_split_011.xhtml#ch03)中,我们处理的数据集可以适应计算机的主内存。第四章(kindle_split_012.xhtml#ch04)介绍了处理无法适应内存但仍然可以在单个计算机上处理的数据集的技术。在本章中,你将学习如何使用可以处理如此大量数据的技术,单个节点(计算机)已不再足够。实际上,它甚至可能无法适应一百台计算机。现在,这是一个挑战,不是吗?

我们将尽可能接近前几章的工作方式;重点是让你对在大数据平台上工作充满信心。为此,本章的主要内容是一个案例研究。你将创建一个仪表板,让你可以探索一家银行的贷款人数据。到本章结束时,你将完成以下步骤:

  • 将数据加载到最常见的大数据平台 Hadoop 中。

  • 使用 Spark 转换和清理数据。

  • 将其存储到名为 Hive 的大数据数据库中。

  • 使用 Qlik Sense,一个可视化工具,交互式地可视化这些数据。

所有这些(除了可视化)都将通过 Python 脚本进行协调。最终结果是允许你探索数据的仪表板,如图 5.1 所示(#ch05fig01)。

图 5.1. 交互式 Qlik 仪表板

图片 5.1

请记住,在本章关于大数据技术的入门章节中,我们只会触及实践和理论的一小部分。案例研究将涉及三种大数据技术(Hadoop、Spark 和 Hive),但仅限于数据处理,而不是模型构建。将你在这里看到的大数据技术与我们在前几章中提到的模型构建技术相结合,将取决于你。

5.1. 使用框架分配数据存储和处理

新的大数据技术,如 Hadoop 和 Spark,使得与计算机集群的交互和工作控制变得更加容易。Hadoop 可以扩展到数千台计算机,创建一个拥有千兆字节存储空间的集群。这使得企业能够掌握大量可用数据的价值。

5.1.1. Hadoop:存储和处理大数据集的框架

Apache Hadoop 是一个简化与计算机集群工作的框架。它旨在成为以下所有事物以及更多:

  • 可靠性 —通过自动创建数据的多个副本并在发生故障时重新部署处理逻辑来确保可靠性。

  • 容错性 —它检测故障并应用自动恢复。

  • 可扩展性 —数据和其处理分布在计算机集群上(横向扩展)。

  • 可移植性 — 可安装在各种硬件和操作系统上。

核心框架由分布式文件系统、资源管理器和运行分布式程序的系统组成。在实践中,它允许你几乎像使用家用计算机的本地文件系统一样轻松地使用分布式文件系统。但在幕后,数据可以在数千个服务器之间分散。

Hadoop 的不同组件

在 Hadoop 的核心,我们发现

  • 分布式文件系统(HDFS)

  • 一种在大型规模上执行程序的方法(MapReduce)

  • 管理集群资源的系统(YARN)

此外,还出现了一个应用生态系统(图 5.2),例如 Hive 和 HBase 数据库以及机器学习框架如 Mahout。在本章中,我们将使用 Hive。Hive 使用基于广泛使用的 SQL 的语言与数据库中存储的数据交互。

图 5.2. Hadoop 核心框架周围出现的应用生态系统的一个示例

可以使用流行的工具 Impala 以高达 100 倍的速度查询 Hive 数据。本书中不会详细介绍 Impala,但更多信息可以在impala.io/找到。我们已经在第四章(kindle_split_012.xhtml#ch04)中简要介绍了 MapReduce,但在这里让我们详细说明一下,因为它对 Hadoop 来说如此重要。

MapReduce:Hadoop 如何实现并行性

Hadoop 使用一种称为 MapReduce 的编程方法来实现并行性。MapReduce 算法将数据分割,并行处理,然后对结果进行排序、合并和汇总。然而,MapReduce 算法不适合交互式分析或迭代程序,因为它在每一步计算之间将数据写入磁盘。当处理大型数据集时,这很昂贵。

让我们看看 MapReduce 在一个小型虚构示例中的工作方式。你是玩具公司的总监。每个玩具都有两种颜色,当客户从网页上订购玩具时,网页会将订单文件放入 Hadoop 中,包含玩具的颜色。你的任务是找出你需要准备多少颜色单元。你将使用 MapReduce 风格的算法来计数颜色。首先让我们看看图 5.3 中的简化版本。

图 5.3. 计算输入文本中颜色计数的 MapReduce 流程的简化示例

正如其名所示,这个过程大致可以分为两个主要阶段:

  • 映射阶段 — 文档被分割成键值对。在我们减少之前,可能会有很多重复。

  • 减少阶段 — 这与 SQL 的“分组”操作类似。不同的唯一发生事件被分组在一起,根据减少函数的不同,可以创建不同的结果。在这里,我们想要每个颜色的计数,所以这就是减少函数返回的内容。

实际上,这比这要复杂一些。

整个过程在以下六个步骤中描述,并在图 5.4 中展示。

图 5.4. 计算输入文本中颜色数量的 MapReduce 流程示例

1.  读取输入文件。

2.  将每一行传递给映射器作业。

3.  映射器作业从文件中解析颜色(键)并输出一个文件,其中包含该颜色出现的次数(值)。或者更技术地说,它将一个键(颜色)映射到一个值(出现的次数)。

4.  键被洗牌和排序以方便聚合。

5.  汇总每个颜色的出现次数,并为每个键输出一个包含每个颜色总出现次数的文件。

6.  键被收集到一个输出文件中。

注意

虽然 Hadoop 让处理大数据变得容易,但设置一个良好的工作集群仍然不是一件简单的事情,但像 Apache Mesos 这样的集群管理器确实减轻了负担。实际上,许多(中等规模)公司缺乏维护健康 Hadoop 安装的技能。这就是为什么我们将使用 Hortonworks Sandbox,这是一个预安装和配置好的 Hadoop 生态系统。安装说明可以在第 1.5 节中找到:Hadoop 的入门级工作示例。

现在,考虑到 Hadoop 的工作原理,让我们来看看 Spark。

5.1.2. Spark:用更好的性能替换 MapReduce

数据科学家经常进行交互式分析,并依赖于本质上迭代的算法;算法收敛到解决方案可能需要一段时间。由于这是 MapReduce 框架的弱点,我们将介绍 Spark 框架来克服它。Spark 通过一个数量级提高了此类任务的性能。

什么是 Spark?

Spark 是一个类似于 MapReduce 的集群计算框架。然而,Spark 并不处理(分布式)文件系统上的文件存储,也不处理资源管理。为此,它依赖于像 Hadoop 文件系统、YARN 或 Apache Mesos 这样的系统。因此,Hadoop 和 Spark 是互补的系统。对于测试和开发,你甚至可以在本地系统上运行 Spark。

Spark 如何解决 MapReduce 的问题?

为了清晰起见,我们对事情进行了一些简化,但 Spark 在你的集群计算机之间创建了一种共享的 RAM 内存。这使得不同的工作者可以共享变量(及其状态),从而消除了将中间结果写入磁盘的需要。更技术性地,如果你感兴趣的话:Spark 使用弹性分布式数据集(RDD),这是一种分布式内存抽象,允许程序员以容错的方式在大型集群上执行内存计算.^([1]) 因为它是一个内存系统,所以避免了昂贵的磁盘操作。

¹

www.cs.berkeley.edu/~matei/papers/2012/nsdi_spark.pdf

Spark 生态系统中的不同组件

Spark 核心提供了一个非常适合交互式、探索性分析的 NoSQL 环境。Spark 可以以批处理和交互式模式运行,并支持 Python。

Spark 有四个其他大型组件,如下所示,并在 图 5.5 中展示。

图 5.5. 使用 Hadoop 框架结合 Spark 框架时的 Spark 框架

图片

1. Spark streaming 是一个用于实时分析的工具。

2. Spark SQL 提供了与 Spark 一起工作的 SQL 接口。

3. MLLib 是 Spark 框架内机器学习的工具。

4. GraphX 是 Spark 的图数据库。我们将在 第七章 中更深入地探讨图数据库。

现在让我们用 Hadoop、Hive 和 Spark 来浅尝贷款数据。

5.2. 案例研究:在贷款时评估风险

在对 Hadoop 和 Spark 有基本了解的基础上,我们现在可以开始接触大数据了。本案例研究的目的是让我们对在本章中较早介绍的技术有一个初步的体验,并看到在很大一部分情况下,你可以(但不必)像使用其他技术一样工作。注意:这里使用的数据量不是很大,因为这需要收集它需要大量的带宽,并且需要多个节点来跟随示例。

我们将使用

  • Horton Sandbox 在虚拟机上。如果你还没有将其下载并导入到虚拟机软件(如 VirtualBox)中,请回到 第 1.5 节,那里有相关说明。在编写本章时使用了 Horton Sandbox 的 2.3.2 版本。

  • Python 库:Pandas 和 pywebhdsf。这次你不需要在本地虚拟环境中安装它们;我们需要它们直接在 Horton Sandbox 上。因此,我们需要启动 Horton Sandbox(例如在 VirtualBox 上)并进行一些准备。

在 Sandbox 命令行中,还有一些事情你需要做才能使这一切都工作,所以连接到命令行。你可以使用像 PuTTY 这样的程序来完成这个任务。如果你不熟悉 PuTTY,它提供了一个到服务器的命令行界面,并且可以免费从 www.chiark.greenend.org.uk/~sgtatham/putty/download.html 下载。

PuTTY 登录配置如图 5.6 所示。

图 5.6. 使用 PuTTY 连接到 Horton Sandbox

图片

默认用户名和密码(在编写本书时)分别是“root”和“hadoop”。不过,你需要在第一次登录时更改此密码。

连接成功后,执行以下命令:

  • yum -y install python-pip — 这将安装 pip,一个 Python 软件包管理器。

  • pip install git+https://github.com/DavyCielen/pywebhdfs.git –upgrade —在撰写本文时,pywebhdfs 库存在问题,我们在这次分支中修复了它。希望你在阅读本文时不再需要它;问题已经报告,应该由该包的维护者解决。

  • pip install pandas —安装 Pandas。这通常需要一段时间,因为存在依赖关系。

可供你打开的.ipynb 文件可以在 Jupyter 或(较老的)Ipython 中使用,并跟随本章中的代码。Horton Sandbox 的设置说明在那里重复;请确保直接在 Horton Sandbox 上运行代码。现在,准备工作已经完成,让我们看看我们需要做什么。

在这个练习中,我们将通过更多数据科学流程步骤:

步骤 1:研究目标。这包括两个部分:

  • 为我们的经理提供仪表板

  • 为其他人准备数据以创建他们自己的仪表板

步骤 2:数据检索

  • 从 Lending Club 网站下载数据

  • 将数据放在 Horton Sandbox 的 Hadoop 文件系统中

步骤 3:数据准备

  • 使用 Spark 转换这些数据

  • 将准备好的数据存储在 Hive 中

步骤 4 & 6:探索和报告创建

  • 使用 Qlik Sense 可视化数据

在本案例研究中,我们没有建立模型,但如果你想要的话,你将拥有自己建立模型的基础设施。例如,你可以使用 SPARK 机器学习来尝试预测某人何时会违约。

是时候认识 Lending Club 了。

5.2.1. 步骤 1:研究目标

Lending Club 是一个将需要贷款的人与有资金投资的人连接起来的组织。你的老板也有资金可以投资,在投入大量资金之前,他需要信息。为此,你将为他创建一份报告,让他了解向某个人贷款的平均评级、风险和回报。通过这个过程,你使数据在仪表板工具中变得可访问,从而使得其他人也能探索这些数据。从某种意义上说,这是本案例的次要目标:开放数据以实现自助式商业智能。自助式商业智能通常应用于没有分析师可用的数据驱动型组织。组织中的任何人都能够自己进行简单的切片和切块,而将更复杂的分析留给数据科学家。

我们可以进行这个案例研究,因为 Lending Club 提供了现有贷款的匿名数据。在本案例研究结束时,你将创建一个类似于图 5.7 的报告。

图 5.7. 本练习的最终结果是用于比较贷款机会的解释性仪表板。

首先,让我们获取数据。

5.2.2. 步骤 2:数据检索

是时候与 Hadoop 文件系统(或 hdfs)一起工作了。首先,我们将通过命令行发送命令,然后通过 Python 脚本语言(借助 pywebhdfs 包)发送命令。

Hadoop 文件系统类似于一个正常的文件系统,除了文件和文件夹存储在多个服务器上,你不知道每个文件的物理地址。如果你使用过 Dropbox 或 Google Drive 等工具,这并不陌生。你放在这些驱动器上的文件存储在服务器上的某个地方,但你不知道确切在哪个服务器上。就像在正常文件系统中一样,你可以创建、重命名和删除文件和文件夹。

使用命令行与 Hadoop 文件系统交互

让我们首先使用命令行检索 Hadoop 根目录下当前存在的目录和文件列表。在 PuTTY 中输入命令 hadoop fs –ls / 来实现这一点。

确保在尝试连接之前启动你的 Hortonworks Sandbox 虚拟机。在 PuTTY 中,你应该连接到 127.0.0.1:2222,如之前在图 5.6 中所示。

Hadoop 命令的输出显示在图 5.8 中。你还可以添加如 hadoop fs –ls –R / 这样的参数来获取所有文件和子目录的递归列表。

图 5.8. Hadoop 列表命令的输出:hadoop fs –ls /。Hadoop 根目录被列出。

现在,我们将在 hdfs 上创建一个新的目录“chapter5”,以便在本章中使用。以下命令将创建新目录并允许每个人访问文件夹:

sudo -u hdfs hadoop fs -mkdir /chapter5
sudo -u hdfs hadoop fs –chmod 777 /chapter5

你可能已经注意到了这里的模式。Hadoop 命令与我们的本地文件系统命令(POSIX 风格)非常相似,但以 Hadoop fs 开头,每个命令前都有一个破折号 -。表 5.1 提供了 Hadoop 上流行的文件系统命令及其本地文件系统命令对应关系的概述。

表 5.1. 常见 Hadoop 文件系统命令列表
目标 Hadoop 文件系统命令 本地文件系统命令
从目录中获取文件和目录列表 hadoop fs –ls URI ls URI
创建目录 hadoop fs –mkdir URI mkdir URI
删除目录 hadoop fs –rm –r URI rm –r URI
修改文件权限 hadoop fs –chmod MODE URI chmod MODE URI
移动或重命名文件 hadoop fs –mv OLDURI NEWURI mv OLDURI NEWURI

你会经常使用两个特殊命令。这些是

  • 从本地文件系统上传文件到分布式文件系统(hadoop fs –put LOCALURI REMOTEURI)。

  • 从分布式文件系统下载文件到本地文件系统(hadoop –get REMOTEURI)。

让我们用一个例子来澄清这一点。假设你在 Linux 虚拟机上有一个 .CSV 文件,你通过这个虚拟机连接到 Linux Hadoop 集群。你想要将 .CSV 文件从你的 Linux 虚拟机复制到集群的 hdfs。使用命令 hadoop –put mycsv.csv /data

使用 PuTTY,我们可以在 Horton Sandbox 上启动一个 Python 会话,使用 Python 脚本检索我们的数据。在命令行中输入“pyspark”命令以启动会话。如果一切顺利,你应该会看到如图 5.9 所示的欢迎屏幕。

图 5.9. Spark 与 Python 交互使用的欢迎屏幕

现在我们使用 Python 代码来为我们获取数据,如下所示。

列表 5.1. 绘制 Lending Club 贷款数据

我们从 Lending Club 的网站 resources.lendingclub.com/LoanStats3d.csv.zip 下载了文件“LoanStats3d.csv.zip”并解压它。我们使用 requests、zipfile 和 stringio Python 包中的方法分别下载数据、创建虚拟文件和解压它。这只是一个单个文件;如果你想要所有数据,你可以创建一个循环,但为了演示目的,这样就可以了。正如我们之前提到的,这个案例研究的一个重要部分将是使用大数据技术进行数据准备。然而,在我们这样做之前,我们需要将其放在 Hadoop 文件系统中。PyWebHdfs 是一个包,允许你从 Python 与 Hadoop 文件系统交互。它将你的命令翻译并传递给 webhdfs 接口的 rest 调用。这很有用,因为你可以使用你喜欢的脚本语言来自动化任务,如下所示。

列表 5.2. 在 Hadoop 上存储数据

我们已经在 列表 5.1 中下载并解压了文件;现在在 列表 5.2 中,我们使用 Pandas 对数据进行子选择并存储到本地。然后我们在 Hadoop 上创建了一个目录,并将本地文件传输到 Hadoop。下载的数据是 .CSV 格式,因为它相对较小,我们可以使用 Pandas 库从文件中删除第一行和最后两行。这些行包含注释,在 Hadoop 环境中处理此文件会变得繁琐。我们的代码的第一行导入了 Pandas 包,第二行将文件解析到内存中并删除第一行和最后两行数据。第三行代码将数据保存到本地文件系统以供以后使用和检查。

在继续之前,我们可以使用以下代码行检查我们的文件:

print hdfs.get_file_dir_status('chapter5/LoanStats3d.csv')

PySpark 控制台应该会告诉我们我们的文件在 Hadoop 系统上是安全且良好的,如图 5.10 所示。

图 5.10. 通过 PySpark 控制台在 Hadoop 上检索文件状态

文件已经准备好并等待我们在 Hadoop 上进行数据准备,因为数据还不够干净,不能直接存储到 Hive。

5.2.3. 第 3 步:数据准备

现在我们已经下载了用于分析的数据,在将其存储到 Hive 之前,我们将使用 Spark 对数据进行清理。

Spark 中的数据准备

清洗数据通常是一个交互式练习,因为你发现了问题并修复了问题,你可能会在得到干净清晰的数据之前这样做几次。一个脏数据的例子可能是一个像“UsA”这样的字符串,它被错误地大写。在这个时候,我们不再在 jobs.py 中工作,而是使用 PySpark 命令行界面直接与 Spark 交互。

Spark 非常适合这种交互式分析,因为它不需要在每一步后保存数据,并且比 Hadoop 在服务器之间共享数据(一种分布式内存)有更好的模型。

转换包括四个部分:

1. 启动 PySpark(应该仍然在第 5.2.2 节中打开)并加载 Spark 和 Hive 上下文。

2. 读取并解析 .CSV 文件。

3. 从数据中分割标题行。

4. 清洗数据。

好的,让我们进入正题。下面的列表显示了 PySpark 控制台中代码的实现。

列表 5.3. 连接到 Apache Spark

让我们进一步深入了解每个步骤的细节。

第 1 步:以交互模式启动 Spark 并加载上下文

在 PySpark 控制台中不需要导入 Spark 上下文,因为上下文作为变量 sc 可用。你可能已经注意到,在打开 PySpark 时也提到了这一点;如果你错过了,请查看图 5.9。然后我们加载一个 Hive 上下文,以便我们可以交互式地使用 Hive。如果你交互式地使用 Spark,Spark 和 Hive 上下文会自动加载,但如果你想以批处理模式使用它,你需要手动加载。要在批处理中提交代码,请在 Horton Sandbox 命令行上使用 spark-submit filename.py 命令。

from pyspark import SparkContext
from pyspark.sql import HiveContext
sc = SparkContext()
sqlContext = HiveContext(sc)

环境设置完毕后,我们准备开始解析 .CSV 文件。

第 2 步:读取和解析 .CSV 文件

接下来,我们从 Hadoop 文件系统中读取文件,并在遇到的每个逗号处分割它。在我们的代码中,第一行从 Hadoop 文件系统中读取 .CSV 文件。第二行在遇到逗号时分割每一行。我们的 .CSV 解析器设计上是天真的,因为我们正在学习 Spark,但你也可以使用 .CSV 包来帮助你更正确地解析一行。

data = sc.textFile("/chapter5/LoanStats3d.csv")
parts = data.map(lambda r:r.split(','))

注意这与函数式编程方法是多么相似。对于那些从未遇到过它的人来说,你可以天真地读 lambda r:r.split(',') 为“对于每个输入 r(在这种情况下是一行),当它遇到逗号时分割这个输入 r。”就像在这种情况下,“对于每个输入”意味着“对于每一行”,但你也可以把它读作“通过逗号分割每一行。”这种类似函数的语法是 Spark 我最喜欢的特性之一。

第 3 步:从数据中分割标题行

为了将标题与数据分开,我们读取第一行并保留与标题行不相似的每一行:

firstline = parts.first()
datalines = parts.filter(lambda x:x != firstline)

遵循大数据的最佳实践,我们不需要执行此步骤,因为第一行已经存储在单独的文件中。实际上,.CSV 文件通常包含标题行,你需要在开始清理数据之前执行类似的操作。

第 4 步:清理数据

在这一步中,我们执行基本的清理以增强数据质量。这使我们能够构建更好的报告。

在第二步之后,我们的数据由数组组成。现在我们将每个 lambda 函数的输入视为一个数组,并返回一个数组。为了简化这项任务,我们构建了一个辅助函数来清理。我们的清理包括将输入“10,4%”重新格式化为 0.104,将每个字符串编码为 utf-8,以及将下划线替换为空格并将所有字符串转换为小写。第二行代码为数组中的每一行调用我们的辅助函数。

def cleans(row):
        row [7] = str(float(row [7][:-1])/100)
        return [s.encode('utf8').replace(r"_"," ").lower() for s in row]
datalines = datalines.map(lambda x: cleans(x))

我们的数据现在已准备好用于报告,因此我们需要使其对我们的报告工具可用。Hive 非常适合此目的,因为许多报告工具都可以连接到它。让我们看看如何完成这项任务。

在 Hive 中保存数据

要在 Hive 中存储数据,我们需要完成两个步骤:

1. 创建并注册元数据。

2. 执行 SQL 语句以在 Hive 中保存数据。

在本节中,我们将再次在我们的挚爱 PySpark shell 中执行下一块代码,如下所示。

列表 5.4. 在 Hive 中存储数据(完整)

让我们深入每个步骤以获得更多的澄清。

第 1 步:创建并注册元数据

许多人更喜欢在处理数据时使用 SQL。使用 Spark 也是可能的。你甚至可以直接读取和存储数据在 Hive 中,就像我们将会做的那样。然而,在这样做之前,你需要创建包含每个列的列名和列类型的元数据。

第一行代码是导入。第二行解析字段名和字段类型,并指定字段是否为必需的。StructType 将行表示为一个结构字段的数组。然后你将其放置在一个注册为(临时)表的 dataframe 中。

from pyspark.sql.types import *
fields = [StructField(field_name,StringType(),True) for field_name in firstline]
schema = StructType(fields)
schemaLoans = sqlContext.createDataFrame(datalines, schema)
schemaLoans.registerTempTable("loans")

元数据准备就绪后,我们现在能够将数据插入到 Hive 中。

第 2 步:执行查询并将表存储在 Hive 中

现在我们已经准备好在数据上使用 SQL 语法。首先,我们将创建一个汇总表,统计每个目的的贷款数量。然后,我们将清洗后的原始数据的一个子集存储在 Hive 中,以便在 Qlik 中进行可视化。

执行类似 SQL 的命令就像传递一个包含 SQL 命令的字符串到 sqlContext.sql 函数一样简单。请注意,我们不是在编写纯 SQL,因为我们正在直接与 Hive 通信。Hive 有自己的 SQL 语法,称为 HiveQL。在我们的 SQL 中,例如,我们立即告诉它将数据存储为 Parquet 文件。Parquet 是一种流行的大数据文件格式。

sqlContext.sql("drop table if exists LoansByTitle")
sql = '''create table LoansByTitle stored as parquet as select title,
     count(1) as number from loans group by title order by number desc'''
sqlContext.sql(sql)

sqlContext.sql('drop table if exists raw')
sql = '''create table raw stored as parquet as select title,
     emp_title,grade,home_ownership,int_rate,recoveries,collection_recovery_f
     ee,loan_amnt,term from loans'''
sqlContext.sql(sql)

数据存储在 Hive 中后,我们可以将我们的可视化工具连接到它。

5.2.4. 第 4 步:数据探索 & 第 6 步:报告构建

我们将使用 Qlik Sense 构建一个交互式报告,以展示给我们的经理。在订阅他们的网站后,可以从www.qlik.com/try-or-buy/download-qlik-sense下载 Qlik Sense。下载开始时,您将被重定向到一个包含有关如何安装和使用 Qlik Sense 的多个信息视频的页面。建议您先观看这些视频。

我们使用 Hive ODBC 连接器从 Hive 读取数据并将其提供给 Qlik。有关在 Qlik 中安装 ODBC 连接器的教程可用。对于主要操作系统,这可以在hortonworks.com/hdp/addons/找到。

注意

在 Windows 上,这可能不会立即工作。一旦安装了 ODBC,请确保检查您的 Windows ODBC 管理器(按 CTRL+F 并查找 ODBC)。在管理器中,转到“系统-DSN”并选择“Sample Hive Hortonworks DSN”。确保您的设置正确(如图图 5.11 所示),否则 Qlik 将无法连接到 Hortonworks Sandbox。

图 5.11. Windows Hortonworks ODBC 配置

图片

希望您没有忘记 Sandbox 密码;如图 5.11 所示,您还需要它。

现在打开 Qlik Sense。如果在 Windows 上安装,您应该在桌面上获得将.exe 快捷方式放置在桌面上的选项。Qlik 不是免费软件;它是一个商业产品,为单个客户提供诱饵版本,但就目前而言,它足够了。在最后一章中,我们将使用免费的 JavaScript 库创建仪表板。

Qlik 可以直接将数据加载到内存中,或者每次都调用 Hive。我们选择了第一种方法,因为它更快。

这部分有三个步骤:

1. 使用 ODBC 连接在 Qlik 中加载数据。

2. 创建报告。

3. 探索数据。

让我们从第一步开始,将数据加载到 Qlik 中。

步骤 1:在 Qlik 中加载数据

当您启动 Qlik Sense 时,它将显示一个包含现有报告(称为应用)的欢迎屏幕,如图图 5.12 所示。

图 5.12. Qlik Sense 欢迎屏幕

图片

要启动一个新的应用,请点击屏幕右侧的“创建新应用”按钮,如图图 5.13 所示。这会打开一个新的对话框。将我们的应用的新名称输入为“第五章”。

图 5.13. 创建新应用的消息框

图片

如果应用创建成功,将出现一个确认框(图 5.14)。

图 5.14. 一个框确认应用已成功创建。

图片

点击“打开应用”按钮,将弹出一个新屏幕,提示您向应用添加数据(图 5.15)。

图 5.15. 当您打开新应用时,会弹出添加数据屏幕。

图片

点击“添加数据”按钮,选择 ODBC 作为数据源(图 5.16)。

图 5.16. 在选择数据源屏幕中选择 ODBC 作为数据源

图片

在下一屏(图 5.17)中选择用户 DSN,Hortonworks,并将根目录指定为用户名,密码为 hadoop(或您首次登录 Sandbox 时提供的密码)。

图 5.17. 在用户 DSN 中选择 Hortonworks 并指定用户名和密码。

图片

备注

Hortonworks 选项默认不显示。您需要安装 HDP 2.3 ODBC 连接器才能使此选项显示(如前所述)。如果您在此阶段尚未成功安装它,可以在blogs.perficient.com/multi-shoring/blog/2015/09/29/how-to-connect-hortonworks-hive-from-qlikview-with-odbc-driver/找到有关此的详细说明。

点击指向右侧的箭头以进入下一屏。

在下一屏(图 5.18)中选择 Hive 数据,默认为用户。选择“原始”作为表以选择,并选择所有列进行导入;然后点击“加载并完成”按钮以完成此步骤。

图 5.18. Hive 界面原始数据列概述

图片

此步骤之后,将花费几秒钟时间在 Qlik 中加载数据(图 5.19)。

图 5.19. 数据已加载到 Qlik 的确认

图片

第 2 步:创建报告

选择“编辑工作表”以开始构建报告。这将添加报告编辑器(图 5.20)。

图 5.20. 报告编辑器屏幕

图片

子步骤 1:向报告中添加选择过滤器 我们将首先向报告中添加一个选择框,以显示每个人为什么想要贷款。为此,将左侧资产面板中的标题度量拖放到报告面板,并给它一个舒适的大小和位置(图 5.21)。点击字段表,以便您可以拖放字段。

图 5.21. 将标题从左侧字段窗格拖动到报告窗格。

图片

子步骤 2:向报告中添加 KPI KPI 图表显示了所选总人口的聚合数值。图表中显示了平均利率和客户总数等数值(图 5.22)。

图 5.22. KPI 图表的示例

图片

向报告中添加 KPI 需要四个步骤,如下所示,并在图 5.23 中展示。

图 5.23. 向 Qlik 报告中添加 KPI 图表的四个步骤

图片

1.  选择一个图表—选择 KPI 作为图表,并将其放置在报告屏幕上;根据您的喜好调整大小和位置。

2.  添加一个度量—点击图表内的添加度量按钮,并选择 int_rate。

3.  选择一个聚合方法—Avg(int_rate).

4.  格式化图表—在右侧面板中,填写平均利率作为标签。

总共我们将向报告中添加四个关键绩效指标图表,因此您需要为以下关键绩效指标重复这些步骤:

  • 平均利率

  • 总贷款金额

  • 平均贷款金额

  • 总回收金额

子步骤 3:将柱状图添加到报告中 接下来,我们将向报告中添加四个柱状图。这些图表将展示每个风险等级的不同数值。一个柱状图将解释每个风险组的平均利率,另一个将显示每个风险组的总贷款金额(图 5.24)。

图 5.24. 柱状图的示例

将柱状图添加到报告中需要五个步骤,如下所示,并在图 5.25 中展示。

图 5.25. 添加柱状图需要五个步骤。

1.  选择图表—选择柱状图作为图表,并将其放置在报告屏幕上;根据您的喜好调整大小和位置。

2.  添加度量值—点击图表内的“添加度量值”按钮,并选择 int_rate。

3.  选择聚合方法—Avg(int_rate)。

4.  添加维度—点击“添加维度”,并选择 grade 作为维度。

5.  格式化图表—在右侧面板中,填写平均利率作为标签。

重复此程序,以下维度和度量值组合:

  • 每个等级的平均利率

  • 每个等级的平均贷款金额

  • 每个等级的总贷款金额

  • 每个等级的总回收金额

子步骤 4:将交叉表添加到报告中 假设您想知道风险组 C 的董事支付的平均利率。在这种情况下,您需要为两个维度(职位和风险等级)的组合获取一个度量值(利率)。这可以通过如图 5.26 所示的交叉表实现。

图 5.26. 交叉表的示例,显示了按职位/风险等级组合支付的平均利率

将交叉表添加到报告中需要六个步骤,如下所示,并在图 5.27 中展示。

图 5.27. 将交叉表添加到报告中需要六个步骤。

1.  选择图表—选择交叉表作为图表,并将其放置在报告屏幕上;根据您的喜好调整大小和位置。

2.  添加度量值—点击图表内的“添加度量值”按钮,并选择 int_rate。

3.  选择聚合方法—Avg(int_rate)。

4.  添加行维度—点击“添加维度”,并选择 emp_title 作为维度。

5.  添加列维度—点击“添加数据”,选择列,并选择 grade。

6.  格式化图表—在右侧面板中,填写平均利率作为标签。

调整大小和重新定位后,您应该得到一个类似于图 5.28 的结果。点击左侧的“完成”按钮,您就可以开始探索数据了。

图 5.28. 编辑模式下的最终结果

步骤 3:探索数据

结果是一个根据你选择的选项自动更新的交互式图表。你为什么不尝试从导演那里寻找信息,并将它们与艺术家进行比较呢?为了实现这一点,点击交叉表中的 emp_title 并在搜索字段中输入导演。结果看起来像图 5.29。以同样的方式,我们可以查看艺术家,如图图 5.30 所示。另一个有趣的见解来自于比较用于购房目的的评级与债务重组目的的评级。

图 5.29. 当我们选择导演时,我们可以看到他们为贷款支付的平均利率为 11.97%。

图 5.30. 当我们选择艺术家时,我们看到他们为贷款支付的平均利率为 13.32%。

我们终于做到了:我们创建了经理渴望的报告,在这个过程中,我们也为其他人使用这些数据创建自己的报告打开了大门。你可以思考的一个有趣的下一步是使用这个设置来找到那些可能违约债务的人。为此,你可以使用由在线算法驱动的 Spark 机器学习功能,如第四章中演示的那样章节 4。

在本章中,我们亲身体验了 Hadoop 和 Spark 框架。我们覆盖了很多内容,但说实话,Python 使得与大数据技术打交道变得极其简单。在下一章中,我们将更深入地探讨 NoSQL 数据库的世界,并接触到更多的大数据技术。

5.3. 摘要

在本章中,你了解到

  • Hadoop 是一个框架,它使你能够存储文件并在多台计算机之间分配计算。

  • Hadoop 为你隐藏了与计算机集群一起工作的所有复杂性。

  • Hadoop 和 Spark 周围围绕着一系列的应用生态系统,从数据库到访问控制。

  • Spark 向 Hadoop 框架中添加了一个更适合数据科学工作的共享内存结构。

  • 在本章案例研究中,我们使用了 PySpark(一个 Python 库)从 Python 与 Hive 和 Spark 通信。我们使用了 pywebhdfs Python 库来与 Hadoop 库一起工作,但你也可以使用操作系统命令行来完成这项工作。

  • 将 BI 工具如 Qlik 连接到 Hadoop 非常容易。

第六章. 加入 NoSQL 运动

本章涵盖

  • 理解 NoSQL 数据库及其为何在今天被使用

  • 识别 NoSQL 数据库和关系型数据库之间的差异

  • 定义 ACID 原则及其与 NoSQL BASE 原则的关系

  • 学习 CAP 定理对于多节点数据库设置的重要性

  • 将数据科学流程应用于使用 NoSQL 数据库 Elasticsearch 的项目

本章分为两部分:理论开始和实际结束。

  • 在本章的第一部分,我们将探讨 NoSQL 数据库的一般情况,并回答以下问题:为什么它们存在?为什么直到最近才出现?有哪些类型,为什么你应该关心?

  • 在第二部分,我们将解决一个现实生活中的问题——疾病诊断和画像——使用免费数据、Python 和 NoSQL 数据库。

毫无疑问,你已经听说过 NoSQL 数据库以及它们被许多高科技公司虔诚地使用。但 NoSQL 数据库是什么,它们与您所习惯的关系型或 SQL 数据库有什么不同?NoSQL代表的是Not Only Structured Query Language,尽管 NoSQL 数据库确实可以让你用 SQL 查询它们,但你不必专注于这个名字。关于这个名字的争论已经很多,甚至有人质疑这一组新数据库是否应该有一个集体名称。相反,让我们看看它们与关系型数据库管理系统(RDBMS)相比代表了什么。传统的数据库驻留在单个计算机或服务器上。只要你的数据没有超出你的服务器,这曾经是可以的,但现在对许多公司来说已经不再是这种情况了。随着互联网的增长,像谷歌和亚马逊这样的公司感到这些单节点数据库限制了它们,并寻求替代方案。

许多公司使用单节点 NoSQL 数据库,例如 MongoDB,因为它们想要灵活的架构或能够分层聚合数据。以下是一些早期的例子:

我们将在本章后面详细讨论这四种类型。

请注意,尽管大小是一个重要因素,但这些数据库并非仅仅源于处理更大数据量的需求。大数据的每个V(体积、种类、速度和有时是真实性)都有影响。例如,图数据库可以处理网络数据。图数据库的爱好者甚至声称一切都可以被视为网络。例如,你如何准备晚餐?用食材。这些食材被组合在一起形成菜肴,可以与其他食材一起使用来形成其他菜肴。从这个角度来看,食材和食谱是网络的一部分。但食谱和食材也可以存储在你的关系数据库或文档存储中;这完全取决于你如何看待问题。这正是 NoSQL 的强大之处:能够从不同的角度看待问题,将数据结构塑造成使用场景。作为数据科学家,你的工作是找到任何问题的最佳答案。尽管有时这仍然更容易通过 RDBMS 来实现,但通常特定的 NoSQL 数据库提供了一种更好的方法。

由于需要分区,关系数据库在公司的大数据中注定要消失吗?不,NewSQL 平台(不要与 NoSQL 混淆)是 RDBMS 对集群设置需求的回答。NewSQL 数据库遵循关系模型,但能够像 NoSQL 数据库一样被划分为分布式集群。这并不是关系数据库的终结,当然也不是 SQL 的终结,因为像 Hive 这样的平台将 SQL 转换为 Hadoop 的 MapReduce 作业。此外,并非每个公司都需要大数据;许多公司用小型数据库就能做得很好,传统的数据库关系数据库非常适合这种情况。

如果你查看图 6.1 中显示的大数据思维导图,你会看到四种类型的 NoSQL 数据库。

图 6.1. NoSQL 和 NewSQL 数据库

这四种类型是文档存储、键值存储、图数据库和列数据库。思维导图还包括 NewSQL 分区关系数据库。在未来,这种 NoSQL 和 NewSQL 之间的巨大分歧将变得过时,因为每种数据库类型都将有其自己的重点,同时结合 NoSQL 和 NewSQL 数据库的元素。随着 RDBMS 类型获得 NoSQL 功能,如列数据库中看到的列式索引,这些界限正在逐渐模糊。但就目前而言,这是一个很好的方式来展示旧的关系数据库已经超越了单节点设置,而其他数据库类型正在 NoSQL 的范畴下出现。

让我们看看 NoSQL 带来了什么。

6.1. NoSQL 简介

如你所读,NoSQL 数据库的目标不仅仅是提供一种在多个节点上成功分区数据库的方法,而且还提供了一种根本不同的方式来建模手头的数据,以适应其结构并符合其使用场景,而不是如何按照关系数据库的要求来建模。

为了帮助您理解 NoSQL,我们将首先查看单服务器关系型数据库的核心 ACID 原则,并展示 NoSQL 数据库如何将它们重写为 BASE 原则,以便在分布式环境中工作得更好。我们还将探讨 CAP 定理,它描述了在多个节点上分布数据库的主要问题以及 ACID 和 BASE 数据库如何处理这个问题。

6.1.1. ACID:关系型数据库的核心原则

传统关系型数据库的主要方面可以通过 ACID 概念来总结:

  • 原子性 —“全有或全无”原则。如果一条记录被放入数据库,它要么完全放入,要么根本不放入。例如,如果在数据库写入操作中途发生断电,您不会得到半条记录;它根本不会在那里。

  • 一致性 —这个重要的原则维护了数据的完整性。任何进入数据库的条目都不会与预定义的规则发生冲突,例如缺少必需的字段或字段为数值而非文本。

  • 隔离性 —当数据库中的某个东西发生变化时,在完全相同的时刻,对此数据的任何操作都无法发生。相反,这些操作会与其他更改串行执行。隔离性是一个从低隔离到高隔离的尺度。在这个尺度上,传统数据库位于“高隔离”的一端。低隔离的一个例子是 Google Docs:多个人可以同时向文档中写入,并立即看到彼此的更改。在光谱的另一端,传统的 Word 文档具有高隔离性;它被第一个打开文档的用户锁定以供编辑。第二个打开文档的人可以查看其最后保存的版本,但无法查看未保存的更改或编辑文档,除非首先将其保存为副本。因此,一旦有人打开它,最新版本就完全隔离于除编辑该文档的人之外的所有人。

  • 持久性 —如果数据已进入数据库,它应该永久存活。硬盘物理损坏会破坏记录,但断电和软件崩溃不应该。

ACID 适用于所有关系型数据库以及某些 NoSQL 数据库,例如图数据库 Neo4j。我们将在本章和第七章 chapter 7 中进一步讨论图数据库。对于大多数其他 NoSQL 数据库,另一个原则适用:BASE。为了理解 BASE 以及为什么它适用于大多数 NoSQL 数据库,我们需要看看 CAP 定理。

6.1.2. CAP 定理:多节点数据库的问题

一旦数据库分散到不同的服务器上,由于 ACID 承诺的一致性,就很难遵循 ACID 原则。CAP 定理指出为什么这会变成一个问题。CAP 定理指出,数据库可以是以下三个属性中的任意两个,但永远不能是三个:

  • 分区容错 —数据库可以处理网络分区或网络故障。

  • 可用性 —只要您连接到的节点处于运行状态并且您可以连接到它,节点就会响应,即使不同数据库节点之间的连接丢失。

  • 一致性 —无论您连接到哪个节点,您都会看到完全相同的数据。

对于单节点数据库,很容易看出它总是可用且一致的:

  • 可用性 —只要节点处于运行状态,它就是可用的。这就是 CAP 可用性所承诺的一切。

  • 一致性 —没有第二个节点,所以不可能出现不一致的情况。

当数据库分区时,事情变得有趣。然后您需要在可用性和一致性之间做出选择,如图 6.2 所示。

图 6.2. CAP 定理:在分区数据库时,您需要在可用性和一致性之间做出选择。

让我们以一个在欧洲有一个服务器在美国有一个服务器,并且有一个单一配送中心的在线商店为例。一个名叫 Fritz 的德国人和一个名叫 Freddy 的美国人同时在同一个在线商店购物。他们看到一件商品,只剩下一个库存:一个青铜色的八爪鱼形状的咖啡桌。灾难发生了,两个本地服务器之间的通信暂时中断。如果您是商店的所有者,您有两个选择:

  • 可用性 —您允许服务器继续为顾客提供服务,之后您再解决所有问题。

  • 一致性 —您将所有销售挂起,直到重新建立通信。

在第一种情况下,Fritz 和 Freddy 都会购买八爪鱼咖啡桌,因为两个节点最后已知的库存数量都是“一个”,并且两个节点都被允许出售它,如图 6.3 所示。

图 6.3. CAP 定理:如果节点断开连接,您可以选择保持可用性,但数据可能会变得不一致。

如果咖啡桌很难找到,您必须通知 Fritz 或 Freddy,他不会在承诺的交货日期收到他的桌子,或者更糟糕的是,他永远也收不到。作为一个优秀的商人,您可能会用一张折扣券补偿其中一个人,以便以后购买,这样一切可能都会好起来。

第二种选择(图 6.4)涉及暂时挂起传入的请求。

图 6.4. CAP 定理:如果节点断开连接,您可以选择在恢复连接之前停止对数据库的访问以保持一致性

这种做法可能对弗里茨和弗雷迪都公平,如果五分钟后网店重新开始营业,但你可能会失去这两笔销售,甚至更多。网店往往倾向于选择可用性而不是一致性,但这并不是所有情况下最佳的选择。以 Tomorrowland 这样的流行节日为例。出于安全考虑,节日通常会有一个最大允许容量。如果你因为服务器在节点通信故障期间继续售票而卖出了超出允许数量的门票,到通信恢复时,你可能已经卖出了允许数量的两倍。在这种情况下,选择一致性并暂时关闭节点可能更明智。像 Tomorrowland 这样的节日,门票通常在前几个小时就售罄了,所以短暂的停机不会像撤回数千张入场券那样造成太大的伤害。

6.1.3. NoSQL 数据库的 BASE 原则

关系型数据库管理系统遵循 ACID 原则;不遵循 ACID 的 NoSQL 数据库,如文档存储和键值存储,遵循 BASE。BASE 是一组较为宽松的数据库承诺:

  • 基本可用性 —在 CAP 意义上保证了可用性。以网店为例,如果一个节点处于运行状态,你可以继续购物。根据设置的不同,节点可以接管其他节点。例如,Elasticsearch 是一个 NoSQL 文档类型搜索引擎,它通过分片和复制数据,使得节点故障不一定会导致服务故障,通过分片的过程来实现。每个分片可以看作是一个独立的数据库服务器实例,但它也能够与其他分片通信,以尽可能高效地分配工作(图 6.5)。一个节点上可以存在多个分片。如果每个分片在另一个节点上都有一个副本,那么节点故障可以通过在剩余节点之间重新分配工作来轻松解决。

    图 6.5. 分片:每个分片可以作为一个自给自足的数据库运行,但它们也作为一个整体协同工作。示例表示两个节点,每个节点包含四个分片:两个主分片和两个副本。一个节点的故障可以通过另一个节点得到备份。

    图片

  • 软状态 —系统的状态可能会随时间变化。这对应于最终一致性原则:系统可能需要改变以使数据再次一致。在一个节点中,数据可能说“A”,而在另一个节点中可能说“B”,因为它们已经进行了调整。稍后,在网络恢复在线时的冲突解决过程中,第一个节点中的“A”可能被替换为“B”。即使没有人明确地将“A”改为“B”,它也会随着与其他节点的数据一致而采取这个值。

  • 最终一致性 —数据库将随着时间的推移而变得一致。在网店示例中,表格被卖出了两次,这导致了数据不一致。一旦重新建立了各个节点之间的连接,它们将进行通信并决定如何解决冲突。这种冲突可以通过例如先到先得的方式或优先考虑运输成本最低的客户来解决。数据库具有默认行为,但鉴于这里有一个实际的业务决策需要做出,这种行为可以被覆盖。即使连接是正常运行的,延迟可能会导致节点变得不一致。通常,产品会被保存在在线购物车中,但将商品放入购物车并不会锁定它供其他用户使用。如果弗里茨比弗雷迪先按下结账按钮,那么当弗雷迪去结账时会出现问题。这可以很容易地向客户解释:他来得太晚了。但如果是两个人在精确的同一毫秒按下结账按钮,并且同时发生销售呢?

ACID 与 BASE

BASE 原则是为了适应化学中的而设计的:酸是一种 pH 值低的液体。碱则相反,具有高 pH 值。我们在这里不会深入化学细节,但图 6.6 展示了对于那些熟悉酸和碱化学等效物的人来说的一个记忆法。

图 6.6. ACID 与 BASE:传统的关系型数据库与大多数 NoSQL 数据库。这些名称来源于 pH 值的化学概念。pH 值低于 7 的是酸性;高于 7 的是碱性。在这个尺度上,平均地表水的 pH 值在 6.5 到 8.5 之间波动。

图片

6.1.4. NoSQL 数据库类型

如你之前所见,有四种主要的 NoSQL 类型:键值存储、文档存储、列式数据库和图数据库。每种类型都解决了关系型数据库无法解决的问题。实际的实现通常是这些类型的组合。例如,OrientDB 是一个多模型数据库,结合了 NoSQL 类型。OrientDB 是一个图数据库,其中每个节点都是一个文档。

在深入了解不同的 NoSQL 数据库之前,让我们看看关系型数据库,这样你就可以有所比较。在数据建模中,有许多可能的方法。关系型数据库通常追求规范化:确保每一条数据只存储一次。规范化标记了它们的结构设置。例如,如果你想存储关于一个人及其爱好数据,你可以使用两个表格:一个关于人的,一个关于他们的爱好。正如你在图 6.7 中看到的,由于多对多关系,需要额外的表格来将爱好与个人联系起来:一个人可以有多个爱好,一个爱好可以有很多人在练习。

图 6.7. 关系型数据库追求规范化(确保每条数据只存储一次)。每个表都有唯一的标识符(主键),用于表示实体(表)之间的关系,因此被称为关系型。

一个完整的关系型数据库可以由许多实体和链接表组成。现在你已经有了可以与 NoSQL 比较的东西,让我们来看看不同的类型。

列式数据库

传统的关系型数据库是列式存储的,每行都有一个行 ID,行内的每个字段都存储在表中。比如说,为了举例,我们不存储关于爱好额外的数据,并且只有一个表来描述人,如图 6.8 所示。注意在这个场景中,你会有轻微的反规范化,因为爱好可能会重复。如果爱好信息是一个很好的额外信息但不是你的用例所必需的,将其作为 Hobbies 列中的列表添加是一个可接受的方法。但如果信息不足以成为一个单独的表,那么是否应该存储它呢?

图 6.8. 列式数据库布局。每个实体(人)由一行表示,分布在多个列中。
行 ID 姓名 生日 爱好
1 Jos The Boss 11-12-1985 Archery, conquering the world
2 Fritz von Braun 27-1-1978 Building things, surfing
3 Freddy Stark Swordplay, lollygagging, archery
4 Delphine Thewiseone 16-9-1986

每次你在列式数据库中查找某项内容时,都会扫描每一行,无论你需要哪些列。比如说,你只想获取 9 月份的生日列表。数据库会从上到下、从左到右扫描整个表,如图 6.9 所示,最终返回生日列表。

图 6.9. 列式查找:从上到下,对每个条目,所有列都被加载到内存中

在某些列上索引数据可以显著提高查找速度,但为每个列索引都会带来额外的开销,数据库仍然会扫描所有列。

列数据库将每个列分别存储,当只涉及少量列时,可以更快地进行扫描;参见图 6.10。

图 6.10. 列式数据库将每个列分别存储,并附带相关的行号。每个实体(人)分布在多个表中。

这种布局看起来与每个列都有索引的行导向数据库非常相似。数据库索引是一种数据结构,它允许以存储空间和额外写入(索引更新)为代价快速查找数据。索引将行号映射到数据,而列数据库将数据映射到行号;这样计数就变得更快,例如,很容易看出有多少人喜欢射箭。将列分别存储还允许进行优化的压缩,因为每个表中只有一个数据类型。

你应该在什么时候使用行导向数据库,什么时候使用列导向数据库?在列导向数据库中,很容易添加另一列,因为现有的任何列都不会受到影响。但添加整个记录需要调整所有表。这使得行导向数据库在在线事务处理(OLTP)方面优于列导向数据库,因为这意味着不断添加或更改记录。当执行分析和报告时,列导向数据库表现出色:求和值和计数条目。行导向数据库通常是实际交易(如销售)的操作数据库的选择。夜间批量作业使列导向数据库保持最新,支持使用 MapReduce 算法进行快速查找和聚合,以生成报告。列族存储的例子包括 Apache HBase、Facebook 的 Cassandra、Hypertable 以及宽列存储的鼻祖,Google BigTable。

键值存储

键值存储是 NoSQL 数据库中最简单的。正如其名所示,它们是一系列键值对,如图 6.11 所示,这种简单性使它们成为 NoSQL 数据库类型中最可扩展的,能够存储大量数据。

图 6.11. 键值存储将所有内容都存储为键和值。

图片

键值存储中的值可以是任何东西:一个字符串、一个数字,也可以是一个封装在对象中的整个新的键值对集合。图 6.12 显示了一个稍微复杂一些的键值结构。键值存储的例子有 Redis、Voldemort、Riak 和亚马逊的 Dynamo。

图 6.12. 键值嵌套结构

图片

文档存储

文档存储在键值存储之上增加了一步复杂性:文档存储确实假设了一种可以由模式指定的文档结构。文档存储在 NoSQL 数据库类型中看起来最为自然,因为它们被设计为以原始形式存储日常文档,并允许对这种通常已经聚合的数据形式进行复杂的查询和计算。关系数据库中存储数据的方式从规范化的角度来看是有意义的:一切都应该只存储一次,并通过外键连接。只要数据结构合理,文档存储对规范化的关注就很少。关系数据模型并不总是很好地与某些业务案例相匹配。例如,报纸或杂志包含文章。为了在关系数据库中存储这些文章,您需要首先将它们拆分:文章文本放在一个表中,作者及其所有信息放在另一个表中,当文章在网站上发布时,评论放在另一个表中。如图 6.13 所示,报纸文章也可以作为一个单一实体存储;这降低了那些习惯于经常看到文章的人处理数据时的认知负担。文档存储的例子包括 MongoDB 和 CouchDB。

图 6.13。文档存储将整个文档保存下来,而关系型数据库管理系统(RDMS)会将文章拆分成几个表来保存。示例取自《卫报》网站。

图片

图数据库

最后一种大型 NoSQL 数据库类型是最复杂的,旨在以高效的方式存储实体之间的关系。当数据高度互联时,例如社交网络、科学论文引用或资本资产集群,图数据库就是解决方案。图或网络数据有两个主要组成部分:

  • 节点 — 实体本身。在社交网络中,这可能是指人。

  • — 两个实体之间的关系。这种关系由一条线表示,并具有自己的属性。边可以有方向,例如,如果箭头表示谁是老板。

在关系和实体类型足够多的情况下,图可以变得极其复杂。图 6.14 已经展示了仅使用有限数量的实体所展现的复杂性。像 Neo4j 这样的图数据库也声称遵守 ACID,而文档存储和键值存储则遵循 BASE。

图 6.14。包含四种实体类型(人、爱好、公司和家具)及其关系的图数据示例,没有额外的边或节点信息

图片

可能性是无限的,由于世界正变得越来越互联,图数据库很可能会在其他类型数据库(包括仍然占主导地位的关系数据库)中赢得更多的市场份额。最流行的数据库排名及其进展情况可以在db-engines.com/en/ranking找到。

图 6.15 显示,在本书编写时,尽管有 9 个条目,关系型数据库仍然占据着前 15 名的位置,而随着 NewSQL 的到来,我们也不能排除它们。最受欢迎的图数据库 Neo4j 在写作时位于第 23 位,Titan 位于第 53 位。

图 6.15. 根据 DB-Engines.com 在 2015 年 3 月的数据,按受欢迎程度排名的前 15 个数据库

现在你已经了解了每种 NoSQL 数据库类型,是时候动手实践其中之一了。

6.2. 案例研究:这是什么病?

这对我们中的许多人来说都发生过:你突然出现医疗症状,首先做的事情就是用谷歌搜索这些症状可能表明的疾病;然后你决定是否值得去看医生。一个网络搜索引擎对于这个任务来说是可以的,但一个更专业的数据库会更好。这样的数据库确实存在,并且相当先进;它们几乎可以成为电视剧《豪斯医生》中那位杰出诊断专家豪斯医生的虚拟版本。但它们建立在受良好保护的数据之上,并非所有数据都对公众开放。此外,尽管大型制药公司和先进医院可以访问这些虚拟医生,但许多普通开业医生仍然只能依靠他们的书籍。这种信息和资源的不对称不仅令人悲哀且危险,实际上根本不需要存在。如果世界上所有普通开业医生都使用一个简单的、针对特定疾病的搜索引擎,许多医疗错误本可以避免。

在本案例研究中,你将学习如何构建这样一个搜索引擎,尽管只使用了可自由获取的医疗数据的一小部分。为了解决这个问题,你将使用名为 Elasticsearch 的现代 NoSQL 数据库来存储数据,并使用数据科学流程来处理数据,将其转化为快速且易于搜索的资源。以下是应用此流程的方法:

1.  设定研究目标。

2.  数据收集——你将从维基百科获取数据。还有更多来源,但为了演示目的,一个就足够了。

3.  数据准备——维基百科数据在其当前格式下可能并不完美。你将应用一些技术来改变这一点。

4.  数据探索——你的用例在数据科学流程的第 4 步也是期望的最终结果:你希望你的数据变得易于探索。

5.  数据建模——在本章中未应用实际的数据建模。用于搜索的文档-词矩阵通常是高级主题建模的起点。我们在这里不会深入探讨这一点。

6.  展示结果——为了使数据可搜索,你需要一个用户界面,例如一个网站,人们可以在其中查询和检索疾病信息。在本章中,你不会构建一个实际的界面。你的次要目标:通过关键词分析疾病类别;你将达到数据科学过程的这一阶段,因为你将把它展示为一个词云,如图 6.16 所示。

图 6.16. 非加权糖尿病关键词的示例词云

要跟随代码进行操作,你需要以下这些物品:

  • 安装了 elasticsearch-py 和 Wikipedia 库的 Python 会话(pip install elasticsearchpip install wikipedia

  • 本地设置的 Elasticsearch 实例;有关安装说明,请参阅附录 A(kindle_split_018.xhtml#app01)。

  • IPython 库

注意

本章的代码可以从本书的 Manning 网站下载,链接为 manning.com/books/introducing-data-science,代码格式为 IPython。

Elasticsearch:开源搜索引擎/NoSQL 数据库

为了解决当前的问题,即诊断疾病,你将使用的 NoSQL 数据库是 Elasticsearch。与 MongoDB 类似,Elasticsearch 是一个文档存储库。但与 MongoDB 不同,Elasticsearch 是一个搜索引擎。MongoDB 在执行复杂计算和 MapReduce 作业方面非常出色,而 Elasticsearch 的主要目的是全文搜索。Elasticsearch 将对索引的数值数据进行基本计算,例如求和、计数、中位数、平均值、标准差等,但本质上它仍然是一个搜索引擎。

Elasticsearch 是建立在 Apache Lucene 之上的,Apache Lucene 是 1999 年创建的 Apache 搜索引擎。Lucene 以难以处理而闻名,它更多的是为更用户友好的应用程序提供构建块,而不是作为一个端到端解决方案本身。但 Lucene 是一个功能强大的搜索引擎,2004 年推出了 Apache Solr,2006 年向公众开放。Solr(一个开源的企业级搜索平台)建立在 Apache Lucene 之上,目前仍然是功能最全面和最受欢迎的开源搜索引擎。Solr 是一个优秀的平台,如果你参与了一个需要搜索引擎的项目,值得调查。2010 年 Elasticsearch 出现,迅速获得了人气。尽管 Solr 对于小型项目来说仍然可能难以设置和配置,但 Elasticsearch 的设置却非常简单。Solr 在扩展其核心功能方面具有优势,但 Elasticsearch 正在迅速追赶,今天其功能质量相当。

6.2.1. 第一步:设定研究目标

你能在本章结束时仅使用自己的家用电脑和免费软件及数据来诊断疾病吗?知道你想要做什么以及如何去做是数据科学过程中的第一步,如图 6.17 所示。

图 6.17。数据科学流程步骤 1:设定研究目标

图片

  • 你的主要目标是建立一个疾病搜索引擎,帮助普通医生诊断疾病。

  • 你的次要目标是描述一种疾病:哪些关键词可以将其与其他疾病区分开来?

这个次要目标在教育目的或作为更高级用途的输入(例如,通过社交媒体检测传播的流行病)很有用。在确定了研究目标和行动计划后,让我们继续到数据检索步骤。

6.2.2。步骤 2 和 3:数据检索和准备

数据检索和数据准备是数据科学流程中的两个不同步骤,尽管这在案例研究中仍然成立,但我们将在这同一节中探讨这两个步骤。这样,你可以在检索数据的同时立即进行数据准备,避免设置本地中间存储。让我们看看我们在数据科学流程中的位置(见图 6.18)。

图 6.18。数据科学流程步骤 2:数据检索。在这种情况下,没有内部数据;所有数据都将从维基百科获取。

图片

如图 6.18 所示,你有两个可能的数据来源:内部数据和外部数据。

  • 内部数据 —你周围没有疾病信息。如果你目前为制药公司或医院工作,你可能更幸运。

  • 外部数据 —在这种情况下,你可以使用的外部数据仅限于外部数据。你有几个选择,但你会选择维基百科。

当你从维基百科拉取数据时,你需要将其存储在你的本地 Elasticsearch 索引中,但在你这样做之前,你需要准备数据。一旦数据进入 Elasticsearch 索引,就不能更改;那时你能做的只是查询它。看看图 6.19 中的数据准备概述。

图 6.19。数据科学流程步骤 3:数据准备

图片

如图 6.19 所示,需要考虑三种不同的数据准备类别:

  • 数据清洗 —从维基百科获取的数据可能是不完整或错误的。数据输入错误和拼写错误是可能的——甚至不包括错误信息。幸运的是,你不需要疾病列表详尽无遗,你可以在搜索时处理拼写错误;关于这一点,稍后会有更多说明。多亏了维基百科 Python 库,你将收到的文本数据已经相当干净。如果你要手动抓取,你需要添加 HTML 清理,移除所有 HTML 标签。实际上,全文搜索通常对常见错误如值错误相当稳健。即使你故意输入 HTML 标签,它们也不太可能影响结果;HTML 标签与正常语言太不同,不会干扰。

  • 数据转换 —在这个阶段,您不需要转换太多数据;您希望以原始形式搜索它。但您将区分页面标题、疾病名称和页面正文。这种区分对于搜索结果解释几乎是强制性的。

  • 数据合并 —在这种情况下,所有数据都来自单一来源,因此您实际上没有合并数据的真正需要。这个练习的一个可能的扩展是从另一个来源获取疾病数据并匹配疾病。这是一个非同小可的任务,因为没有唯一的标识符,而且名称通常略有不同。

您只能在两个阶段进行数据清理:当使用连接维基百科和 Elasticsearch 的 Python 程序时,以及当运行 Elasticsearch 内部索引系统时:

  • Python —在这里,您定义您的文档存储将允许存储哪些数据,但在这个阶段,您不会清理或转换数据,因为 Elasticsearch 在这方面做得更好,而且更省力。

  • Elasticsearch —Elasticsearch 将在幕后处理数据操作(创建索引)。您仍然可以影响这个过程,您将在本章的后面更明确地这样做。

现在您已经了解了即将到来的步骤概述,让我们开始工作。如果您遵循了附录中的说明,您现在应该有一个本地运行的 Elasticsearch 实例。首先进行数据检索:您需要有关不同疾病的信息。您有几种获取这种数据的方式。您可以向公司索求数据,或从 Freebase 或其他开放和免费数据源获取数据。获取数据可能是一个挑战,但在这个例子中,您将从维基百科获取数据。这有点讽刺,因为维基百科网站本身的搜索是由 Elasticsearch 处理的。维基百科曾经拥有一个基于 Apache Lucene 构建的自有系统,但这个系统变得难以维护,截至 2014 年 1 月,维基百科开始使用 Elasticsearch。

维基百科有一个疾病列表页面,如图 6.20 所示。图 6.20。从这里,您可以借用字母顺序列表中的数据。

图 6.20. 维基百科的疾病列表页面,您数据检索的起点

图片 6.20 的替代文本

您知道您想要什么数据;现在去获取它。您可以下载整个维基百科数据存档。如果您愿意,您可以将其下载到meta.wikimedia.org/wiki/Data_dump_torrents#enwiki

当然,如果你要索引整个维基百科,索引最终可能需要大约 40 GB 的存储空间。你可以自由使用这个解决方案,但为了节省存储和带宽,我们在这本书中只提取我们打算使用的那些数据。另一种选择是抓取所需的页面。像 Google 一样,你可以编写一个程序在页面之间进行爬取并检索整个渲染的 HTML。这将有效,但你最终会得到实际的 HTML,因此你需要在索引之前清理它。此外,除非你是 Google,否则网站通常不太喜欢爬虫抓取它们的网页。这会创建不必要的流量,如果足够多的人发送爬虫,它可能会使 HTTP 服务器崩溃,让所有人的乐趣都受到影响。同时发送数十亿个请求也是执行拒绝服务(DoA)攻击的一种方式。如果你确实需要抓取网站,请在每个页面请求之间留出时间间隔。这样,你的爬虫会更接近普通网站访问者的行为,你也不会使他们的服务器崩溃。

幸运的是,维基百科的创建者足够聪明,知道当所有这些信息对每个人开放时会发生什么。他们已经建立了一个 API,你可以从中安全地获取信息。你可以在www.mediawiki.org/wiki/API:Main_page了解更多相关信息。

你将从 API 中获取数据。Python 如果没有现成的库来完成这项工作,那就不是 Python 了。实际上有几个库,但最简单的一个就足以满足你的需求:维基百科。

激活你的 Python 虚拟环境,并安装本书剩余部分所需的所有库:

pip install wikipedia
pip install Elasticsearch

你将使用维基百科来访问维基百科。Elasticsearch 是主要的 Elasticsearch Python 库;通过它,你可以与你的数据库进行通信。

打开你喜欢的 Python 解释器并导入必要的库:

from elasticsearch import Elasticsearch
import wikipedia

你将从维基百科 API 中获取数据,同时在你的本地 Elasticsearch 实例上进行索引,因此首先你需要为数据接受做好准备。

图片

你首先需要的是一个客户端。Elasticsearch() 可以用地址初始化,但默认是 localhost:9200。因此,Elasticsearch()Elasticsearch ('localhost:9200') 是同一件事:你的客户端连接到了你的本地 Elasticsearch 节点。然后你创建一个名为 "medical" 的索引。如果一切顺利,你应该会看到一个 "acknowledged:true" 的回复,如图 6.21 所示。

图 6.21. 使用 Python-Elasticsearch 创建 Elasticsearch 索引

图片

Elasticsearch 声称是无模式的,这意味着您可以在不定义数据库模式也不告诉 Elasticsearch 需要期望什么类型的数据的情况下使用 Elasticsearch。尽管这在简单情况下是正确的,但您最终无法避免拥有一个模式,所以让我们创建一个,如下所示。

列表 6.1. 向文档类型添加映射

图片

这样您就告诉 Elasticsearch,您的索引将有一个名为 "disease" 的文档类型,并为每个字段提供了字段类型。疾病文档中有三个字段:nametitlefulltext,它们都是 string 类型。如果您没有提供映射,Elasticsearch 会通过查看它接收到的第一个条目来猜测它们的类型。如果它没有识别出字段为 booleandoublefloatlongintegerdate,它就会将其设置为 string。在这种情况下,您不需要手动指定映射。

现在让我们转到维基百科。您首先想做的事情是获取疾病列表页面,因为这是您进一步探索的入口点:

dl = wikipedia.page("Lists_of_diseases")

您现在有了第一页,但您更感兴趣的是列表页面,因为它们包含指向疾病的链接。查看以下链接:

dl.links

疾病列表页面包含的链接比您需要的要多。图 6.22 展示了从第 16 个链接开始的字母列表。

图 6.22. 维基百科页面“疾病列表”上的链接。它包含的链接比您需要的多。

图片

dl = wikipedia.page("Lists_of_diseases")
dl.links

这个页面有很多链接,但只有字母列表对您感兴趣,所以只保留那些:

diseaseListArray = []
for link in dl.links[15:42]:
    try:
        diseaseListArray.append(wikipedia.page(link))
    except Exception,e:
        print str(e)

您可能已经注意到子集是硬编码的,因为您知道它们是数组中的第 16 到第 43 个条目。如果维基百科在您感兴趣的链接之前添加任何链接,它就会影响结果。更好的做法是使用正则表达式来完成这项任务。出于探索目的,硬编码条目编号是可以的,但如果正则表达式对您来说很自然,或者您打算将此代码转换为批处理作业,建议使用正则表达式。您可以在docs.python.org/2/howto/regex.html找到更多关于它们的信息。

正则表达式版本的一个可能代码片段如下。

diseaseListArray = []
check = re.compile("List of diseases*")
for link in dl.links:
    if check.match(link):
        try:
            diseaseListArray.append(wikipedia.page(link))
        except Exception,e:
            print str(e)

图 6.23 展示了您所追求的第一批条目:疾病本身。

图 6.23. 第一批维基百科疾病列表,“疾病列表(0-9)”

图片

diseaseListArray[0].links

现在是索引疾病的时候了。一旦它们被索引,数据录入和数据准备就有效完成了,如下所示。

列表 6.2. 从维基百科索引疾病

图片

因为每个列表页面都会有你不需要的链接,检查一个条目是否是疾病。你为每个列表指定疾病开始的字符,所以你检查这个。此外,你还排除了以“list”开头的链接,因为这些链接会在你到达疾病列表的 L 时出现。这个检查相当简单,但由于搜索算法在开始查询时会排除无关结果,所以几个不想要的条目成本相当低。对于你索引的每个疾病,你将索引疾病名称和页面的全文。名称也用作其索引 ID;这对于几个高级 Elasticsearch 功能很有用,但也可以用于浏览器中的快速查找。例如,在你的浏览器中尝试这个 URL:http://localhost:9200/medical/diseases/11 beta hydroxylase deficiency。标题是单独索引的;在大多数情况下,链接名称和页面标题将是相同的,有时标题将包含疾病的替代名称。

至少索引了几种疾病后,可以利用 Elasticsearch URI 进行简单的查找。看看图 6.24 中对于单词 headache 的全身搜索。你可以在索引的同时做这件事;Elasticsearch 可以同时更新索引并返回查询结果。

图 6.24. Elasticsearch URL 示例构建

图片 6.24 替代

如果你没有查询索引,你仍然可以在不知道索引的情况下获得一些结果。指定 http://localhost:9200/medical/diseases/_search 将返回前五个结果。为了对数据进行更结构化的查看,你可以请求此文档类型的映射,在 http://localhost:9200/medical/diseases/_mapping?pretty。pretty get 参数以更易读的格式显示返回的 JSON,如图 6.25 所示。映射似乎正是你指定的那样:所有字段类型都是 string

图 6.25. 通过 Elasticsearch URL 疾病文档类型映射

图片 6.25

Elasticsearch URL 确实很有用,但不足以满足你的需求。你仍需要诊断疾病,为此你将通过 Elasticsearch Python 库向 Elasticsearch 发送 POST 请求。

数据检索和准备完成后,你可以继续探索你的数据。

6.2.3. 步骤 4:数据探索

不是狼疮。绝不是狼疮!

《豪斯医生》中的豪斯博士*

数据探索是本案例研究的标志,因为项目的首要目标(疾病诊断)是通过查询疾病症状来探索数据的一种特定方式。图 6.26 展示了几个数据探索技术,但在这个案例中是非图形化的:解释文本搜索查询结果。

图 6.26. 数据科学流程步骤 4:数据探索

图片 6.26 替代

真正的时刻到了:通过向搜索引擎提供症状,你能找到某些疾病吗?让我们首先确保你具备基本的知识并开始运行。导入 Elasticsearch 库并定义全局搜索设置:

from elasticsearch import Elasticsearch
client = Elasticsearch()
indexName = "medical"
docType="diseases"
searchFrom = 0
searchSize= 3

你将只返回前三个结果;默认为五个。

Elasticsearch 有一个复杂的 JSON 查询语言;每次搜索都是一个发送到服务器的 POST 请求,并将以 JSON 格式回答。大致来说,该语言由三个主要部分组成:查询、过滤和聚合。一个 查询 会接收搜索关键词,并在索引中查找单词之前通过一个或多个分析器。我们将在本章稍后深入了解分析器。一个 过滤 与查询类似,但不会尝试分析你提供的内容;它根据我们提供的条件进行过滤。因此,过滤比查询更简单,但效率更高,因为它们也会在 Elasticsearch 中临时存储,以防你两次使用相同的过滤条件。聚合 可以与 SQL 中的分组进行比较;将创建单词的桶,并为每个桶计算相关统计信息。这三个部分中的每一个都有大量的选项和功能,使得在这里详细阐述整个语言变得不可能。幸运的是,我们不需要深入了解 Elasticsearch 查询可以表示的复杂性。我们将使用“查询字符串查询语言”,这是一种查询数据的方式,与 Google 搜索查询语言非常相似。例如,如果你想强制搜索项,你可以在前面添加一个加号(+);如果你想排除搜索项,你使用减号(-)。不建议使用 Elasticsearch 进行查询,因为它会降低性能;搜索引擎首先需要将查询字符串转换为它自己的原生 JSON 查询语言。但对你来说,它将工作得很好;此外,在索引中的几万条记录中,性能不会成为一个因素。现在,是时候查询你的疾病数据了。

项目主要目标:通过症状诊断疾病

如果你曾经看过流行的电视连续剧 House M.D.,句子“它绝不是狼疮”可能听起来很熟悉。狼疮是一种自身免疫性疾病,其中身体的免疫系统攻击身体的健康部分。让我们看看你的搜索引擎需要哪些症状来确定你正在寻找狼疮。

从三个症状开始:疲劳、发烧和关节痛。你的想象中的病人都有这三种症状(还有更多),所以通过在每个症状前添加一个加号使其都成为强制性的:

列表 6.3. “简单查询字符串” Elasticsearch 查询包含三个强制关键词

图片

在具有 JSON 结构的searchBody中,你指定你想要返回的字段,在这种情况下,疾病的名称就足够了。你使用查询字符串语法在所有索引字段中进行搜索:fulltexttitlename。通过添加^,你可以给每个字段赋予一个权重。如果一个症状出现在标题中,它比在开放文本中重要五倍;如果它出现在名称本身中,它被认为重要十倍。注意“关节痛”被一对引号包围。如果你没有引号,关节pain将被视为两个单独的关键词,而不是一个短语。在 Elasticsearch 中,这被称为短语匹配。让我们看看图 6.27 中的结果。

图 6.27. 狼疮第一次搜索,共有 34 个结果

图 6.27

图 6.27显示了从 34 个匹配疾病中返回的前三个结果。结果按其匹配分数排序,即变量_score。匹配分数并不是一件简单的事情来解释;它考虑了疾病与你的查询的匹配程度,关键词出现的次数,你给出的权重等等。目前,狼疮甚至没有出现在前三名结果中。幸运的是,狼疮还有另一个独特的症状:皮疹。皮疹并不总是出现在人的脸上,但它确实会发生,这也是狼疮得名的原因:面部皮疹使人们略似狼。你的病人有皮疹,但没有面部特有的皮疹,所以将“皮疹”添加到症状中,而不提及其面部。

"query": '+fatigue+fever+"joint pain"+rash',

新搜索的结果在图 6.28 中显示。

图 6.28. 狼疮第二次搜索尝试,共有六个结果,狼疮位于前三名

图 6.28

现在结果已经缩小到六个,狼疮位于前三名。此时,搜索引擎表示人粒细胞无形体病(HGE)的可能性更大。HGE 是一种由蜱传播的疾病,就像臭名昭著的莱姆病一样。到目前为止,一个有能力的医生已经能够确定困扰你病人的疾病,因为在确定疾病时,许多因素都在发挥作用,远远超出了你那谦逊的搜索引擎所能处理的范围。例如,皮疹只出现在 HGE 的 10%和狼疮患者的 50%中。狼疮缓慢出现,而 HGE 是由蜱咬引起的。通过以更结构化的方式提供所有这些信息的先进机器学习数据库,可以以更高的确定性做出诊断。鉴于你需要依靠维基百科页面,你需要另一个症状来确认它是狼疮。病人有胸痛,所以将其添加到列表中。

"query": '+fatigue+fever+"joint pain"+rash+"chest pain"',

结果在图 6.29 中显示。

图 6.29. 狼疮第三次搜索:有足够的症状来确定它一定是狼疮

图 6.29

看起来是狼疮。得出这个结论花了一些时间,但你做到了。当然,你在向 Elasticsearch 展示症状的方式上受到了限制。你只使用了单个术语(“疲劳”)或直接短语(“关节疼痛”)。这个例子中这行得通,但 Elasticsearch 比这更灵活。它可以接受正则表达式并进行模糊搜索,但这超出了本书的范围,尽管在可下载的代码中包含了一些示例。

处理拼写错误:Damerau-Levenshtein

假设有人输入了“lupsu”而不是“lupus”。拼写错误在所有类型的人类文档中都会发生。为了处理这种情况,数据科学家通常会使用 Damerau-Levenshtein。两个字符串之间的 Damerau-Levenshtein 距离是将一个字符串转换为另一个字符串所需的操作数。计算距离允许进行四种操作:

  • 删除 —从字符串中删除一个字符。

  • 插入 —向字符串中添加一个字符。

  • 替换 —用一个字符替换另一个字符。如果没有将替换计为一个操作,将一个字符更改为另一个字符将需要两个操作:一个删除和一个插入。

  • 两个相邻字符的置换 —交换两个相邻字符。

最后一个操作(置换)是传统 Levenshtein 距离和 Damerau-Levenshtein 距离之间的区别。正是这个最后的操作使得我们的阅读障碍拼写错误在可接受的范围内。Damerau-Levenshtein 对这种置换错误很宽容,这使得它在搜索引擎中非常出色,但它也被用于其他事情,例如计算 DNA 字符串之间的差异。

图 6.30 展示了如何通过单个置换将“lupsu”转换为“lupus”。

图 6.30。相邻字符的置换是 Damerau-Levenshtein 距离中的操作之一。其他三个操作是插入、删除和替换。

仅此而已,你就已经实现了你的第一个目标:诊断疾病。但不要忘记你的次要项目目标:疾病分析

项目次要目标:疾病分析

你想要的是一个适合你选定疾病的单词列表。为此,你将使用显著术语聚合。用于确定哪些单词是显著的分数计算又是一次因素组合,但大致可以归结为比较一个术语在结果集中出现的次数与所有其他文档相比。这样,Elasticsearch 通过提供区分其他数据的关键词来对你的结果集进行配置文件。让我们以糖尿病为例,这是一种可以采取多种形式的常见疾病:

列表 6.4。针对“糖尿病”的显著术语 Elasticsearch 查询

你在这里看到了新的代码。你放弃了查询字符串搜索,而是使用了过滤器。过滤器被封装在查询部分,因为搜索查询可以与过滤器结合使用。在示例中并未出现,但在此发生时,Elasticsearch 将首先应用效率更高的过滤器,然后再尝试搜索。如果你知道你想要在数据子集中进行搜索,添加一个过滤器首先创建这个子集总是一个好主意。为了演示这一点,考虑以下两个代码片段。它们产生相同的结果,但它们并不完全相同。

在疾病名称中搜索“糖尿病”的简单查询字符串:

"query":{
    "simple_query_string" : {
        "query": 'diabetes',
        "fields": ["name"]
        }
    }

一个过滤所有疾病名称中包含“糖尿病”的术语过滤器:

"query":{
    "filtered" : {
        "filter": {
            'term': {'name':'diabetes'}
        }
    }
}

尽管它不会显示在你可用的少量数据中,但过滤器的速度远远快于搜索。搜索查询将为每种疾病计算一个搜索分数并相应地排名,而过滤器只是过滤掉所有不符合条件的。因此,过滤器比实际搜索要简单得多:它只是“是”或“否”,这在输出中很明显。分数对所有内容都是 1;在结果集中没有做出区分。由于显著术语聚合,输出现在由两部分组成,之前只有命中项;现在有命中项和聚合项。首先,看看图 6.31 中的命中项。

图 6.31. 带有“糖尿病”过滤条件的疾病名称查询输出

现在应该很熟悉了,只有一个显著的例外:所有结果都有 1 分的评分。除了更容易执行之外,过滤器还会被 Elasticsearch 缓存一段时间。这样,具有相同过滤器的后续请求甚至更快,这比搜索查询有巨大的性能优势。

你应该在什么时候使用过滤器,什么时候使用搜索查询?规则很简单:尽可能使用过滤器,当需要结果之间的排名以获取最有趣的结果放在顶部时,使用全文搜索查询。

现在看看图 6.32 中的显著术语。

图 6.32. 糖尿病显著术语聚合,前五个关键词

如果你查看图 6.32 中的前五个关键词,你会发现前四个与糖尿病的起源相关。以下维基百科段落提供了帮助:

单词 diabetes()来自拉丁语 diabētēs,它又来自古希腊语 διαβήτης (diabētēs),其字面意思是“通过者;虹吸管” [69]。古希腊医生卡帕多西亚的阿雷塔乌斯(公元 1 世纪活跃)使用这个词,其意图是“尿液过度排出”,作为疾病的名称 [70, 71, 72]。最终,这个词来自希腊语 διαβαívειv (diabainein),意为“通过”,[69] 由 dia-(dia-)意为“通过”和 βαívειv (bainein) 意为“去” [70] 组成。单词“diabetes”首次记录在英语中,形式为 diabete,在 1425 年左右撰写的一篇医学文本中。

维基百科页面 Diabetes_mellitus

这告诉你单词 diabetes 的来源:“在希腊语中是‘通过者;虹吸管’”。它还提到了 diabaineinbainein。你可能知道,与疾病最相关的关键词将是实际定义和起源。幸运的是,我们请求了 30 个关键词,所以让我们挑选一些更有趣的,比如 ndindiNDI 的小写版本,即“Nephrogenic Diabetes Insipidus”,这是最常见的糖尿病获得形式。小写关键词被返回,因为这是我们通过标准分析器索引时它们在索引中的存储方式。我们在索引时没有指定任何内容,所以默认使用了标准分析器。前 30 个有趣的关键词还包括 avp,与糖尿病相关的基因;thirst,糖尿病的症状;以及 Amiloride,糖尿病的药物。这些关键词似乎确实可以描述糖尿病,但我们缺少多词关键词;我们仅在索引中存储了单个术语,因为这默认行为。某些单词单独出现时永远不会出现,因为它们不太常用,但与其他术语结合使用时仍然很重要。目前,我们错过了某些术语之间的关系。以 avp 为例;如果 avp 总是以其完整形式“Nephrogenic Diabetes Insipidus”书写,它就不会被选中。存储 n-gramsn 个单词的组合)会占用存储空间,并且使用它们进行查询或聚合会加重搜索服务器的负担。决定在哪里停止是一个平衡练习,取决于你的数据和用例。

通常,二元组(两个术语的组合)是有用的,因为自然语言中存在有意义的二元组,尽管 10-grams 并不多。对于疾病特征,二元组的关键概念将是有用的,但为了创建这些二元组显著术语聚合,你需要将它们作为二元组存储在你的索引中。正如数据科学中经常发生的那样,你需要退回几步来做出一些更改。让我们回到数据准备阶段。

6.2.4. 步骤 3 回顾:疾病特征数据准备

如图 6.33 所示,您回到了数据准备阶段,这并不奇怪。毕竟,数据科学流程是一个迭代的过程。当您索引数据时,实际上几乎没有进行数据清洗或数据转换。您现在可以通过例如停用词过滤来添加数据清洗。停用词是如此常见,以至于它们通常被丢弃,因为它们可能会污染结果。我们不会在这里详细介绍停用词过滤(或其他数据清洗),但您可以自由尝试。

图 6.33. 数据科学流程步骤 3:数据准备。文本的数据清洗可以是停用词过滤;数据转换可以是字符的小写化。

要索引二元组,您需要创建自己的标记过滤器和文本分析器。一个标记过滤器能够对标记进行转换。您的特定标记过滤器需要将标记组合起来创建n-gram,也称为瓦片。默认的 Elasticsearch 标记器称为标准标记器,它会寻找单词边界,如单词之间的空格,将文本切割成不同的标记或术语。请查看您疾病索引的新设置,如下所示。

列表 6.5. 更新 Elasticsearch 索引设置

您创建了两个新元素:一个名为“my shingle filter”的标记过滤器和一个名为“my_shingle_analyzer”的新分析器。由于n-gram 非常常见,Elasticsearch 自带了一个内置的瓦片标记过滤器类型。您需要告诉它的只是您想要二元组"min_shingle_size" : 2, "max_shingle_size" : 2,如图 6.34 所示。您可以选择三元组或更高阶的,但为了演示目的,这已经足够了。

图 6.34. 用于生成二元组的瓦片标记过滤器

如图 6.35 所示的分析器是所有从输入文本到索引所需操作的组合。它包含了瓦片过滤器,但远不止于此。标记器将文本分割成标记或术语;然后您可以使用小写过滤器,这样在搜索“糖尿病”与“diabetes”时就没有区别。最后,您应用您的瓦片过滤器,创建您的二元组。

图 6.35. 带有标准标记化和瓦片标记过滤器的自定义分析器,用于生成二元组

注意,在更新设置之前您需要关闭索引。然后您可以安全地重新打开索引,知道您的设置已经更新。并非所有设置更改都需要关闭索引,但这次需要。您可以在www.elastic.co/guide/en/elasticsearch/reference/current/indices-update-settings.html找到需要关闭索引的设置概览。

索引现在可以准备使用你的新分析器了。为此,你将创建一个新的文档类型 diseases2,并使用新的映射,如下所示。

列表 6.6. 创建更高级的 Elasticsearch doctype 映射

图片

fulltext 中,你现在有一个额外的参数,fields。在这里,你可以指定所有不同的 fulltext 同位素。你只有一个;它被称为 shingles,将使用你的新 my_shingle_analyzer 分析 fulltext。你仍然可以访问你原始的 fulltext,你没有为这个指定分析器,所以将使用之前的标准分析器。你可以通过给出属性名后跟其字段名来访问新的一个:fulltext.shingles。你现在需要做的就是按照前面的步骤进行,并使用维基百科 API 索引数据,如下所示。

列表 6.7. 使用新的 doctype 映射重新索引维基百科疾病解释

图片

图片

这里没有新的内容,只是这次你将索引 doc_type diseases2 而不是 diseases。当这一步完成后,你又可以继续前进到第 4 步,数据探索,并检查结果。

6.2.5. 重新审视第 4 步:疾病配置文件的数据探索

你又一次到达了数据探索阶段。你可以调整聚合查询,并使用你的新字段来获取与糖尿病相关的大词组关键概念:

列表 6.8. 基于 bigrams 对“糖尿病”进行显著术语聚合
searchBody={
"fields":["name"],
"query":{
    "filtered" : {
        "filter": {
            'term': {'name':'diabetes'}
        }
    }
},
"aggregations" : {
        "DiseaseKeywords" : {
            "significant_terms" : { "field" : "fulltext", "size" : 30 }
        },
        "DiseaseBigrams": {
            "significant_terms" : { "field" : "fulltext.shingles",
"size" : 30 }
        }
    }
}
client.search(index=indexName,doc_type=docType,
body=searchBody, from_ = 0, size=3)

你的新聚合,称为 DiseaseBigrams,使用 fulltext.shingles 字段为糖尿病提供一些新的见解。这些新关键词出现了:

  • 过度排尿 —糖尿病患者需要频繁排尿。

  • 多尿的原因 —这表明了相同的事情:糖尿病导致患者频繁排尿。

  • 剥夺测试 —这实际上是一个三元组,“水剥夺测试”,但它识别了 剥夺测试,因为你只有 bigrams。这是一个用来确定患者是否患有糖尿病的测试。

  • 过度口渴 —你已经通过单语元关键词搜索找到了“口渴”,但从技术上讲,在那个阶段它可能意味着“没有口渴”。

还有其他有趣的 bigrams、unigrams,可能还有 trigrams。整体来看,它们可以在阅读之前用来分析文本或文本集合。请注意,你没有到达建模阶段就实现了预期的结果。有时,数据探索中至少有与数据建模一样多的有价值信息。现在你已经完全实现了你的次要目标,可以继续到数据科学过程的第 6 步:展示和自动化。

6.2.6. 第 6 步:展示和自动化

您的主要目标,疾病诊断,通过允许医生通过例如网络应用程序查询它,变成了一个自助诊断工具。在这种情况下,您不会构建一个网站,但如果您打算这样做,请阅读侧边栏“Elasticsearch for web applications。”

Elasticsearch for web applications

与任何其他数据库一样,直接将您的 Elasticsearch REST API 暴露给网络应用程序的前端是一种不良做法。如果网站可以直接向您的数据库发送 POST 请求,任何人都可以轻易地删除您的数据:始终需要一个中间层。这个中间层可以是 Python,如果您喜欢的话。两种流行的 Python 解决方案是 Django 或与独立前端结合使用的 Django REST 框架。Django 通常用于构建往返应用程序(服务器根据数据库中的数据和一个模板系统动态构建前端,的 Web 应用程序)。Django REST 框架是 Django 的一个插件,将 Django 转换成一个 REST 服务,使其能够成为单页应用程序的一部分。单页应用程序是一个使用单个网页作为锚点的 Web 应用程序,但它能够通过从 HTTP 服务器检索静态文件和从 RESTful API 获取数据来动态更改内容。这两种方法(往返和单页)都是可以的,只要 Elasticsearch 服务器本身不对公众开放,因为它没有内置的安全措施。可以直接使用“Shield”,一个 Elasticsearch 付费服务,向 Elasticsearch 添加安全措施。

第二个目标,疾病分析,也可以提升到用户界面的水平;可以让搜索结果生成一个词云,直观地总结搜索结果。我们在这本书中不会走那么远,但如果您对在 Python 中设置类似的东西感兴趣,请使用 word_cloud 库(pip install word_cloud)。或者如果您更喜欢 JavaScript,D3.js 是一个不错的选择。您可以在www.jasondavies.com/wordcloud/#%2F%2Fwww.jasondavies.com%2Fwordcloud%2Fabout%2F找到示例实现。

在这个由 D3.js 驱动的网站上添加您的关键词将生成一个类似于图 6.36 的单词云,可以将其纳入您项目结果展示中。在这种情况下,术语不是根据它们的分数进行加权的,但它已经提供了对发现的一个很好的表示。

图 6.36. 来自 Elasticsearch 的非加权糖尿病关键词的单词云

您的应用程序有很多改进空间,尤其是在数据准备方面。但深入探讨所有可能性会让我们走得太远;因此,我们来到了这一章的结尾。在下一章中,我们将探讨流数据。

6.3. 总结

在本章中,你学习了以下内容:

  • NoSQL 代表“不仅限于结构化查询语言”,它源于处理指数级增长的数据量和种类以及对于更多样化和灵活的模式的日益增长的需求,如网络和层次结构。

  • 处理所有这些数据需要数据库分区,因为没有任何一台机器能够完成所有的工作。在分区时,CAP 定理适用:你可以拥有可用性或一致性,但永远不会同时拥有两者。

  • 关系型数据库和图数据库遵循 ACID 原则:原子性、一致性、隔离性和持久性。NoSQL 数据库通常遵循 BASE 原则:基本可用性、软状态和最终一致性。

  • 最大的四种 NoSQL 数据库类型

    • 键值存储 — 实质上是一系列存储在数据库中的键值对。这些数据库可以非常大且非常灵活,但数据复杂性较低。一个著名的例子是 Redis。

    • 宽列数据库 — 这些数据库比键值存储更复杂,因为它们使用列,但比常规的关系型数据库管理系统(RDBMS)更高效。列基本上是解耦的,允许你快速检索单个列中的数据。一个著名的数据库是 Cassandra。

    • 文档存储 — 这些数据库稍微复杂一些,以文档的形式存储数据。目前最受欢迎的是 MongoDB,但在我们的案例研究中,我们使用的是 Elasticsearch,它既是文档存储也是搜索引擎。

    • 图数据库 — 这些数据库可以存储最复杂的数据结构,因为它们以相同的方式关注实体及其之间的关系。这种复杂性在查找速度上是有代价的。一个流行的是 Neo4j,但 GraphX(与 Apache Spark 相关的图数据库)正在赢得市场份额。

  • Elasticsearch 是建立在 Apache Lucene 之上的文档存储和全文搜索引擎,Apache Lucene 是一个开源的搜索引擎。它可以用于分词、执行聚合查询、执行维度(分面)查询、分析搜索查询以及更多功能。

第七章. 图数据库的兴起

本章涵盖

  • 介绍连接数据及其与图和图数据库的关系

  • 学习图数据库与关系数据库的不同之处

  • 发现图数据库 Neo4j

  • 将数据科学流程应用于使用图数据库 Neo4j 的推荐引擎项目

一方面,我们在大规模地产生数据,促使谷歌、亚马逊和 Facebook 等公司想出智能的方法来处理这个问题,另一方面,我们面临着比以往任何时候都更加互联的数据。图和网络在我们的生活中无处不在。通过展示几个激励性的例子,我们希望教会读者如何在数据揭示自身时识别图问题。在本章中,我们将探讨如何利用图数据库中的这些连接,充分利用其价值,并演示如何使用流行的图数据库 Neo4j。

7.1. 介绍连接数据和图数据库

让我们先熟悉一下连接数据及其作为图数据表示的概念。

  • 连接数据 —正如其名所示,连接数据的特点是手头的数据具有使其连接的关系。

  • —经常与连接数据一起在同一个句子中提到。图非常适合以有意义的方式表示数据的连接性。

  • 图数据库 —在第六章中介绍。这个主题值得特别注意的原因是,除了数据量不断增加之外,它还变得更加互联。不需要太多努力就能找到连接数据的知名例子。

数据以网络形式存在的一个突出例子是社交媒体数据。社交媒体使我们能够在网络中分享和交换数据,从而生成大量连接数据。我们可以用一个简单的例子来说明这一点。假设我们的数据中有两个人,User1 和 User2。此外,我们还知道 User1(名字:Paul,姓氏:Beun)和 User2(名字:Jelme 和姓氏:Ragnar)的名字和姓氏。表示这种关系的一种自然方式是在白板上绘制出来,如图图 7.1 所示。

图 7.1. 一个简单的连接数据示例:两个实体或节点(User1,User2),每个实体或节点都有属性(名字,姓氏),通过一个关系(认识)连接

图片

下面描述了图 7.1 的术语:

  • 实体 —我们有两个实体代表人物(User1 和 User2)。这些实体具有“name”和“lastname”属性。

  • 属性 —属性由键值对定义。从这个图中,我们还可以推断出具有“name”属性 Paul 的 User1 认识具有“name”属性 Jelme 的 User2。

  • 关系 —这是保罗和 Jelme 之间的关系。请注意,关系有一个方向:是保罗“认识”Jelme,而不是反过来。User1 和 User2 都代表人物,因此可以被分组。

  • 标签 —在图数据库中,可以通过标签对节点进行分组。在这种情况下,User1 和 User2 都可以被标记为“User”。

连接数据通常包含更多的实体和连接。在图 7.2 中,我们可以看到一个更广泛的图。包括了两个更多的实体:名为柬埔寨的 Country1 和名为瑞典的 Country2。存在两个更多的关系:“Has_been_in”和“Is_born_in”。在前一个图中,只有包含属性的实体,现在关系也包含了属性。这种图被称为属性图。连接节点 User1 和 Country1 的关系类型为“Has_been_in”,并具有一个表示数据值的属性“Date”。同样,User2 连接到 Country2,但通过不同类型的关系,即“Is_born_in”类型的关系。请注意,关系的类型为我们提供了节点之间关系的环境。节点可以有多个关系。

图 7.2. 包含了两个更多实体(Country1 和 Country2)和两个新关系(“Has_been_in”和“Is_born_in”)的更复杂的连接数据示例

图片

这种表示我们数据的方式为我们存储连接数据提供了一种直观的方法。为了探索我们的数据,我们需要沿着预定义的路径遍历图以找到我们正在寻找的模式。如果有人想知道保罗去过哪里?用图数据库术语来说,我们想找到“保罗去过”的模式。为了回答这个问题,我们会从名为“保罗”的节点开始,通过“Has_been_in”关系遍历到柬埔寨。因此,一个图遍历,对应于数据库查询,会是以下这样的:

1.  起始节点—在这种情况下,具有名称属性“Paul”的节点

2.  遍历路径—在这种情况下,从节点保罗开始到柬埔寨的路径

3.  结束节点—具有名称属性“柬埔寨”的国家节点

为了更好地理解图数据库如何处理连接数据,适当地对图的一般概念进行更多扩展是合适的。图在计算机科学和数学的图论领域中得到了广泛的研究。图论是研究图的一门学科,其中图代表用于模型化对象之间成对关系的数学结构,如图图 7.3 所示。它们之所以如此吸引人,是因为它们具有适合可视化连接数据的结构。图由顶点(在图数据库世界中也称为节点)和边(也称为关系)定义。这些概念构成了图数据结构的基本基础。

图 7.3。从数学定义的图来看,图的核心由节点(也称为顶点)和边(连接顶点的边)组成。这些对象的集合代表了图。

图 7.3

与其他数据结构相比,连接数据的独特特征是其非线性性质:任何实体都可以通过各种关系类型、中间实体和路径与其他任何实体相连。在图中,你可以区分有向图和无向图。有向图的边有——怎么可能不是这样——方向。尽管有人可能会争辩说每个问题都可以以某种方式表示为图问题,但了解何时这样做是理想的,何时不是,是很重要的。

7.1.1。我应该在何时以及为什么使用图数据库?

确定应该使用哪种图数据库的过程可能是一个复杂的过程。在这个决策过程中,一个重要的方面是找到适合您数据的确切表示。自从 20 世纪 70 年代初以来,人们必须依赖的最常见的数据库类型是关系型数据库。后来,其他类型出现了,例如层次数据库(例如,IMS),以及图数据库最接近的亲属:网络数据库(例如,IDMS)。但在过去几十年里,这个领域变得更加多样化,为最终用户提供了更多选择,这取决于他们的具体需求。考虑到最近可用的数据的发展,有两个特点在这里值得强调。第一个是数据的大小,另一个是数据的复杂性,如图 7.4 所示。

图 7.4。此图说明了图数据库在二维空间中的定位,其中一条维度代表处理的数据的大小,另一条维度代表数据的复杂性。当关系数据库由于数据的连接性而无法处理数据集的复杂性,但不是其大小时,图数据库可能是你的最佳选择。

图 7.4

如图 7.4 所示,当数据复杂但仍然较小时,我们需要依赖图数据库。虽然这里的“小”是一个相对的概念,但我们仍在谈论数亿个节点。处理复杂性是图数据库的主要优势,也是你使用它的最终“原因”。为了解释这里所说的“复杂性”是什么,首先考虑一下传统的数据库是如何工作的。

与关系型数据库的名称所暗示的相反,它们之间并没有多少关系,除了外键和主键是连接表的关系。相比之下,图数据库中的关系是一等公民。通过这一方面,它们非常适合建模和查询关联数据。关系型数据库更倾向于努力最小化数据冗余。这个过程被称为数据库规范化,其中表被分解成更小(更少冗余)的表,同时保持所有信息完整。在规范化的数据库中,只需要在一个表中执行属性的变化。这个过程的目标是将数据变化隔离在一个表中。关系型数据库管理系统(RDBMS)是作为适合表格格式的数据的数据库的一个好选择。数据中的关系可以通过连接表来表示。当连接变得更为复杂时,尤其是成为多对多连接时,它们的适用性开始下降。当你的数据量开始增加时,查询时间也会增加,维护数据库将更具挑战性。这些因素将阻碍数据库的性能。另一方面,图数据库本质上以节点和关系存储数据。尽管图数据库被归类为 NoSQL 类型数据库,但将它们作为一个独立类别呈现的趋势正在出现。人们通过注意到其他类型的 NoSQL 数据库是面向聚合的,而图数据库则不是来寻求这种分类的合理性。

例如,一个关系型数据库可能有一个表示“人”及其属性的表。任何人都通过血缘关系(以及友谊等)与其他人相关联;每一行可能代表一个人,但将它们连接到“人”表中的其他行将是一项极其困难的工作。你是否添加一个变量来持有第一个孩子的唯一标识符,并添加另一个来持有第二个孩子的 ID?你在哪里停止?第十个孩子?

另一种选择是使用中间表来表示父子关系,但你将需要一个单独的表来表示其他关系类型,如友谊。在这种情况下,你不会得到列的激增,但会得到表的激增:每种关系类型一个关系表。即使你设法以这种方式建模数据,使得所有家庭关系都存在,你也需要复杂的查询来回答简单的问题,例如“我想知道 John McBain 的孙子们。”首先,你需要找到 John McBain 的孩子。一旦找到他的孩子,你需要找到他们的孩子。当你找到所有孙子时,你已经击中了“人”表三次:

1.  找到 McBain 并获取他的孩子。

2.  查找您获得的 ID 对应的儿童,并获取他们孩子的 ID。

3.  找到 McBain 的孙子们。

图 7.5 展示了在关系数据库中递归查找的必要步骤,以便从约翰·麦克拜恩找到他的孙子们,如果所有内容都在一个表中。

图 7.5. 递归查找版本 1:所有数据在一个表中

图 7.6 是另一种建模数据的方式:父子关系是一个单独的表。

图 7.6. 递归查找版本 2:使用父子关系表

至少可以说,这种递归查找是不高效的。

当出现这种复杂性时,图数据库表现出色。让我们看看其中最受欢迎的。

7.2. 介绍 Neo4j:一个图数据库

连接数据通常存储在图数据库中。这些数据库专门设计来处理连接数据的结构。如今可用的图数据库种类繁多。按流行度递减的顺序,最知名的三个是 Neo4j、OrientDb 和 Titan。为了展示我们的案例研究,我们将选择在撰写本文时的最受欢迎的一个(参见db-engines.com/en/ranking/graph+dbms,2015 年 9 月)。

Neo4j 是一个图数据库,它将数据存储在包含节点和关系的图中(两者都可以包含属性)。这种类型的图数据库被称为属性图,非常适合存储连接数据。它具有灵活的模式,如果需要,将给我们改变数据结构的自由,提供我们添加新数据和新的关系的能力。它是一个开源项目,成熟的技术,易于安装,用户友好,并且有良好的文档。Neo4j 还有一个基于浏览器的界面,便于创建用于可视化的图。为了跟上,现在安装 Neo4j 将是正确的时机。Neo4j 可以从neo4j.com/download/下载。成功安装的所有必要步骤总结在附录 C 中。

现在让我们介绍 Neo4j 中的四个基本结构:

  • 节点 —代表实体,如文档、用户、食谱等。可以分配给节点某些属性。

  • 关系 —存在于不同的节点之间。它们可以通过独立访问或通过它们所附加的节点来访问。关系也可以包含属性,因此得名属性图模型。每个关系都有一个名称和方向,这两个属性共同为通过关系连接的节点提供语义上下文。

  • 属性 —节点和关系都可以有属性。属性由键值对定义。

  • 标签 —可以用来将相似的节点分组,以便更快地遍历图。

在进行数据分析之前,一个好的习惯是仔细设计你的数据库,以便它适合你在分析过程中想要运行的查询。图数据库有一个令人愉悦的特性,即它们适合在白板上绘制。如果尝试在白板上绘制问题设置,这个绘制将非常接近定义问题的数据库设计。因此,这样的白板绘制将是一个很好的起点,用于设计我们的数据库。

现在如何检索数据?为了探索我们的数据,我们需要通过预定义的路径在图中遍历,以找到我们正在寻找的模式。Neo4j 浏览器是一个理想的环境,可以创建和操作你的连接数据,直到你得到适合最佳查询的表示,如图 7.7 所示。图数据库的灵活模式非常适合这里。在这个浏览器中,你可以以行或图的形式检索你的数据。Neo4j 有自己的查询语言,以简化图创建和查询功能。

图 7.7. Neo4j 2.2.5 界面,已解析章节案例研究中的查询

Cypher 是一种高度表达的语言,与 SQL 有足够的相似性,可以增强语言的学习过程。在下一节中,我们将使用 Cypher 创建自己的数据并将其插入 Neo4j。然后我们可以围绕这些数据进行操作。

7.2.1. Cypher:图查询语言

让我们介绍 Cypher 及其基本的图操作语法。本节的目标是介绍足够多的关于 Cypher 的内容,以便我们能够开始使用 Neo4j 浏览器。在本节结束时,你应该能够使用 Cypher 在 Neo4j 浏览器中创建自己的连接数据,并运行基本的查询以检索查询结果。要获取 Cypher 的更详细介绍,请访问neo4j.com/docs/stable/cypher-query-lang.html。我们将从绘制一个简单的社交图并附带一个基本查询以检索预定义模式为例开始。在下一步中,我们将绘制一个更复杂的图,这将允许我们在 Cypher 中使用更复杂的查询。这将帮助我们熟悉 Cypher,并使我们能够将我们的用例带入现实。此外,我们将展示如何使用 Cypher 创建我们自己的模拟连接数据。

图 7.8 展示了两个节点之间的简单社交图,这两个节点通过“认识”类型的关系连接。节点同时具有“姓名”和“姓氏”属性。

图 7.8. 具有两个用户和一个关系的简单社交图示例

现在,如果我们想找出以下模式,“保罗认识谁?”我们可以使用 Cypher 进行查询。在 Cypher 中查找模式时,我们将从Match子句开始。在这个查询中,我们将从具有名称属性“Paul”的节点 User 开始搜索。注意节点被括号包围,如以下代码片段所示,关系被方括号包围。关系以冒号(:)前缀命名,方向使用箭头描述。占位符 p2 将包含所有具有类型“knows”关系作为入边关系的 User 节点。通过return子句,我们可以检索查询结果。

Match(p1:User { name: 'Paul' } )-[:knows]->(p2:User)
 Return p2.name

注意我们如何用口头方式表达问题与图数据库如何将其转换为遍历之间的紧密关系。在 Neo4j 中,这种令人印象深刻的表达能力是通过其图查询语言 Cypher 实现的。

为了使示例更有趣,让我们假设我们的数据由图 7.9 中的图形表示。

图 7.9. 一个更复杂的连接数据示例,包含不同类型节点的多个相互连接的节点

图片 7.9 的替代文本

我们可以使用 Cypher 将图 7.9 中的连接数据插入到 Neo4j 中。我们可以在 Neo4j 基于浏览器的界面中直接编写 Cypher 命令,或者通过 Python 驱动程序(参见neo4j.com/developer/python/以获取概述)。这是了解连接数据和图数据库的好方法。

要在 Cypher 中编写适当的创建语句,首先我们应该很好地理解我们想将哪些数据存储为节点,哪些作为关系,它们的属性应该是什么,以及标签是否有用。第一个决定是决定哪些数据应被视为节点,哪些作为关系,为这些节点提供语义上下文。在图 7.9 中,我们选择将用户和他们访问过的国家表示为节点。提供特定节点信息的数据,例如与节点关联的名称,可以表示为属性。所有提供两个或多个节点上下文的数据都将被视为关系。具有共同特征的节点,例如柬埔寨和瑞典都是国家,也将通过标签分组。在图 7.9 中,这已经完成。

在以下列表中,我们演示了如何通过一个大的创建语句在 Cypher 中编码不同的对象。请注意,Cypher 区分大小写

列表 7.1. Cypher 数据创建语句
CREATE (user1:User {name :'Annelies'}),
 (user2:User {name :'Paul' , LastName: 'Beun'}),
 (user3:User {name :'Muhuba'}),
 (user4:User {name : 'Jelme' , LastName: 'Ragnar'}),
 (country1:Country { name:'Mongolia'}),
 (country2:Country { name:'Cambodia'}),
 (country3:Country { name:'New Zealand'}),
 (country4:Country { name:'Sweden'}),
 (food1:Food { name:'Sushi' }),
 (hobby1:Hobby { name:'Travelling'}),
 (user1)-[:Has_been_in]->(country1),
 (user1)-[: Has_been_in]->(country2),
 (user1)-[: Has_been_in]->(country3),
 (user2)-[: Has_been_in]->(country2),
 (user1)-[: Is_mother_of]->(user4),
 (user2)-[: knows]->(user4),
 (user1)-[: Is_friend_of]->(user3),
 (user2)-[: Likes]->( food1),
 (user3)-[: Likes]->( food1),
 (user4)-[: Is_born_in]->(country4)

一次性运行这个创建语句的优点是,这个执行的成败将确保我们图数据库已成功创建。如果存在错误,图将不会被创建。

在实际场景中,还应该定义索引和约束以确保快速查找,而不是搜索整个数据库。我们没有在这里这样做,因为我们的模拟数据集很小。然而,这可以通过 Cypher 轻松完成。查阅 Cypher 文档以了解更多关于索引和约束的信息(neo4j.com/docs/stable/cypherdoc-labels-constraints-and-indexes.html)。现在我们已经创建了数据,我们可以查询它。以下查询将返回数据库中的所有节点和关系:

图 7.10 显示了我们所创建的数据库。我们可以将这个图与我们白板上构思的图进行比较。在我们的白板上,我们将人的节点分组为“用户”标签,将国家的节点分组为“国家”标签。尽管这个图中的节点没有用它们的标签表示,但标签确实存在于我们的数据库中。此外,我们还缺少一个节点(爱好)和类型为“喜爱”的关系。这些可以通过一个合并语句轻松添加,如果它们还不存在的话:

图 7.10. 图 7.9 中绘制的图现在已经在 Neo4j 网络界面中创建。节点不是通过它们的标签,而是通过它们的名称来表示。我们可以从图中推断出我们缺少名为Traveling爱好标签。原因是我们在创建语句中忘记包含这个节点及其对应的关系。

Merge (user3)-[: Loves]->( hobby1)

我们可以提出很多问题。例如:

  • 问题 1:安奈莉丝访问过哪些国家?创建答案的 Cypher 代码(如图 7.11 所示)是

    图 7.11. 问题 1 的结果:安奈莉丝访问过哪些国家?我们可以通过 Neo4j 的行表示看到安奈莉丝曾去过三个国家。这次遍历只用了 97 毫秒。

    Match(u:User{name:'Annelies'}) – [:Has_been_in]-> (c:Country)
    Return u.name, c.name
    
  • 问题 2:人在哪里?Cypher 代码(如图 7.12 所示)是

    图 7.12. 人在哪里?查询构建解释。

    Match ()-[r: Has_been_in]->()
    Return r LIMIT 25
    

当我们运行这个查询时,我们得到图 7.13 中显示的答案。

图 7.13. 问题 2 的结果:人在哪里?我们的遍历结果现在以 Neo4j 的图表示形式显示。现在我们可以看到,除了安奈莉丝之外,保罗也去过柬埔寨。

在问题 2 中,我们没有指定起始节点。因此,Cypher 将遍历数据库中所有存在的节点,以找到具有类型“Has_been_in”的出向关系的节点。应该避免不指定起始节点,因为,根据数据库的大小,这样的查询可能需要很长时间才能收敛。在数据上玩来玩去以获得正确的图数据库也意味着大量的数据删除。Cypher 有一个适合删除少量数据的删除语句。以下查询演示了如何删除数据库中的所有节点和关系:

MATCH(n)
Optional MATCH (n)-[r]-()
Delete n,r

现在我们已经熟悉了连接数据,并了解了如何在图数据库中管理它,我们可以更进一步,看看连接数据的实际应用。例如,社交图可以用来在图社区内找到紧密连接的节点簇。簇中彼此不认识的人可以相互介绍。寻找紧密连接的节点,即具有大量共同特征的节点,是一个广泛使用的概念。在下一节中,我们将使用这个想法,目标是找到成分网络内的簇。

7.3. 连接数据示例:食谱推荐引擎

图数据库最流行的用例之一是推荐引擎的开发。推荐引擎因其承诺创建相关内容而得到广泛应用。生活在这样一个数据丰富的时代,对许多消费者来说可能会感到不知所措。企业看到了通过个性化内容吸引客户的明确需求,因此利用推荐引擎的优势。

在我们的案例研究中,我们将根据用户的菜品偏好和成分网络推荐食谱。在数据准备过程中,我们将使用 Elasticsearch 来加速流程,并使我们能够更多地关注实际的图数据库。在这里,它的主要目的将是用我们自己的“干净”列表中的成分替换“脏”下载数据的成分列表。

如果您跳过了前面的章节直接来到这里,至少应该阅读附录 A 关于安装 Elasticsearch 的内容,这样您就可以在您的电脑上运行它。如果您不想处理第六章的案例研究,您可以从本章的下载页面下载我们将使用的索引,并将其粘贴到您本地的 Elasticsearch 数据目录中。

您可以从 Manning 网站下载本章的相关信息:

三个 .py 代码文件及其 .ipynb 对应文件

  • 数据准备第一部分 — 将数据上传到 Elasticsearch(或者您可以将可下载的索引粘贴到您本地的 Elasticsearch 数据文件夹中)

  • 数据准备第二部分 — 将数据从 Elasticsearch 移动到 Neo4j

  • 探索与推荐系统

三个数据文件

  • 食材 (.txt) —自行编译的食材文件

  • 食谱 (.json) —包含所有食材

  • Elasticsearch 索引 (.zip) —包含“美食”Elasticsearch 索引,您可以使用它跳过数据准备的第一部分

现在我们已经拥有了所有需要的东西,让我们看看研究目标和我们需要采取的步骤来实现它。

7.3.1. 第 1 步:设定研究目标

让我们看看遵循数据科学流程(图 7.14)时会出现什么情况。

图 7.14. 将数据科学流程概述应用于连接数据推荐模型

我们的主要目标是建立一个推荐引擎,帮助烹饪网站的用户找到合适的食谱。用户可以喜欢几个食谱,我们将根据食谱网络中食材的重叠来制定菜肴推荐。这是一个简单直观的方法,但已经产生了相当准确的结果。让我们看看我们需要的三个数据元素。

7.3.2. 第 2 步:数据检索

对于这个练习,我们需要三种类型的数据:

  • 食谱及其相应的食材

  • 我们想要建模的不同食材列表

  • 至少一个用户及其对某些菜肴的偏好

和往常一样,我们可以将其分为内部可用或创建的数据和外部获取的数据。

  • 内部数据 —我们没有任何用户偏好或食材,但这些只是我们数据的一小部分,并且很容易创建。一些手动输入的偏好应该足以创建一个推荐。用户给出的反馈越多,他得到的结果就越有趣、越准确。我们将在案例研究中稍后输入用户偏好。食材列表可以手动编制,并将在未来几年内保持相关性,因此请随意使用下载材料中的列表,无论是商业用途还是其他用途。

  • 外部数据食谱是另一回事。有成千上万的食材存在,但它们可以组合成数百万道菜肴。然而,我们很幸运,因为有一个相当大的列表在github.com/fictivekin/openrecipes上免费提供。非常感谢 Fictive Kin 为我们提供了这个包含超过十万道食谱的宝贵数据集。当然,这里有一些重复项,但它们对我们的用例影响不大。

现在我们有可用的两个数据文件:一个包含 800 多个食材的列表(ingredients.txt)和超过十万道食谱的 recipes.json 文件。以下是一个食材列表的示例。

列表 7.2. 食材列表文本文件示例
Ditalini
Egg Noodles
Farfalle
Fettuccine
Fusilli
Lasagna
Linguine
Macaroni
Orzo

“openrecipes” JSON 文件包含超过十万道食谱,具有多个属性,如发布日期、来源位置、准备时间、描述等。我们只对名称和食材列表感兴趣。以下是一个示例食谱。

列表 7.3. 一个示例 JSON 食谱
{ "_id" : { "$oid" : "5160756b96cc62079cc2db15" },
    "name" : "Drop Biscuits and Sausage Gravy",
    "ingredients" : "Biscuits\n3 cups All-purpose Flour\n2 Tablespoons Baking
     Powder\n1/2 teaspoon Salt\n1-1/2 stick (3/4 Cup) Cold Butter, Cut Into
     Pieces\n1-1/4 cup Butermilk\n SAUSAGE GRAVY\n1 pound Breakfast Sausage,
     Hot Or Mild\n1/3 cup All-purpose Flour\n4 cups Whole Milk\n1/2 teaspoon
     Seasoned Salt\n2 teaspoons Black Pepper, More To Taste",
    "url" : "http://thepioneerwoman.com/cooking/2013/03/drop-biscuits-and-
     sausage-gravy/",
    "image" : "http://static.thepioneerwoman.com/cooking/files/2013/03/
     bisgrav.jpg",
    "ts" : { "$date" : 1365276011104 },
    "cookTime" : "PT30M",
    "source" : "thepioneerwoman",
    "recipeYield" : "12",
    "datePublished" : "2013-03-11",
    "prepTime" : "PT10M",
    "description" : "Late Saturday afternoon, after Marlboro Man had returned home
     with the soccer-playing girls, and I had returned home with the..."
}

因为在这里我们处理的是文本数据,问题有两个方面:首先,按照文本挖掘章节中描述的准备工作准备文本数据。然后,一旦数据被彻底清理,就可以用来根据成分网络生成食谱推荐。本章不专注于文本数据准备,因为这部分在其他地方已有描述,所以我们将允许自己在即将到来的数据准备阶段使用捷径。

7.3.3. 第 3 步:数据准备

现在我们有两组数据文件可供使用,我们需要将它们合并成一个图数据库。未清理的食谱数据存在一个问题,我们可以通过使用我们的清洁成分列表和搜索引擎以及 NoSQL 数据库 Elasticsearch 来解决。我们已经在之前的章节中依赖过 Elasticsearch,现在它将在创建索引时隐式地清理食谱数据。然后我们可以搜索这些数据,将每个成分与其出现的每个食谱联系起来。我们可以像在文本挖掘章节中做的那样,使用纯 Python 清理文本数据,但这表明了解每个 NoSQL 数据库的强点是很好的;不要局限于单一技术,而是将它们结合起来,以利于项目。

让我们从将我们的食谱数据输入到 Elasticsearch 开始。如果你不明白发生了什么,请再次查看第六章的案例研究[kindle_split_014.xhtml#ch06],它应该会变得清晰。确保在运行以下列表中的代码片段之前,打开你的本地 Elasticsearch 实例并激活一个已安装 Elasticsearch 模块的 Python 环境。建议不要直接在 Ipython(或 Jupyter)中运行此代码,因为它会将每个食谱键打印到屏幕上,而你的浏览器只能处理这么多输出。要么关闭打印语句,要么在另一个 Python IDE 中运行。此代码片段中的代码可以在“Data Preparation Part 1.py”中找到。

列表 7.4. 将食谱数据导入 Elasticsearch

如果一切顺利,我们现在已经有一个名为“gastronomical”的 Elasticsearch 索引,其中包含数千个食谱。请注意,我们没有将食谱名称指定为文档键,因此允许了相同食谱的重复。例如,如果食谱名称为“lasagna”,那么这可能是一个三文鱼 lasagna、牛肉 lasagna、鸡肉 lasagna 或任何其他类型的 lasagna。没有单个食谱被选为原型 lasagna;它们都使用相同的名称“lasagna”上传到 Elasticsearch。这是一个选择,所以请随意决定是否不同。这将产生重大影响,正如我们稍后将会看到的。现在,我们可以系统地上传到我们的本地图数据库。在应用以下代码时,请确保您的本地图数据库实例已开启。我们在这个数据库上的用户名是默认的 Neo4j,密码是 Neo4ja;请确保根据您的本地设置进行调整。为此,我们还需要一个名为 py2neo 的特定于 Neo4j 的 Python 库。如果您还没有安装,现在就是时候使用pip install py2neo或使用 Anaconda 时使用conda install py2neo将其安装到您的虚拟环境中。再次提醒,直接在 Ipython 或 Jupiter 中运行此代码将导致浏览器崩溃。此列表中的代码可以在“Data Preparation Part 2.py”中找到。

列表 7.5. 使用 Elasticsearch 索引填充图数据库

图片

图片

太好了,我们现在自豪地拥有了一个充满食谱的图数据库!现在是时候进行连接数据探索了。

7.3.4. 步骤 4:数据探索

现在我们已经将数据放在了我们想要的位置,我们可以使用 Neo4j 界面在 http://localhost:7474/browser/上手动探索它。

在这个环境中运行您的 Cypher 代码没有任何阻碍,但 Cypher 也可以通过 py2neo 库来执行。我们可以提出一个有趣的问题:在所有食谱中,哪些成分出现的频率最高?如果我们随机选择并食用这个数据库中的菜肴,我们最有可能摄入消化系统的是什么?

from py2neo import Graph, authenticate, Node, Relationship
authenticate("localhost:7474", "user", "password")
graph_db = Graph("http://localhost:7474/db/data/")graph_db.cypher.execute("
   MATCH (REC:Recipe)-[r:Contains]->(ING:Ingredient) WITH ING, count(r) AS num
   RETURN ING.Name as Name, num ORDER BY num DESC LIMIT 10;")

查询是用 Cypher 创建的,内容如下:对于所有食谱及其成分,计算每个成分的关系数量,并返回关系数量最多的十个成分及其相应的计数。结果如图 7.15 所示。

图 7.15. 在最多食谱中出现的最常见前 10 个成分

图片

图 7.15 中的前 10 名列表中的大多数内容可能不会令人惊讶。鉴于盐在我们的列表中高居榜首,我们不应该对在大多数西方国家血管疾病成为头号杀手感到震惊。现在,另一个有趣的问题浮现在脑海中:从不同角度来看,哪些食谱需要最多的成分?

from py2neo import Graph, Node, Relationship
graph_db = Graph("http://neo4j:neo4ja@localhost:7474/db/data/")
graph_db.cypher.execute("
    MATCH (REC:Recipe)-[r:Contains]->(ING:Ingredient) WITH REC, count(r) AS num
    RETURN REC.Name as Name, num ORDER BY num DESC LIMIT 10;")

查询几乎与之前相同,但我们不是返回成分,而是要求食谱。结果如图 7.16 所示。

图 7.16. 可以使用最多不同成分制作的前 10 道菜

图片 7.16

现在这可能是一个令人惊讶的景象。意大利面肉酱听起来几乎不像需要 59 种配料的菜品。让我们仔细看看为意大利面肉酱列出的配料。

from py2neo import Graph, Node, Relationship
graph_db = Graph("http://neo4j:neo4ja@localhost:7474/db/data/")
graph_db.cypher.execute("MATCH (REC1:Recipe{Name:'Spaghetti Bolognese'})-
     [r:Contains]->(ING:Ingredient) RETURN REC1.Name, ING.Name;")

Cypher 查询只是列出了与意大利面肉酱链接的配料。图 7.17 显示了在 Neo4j 网络界面中的结果。

图 7.17. 意大利面肉酱可能的配料

图片 7.17

让我们回忆一下在 Elasticsearch 中索引数据时我们做出的评论。对意大利面肉酱的快速 Elasticsearch 搜索显示它出现了多次,所有这些实例都被用来将配料与意大利面肉酱作为食谱链接起来。我们不必将意大利面肉酱视为单一的食谱,而更多地将其视为人们创造自己的“意大利面肉酱”的方式集合。这为查看这些数据提供了一种有趣的方法。人们可以用番茄酱、红酒和鸡肉来制作自己的版本,甚至可能加入汤。由于“意大利面肉酱”这道菜如此开放,难怪这么多人喜欢它。

意大利面肉酱故事是一个有趣的分心点,但并非我们此行的目的。现在是时候向我们的美食家“Ragnar”推荐菜品了。

7.3.5. 第 5 步:数据建模

在我们对数据的了解略有丰富之后,我们达到了这个练习的目标:推荐。

为了这个目的,我们引入了一个我们称之为“Ragnar”的用户,他喜欢几道菜品。在我们期望它建议新菜品之前,我们需要让我们的图数据库吸收这些新信息。因此,现在让我们创建 Ragnar 的用户节点和一些食谱偏好。

列表 7.6. 在 Neo4j 图数据库中创建喜欢某些食谱的用户节点

图片 7.18

图片 7.6

在列表 7.6 中,我们的美食家 Ragnar 及其对几道菜品的偏好被添加到数据库中。如果我们选择 Ragnar 在 Neo4j 界面中,我们会得到图 7.18。这个 Cypher 查询如下:

图 7.18. 用户 Ragnar 喜欢几道菜品

图片 7.18

MATCH (U:User)-[r:Likes]->(REC:Recipe) RETURN U,REC LIMIT 25

图 7.18 中没有惊喜:很多人喜欢意大利面肉酱,我们的斯堪的纳维亚美食家 Ragnar 也是如此。

对于我们想要构建的简单推荐引擎,我们剩下要做的就是请求图数据库给出基于配料的最近似菜品。这再次是一个基本的推荐系统方法,因为它没有考虑到诸如

  • 对某种配料或菜品的厌恶。

  • 喜欢或不喜欢程度。10 分制的评分而不是简单的喜欢或不喜欢可能更有区别。

  • 菜品中某种配料的含量。

  • 某种配料在味道中显现的阈值。某些配料,如辣椒,在较小剂量下对味道的影响比其他配料要大。

  • 食物过敏。虽然这将在带有某些成分的菜肴的喜欢或不喜欢中隐式建模,但食物过敏可能非常重要,以至于一次错误可能导致致命。应避免过敏原,以覆盖整个推荐系统。

  • 还有更多的事情值得你思考。

可能会有些惊讶,但一个单一的 Cypher 命令就足够了。

from py2neo import Graph, Node, Relationship
graph_db = Graph("http://neo4j:neo4ja@localhost:7474/db/data/")
graph_db.cypher.execute("
   MATCH (USR1:User{Name:'Ragnar'})-[l1:Likes]->(REC1:Recipe),
         (REC1)-[c1:Contains]->(ING1:Ingredient)
      WITH  ING1,REC1 MATCH (REC2:Recipe)-[c2:Contains]->(ING1:Ingredient)
      WHERE REC1 <> REC2
   RETURN REC2.Name,count(ING1) AS IngCount ORDER BY IngCount DESC LIMIT 20;")

首先收集 Ragnar 喜欢的所有食谱。然后使用它们的成分来检索共享这些成分的所有其他菜肴。接着对每个关联菜肴的成分进行计数并按从许多常见成分到较少成分的顺序进行排名。只保留前 20 道菜肴;这导致了图 7.19 的表格。

图 7.19. 食谱推荐的输出;用户可能喜欢的顶级 20 道菜肴

从图 7.19 我们可以推断,是时候让 Ragnar 尝试意大利面和肉丸了,这是一道由迪士尼动画电影《小姐与流浪汉》使不朽的菜肴。这听起来确实是为那些喜欢含有面条和肉丸的菜肴的人的一个很好的推荐,但正如我们可以通过成分计数看到的那样,还有更多成分支持这个建议。为了给我们一点提示,我们可以通过一个总结的图形图像展示偏好的菜肴、顶级推荐以及它们重叠的一些成分。

7.3.6. 步骤 6:展示

Neo4j 的 Web 界面允许我们运行模型并检索一个漂亮的图形,该图形总结了推荐背后的部分逻辑。它显示了推荐菜肴如何通过成分与偏好菜肴相连接。这显示在图 7.20 中,并且是我们案例研究的最终输出。

图 7.20. 用户偏好的菜肴与基于重叠成分子集推荐的顶级 10 道菜肴之间的相互关联

通过这张美丽的图形图像,我们可以得出结论,Ragnar 有一些美味的菜肴可以期待。别忘了亲自尝试推荐系统,通过插入你自己的偏好来体验。

7.4. 总结

在本章中,你学习了

  • 图数据库在遇到实体之间的关系与实体本身一样重要的数据时特别有用。与其他 NoSQL 数据库相比,它们可以处理最大的复杂性,但数据量最少。

  • 图形数据结构由两个主要组件组成:

    • 节点 —这些是实体本身。在我们的案例研究中,这些是食谱和成分。

    • —实体之间的关系。关系,像节点一样,可以是各种类型(例如“包含”、“喜欢”、“去过”)并且可以有自己的特定属性,如名称、权重或其他度量。

  • 我们研究了目前最受欢迎的图数据库 Neo4j。有关如何安装它的说明,您可以参考附录 B。我们还研究了如何向 Neo4j 添加数据,如何使用 Cypher 查询它,以及如何访问其 Web 界面。

  • Cypher 是 Neo4j 数据库特定的查询语言,我们查看了一些示例。我们还在案例研究中将其作为我们菜谱推荐系统的一部分使用。

  • 在本章的案例研究中,我们使用了 Elasticsearch 来清理大量的食谱数据。然后我们将这些数据转换成包含食谱和成分的 Neo4j 数据库。案例研究的目标是根据用户之前对其他菜谱的兴趣来推荐菜谱。为此,我们利用了通过成分连接的食谱。py2neo 库使我们能够从 Python 与 Neo4j 服务器进行通信。

  • 结果表明,图数据库不仅对实现推荐系统很有用,而且对于数据探索也非常有用。我们发现的一件事是,Spaghetti Bolognese 食谱的多样性(从成分角度来看)。

  • 我们使用 Neo4j 的 Web 界面来创建了一个可视化表示,展示了我们如何通过成分节点从菜谱偏好到菜谱推荐的转换过程。

第八章。文本挖掘和文本分析

本章涵盖

  • 理解文本挖掘的重要性

  • 介绍文本挖掘中的最重要的概念

  • 完成文本挖掘项目

世界上大部分人类记录的信息都是以书面文字的形式存在的。我们从婴儿时期就开始学习阅读和写作,以便通过写作表达自己,并学习他人的知识、想法和感受。我们在阅读或撰写电子邮件、博客、短信或这本书时都会使用这项技能,因此对大多数人来说,书面语言是自然而然的。企业相信,人们产生的文本中可以找到很多价值,这是正确的,因为这些文本包含了关于这些人喜欢什么、不喜欢什么、知道什么或想了解什么、渴望和需求、他们的当前健康状况或情绪等等的信息。其中许多内容对公司或研究人员来说都可能是相关的,但没有人能够独自阅读和解释这些海量的书面材料。再次,我们需要求助于计算机来完成这项工作。

然而,不幸的是,自然语言对计算机来说并不像对人类那样“自然”。从意义中提取并过滤掉不重要的信息仍然是人类比任何机器都擅长的事情。幸运的是,数据科学家可以应用特定的文本挖掘和文本分析技术,从他们自己可能需要数百年时间才能阅读的文本堆中找到相关信息。

文本挖掘文本分析是一门结合语言科学和计算机科学、统计和机器学习技术的学科。文本挖掘用于分析文本并将它们转化为更结构化的形式。然后,它尝试从这个结构化形式中提取见解。例如,在分析警察报告中的犯罪时,文本挖掘可以帮助你从报告中识别人员、地点和犯罪类型。然后,这种新的结构被用来深入了解犯罪的演变。参见图 8.1。

图 8.1. 在文本分析中,(通常)第一个挑战是结构化输入文本;然后它可以被彻底分析。

虽然语言并不仅限于自然语言,但本章的重点将放在自然语言处理(NLP)上。非自然语言的例子包括机器日志、数学和莫尔斯电码。技术上讲,甚至世界语、克林贡语和龙语也不属于自然语言的范畴,因为它们是人为发明而非随着时间的推移演变而来的;它们对我们来说并非“自然”。然而,这些最后提到的语言适合自然交流(口语、写作);它们拥有像所有自然语言一样的语法和词汇,并且可以使用相同的文本挖掘技术。

8.1. 真实世界中的文本挖掘

在你的日常生活中,你已经遇到了文本挖掘和自然语言应用。自动补全和拼写检查器在发送电子邮件或短信之前会不断分析你输入的文本。当 Facebook 使用名为“命名实体识别”的技术自动补全你的状态时,它会帮助你完成这个过程,尽管这将是他们技能库中的一个组成部分。目标不仅仅是检测你正在输入一个名词,而是猜测你指的是一个人并识别可能是谁。命名实体识别的另一个例子在图 8.2 中展示。谷歌知道切尔西是一个足球俱乐部,但在被问及一个人时,它的回应则不同。

图 8.2。对查询“切尔西是谁?”和“切尔西是什么?”的不同回答表明,谷歌使用文本挖掘技术来回答这些问题。

图片

谷歌在呈现查询结果时使用了多种类型的文本挖掘。当有人说“切尔西”时,你脑海中首先想到的是什么?切尔西可能指很多事物:一个人;一个足球俱乐部;纽约或伦敦的一个社区;一个食品市场;一个花展;等等。谷歌知道这一点,并在回答“切尔西是谁?”和“切尔西是什么?”这两个问题时给出不同的答案。为了提供最相关的答案,谷歌必须做(包括其他事情)以下所有事情:

  • 对它收集的所有文档进行命名实体预处理

  • 执行语言识别

  • 检测你指的是哪种实体

  • 将查询与结果进行匹配

  • 检测要返回的内容类型(PDF,成人敏感)

这个例子表明,文本挖掘不仅仅是关于文本的直接含义,还涉及到元属性,如语言和文档类型。

谷歌在回答查询时不仅使用文本挖掘,还用于其他许多方面。除了保护其 Gmail 用户免受垃圾邮件的侵扰外,它还将电子邮件分为不同的类别,如社交、更新和论坛,如图 8.3 所示。

图 8.3。根据内容和来源,可以自动根据内容将电子邮件分类。

图片

当你结合文本与其他逻辑和数学时,你可以在回答简单问题之外走得更远。

这使得基于自然语言查询的自动推理引擎的创建成为可能。图 8.4 展示了计算知识引擎“Wolfram Alpha”如何使用文本挖掘和自动推理来回答问题“美国人口是否比中国多?”

图 8.4。Wolfram Alpha 引擎使用文本挖掘和逻辑推理来回答问题。

图片

如果这还不够令人印象深刻,IBM Watson 在 2011 年让许多人惊讶,当时机器在一场Jeopardy游戏中对抗两名人类玩家。Jeopardy是一档美国问答节目,人们收到问题的答案,并因猜测出正确的问题而得分。参见图 8.5。

图 8.5。IBM Watson 在Jeopardy游戏中战胜人类玩家。

可以肯定地说,这一轮人工智能获胜。IBM Watson 是一个认知引擎,它可以解释自然语言,并基于广泛的知识库回答问题。

文本挖掘有许多应用,包括但不限于以下内容:

  • 实体识别

  • 抄袭检测

  • 主题识别

  • 文本聚类

  • 翻译

  • 自动文本摘要

  • 欺诈检测

  • 邮件过滤

  • 情感分析

文本挖掘很有用,但它困难吗?很抱歉让你失望:是的,它很困难。

当查看 Wolfram Alpha 和 IBM Watson 的示例时,你可能会得到文本挖掘很容易的印象。遗憾的是,并非如此。实际上,文本挖掘是一项复杂的任务,甚至许多看似简单的事情也无法令人满意地完成。例如,考虑猜测正确地址的任务。图 8.6 显示了如何难以确信地返回精确的结果,以及当搜索“Springfield”时,Google Maps 如何提示你提供更多信息。在这种情况下,如果没有额外的上下文,人类也不会做得更好,但这个歧义是你在文本挖掘应用中面临的问题之一。

图 8.6。由于查询“Springfield”的歧义,Google Maps 要求你提供更多信息。

另一个问题是对一个词的拼写错误不同的(正确的)拼写形式。以下是对纽约的三个引用:“NY”,“Neww York”,和“New York”。对人类来说,很容易看出它们都指的是纽约市。由于我们的大脑解释文本的方式,理解带有拼写错误的文本对我们来说很自然;人们甚至可能没有注意到它们。但对于计算机来说,除非我们使用算法告诉它它们指的是同一个实体,否则这些是无关的字符串。相关问题是同义词和代词的使用。尝试在以下句子中为代词“她”分配正确的人:“John 在他第一次见到 Marleen 的父母时给了 Marleen 的父母花。她对此举感到非常高兴。”这很容易,对吗?不对,对计算机来说不是这样。

我们可以轻松解决许多类似的问题,但它们通常对机器来说很难。我们可以训练算法在定义良好的范围内解决特定问题,但更通用的算法在所有情况下都是另一回事。例如,我们可以教会计算机从文本中识别和检索美国账户号码,但这并不能很好地推广到其他国家的账户号码。

语言算法对语言使用的上下文也很敏感,即使语言本身保持不变。英语模型对阿拉伯语不起作用,反之亦然,但即使我们坚持使用英语——为 Twitter 数据训练的算法在法律文本上可能表现不佳。当我们继续进行章节案例研究时,请记住这一点:在文本挖掘中没有完美的一劳永逸的解决方案。

8.2. 文本挖掘技术

在我们即将进行的案例研究中,我们将解决文本分类问题:自动将未分类的文本分类到特定的类别中。要从原始文本数据到达我们的最终目的地,我们需要一些数据挖掘技术,这些技术需要背景信息以便我们有效地使用它们。文本挖掘中的第一个重要概念是“词袋”。

8.2.1. 词袋

为了构建我们的分类模型,我们将采用词袋方法。词袋是结构化文本数据最简单的方式:每个文档都被转换成一个词向量。如果一个特定的词出现在向量中,它被标记为“True”;其他的是“False”。图 8.7 展示了这种方法的简化示例,假设只有两个文档:一个是关于电视剧权力的游戏的,另一个是关于数据科学的。这两个词向量一起形成了文档-词矩阵。文档-词矩阵为每个术语都有一个列,为每个文档都有一个行。值由你决定。在本章中,我们将使用二进制:术语是否存在?是或否。

图 8.7. 文本通过标记每个词(术语)在文档中存在时为“True”,不存在时为“False”来转换为词袋。

图片

图 8.7 中的示例确实给你一个关于我们将需要开始文本分析的结构化数据的概念,但它严重简化了:没有过滤掉任何单词,也没有应用词干提取(我们稍后会讨论)。一个大型的语料库可以包含成千上万的独特单词。如果所有这些单词都必须这样标记而没有过滤,很容易看出我们可能会得到大量数据。图 8.7 中显示的二进制编码词袋只是结构化数据的一种方式;其他技术也存在。

词频-逆文档频率(TF-IDF

填充文档-词矩阵的一个著名公式是TF-IDF,即词频乘以逆文档频率。二进制词袋分配 True 或 False(术语是否存在),而简单频率计算术语出现的次数。TF-IDF 稍微复杂一些,考虑了术语在文档中出现的次数(TF)。TF 可以是简单的术语计数,二进制计数(True 或 False),或对数缩放的术语计数。这取决于对你来说什么最有效。如果 TF 是词频,TF 的公式如下:

TF = f[t,d]

TF 是术语(t)在文档(d)中的频率(f)。

但 TF-IDF 也考虑了所有其他文档,这是由于逆文档频率(Inverse Document Frequency)的影响。IDF 给出了一个词在整个语料库中普遍程度的概念:文档频率越高,词越常见,而常见的词提供的信息越少。例如,“a”或“the”这样的词不太可能提供关于文本的具体信息。具有对数缩放的 IDF 公式是最常用的 IDF 形式:

IDF = log(N/|{d ε D:t ε d}|)

其中N是语料库中文档的总数,而|{d ε D:t ε d}|是包含术语(t)的文档(d)的数量。

TF-IDF 分数对某个词的描述是:这个词在区分语料库中的文档与其他文档时有多重要?因此,TF-IDF 的公式是

我们不会使用 TF-IDF,但在设置您在文本挖掘中的下一步时,这应该是您将遇到的第一件事之一。TF-IDF 也是 Elasticsearch 在第六章幕后所使用的。如果您想使用 TF-IDF 进行文本分析,这是一个好方法;将文本挖掘留给专门的软件,如 SOLR 或 Elasticsearch,并从那里获取文本分析的文档/术语矩阵。

在到达实际的词袋模型之前,许多其他数据操作步骤都会发生:

  • 分词 —文本被切分成称为“标记”或“术语”的片段。这些标记是您在模型中将使用的最基本的信息单元。术语通常是单词,但这不是必需的。整个句子也可以用于分析。我们将使用单词:只包含一个单词的术语。然而,通常包括二元组(每个标记两个单词)或三元组(每个标记三个单词)以捕捉额外的意义并提高模型的表现力。但这也会带来成本,因为您通过在方程中包含二元组和/或三元组来构建更大的术语向量。

  • 停用词过滤 —每种语言都有一些词在文本分析中价值不大,因为它们被使用得非常频繁。NLTK 附带了一个简短的英语停用词列表,我们可以过滤掉这些低信息量的停用词。如果文本被切分成单词,通常有道理从单词向量中去除这些低信息的停用词。

  • 小写化 —大写字母开头的单词出现在句子的开头,其他单词因为它们是专有名词或形容词。在我们的术语矩阵中,我们不会从这种区分中获得额外的价值,因此所有术语都将设置为小写。

另一种数据准备技术是词干提取。这一点需要更详细的解释。

8.2.2. 词干提取和词形还原

词干提取 是将单词还原到其词根形式的过程;这样你最终得到的数据变异性更小。如果单词具有相似的含义但书写不同,例如一个单词是复数形式,这就有意义了。词干提取试图通过截断单词的一部分来实现统一。例如,“planes”和“plane”都变成了“plane”。

另一种技术,称为 词干提取,具有相同的目标,但以更语法敏感的方式进行。例如,虽然词干提取和词干提取都会将“cars”还原为“car”,但词干提取还可以将动词的变形还原为非变形形式,例如将“are”还原为“be”。你使用哪一种取决于你的情况,词干提取在很大程度上受益于词性标注(词性标注)。词性标注 是将语法标签分配给句子中每个部分的过程。你可能在学校作为语言练习手动执行过这个操作。以句子“Game of Thrones is a television series.”为例。如果我们对它进行词性标注,我们得到

  • | VBZ | 动词,第三人称单数现在时 | WDT | 疑问限定词 |

NN 是名词,IN 是介词,NNS 是名词的复数形式,VBZ 是第三人称单数动词,而 DT 是限定词。表 8.1 包含了完整的列表。

表 8.1. 所有词性标注的列表
Tag 标签含义 Tag 标签含义
CC 并列连词 CD 基数词
DT 限定词 EX 存在
({“game”:”NN”},{“of”:”IN”},{“thrones”:”NNS”},{“is”:”VBZ”},{“a”:”DT”},{“television”:”NN”},{“series”:”NN”})
JJ 形容词 JJR 形容词,比较级
JJS 形容词,最高级 LS 列表项标记
MD 情态动词 NN 名词,单数或复数
NNS 名词,复数 NNP 名词,单数
NNPS 名词,复数 PDT 预定冠词
POS 物主后缀 PRP 人称代词
PRP$ 物主代词 RB 副词
RBR 副词,比较级 RBS 副词,最高级
VBN 动词,过去分词 VBP 动词,非第三人称单数现在时
UH 呼语 VB 动词,基本形式
VBD 动词,过去式 VBG 动词,动名词或现在分词
FW 外来词 IN 介词或从属连词
我们现在知道我们将使用来做数据清洗和操作(文本挖掘)的最重要的事情。对于我们的文本分析,让我们将决策树分类器添加到我们的工具箱中。
WP 疑问代词 WP$ 物主疑问代词
RP 动词小品词 SYM 符号

词性标注是句子分词而不是单词分词的一个用例。在完成词性标注后,你仍然可以继续进行单词分词,但词性标注器需要整个句子。将词性标注和词干提取结合起来,可能比仅使用词干提取提供更干净的数据。为了简化,我们在案例研究中将坚持使用词干提取,但请将此视为详细阐述练习的机会。

| WRB | 疑问副词 |   |   |

8.2.3. 决策树分类器

我们案例研究的数据分析部分也将保持简单。我们将测试一个朴素贝叶斯分类器和决策树分类器。如第三章中所述,朴素贝叶斯分类器之所以被称为朴素贝叶斯,是因为它认为每个输入变量都是独立于所有其他变量的,这在文本挖掘中尤其天真。以“数据科学”、“数据分析”或“权力的游戏”的简单例子来说,如果我们对我们的数据使用单语元进行切割,我们会得到以下单独的变量(如果我们忽略词干提取等):“数据”、“科学”、“分析”、“游戏”、“of”和“thrones”。显然,链接将会丢失。这可以通过创建双语元(数据科学、数据分析)和三元组(权力的游戏)来克服。

然而,决策树分类器并不认为变量之间是相互独立的,并且会积极创建交互变量。一个交互变量是结合其他变量的变量。例如,“数据”和“科学”本身可能是很好的预测因子,但这两者在同一文本中同时出现可能具有其自身的价值。桶则相反。不是将两个变量合并,而是将一个变量分割成多个新的变量。这对于数值变量来说是有意义的。图 8.8 展示了决策树可能的样子以及你可以找到交互和桶化的地方。

图 8.8. 虚拟决策树模型。决策树自动创建桶并假设输入变量之间存在交互。

图 8.8

与朴素贝叶斯假设所有输入变量相互独立不同,决策树是基于相互依赖的假设构建的。但它是如何构建这个结构的呢?决策树有几个可能的准则可以用来分割成分支并决定哪些变量比其他变量更重要(更接近树的根部)。我们在 NLTK 决策树分类器中使用的准则是“信息增益”。为了理解信息增益,我们首先需要看看熵。是衡量不可预测性或混沌的度量。一个简单的例子就是婴儿的性别。当女性怀孕时,胎儿的性别可能是男性或女性,但我们不知道具体是哪一个。如果你要猜测,你有 50%的机会猜对(考虑到性别分布并不完全均匀)。然而,在怀孕期间,你有机会进行超声波检查以确定胎儿的性别。超声波检查永远不会 100%确定,但随着胎儿发育的深入,其准确性会越来越高。这种准确性的提升,或称信息增益,是因为不确定性或熵降低了。假设怀孕 12 周时进行超声波检查,在确定婴儿性别方面有 90%的准确性。仍然存在 10%的不确定性,但超声波确实将不确定性从 50%降低到了 10%。这是一个相当好的判别器。决策树遵循同样的原则,如图 8.9 所示。

图 8.9. 具有一个变量的决策树:医生通过观察怀孕期间的超声波检查得出的结论。胎儿为女性的概率是多少?

图片

如果另一个性别测试具有更强的预测能力,它可能成为树的根,而超声波测试则位于树枝上,这个过程可以一直持续到我们用尽变量或观察数据。我们可能会用尽观察数据,因为在每个分支分叉时,我们也会分割输入数据。这是决策树的一个大弱点,因为在树的叶级,如果剩余的观察数据太少,鲁棒性就会崩溃;决策树开始过度拟合数据。过度拟合使得模型会将随机性误认为是真实的关联。为了对抗这一点,决策树会被剪枝:其无意义的分支被排除在最终模型之外。

现在我们已经了解了最重要的新技巧,让我们深入到案例研究中。

8.3. 案例研究:分类 Reddit 帖子

虽然文本挖掘有许多应用,但在本章的案例研究中,我们专注于文档分类。正如本章前面所指出的,这正是谷歌在将您的电子邮件分类或尝试区分垃圾邮件和常规邮件时所做的事情。它也被客户服务中心广泛使用,以处理客户提出的问题或投诉:书面投诉首先通过主题检测过滤器,以便可以分配给正确的人员处理。文档分类也是社交媒体监控系统的一项必备功能。被监控的推文、论坛或 Facebook 帖子、报纸文章以及许多其他互联网资源都被分配了主题标签。这样,它们可以在报告中重复使用。情感分析是文本分类的一种特定类型:帖子的作者对某事是消极的、积极的还是中性的?这个“某事”可以通过实体识别来识别。

在这个案例研究中,我们将利用 Reddit 的帖子,Reddit 也被称为自称为“互联网首页”的网站,并尝试训练一个能够区分某人是否在谈论“数据科学”或“权力的游戏”的模型。

最终结果可以是我们的模型演示或一个完整的交互式应用程序。在第九章(kindle_split_017.xhtml#ch09)中,我们将专注于为最终用户构建应用程序,所以现在我们将坚持展示我们的分类模型。

为了实现我们的目标,我们需要尽可能多的帮助和工具,而 Python 一次又一次地准备好提供这些。

8.3.1. 认识自然语言工具包

Python 可能不是地球上执行效率最高的语言,但它有一个成熟的文本挖掘和语言处理包:自然语言工具包 (NLTK)。NLTK 是一组算法、函数和标注作品,将引导您在文本挖掘和自然语言处理的第一步。NLTK 在 nltk.org 上也有出色的文档。然而,NLTK 并不常用于生产级工作,如 scikit-learn 等其他库。

安装 NLTK 及其语料库

使用您喜欢的包安装程序安装 NLTK。如果您使用 Anaconda,它将默认与 Anaconda 设置一起安装。否则,您可以选择“pip”或“easy_install”。完成此操作后,您仍然需要安装包含的模型和语料库,以便使其完全功能。为此,请运行以下 Python 代码:

  • import nltk

  • nltk.download()

根据您的安装,这将为您提供弹出窗口或更多命令行选项。

图 8.10 显示了执行 nltk.download() 命令时出现的弹出窗口。

图 8.10. 选择所有包以完全完成 NLTK 的安装。

图片

如果您喜欢,可以下载所有语料库,但本章我们只会使用“punkt”和“stopwords”。此下载将在本书附带代码中明确提及。

本章提供了两个 IPython 笔记本文件:

  • 数据收集 —包含本章案例研究的数据收集部分。

  • 数据准备和分析 —存储的数据经过数据准备,然后进行数据分析。

所有即将到来的案例研究中的代码都可以在这两个文件中按相同顺序找到,也可以这样运行。此外,还提供了两个可下载的交互式图形:

  • forceGraph.html —表示我们的朴素贝叶斯模型的前 20 个特征

  • Sunburst.html —表示我们的决策树模型的前四个分支

要打开这两个 HTML 页面,需要一个 HTTP 服务器,您可以使用 Python 和命令窗口来获取:

  • 打开一个命令窗口(Linux、Windows,随便您喜欢)。

  • 移动到包含 HTML 文件及其 JSON 数据文件的文件夹:decisionTreeData.json 用于 sunburst 图和 NaiveBayesData.json 用于 force 图。重要的是 HTML 文件必须与它们的数据文件位于同一位置,否则您将不得不更改 HTML 文件中的 JavaScript。

  • 使用以下命令创建一个 Python HTTP 服务器:python –m Simple-HTTPServer 8000

  • 打开浏览器并转到 localhost:8000;在这里,您可以像图 8.11 所示选择 HTML 文件。

    图 8.11. Python HTTP 服务器提供本章的输出

本章我们将使用的 Python 包:

  • NLTK —用于文本挖掘

  • PRAW —允许从 Reddit 下载帖子

  • SQLite3 —使我们能够以 SQLite 格式存储数据

  • Matplotlib —用于数据可视化的绘图库

在继续之前,请确保安装所有必要的库和语料库。然而,在我们深入行动之前,让我们看看我们将采取的步骤来实现我们的目标:创建一个主题分类模型。

8.3.2. 数据科学流程概述和第 1 步:研究目标

为了解决这个文本挖掘练习,我们再次利用数据科学流程。图 8.12 显示了将数据科学流程应用于我们的 Reddit 分类案例。

图 8.12. 将数据科学流程概述应用于 Reddit 主题分类案例研究

在此阶段,图 8.12 中描绘的所有元素可能并不都很有意义,本章的其余部分将致力于在实践中解决这个问题,我们朝着我们的研究目标前进:创建一个能够区分关于“数据科学”的帖子与关于“权力的游戏”的帖子的分类模型。无需多言,让我们去获取我们的数据。

8.3.3. 第 2 步:数据检索

我们将使用 Reddit 数据来处理这个案例,对于那些不熟悉 Reddit 的人,请花时间熟悉其概念,请访问www.reddit.com

Reddit 自称为“互联网首页”,因为用户可以发布他们觉得有趣或在网上找到的内容,只有那些被许多人认为有趣的内容才会被展示在其首页上作为“热门”。可以说 Reddit 给出了互联网上趋势事物的概述。任何用户都可以在预定义的分类中发布,这个分类被称为“subreddit”。当一个帖子发布后,其他用户可以对其发表评论,如果他们喜欢内容,可以对其进行点赞;如果不喜欢,可以对其进行踩。因为帖子总是属于某个 subreddit,所以当我们连接到 Reddit API 获取数据时,我们就有这样的元数据可以利用。我们实际上是在获取标记数据,因为我们假设 subreddit “gameofthrones” 中的帖子与“gameofthrones”有关。

为了获取我们的数据,我们使用了官方的 Reddit Python API 库,称为 PRAW。一旦我们获取了所需的数据,我们就会将其存储在一个轻量级的类似数据库的文件中,称为 SQLite。SQLite 对于存储少量数据非常理想,因为它不需要任何设置即可使用,并且可以像任何常规的关系数据库一样响应 SQL 查询。任何其他数据存储介质都可以;如果您更喜欢 Oracle 或 Postgres 数据库,Python 有一个出色的库可以与之交互,而无需编写 SQL。SQLAlchemy 也可以用于 SQLite 文件。图 8.13 显示了数据科学过程中的数据检索步骤。

图 8.13. Reddit 主题分类案例的数据科学过程数据检索步骤

打开您喜欢的 Python 解释器;现在是行动的时候了,如列表 8.1 所示。首先我们需要从 Reddit 网站收集我们的数据。如果您还没有安装,请在运行以下脚本之前使用 pip install prawconda install praw(Anaconda)。

注意

步骤 2 的代码也可以在 IPython 文件中找到,位于“第八章 数据收集”部分。它可在本书的下载区获取。

列表 8.1. 设置 SQLite 数据库和 Reddit API 客户端

让我们先导入必要的库。

现在我们已经可以访问 SQLite3 和 PRAW 功能,我们需要为即将接收的数据准备我们的本地数据库。通过定义一个到 SQLite 文件的连接,如果它还不存在,我们会自动创建它。然后我们定义一个数据游标,它可以执行任何 SQL 语句,因此我们使用它来预定义数据库的结构。数据库将包含两个表:主题表包含 Reddit 主题,类似于某人在论坛上发起一个新的帖子,第二个表包含评论,并通过“topicID”列与主题表相关联。这两个表之间存在一对一(主题表)到多对一(评论表)的关系。对于案例研究,我们将限制自己只使用主题表,但数据收集将包括两者,因为这允许你在愿意的情况下实验这些额外数据。为了磨练你的文本挖掘技能,你可以对主题评论进行情感分析,找出哪些主题收到了负面或正面的评论。然后你可以将此与本章末尾我们将产生的模型特征相关联。

我们需要创建一个 PRAW 客户端来获取数据。每个 subreddit 都可以通过其名称来识别,我们感兴趣的是“datascience”和“gameofthrones”。限制表示我们从 Reddit 中抽取的最大主题数量(帖子,不是评论)。一千也是 API 在任何给定请求中允许我们获取的最大数量,尽管我们可以在人们发布新内容后稍后请求更多。实际上,我们可以定期运行 API 请求并随着时间的推移收集数据。虽然在任何给定时间你只能限制为一千篇帖子,但没有任何东西阻止你在几个月的时间里扩大你自己的数据库。值得注意的是,以下脚本可能需要大约一个小时才能完成。如果你不想等待,请随意继续并使用可下载的 SQLite 文件。此外,如果你现在运行它,你不太可能得到与最初运行以创建本章中显示的输出完全相同的输出。

让我们看看我们的数据检索函数,如下所示。

列表 8.2. Reddit 数据在 SQLite 中的检索和存储

图片

图片

prawGetData()函数检索其 subreddit 中的“热门”主题,将其追加到数组中,然后获取所有相关评论。这个过程会一直进行,直到达到一千个主题或没有更多主题可以检索,并且所有内容都存储在 SQLite 数据库中。打印语句用于告知你收集一千个主题的进度。我们剩下的工作就是为每个 subreddit 执行该函数。

如果你希望这个分析包含超过两个 subreddits,这只是一个在 subreddits 数组中添加额外类别的问题。

收集数据后,我们就可以继续进行数据准备了。

8.3.4. 第 3 步:数据准备

总是来说,数据准备是获取正确结果的最关键步骤。对于文本挖掘来说,这一点尤为重要,因为我们甚至没有从结构化数据开始。

接下来的代码作为 IPython 文件在线提供,文件名为“第八章 数据准备和分析。”让我们首先导入所需的库并准备 SQLite 数据库,如下面的列表所示。

列表 8.3. 文本挖掘、库、语料库依赖项和 SQLite 数据库连接

图片

如果你还没有下载完整的 NLTK 语料库,我们现在将下载我们将要使用的那部分。如果你已经下载了它,脚本将检测你的语料库是否是最新的。

我们的数据仍然存储在 Reddit SQLite 文件中,所以让我们创建一个连接到它的连接。

在探索我们的数据之前,我们就知道至少有两件事要做来清理数据:停用词过滤和转换为小写。

一个通用的词过滤函数将帮助我们过滤掉不干净的部分。让我们在下面的列表中创建一个。

列表 8.4. 单词过滤和转换为小写的函数

图片

英语停用词将首先离开我们的数据。下面的代码将提供这些停用词:

stopwords = nltk.corpus.stopwords.words('english')
print stopwords

图 8.14 显示了 NLTK 中的英语停用词列表。

图 8.14. NLTK 中的英语停用词列表

图片

在所有必要的组件都到位之后,让我们看看下面的列表中我们的第一个数据处理函数。

列表 8.5. 第一个数据准备函数和执行

图片

图片

我们的 data_processing() 函数接收一个 SQL 语句并返回文档-词矩阵。它是通过逐个遍历数据(Reddit 主题)并使用词标记化将主题标题和主题正文文本合并成一个单词向量来做到这一点的。一个 标记化器 是一个文本处理脚本,它将文本分割成片段。你有多种不同的方式来标记化文本:你可以将其分割成句子或单词,可以按空格和标点符号分割,或者可以考虑到其他字符,等等。在这里,我们选择了标准的 NLTK 单词标记化器。这个单词标记化器很简单;它所做的只是如果单词之间有空格,就将文本分割成术语。然后我们将向量转换为小写并过滤掉停用词。注意这里的顺序很重要;如果我们在转换为小写之前先过滤停用词,那么句子开头的停用词就不会被过滤掉。例如,在“我喜欢权力的游戏”中,“我”就不会被转换为小写,因此不会被过滤掉。然后我们创建一个单词矩阵(术语-文档矩阵)和一个包含所有单词的列表。注意我们在这里没有过滤重复项;这样我们就可以在数据探索期间创建单词出现频率的直方图。让我们为我们的两个主题类别执行这个函数。

图 8.15 显示了“datascience”类别的第一个词向量。

图 8.15. 首次数据处理尝试后“datascience”类别的第一个词向量

print data['datascience']['wordMatrix'][0]

这确实看起来很杂乱:标点符号被当作单独的术语,而且一些单词甚至没有被分割。进一步的数据探索应该会为我们澄清一些事情。

8.3.5. 步骤 4:数据探索

现在我们已经将所有术语分开,但数据的巨大规模阻碍了我们判断它是否足够干净以供实际使用。通过查看单个向量,我们已经发现了几个问题:一些单词没有被正确分割,向量中包含许多单字符术语。在某些情况下,单字符术语可能是好的主题区分器。例如,经济文本将包含比医学文本更多的$、£和€符号。但在大多数情况下,这些单字符术语是无用的。首先,让我们看看我们术语的频率分布。

wordfreqs_cat1 = nltk.FreqDist(data['datascience']['all_words'])
plt.hist(wordfreqs_cat1.values(), bins = range(10))
plt.show()
wordfreqs_cat2 = nltk.FreqDist(data['gameofthrones']['all_words'])
plt.hist(wordfreqs_cat2.values(), bins = range(20))
plt.show()

通过绘制频率分布直方图(图 8.16),我们很快注意到我们的大部分术语只出现在单个文档中。

图 8.16. 这个词频直方图显示了“数据科学”和“权力的游戏”术语矩阵中都有超过 3,000 个只出现一次的术语。

像这样的单次出现术语被称为单现词,从模型的角度来看它们是无用的,因为一个特征的单一出现永远不足以构建一个可靠的模型。这对我们来说是个好消息;去掉这些单现词将显著减少我们的数据量,而不会损害我们最终构建的模型。让我们看看这些单次出现术语中的几个。

print wordfreqs_cat1.hapaxes()
print wordfreqs_cat2.hapaxes()

图 8.17 中看到的术语是有意义的,如果我们有更多的数据,它们可能会更频繁地出现。

图 8.17. “数据科学”和“权力的游戏”单次出现术语(单现词)

print wordfreqs_cat1.hapaxes()
print wordfreqs_cat2.hapaxes()

许多这些术语是其他有用的单词的错误拼写,例如:Jaimie 是 Jaime(兰尼斯特家族),Milisandre 应该是 Melisandre,等等。一个不错的《权力的游戏》专用同义词典可以帮助我们使用模糊搜索算法找到并替换这些错误拼写。这证明,如果你愿意,文本挖掘中的数据清洗可以无限期地进行;保持努力和回报的平衡在这里至关重要。

现在我们来看看最常见的单词。

print wordfreqs_cat1.most_common(20)
print wordfreqs_cat2.most_common(20)

图 8.18 显示了请求每个类别中最常见的 20 个单词的输出。

图 8.18. “数据科学”和“权力的游戏”帖子中最常见的 20 个单词

现在看起来很有希望:一些常见的单词似乎与它们的主题相关。像“数据”、“科学”和“季节”这样的单词很可能是好的区分器。另一个需要注意的重要事情是单字符术语(如“。”和“,”)的丰富性;我们将去掉这些。

带着这些额外的知识,让我们修改我们的数据准备脚本。

8.3.6。步骤 3 回顾:数据准备调整

这短暂的探索已经引起了我们对几个可以改进文本的明显调整的注意。另一个重要的一点是对术语进行词干提取。

下面的列表显示了一个简单的称为“snowball 词干提取”的词干提取算法。这些 snowball 词干提取器可以是语言特定的,所以我们将使用英语版本;然而,它也支持许多语言。

列表 8.6。数据探索后修订的 Reddit 数据处理

图片

图片

注意自上次data_processing()函数以来的变化。我们的分词器现在是一个正则表达式分词器。正则表达式不是本书的内容,通常被认为很难掌握,但这个简单的正则表达式所做的只是将文本切分成单词。对于单词,任何字母数字组合都是允许的(\w),因此不再有特殊字符或标点符号。我们还应用了词干提取器并移除了一个额外的停用词列表。最后,所有单次出现的词都被移除了,因为一切都需要先进行词干提取。让我们再次运行我们的数据准备。

如果我们像以前一样进行相同的探索性分析,我们会发现它更有意义,而且我们没有更多的单次出现词。

print wordfreqs_cat1.hapaxes()
print wordfreqs_cat2.hapaxes()

让我们再次取每个类别的前 20 个单词(参见图 8.19)。

图 8.19。数据准备后的“数据科学”和“权力的游戏”Reddit 帖子中最常见的 20 个单词

图片

我们可以在图 8.19 中看到数据质量已经显著提高。同时,请注意由于我们应用的词干提取,某些单词被缩短了。例如,“science”和“sciences”变成了“scienc;”,“courses”和“course”变成了“cours,”等等。结果术语不是实际单词,但仍然可以解释。如果您坚持让术语保持为实际单词,那么词形还原可能是正确的选择。

在数据清理过程“完成”(备注:文本挖掘清理练习几乎永远无法完全完成)后,剩下的只是进行一些数据转换,以将数据转换为词袋格式。

首先,让我们给所有数据贴上标签,并为每个类别创建一个包含 100 个观察值的保留样本,如下所示。

列表 8.7。建模前的最终数据转换和数据拆分

图片

图片

保留样本将被用于我们模型的最终测试和混淆矩阵的创建。混淆矩阵是一种检查模型在之前未见过的数据上表现如何的方法。矩阵显示了有多少观察值被正确和错误地分类。

在创建或训练和测试数据之前,我们需要采取最后一步:将数据倒入一个词袋格式,其中每个术语根据其在特定帖子中的存在与否被赋予“True”或“False”标签。我们还需要对未标记的保留样本做同样的事情。

我们准备的数据现在包含了每个向量的每个术语,如图 8.20 所示。

图 8.20。一个准备建模的二进制词袋模型是非常稀疏的数据。

图片

print prepared_data[0]

我们创建了一个大但稀疏的矩阵,允许我们在机器上处理不过大时应用第五章中的技术。然而,对于如此小的表格,现在没有必要这样做,我们可以继续将数据随机打乱并分割成训练集和测试集。

虽然你的数据的大部分应该总是用于模型训练,但存在一个最佳分割比例。在这里,我们选择了 3-1 的分割,但你可以自由地尝试不同的比例。你拥有的观察值越多,你在这里的自由度就越大。如果你观察值很少,你需要相对更多地分配给模型训练。我们现在可以继续到最有回报的部分:数据分析。

8.3.7. 步骤 5:数据分析

在我们的分析中,我们将拟合两个分类算法到我们的数据:朴素贝叶斯和决策树。朴素贝叶斯在第三章中解释过,决策树在本章的早期也提到过。

让我们先测试我们的朴素贝叶斯分类器的性能。NLTK 自带了一个分类器,但你可以自由使用来自其他包(如 SciPy)的算法。

classifier  = nltk.NaiveBayesClassifier.train(train)

分类器训练完成后,我们可以使用测试数据来衡量整体准确率。

nltk.classify.accuracy(classifier, test)

测试数据上的准确率估计超过 90%,如图 8.21 所示。[分类准确率]是指正确分类的观察值占总观察值的百分比。不过,请注意,如果你使用了不同的数据,这个结果可能会有所不同。

图 8.21。分类准确率是表示在测试数据上正确分类的观察值所占百分比的度量。

图片

nltk.classify.accuracy(classifier, test)

这是一个很好的数字。现在我们可以放松一下,对吧?不,实际上并不是。让我们再次在 200 个观察值的保留样本上测试它,这次创建一个混淆矩阵。

classified_data = classifier.classify_many(prepared_holdout_data)
cm = nltk.ConfusionMatrix(holdout_data_labels, classified_data)
print cm

图 8.22 中的混淆矩阵显示,97%可能过高,因为我们有 28 个(23 + 5)个错误分类的案例。再次提醒,如果你自己填充了 SQLite 文件,这个结果可能会有所不同。

图 8.22。朴素贝叶斯模型混淆矩阵显示,在 200 个观察值中有 28 个(23 + 5)个被错误分类。

图片

二十八次错误分类意味着我们在保留样本上的准确率为 86%。这需要与随机分配一个新帖子到“datascience”或“gameofthrones”组进行比较。如果我们随机分配,我们可能会期望准确率为 50%,而我们的模型似乎表现得更好。让我们深入挖掘最有信息量的模型特征,看看它如何确定类别。

print(classifier.show_most_informative_features(20))

图 8.23 显示了能够区分两个类别的顶级 20 个术语。

图 8.23. 朴素贝叶斯分类模型中最重要的术语

图片

术语“数据”被赋予了很高的权重,似乎是最重要的指标,用以判断一个主题是否属于数据科学类别。诸如“场景”、“季节”、“国王”、“电视”和“杀戮”等术语是判断主题是权力的游戏而不是数据科学的良好指标。所有这些事情都完全合理,因此模型通过了准确性和合理性检查。

朴素贝叶斯表现良好,因此让我们看看以下列表中的决策树。

列表 8.8. 决策树模型训练和评估

图片

如图 8.24 所示,承诺的准确率是 93%。

图 8.24. 决策树模型准确率

图片

我们现在知道不能仅仅依赖这个单一的测试,因此我们再次转向第二组数据的混淆矩阵,如图图 8.25 所示。

图 8.25. 决策树模型的混淆矩阵

图片

图 8.25 展示了不同的故事。在这些 200 个保留样本的观察中,当帖子是关于权力的游戏时,决策树模型倾向于很好地分类,但当面对数据科学帖子时却表现糟糕。似乎模型更喜欢权力的游戏,你能责怪它吗?让我们看看实际的模型,尽管在这种情况下我们将使用朴素贝叶斯作为我们的最终模型。

print(classifier2.pseudocode(depth=4))

决策树,正如其名所示,具有树状模型,如图图 8.26 所示。

图 8.26. 决策树模型树结构表示

图片

朴素贝叶斯考虑了所有术语并赋予了权重,但决策树模型按顺序通过它们,从根到外部的分支和叶子。 图 8.26 仅显示了前四层,从“数据”这个术语开始。如果帖子中存在“数据”,它总是指数据科学。如果找不到“数据”,它会检查“学习”这个术语,然后继续。这个决策树表现不佳的可能原因是缺乏剪枝。当构建决策树时,它有许多叶子,通常太多。然后,将树剪枝到一定水平以最小化过拟合。决策树的一个大优点是它在构建分支时考虑到的单词之间的隐式交互效应。当多个术语一起创建比单个术语更强的分类时,决策树实际上会优于朴素贝叶斯。我们不会深入探讨这一点,但请考虑这是您可以采取的下一步来改进模型。

现在我们有了两个分类模型,它们让我们了解了两个 subreddits 的内容有何不同。最后一步就是将这个新发现的信息与其他人分享。

8.3.8. 步骤 6:展示和自动化

作为最后一步,我们需要运用我们所学到的知识,要么将其转化为有用的应用,要么向他人展示我们的成果。本书的最后一章讨论了构建交互式应用,因为这本身就是一个项目。现在,我们将满足于以一种优雅的方式传达我们的发现。一张好的图表,或者更好的是,一张交互式图表,可以吸引人的注意力;这是演示文稿的点睛之笔。虽然将数字以这样的方式或最多以条形图的形式表示很容易且诱人,但更进一步可能更好。

例如,为了表示朴素贝叶斯模型,我们可以使用力图(图 8.27),其中气泡和链接的大小表示一个单词与“权力的游戏”或“数据科学”subreddits 的关联强度。注意气泡上的单词通常被截断;记住这是由于我们应用的词干提取造成的。

图 8.27. 包含前 20 个朴素贝叶斯显著术语及其权重的交互式力图

虽然图 8.27 本身是静态的,但你可以通过打开“forceGraph.html”这个 HTML 文件来享受本章前面解释过的 d3.js 力图效果。d3.js 超出了本书的范围,但你不需要对 d3.js 有深入的了解就可以使用它。可以通过对提供的代码进行最小调整来使用一套广泛的示例,这些示例可以在 github.com/mbostock/d3/wiki/Gallery 找到。你所需要的只是常识和一点 JavaScript 知识。力图示例的代码可以在 bl.ocks.org/mbostock/4062045 找到。

我们还可以以一种相当独特的方式表示我们的决策树。我们可以选择一个实际的树图的华丽版本,但下面的太阳图更加独特,同样有趣。

图 8.28 展示了太阳图的最顶层。可以通过点击一个圆环段来放大视图。点击中心圆可以缩小视图。本例的代码可以在 bl.ocks.org/metmajer/5480307 找到。

图 8.28. 从决策树模型的前四个分支创建的太阳图

以一种独特的方式展示你的结果可能是成功项目的关键。如果你无法传达你的成果,而它们对他们来说又是有意义的,人们永远不会欣赏你为达到这些成果所付出的努力。在这里,偶尔使用一些原创的数据可视化肯定有助于这一点。

8.4. 摘要

  • 文本挖掘被广泛用于实体识别、抄袭检测、主题识别、翻译、欺诈检测、垃圾邮件过滤等领域。

  • Python 拥有一个成熟的文本挖掘工具包,称为 NLTK,或自然语言工具包。NLTK 适合玩耍和学习基本操作;然而,对于实际应用,Scikit-learn 通常被认为更“适用于生产”。Scikit-learn 在前几章中被广泛使用。

  • 文本数据准备比数值数据准备更复杂,涉及额外的技术,例如

    • 词干提取 —— 以一种智能的方式截取单词的末尾,以便它可以与这个单词的某些变形或复数形式相匹配。

    • 词形还原 —— 与词干提取类似,它的目的是去除重复的部分,但与词干提取不同的是,它关注单词的意义。

    • 停用词过滤 —— 某些单词出现得太频繁,以至于没有用处,过滤掉它们可以显著提高模型。停用词通常是语料库特定的。

    • 分词 —— 将文本分割成片段。标记可以是单个单词,单词的组合(n-gram),甚至是整个句子。

    • 词性标注 —— 词性标注。有时了解句子中某个单词的功能对于更好地理解它是有用的。

  • 在我们的案例研究中,我们试图区分关于“权力的游戏”的 Reddit 帖子和关于“数据科学”的帖子。在这个努力中,我们尝试了朴素贝叶斯和决策树分类器。朴素贝叶斯假设所有特征都是相互独立的;决策树分类器假设存在依赖关系,允许使用不同的模型。

  • 在我们的例子中,朴素贝叶斯产生了更好的模型,但非常经常决策树分类器做得更好,通常当有更多数据时。

  • 我们通过在新的(但已标记的)数据上应用这两种模型后计算出的混淆矩阵来确定性能差异。

  • 当向其他人展示研究结果时,包括一个有趣的数据可视化可以帮助以难忘的方式传达你的结果。

第九章. 数据可视化到最终用户

本章涵盖

  • 考虑为您的最终用户选择数据可视化方案

  • 设置基本的 Crossfilter MapReduce 应用程序

  • 使用 dc.js 创建仪表板

  • 使用仪表板开发工具进行工作

专注于应用的章节

您会很快注意到,本章与第三章 chapters 3 到第八章 8 的内容确实不同,因为这里的重点在于数据科学流程的第 6 步。更具体地说,我们在这里想要做的是创建一个小型数据科学应用。因此,我们不会遵循数据科学流程的步骤。案例研究中使用的数据只有部分是真实的,但它在数据准备或数据建模阶段充当数据流。享受这次旅程吧。

通常,数据科学家必须将他们的新见解传达给最终用户。结果可以通过多种方式传达:

  • 一次性演示 —研究问题是单次交易,因为由此产生的商业决策将使组织在未来许多年里绑定在某个特定的道路上。以公司投资决策为例:我们是将商品从两个分销中心分发还是只从一个分发?它们需要位于何处才能实现最佳效率? 决策一旦做出,可能直到你退休都不会重复。在这种情况下,结果将以报告的形式交付,演示文稿作为甜点。

  • 数据的新视角 —这里最明显的例子是客户细分。当然,细分本身将通过报告和演示文稿进行传达,但本质上它们形成的是工具,而不是最终结果本身。当发现一个清晰且相关的客户细分时,它可以作为从其派生出来的数据的新维度反馈到数据库中。从那时起,人们可以自己制作报告,例如,向每个客户细分销售了多少产品。

  • 实时仪表板 —有时作为数据科学家的工作并不在你发现你寻找的新信息后结束。你可以将你的信息发送回数据库并完成它。但是,当其他人开始对这个新发现的金块进行报告时,他们可能会错误地解释它并制作出没有意义的报告。作为发现这个新信息的科学家,你必须树立榜样:制作第一个可刷新的报告,这样其他人,主要是记者和 IT 人员,就可以理解它并跟随你的脚步。制作第一个仪表板也是缩短最终用户(他们希望每天使用它)获得洞察力的交付时间的一种方式。这样,至少他们已经有一些东西可以工作了,直到报告部门找到时间在公司报告软件上创建一个永久性的报告。

你可能已经注意到有几个重要因素在起作用:

  • 您支持的是哪种类型的决策?是战略性的还是操作性的?战略决策通常只需要您分析并报告一次,而操作决策则需要定期刷新报告。

  • 您的组织有多大? 在较小的组织中,您将负责整个周期:从数据收集到报告。在较大的组织中,可能有一个报告团队为您制作仪表板。但即使在最后一种情况下,交付一个原型仪表板也可能是有益的,因为它提供了一个示例,并且通常可以缩短交付时间。

虽然整本书都是致力于生成洞察力,但在最后一章中,我们将专注于交付一个操作仪表板。创建一个展示您发现或展示战略洞察力的演示文稿超出了本书的范围。

9.1. 数据可视化选项

您有几种方式将仪表板交付给最终用户。在这里,我们将专注于一种方式,到本章结束时,您将能够自己创建仪表板。

本章的案例是一个拥有几千种药品的医院药房。政府对所有药房出台了一项新规定:所有药品都应检查其对光线的敏感性,并存储在新、特殊的容器中。政府没有提供给药房的实际上是光敏药品的清单。作为数据科学家,这对您来说不是问题,因为每种药品都有包含此信息的患者信息手册。您通过巧妙地使用文本挖掘来提炼信息,并为每种药品分配一个“光敏”或“非光敏”标签。然后,将此信息上传到中央数据库。此外,药房还需要知道需要多少个容器。为此,他们为您提供药房库存数据的访问权限。当您只抽取所需的变量时,数据集在 Excel 中打开时看起来像图 9.1。

图 9.1. 在 Excel 中打开的药房药品数据集:前 10 行库存数据增加了光敏性变量

图片

如您所见,这些信息是一整年股票走势的时间序列数据,因此每种药物在数据集中都有 365 条记录。尽管这个案例研究是现成的,数据集中的药物也是真实的,但这里展示的其他变量的值是随机生成的,因为原始数据是分类的。此外,数据集仅限于 29 种药物,大约有 10,000 多行数据。尽管人们确实会使用 crossfilter.js(一个 JavaScript MapReduce 库)和 dc.js(一个 JavaScript 仪表板库)来创建包含超过一百万行数据的报告,但为了示例,您将使用其中的一小部分。此外,不建议将整个数据库加载到用户的浏览器中;在加载过程中,浏览器会冻结,如果数据量过大,浏览器甚至可能崩溃。通常数据是在服务器上预先计算的,并通过例如 REST 服务请求其部分。

要将此数据转换为实际的仪表板,您有许多选择,您可以在本章后面的部分找到这些工具的简要概述。

在所有选项中,对于这本书,我们决定选择 dc.js,它是 JavaScript MapReduce 库 Crossfilter 和数据可视化库 d3.js 的混合体。Crossfilter 是由 Square Register 公司开发的,该公司处理支付交易;它类似于 PayPal,但其重点是移动端。Square 开发了 Crossfilter,以便他们的客户能够极其快速地对他们的支付历史进行切片和切块。Crossfilter 不是唯一能够进行 Map-Reduce 处理的 JavaScript 库,但它确实能够完成这项工作,是开源的,免费使用,并由一家知名公司(Square)维护。Crossfilter 的替代方案示例包括 Map.js、Meguro 和 Underscore.js。JavaScript 可能不是以数据处理语言而闻名,但这些库确实为浏览器提供了额外的处理能力,以防数据需要在浏览器中处理。我们不会深入探讨 JavaScript 如何在协作分布式框架中进行大规模计算,但一群矮人可以推翻一个巨人。如果您对这个主题感兴趣,您可以在www.igvita.com/2009/03/03/collaborative-map-reduce-in-the-browser/dyn.com/blog/browsers-vs-servers-using-javascript-for-number-crunching-theories/上了解更多信息。

d3.js 可以安全地称为在撰写本文时最通用的 JavaScript 数据可视化库;它是由 Mike Bostock 开发的,作为他 Protovis 库的继任者。许多 JavaScript 库都是建立在 d3.js 之上的。

NVD3、C3.js、xCharts 和 Dimple 提供了大致相同的东西:在 d3.js 之上的抽象层,这使得绘制简单的图表更加容易。它们主要区别在于它们支持的图表类型和默认设计。请随意访问它们的网站,并亲自了解:

选项很多。那么为什么选择 dc.js?

主要原因:与它所提供的功能相比,一个交互式仪表板,点击一个图表就会在相关图表上创建过滤视图,dc.js 的设置非常简单。简单到本章结束时你将有一个可工作的示例。作为一名数据科学家,你已经投入了足够的时间在你的实际分析上;易于实现的仪表板是一个受欢迎的礼物。

为了了解你即将创建的内容,你可以访问以下网站,dc-js.github.io/dc.js/,并滚动到 NASDAQ 示例,如图 9.2 所示。

图 9.2. dc.js 在其官方网站上的交互式示例

在仪表板上点击,当你选择和取消选择数据点时,你会看到图表如何做出反应和交互。但不要花太多时间;现在是时候自己创建它了。

如前所述,dc.js 有两个主要前提:d3.js 和 crossfilter.js。d3.js 有一个陡峭的学习曲线,如果你对完全自定义你的可视化感兴趣,有几本书值得阅读。但为了使用 dc.js,你不需要了解它,所以我们不会在本书中涉及它。crossfilter.js 是另一回事;你需要对这种 MapReduce 库有一定的了解,才能在你的数据上运行 dc.js。但由于 MapReduce 的概念本身并不新颖,这个过程将会顺利。

9.2. Crossfilter,JavaScript MapReduce 库

JavaScript 并非数据处理的最佳语言。但这并没有阻止人们,例如 Square 的人,为其开发 MapReduce 库。如果你在处理数据,任何速度的提升都是有益的。然而,你并不希望发送大量数据通过互联网或甚至你的内部网络,原因如下:

  • 发送大量数据会网络到极限,以至于会打扰其他用户。

  • 浏览器是接收方,在加载数据时它将暂时冻结。对于少量数据来说这是不可察觉的,但当你开始查看 100,000 行数据时,它可能成为明显的延迟。当你超过 1,000,000 行数据时,根据你数据的宽度,你的浏览器可能会放弃你。

结论:这是一个平衡练习。对于您发送的数据,一旦它到达浏览器,Crossfilter 将为您处理它。在我们的案例研究中,药剂师请求了 2015 年 29 种她特别感兴趣的药品的库存数据。我们已经查看过这些数据,所以让我们深入了解应用程序本身。

9.2.1. 设置一切

现在是时候构建实际的应用程序了,我们的小 dc.js 应用程序的成分如下:

  • JQuery —用于处理交互性

  • Crossfilter.js —一个 MapReduce 库,也是 dc.js 的先决条件

  • d3.js —一个流行的数据可视化库,也是 dc.js 的先决条件

  • dc.js —您将使用的可视化库来创建您的交互式仪表板

  • Bootstrap —一个广泛使用的布局库,您将使用它使一切看起来更好

您只需编写三个文件:

  • index.html —包含您的应用程序的 HTML 页面

  • application.js —用于存放您将要编写的所有 JavaScript 代码

  • application.css —用于您自己的 CSS

此外,您还需要在 HTTP 服务器上运行我们的代码。您可以费心设置 LAMP(Linux, Apache, MySQL, PHP)、WAMP(Windows, Apache, MySQL, PHP)或 XAMPP(Cross Environment, Apache, MySQL, PHP, Perl)服务器。但为了简单起见,我们在这里不会设置任何这些服务器。相反,您可以使用单个 Python 命令来完成它。使用您的命令行工具(Linux shell 或 Windows CMD)并移动到包含您的 index.html 的文件夹(一旦它在那里)。您应该已经安装了 Python,以便阅读本书的其他章节,因此以下命令应该在您的本地主机上启动一个 Python HTTP 服务器。

python -m SimpleHTTPServer

对于 Python 3.4

python -m http.server 8000

正如您在图 9.3 中看到的,一个 HTTP 服务器在本地主机端口 8000 上启动。在您的浏览器中,这表示为“localhost:8000”;使用“0.0.0.0:8000”将不起作用。

图 9.3. 启动一个简单的 Python HTTP 服务器

确保所有必需的文件都位于与您的 index.html 相同的文件夹中。您可以从 Manning 网站或它们的创建者网站上下载它们。

现在我们知道了如何运行我们即将创建的代码,让我们看看以下列表中的 index.html 页面。

列表 9.1. index.html 的初始版本

没有任何惊喜。这里包含了你将使用的所有 CSS 库,因此我们将 JavaScript 加载在 HTML 体部的末尾。使用 JQuery 的 onload 处理程序,当页面其余部分准备就绪时,你的应用程序将被加载。你开始时有两个表格占位符:一个用于显示你的输入数据的样子,<div id="input-table"></div>,另一个将用于与 Crossfilter 一起显示一个过滤后的表格,<div id="filteredtable"></div>。使用了几个 Bootstrap CSS 类,例如“well”,“container”,Bootstrap 网格系统中的“row”和“col-xx-xx”,等等。它们使整个界面看起来更美观,但它们不是必需的。有关 Bootstrap CSS 类的更多信息,可以在他们的网站上找到:getbootstrap.com/css/.

现在你已经设置了 HTML,是时候在屏幕上显示你的数据了。为此,将注意力转向你创建的应用程序.js 文件。首先,我们将整个“待执行”代码包裹在一个 JQuery onload 处理程序中。

$(function() {
    //All future code will end up in this wrapper
})

现在我们确定只有当所有其他内容都准备就绪时,我们的应用程序才会被加载。这很重要,因为我们将使用 JQuery 选择器来操作 HTML。现在是加载数据的时候了。

d3.csv('medicines.csv',function(data) {
    main(data)
});

你没有准备好一个等待你的 REST 服务,所以在这个例子中,你将从.csv 文件中抽取数据。这个文件可以在 Manning 的网站上下载。d3.js 提供了一个简单的函数来完成这个任务。在加载数据后,你将其在 d3.csv 回调函数中传递给你的主应用程序函数。

除了主函数外,你还有一个CreateTable函数,你将使用它来...你猜对了...创建你的表格,如下面的列表所示。

列表 9.2. CreateTable函数
var tableTemplate = $([
        "<table class='table table-hover table-condensed table-striped'>",
        "  <caption></caption>",
        "  <thead><tr/></thead>",
        "  <tbody></tbody>",
        "</table>"
    ].join('\n'));

    CreateTable = function(data,variablesInTable,title){
        var table = tableTemplate.clone();
        var ths = variablesInTable.map(function(v) { return $("<th>").text(v) });
        $('caption', table).text(title);
        $('thead tr', table).append(ths);
        data.forEach(function(row) {
            var tr = $("<tr>").appendTo($('tbody', table));
            variablesInTable.forEach(function(varName) {
                var val = row, keys = varName.split('.');
                keys.forEach(function(key) { val = val[key] });
                tr.append($("<td>").text(val));
            });
        });
        return table;
    }

CreateTable()需要三个参数:

  • data —需要放入表格中的数据。

  • variablesInTable —需要显示的变量。

  • Title —表格的标题。知道你在看什么总是件好事。

CreateTable()使用一个预定义的变量tableTemplate,它包含我们的整体表格布局。然后CreateTable()可以向这个模板添加数据行。

现在你有了你的工具,让我们来看看应用程序的main函数,如下面的列表所示。

列表 9.3. JavaScript main函数

图片

你开始时在屏幕上显示你的数据,但最好是不要显示全部;只显示前五条记录就足够了,如图 9.4 所示。你的数据中有一个日期变量,你想要确保 Crossfilter 稍后能将其识别为日期类型,因此你首先解析它并创建一个新的变量名为Day。你暂时在表格中显示原始的Date,但稍后你将使用Day进行所有计算。

图 9.4. 浏览器中显示的输入药物表格:前五行

图片

这就是你最终得到的结果:与你在 Excel 中看到的一模一样。现在你知道基础知识正在工作,你将引入 Crossfilter 到等式中。

9.2.2。释放 Crossfilter 以过滤药物数据集

现在让我们进入 Crossfilter 使用过滤和 MapReduce。从现在起,你可以在 9.2.1 节的代码之后放置所有即将到来的代码,在main()函数内。你需要做的第一件事是声明一个 Crossfilter 实例,并用你的数据初始化它。

CrossfilterInstance = crossfilter(medicineData);

从这里你可以开始工作。在这个实例中,你可以注册维度,这些维度是你的表格的列。目前 Crossfilter 限制为 32 个维度。如果你处理的数据超过 32 个维度,你应该在发送到浏览器之前将其缩小。让我们创建我们的第一个维度,药物名称维度:

var medNameDim = CrossfilterInstance.dimension(function(d) {return
   d.MedName;});

你的第一个维度是药物名称,你现在已经可以使用它来过滤你的数据集,并使用我们的CreateTable()函数显示过滤后的数据。

   var dataFiltered= medNameDim.filter('Grazax 75 000 SQ-T')
   var filteredTable = $('#filteredtable');
   filteredTable
 .empty().append(CreateTable(dataFiltered.top(5),variablesInTable,'Our
First Filtered Table'));

你只显示了前五个观察值(图 9.5);你有 365 个,因为你有一个药物一整年的结果。

图 9.5。按药物名称 Grazax 75 000 SQ-T 过滤的数据

这个表格看起来没有排序,但实际上是排序过的。top()函数按药物名称排序了它。因为你只选择了一种药物,所以这无关紧要。使用你新的Day变量按日期排序很容易。让我们注册另一个维度,日期维度:

        var DateDim = CrossfilterInstance.dimension(
function(d) {return d.Day;});

现在我们可以按日期而不是药物名称排序:

   filteredTable
           .empty()
           .append(CreateTable(DateDim.bottom(5),variablesInTable,'Our
First Filtered Table'));

结果如图 9.6 所示,更加吸引人。

图 9.6。按药物名称 Grazax 75 000 SQ-T 过滤并按天排序的数据

这个表格为你提供了数据的窗口视图,但它还没有为你总结。这就是 Crossfilter MapReduce 功能发挥作用的地方。假设你想知道每种药物有多少观察值。逻辑上,你应该为每种药物得到相同的数字:365,或者 2015 年每天一个观察值。

        var countPerMed = medNameDim.group().reduceCount();
        variablesInTable = ["key","value"]
        filteredTable
                .empty()
     .append(CreateTable(countPerMed.top(Infinity),
variablesInTable,'Reduced Table'));

Crossfilter 提供了两个 MapReduce 函数:reduceCount()reduceSum()。如果你想做的不仅仅是计数和求和,你需要为它编写 reduce 函数。countPerMed变量现在包含按药物维度分组的数据,以及每个药物的行计数,形式为键和值。为了创建表格,你需要处理变量key而不是medName,以及value作为计数(图 9.7)。

图 9.7。以药物为组并按数据行计数作为值的 MapReduced 表格

通过指定 .top(Infinity),你要求在屏幕上显示所有 29 种药物,但为了节省纸张,图 9.7 只显示了前五个结果。好吧,你可以放心了;每种药物的数据包含 365 行。注意 Crossfilter 忽略了“Grazax”上的过滤器。如果一个维度用于分组,则过滤器不适用于它。只有其他维度的过滤器才能缩小结果。

那么,关于那些没有与 Crossfilter 一起打包的更有趣的计算呢?例如,计算平均值?你仍然可以这样做,但你需要编写三个函数并将它们传递给 .reduce() 方法。假设你想知道每类药物的平均股票。如前所述,几乎所有的 MapReduce 逻辑都需要你自己编写。平均值不过是总和除以计数,所以你需要两者;你如何处理这个问题?除了 reduceCount()reduceSum() 函数之外,Crossfilter 还有一个更通用的 reduce() 函数。这个函数接受三个参数:

  • reduceAdd() 函数 —一个描述当添加额外观察时会发生什么的函数。

  • reduceRemove() 函数 —一个描述当观察消失时(例如,因为应用了过滤器)需要发生什么的函数。

  • reduceInit() 函数 —这个函数用于设置所有计算初始值。对于总和和计数,最合理的起始点是 0。

在尝试调用 Crossfilter 的 .reduce() 方法之前,让我们看看你需要使用的单个 reduce 函数。.reduce() 方法接受这三个组件作为参数。一个自定义的 reduce 函数需要三个组件:一个初始化函数、一个添加函数和一个移除函数。初始 reduce 函数将设置 p 对象的起始值:

var reduceInitAvg = function(p,v){
    return {count: 0, stockSum : 0, stockAvg:0};
}

如你所见,reduce 函数本身接受两个参数。这些参数由 Crossfilter 的 .reduce() 方法自动传入:

  • p 是一个包含迄今为止组合情况的对象;它在所有观察中持续存在。这个变量为你跟踪总和和计数,因此代表你的目标,你的最终结果。

  • v 代表输入数据的记录,并提供了所有可用的变量。与 p 不同,它不会持续存在,每次函数被调用时都会被新的数据行替换。reduceInit() 只调用一次,但 reduceAdd() 每次添加记录时调用一次,reduceRemove() 每次删除一行数据时调用一次。

  • reduceInit() 函数,在这里称为 reduceInitAvg(),因为你将计算平均值,基本上通过定义其组件(countsumaverage)并设置它们的初始值来初始化 p 对象。让我们看看 reduce-AddAvg()

    var reduceAddAvg = function(p,v){
        p.count += 1;
        p.stockSum  = p.stockSum  + Number(v.Stock);
        p.stockAvg = Math.round(p.stockSum  / p.count);
        return p;
    }
    

reduceAddAvg() 使用相同的 pv 参数,但现在你实际上使用了 v;在这种情况下,你不需要你的数据来设置 p 的初始值,尽管你可以这样做。你的 Stock 对每条添加的记录进行求和,然后根据累积总和和记录计数计算平均值:

var reduceRemoveAvg = function(p,v){
    p.count -= 1;
    p.stockSum  = p.stockSum  -  Number(v.Stock);
    p.stockAvg = Math.round(p.stockSum  / p.count);
    return p;
}

reduceRemoveAvg() 函数看起来相似,但做的是相反的事情:当一条记录被移除时,计数和总和会降低。平均值总是以相同的方式计算,所以没有必要改变那个公式。

真相时刻:你将这个自制的 MapReduce 函数应用到数据集上:

注意你的输出变量名从 value 变成了 value .stockAvg。因为你自己定义了 reduce 函数,所以如果你想输出多个变量,可以做到。因此,value 变成了一个包含你计算的所有变量的对象;stockSumcount 也在其中。

结果不言自明,如图 9.8 所示。看起来我们借鉴了其他医院的 Cimalgex,进入了一个平均负库存。

图 9.8. 每种药品的平均库存的 MapReduced 表格

这就是你需要了解的所有 Crossfilter 知识,以便与 dc.js 一起工作,所以让我们继续前进,展示那些交互式图表。

9.3. 使用 dc.js 创建交互式仪表板

现在你已经了解了 Crossfilter 的基础知识,是时候采取最后一步:构建仪表板。让我们从在 index.html 页面上为你的图表留出空间开始。新的主体看起来如下所示。你会注意到它看起来与我们的初始设置相似,除了添加了图表占位符 <div> 标签和重置按钮 <button> 标签。

列表 9.4. 带有 dc.js 生成的图表空间的修订版 index.html

我们正在进行 Bootstrap 格式化,但最重要的元素是带有 ID 的三个 <div> 标签和按钮。你想要构建的是总库存随时间变化的表示,<div id="StockOverTime"></div>,有过滤药品的可能性,<div id="StockPerMedicine"></div>,以及它们是否对光敏感,<div id="LightSensitiveStock"></div>。你还需要一个重置所有过滤器的按钮,<button class="btn btn-success">重置过滤器</button>。这个重置按钮元素不是必需的,但很有用。

现在,将你的注意力转回 application.js。在这里,你可以在 main() 函数中添加所有即将到来的代码,就像之前一样。然而,有一个例外:dc.renderAll(); 是 dc 绘制图表的命令。你需要将此渲染命令只放置一次,在 main() 函数的底部。你需要的第一张图是“总库存随时间变化”,如下所示。你已经有时间维度声明了,所以你只需要按时间维度对库存求和。

列表 9.5. 生成“随时间推移的总库存”图表的代码

看看这里发生了什么。首先你需要计算 x 轴的范围,这样 dc.js 就会知道线图的起始和结束位置。然后初始化并配置线图。这里最不具说明性的方法是 .group().dimension().group() 接收时间维度并代表 x 轴。.dimension() 是它的对应物,代表 y 轴并接收你的汇总数据作为输入。图 9.9 看起来像一条无聊的线图,但外表可能具有欺骗性。

图 9.9. dc.js 图表:2015 年药品库存总和

一旦引入第二个元素,情况就会发生巨大变化,因此让我们创建一个行图,表示每种药品的平均库存,如下面的列表所示。

列表 9.6. 生成“每种药品的平均库存”图表的代码

这应该很熟悉,因为它是你之前创建的表的图表表示。一个值得注意的大点:因为你这次使用了自定义定义的 reduce() 函数,dc.js 就不知道要表示什么数据。你可以使用 .valueAccessor() 方法指定 p.value.stockAvg 作为你选择的价值。dc.js 行图的标签字体颜色是灰色;这使得你的行图有些难以阅读。你可以在应用程序的 application.css 文件中覆盖它的 CSS 来解决这个问题:

.dc-chart g.row text {fill: black;}

一条简单的线就能区分清晰和模糊的图表(图 9.10)。

图 9.10. dc.js 线图和行图交互

现在当你选择折线图上的一个区域时,行图会自动调整以表示正确时间段的数据。反之,你可以在行图上选择一个或多个药品,这将导致折线图相应地调整。最后,让我们添加光敏度维度,以便药剂师可以区分光敏药品和非光敏药品的库存,如下面的列表所示。

列表 9.7. 添加光敏度维度
        var lightSenDim = CrossfilterInstance.dimension(
function(d){return d.LightSen;});
        var SummatedStockLight =  lightSenDim.group().reduceSum(
function(d) {return d.Stock;});

        var LightSensitiveStockPieChart = dc.pieChart("#LightSensitiveStock");

        LightSensitiveStockPieChart
                .width(null) // null means size to fit container
                .height(300)
                .dimension(lightSenDim)
                .radius(90)
                .group(SummatedStockLight)

我们还没有介绍光维度,所以你首先需要将其注册到你的 Crossfilter 实例中。你还可以添加一个重置按钮,这将重置所有过滤器,如下面的列表所示。

列表 9.8. 仪表板重置过滤器按钮

.filterAll() 方法移除特定维度上的所有过滤器;然后 dc.redraw-All() 手动触发所有 dc 图表的重绘。

最终结果是交互式仪表板(图 9.11),准备好供我们的药剂师使用,以深入了解其库存的行为。

图 9.11. 医院药房内药品及其库存的 dc.js 完全交互式仪表板

9.4. 仪表板开发工具

我们已经有了我们辉煌的仪表板,但我们在本章结束时想要简要(而且远非详尽)地概述一下,在以吸引人的方式展示你的数据时,可供选择的替代软件。

你可以选择知名开发者如 Tableau、MicroStrategy、Qlik、SAP、IBM、SAS、Microsoft、Spotfire 等提供的经过验证的软件包。这些公司都提供值得调查的仪表板工具。如果你在大公司工作,很可能你至少拥有这些付费工具之一。开发者也可以提供带有有限功能的免费公共版本。如果你还没有的话,请务必查看www.tableausoftware.com/public/download上的 Tableau。

其他公司至少会提供试用版。最终,你必须为这些软件包的完整版本付费,这可能值得,尤其是对于能够承担得起的大公司来说。

然而,本书的主要重点是免费工具。在查看免费数据可视化工具时,你很快就会进入 HTML 世界,那里充满了免费 JavaScript 库,可以绘制你想要的任何数据。这个领域非常庞大:

  • HighCharts —最成熟的基于浏览器的图形库之一。免费许可证仅适用于非商业用途。如果你想在商业环境中使用它,价格从 90 美元到 4000 美元不等。请参阅shop.highsoft.com/highcharts.html

  • Chartkick —一个针对 Ruby on Rails 爱好者的 JavaScript 图表库。请参阅ankane.github.io/chartkick/

  • Google Charts —Google 的免费图表库。与许多 Google 产品一样,它免费使用,甚至可以用于商业目的,并提供广泛的图表类型。请参阅developers.google.com/chart/

  • d3.js —这是一个与众不同的例子,因为它不是一个图形库,而是一个数据可视化库。这种差异可能听起来很微妙,但其影响却不容忽视。与 HighCharts 和 Google Charts 等库不同,它们旨在绘制某些预定义的图表,而 d3.js 没有这样的限制。d3.js 目前是可用的最灵活的 JavaScript 数据可视化库。你只需快速浏览一下官方网站上的交互式示例,就能理解它与常规图形库之间的差异。请参阅d3js.org/

当然,还有其他我们没有提到的工具。

你还可以获得只提供试用版而没有免费社区版的可视化库,例如 Wijmo、Kendo 和 FusionCharts。它们值得一看,因为它们也提供支持和保证定期更新。

你有选择。但为什么或何时你会考虑用 HTML5 而不是使用 SAP 的 BusinessObjects、SAS JMP、Tableau、Clickview 或其他许多替代品来构建自己的界面呢?以下是一些原因:

  • 没有预算 — 当你在初创公司或其他小型公司工作时,这类软件的许可费用可能会很高。

  • 高度可访问性 — 数据科学应用旨在向任何类型的用户发布结果,特别是那些可能只有浏览器可用的人——比如你的客户。HTML5 在移动设备上的数据可视化运行流畅。

  • 外面有大量的人才 — 虽然 Tableau 开发者并不多,但很多人有网页开发技能。在规划项目时,考虑你是否能够配备人员是很重要的。

  • 快速发布 — 在你的公司中,整个 IT 周期可能需要太长时间,你希望人们能够快速享受你的分析。一旦你的界面可用并被使用,IT 就可以花他们想要的时间来工业化产品。

  • 原型设计 — 你能更好地向 IT 展示其目的和它应该具备的能力,那么他们就越容易构建或购买一个可持续的应用程序,使其能够完成你想要它完成的任务。

  • 可定制性 — 虽然现有的软件包在其领域内做得很好,但应用程序永远无法像你自己创建时那样定制。

那为什么不这样做呢?

  • 公司政策 — 这是最重要的一点:不允许。大型公司有 IT 支持团队,只允许使用一定数量的工具,以便他们能够控制其支持角色。

  • 你有一支经验丰富的报告团队可供使用 — 你会做他们的工作,他们可能会拿着长柄叉子来找你。

  • 你的工具允许足够的定制以适应你的口味 — 一些较大的平台是浏览器界面,底层运行 JavaScript。Tableau、BusinessObjects Webi、SAS Visual Analytics 等所有都有 HTML 界面;它们的定制容忍度可能会随着时间的推移而增长。

任何应用程序的前端都能赢得大众的心。你投入的所有数据准备工作和应用的复杂分析,只有当你能够传达给使用它的人时才有价值。现在你正走在实现这一目标的正确道路上。在这个积极的基调中,我们将结束本章。

9.5. 摘要

  • 本章重点介绍了数据科学流程的最后部分,我们的目标是构建一个数据科学应用,其中最终用户可以获得一个交互式仪表板。经过数据科学流程的所有步骤后,我们得到了干净、通常紧凑或信息密集的数据。这样我们就可以查询更少的数据,并获得我们想要的分析结果。

  • 在我们的例子中,药房库存数据被认为是彻底清洗和准备好的,并且信息到达最终用户时应该始终如此。

  • 基于 JavaScript 的仪表板非常适合快速提供数据科学结果,因为它们只需要用户拥有一个网络浏览器。存在其他替代方案,例如 Qlik(第五章)。

  • Crossfilter 是一个 MapReduce 库,是众多 JavaScript MapReduce 库之一,但它已经证明了其稳定性,并且正在由 Square 公司(一家从事货币交易的公司)开发和使用。即使在单个节点和浏览器中应用 MapReduce 也是有效的;它提高了计算速度。

  • dc.js 是一个基于 d3.js 和 Crossfilter 构建的图表库,它允许快速构建浏览器仪表板。

  • 我们探索了医院药房的数据集,并为药剂师构建了一个交互式仪表板。仪表板的优势在于其自助特性:他们并不总是需要报告员或数据科学家来提供他们渴望的洞察。

  • 数据可视化替代方案是可用的,花时间找到最适合你需求的方案是值得的。

  • 你会创建自己的定制报告而不是选择(通常更昂贵)的公司工具,原因有很多:

    • 没有预算 —初创公司并不总是能负担得起每个工具

    • 高度可访问性 —每个人都有浏览器

    • 可用的人才 —(相对)容易获得 JavaScript 开发者

    • 快速发布 —IT 周期可能需要一段时间

    • 原型设计 —原型应用程序可以提供时间,让 IT 部门构建生产版本

    • 可定制性 —有时你只想让它完全符合你的梦想。

  • 当然,也有反对开发自己的应用程序的理由:

    • 公司政策 —应用程序的扩散并不是好事,公司可能希望通过限制本地开发来防止这种情况。

    • 成熟的报告团队 —如果你有一个优秀的报告部门,你为什么还要费心呢?

    • 定制令人满意 —并不是每个人都想要那些闪亮的东西;基本功能就足够了。

恭喜!你已经走完了这本书的结尾,也是你作为数据科学家职业生涯的真正开始。我们希望你在阅读和通过示例和案例研究的过程中玩得开心。现在你已经对数据科学的世界有了基本的了解,选择一条道路就取决于你了。故事还在继续,我们都祝愿你在成为有史以来最伟大的数据科学家的追求中取得巨大成功!愿我们有一天能再次相见。 😉

附录 A. 设置 Elasticsearch

在本附录中,我们将介绍安装和设置第六章和第七章中使用的 Elasticsearch 数据库。包括 Linux 和 Windows 安装的说明。注意,如果你遇到麻烦或需要有关 Elasticsearch 的更多信息,它有相当不错的文档,可以在www.elastic.co/guide/en/elasticsearch/reference/1.4/setup.html找到。

注意

Elasticsearch 依赖于 Java,因此我们还将介绍如何安装 Java。

A.1. Linux 安装

首先检查你的机器上是否已经安装了 Java。

1. 你可以在控制台窗口中使用java –version来检查你的 Java 版本。如果 Java 已安装,你将看到类似于图 A.1 中的响应。你需要至少 Java 7 来运行本书中使用的 Elasticsearch 版本(1.4)。注意:当本书发布时,Elasticsearch 已经升级到版本 2,但尽管代码可能略有变化,核心原则仍然是相同的。

图 A.1. 在 Linux 中检查 Java 版本。Elasticsearch 需要 Java 7 或更高版本。

2. 如果 Java 未安装或版本不够高,Elasticsearch 推荐使用 Oracle 版本的 Java。使用以下控制台命令进行安装。

sudo add-apt-repository ppa:webupd8team/java sudo apt-get install oracle-java7-installer

现在你可以安装 Elasticsearch 了:

1. 将 Elasticsearch 1.4 仓库(写作时的最新版本)添加到你的仓库列表中,然后使用以下命令进行安装。

sudo add-apt-repository "deb http://packages.Elasticsearch.org/ ![](https://github.com/OpenDocCN/ibooker-ds-zh/raw/master/docs/intd-ds/img/enter.jpg) Elasticsearch/1.4/debian stable main" sudo apt-get update && sudo apt-get install Elasticsearch

2. 确保 Elasticsearch 在重启后能够启动,运行以下命令。

sudo update-rc.d Elasticsearch defaults 95 10

3. 启用 Elasticsearch。见图 A.2。

图 A.2. 在 Linux 上启动 Elasticsearch

sudo /etc/init.d/Elasticsearch start

如果 Linux 是你的本地计算机,打开浏览器并访问localhost:9200。9200 是 Elasticsearch API 的默认端口。见图 A.3。

图 A.3. 本地主机的 Elasticsearch 欢迎屏幕

Elasticsearch 的欢迎界面应该会欢迎您。注意您的数据库甚至有一个名字。这个名字是从漫威角色的池中选取的,并且每次您重新启动数据库时都会改变。在生产环境中,拥有这样一个不一致且不唯一的名字可能会出现问题。您启动的实例是可能成为庞大分布式集群一部分的单节点。如果所有这些节点在重启时都更改了名字,那么在出现问题时几乎不可能通过日志跟踪它们。Elasticsearch 以其几乎不需要配置即可启动并且本质上具有分布式特性而自豪。虽然这无疑是正确的,但像这种随机名字这样的例子证明,部署实际的多个节点设置将需要您仔细考虑某些默认设置。幸运的是,Elasticsearch 几乎在所有事情上都有充分的文档,包括部署 (www.Elasticsearch.org/guide/en/Elasticsearch/guide/current/deploy.html)。多节点 Elasticsearch 部署不在此章的范围内,但最好记住这一点。

A.2. Windows 安装

在 Windows 上,Elasticsearch 还需要至少安装 Java 7——JRE 和 JDK——并且需要 JAVA_HOME 变量指向 Java 文件夹。

1. 从 www.oracle.com/technetwork/java/javase/downloads/index.html 下载 Java 的 Windows 安装程序并运行它们。

2. 安装完成后,请确保您的 JAVA_HOME Windows 环境变量指向您安装的 Java 开发工具包的位置。您可以在系统控制面板 > 高级系统设置中找到环境变量。参见 图 A.4。

图 A.4. 将 JAVA_HOME 变量设置为 Java 安装文件夹

图片

在您拥有足够的 Java 版本之前尝试安装将导致错误。参见 图 A.5。

图 A.5. 当 JAVA_HOME 设置不正确时,Elasticsearch 安装失败。

图片

在权限有限的 PC 上安装

有时候您想尝试一款软件,但您无法安装自己的程序。如果是这种情况,请不要绝望:便携式 JDK 是存在的。当您找到其中之一时,您可以临时将 JAVA_HOME 变量设置为便携式 JDK 的路径,并通过这种方式启动 Elasticsearch。如果您只是想检查它,甚至不需要安装 Elasticsearch。参见 图 A.6。

图 A.6. 不安装即可启动 Elasticsearch。这仅建议在您拥有有限权限的计算机上进行测试。

图片

现在您已经安装并设置了 Java,您可以安装 Elasticsearch。

1. 请手动从www.Elasticsearch.org/download/下载 Elasticsearch 的 zip 包。将其解压到你的电脑上的任何位置。这个文件夹现在将成为你的独立数据库。如果你有 SSD 驱动器,考虑给它分配一个位置,因为它可以显著提高 Elasticsearch 的速度。

2. 如果你已经打开了 Windows 命令窗口,请不要用它来进行安装;而是打开一个新的窗口。打开窗口中的环境变量已经不再是最新的。将目录更改为你的 Elasticsearch /bin 文件夹,并使用service install命令进行安装。参见图 A.7。

图 A.7. Elasticsearch Windows 64 位安装

3. 数据库现在应该已经准备好启动了。使用service start命令。参见图 A.8。

图 A.8. Elasticsearch 在 Windows 上启动一个节点。

如果你想停止服务器,请输入service stop命令。打开你选择的浏览器,并在地址栏中输入localhost:9200。如果出现 Elasticsearch 欢迎界面(图 A.9),则表示你已成功安装 Elasticsearch。

图 A.9. 本地主机上的 Elasticsearch 欢迎界面

附录 B. 设置 Neo4j

在本附录中,我们将介绍安装和设置第七章中使用的 Neo4j 社区版数据库。包括 Linux 和 Windows 安装的说明。

B.1. Linux 安装

要在 Linux 上安装 Neo4j 社区版,请按照以下说明使用命令行:debian.neo4j.org/?_ga=1.84149595.332593114.1442594242

Neo Technology 提供这个 Debian 仓库,以便轻松安装 Neo4j。它包括三个仓库:

  • 稳定版 —除以下说明外,所有 Neo4j 版本。您应该默认选择此版本。

  • 测试版 —预发布版本(里程碑和发布候选版)。

  • 旧稳定版 —不再积极使用,此仓库包含旧小版本的补丁发布。如果您在稳定版中找不到所需的内容,请查看此处。

要使用新的稳定版软件包,您需要以 root 身份运行以下命令(注意以下我们使用 sudo):

sudo -s
wget -O - https://debian.neo4j.org/neotechnology.gpg.key| apt-key add - #
     Import our signing key
echo 'deb http://debian.neo4j.org/repo stable/' > /etc/apt/sources.list.d/
     neo4j.list # Create an Apt sources.list file
aptitude update -y # Find out about the files in our repository
aptitude install neo4j -y # Install Neo4j, community edition

如果您想要一个更新的(但不支持)的 Neo4j 构建版本,可以将稳定版替换为测试版。如果您想选择不同的版本,可以运行:

apt-get install neo4j-advanced

apt-get install neo4j-enterprise

B.2. Windows 安装

要在 Windows 上安装 Neo4j 社区版:

1. 访问 neo4j.com/download/ 并下载社区版。将出现以下屏幕。

2. 保存此文件并运行它。

3. 安装完成后,您将看到一个新弹窗,您可以选择默认数据库位置,或者浏览以找到另一个位置作为数据库位置。

4. 选择后,按开始按钮,您就可以开始了。

几秒钟后,数据库将准备好使用。如果您想停止服务器,只需按停止按钮即可。

5. 打开您选择的浏览器,并在地址栏中输入 localhost:7474

您已到达 Neo4j 浏览器。

6. 当数据库访问请求认证时,使用用户名和密码 “neo4j”, 然后按连接。

在以下窗口中您可以设置自己的密码。

现在您可以输入您的 Cypher 查询并查询您的节点、关系和结果。

附录 C. 安装 MySQL 服务器

在本附录中,我们将介绍安装和设置 MySQL 数据库。包括 Windows 和 Linux 安装的说明。

C.1. Windows 安装

最方便且推荐的方法是下载 MySQL 安装程序(适用于 Windows),并让它设置系统上的所有 MySQL 组件。以下步骤解释了如何操作:

1. 从 dev.mysql.com/downloads/installer/ 下载 MySQL 安装程序并打开它。请注意,与标准 MySQL 安装程序不同,较小的“web-group”版本会自动包含任何 MySQL 组件,但只会下载您选择安装的组件。您可以选择任一安装程序。参见 图 C.1。

图 C.1. Windows MySQL 安装程序的下载选项

2. 选择您偏好的合适设置类型。选项“开发者默认”将安装 MySQL 服务器和其他与 MySQL 进步相关的 MySQL 组件,以及支持功能,如 MySQL Workbench。如果您想选择要安装在系统上的 MySQL 项目,也可以选择自定义设置。如果您愿意,还可以在单个系统上运行不同版本的 MySQL。MySQL 通知器用于监控运行实例、停止它们和重新启动它们。您也可以稍后使用 MySQL 安装程序添加它。

3. 然后,MySQL 安装向导的说明将引导您完成设置过程。主要是接受即将发生的事情。开发机器将作为服务器配置类型执行。请确保设置 MySQL 根密码,并且不要忘记它,因为您稍后需要它。您可以将其作为 Windows 服务运行;这样,您就不需要手动启动它。

4. 安装完成。如果您选择了完整安装,MySQL 服务器、MySQL Workbench 和 MySQL 通知器将默认在计算机启动时自动启动。MySQL 安装程序可用于升级或更改已安装组件的设置。

5. 实例应该已经启动并运行,您可以使用 MySQL Workbench 连接到它。参见 图 C.2。

图 C.2. MySQL Workbench 界面

C.2. Linux 安装

MySQL 在 Linux 上的官方安装说明可以在 dev.mysql.com/doc/refman/5.7/en/linux-installation.html 找到。

然而,某些 Linux 发行版为此提供了特定的安装指南。例如,在 Ubuntu 14.04 上安装 Linux 的说明可以在 www.linode.com/docs/databases/mysql/how-to-install-mysql-on-ubuntu-14-04 找到。以下说明基于官方说明。

1. 首先检查您的计算机名:

hostname hostname -f

第一条命令应显示您的短主机名,第二条命令应显示您的完全限定域名(FQDN)。

2.  更新您的系统:

sudo apt-get update sudo apt-get upgrade

3.  安装 MySQL:

Sudo apt-get install msql-server

在安装过程中,您将收到一条消息,提示您为 MySQL root 用户选择密码,如图 C.3 所示。

图 C.3. 选择您的 MySQL root 用户的密码。

MySQL 默认将绑定到 localhost(127.0.0.1)。

4.  登录 MySQL:

mysql –u root –p

输入您选择的密码,您应该会看到如图 C.4 所示的 MySQL 控制台。

图 C.4. Linux 上的 MySQL 控制台

5.  最后,创建一个模式,以便在第四章的案例研究中有所参考。

Create database test;

附录 D. 使用虚拟环境设置 Anaconda

Anaconda 是一个特别适用于数据科学的 Python 代码包。默认安装将包含数据科学家可能会使用的许多工具。在我们的书中,我们将使用 32 位版本,因为它通常与许多 Python 包(尤其是 SQL 包)一起保持更高的稳定性。

虽然我们推荐使用 Anaconda,但这并不是必需的。在本附录中,我们将介绍安装和设置 Anaconda 的方法。包括 Linux 和 Windows 的安装说明,随后是环境设置说明。如果你对使用 Python 包有所了解,可以自由选择自己的方式。例如,你可以使用 virtualenv 和 pip 库。

D.1. Linux 安装

要在 Linux 上安装 Anaconda:

1.  前往 www.continuum.io/downloads 并下载基于 Python 2.7 的 32 位 Anaconda Linux 安装程序。

2.  下载完成后,使用以下命令安装 Anaconda:

bash Anaconda2-2.4.0-Linux-x86_64.sh

3.  我们需要在 Linux 命令提示符中使conda命令生效。Anaconda 会询问你是否需要这样做,所以回答“是”。

D.2. Windows 安装

要在 Windows 上安装 Anaconda:

1.  前往 www.continuum.io/downloads 并下载基于 Python 2.7 的 32 位 Anaconda Windows 安装程序。

2.  运行安装程序。

D.3. 设置环境

安装完成后,就是时候设置环境了。关于condapip命令的一个有趣方案可以在conda.pydata.org/docs/_downloads/conda-pip-virtualenv-translator.html找到。

1.  在你的操作系统命令行中使用以下命令。将“nameoftheenv”替换为你希望环境拥有的实际名称。

conda create –n nameoftheenv anaconda

2.  确保你同意继续设置,在列表末尾输入“y”,如图 D.1(#app04fig01)所示,然后过一会儿你就可以开始了。

图 D.1. Windows 命令提示符中的 Anaconda 虚拟环境设置

Anaconda 将在其默认位置创建环境,但如果你想要更改位置,也有选项可用。

3.  现在你已经有了环境,你可以在命令行中激活它:

  • 在 Windows 中,输入 activate nameoftheenv
  • 在 Linux 中,输入 source activate nameoftheenv

或者你可以通过你的 Python IDE(集成开发环境)来指向它。

4.  如果你在命令行中激活它,可以使用以下命令启动 Jupiter(或 IPython)IDE:

Ipython notebook

Jupiter(以前称为 IPython)是一个在浏览器中运行的交互式 Python 开发接口。它对于给你的代码添加结构很有用。

5. 对于书中提到的每个未在默认 Anaconda 环境中安装的包:

  1. 在命令行中激活你的环境。
  2. 要么在命令行中使用 conda install libraryname,要么使用 pip install libraryname

想了解更多关于pip安装的信息,请访问 python-packaging-user-guide.readthedocs.org/en/latest/installing/

想了解更多关于 Anaconda conda 安装的信息,请访问 conda.pydata.org/docs/intro.html

posted @ 2025-11-23 09:27  绝不原创的飞龙  阅读(32)  评论(0)    收藏  举报