從大數據到微服務:如何通過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訓練的模型?


分享到:


相關文章: