3. 預(yù)測系統(tǒng)核心介紹
3.1 預(yù)測系統(tǒng)核心層技術(shù)選型
預(yù)測系統(tǒng)核心層技術(shù)主要分為四層:基礎(chǔ)層、框架層、工具層和算法層
基礎(chǔ)層: HDFS用來做數(shù)據(jù)存儲,Yarn用來做資源調(diào)度,BDP(Big Data Platform)是京東自己研發(fā)的大數(shù)據(jù)平臺,我們主要用它來做任務(wù)調(diào)度。
框架層: 以Spark RDD、Spark SQL、Hive為主, MapReduce程序占一小部分,是原先遺留下來的,目前正逐步替換成Spark RDD。 選擇Spark除了對性能的考慮外,還考慮了Spark程序開發(fā)的高效率、多語言特性以及對機器學(xué)習(xí)算法的支持。在Spark開發(fā)語言上我們選擇了Python,原因有以下三點:
Python有很多不錯的機器學(xué)習(xí)算法包可以使用,比起Spark的MLlib,算法的準確度更高。我們用GBDT做過對比,發(fā)現(xiàn)xgboost比MLlib里面提供的提升樹模型預(yù)測準確度高出大概5%~10%。雖然直接使用Spark自帶的機器學(xué)習(xí)框架會節(jié)省我們的開發(fā)成本,但預(yù)測準確度對于我們來說至關(guān)重要,每提升1%的準確度,就可能會帶來成本的成倍降低。
我們的團隊中包括開發(fā)工程師和算法工程師,對于算法工程師而言他們更擅長使用Python進行數(shù)據(jù)分析,使用Java或Scala會有不小的學(xué)習(xí)成本。
對比其他語言,我們發(fā)現(xiàn)使用Python的開發(fā)效率是最高的,并且對于一個新人,學(xué)習(xí)Python比學(xué)習(xí)其他語言更加容易。
工具層: 一方面我們會結(jié)合自身業(yè)務(wù)有針對性的開發(fā)一些算法,另一方面我們會直接使用業(yè)界比較成熟的算法和模型,這些算法都封裝在第三方Python包中。我們比較常用的包有xgboost、numpy、pandas、sklearn、scipy和hyperopt等。
Xgboost:它是Gradient Boosting Machine的一個C++實現(xiàn),xgboost最大的特點在于,它能夠自動利用CPU的多線程進行并行,同時在算法上加以改進提高了精度。
numpy:是Python的一種開源的數(shù)值計算擴展。這種工具可用來存儲和處理大型矩陣,比Python自身的嵌套列表結(jié)構(gòu)要高效的多(該結(jié)構(gòu)也可以用來表示矩陣)。
pandas:是基于NumPy 的一種工具,該工具是為了解決數(shù)據(jù)分析任務(wù)而創(chuàng)建的。Pandas 納入了大量庫和一些標準的數(shù)據(jù)模型,提供了高效地操作大型數(shù)據(jù)集所需的工具。
sklearn:是Python重要的機器學(xué)習(xí)庫,支持包括分類、回歸、降維和聚類四大機器學(xué)習(xí)算法。還包含了特征提取、數(shù)據(jù)處理和模型評估三大模塊。
scipy:是在NumPy庫的基礎(chǔ)上增加了眾多的數(shù)學(xué)、科學(xué)以及工程計算中常用的庫函數(shù)。例如線性代數(shù)、常微分方程數(shù)值求解、信號處理、圖像處理和稀疏矩陣等等。
算法層: 我們用到的算法模型非常多,原因是京東的商品品類齊全、業(yè)務(wù)復(fù)雜,需要根據(jù)不同的情況采用不同的算法模型。我們有一個獨立的系統(tǒng)來為算法模型與商品之間建立匹配關(guān)系,有些比較復(fù)雜的預(yù)測業(yè)務(wù)還需要使用多個模型。我們使用的算法總體上可以分為三類:時間序列、機器學(xué)習(xí)和結(jié)合業(yè)務(wù)開發(fā)的一些獨有的算法。
1. 機器學(xué)習(xí)算法主要包括GBDT、LASSO和RNN :
GBDT:是一種迭代的決策樹算法,該算法由多棵決策樹組成,所有樹的結(jié)論累加起來做最終答案。我們用它來預(yù)測高銷量,但歷史規(guī)律不明顯的商品。
RNN:這種網(wǎng)絡(luò)的內(nèi)部狀態(tài)可以展示動態(tài)時序行為。不同于前饋神經(jīng)網(wǎng)絡(luò)的是,RNN可以利用它內(nèi)部的記憶來處理任意時序的輸入序列,這讓它可以更容易處理如時序預(yù)測、語音識別等。
LASSO:該方法是一種壓縮估計。它通過構(gòu)造一個罰函數(shù)得到一個較為精煉的模型,使得它壓縮一些系數(shù),同時設(shè)定一些系數(shù)為零。因此保留了子集收縮的優(yōu)點,是一種處理具有復(fù)共線性數(shù)據(jù)的有偏估計。用來預(yù)測低銷量,歷史數(shù)據(jù)平穩(wěn)的商品效果較好。
2. 時間序列主要包括ARIMA和Holt winters :
ARIMA:全稱為自回歸積分滑動平均模型,于70年代初提出的一個著名時間序列預(yù)測方法,我們用它來主要預(yù)測類似庫房單量這種平穩(wěn)的序列。
Holt winters:又稱三次指數(shù)平滑算法,也是一個經(jīng)典的時間序列算法,我們用它來預(yù)測季節(jié)性和趨勢都很明顯的商品。
3. 結(jié)合業(yè)務(wù)開發(fā)的獨有算法包括WMAStockDT、SimilarityModel和NewProduct等:
WMAStockDT:庫存決策樹模型,用來預(yù)測受庫存狀態(tài)影響較大的商品。
SimilarityModel:相似品模型,使用指定的同類品數(shù)據(jù)來預(yù)測某商品未來銷量。
NewProduct:新品模型,顧名思義就是用來預(yù)測新品的銷量。
3.2 預(yù)測系統(tǒng)核心流程
預(yù)測核心流程主要包括兩類:以機器學(xué)習(xí)算法為主的流程和以時間序列分析為主的流程。
1. 以機器學(xué)習(xí)算法為主的流程如下:
特征構(gòu)建:通過數(shù)據(jù)分析、模型試驗確定主要特征,通過一系列任務(wù)生成標準格式的特征數(shù)據(jù)。
模型選擇:不同的商品有不同的特性,所以首先會根據(jù)商品的銷量高低、新品舊品、假節(jié)日敏感性等因素分配不同的算法模型。
特征選擇:對一批特征進行篩選過濾不需要的特征,不同類型的商品特征不同。
樣本分區(qū):對訓(xùn)練數(shù)據(jù)進行分組,分成多組樣本,真正訓(xùn)練時針對每組樣本生成一個模型文件。一般是同類型商品被分成一組,比如按品類維度分組,這樣做是考慮并行化以及模型的準確性。
模型參數(shù):選擇最優(yōu)的模型參數(shù),合適的參數(shù)將提高模型的準確度,因為需要對不同的參數(shù)組合分別進行模型訓(xùn)練和預(yù)測,所以這一步是非常耗費資源。
模型訓(xùn)練:待特征、模型、樣本都確定好后就可以進行模型訓(xùn)練,訓(xùn)練往往會耗費很長時間,訓(xùn)練后會生成模型文件,存儲在HDFS中。
模型預(yù)測:讀取模型文件進行預(yù)測執(zhí)行。
多模型擇優(yōu):為了提高預(yù)測準確度,我們可能會使用多個算法模型,當每個模型的預(yù)測結(jié)果輸出后系統(tǒng)會通過一些規(guī)則來選擇一個最優(yōu)的預(yù)測結(jié)果。
預(yù)測值異常攔截:我們發(fā)現(xiàn)越是復(fù)雜且不易解釋的算法越容易出現(xiàn)極個別預(yù)測值異常偏高的情況,這種預(yù)測偏高無法結(jié)合歷史數(shù)據(jù)進行解釋,因此我們會通過一些規(guī)則將這些異常值攔截下來,并且用一個更加保守的數(shù)值代替。
模型評價:計算預(yù)測準確度,我們通常用使用mapd來作為評價指標。
誤差分析:通過分析預(yù)測準確度得出一個誤差在不同維度上的分布,以便給算法優(yōu)化提供參考依據(jù)。
2. 以時間序列分析為主的預(yù)測流程如下:
3.3 Spark在預(yù)測核心層的應(yīng)用
我們使用Spark SQL和Spark RDD相結(jié)合的方式來編寫程序,對于一般的數(shù)據(jù)處理,我們使用Spark的方式與其他無異,但是對于模型訓(xùn)練、預(yù)測這些需要調(diào)用算法接口的邏輯就需要考慮一下并行化的問題了。我們平均一個訓(xùn)練任務(wù)在一天處理的數(shù)據(jù)量大約在500G左右,雖然數(shù)據(jù)規(guī)模不是特別的龐大,但是Python算法包提供的算法都是單進程執(zhí)行。我們計算過,如果使用一臺機器訓(xùn)練全部品類數(shù)據(jù)需要一個星期的時間,這是無法接收的,所以我們需要借助Spark這種分布式并行計算框架來將計算分攤到多個節(jié)點上實現(xiàn)并行化處理。
我們實現(xiàn)的方法很簡單,首先需要在集群的每個節(jié)點上安裝所需的全部Python包,然后在編寫Spark程序時考慮通過某種規(guī)則將數(shù)據(jù)分區(qū),比如按品類維度,通過groupByKey操作將數(shù)據(jù)重新分區(qū),每一個分區(qū)是一個樣本集合并進行獨立的訓(xùn)練,以此達到并行化。流程如下圖所示:
偽碼如下:
repartitionBy方法即設(shè)置一個重分區(qū)的邏輯返回(K,V)結(jié)構(gòu)RDD,train方法是訓(xùn)練數(shù)據(jù),在train方法里面會調(diào)用Python算法包接口。saveAsPickleFile是Spark Python獨有的一個Action操作,支持將RDD保存成序列化后的sequnceFile格式的文件,在序列化過程中會以10個一批的方式進行處理,保存模型文件非常適合。
雖然原理簡單,但存在著一個難點,即以什么樣的規(guī)則進行分區(qū),key應(yīng)該如何設(shè)置。為了解決這個問題我們需要考慮幾個方面,第一就是哪些數(shù)據(jù)應(yīng)該被聚合到一起進行訓(xùn)練,第二就是如何避免數(shù)據(jù)傾斜。
針對第一個問題我們做了如下幾點考慮:
被分在一個分區(qū)的數(shù)據(jù)要有一定的相似性,這樣訓(xùn)練的效果才會更好,比如按品類分區(qū)就是個典型例子。
分析商品的特性,根據(jù)特性的不同選擇不同的模型,例如高銷商品和低銷商品的預(yù)測模型是不一樣的,即使是同一模型使用的特征也可能不同,比如對促銷敏感的商品就需要更多與促銷相關(guān)特征,相同模型相同特征的商品應(yīng)傾向于分在一個分區(qū)中。
針對第二個問題我們采用了如下的方式解決:
對于數(shù)據(jù)量過大的分區(qū)進行隨機抽樣選取。
對于數(shù)據(jù)量過大的分區(qū)還可以做二次拆分,比如圖書小說這個品類數(shù)據(jù)量明顯大于其他品類,于是就可以分析小說品類下的子品類數(shù)據(jù)量分布情況,并將子品類合并成新的幾個分區(qū)。
對于數(shù)據(jù)量過小這種情況則需要考慮進行幾個分區(qū)數(shù)據(jù)的合并處理。
總之對于后兩種處理方式可以單獨通過一個Spark任務(wù)定期運行,并將這種分區(qū)規(guī)則保存。
未完待續(xù)......