模型融合:stacking&blending

對於機器學習和深度學習來說,用單模型的效果往往都沒有進行模型融合後的效果好。

對模型來說,我們需要選擇具有多樣性、準確性的模型,對於融合的方式來說也有很多種,比如最簡單的取平均或者投票法就是一種。這裏主要講一下stacking和blending,二者都是用了兩層的模型。

1.stacking

stacking也是一種模型融合的方法,首先,直接用所有的訓練數據對第一層多個模型進行k折交叉驗證,這樣每個模型在訓練集上都有一個預測值,然後將這些預測值做爲新特徵對第二層的模型進行訓練。相比blending,stacking兩層模型都使用了全部的訓練數據。

stacking是一種分層模型集成框架。以兩層爲例,第一層由多個基學習器組成,其輸入爲原始訓練集,第二層的模型則是以第一層基學習器的輸出作爲訓練集進行再訓練,從而得到完整的stacking模型。

stacking訓練過程:

  • 1) 拆解訓練集。將訓練數據隨機且大致均勻的拆爲m份
  • 2)在拆解後的訓練集上訓練模型,同時在測試集上預測。利用m-1份訓練數據進行訓練,預測剩餘一份;在此過程進行的同時,利用相同的m-1份數據訓練,在真正的測試集上預測;如此重複m次,將訓練集上m次結果疊加爲1列,將測試集上m次結果取均值融合爲1列。
  • 3)使用k個分類器重複2過程。將分別得到k列訓練集的預測結果,k列測試集預測結果。
  • 4)訓練3過程得到的數據。將k列訓練集預測結果和訓練集真實label進行訓練,將k列測試集預測結果作爲測試集。

上半部分是用一個基礎模型進行5折交叉驗證,如:用XGBoost作爲基礎模型Model1,5折交叉驗證就是先拿出四折作爲training data,另外一折作爲testing data。注意:在stacking中此部分數據會用到整個traing set。如:假設我們整個training set包含10000行數據,testing set包含2500行數據,那麼每一次交叉驗證其實就是對training set進行劃分,在每一次的交叉驗證中training data將會是8000行,testing data是2000行。

每一次的交叉驗證包含兩個過程,1. 基於training data訓練模型;2. 基於training data訓練生成的模型對testing data進行預測。在整個第一次的交叉驗證完成之後我們將會得到關於當前testing data的預測值,這將會是一個一維2000行的數據,記爲a1。注意!在這部分操作完成後,我們還要對數據集原來的整個testing set進行預測,這個過程會生成2500個預測值,這部分預測值將會作爲下一層模型testing data的一部分,記爲b1。因爲我們進行的是5折交叉驗證,所以以上提及的過程將會進行五次,最終會生成針對testing set數據預測的5列2000行的數據a1,a2,a3,a4,a5,對testing set的預測會是5列2500行數據b1,b2,b3,b4,b5。

在完成對Model1的整個步驟之後,我們可以發現a1,a2,a3,a4,a5其實就是對原來整個training set的預測值,將他們拼湊起來,會形成一個10000行一列的矩陣,記爲A1。而對於b1,b2,b3,b4,b5這部分數據,我們將各部分相加取平均值,得到一個2500行一列的矩陣,記爲B1。

以上就是stacking中一個模型的完整流程,stacking中同一層通常包含多個模型,假設還有Model2: LR,Model3:RF,Model4: GBDT,Model5:SVM,對於這四個模型,我們可以重複以上的步驟,在整個流程結束之後,我們可以得到新的A2,A3,A4,A5,B2,B3,B4,B5矩陣。

在此之後,我們把A1,A2,A3,A4,A5並列合併得到一個10000行五列的矩陣作爲training data,B1,B2,B3,B4,B5並列合併得到一個2500行五列的矩陣作爲testing data。讓下一層的模型,基於他們進一步訓練。

from sklearn import datasets
from sklearn.ensemble import RandomForestClassifier, ExtraTreesClassifier, GradientBoostingClassifier
from sklearn.cross_validation import train_test_split
from sklearn.cross_validation import StratifiedKFold
import numpy as np
from sklearn.metrics import roc_auc_score
from sklearn.datasets.samples_generator import make_blobs

'''創建訓練的數據集'''
data, target = make_blobs(n_samples=50000, centers=2, random_state=0, cluster_std=0.60)

'''模型融合中使用到的各個單模型'''
clfs = [RandomForestClassifier(n_estimators=5, n_jobs=-1, criterion='gini'),
        RandomForestClassifier(n_estimators=5, n_jobs=-1, criterion='entropy'),
        ExtraTreesClassifier(n_estimators=5, n_jobs=-1, criterion='gini'),
        ExtraTreesClassifier(n_estimators=5, n_jobs=-1, criterion='entropy'),
        GradientBoostingClassifier(learning_rate=0.05, subsample=0.5, max_depth=6, n_estimators=5)]

'''切分一部分數據作爲測試集'''
X, X_predict, y, y_predict = train_test_split(data, target, test_size=0.33, random_state=2017)


dataset_blend_train = np.zeros((X.shape[0], len(clfs)))
dataset_blend_test = np.zeros((X_predict.shape[0], len(clfs)))

'''5折stacking'''
n_folds = 5
skf = list(StratifiedKFold(y, n_folds))
for j, clf in enumerate(clfs):
    '''依次訓練各個單模型'''
    # print(j, clf)
    dataset_blend_test_j = np.zeros((X_predict.shape[0], len(skf)))
    for i, (train, test) in enumerate(skf):
        '''使用第i個部分作爲預測,剩餘的部分來訓練模型,獲得其預測的輸出作爲第i部分的新特徵。'''
        # print("Fold", i)
        X_train, y_train, X_test, y_test = X[train], y[train], X[test], y[test]
        clf.fit(X_train, y_train)
        y_submission = clf.predict_proba(X_test)[:, 1]
        dataset_blend_train[test, j] = y_submission
        dataset_blend_test_j[:, i] = clf.predict_proba(X_predict)[:, 1]
    '''對於測試集,直接用這k個模型的預測值均值作爲新的特徵。'''
    dataset_blend_test[:, j] = dataset_blend_test_j.mean(1)
    print("val auc Score: %f" % roc_auc_score(y_predict, dataset_blend_test[:, j]))
# clf = LogisticRegression()
clf = GradientBoostingClassifier(learning_rate=0.02, subsample=0.5, max_depth=6, n_estimators=30)
clf.fit(dataset_blend_train, y)
y_submission = clf.predict_proba(dataset_blend_test)[:, 1]

print("Linear stretch of predictions to [0,1]")
y_submission = (y_submission - y_submission.min()) / (y_submission.max() - y_submission.min())
print("blend result")
print("val auc Score: %f" % (roc_auc_score(y_predict, y_submission)))

2. blending

Blending與Stacking大致相同,只是Blending的主要區別在於訓練集不是通過K-Fold的CV策略來獲得預測值從而生成第二階段模型的特徵,而是建立一個Holdout集。簡單來說,Blending直接用不相交的數據集用於不同層的訓練。

以兩層的Blending爲例,訓練集劃分爲兩部分(d1,d2),測試集爲test。

  1. 第一層:用d1訓練多個模型,將其對d2和test的預測結果作爲第二層的New Features。 
  2. 第二層:用d2的New Features和標籤訓練新的分類器,然後把test的New Features輸入作爲最終的測試集,對test預測出的結果就是最終的模型融合的值。
from sklearn import datasets
from sklearn.ensemble import RandomForestClassifier, ExtraTreesClassifier, GradientBoostingClassifier
from sklearn.cross_validation import train_test_split
from sklearn.cross_validation import StratifiedKFold
import numpy as np
from sklearn.metrics import roc_auc_score
from sklearn.datasets.samples_generator import make_blobs

'''創建訓練的數據集'''
data, target = make_blobs(n_samples=50000, centers=2, random_state=0, cluster_std=0.60)

'''模型融合中使用到的各個單模型'''
clfs = [RandomForestClassifier(n_estimators=5, n_jobs=-1, criterion='gini'),
        RandomForestClassifier(n_estimators=5, n_jobs=-1, criterion='entropy'),
        ExtraTreesClassifier(n_estimators=5, n_jobs=-1, criterion='gini'),
        ExtraTreesClassifier(n_estimators=5, n_jobs=-1, criterion='entropy'),
        GradientBoostingClassifier(learning_rate=0.05, subsample=0.5, max_depth=6, n_estimators=5)]

'''切分一部分數據作爲測試集'''
X, X_predict, y, y_predict = train_test_split(data, target, test_size=0.33, random_state=2017)


'''切分訓練數據集爲d1,d2兩部分'''
X_d1, X_d2, y_d1, y_d2 = train_test_split(X, y, test_size=0.5, random_state=2017)
dataset_d1 = np.zeros((X_d2.shape[0], len(clfs)))
dataset_d2 = np.zeros((X_predict.shape[0], len(clfs)))

for j, clf in enumerate(clfs):
    '''依次訓練各個單模型'''
    # print(j, clf)
    '''使用第1個部分作爲預測,第2部分來訓練模型,獲得其預測的輸出作爲第2部分的新特徵。'''
    clf.fit(X_d1, y_d1)
    y_submission = clf.predict_proba(X_d2)[:, 1]
    dataset_d1[:, j] = y_submission
    '''對於測試集,直接用這k個模型的預測值作爲新的特徵。'''
    dataset_d2[:, j] = clf.predict_proba(X_predict)[:, 1]
    print("val auc Score: %f" % roc_auc_score(y_predict, dataset_d2[:, j]))

'''融合使用的模型'''
# clf = LogisticRegression()
clf = GradientBoostingClassifier(learning_rate=0.02, subsample=0.5, max_depth=6, n_estimators=30)
clf.fit(dataset_d1, y_d2)
y_submission = clf.predict_proba(dataset_d2)[:, 1]

print("Linear stretch of predictions to [0,1]")
y_submission = (y_submission - y_submission.min()) / (y_submission.max() - y_submission.min())
print("blend result")
print("val auc Score: %f" % (roc_auc_score(y_predict, y_submission)))

這兩者其實不用分的太清楚,各有好壞:

Blending與stacking相比優點在於:

1.比stacking簡單(因爲不用進行k次的交叉驗證來獲得新特徵)

2.由於兩層使用的數據不同,所以避免了一個信息泄露的問題。

3.在團隊建模過程中,不需要給隊友分享自己的隨機種子。

而缺點在於:

1.由於blending對數據集這種劃分形式,第二層的數據量比較少。

2.由於第二層數據量比較少所以可能會過擬合。

3.stacking使用多次的CV會比較穩健

對於實踐中的結果而言,stacking和blending的效果是差不多的,所以使用哪種方法都沒什麼所謂,完全取決於個人愛好。

 

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