Delta Lake如何成爲機器學習生命週期的理想平臺?

Delta Lake非常適合於機器學習生命週期,因爲它提供了一些特性,如模式執行、模式演化、時間旅行等。這些特性使數據工程師和科學家能夠比以往更快地設計出可靠、有彈性的自動化數據管道和機器學習模型。

對於許多數據科學家來說,構建和優化機器學習模型的過程只是他們每天工作的一小部分。他們的絕大多數時間都花在執行ETL、構建數據管道和將模型投入生產等不那麼光鮮(但至關重要)的工作上。

在本文中,我們將逐步介紹構建生產環境數據科學管道的過程。在此過程中,我們將演示Delta Lake如何成爲機器學習生命週期的理想平臺,因爲它提供了統一數據科學、數據工程和生產工作流的工具和特性,包括:

  • 能夠連續處理來自歷史和實時流源的新數據流的表,極大地簡化了數據科學生產管道。
  • 模式執行,確保表保持乾淨整潔,不受列污染(column contamination),併爲機器學習做好準備。
  • 模式演化,它允許向現有數據表添加新列,即使生產環境正在使用這些表,也不會導致破壞性更改。
  • 時間旅行,也就是數據版本控制,允許對任何Delta Lake表的更改進行審計、再現,甚至在由於用戶錯誤而發生意外更改時,還可以根據需要回滾這些更改。
  • 與MLflow集成,通過自動記錄實驗參數、結果、模型和圖表來跟蹤和再現實驗。

Delta Lake的這些特性使數據工程師和科學家能夠比以往更快地設計出可靠、有彈性的自動化數據管道和機器學習模型。

使用Delta Lake構建機器學習數據管道

多跳架構

一種常見的架構是,使用對應於數據工程管道中不同質量級別的表,逐步向數據添加結構:數據攝取(“Bronze”表)、轉換/特徵工程(“Silver”表)和機器學習訓練或預測(“Gold”表)。把這些表組合在一起,我們稱之爲“多跳”架構。它允許數據工程師構建一個管道,以原始數據作爲“單一的真理來源”,所有的東西都從原始數據開始流動。後續的轉換和聚合可以重新計算和驗證,以確保業務級聚合表仍然反映底層數據,即使下游用戶對數據進行加工並引入特定於上下文的結構。

爲了理解Delta Lake管道是如何工作的,我們有必要更深入地研究下數據與水的類比(如果您允許我們使用擴展的示例)。Delta Lake不需要調度一系列不同的批處理作業來使數據分階段地通過管道,而是允許數據像水一樣流動:無縫地、持續地、實時地。

Bronze表是典型的湖泊,大量的水(數據)源源不斷地流入。當它到達時,它是髒的,因爲它來自不同的來源,其中一些不那麼幹淨。從那裏,數據源源不斷地流入Silver表,就像一條與湖泊相連的小溪的源頭,快速不斷地流動。當水(在我們的例子中是數據)順流而下時,迂迴曲折的河流會對它進行淨化和過濾,它在流動的過程中會變得更加純淨。當它到達下游的水處理廠(我們的Gold表)時,它會接受一些最終的淨化和嚴格的檢測,以使其可以被消費,因爲消費者(在本例中是ML算法)非常挑剔,不會容忍受污染的水。最後,它從淨化工廠通過管道進入每個下游消費者的水龍頭(無論是ML算法,還是BI分析師),準備好以最純淨的形式供消費。

爲機器學習準備數據的第一步是創建一個Bronze表,在這裏可以以最原始的形式捕獲和保存數據。讓我們看看如何做到這一點——但首先,讓我們討論一下爲什麼Delta Lake是數據湖的首選。

數據湖的困境

目前,我們看到的最常見的模式是,公司使用Azure Event Hubs或AWS Kinesis收集實時流數據(比如客戶在網站上的點擊行爲),並將其保存到廉價、充裕的雲存儲中,比如Blob存儲或S3存儲桶。通常,公司希望使用歷史數據(比如客戶過去的購買歷史)來補充實時流數據,以獲得過去和現在的完整圖景。

因此,公司往往會從各種來源收集到大量原始的、非結構化的數據,而這些數據卻停滯在數據湖中。如果無法將歷史數據與實時流數據可靠地結合起來,併爲數據添加結構,使其能夠被輸入機器學習模型,這些數據湖很快就會變得錯綜複雜、雜亂無章,這就是“數據沼澤”一詞的由來。

在轉換或分析單個數據點之前,數據工程師已經遇到了他們的第一個難題:如何將(“批”)歷史數據的處理和實時流數據結合起來。通常,可以使用lambda架構來彌補這一差距,但這本身就存在一些問題,這些問題源於lambda的複雜性,以及它導致數據丟失或損壞的傾向。

Delta Lake的解決方案:將歷史數據和當前數據組合到同一個表中

解決“數據湖困境”的辦法是利用Delta Lake。Delta Lake是一個位於數據湖之上的開源存儲層。它是爲分佈式計算而構建的,百分之百兼容Apache Spark,因此,你很容易轉換現有的數據表,不管它們以什麼格式存儲(如CSV、Parquet等),並使用你喜歡的Spark API將它們保存成Delta Lake格式的Bronze表,如下所示。(注意,在本文中,我們將分析Lending Club提供的數據集,可以從這裏下載。)

# Read loanstats_2012_2017.parquet
loan_stats_ce = spark.read.parquet(PARQUET_FILE_PATH)

# Save table as Delta Lake
loan_stats_ce.write.format("delta").mode("overwrite").save(DELTALAKE_FILE_PATH)

# Re-read as Delta Lake
loan_stats = spark.read.format("delta").load(DELTALAKE_FILE_PATH)

一旦你創建了一個存儲原始數據的Bronze表,並將現有的錶轉換爲Delta Lake格式,你已經解決了數據工程師的第一個困境:結合過去和現在的數據。如何解決?Delta Lake表可以無縫地處理來自歷史和實時流源的連續數據流。而且,由於它使用Spark,所以它近乎兼容所有不同的流數據輸入格式和源系統,比如Kafka、Kinesis、Cassandra等。

爲了說明Delta Lake表可以同時處理批數據和流數據,請看下面的代碼。這段代碼從文件夾DELTALAKE_FILE_PATH將初始數據集加載到Delta Lake表中(如上文代碼塊所示),在將新數據流入表之前,我們可以使用SQL友好的語法在當前的數據上運行一個批處理查詢。

%sql
SELECT addr_state, SUM(`count`) AS loans
FROM loan_by_state_delta
GROUP BY addr_state


如上所見,最初,加州和德州的貸款數最高。

現在,我們已經演示了Delta Lake運行批數據查詢的能力,下一步我們將展示其同時在流數據上運行查詢的能力。

我們將創建一個流數據源,不斷將新數據添加到Delta Lake表中,並混合我們前面繪製過的已有的批數據。注意,和之前的批查詢代碼塊一樣,loan_by_state_readStream從同一位置(即DELTALAKE_FILE_PATH文件夾)讀取。

loan_by_state_readStream = spark.readStream.format("delta").load(DELTALAKE_FILE_PATH)
loan_by_state_readStream.createOrReplaceTempView("loan_by_state_readStream")

實際上,批數據和流數據可以在同一位置(例如DELTALAKE_FILE_PATH),而Delta Lake可以同時響應兩種類型數據的查詢,因此纔有一個說法,Delta Lake表提供提供了“統一的批數據和流數據源以及數據消費(sink)。”

在Delta Lake處理流時,可視化更新就在我們眼前,我們開始看到一個不同的模式出現。


 
如你所見,最近的數據流導致愛荷華州(中西部的州顏色越來越深)的貸款最多。即使使用loan_by_state_readStream將新數據併發地流進表中,loan_by_state_delta表也會被更新。

既然我們已經看到了Delta Lake允許我們同時對批量數據和流媒體來源的數據進行可靠地分析,下一步是做一些數據清洗、轉換和特徵工程,爲機器學習工作做好準備。

使用Delta Lake創建高質量特徵存儲

數據清洗和轉換

到目前爲止,我們已經成功地將我們的數據轉換爲Delta Lake的格式,並創建了一個Bronze表作爲無縫處理歷史數據和實時數據的着陸區。目前,數據已就位,但當前的形式還遠遠談不上有用:在可以用於機器學習模型之前,它需要大量的清洗、轉換和結構化。ML建模庫沒有提供(如果有的話)與數據類型、空值和缺失數據相關的靈活性,所以數據工程師接下來的工作是清理和處理原始數據。由於Delta Lake百分之百兼容Apache Spark,我們可以在我們的Delta Lake表上使用熟悉的Spark API對核心內容進行數據再加工,如下所示。

print("Map multiple levels into one factor level for verification_status...")
loan_stats = loan_stats.withColumn('verification_status', trim(regexp_replace(loan_stats.verification_status, 'Source Verified', 'Verified')))

print("Calculate the total amount of money earned or lost per loan...")
loan_stats = loan_stats.withColumn('net', round( loan_stats.total_pymnt - loan_stats.loan_amnt, 2))

在執行完ETL之後,我們可以將清洗、處理過的數據保存到一個新的Delta Lake Silver表,這使得我們無需修改原始數據,就可以將結果保存爲一個新表。

中間步驟的重要性

中間的Silver表很重要,因爲它可能作爲下游多個Gold表的數據來源,由不同的業務單位和用戶控制。例如,你可以想象一下,一個代表“產品銷售”的Silver表流入有幾種不同用途的Gold表,比如,更新供應鏈儀表板、計算銷售人員的工資獎金或爲董事會成員提供高層次的KPI。

我們不直接將Gold表與Bronze表中的原始數據連接起來的原因是,這會導致大量重複的工作。這將要求每個業務單元對其數據執行相同的ETL。相反,我們可以僅執行一次。還有一個附帶的好處,這一步可以避免由於數據分流而導致的混亂,像不同的業務單位計算相同的指標但又略有不同。

按照這個模型,我們就可以保證,保存或流入最後的Gold表的數據是乾淨、合規且一致的。

模式執行

現在,我們已經轉換了我們的數據,下一步是通過模式執行把結構引入我們的Delta Lake Silver表。模式執行(Schema enforcement)是數據科學家和工程師的一個重要特點,因爲它能確保我們能夠保持表的整潔。沒有模式執行,單個列中可能會有不同的數據類型混在一起,對我們的數據可靠性造成了損害。例如,如果我們不小心把StringType類型的數據引入了一個FloatType數據類型的列,我們可能會無意中使我們的機器學習模型無法讀取列,破壞我們寶貴的數據管道。

Delta Lake提供了寫入模式驗證,這意味着Delta Lake會在將新記錄寫入一個表時進行檢查,以確保這些記錄匹配表上預定義的模式。如果記錄不匹配表的模式,Delta Lake將會引發一個異常,防止不匹配的數據污染數據類型存在衝突的列。這個方法比讀取模式驗證更好,因爲一旦列已經被不正確的數據類型所污染,就很難再“把魔鬼重新放回瓶子裏”。

Delta Lake使得定義模式很容易,使用下面的代碼執行模式。注意傳入的數據如何被拒絕,因爲它們與表的模式不匹配。

# Generate sample loans with dollar amounts
loans = sql("select addr_state, cast(rand(10)*count as bigint) as count, cast(rand(10) * 10000 * count as double) as amount from loan_by_state_delta")
display(loans)

# Let's write this data out to our Delta table
loans.write.format("delta").mode("append").save(DELTALAKE_SILVER_PATH)

// AnalysisException: A schema mismatch detected when writing to the Delta table.

如果錯誤不是由於一個列包含了錯誤的數據類型所導致,但是因爲我們(故意)添加了一個沒有在當前模式中體現的新列,我們可以添加列,然後使用模式演化糾正這個錯誤,我們稍後會解釋。

一旦數據已經通過模式執行達到這個階段,我們可以將其以最終形式保存在Delta Lake Gold表中。現在,數據已經經過徹底地清洗、轉換,並且已經準備好供我們的機器學習模型使用——它們對數據結構非常挑剔!在將數據從原始狀態流入Bronze表和Silver表的過程中,我們已經建立了一個可再現的數據科學管道,它可以使所有獲取到的新數據進入ML就緒狀態。這些流可以是低延遲或手動觸發的,消除了傳統管道所需的調度和作業管理。

使用Delta Lake的時間旅行特性和MLflow運行可再現試驗

現在,我們已經轉換了我們的數據,並通過模式執行添加了結構,我們已經準備好開始運行試驗,並使用我們的數據建立模型。這就是數據科學中的“科學”真正發揮作用的地方。我們創建零和替代假說,構建和測試模型,衡量我們的模型對因變量的預測有多好。事實上,這個階段就是我們中的許多人閃光的時候!

數據科學家需要能夠進行可再現的實驗。再現性是所有科學探究的基礎:如果觀察結果不能測試、重複測試和複製,它是不可能進一步接近真相的。然而,當有這麼多不同的方式可以處理相同的問題時,我們中有誰是嚴格線性推進的?

毫無疑問,我們中有很多人認爲,我們處理事情的方式有點“神奇”,我們到達目的地的方式是沿着不確定和迂迴的路線進行調查和探索。這沒問題——只要我們使用的工具允許我們展示我們的工作、追溯步驟、留下麪包屑——向缺乏仔細思考的想法中添加點科學的方法,如果你願意的話。藉助Delta Lake的時間旅行和MLflow,上述一切皆成爲可能,你甚至可以做更多。

Delta Lake時間旅行

對於數據科學家,Delta Lake最有用的功能之一是能夠使用數據版本控制回到過去,或者說“時間旅行”。Delta Lake按順序維護着在任何Delta Lake表上執行的每個操作的日誌,所以如果你可以根據需要恢復到早期版本的表,撤銷一項意想不到的操作,或者只是看看你的數據在特定時期的情況。

使用時間旅行從表的早期版本中選擇數據很容易。用戶可以查看錶的歷史,使用一個版本歷史號(如以下代碼所示,當選擇表loan_by_state_delta VERSION AS OF 0)或時間戳查看數據在那個時間點的狀態。

%sql
DESCRIBE HISTORY loan_by_state_delta

要選擇表的以前版本,你可以使用熟悉的SQL語法,如下所示:

%sql
SELECT * FROM loan_by_state_delta VERSION AS OF 0

除了使用表的版本號之外,你還可以使用一個時間戳獲取數據快照,看看數據在一個特定的時間點是什麼樣子。

%sql
SELECT * FROM loan_by_state_delta TIMESTAMP AS OF '2019-07-14 16:30:00'

搭配MLflow(下面討論),Delta Lake的時間旅行可以確保你執行的所有轉換和試驗都是可追蹤、可再現、可逆的。它可以用來:

  • 重新創建數據集或表在特定時間點的狀態(創建數據“快照”)。
  • 重建和驗證訓練和測試數據集,再現試驗。
  • 回滾表中任何意外的更改或轉換。

順序事務日誌創建了一個可查證的數據血統,這對GRC(治理、風險和合規)應用程序特別有用。對於像GDPR和CCPA這樣的監管法規,公司需要有能力證明數據被正確刪除或匿名化(集體或個人層面)。更新、合併、刪除、插入等都可以出於審計目的來確認和驗證。

最後,在得知像無意中刪除行或算錯列等這樣的人爲錯誤百分之百可以使用時間旅行撤銷時,數據工程師就更容易入眠了。著名的墨菲定律指出,如果任何事情有可能出錯,它就會出錯,數據管道也不例外——由於人爲錯誤,錯誤不可避免會出現。與硬件故障相比,丟失數據的情況更可能因爲有人無意中編輯了一個表而發生,而這些錯誤是可以撤銷的。

事務日誌提供幫助的另一種方式是在調試錯誤時,你看——你可以回到過去,找出一個問題是如何產生的,並修復問題或還原數據集。

跟蹤MLflow中的試驗和工件

MLflow是一個與Delta Lake搭配使用的開源Python庫,使數據科學家能夠毫不費力地記錄和跟蹤指標、參數、文件和鏡像工件。用戶可以運行多個不同的試驗,根據需要修改變量和參數,並且知道,輸入和輸出都已經寫入日誌記錄下來。你甚至可以自動保存訓練模型,因爲你要嘗試不同的超參組合,這樣,一旦你選擇出表現最好的模型,模型權重已經保存並準備好供我們使用了。

在Databricks,MLflow自動啓用MLR 5.5,你可以使用MLflow Runs Sidebar查看MLflow運行,如下所示。

通過模式執行調整數據管道以適應新的或不斷變化的需求

數據工程師和科學家經常會發現,從開始構建數據管道比維護容易。由於不斷變化的業務需求、業務定義、產品更新和時間序列數據的性質,表模式不可避免地會隨着時間的推移不斷變化,所以重要的是要使用的工具使這些變化更容易管理。Delta Lake提供的工具不僅可用於模式執行,也可以通過mergeSchema選項用於模式演化,如下所示。

# Add the mergeSchema option
loans.write.option("mergeSchema","true").format("delta").mode("append").save(DELTALAKE_SILVER_PATH)

%sql
-- Review current loans within the `loan_by_state_delta` Delta Lake table
SELECT addr_state, sum(`amount`) AS amount
FROM loan_by_state_delta
GROUP BY addr_state
ORDER BY sum(`amount`)
DESC LIMIT 10

通過在查詢中添加.option (“mergeSchema”、“true”),任何出現在DataFrame中而不在Delta Lake目標表中的列都會自動作爲寫入事務的一部分添加。數據工程師和科學家可以使用這個選項向現有的機器學習生產表中添加新列(也許是一個新的指標跟蹤或本月的銷售金額),而不破壞現有的依賴於舊列的模型。

全部整合在一起:從Delta Lake表構建機器學習模型

MLflow已在後臺記錄我們的參數和結果,我們準備將我們的數據分爲訓練集和測試集,並訓練我們的機器學習模型。我們已經從Silver表中獲取經過轉換的數據,並執行模式以確保所有進入最終表的數據合規、無誤,從而創建了Gold表,我們將在其上訓練我們的模型。現在,我們已經使用我們之前介紹的“多跳”架構建立好了管道,使新數據不斷流入我們的管道,然後加工和保存在中間表裏。

爲了完善機器學習生命週期,我們將使用下面代碼片段中的標準和交叉驗證建立一個GLM模型網格。我們這裏的目標是預測借款者是否會在給定的貸款上違約。點擊這裏,查看完整代碼。

# Use logistic regression 
lr = LogisticRegression(maxIter=10, elasticNetParam=0.5, featuresCol = "scaledFeatures")

# Build our ML pipeline
pipeline = Pipeline(stages=model_matrix_stages+[scaler]+[lr])

# Build the parameter grid for model tuning
paramGrid = ParamGridBuilder() \
              .addGrid(lr.regParam, [0.1, 0.01]) \
              .build()

# Execute CrossValidator for model tuning
crossval = CrossValidator(estimator=pipeline,
                          estimatorParamMaps=paramGrid,
                          evaluator=BinaryClassificationEvaluator(),
                          numFolds=5)

# Train the tuned model and establish our best model
cvModel = crossval.fit(train)
glm_model = cvModel.bestModel

# Return ROC
lr_summary = glm_model.stages[len(glm_model.stages)-1].summary
display(lr_summary.roc)

得到的受試者工作特徵(ROC)曲線如下圖所示。

然後,我們將這個模型和完整代碼筆記本(這裏)中的其他幾個廣義線性模型做比較。在選出最好的模型(一個XGBoost模型)後,我們用它來預測我們的測試集,然後基於每個正確或錯誤分類繪製出我們節省或損失的錢數。在數據科學家看來,像這樣以確鑿的美元和美分表示你的分析總是一個好主意,因爲它讓你的結果具體而容易理解。

display(glm_valid.groupBy("label", "prediction").agg((sum(col("net"))).alias("sum_net")))

這裏有一篇更深入的博客文章,其中的示例使用了Scala。

總結

Delta Lake非常適合於機器學習生命週期,因爲它提供了一些特性,統一了數據科學、數據工程和生產工作流。它實現了從原始數據到結構化數據的連續數據流,允許在新輸入的數據上訓練新的ML模型,而現有的生產模型仍在提供預測服務。它提供了模式執行(確保數據格式正確,可以爲數據機器學習模型所處理)和模式演化(防止模式變化破壞現有的生產模型)。最後,Delta Lake提供的“時間旅行”,即基於有序事務日誌的數據版本控制,允許根據需要審計、再現、甚至回滾數據。

英文原文:

Productionizing Machine Learning with Delta Lake

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章