即刻上有不少有趣的即友和好玩的圈子,如何幫助用戶發現喜歡的圈子、找到本身人,是即刻推薦團隊一直以來的願景。在這篇文章中,咱們將介紹即刻推薦系統中一個組件——基於Spark的機器學習庫,以及它是如何解決在線預測和離線訓練的矛盾的。html
首先,咱們將介紹推薦系統中的兩種計算環境,以及它們各自的優缺點。咱們指出了一個機器學習在工程實踐中的一個難點:如何將離線分佈式訓練的模型直接用到在線實時預測服務中。最後,將介紹咱們是如何經過使計算邏輯與分佈式數據結構解綁,從而解決這個難題的。算法
根據計算環境的不一樣,推薦系統的預測大致上能夠分爲在線(Online)、離線(Offline)兩種。在線計算,指的是在線上的推薦服務中,對接受到的請求,進行實時計算,生成推薦結果並直接返回給請求方。離線計算,是指以必定時間週期運行的,對數據庫中的大批量數據進行的計算。離線計算的結果一般會寫入數據庫中,供後續任務讀取。除此以外,還有介於在線和離線之間的近線(Nearline)計算,它主要以流處理的方式對近實時的數據進行處理,並將結果寫入數據庫。數據庫
在推薦系統中,在線計算和離線計算有各自的優缺點,及其適合的使用場景。在線計算可以作到實時地對用戶行爲做出反饋,從而能夠針對用戶當前所處的環境和臨時萌生的興趣,爲其提供更即時、更精準的推薦。可是,受限於系統對於延遲的要求,在線計算必須在算法的複雜性上做出一些犧牲。此外,在線計算可以處理的數據量一般也是比較小的。apache
離線計算對於算法的複雜性要求則沒有那麼高。它一般是以批處理的方式在分佈式集羣上計算。所以,它每每能夠處理更大量的數據,考慮更多的特徵。但因爲離線計算一般是天天一次,所以也就相應的損失了一些實時性,沒法對用戶行爲做出及時反饋。除了預測任務,模型的訓練過程也能夠算做爲一種離線計算。它使用日誌系統收集的歷史數據,訓練獲得一個模型,並對其進行性能評估。產出的模型,將會被用於後面的離線和在線預測。模型訓練過程,佔用的資源多,花費的時間長,比較適合在分佈式集羣上計算。服務器
在經常使用的計算平臺上,離線預測任務能夠和模型訓練無縫銜接。Spark MLlib 提供了 Pipeline 的接口。它能夠將模型訓練,連同訓練前的特徵預處理、特徵工程、特徵交叉等階段,按照必定次序組合成爲一個流水線,並支持將訓練好的整個流水線持久化到磁盤上。在預測階段,只需將訓練時存下的流水線模型整個加載到集羣上,而後將原始特徵直接輸入進流水線,便可獲得模型預測的結果了。模型分佈式訓練中使用 DataFrame
包裝訓練數據,模型離線預測時也是在分佈式環境中使用 DataFrame
包裝待預測數據。這種使用方法,也是 Spark MLlib 官方文檔中推薦的用法。數據結構
可是,正如上面提到的,離線預測最大的弊端是,它缺乏實時性。好比,某個即友在即刻上看了一條關於乒乓球國家隊隊員的視頻後,對有關乒乓球的內容產生了很大的興趣。若是,咱們的推薦系統能夠實時地,對用戶這種忽然產生的短時間偏好給予必定反饋:向她推薦幾條有關乒乓球的動態,或是推薦幾個「乒乓球俱樂部」圈子下的達人。那麼,也許這個即友就有機會即刻上發現一個新的興趣愛好,甚至認識不少有趣的即友。框架
爲了爲推薦系統提供更好的實時性,咱們須要在線上服務中,使用用戶當前的實時特徵和反饋,爲其推薦出她當下可能感興趣的東西。在線上服務中,咱們想對用戶可能感興趣的物品進行排序,使得最合口味的物品被排在推薦列表的前列。所以,須要將離線訓練的模型部署到線上服務中。機器學習
即刻推薦系統的離線計算使用 Spark MLlib 以分佈式的方式在集羣上,在線計算則是在用 Java 寫的線上服務中完成的。不一樣於離線預測,在線的模型預測沒有直接使用離線訓練儲存的流水線模型,而是獨立實現了模型預測的算法,以及輸入模型前的全套特徵預處理過程。所以,在每次離線訓練結束,將模型部署到線上時,須要將訓練獲得的模型參數拷貝到線上服務中,同時特徵預處理過程須要的參數也須要同步更新到線上服務中。換句話說,咱們須要在線上原封不動地再實現一遍離線訓練中定義的特徵預處理操做和具體的模型結構。分佈式
這就意味着,在線下模型訓練時作的任何一點改動,都必須在線上服務中同步修改。這種方式不只使得模型的更新鏈路冗長,不利於模型的快速迭代與驗證, 並且增長了線上服務開發的工程師和離線模型優化的算法研究員之間的溝通成本,迫使實現線上服務的工程師不得不感知具體的模型實現細節。此外,同一套邏輯須要兩套代碼實現,這種方式也對以後代碼的維護形成了不少麻煩。ide
軟件工程,強調代碼複用的重要性。那麼在離線模型訓練和在線模型預測之間,如何作到代碼複用?在深度學習領域,最受工業界歡迎的框架 TensorFlow 爲了解決這個問題,提出了 TensorFlow Serving。它是一個爲生產環境設計的模型部署系統,目的是使得訓練好的模型能夠方便地部署在服務器上,作實時的預測。傳統的機器學習框架,尤爲是分佈式機器學習框架,不多有相似的解決方案。其中最主要的問題在於,相似於 Spark MLlib 這樣的分佈式學習框架,主要適用於數據規模較大的應用場景。它採用分佈式批處理(batch processing)的機制,可以在多臺機器上並行地處理大量預測樣本,具備較高的吞吐量。然而,對於實時推薦這樣的線上服務來 說,高吞吐量並非它須要的,低延遲纔是這種線上服務最大的要求。Spark MLlib 在預測時須要將數據轉換爲 DataFrame
這樣的分佈式數據結構,而這種轉換會產生秒級別的常常性開支(overhead),這在毫秒級延遲的線上服務中是不能夠接受的。
所以,咱們面臨了一個兩難的困境:既但願模型能夠以分佈式高吞吐量的方式進行離線訓練,同時又但願訓練好的模型能夠在線上以低延遲的方式進行實時預測。這就是在線預測和離線訓練之間的矛盾。
爲了解決上面所說的矛盾,咱們考慮在 Spark MLlib 的接口上作一些改動,給它加上實時預測的接口。
Spark MLlib 的 Pipeline 接口的通用性,在很大程度上依賴 DataFrame
這一通用的數據結構。對於 Spark MLlib 來講,在 Pipeline 中流動的數據,都是使用 DataFrame
包裝的,每一個 Transformer
都接收一個 DataFrame
,對其作一個「變形」的操做,而後輸出一個新的 DataFrame
。DataFrame
的 schema 經過 Transformer
的參數(Param
)來約定。
這一套行事方式在分佈式計算中很是好用,但在對延遲要求很高的線上服務中,就不太適用了。其中,最主要的緣由是,在線上單機計算的環境中將數據轉化爲 DataFrame
會有不少沒必要要的開銷,影響服務的延遲。所以,一個直觀的想法是:在 Spark MLlib 中爲 Transformer
提供一個不依賴 DataFrame
的接口,使其內部核心的計算邏輯直接暴露出來,而後在線上服務中使用這一接口,從而繞過將數據用 DataFrame
封裝這一耗時的操做。
通過觀察,咱們發現,推薦系統中最經常使用的模型預測流水線,主要由三個部分組成:特徵向量化,特徵預處理,和模型預測。其中,特徵向量化是將各類數據類型的原始特徵轉化爲向量的過程,它能夠由一個特徵向量化器(feature vectorizer)完成,其輸入是一個某種數據類型的原始特徵(好比說一個Map),輸出是一個特徵向量;特徵預處理是對特徵向量進行變形、歸一化等操做的過程,它能夠由一個或多個特徵轉換器(feature transformer)組成,其中每一個轉換器的輸入和輸入都是一個向量類型的特徵;模型預測是指將特徵輸入一個訓練好的分類器、迴歸器,或排序器中,獲得一個「分數」的過程,其中「分數」能夠表示離散的標籤(分類器),也能夠表示連續的值(迴歸器),甚至能夠是一個排名(排序器),它的輸入是一個向量類型的特徵,輸出是一個標量值。因爲樣本特徵在存儲的時候可能不是採用向量這種類型,而通用的特徵處理器都是假設了特徵爲向量,因此通常咱們會在第一步首先將非向量類型的特徵轉化爲向量類型。
基於上面的分析,咱們設計了兩個面向實時預測的接口,分別用於特徵向量化、預處理和模型預測。
預測器的抽象接口是 OnlinePredictor
。它有一個類型參數 FeaturesType
,表示這個預測器能夠接收的特徵類型。咱們注意到,對於某些預測器,咱們有時須要獲得兩種類型的預測得分,好比對於二分類器,可能不只須要輸出分類標籤,還要輸出原始的預測得分。所以,在 OnlinePredictor
中有 predict
和 predictRaw
兩個預測接口,根據具體的模型實現須要,能夠分別設定兩個接口結果的含義。
OnlinePredictor
是一個可供線上實時預測時使用的接口,所以它的 predict
函數接受的輸入類型直接是特徵的類型,而非 Spark 提供的 DataFrame
。 此外,它同時繼承了 Spark MLlib 中的 Transformer
,所以它也能夠在 transform
函數中接受一個 DataFrame
做爲輸入,從而支持大批量分佈式場景下的預測。
若是說 OnlinePredictor
是模型預測的抽象的話,那麼 FeatureTransformer
就是特徵向量化和預處理的抽象。FeatureTransformer
有兩個類型參數,IN
和 OUT
,分別表示輸入特徵類型和輸出特徵類型。與 OnlinePredictor
相似,它除了提供批量處理的 DataFrame
接口以外,還提供了在線上實時預測時使用的 transformOne
和 transformBatch
,能夠直接接收特徵,無須使用 DataFrame
包裝。
爲了在 FeatureTransformer
的實時預測接口和分佈式計算接口共享處理邏輯,它在內部提供了一 個 transformFunc
,它是一個 IN => OUT
類型的函數。具體的特徵處理器只需在這個函數中實現處理邏輯,在處理線上接口時,會直接調用這個函數,而在處理批量數據時會將這個函數包裝爲 Spark 中的用戶定義函數(user defined function, UDF),廣播到每一個節點上對數據進行分佈式處理。
在特徵預處理中,一個典型的操做是特徵標準化。它會統計特徵在每一個維度上的平均值和標準差,並對輸入的特徵進行標準化——即減去均值後除以標準差——使全部特徵的均值都爲 0,標準差都爲 1。在面向實時預測的接口中,特徵標準化的操做則能夠由一個 FeatureTransformer[Vector, Vector]
類型的特徵轉換器來完成,它能夠接收一個向量類型的原始特徵,輸出一個向量類型的標準化後的特徵。在內部,它的實現方式與 Spark MLlib 中的類似,只不過它的特徵轉換邏輯是實如今 transformFunc
函數中的。所以,它不只能夠轉換一個 DataFrame
中的特徵列,也能夠直接轉換一個向量類型的特徵值。
特徵向量化則能夠看做是一類特殊的特徵預處理,它是一個 FeatureTransformer[FeaturesType, Vector]
的特徵轉換器,其中 FeaturesType
表示自定義的輸入特徵類型,輸出一個向量類型的特徵。
有了 OnlinePredictor
和 FeatureTransformer
,咱們能夠構建各類各樣的預測器和特徵預處理器,並同時在離線分佈式環境和線上實時預測中使用它們。 爲了更好地封裝模型訓練過程,使得在線上再也不須要感知任何關於模型訓練的細節, 咱們又進一步提出了面向實時預測的流水線——OnlinePredictionPipeline
。
正如上面所說,推薦系統中最經常使用的模型預測流水線,主要由特徵向量化,特徵預處理,和模型預測三個部分組成。所以,在 OnlinePredictionPipeline
中,咱們將組件也分紅了三個部分:一個特徵向量化處理器(vectorizer)、一個或多個特徵轉換器(transformers),和一個最終的預測器 (predictor)。原始的特徵像流水線上的物品同樣,依次經過向量化、特徵變形,以及最後的模型預測,最終輸出一個預測的分數。
在 API 層面,OnlinePredictionPipeline
也是一種 OnlinePredictor
,它能夠接受一個原始特徵,並對其進行向量化、預處理和模型預測;也能夠在離線計算環境中處理一個 DataFrame
,對批量的數據進行預測並輸出一個 DataFrame
。
在離線分佈式訓練時,能夠對整個 OnlinePredictionPipeline
進行組裝和訓練,並將訓練獲得的流水線模型整個持久化到文件系統中。在預測階段,若是是離線計算環境,可使用 Spark MLlib 的 transform
接口進行分佈式計算;若是是在線服務,能夠在不感知內部具體流程和實現的狀況下,將其當作爲 一個 OnlinePredictor
,使用 predict
接口對輸入的單條原始特徵進行預測。
有了 OnlinePredictionPipeline
,在線下模型訓練時,不管是增長特徵處理器,仍是替換預測模型,都不須要改動線上服務的預測邏輯,作到了「一套系統,兩處運行」。
同時,因爲 OnlinePredictor
和 FeatureTransformer
兩個面向實時預測的接口直接將計算邏輯暴露了出來,不須要在線上服務中將特徵轉化爲 DataFrame
,減少了線上服務的延遲。實驗代表,在線上環境中,相比於直接使用 Spark MLlib 的 DataFrame
接口,面向實時預測的接口能夠有效下降延遲。
咱們分析了推薦系統對於機器學習的使用場景和模式,指出了離線訓練和在線預測兩個計算場景的特色,提出了在這兩個場景下複用代碼的可能性和現有框架沒法解決的問題。對此,咱們在 Spark MLlib 提出的流水線接口上作了進一步擴展,提出了一種面向實時預測的接口,使得機器學習流水線不只保留了在批量處理時高吞吐量的特性,並且顯著下降了在實時預測場景下的延遲。除此以外,它還使得模型的離線訓練與在線預測的代碼得以複用,簡化了模型的部署與維護。
一個成熟的推薦系統,離不開一個健壯的機器學習庫的支撐。在即刻,咱們持續研究複雜的前沿機器學習算法,並將其應用於真實的推薦系統中;咱們還關注如何創建一套靈活、敏捷的部署流程,方便快速迭代模型。
----
做者:歐承祖
參考連接: