作者:Sandy Ryza

(数据应用)就像香肠,最好别看见它们是怎么做出来的。 ——Otto von Bismarck

• 用数千个特征和数十亿个交易来构建信用卡欺诈检测模型

• 向数百万用户智能地推荐数百万产品

• 通过模拟包含数百万金融工具的投资组合来评估金融风险

• 轻松地操作成千上万个人类基因的相关数据以发现致病基因

5 到10 年前想要完成上述任务困难重重。我们说生活在“大数据”时代,其意思是指我们拥有收集、存储、处理大量信息的工具,而这些信息的规模以前我们闻所未闻。这些能力的背后是许多开源软件组成的生态系统,它们能利用大量普通计算机处理大规模数据。Apache Hadoop 之类的分布式系统已经进入主流,并被广泛部署在几乎各个领域的组织里。

但就像锉刀和石头本身并不构成雕塑一样,有了工具和数据并不等于就可以做有用的事情。这时我们就需要“数据科学”了。雕刻是利用工具将原始石材变成普通人都能看懂的雕塑,数据科学则是利用工具将原始数据变成对不懂数据科学的普通人有价值的东西。

通常“做有用的事情”指给数据加上模式并用SQL 来回答问题,比如:“注册过程中许多用户进入到第三个页面,其中有多少用户年龄超过25 岁?”如何结构化数据并组织信息来回答此类问题涉及面很广,本书不对其细节过多赘述。

有时候“产生价值”需要多付出一些努力。SQL 可能仍扮演重要角色,但为了处理数据的特质或进行复杂分析,人们需要一个更灵活、更易用的,且在机器学习和统计方面功能更丰富的编程模式。本书将重点讨论此类型的分析。

长久以来,人们利用R、PyData 和Octave 等开源框架可以在小数据集上进行快速分析和建模。只需不到10 行代码,就可以利用数据集的一部分数据构建出机器学习模型,再利用该模型预测其余数据的分类。如果多写几行代码,我们还能处理遗失数据,尝试多个模型并从中找出最佳模型,或者用一个模型的结果作为输入来拟合另一个模型。但如果数据集巨大,必须利用大量计算机来达到相同效果,我们该怎样做呢?

一个可能正确的方法是简单扩展这些框架使之能运行在多台机器上,保留框架的编程模型,同时重写其内核使之在分布式环境下能顺利运行。但是,分布式计算难度大,我们必须重新思考在单机系统中的许多基本假设在分布式环境下是否依然成立。比如,由于集群环境下数据需要在多个节点间切分,网络传输速度比内存访问慢几个数量级,如果算法涉及宽数据依赖,情况就很糟糕。随着机器数量的增加,发生故障的概率也相应增加。这些实际情况要求编程模式适配底层系统:编程模式要防止不当选项并简化高度并行代码的编写。

当然,除了像PyData 和R 这样在软件社区里表现优异的单机工具,数据分析还用到其他工具。在科学领域,比如常常涉及大规模数据的基因学,人们使用并行计算框架已经有几十年的历史了。今天,在这些领域处理数据的人大多数都熟悉HPC(High-Performance Computing,高性能计算)集群计算环境。然而,PyData 和R 的问题在于它们很难扩展。同样,HPC 的问题在于它的抽象层次较低,难于使用。比如要并行处理一个大DNA 测序文件,人们需要手工将该文件拆成许多小文件,并为每个小文件向集群调度器提交一个作业。如果某些作业失败,用户需要检查失败并手工重新提交。如果操作涉及整个数据集, 比如对整个数据集排序,庞大的数据集必须流入单个节点,否则科学家就要用MPI 这样底层的分布式框架。这些底层框架使用难度大,用户必须精通C 语言和分布式/ 网络系统。这些工具为HPC 环境编写,往往很难将内存数据模型和底层存储模型独立开来。比如很多工具只能从单个流读取POSIX 文件系统数据,很难自然并行化,不能用于读取数据库等其他后台存储。最近,Hadoop 生态系统提供了抽象,让用户使用计算机集群就像使用单台计算机一样。该抽象自动拆分文件并在多台计算机上分布式存储,自动将工作拆分成若干粒度更小的任务并分布式执行,出错时自动恢复。Hadoop 生态系统将大数据集处理涉及的许多琐碎工作自动化,并且启动开销比HPC 小得多。

1.2  认识Apache Spark

该介绍Apache Spark 了。Spark 是一个开源框架,作为计算引擎,它把程序分发到集群中的许多机器,同时它提供了一个优雅的编程模型。Spark 源自加州大学伯克利分校的AMPLab,现在已被捐献给了Apache 软件基金会。可以这么说,对于数据科学家而言,真正让分布式编程进入寻常百姓家的开源软件,Spark 是第一个。

了解Spark 的最好办法莫过于了解相比于它的前辈MapReduce,Spark 有哪些进步。MapReduce 革新了海量数据计算的方式,为运行在成百上千台机器上的并行程序提供了简单的编程模型。MapReduce 引擎几乎可以做到线性扩展:随着数据量的增加,可以通过增加更多的计算机来保持作业时间不变。而且MapReduce 是健壮的。故障虽然在单台机器上很少出现,但在数千个节点的集群上却总是出现。对于这种情况,MapReduce 也能妥善处理。它将工作拆分成多个小的任务,能优雅地处理任务失败,并且不影响任务所属作业的正确执行。

Spark 继承了MapReduce 的线性扩展性和容错性,同时对它做了一些重量级扩展。首先, Spark 摒弃了MapReduce 先map 再reduce 这样的严格方式,Spark 引擎可以执行更通用的有向无环图(DAG)算子。这就意味着,在MapReduce 中需要将中间结果写入分布式文件系统时,Spark 能将中间结果直接传到流水作业线的下一步。在这方面,它类似于Dryad (http://research.microsoft.com/en-us/projects/dryad/)。Dryad 也是从MapReduce 衍生出来的, 起源于微软研究院。其次,它也补充了这种能力,通过提供许多转换操作,用户可以更自然地表达计算逻辑。Dryad 更加面向开发人员,其流式API 可以做到用几行代码表示复杂的流水作业。

第三,Spark 扩展了前辈们的内存计算能力。它的弹性分布式数据集(RDD)抽象使开发人员将流水处理线上的任何点物化在跨越集群节点的内存中。这样后续步骤如果需要相同数据集时就不必重新计算或从磁盘加载。这个特性使Spark 可以应用于以前分布式处理引擎无法胜任的应用场景中。Spark 非常适合用于涉及大量迭代的算法,这些算法需要多次遍历相同数据集。Spark 也适用于反应式(reactive)应用,这些应用需要扫描大量内存数据并快速响应用户的查询。

或许最重要的是,Spark 契合了前面提到的数据科学领域的硬道理。它认识到构建数据应用的最大瓶颈不是CPU、磁盘或者网络,而是分析人员的生产率。通过将预处理到模型评价的整个流水线整合在一个编程环境中,Spark 大大加速了开发过程。这一点尤为值得称赞。Spark 编程模型富有表达力,在REPL 下包装了一组分析库,省去了多次往返IDE 的开销。而这些开销对诸如MapReduce 等框架来说是无法避免的。Spark 还避免了采样和从HDFS 来回倒腾数据所带来的问题,这些问题是R 之类的框架经常遇到的。分析人员在数据上做实验的速度越快,他们能从数据中挖掘出价值的可能性就越大。

在数据处理和ETL 方面,Spark 的目标是成为大数据界的Python 而不是大数据界的Matlab。作为一个通用的计算引擎,它的核心API 为数据转换提供了强大的基础,它独立于统计学、机器学习或矩阵代数的任何功能。它的Scala 和Python API 让我们可以用表达力极强的通用编程语言编写程序,还也可以访问已有的库。

Spark 的内存缓存使它适应于微观和宏观两个层面的迭代计算。机器学习算法需要多次遍历训练集,可以将训练集缓存在内存里。在对数据集进行探索和初步了解时,数据科学家可以在运行查询的时候将数据集放在内存,也很容易将转换后的版本缓存起来,这样就节省了访问磁盘的开销。

最后,Spark 在探索型分析系统和操作型分析系统之间搭起一座桥梁。我们经常说,数据科学家比统计学家更懂软件工程,而比软件工程师更懂统计学。基本上讲,Spark 比探索型系统更像操作型系统,而比操作型系统常见的技术则更善于数据探索。Spark 从根本上是为性能和可靠性而生的。由于构建于JVM 之上,它可以利用许多Java 技术栈里的操作和调试工具。

Spark 还紧密集成Hadoop 生态系统里的许多工具。它能读写MapReduce 支持的所有数据格式,可以与Hadoop 上的常用数据格式,如Avro 和Parquet(当然也包括古老的CSV), 进行交互。它能读写NoSQL 数据库,比如HBase 和Cassandra。它的流式处理组件Spark Streaming 能连续从Flume 和Kafka 之类的系统读取数据。它的SQL 库SparkSQL 能和Hive Metastore 交互,而且在另外一个项目中Spark 还能替代MapReduce 作为Hive 的底层执行引擎,该项目在本书撰写时还在处于开发过程。它可以运行在Hadoop 集群调度和资源管理器YARN 之上,这样Spark 可以和MapReduce 和Impala 等其他处理引擎动态共享集群资源和管理策略。

当然,Spark 并不完美。虽然它的核心引擎在成熟度上不断进步,即使是在本书撰写期间也是如此,但Spark 相比MapReduce 仍然很年轻,其批处理能力仍然比不过MapReduce。它的各个特殊子组件,比如流式处理、SQL、机器学习和图处理,分别处在不同的成熟阶段,每次升级API 变化较大。比如说,MLlib 的流水线和转换API 模型在本书写作时还在开发之中。它的统计和建模功能跟R 等单机版语言还没有可比性。它的SQL 功能虽然丰富,但和Hive 的SQL 功能相比差距还非常大。

1.1  数据科学面临的挑战

数据科学界有几个硬道理是不能违背的,Cloudera 数据科学团队的一项重要职责就是宣扬这些硬道理。一个系统要想在海量数据的复杂数据分析方面取得成功,必须要明白这些硬道理,至少不能违背这些硬道理。

第一,成功的分析中绝大部分工作是数据预处理。数据是混乱的,在让数据产生价值之前,必须对数据进行清洗、处理、融合、挖掘和许多其他操作。特别是对大数据集,由于人们很难直接检查,为了知道需要哪些预处理步骤,甚至需采用计算方法。一般情况下, 即使在模型调优阶段,在整个数据处理管道各个作业中,花在特征提取和选择上的时间比选择和实现算法的时间还要多。

比如,在构建网站欺诈交易检测模型时,数据科学家需要从许多可能的特征中进行选择。这些特征包括必填项、IP 地址信息、登录次数、用户浏览网站时的点击日志等。在将特征转换成适用于机器学习算法的向量时,每个特征可能都会有不同的问题。系统需要支持更灵活的转换,远远不止是将二维双精度数组转换成一个数学模型那么简单。

第二,迭代与数据科学紧密相关。建模和分析经常需要对一个数据集进行多次遍历。这其中一方面是由机器学习算法和统计过程本身造成的。常用的优化过程,比如随机梯度下降和最大似然估计,在收敛前都需要多次扫描输入数据。数据科学家自身的工作流程也涉及迭代。在初步调查和理解数据集时,一个查询的结果往往给下一个查询带来启示。在构建模型时,数据科学家往往很难在第一次就得到理想的结果。选择正确的特征,挑选合适的算法,运行恰当的显著性测试,找到合适的超参数,所有这些工作都需要反复试验。框架每次访问数据都要读磁盘,这样会增加时延,降低探索数据的速度,限制了数据科学家进行试验的次数。

第三,构建完表现卓越的模型不等于大功告成。数据科学的目标在于让数据对不懂数据科学的人有用。把模型以许多回归权值的形式存成文本文件放在数据科学家的计算机里,这样做根本没有实现数据科学的目标。数据推荐引擎和实时欺诈检测系统是最常见的数据应用。这些应用中模型作为生产服务的一部分,需要定期甚至是实时重建。

在这些场景中,有必要区别分析是试验环境还是生产环境。在试验环境下,数据科学家进行探测式分析。他们想理解工作数据集的本质。他们将数据图形化并用各种理论来测试。他们用各种特征做试验,用辅助数据源来增强数据。他们试验各种算法,希望从中找到一两个有效算法。在生产环境下,构建数据应用时,数据科学家进行操作式分析。他们把模型打包成服务,这些服务可以作为现实世界的决策依据。他们跟踪模型随时间的表现,哪怕是为了将模型准确度提高一个百分点,他们都会精心调整模型并且乐此不疲。他们关心服务SLA 和在线时间。由于历史原因,探索式分析经常使用R 之类的语言,但在构建生产应用时,数据处理过程则完全用Java 或C++ 重写。

当然,如果用于建模的原始代码也可用于生产应用,那就能节省每个人的时间。但像R 之类的语言运行缓慢,很难将其与生产基础设施的技术平台进行集成,而Java 和C++ 之类的语言又很难用于探索式分析。它们缺乏交互式数据操作所需的REPL(Read-Evaluate- Print-Loop,读取- 计算- 打印- 循环)环境,即使是简单的转换,也需要写大量代码。人们迫切需要一个既能轻松建模又适合生产系统的框架。

1.3  关于本书

本书接下来的部分不会继续讨论Spark 的优缺点。本书也不会涉及其他几个话题。本书会介绍Spark 的流式编程模型和Scala 基础知识,但它不是一个Spark 的参考书或参考大全, 不会讲Spark 技术细节。它也不是机器学习、统计学、线性代数的参考书,但在讲到这些知识的时候,许多章节会提供一些背景知识。

另一方面,本书将帮助读者建立用Spark 在大规模数据集上进行复杂分析的感觉。我们会讲述整个处理过程:不但涉及模型的构建和评价,也会讲述数据清洗、数据预处理和数据探索,并会花费笔墨描述怎样将结果变成生产应用。我们认为最好的教学方法是实例,所以在快速介绍完Spark 及其生态系统之后,本书其余各章分别讨论了在不同领域使用Spark 进行数据分析的实例,每个实例都自成一体。

如果可能,我们尽可能做到不只是提供解决方案。我们会描述数据科学的整个工作流程, 包括它所有的迭代、无解以及需要重新开始的情况。本书将有助于读者熟悉Scala、Spark、机器学习和数据分析。但这都是为了一个更大的目标服务,我们希望本书首先教会读者如何完成本章开头部分提到的任务。每一章虽然只有薄薄的20 来页,但我们会力求把怎样构建一个此类数据应用讲清楚讲透彻。