重慶分公司,新征程啟航
為企業提供網站建設、域名注冊、服務器等服務
為企業提供網站建設、域名注冊、服務器等服務
XGBoost參數調優完全指南(附Python代碼)
成都創新互聯是一家專注于網站建設、成都網站設計和內蒙古服務器托管的網絡公司,有著豐富的建站經驗和案例。
譯注:文內提供的代碼和運行結果有一定差異,可以從這里完整代碼對照參考。另外,我自己跟著教程做的時候,發現我的庫無法解析字符串類型的特征,所以只用其中一部分特征做的,具體數值跟文章中不一樣,反而可以幫助理解文章。所以大家其實也可以小小修改一下代碼,不一定要完全跟著教程做~ ^0^
需要提前安裝好的庫:簡介如果你的預測模型表現得有些不盡如人意,那就用XGBoost吧。XGBoost算法現在已經成為很多數據工程師的重要武器。它是一種十分精致的算法,可以處理各種不規則的數據。
構造一個使用XGBoost的模型十分簡單。但是,提高這個模型的表現就有些困難(至少我覺得十分糾結)。這個算法使用了好幾個參數。所以為了提高模型的表現,參數的調整十分必要。在解決實際問題的時候,有些問題是很難回答的——你需要調整哪些參數?這些參數要調到什么值,才能達到理想的輸出?
這篇文章最適合剛剛接觸XGBoost的人閱讀。在這篇文章中,我們會學到參數調優的技巧,以及XGboost相關的一些有用的知識。以及,我們會用Python在一個數據集上實踐一下這個算法。你需要知道的XGBoost(eXtreme Gradient Boosting)是Gradient Boosting算法的一個優化的版本。特別鳴謝:我個人十分感謝Mr Sudalai Rajkumar (aka SRK)大神的支持,目前他在AV Rank中位列第二。如果沒有他的幫助,就沒有這篇文章。在他的幫助下,我們才能給無數的數據科學家指點迷津。給他一個大大的贊!內容列表1、XGBoost的優勢
2、理解XGBoost的參數
3、調整參數(含示例)1、XGBoost的優勢XGBoost算法可以給預測模型帶來能力的提升。當我對它的表現有更多了解的時候,當我對它的高準確率背后的原理有更多了解的時候,我發現它具有很多優勢:1、正則化標準GBM的實現沒有像XGBoost這樣的正則化步驟。正則化對減少過擬合也是有幫助的。 實際上,XGBoost以“正則化提升(regularized boosting)”技術而聞名。2、并行處理XGBoost可以實現并行處理,相比GBM有了速度的飛躍。 不過,眾所周知,Boosting算法是順序處理的,它怎么可能并行呢?每一課樹的構造都依賴于前一棵樹,那具體是什么讓我們能用多核處理器去構造一個樹呢?我希望你理解了這句話的意思。 XGBoost 也支持Hadoop實現。3、高度的靈活性XGBoost 允許用戶定義自定義優化目標和評價標準 它對模型增加了一個全新的維度,所以我們的處理不會受到任何限制。4、缺失值處理XGBoost內置處理缺失值的規則。 用戶需要提供一個和其它樣本不同的值,然后把它作為一個參數傳進去,以此來作為缺失值的取值。XGBoost在不同節點遇到缺失值時采用不同的處理方法,并且會學習未來遇到缺失值時的處理方法。5、剪枝當分裂時遇到一個負損失時,GBM會停止分裂。因此GBM實際上是一個貪心算法。 XGBoost會一直分裂到指定的最大深度(max_depth),然后回過頭來剪枝。如果某個節點之后不再有正值,它會去除這個分裂。 這種做法的優點,當一個負損失(如-2)后面有個正損失(如+10)的時候,就顯現出來了。GBM會在-2處停下來,因為它遇到了一個負值。但是XGBoost會繼續分裂,然后發現這兩個分裂綜合起來會得到+8,因此會保留這兩個分裂。6、內置交叉驗證XGBoost允許在每一輪boosting迭代中使用交叉驗證。因此,可以方便地獲得最優boosting迭代次數。 而GBM使用網格搜索,只能檢測有限個值。7、在已有的模型基礎上繼續XGBoost可以在上一輪的結果上繼續訓練。這個特性在某些特定的應用上是一個巨大的優勢。 sklearn中的GBM的實現也有這個功能,兩種算法在這一點上是一致的。相信你已經對XGBoost強大的功能有了點概念。注意這是我自己總結出來的幾點,你如果有更多的想法,盡管在下面評論指出,我會更新這個列表的!2、XGBoost的參數XGBoost的作者把所有的參數分成了三類:
1、通用參數:宏觀函數控制。
2、Booster參數:控制每一步的booster(tree/regression)。
3、學習目標參數:控制訓練目標的表現。
在這里我會類比GBM來講解,所以作為一種基礎知識。通用參數這些參數用來控制XGBoost的宏觀功能。1、booster[默認gbtree]選擇每次迭代的模型,有兩種選擇:
gbtree:基于樹的模型
gbliner:線性模型2、silent[默認0]當這個參數值為1時,靜默模式開啟,不會輸出任何信息。 一般這個參數就保持默認的0,因為這樣能幫我們更好地理解模型。3、nthread[默認值為最大可能的線程數]這個參數用來進行多線程控制,應當輸入系統的核數。 如果你希望使用CPU全部的核,那就不要輸入這個參數,算法會自動檢測它。
還有兩個參數,XGBoost會自動設置,目前你不用管它。接下來咱們一起看booster參數。booster參數盡管有兩種booster可供選擇,我這里只介紹tree booster,因為它的表現遠遠勝過linear booster,所以linear booster很少用到。1、eta[默認0.3]和GBM中的 learning rate 參數類似。 通過減少每一步的權重,可以提高模型的魯棒性。 典型值為0.01-0.2。2、min_child_weight[默認1]決定最小葉子節點樣本權重和。 和GBM的 min_child_leaf 參數類似,但不完全一樣。XGBoost的這個參數是最小樣本權重的和,而GBM參數是最小樣本總數。 這個參數用于避免過擬合。當它的值較大時,可以避免模型學習到局部的特殊樣本。 但是如果這個值過高,會導致欠擬合。這個參數需要使用CV來調整。3、max_depth[默認6]和GBM中的參數相同,這個值為樹的最大深度。 這個值也是用來避免過擬合的。max_depth越大,模型會學到更具體更局部的樣本。 需要使用CV函數來進行調優。 典型值:3-104、max_leaf_nodes樹上最大的節點或葉子的數量。 可以替代max_depth的作用。因為如果生成的是二叉樹,一個深度為n的樹最多生成n2個葉子。 如果定義了這個參數,GBM會忽略max_depth參數。5、gamma[默認0]在節點分裂時,只有分裂后損失函數的值下降了,才會分裂這個節點。Gamma指定了節點分裂所需的最小損失函數下降值。 這個參數的值越大,算法越保守。這個參數的值和損失函數息息相關,所以是需要調整的。6、max_delta_step[默認0]這參數限制每棵樹權重改變的最大步長。如果這個參數的值為0,那就意味著沒有約束。如果它被賦予了某個正值,那么它會讓這個算法更加保守。 通常,這個參數不需要設置。但是當各類別的樣本十分不平衡時,它對邏輯回歸是很有幫助的。 這個參數一般用不到,但是你可以挖掘出來它更多的用處。7、subsample[默認1]和GBM中的subsample參數一模一樣。這個參數控制對于每棵樹,隨機采樣的比例。 減小這個參數的值,算法會更加保守,避免過擬合。但是,如果這個值設置得過小,它可能會導致欠擬合。 典型值:0.5-18、colsample_bytree[默認1]和GBM里面的max_features參數類似。用來控制每棵隨機采樣的列數的占比(每一列是一個特征)。 典型值:0.5-19、colsample_bylevel[默認1]用來控制樹的每一級的每一次分裂,對列數的采樣的占比。 我個人一般不太用這個參數,因為subsample參數和colsample_bytree參數可以起到相同的作用。但是如果感興趣,可以挖掘這個參數更多的用處。10、lambda[默認1]權重的L2正則化項。(和Ridge regression類似)。 這個參數是用來控制XGBoost的正則化部分的。雖然大部分數據科學家很少用到這個參數,但是這個參數在減少過擬合上還是可以挖掘出更多用處的。11、alpha[默認1]權重的L1正則化項。(和Lasso regression類似)。 可以應用在很高維度的情況下,使得算法的速度更快。12、scale_pos_weight[默認1]在各類別樣本十分不平衡時,把這個參數設定為一個正值,可以使算法更快收斂。學習目標參數這個參數用來控制理想的優化目標和每一步結果的度量方法。1、objective[默認reg:linear]這個參數定義需要被最小化的損失函數。最常用的值有:
binary:logistic 二分類的邏輯回歸,返回預測的概率(不是類別)。 multi:softmax 使用softmax的多分類器,返回預測的類別(不是概率)。
在這種情況下,你還需要多設一個參數:num_class(類別數目)。 multi:softprob 和multi:softmax參數一樣,但是返回的是每個數據屬于各個類別的概率。2、eval_metric[默認值取決于objective參數的取值]對于有效數據的度量方法。 對于回歸問題,默認值是rmse,對于分類問題,默認值是error。 典型值有:
rmse 均方根誤差(∑Ni=1?2N??????√) mae 平均絕對誤差(∑Ni=1|?|N) logloss 負對數似然函數值 error 二分類錯誤率(閾值為0.5) merror 多分類錯誤率 mlogloss 多分類logloss損失函數 auc 曲線下面積3、seed(默認0)隨機數的種子 設置它可以復現隨機數據的結果,也可以用于調整參數如果你之前用的是Scikit-learn,你可能不太熟悉這些參數。但是有個好消息,python的XGBoost模塊有一個sklearn包,XGBClassifier。這個包中的參數是按sklearn風格命名的。會改變的函數名是:
1、eta -learning_rate
2、lambda-reg_lambda
3、alpha-reg_alpha
你肯定在疑惑為啥咱們沒有介紹和GBM中的’n_estimators’類似的參數。XGBClassifier中確實有一個類似的參數,但是,是在標準XGBoost實現中調用擬合函數時,把它作為’num_boosting_rounds’參數傳入。調整參數(含示例)我已經對這些數據進行了一些處理:City變量,因為類別太多,所以刪掉了一些類別。 DOB變量換算成年齡,并刪除了一些數據。 增加了 EMI_Loan_Submitted_Missing 變量。如果EMI_Loan_Submitted變量的數據缺失,則這個參數的值為1。否則為0。刪除了原先的EMI_Loan_Submitted變量。 EmployerName變量,因為類別太多,所以刪掉了一些類別。 因為Existing_EMI變量只有111個值缺失,所以缺失值補充為中位數0。 增加了 Interest_Rate_Missing 變量。如果Interest_Rate變量的數據缺失,則這個參數的值為1。否則為0。刪除了原先的Interest_Rate變量。 刪除了Lead_Creation_Date,從直覺上這個特征就對最終結果沒什么幫助。 Loan_Amount_Applied, Loan_Tenure_Applied 兩個變量的缺項用中位數補足。 增加了 Loan_Amount_Submitted_Missing 變量。如果Loan_Amount_Submitted變量的數據缺失,則這個參數的值為1。否則為0。刪除了原先的Loan_Amount_Submitted變量。 增加了 Loan_Tenure_Submitted_Missing 變量。如果 Loan_Tenure_Submitted 變量的數據缺失,則這個參數的值為1。否則為0。刪除了原先的 Loan_Tenure_Submitted 變量。 刪除了LoggedIn, Salary_Account 兩個變量 增加了 Processing_Fee_Missing 變量。如果 Processing_Fee 變量的數據缺失,則這個參數的值為1。否則為0。刪除了原先的 Processing_Fee 變量。 Source前兩位不變,其它分成不同的類別。 進行了量化和獨熱編碼(一位有效編碼)。如果你有原始數據,可以從資源庫里面data_preparation的Ipython notebook 文件,然后自己過一遍這些步驟。首先,import必要的庫,然后加載數據。#Import libraries:
import pandas as pd
import numpy as np
import xgboost as xgb
from xgboost.sklearn import XGBClassifier
from sklearn import cross_validation, metrics ? #Additional ? ? scklearn functions
from sklearn.grid_search import GridSearchCV ? #Perforing grid search
import matplotlib.pylab as plt
%matplotlib inline
from matplotlib.pylab import rcParams
rcParams['figure.figsize'] = 12, 4
train = pd.read_csv('train_modified.csv')
target = 'Disbursed'
IDcol = 'ID'
注意我import了兩種XGBoost:xgb - 直接引用xgboost。接下來會用到其中的“cv”函數。 XGBClassifier - 是xgboost的sklearn包。這個包允許我們像GBM一樣使用Grid Search 和并行處理。在向下進行之前,我們先定義一個函數,它可以幫助我們建立XGBoost models 并進行交叉驗證。好消息是你可以直接用下面的函數,以后再自己的models中也可以使用它。def modelfit(alg, dtrain, predictors,useTrainCV=True, cv_folds=5, early_stopping_rounds=50):
if useTrainCV:
xgb_param = alg.get_xgb_params()
xgtrain = xgb.DMatrix(dtrain[predictors].values, label=dtrain[target].values)
cvresult = xgb.cv(xgb_param, xgtrain, num_boost_round=alg.get_params()['n_estimators'], nfold=cv_folds,
metrics='auc', early_stopping_rounds=early_stopping_rounds, show_progress=False)
alg.set_params(n_estimators=cvresult.shape[0])
#Fit the algorithm on the data
alg.fit(dtrain[predictors], dtrain['Disbursed'],eval_metric='auc')
#Predict training set:
dtrain_predictions = alg.predict(dtrain[predictors])
dtrain_predprob = alg.predict_proba(dtrain[predictors])[:,1]
#Print model report:
print "\nModel Report"
print "Accuracy : %.4g" % metrics.accuracy_score(dtrain['Disbursed'].values, dtrain_predictions)
print "AUC Score (Train): %f" % metrics.roc_auc_score(dtrain['Disbursed'], dtrain_predprob)
feat_imp = pd.Series(alg.booster().get_fscore()).sort_values(ascending=False)
feat_imp.plot(kind='bar', title='Feature Importances')
plt.ylabel('Feature Importance Score')
這個函數和GBM中使用的有些許不同。不過本文章的重點是講解重要的概念,而不是寫代碼。如果哪里有不理解的地方,請在下面評論,不要有壓力。注意xgboost的sklearn包沒有“feature_importance”這個量度,但是get_fscore()函數有相同的功能。參數調優的一般方法。我們會使用和GBM中相似的方法。需要進行如下步驟:
選擇較高的學習速率(learning rate)。一般情況下,學習速率的值為0.1。但是,對于不同的問題,理想的學習速率有時候會在0.05到0.3之間波動。選擇對應于此學習速率的理想決策樹數量。XGBoost有一個很有用的函數“cv”,這個函數可以在每一次迭代中使用交叉驗證,并返回理想的決策樹數量。
2. 對于給定的學習速率和決策樹數量,進行決策樹特定參數調優(max_depth, min_child_weight, gamma, subsample, colsample_bytree)。在確定一棵樹的過程中,我們可以選擇不同的參數,待會兒我會舉例說明。
3. xgboost的正則化參數的調優。(lambda, alpha)。這些參數可以降低模型的復雜度,從而提高模型的表現。
4. 降低學習速率,確定理想參數。咱們一起詳細地一步步進行這些操作。第一步:確定學習速率和tree_based 參數調優的估計器數目。為了確定boosting 參數,我們要先給其它參數一個初始值。咱們先按如下方法取值:
1、max_depth = 5 :這個參數的取值最好在3-10之間。我選的起始值為5,但是你也可以選擇其它的值。起始值在4-6之間都是不錯的選擇。
2、min_child_weight = 1:在這里選了一個比較小的值,因為這是一個極不平衡的分類問題。因此,某些葉子節點下的值會比較小。
3、gamma = 0: 起始值也可以選其它比較小的值,在0.1到0.2之間就可以。這個參數后繼也是要調整的。
4、subsample,colsample_bytree = 0.8: 這個是最常見的初始值了。典型值的范圍在0.5-0.9之間。
5、scale_pos_weight = 1: 這個值是因為類別十分不平衡。
注意哦,上面這些參數的值只是一個初始的估計值,后繼需要調優。這里把學習速率就設成默認的0.1。然后用xgboost中的cv函數來確定最佳的決策樹數量。前文中的函數可以完成這個工作。#Choose all predictors except target ?IDcols
predictors = [x for x in train.columns if x not in [target,IDcol]]
xgb1 = XGBClassifier(
learning_rate =0.1,
n_estimators=1000,
max_depth=5,
min_child_weight=1,
gamma=0,
subsample=0.8,
colsample_bytree=0.8,
objective= 'binary:logistic',
nthread=4,
scale_pos_weight=1,
seed=27)
modelfit(xgb1, train, predictors)
從輸出結果可以看出,在學習速率為0.1時,理想的決策樹數目是140。這個數字對你而言可能比較高,當然這也取決于你的系統的性能。注意:在AUC(test)這里你可以看到測試集的AUC值。但是如果你在自己的系統上運行這些命令,并不會出現這個值。因為數據并不公開。這里提供的值僅供參考。生成這個值的代碼部分已經被刪掉了。喎?"/kf/ware/vc/" target="_blank" class="keylink"vcD4NCjwvYmxvY2txdW90ZT4NCjxoMSBpZD0="第二步-maxdepth-和-minweight-參數調優"第二步: max_depth 和 min_weight 參數調優我們先對這兩個參數調優,是因為它們對最終結果有很大的影響。首先,我們先大范圍地粗調參數,然后再小范圍地微調。
注意:在這一節我會進行高負荷的柵格搜索(grid search),這個過程大約需要15-30分鐘甚至更久,具體取決于你系統的性能。你也可以根據自己系統的性能選擇不同的值。param_test1 = {
'max_depth':range(3,10,2),
'min_child_weight':range(1,6,2)
}
gsearch1 = GridSearchCV(estimator = XGBClassifier( ? ? ? ? learning_rate =0.1, n_estimators=140, max_depth=5,
min_child_weight=1, gamma=0, subsample=0.8, ? ? ? ? ? ? colsample_bytree=0.8,
objective= 'binary:logistic', nthread=4, ? ? scale_pos_weight=1, seed=27),
param_grid = param_test1, ? ? scoring='roc_auc',n_jobs=4,iid=False, cv=5)
gsearch1.fit(train[predictors],train[target])
gsearch1.grid_scores_, gsearch1.best_params_, ? ? gsearch1.best_score_
這是定時器做的事情。不是cache做的。
定時器很多。我一幫用quartz,它能根據時間表達式去執行。
如果你使用quartz的話,寫個Task,然后再8:00 11:00 18:00時調用cache更新方法就行了。
java.lang.String.compareTo(String)
public int compareTo(String anotherString)按字典順序比較兩個字符串。該比較基于字符串中各個字符的 Unicode 值。將此 String 對象表示的字符序列與參數字符串所表示的字符序列進行比較。如果按字典順序此 String 對象在參數字符串之前,則比較結果為一個負整數。如果按字典順序此 String 對象位于參數字符串之后,則比較結果為一個正整數。如果這兩個字符串相等,則結果為 0;compareTo 只有在方法 equals(Object) 返回 true 時才返回 0。
這是字典排序的定義。如果這兩個字符串不同,則要么它們在某個索引處具有不同的字符,該索引對二者均為有效索引,要么它們的長度不同,或者同時具備上述兩種情況。如果它們在一個或多個索引位置上具有不同的字符,假設 k 是這類索引的最小值;則按照 運算符確定的那個字符串在位置 k 上具有較小的值,其字典順序在其他字符串之前。這種情況下,compareTo 返回這兩個字符串在位置 k 處的兩個不同的 char 值,即值:
this.charAt(k)-anotherString.charAt(k)
如果它們沒有不同的索引位置,則較短字符串在字典順序上位于較長字符串的前面。這種情況下,compareTo 返回這兩個字符串長度的不同,即值:
this.length()-anotherString.length()
指定者:
接口 ComparableString 中的 compareTo
參數:
anotherString - 要比較的 String。
返回:
如果參數字符串等于此字符串,則返回 0 值;如果按字典順序此字符串小于字符串參數,則返回一個小于 0 的值;如果按字典順序此字符串大于字符串參數,則返回一個大于 0 的值。
解決了比較大小的問題,最大值和最小值一個循環搞出來主可以了。
時間序列模型
時間序列預測分析就是利用過去一段時間內某事件時間的特征來預測未來一段時間內該事件的特征。這是一類相對比較復雜的預測建模問題,和回歸分析模型的預測不同,時間序列模型是依賴于事件發生的先后順序的,同樣大小的值改變順序后輸入模型產生的結果是不同的。
舉個栗子:根據過去兩年某股票的每天的股價數據推測之后一周的股價變化;根據過去2年某店鋪每周想消費人數預測下周來店消費的人數等等
RNN 和 LSTM 模型
時間序列模型最常用最強大的的工具就是遞歸神經網絡(recurrent neural network, RNN)。相比與普通神經網絡的各計算結果之間相互獨立的特點,RNN的每一次隱含層的計算結果都與當前輸入以及上一次的隱含層結果相關。通過這種方法,RNN的計算結果便具備了記憶之前幾次結果的特點。
典型的RNN網路結構如下:
右側為計算時便于理解記憶而產開的結構。簡單說,x為輸入層,o為輸出層,s為隱含層,而t指第幾次的計算;V,W,U為權重,其中計算第t次的隱含層狀態時為St = f(U*Xt + W*St-1),實現當前輸入結果與之前的計算掛鉤的目的。對RNN想要更深入的了解可以戳這里。
RNN的局限:
由于RNN模型如果需要實現長期記憶的話需要將當前的隱含態的計算與前n次的計算掛鉤,即St = f(U*Xt + W1*St-1 + W2*St-2 + ... + Wn*St-n),那樣的話計算量會呈指數式增長,導致模型訓練的時間大幅增加,因此RNN模型一般直接用來進行長期記憶計算。
LSTM模型
LSTM(Long Short-Term Memory)模型是一種RNN的變型,最早由Juergen Schmidhuber提出的。經典的LSTM模型結構如下:
LSTM的特點就是在RNN結構以外添加了各層的閥門節點。閥門有3類:遺忘閥門(forget gate),輸入閥門(input gate)和輸出閥門(output gate)。這些閥門可以打開或關閉,用于將判斷模型網絡的記憶態(之前網絡的狀態)在該層輸出的結果是否達到閾值從而加入到當前該層的計算中。如圖中所示,閥門節點利用sigmoid函數將網絡的記憶態作為輸入計算;如果輸出結果達到閾值則將該閥門輸出與當前層的的計算結果相乘作為下一層的輸入(PS:這里的相乘是在指矩陣中的逐元素相乘);如果沒有達到閾值則將該輸出結果遺忘掉。每一層包括閥門節點的權重都會在每一次模型反向傳播訓練過程中更新。更具體的LSTM的判斷計算過程如下圖所示:
LSTM模型的記憶功能就是由這些閥門節點實現的。當閥門打開的時候,前面模型的訓練結果就會關聯到當前的模型計算,而當閥門關閉的時候之前的計算結果就不再影響當前的計算。因此,通過調節閥門的開關我們就可以實現早期序列對最終結果的影響。而當你不不希望之前結果對之后產生影響,比如自然語言處理中的開始分析新段落或新章節,那么把閥門關掉即可。(對LSTM想要更具體的了解可以戳這里)
下圖具體演示了閥門是如何工作的:通過閥門控制使序列第1的輸入的變量影響到了序列第4,6的的變量計算結果。
黑色實心圓代表對該節點的計算結果輸出到下一層或下一次計算;空心圓則表示該節點的計算結果沒有輸入到網絡或者沒有從上一次收到信號。
Python中實現LSTM模型搭建
Python中有不少包可以直接調用來構建LSTM模型,比如pybrain, kears, tensorflow, cikit-neuralnetwork等(更多戳這里)。這里我們選用keras。(PS:如果操作系統用的linux或者mac,強推Tensorflow!!!)
因為LSTM神經網絡模型的訓練可以通過調整很多參數來優化,例如activation函數,LSTM層數,輸入輸出的變量維度等,調節過程相當復雜。這里只舉一個最簡單的應用例子來描述LSTM的搭建過程。
應用實例
基于某家店的某顧客的歷史消費的時間推測該顧客前下次來店的時間。具體數據如下所示:
消費時間
2015-05-15 14:03:512015-05-15 15:32:462015-06-28 18:00:172015-07-16 21:27:182015-07-16 22:04:512015-09-08 14:59:56..
..
具體操作:
1. 原始數據轉化
首先需要將時間點數據進行數值化。將具體時間轉化為時間段用于表示該用戶相鄰兩次消費的時間間隔,然后再導入模型進行訓練是比較常用的手段。轉化后的數據如下:
消費間隔04418054..
..
2.生成模型訓練數據集(確定訓練集的窗口長度)
這里的窗口指需要幾次消費間隔用來預測下一次的消費間隔。這里我們先采用窗口長度為3, 即用t-2, t-1,t次的消費間隔進行模型訓練,然后用t+1次間隔對結果進行驗證。數據集格式如下:X為訓練數據,Y為驗證數據。
PS: 這里說確定也不太合適,因為窗口長度需要根據模型驗證結果進行調整的。
X1 ? ?X2 ? ?X3 ? ?Y0 ? ?44 ? ?18 ? ?044 ? ?18 ? ?0 ? ?54..
.. ? ?
注:直接這樣預測一般精度會比較差,可以把預測值Y根據數值bin到幾類,然后用轉換成one-hot標簽再來訓練會比較好。比如如果把Y按數值范圍分到五類(1:0-20,2:20-40,3:40-60,4:60-80,5:80-100)上式可化為:
X1 ? ?X2 ? ?X3 ? ?Y0 ? ?44 ? ?18 ? ?044 ? ?18 ? ?0 ? ?4...
Y轉化成one-hot以后則是(關于one-hot編碼可以參考這里)
1 ? ?0 ? ?0 ? ?0 ? ?00 ? ?0 ? ?0 ? ?0 ? ?1...
3. 網絡模型結構的確定和調整
這里我們使用python的keras庫。(用java的同學可以參考下deeplearning4j這個庫)。網絡的訓練過程設計到許多參數的調整:比如
需要確定LSTM模塊的激活函數(activation fucntion)(keras中默認的是tanh);
確定接收LSTM輸出的完全連接人工神經網絡(fully-connected artificial neural network)的激活函數(keras中默認為linear);
確定每一層網絡節點的舍棄率(為了防止過度擬合(overfit)),這里我們默認值設定為0.2;
確定誤差的計算方式,這里我們使用均方誤差(mean squared error);
確定權重參數的迭代更新方式,這里我們采用RMSprop算法,通常用于RNN網絡。
確定模型訓練的epoch和batch size(關于模型的這兩個參數具體解釋戳這里)
一般來說LSTM模塊的層數越多(一般不超過3層,再多訓練的時候就比較難收斂),對高級別的時間表示的學習能力越強;同時,最后會加一層普通的神經網路層用于輸出結果的降維。典型結構如下:
如果需要將多個序列進行同一個模型的訓練,可以將序列分別輸入到獨立的LSTM模塊然后輸出結果合并后輸入到普通層。結構如下:
4. 模型訓練和結果預測
將上述數據集按4:1的比例隨機拆分為訓練集和驗證集,這是為了防止過度擬合。訓練模型。然后將數據的X列作為參數導入模型便可得到預測值,與實際的Y值相比便可得到該模型的優劣。
實現代碼
時間間隔序列格式化成所需的訓練集格式
import pandas as pdimport numpy as npdef create_interval_dataset(dataset, look_back):
""" ? ?:param dataset: input array of time intervals ? ?:param look_back: each training set feature length ? ?:return: convert an array of values into a dataset matrix. ? ?"""
dataX, dataY = [], [] ? ?for i in range(len(dataset) - look_back):
?dataX.append(dataset[i:i+look_back])
?dataY.append(dataset[i+look_back]) ? ?return np.asarray(dataX), np.asarray(dataY)
df = pd.read_csv("path-to-your-time-interval-file") ? ?
dataset_init = np.asarray(df) ? ?# if only 1 columndataX, dataY = create_interval_dataset(dataset, lookback=3) ? ?# look back if the training set sequence length
這里的輸入數據來源是csv文件,如果輸入數據是來自數據庫的話可以參考這里
LSTM網絡結構搭建
import pandas as pdimport numpy as npimport randomfrom keras.models import Sequential, model_from_jsonfrom keras.layers import Dense, LSTM, Dropoutclass NeuralNetwork():
def __init__(self, **kwargs):
?""" ? ? ? ?:param **kwargs: output_dim=4: output dimension of LSTM layer; activation_lstm='tanh': activation function for LSTM layers; activation_dense='relu': activation function for Dense layer; activation_last='sigmoid': activation function for last layer; drop_out=0.2: fraction of input units to drop; np_epoch=10, the number of epoches to train the model. epoch is one forward pass and one backward pass of all the training examples; batch_size=32: number of samples per gradient update. The higher the batch size, the more memory space you'll need; loss='mean_square_error': loss function; optimizer='rmsprop' ? ? ? ?"""
?self.output_dim = kwargs.get('output_dim', 8) ? ? ? ?self.activation_lstm = kwargs.get('activation_lstm', 'relu') ? ? ? ?self.activation_dense = kwargs.get('activation_dense', 'relu') ? ? ? ?self.activation_last = kwargs.get('activation_last', 'softmax') ? ?# softmax for multiple output
?self.dense_layer = kwargs.get('dense_layer', 2) ? ? # at least 2 layers
?self.lstm_layer = kwargs.get('lstm_layer', 2) ? ? ? ?self.drop_out = kwargs.get('drop_out', 0.2) ? ? ? ?self.nb_epoch = kwargs.get('nb_epoch', 10) ? ? ? ?self.batch_size = kwargs.get('batch_size', 100) ? ? ? ?self.loss = kwargs.get('loss', 'categorical_crossentropy') ? ? ? ?self.optimizer = kwargs.get('optimizer', 'rmsprop') ? ? ? ?def NN_model(self, trainX, trainY, testX, testY):
?""" ? ? ? ?:param trainX: training data set ? ? ? ?:param trainY: expect value of training data ? ? ? ?:param testX: test data set ? ? ? ?:param testY: epect value of test data ? ? ? ?:return: model after training ? ? ? ?"""
?print "Training model is LSTM network!"
?input_dim = trainX[1].shape[1]
?output_dim = trainY.shape[1] # one-hot label
?# print predefined parameters of current model:
?model = Sequential() ? ? ? ?# applying a LSTM layer with x dim output and y dim input. Use dropout parameter to avoid overfitting
?model.add(LSTM(output_dim=self.output_dim,
? ? ? ? ? ? ? ? input_dim=input_dim,
? ? ? ? ? ? ? ? activation=self.activation_lstm,
? ? ? ? ? ? ? ? dropout_U=self.drop_out,
? ? ? ? ? ? ? ? return_sequences=True)) ? ? ? ?for i in range(self.lstm_layer-2):
? ? ?model.add(LSTM(output_dim=self.output_dim,
? ? ? ? ? ? ? ? input_dim=self.output_dim,
? ? ? ? ? ? ? ? activation=self.activation_lstm,
? ? ? ? ? ? ? ? dropout_U=self.drop_out,
? ? ? ? ? ? ? ? return_sequences=True)) ? ? ? ?# argument return_sequences should be false in last lstm layer to avoid input dimension incompatibility with dense layer
?model.add(LSTM(output_dim=self.output_dim,
? ? ? ? ? ? ? ? input_dim=self.output_dim,
? ? ? ? ? ? ? ? activation=self.activation_lstm,
? ? ? ? ? ? ? ? dropout_U=self.drop_out)) ? ? ? ?for i in range(self.dense_layer-1):
? ? ?model.add(Dense(output_dim=self.output_dim,
? ? ? ? ? ? ? ? ?activation=self.activation_last))
?model.add(Dense(output_dim=output_dim,
? ? ? ? ? ? ? ? ?input_dim=self.output_dim,
? ? ? ? ? ? ? ? ?activation=self.activation_last)) ? ? ? ?# configure the learning process
?model.compile(loss=self.loss, optimizer=self.optimizer, metrics=['accuracy']) ? ? ? ?# train the model with fixed number of epoches
?model.fit(x=trainX, y=trainY, nb_epoch=self.nb_epoch, batch_size=self.batch_size, validation_data=(testX, testY)) ? ? ? ?# store model to json file
?model_json = model.to_json() ? ? ? ?with open(model_path, "w") as json_file:
? ? ?json_file.write(model_json) ? ? ? ?# store model weights to hdf5 file
?if model_weight_path: ? ? ? ? ? ?if os.path.exists(model_weight_path):
? ? ? ? ?os.remove(model_weight_path)
? ? ?model.save_weights(model_weight_path) # eg: model_weight.h5
?return model
這里寫的只涉及LSTM網絡的結構搭建,至于如何把數據處理規范化成網絡所需的結構以及把模型預測結果與實際值比較統計的可視化,就需要根據實際情況做調整了。