从大数据到微服务:如何通过AWS lambda服务于Spark训练的模型?

点击上方关注,All in AI中国

作者:Jacopo Tagliabue

介绍

“我们选择它是因为我们要处理大量数据。此外,这听起来真的很酷。” ——Larry Page

恭喜!你用Spark训练决策树遍历大量的数据点,并从中获得了一个很好的模型。

你希望利用你的模型进行实时预测,但Spark内部没有简单的方法可以获得网站或应用程序所需的交互性:如果你正构建欺诈检测,那么在每个用户操作时想要实时触发预测并采取相应的行动的时候——时间至关重要!

我们需要的是一种快速而简便的方法,把我们的大数据模型转变为一个每次只根据需要提供一个预测的微服务。

当然,可能有更好地满足你的需求和口味的选择:你可能事先知道测试集(在这种情况下,请参见此处的示例:https://databricks.com/blog/2016/10/11/using-aws-lambda-with-databricks-for-etl-automation-and-ml-model-serving.html);你可能会对处理传入的流数据并定期点击缓存进行近实时预测感到满意(例如此处详述:https://vimeo.com/217723073);最后,你可能喜欢JVM,并寻找一些经过优化和准备就绪的东西(在这种情况下,你应该完全了解Mleap:https://github.com/combust/mleap)。

我们在这里要做的是共享一个纯粹的Pythonic端到端的工作流程,这将使你在几分钟内从Spark训练的模型到服务于预测的公共端点。

我们的工作流程基于以下“原则”:

  • 它是你所熟悉和喜爱的Python,从头到尾(加上一个非常小的yml文件);
  • 涉及一些语言解析(在Tooso:https://tooso.ai/);
  • 不会涉及部署服务器甚至是明确地编写端点(对于适度的工作负载,它也是免费的);
  • 我们将使用决策树演示工作流程,但同样的想法可以很容易地扩展到其他Spark ML算法中。

它不仅仅是部署Spark模型的一种方式,我们还将有机会看到工作中真正的数据工程挑战。

这是前一篇poston系列“being-lazy-at-devOps”的概念续集,其中包括AWS Lambdas和Tensorflow模型。(https://medium.com/tooso/serving-tensorflow-predictions-with-python-and-aws-lambda-facb4ab87ddd)

先决条件

在深入了解代码之前,请确保:

  1. 可以访问Spark集群:为方便起见,我们使用了Microsoft Azure提供的Linux Spark 2.2,但当然我们所说的任何内容都可以轻松应用于其他设置。(https://docs.microsoft.com/en-us/azure/hdinsight/spark/apache-spark-jupyter-spark-sql)
  2. 设置AWS账户,用于将我们的代码部署到AWS Lambda。(https://aws.amazon.com/lambda/)
  3. setup Serverless,可以按照这里的说明轻松安装。(https://serverless.com/framework/docs/providers/aws/guide/installation/)

你可以在GitHub repo中找到所有代码:让我们开始。(https://github.com/jacopotagliabue/spark_tree2lambda)

前传:在Spark中训练机器学习模型

Spark机器学习库的内部工作原理不是这篇文章的重点:如果你看到这里,你很可能已经知道如何训练你的模型——如果你跳过这一节,我们唯一要做的事情是训练模型的序列化版本到一个文本文件中。我们决定在Spark中加入一小部分来分享一个端到端的、独立的用例,所以我们只是做了最低限度的工作,让模型经过训练并准备好使用。

给SPARK初学者的注意事项:如果你已为群集使用了Azure部署,则可以按照Microsoft的指南开始查询某些示例数据,或者花些时间使用随群集提供的示例笔记本。

从大数据到微服务:如何通过AWS lambda服务于Spark训练的模型?

Azure Jupyter中的PySpark文件夹包含几个现成的笔记本,可以帮助你入门。

repo中包含的decision_tree_training笔记本仅包含加载某些数据和获取训练模型的基本步骤。我们使用的数据集是钞票认证数据集的csv版本(也可在此处获得:https://drive.google.com/file/d/1BLNKLEbrLBYUaT6yJdRgRzFJmR-x3H4L/view?usp=sharing),其中四个连续变量作为预测变量和两个目标类,在数据集中用1/0标识:多亏了Spark,我们可以很快理解使用简单的SQL语法分配:

从大数据到微服务:如何通过AWS lambda服务于Spark训练的模型?

通过笔记本,你可以使用表格和简单图表轻松显示数据集的基本统计信息。

为了训练我们的决策树模型,我们只需要将数据转换为标签点,并将生成的RDD提供给机器学习库。

经过快速计算后,模型终于可以使用了!请记住,我们的目标是将我们在Spark上学到的东西转换为一个即用型的无服务器端点,因此我们需要一种方法来提取我们在训练期间学到的数据的知识,以便它可以在我们的堆栈中的其他位置运行。对我们来说幸运的是,决策树可以只用一行导出:

从大数据到微服务:如何通过AWS lambda服务于Spark训练的模型?

serialized_model包含了在训练期间推断的决策规则列表,例如:

从大数据到微服务:如何通过AWS lambda服务于Spark训练的模型?

作为一个简单的python字符串,可以使用标准Spark方法或通过简单的复制+粘贴将其导出到文本文件(可以在repo中找到通过数据集训练生成的模型的副本)。

解决方案概述

我们的快速Pythonic解决方案详述如下。

从大数据到微服务:如何通过AWS lambda服务于Spark训练的模型?

解决方案概述:从CSV到基于lambda的端点。

从左到右:

  • 我们从Spark读取CSV文件开始(当然,在这里可以替换你拥有的任何数据管道);
  • 我们训练我们的ML模型(决策树)并使用Spark序列化选项来实现它;
  • 我们编写一个Python函数(详见下文),它将读取序列化模型并生成模型的Python可运行版本;
  • 我们在AWS中部署了一个基本的lambda函数,该函数将加载Python可运行模型,并利用API网关向外界公开其预测。

显然,所有的神奇之处都在于将模型从Spark表示形式“移植”到Python可运行的代码中。在下一节中,我们将看到我们如何以有原则和优雅的方式实现这一目标:进入DSL解析。

模型转换作为形式语义的练习

“这些是我的原则。如果你不喜欢它们,那么,我还有其他的。” - Groucho Marx

这种实用主义方法的主要观点(以及Tooso的大部分乐趣)是将问题视为语义挑战:我们有一种形式语言(Spark序列化语言)在模型理论意义上提供解释,即我们需要找到一种系统的方法来将Spark模型的句法组件与Python数据结构配对,这样我们就可以通过这些结构运行预测,同时保留原始模型中的信息。听起来很难?让我们先从一个例子开始。

让我们以正式的玩具语言L为例,从而定义:

  • 字母表由整数1,2 ... 10和运算符'+'组成
  • 格式良好的公式(wff)是A + B形式的任何符号序列,其中A和B是整数或wff。

要生成有效的L语句,我们只需要应用(一次或多次)语言规则。例如,我们可以这样做:

从大数据到微服务:如何通过AWS lambda服务于Spark训练的模型?

得到wff'2 + 9',或者我们可以做到:

我们将第二步中的B扩展为新的'A + C',然后用整数填充公式得到'2 + 9 + 7'。显然,L中并非所有公式都可以接受的。例如,以下都是无效的:

从大数据到微服务:如何通过AWS lambda服务于Spark训练的模型?

L是一种非常简单的语言,没有内在意义:虽然人类不可避免地认为'2 + 9 + 7'是基于L的算术运算2 + 9 + 7(18岁),到目前为止L中没有任何指定证明了这个结论。为了给L句子提供“自然”的算术意义,我们需要用语义学家称之为模型的东西(不要与机器学习模型混淆),即我们需要以原则性的方式指定我们如何将“意义”附加到L句子中。由于“意义”是一个相当抽象的概念,我们将为教程找到一些更谦逊且有用的东西:我们将使用另一种形式语言Python来解释L。

因此,我们的语义将是从L句子到Python指令的映射,它具有可操作的非次要好处(因此我们可以用L句子进行算术运算)

我们的(粗略定义的)模型M可以如下所示:

  • 整数1,2 ... 10映射到Python int 1,2 ... 10
  • 运算符'+'映射到Python lambda lambda x,y:x + y
  • 像A + B这样的wff映射到Python函数映射(+,[A],[B])),即wff是在'+'操作中用实际值“填充槽”的结果。

有了M,我们现在可以将我们无意义的L句子翻译成熟悉的Python命令,这样'2 + 9'就可以看作是表达以下代码的L方式:

从大数据到微服务:如何通过AWS lambda服务于Spark训练的模型?

将更复杂的东西按预期翻译,所以'2 + 9 + 7'将成为:

从大数据到微服务:如何通过AWS lambda服务于Spark训练的模型?

关于我们的建模语言Python的一个很酷的事情是,表达式可以运行,因此现在它们具有意义,L语句可以看作是简洁的Python指令来进行算术运算:对于所有L表达式,我们可以关联相应的 、唯一的Python代码来运行该表达式。

现在我们已经了解了模型构建的含义,我们可以回到Spark序列化模型的原始语言,即产生“wff”的语言,例如:

从大数据到微服务:如何通过AWS lambda服务于Spark训练的模型?

我们的目标是以一种原则方式为这些字符串分配“Python含义”,以便每个字符串都有一个相应的、唯一的Python代码来运行该决策树。

我们通过利用lark来构建我们的spark2python服务,这是一个很棒的工具,在给定语法规范和目标字符串的情况下,它将生成一个“解析树”,即组成字符串的句法片段的嵌套表示。如果你想象一下我们如何构建'2 + 9 + 7'句子,你会很容易看到结构:首先2和9相加,然后结果相加为7。

从大数据到微服务:如何通过AWS lambda服务于Spark训练的模型?

当lark解析Spark模型时,结果是一个带有嵌套if / else块的树(如预期的那样):一旦我们有了解析树,我们就可以逐个节点地导航它并替换(非常类似于上面的L)Spark具有等效Python片段的标记,我们还可以针对目标值的特性向量运行。

如果你查看处理程序中的预测函数(AWS lambda入口点,请参阅下文),你可以轻松地了解运行时发生的情况:客户端在调用服务时将特性向量作为查询参数传递; 在启动时初始化的Spark2Python类加载了lark语法并解析了序列化的模型字符串。

当客户端提供的特性向量到达映射服务时,run_instruction将开始遍历所有if / else块:对于每个节点,组成相等性测试的标记将映射到相应的Python对象。举个例子,这个Spark节点:

从大数据到微服务:如何通过AWS lambda服务于Spark训练的模型?

将等同于Python表达式:

从大数据到微服务:如何通过AWS lambda服务于Spark训练的模型?

结果表达式将根据客户端提供的向量中的特性0进行计算。到达预测节点时,例如:

在给定特性向量和存储的模型的情况下,程序将停止并向客户端返回预测值。

虽然run_instruction可能看起来令人生畏,但是它实际上是一个相当简单的概念:程序将递归地遍历if / else树结构的分支,并在每次遇到合适的节点时运行等效的Python代码。这就是服务执行的“自动”转换!

从大数据到微服务:如何通过AWS lambda服务于Spark训练的模型?

输入特性与模型决策规则的运行时比较:在每个节点处做出决策并且探索相应的分支,直到达到预测。

理解我们的语义到底有多普遍是很容易遇到的。由于我们为Spark模型构建了一个正式的语法,然后定义了树的递归Python解释,我们“保证”所有未来的模型,无论花费多长时间或如何嵌套,都将被系统正确执行,我们保证所有现在和将来的L句都可以通过Python进行评估。

使用AWS Lambda提供模型

我们的承诺是在几分钟内为你提供一个微服务,以便在没有服务器的情况下进行原型设计。实现这一目标的方法是使用AWS Lambda来包装我们经过训练的模型,并使用无服务器框架在一个命令中发布我们的服务,供全世界查看!

关于Lambda函数的解剖结构在网络上并不缺乏教程和解释(可以参考:https://medium.com/tooso/serving-tensorflow-predictions-with-python-and-aws-lambda-facb4ab87ddd)。对于那些在过去三年里用frozen in graphite的人来说,要点如下:

  • 无服务器计算是一种云计算范式,允许你部署特定的函数/服务,而不需要考虑任何关于底层硬件,操作系统甚至容器的问题:计算是在需要做的基础上进行的,你只需要在函数实际运行时收取费用。
  • 无服务器功能可根据云提供商的需要进行水平管理、运行和扩展,使开发人员可以自由地专注于业务逻辑而不是部署/管理应用层。

AWS Lambdas可以在chron上调用,也可以通过几个“触发器”(队列中的新消息,s3中的对象创建,http请求通过API网关等)调用,允许复杂的转换链和基于事件的管道。虽然可以使用AWS控制台来手动部署和管理lambda函数,但我们发现使用部署框架可以使你的项目保持整洁,自包含并自动进行版本控制。

在这个项目中,所需的基础设施非常简单,并且它可以在serverless.yml文件中捕获(env变量具有合理的默认值,但你可以随意使用你的名称/资源)。函数的名称是predict,函数在handler.py文件中定义; 最后,“触发器”是一个http GET请求,因此对/ predict路由的调用将被路由到我们的函数。如果你对命名约定、AWS的目标区域和分段感到满意,那么我们离工作端点只有两个简单命令。首先,我们需要确保项目中的vendored文件夹(或者你想要使用的任何名称:确保文件夹在那里!)包含该项目的依赖项(列在包含的requirements.txt中); 打开终端,进入项目文件夹并输入:

从大数据到微服务:如何通过AWS lambda服务于Spark训练的模型?

(请注意,在我们的例子中,依赖是纯Python,所以不必担心Linux兼容的二进制文件;但是,作为使用lambdas时的一般做法,你应该有一个系统,比如这个:https://github.com/UnitedIncome/serverless-python-requirements ,以确保你上传AWS容器的正确依赖项!)

最后,(安装了Serveless),只需在终端输入:

从大数据到微服务:如何通过AWS lambda服务于Spark训练的模型?

无服务器将我们的函数部署到我们选择的AWS数据中心,并自动为我们设置API网关,以便新的公共URL可用,并且该URL将所有/预测调用路由到该函数中。完成后(由于需要创建所有资源,第一次部署会花费更多时间),你将得到类似于此的输出:

从大数据到微服务:如何通过AWS lambda服务于Spark训练的模型?

要确保一切正常,请打开浏览器并测试示例GET调用,例如:

从大数据到微服务:如何通过AWS lambda服务于Spark训练的模型?

其中YOUR-LAMBDA-URL是在上面的部署中创建的URL。你应该收到如下响应:

从大数据到微服务:如何通过AWS lambda服务于Spark训练的模型?

直接使用来自浏览器的GET调用测试运行时预测。

恭喜:你的大数据模型现在可通过你的微服务获得!通过交换模型文件(或者甚至使用更多处理程序/代码切换同时部署多个模型),你现在可以使用此模板并在不到一分钟的时间内部署任何决策树。

从大数据到微服务:如何通过AWS lambda服务于Spark训练的模型?

相关推荐