零基礎數據挖掘入門系列(六) - 模型的融合技術大總結與結果部署

思維導圖:零基礎入門數據挖掘的學習路徑

1. 寫在前面

零基礎入門數據挖掘是記錄自己在Datawhale舉辦的數據挖掘專題學習中的所學和所想, 該系列筆記使用理論結合實踐的方式,整理數據挖掘相關知識,提升在實際場景中的數據分析、數據清洗,特徵工程、建模調參和模型融合等技能。所以這個系列筆記共五篇重點內容, 也分別從上面五方面進行整理學習, 既是希望能對知識從實戰的角度串聯回憶,加強動手能力的鍛鍊,也希望這五篇筆記能夠幫助到更多喜歡數據挖掘的小夥伴,我們一起學習,一起交流吧。

既然是理論結合實踐的方式,那麼我們是從天池的一個二手車交易價格預測比賽出發進行學習,既可以學習到知識,又可以學習如何入門一個數據競賽, 下面我們開始吧。

今天是數據挖掘入門系列的最後一篇文章模型融合技術大總結和結果部署, 一般來說,通過融合多個不同的模型,可能提升機器學習的性能,這一方法在各種機器學習比賽中廣泛應用, 也是在比賽的攻堅時刻衝刺Top的關鍵,而融合模型往往又可以從模型結果,模型自身,樣本集等不同的角度進行融合。 所以今天會從不同的角度介紹模型融合的技術,今天整理的這些技術在今後的比賽或者項目裏面會非常的有用,既然是這個系列的最後一篇文章,就想着把這些技術整理的詳細一些作爲最後的壓軸了,通過這篇文章,希望你能真正的理解模型結果層面的融合方式Voting和Averaging的原理、從樣本集角度出發把模型進行集成的Boosting和Bagging的區別,以及更爲強大的模型自身融合方法Stacking和Blending的原理和區別(其實後面這兩個纔是真正的模型融合,即將多個已經有較好效果的模型融合成更好的模型)。 在這個比賽中, 我嘗試把baseline裏面的xgb和lgb作爲第一層, 然後把LinearRegressor作爲第二層進行Stacking,最後再進行結果層面加權融合,效果又會有一個提升。所以今天的這些技術可以讓模型發揮更大的作用, 當然這些技術不會僅適用於這個比賽。

首先,會針對不同的任務(分類/迴歸)從簡單的加權融合開始, 介紹一下分類裏面的投票方式(Voting)的原理和具體實現, 然後是迴歸裏面的平均融合機制(Averaging), 其次簡單的介紹一下Boosting和Bagging以及兩者的關聯,當然這個在這次不是重點,畢竟xgboost或者隨機森林就已經用到了這種機制, 最後會整理這次的重頭戲Stacking/Blending的原理,具體實現和兩者的比較,Stacking方法在機器學習比賽中被譽爲“七條龍神技”。聽這個名字也知道這個技術的重要性了吧。這一塊是這次融合技術的重點, 也是目前最常用的效果比較好的方式。 在收尾的時候,簡單的介紹一下結果部署的問題,即如何保存辛辛苦苦訓練好的模型,畢竟模型的訓練是花費很長時間的,總不能每一次預測的時候都重新訓練吧, 所以這也是比較重要的一步。

PS: 這次這個系列有一個特點就是層次性很強,畢竟是圍繞着一個比賽進行展開。後面的每一篇都是基於前一篇文章過來的, 所以今天的整理是基於我們已經有了調參好的幾個模型的基礎之上,研究如何對這幾個模型進行整合以發揮更大的性能,如果還沒有對模型進行調參,建議先學習一下前面的知識零基礎數據挖掘入門系列(五) - 模型建立與調參,這裏面通過實驗也證明了一下調參對最後的結果影響也是很大。 作爲壓軸, 這篇文章會很長,因爲有些東西想借這個機會整理的詳細些後面查閱的時候方便(很多知識面試的時候會涉及到),所以根據目錄各取所需即可

大綱如下

  • 迴歸任務中的加權融合與分類任務中的Voting
  • Boosting和Bagging的原理與對比
  • Stacking/Blending構建多層模型(原理,實現和比較)
  • 簡單的結果部署方式
  • 對本篇文章和整個系列的總結

Ok, let’s go!

開始之前,依然是先導入之前保存好的數據:

# 導入數據, 並構造一個線下測試集
data = pd.read_csv('./pre_data/pre_data.csv')

train = data[:train_data.shape[0]]
test = data[train_data.shape[0]:]    # 這個先不用

# 選數據
X = train[:100000]
Y= train_data['price'][:100000]
Y_ln = np.log1p(Y)

XTest = train[100000:]   # 模擬一個線下測試集, 看看模型的泛化能力
Ytrue = train_data['price'][100000:]

2. 迴歸任務中的加權融合與分類任務中的Voting(模型的結果層面的融合技術)

2.1 迴歸任務中的加權融合

給出的baseline裏面最後的lgb和xgb的融合就使用了這種方式,當然這種方式也比較好理解,就是根據各個模型的最終預測表現分配不同的權重以改變其對最終結果影響的大小。對於正確率低的模型給予更低的權重,而正確率更高的模型給予更高的權重。 所以爲了更簡單的說明這個原理,這裏給出了簡單的例子,沒有再用baseline的模型跑出結果再加權融合, 那樣速度太慢了。 重點是get思想。

#生成一些簡單的樣本數據, test_prei代表第i個模型的預測值
test_pre1 = [1.2, 3.2, 2.1, 6.2]
test_pre2 = [0.9, 3.1, 2.0, 5.9]
test_pre3 = [1.1, 2.9, 2.2, 6.0]

# y_test_true 代表模型的真實值
y_test_true = [1, 3, 2, 6]

# 可以先看一下各個模型的預測結果
print('Pred1 MAE:',mean_absolute_error(y_test_true, test_pre1)) 
print('Pred2 MAE:',mean_absolute_error(y_test_true, test_pre2)) 
print('Pred3 MAE:',mean_absolute_error(y_test_true, test_pre3))

## 結果:
Pred1 MAE: 0.1750000000000001
Pred2 MAE: 0.07499999999999993
Pred3 MAE: 0.10000000000000009

下面我們進行上面三個模型的加權融合,看看最終的效果:

# 下面我們進行加權融合
def Weighted_method(test_pre1, test_pre2, test_pre3, w=[1/3, 1/3, 1/3]):
    Weighted_result = w[0] * pd.Series(test_pre1) + w[1] * pd.Series(test_pre2) + w[2] * pd.Series(test_pre3)
    return Weighted_result

# 根據上面的MAE,我們計算每個模型的權重, 計算方式就是: wi = mae(i) / sum(mae)
w = [0.3, 0.4, 0.3]
Weighted_pre = Weighted_method(test_pre1,test_pre2,test_pre3,w) 
print('Weighted_pre MAE:',mean_absolute_error(y_test_true, Weighted_pre))   # 會發現這個效果會提高一些

## 結果:
Weighted_pre MAE: 0.05750000000000027

其實這種加權融合的技術是從模型結果的層面進行的, 就是讓每個模型跑一遍結果,然後把這個結果想辦法融合起來, 當然融合起來的方式也不止有加權平均,還有一些特殊的方式,比如結果取平均, 取中位數等。 這裏也給出取平均和中位數的方式,但是這個效果一般不如加權平均的效果好。

## 定義結果的mean平均函數
def Mean_method(test_pre1,test_pre2,test_pre3):
    Mean_result = pd.concat([pd.Series(test_pre1),pd.Series(test_pre2),pd.Series(test_pre3)],axis=1).mean(axis=1)
    return Mean_result

## 定義結果的中位數平均函數
def Median_method(test_pre1,test_pre2,test_pre3):
    Median_result = pd.concat([pd.Series(test_pre1),pd.Series(test_pre2),pd.Series(test_pre3)],axis=1).median(axis=1)
    return Median_result

加權融合這個一般適用於迴歸任務中模型的結果層面, 那麼分類任務中我們有沒有類似的結果融合的方式呢? 那就是投票法voting了。

2.2 分類任務中的Voting

投票法(Voting)是集成學習裏面針對分類問題的一種結果結合策略。基本思想是選擇所有機器學習算法當中輸出最多的那個類。 機器學習的算法有很多,對於每一種機器學習算法,考慮問題的方式都略微有所不同,所以對於同一個問題,不同的算法可能會給出不同的結果,那麼在這種情況下,我們選擇哪個算法的結果作爲最終結果呢?那麼此時,我們完全可以把多種算法集中起來,讓不同算法對同一種問題都進行預測,最終少數服從多數,這就是集成學習的思路。

在不改變模型的情況下,直接對各個不同的模型預測的結果,進行投票或者平均,這是一種簡單卻行之有效的融合方式。比如對於分類問題,假設有三個相互獨立的模型,每個正確率都是70%,採用少數服從多數的方式進行投票。那麼最終的正確率將是:

在這裏插入圖片描述
即結果經過簡單的投票,使得正確率提升了8%。這是一個簡單的概率學問題——如果進行投票的模型越多,那麼顯然其結果將會更好。但是其前提條件是模型之間相互獨立,結果之間沒有相關性。越相近的模型進行融合,融合效果也會越差。 有人問這個是怎麼算的? 其實就是在少數服從多數算正確率 (三個0.7相乘,就是我三個模型都預測對的概率, 後面的那部分就是我有兩個模型預測對了,一個預測錯了,那我還是挺預測對了的,這個概率就是後面那個,*3是因爲這種情況有三種), 具體詳情可以參考這篇博客:模型融合方法學習總結, 這裏就不對原理部分解釋太多了,少數服從多數的投票方式也比較好理解,關鍵我們怎麼實現這種方式呢?

sklearn中的VotingClassifier是投票法的實現, 投票法的輸出有兩種類型:一種是直接輸出類標籤,另外一種是輸出類概率,使用前者進行投票叫做硬投票(Majority/Hard voting),使用後者進行分類叫做軟投票(Soft voting)。 這個是通過voting參數進行控制。硬投票其實就是少數服從多數的原則, 但是有時候少數服從多數並不適用,那麼更加合理的投票方式,應該是有權值的投票方式,在現實生活中也有這樣的例子,比如在唱歌比賽中,專業的評審的投票分值就應該比觀衆的投票分值高。

我們簡單的理解一下硬投票和軟投票,詳情參考集成學習voting Classifier在sklearn中的實現

  • 硬投票是選擇算法輸出最多的標籤,如果標籤數量相等,那麼按照升序的次序進行選擇, 看下圖:
    在這裏插入圖片描述
    hard voting的少數服從多數原則在上面這種情況下就顯得不太合理,雖然只有模型1和模型4分類結果爲A,但概率都高於90%,也就是說很確定結果爲A,其他三個模型分類結果是B,但從概率看,並不是很確定。這時如果用hard voting得出的最終結果爲就顯得不太合理。

  • 軟投票是使用各個算法輸出的類概率來進行類的選擇,輸入權重的話,會得到每個類的類概率的加權平均值,值大的類會被選擇。
    在這裏插入圖片描述
    在使用soft voting時,把概率當做權值,這時候集成後的結果爲A就顯得更爲合理。

下面我們就以鳶尾花數據集簡單的看一下投票算法和單個模型的效果對比:

from sklearn.datasets import load_iris

from sklearn.linear_model import LogisticRegression
from sklearn.tree import DecisionTreeClassifier
from sklearn.svm import SVC
from sklearn.ensemble import VotingClassifier
from sklearn.ensemble import AdaBoostClassifier
from xgboost import XGBClassifier
from sklearn.ensemble import RandomForestClassifier

iris =load_iris()

x=iris.data
y=iris.target
x_train,x_test,y_train,y_test=train_test_split(x,y,test_size=0.3)

clf1 = XGBClassifier(learning_rate=0.1, n_estimators=150, max_depth=3, min_child_weight=2, subsample=0.7,
                     colsample_bytree=0.6, objective='binary:logistic')
clf2 = RandomForestClassifier(n_estimators=50, max_depth=1, min_samples_split=4,
                              min_samples_leaf=63,oob_score=True)
clf3 = SVC(C=0.1, probability=True)  # 軟投票的時候,probability必須指定且爲true

# 硬投票
eclf = VotingClassifier(estimators=[('xgb', clf1), ('rf', clf2), ('svc', clf3)], voting='hard')
for clf, label in zip([clf1, clf2, clf3, eclf], ['XGBBoosting', 'Random Forest', 'SVM', 'Voting']):
    scores = cross_val_score(clf, x, y, cv=5, scoring='accuracy')
    print("Accuracy: %0.2f (+/- %0.2f) [%s]" % (scores.mean(), scores.std(), label))

## 結果如下:
Accuracy: 0.96 (+/- 0.02) [XGBBoosting]
Accuracy: 0.33 (+/- 0.00) [Random Forest]
Accuracy: 0.95 (+/- 0.03) [SVM]
Accuracy: 0.94 (+/- 0.04) [Voting]

# 軟投票只需要把voting的參數改成soft即可, 這樣最後的Voting正確率會成爲0.96

是不是投票的方式也挺簡單的啊, 但是這種方法其實有一點風險,就是萬一融合的模型中有些結果並不是很好,那麼會整體把結果往下拉,即可能得不到最好的結果。 這個按照我們投票的方式其實也是有這樣的一個風險啊, 投的最多的那個結果一定是一個好的結果嗎? 不一定吧!

所以這就是模型結果層面融合的兩種方式了, 迴歸任務上一般就是將多個模型的結果進行加權融合或者取平均等已獲得更好的效果,而分類任務上可以採用投票表決的方式獲取最終的結果。

3. Boosting和Bagging的原理與對比

介紹完了模型結果層面的兩種方式之後,再介紹一下Boosting和Bagging, 這兩個都是從樣本集的角度考慮把多個弱模型集成起來的一種方式, 只不過兩者在集成的時候還是有些區別的。這兩個其實不是那麼神祕了,我們使用的xgb,lgb就屬於Boosting方法,而隨機森林就是Bagging方式, 所以下面重點看看細節和原理,關於數學和公式推導這些底層的東西,下面不會涉及到。

3.1 Boosting

Boosting是一種將各種弱分類器串聯起來的集成學習方式,每一個分類器的訓練都依賴於前一個分類器的結果,順序運行的方式導致了運行速度慢。和所有融合方式一樣,它不會考慮各個弱分類器模型本身結構爲何,而是對訓練數據(樣本集)和連接方式進行操縱以獲得更小的誤差。但是爲了將最終的強分類器的誤差均衡,之前所選取的分類器一般都是相對比較弱的分類器,因爲一旦某個分類器較強將使得後續結果受到影響太大。所以多用於集成學習而非模型融合(將多個已經有較好效果的模型融合成更好的模型)。

這裏拿一張圖片來簡單的看一下(摘自知乎專欄 《【機器學習】模型融合方法概述》③處引用的加州大學歐文分校Alex Ihler教授PPT)
在這裏插入圖片描述
Boosing方式理解起來,比如我想用很多個模型M1, M2, …Mn去預測二手車的價格, 但是我這些模型具體工作是這樣安排的, 我讓M1先去訓練然後對價格進行預測, 等M1預測完了之後, M2訓練的時候是對M1訓練的改進和提升,即優化M1沒有做好的事情, 同樣M3會基於M2的結果再次進行優化,這樣一直到Mn。 看到這個串聯的關係了嗎? 其實就是在訓練的過程中這 K 個模型之間是有依賴性的,當引入第 K 個模型的時候,實際上是對前 K-1 個模型的優化。最終的預測結果是這K個模型結果的一個大組合。 這樣形成了預測結果的增強。

這裏再多說一句吧, 我們知道Boosting家族裏面有代表的像AdaBoost, GBDT, xgboost, lightgbm等,但是這些模型雖然都是串聯集成多個弱分類器但是之間還是有區別的,又可以分成AdaBoost流派和GBDT流派, 比如AdaBoost,在引入M2的時候,其實它關注的是M1預測不好的那些樣本, 這些樣本如果M2在訓練的時候,就會加大權重。 後面的模型引入也都是這個道理, 即關注前面模型預測不好的那些樣本。 而GBDT,包括後面的xgboost這些,他們是更加聚焦於殘差,即M2引入的時候,它關注的是M1的預測結果與標準結果之間的那個差距, 它想減少的是這個差距,後面的模型引入也是這個道理,即關注前面模型預測結果與標準答案之間的差距,然後一步一步的進行縮小。xgboost其實也是聚焦殘差,只不過和GBDT的不同是減小這個殘差的這種策略,GBDT是用模型在數據上的負梯度作爲殘差的近似值,從而擬合殘差。XGBoost也是擬合數據殘差,並用泰勒展開式(二階泰勒展開式)對模型損失殘差的近似,同時在損失函數上添加了正則化項。 lgb的話和xgboost的原理是一樣的,只不過又在模型的訓練速度上進行了優化,採用了直方圖的決策樹算法使得收斂更快。 這個在這裏只是梳理一下它們之間的這種邏輯關係,詳細原理就不在這裏討論了。因爲每一個模型都是一個很長的故事了。

3.2 Bagging

Bagging是Bootstrap Aggregating的縮寫。這種方法同樣不對模型本身進行操作,而是作用於樣本集上。採用的是隨機有放回的選擇訓練數據然後構造分類器,最後進行組合。與Boosting方法中各分類器之間的相互依賴和串行運行不同,Bagging方法中基學習器之間不存在強依賴關係,且同時生成並行運行。

其基本思路爲:

  • 在樣本集中進行K輪有放回的抽樣,每次抽取n個樣本,得到K個訓練集;
  • 分別用K個訓練集訓練得到K個模型。
  • 對得到的K個模型預測結果用投票或平均的方式進行融合。

在這裏,訓練集的選取可能不會包含所有樣本集,未被包含的數據成爲包/袋外數據,可用來進行包外誤差的泛化估計。每個模型的訓練過程中,每次訓練集可以取全部的特徵進行訓練,也可以隨機選取部分特徵訓練,例如極有代表性的隨機森林算法就是每次隨機選取部分特徵。

下面僅從思想層面介紹隨機森林算法:

  • 在樣本集中進行K輪有放回的抽樣,每次抽取n個樣本,得到K個訓練集,其中n一般遠小於樣本集總數;
  • 選取訓練集,在整體特徵集M中選取部分特徵集m構建決策樹,其中m一般遠小於M;
  • 在構造每棵決策樹的過程中,按照選取最小的基尼指數進行分裂節點的選取進行決策樹的構建。決策樹的其他結點都採取相同的分裂規則進行構建,直到該節點的所有訓練樣例都屬於同一類或者達到樹的最大深度;
  • 重複上述步驟,得到隨機森林;
  • 多棵決策樹同時進行預測,對結果進行投票或平均得到最終的分類結果。

多次隨機選擇的過程,使得隨機森林不容易過擬合且有很好的抗干擾能力。

3.3 Boosting和Bagging的比較

下面我們就從不同的角度看看Boosting和Bagging的比較(這個在面試中經常會被問到)

  1. 優化方式上
    在機器學習中,我們訓練一個模型通常是將定義的Loss最小化的過程。但是單單的最小化loss並不能保證模型在解決一般化的問題時能夠最優,甚至不能保證模型可用。訓練數據集的Loss與一般化數據集的Loss之間的差異被稱爲generalization error。
    error=Bias+Varianceerror = Bias + Variance
    VarianceVariance過大會導致模型過擬合, 而BiasBias過大會導致模型欠擬合。
    Bagging方法主要通過降低Variance來降低error, Boosting方法主要通過降低Bias來降低error

    • Bagging方法採用多個不完全相同的訓練集訓練多個模型,最後結果取平均, 由於E[Xin]=E[Xi]E[\frac{\sum X_i}{n}]=E[X_i], 所以最終結果的Bias與單個模型的Bias相近, 一般不會顯著降低Bias。 而對於Variance, 有
      Var[Xin]=Var[Xi]n,Var[\frac{\sum X_i}{n}] = \frac{Var[X_i]}{n}, 子模型相互獨立
      Var[Xin]=Var[Xi],Var[\frac{\sum X_i}{n}] = Var[Xi], 子模型完全相同
      Bagging的多個子模型由不完全相同的數據集訓練而成,所以子模型間有一定的相關性但又不完全獨立,所以其結果在上述兩式的中間狀態。因此可以在一定程度上降低Variance從而使得總error減小。
    • Boosting方法從優化角度來說, 是用是用forward-stagewise這種貪心法去最小化損失函數L(y,aifi(x))L(y, \sum{a_if_i(x)})。所謂forward-stagewise, 就是在迭代的第n步, 求解新的子模型f(x)f(x)及步長aa來最小化L(y,fn1(x)+af(x))L(y, f_{n-1}(x)+af(x)), 這裏的fn1(x)f_{n-1}(x)是前n步得到的子模型的和。 因此Boosting在最小化損失函數, Bias自然逐步下降, 而由於模型之間強相關, 不能顯著降低Variance
  2. 樣本選擇上
    Bagging:訓練集是在原始集中有放回選取的,從原始集中選出的各輪訓練集之間是獨立的。
    Boosting:每一輪的訓練集不變,只是訓練集中每個樣例在分類器中的權重發生變化。而權值是根據上一輪的分類結果進行調整。

  3. 樣例權重
    Bagging:使用均勻取樣,每個樣例的權重相等
    Boosting:根據錯誤率不斷調整樣例的權值,錯誤率越大則權重越大(當然這是AdaBoost方式)

  4. 預測函數
    Bagging:所有預測函數的權重相等。
    Boosting:每個弱分類器都有相應的權重,對於分類誤差小的分類器會有更大的權重(這依然是AdaBoost方式)

  5. 並且計算
    Bagging:各個預測函數可以並行生成
    Boosting:理論上各個預測函數只能順序生成,因爲後一個模型參數需要前一輪模型的結果。計算角度來看,兩種方法都可以並行。bagging, random forest並行化方法顯而意見。boosting有強力工具stochastic gradient boosting

壞了, 感覺這個地方寫多了, 畢竟本來想簡單整理這個地方來,下面纔是重點,所以這個就到此爲止吧, 詳情可參考博客:模型融合方法學習總結

下面就是重頭戲了 😉

4. Stacking/Blending構建多層模型(原理,實現和比較)

接下來介紹在各種機器學習比賽中被譽爲“七頭龍神技”的stacking方法, 但因其模型的龐大程度與效果的提升程度往往不成正比, 所以一般很難應用於實際生產中。

4.1 Stacking

Stacking模型的本質是一種分層的結構,用了大量的基分類器,將其預測的結果作爲下一層輸入的特徵,這樣的結構使得它比相互獨立訓練模型能夠獲得更多的特徵。

下面以一種易於理解但不會實際使用的兩層的stacking方法爲例,簡要說明其結構和工作原理:(這種模型問題將在後續說明), 詳細內容參考博客:模型融合方法學習總結

在這裏插入圖片描述
假設我們有三個基模型M1,M2,M3和一個元模型M4, 有訓練集train和測試集test,我們進行下面的操作(爲了便於理解,我這裏還給出了代碼的形式):

  1. 用訓練集train訓練基模型M1(M1.fit(train)), 然後分別在train和test上做預測, 得到P1(M1.predict(train))和T1(M1.predict(test)
  2. 用訓練集train訓練基模型M2(M2.fit(train)), 然後分別在train和test上做預測, 得到P2(M2.predict(train))和T2(M2.predict(test)
  3. 用訓練集train訓練基模型M3(M3.fit(train)), 然後分別在train和test上做預測, 得到P3(M3.predict(train))和T3(M3.predict(test)

這樣第一層的模型訓練完畢。 接下來:

  1. 把P1, P2, P3進行合併組成新的訓練集train2, 把T1, T2, T3進行合併組成新的測試集test2
  2. 用新的訓練集train2訓練元模型M4(M4.fit(train2)), 然後再test2上進行預測得到最終的預測結果Y_pre(M4.predict(test2))

這樣第二層訓練預測得到了最終的預測結果。(可以結合着上面這張圖來看更加清晰),這就是我們兩層堆疊的一種基本的原始思路想法。在不同模型預測的結果基礎上再加一層模型,進行再訓練,從而得到模型最終的預測。

Stacking本質上就是這麼直接的思路, 但是直接這樣有時對於如果訓練集和測試集分佈不那麼一致的情況下是有一點問題的, 其問題在於我們用訓練集訓練原始模型, 又接着用訓練的模型去預測訓練集,這不明擺着會過擬合訓練集嗎? 因此現在的問題變成如何降低再訓練的過擬合性, 這裏一般有兩種方式:

  • 次級模型儘量選擇簡單的線性模型
  • 利用第一層訓練模型使用交叉驗證的方式

第一種方式比較容易理解, 我們重點來看看第二種方式到底是怎麼做的, 先來一個圖體會一下:

在這裏插入圖片描述
我們就拿5折交叉驗證爲例, 其實在做這樣的事情:

  1. 首先我們將訓練集分成5份(5折交叉驗證)
  2. 對於每一個基模型i來說, 我們用其中的四份進行訓練, 然後用另一份訓練集作爲驗證集進行預測得到Pi的一部分, 然後再用測試集進行預測得到Ti的一部分,這樣當五輪下來之後,驗證集的預測值就會拼接成一個完整的Pi, 測試集的label值取個平均就會得到一個Ti(看下面的預測示意圖)。
  3. 這些Pi進行合併就會得到下一層的訓練集train2, Ti進行合併就得到了下一層的測試集test2。
  4. 利用train2訓練第二層的模型, 然後再test2上得到預測結果,就是最終的結果。

在這裏插入圖片描述
所以, Stacking的整理過程原理其實可以用下面的圖進行表示(是不是和某些博客記錄的不太一樣? 看了Blending的那個圖就明白了)

在這裏插入圖片描述
關於Stacking的原理部分就整理這麼多吧,希望上面的這個圖能給你眼前一亮的感覺, 當然這些原理部分都來自於上面給出的鏈接,詳細情況可以看看那裏面的描述, 我覺得這個圖就能說明一切了。 明白了原理之後,我主要講如何實現。畢竟理論到處有, 但實踐是王道

那麼具體實現應該如何實現呢? 首先我會拿上面加權平均的那個迴歸例子來看一下stacking具體應該怎麼實現,然後我會介紹一款強大的stacking利器mlxtend庫裏面的StackingCVRegressor並使用它融合我們調參好的xgb和lgb用於我們的這個比賽。 然後我們再看看stacking怎麼用於分類的任務中,依然是同樣的邏輯,先自己實現stacking, 然後使用mlxtend裏面的StackingClassifier實現stacking,所以這是下面這一塊的邏輯, stacking分別用於迴歸和分類任務,在每一個任務中先基於原理自己實現,然後學會掉包,前者爲了更加深入理解stacking,後者便於實際應用。

4.1.1 迴歸中的Stacking

我們先拿上面那個迴歸的例子來看看Stacking的效果,

# 生成一些簡單的樣本數據, test_prei代表第i個模型的預測值
train_reg1 = [3.2, 8.2, 9.1, 5.2]
train_reg2 = [2.9, 8.1, 9.0, 4.9]
train_reg3 = [3.1, 7.9, 9.2, 5.0]

# y_test_true代表模型的真實值
y_train_true = [3, 8, 9, 5]

test_pre1 = [1.2, 3.2, 2.1, 6.2]
test_pre2 = [0.9, 3.1, 2.0, 5.9]
test_pre3 = [1.1, 2.9, 2.2, 6.0]

y_test_true = [1, 3, 2, 6]

def Stacking_method(train_reg1,train_reg2,train_reg3,y_train_true,test_pre1,test_pre2,test_pre3,model_L2= LinearRegression()):
    model_L2.fit(pd.concat([pd.Series(train_reg1),pd.Series(train_reg2),pd.Series(train_reg3)],axis=1).values,y_train_true)
    Stacking_result = model_L2.predict(pd.concat([pd.Series(test_pre1),pd.Series(test_pre2),pd.Series(test_pre3)],axis=1).values)
    return Stacking_result
 
model_L2 = LinearRegression()
Stacking_pre = Stacking_method(train_reg1, train_reg2, train_reg3, y_train_true,
                               test_pre1, test_pre2, test_pre3, model_L2)
print('Stacking_pre MAE:', mean_absolute_error(y_test_true, Stacking_pre))


## 結果:
Stacking_pre MAE: 0.042134831460675204

這裏的邏輯就是把第一層模型的在訓練集上的預測值當做第二層訓練集的特徵, 第一層模型在測試集上的預測值當做第二層測試集的特徵, 然後建立第二層的邏輯迴歸模型進行訓練。

可以發現模型結果相對於之前有進一步的提升, 這時候我們需要注意的是,對於第二層的Stacking的模型不宜選取的過於複雜, 否則會導致模型在訓練集上過擬合, 從而使得在測試集上並不能取得很好地效果。

接下來介紹一款強大的stacking工具StackingCVRegressor, 這是一種集成學習的元迴歸器, 首先導入:

from mxltend.regressor import StackingCVRegressor

在標準 stacking程序中,擬合一級迴歸器的時候,我們如果使用第二級迴歸器的輸入的相同訓練集,這很可能會導致過度擬合。 然而,StackingCVRegressor使用了‘非摺疊預測’的概念:數據集被分成k個摺疊,並且在k個連續的循環中,使用k-1摺疊來擬合第一級迴歸器(即k折交叉驗證的stackingRegressor)。在每一輪中(一共K輪),一級迴歸器然後被應用於在每次迭代中還未用過的模型擬合的剩餘1個子集。然後將得到的預測疊加起來並作爲輸入數據提供給二級迴歸器。在StackingCVRegressor的訓練完之後,一級迴歸器擬合整個數據集以獲得最佳預測。這其實就是上面我們介紹的原理。

那麼關鍵是怎麼用呢?好使的的要哭, 直接調用API接口:

StackingCVRegressor(regressors,meta_regressor,cv = 5,shuffle = True,use_features_in_secondary = False)

  • regressors: 基迴歸器,列表的形式,第一層的模型, 比如在這個比賽中,我打算第一層用xgb和lgb,第二層用線性模型, 那我這裏就應該這麼寫[xgb, lgb], 當然這些模型是事先定義好。
  • meta_regressor: 元迴歸器, 這個可以理解爲第二層的那個模型,即將前面迴歸器結果合起來的那個迴歸器,這裏我使用lr,因爲第二層的模型不要太複雜
  • cv: 交叉驗證策略, 默認是5折交叉驗證,不明白的可以看一下上面的原理圖
  • use_features_in_secondary: 默認是false, 意思是第二層的迴歸器只接收第一層迴歸器的結果進行訓練和預測, 和我們上面介紹的原理樣,如果設置爲true,意思是第二層的迴歸器不僅接收第一層迴歸器的結果,還接收原始的數據集一塊進行訓練。
  • shuffle: 如果設置爲true, 則在交叉驗證之前的訓練數據將在擬合階段打亂順序。

方法:

  • 依然是.fit(x,y)進行訓練, 但這裏的x和y要求是數組了, 所以如果是DataFrame, 需要np.array()一下。並且X的shape[n_samples, n_features], y的shape, [n_samples]
  • 預測依然是.redict(x_test), 只不過這裏的x_test依然是數組, 形狀和上面的一樣。

下面就是針對這個比賽,我們進行模型的融合:

# 使用lgb和xgb那兩個模型
def bulid_modl_xgb(x_train, y_train):
    model = XGBRegressor(n_estimators=400, learning_rate=0.05, gamma=0, subsample=0.8, colsample_bytree=0.9, max_depth=7)
    model.fit(x_train, y_train)
    return model

def bulid_modl_lgb(x_train, y_train):
    model = LGBMRegressor(n_estimators=1100, leaves=200, learning_rate=0.05, objective='regression_l1')
    model.fit(x_train, y_train)
    return model


def build_model_stackReg(x_train, y_train, xgb, lgb, lr):
    model = StackingCVRegressor(regressors=(xgb, lgb), meta_regressor=lr)
    model.fit(np.array(x_train), np.array(y_train))
    return model

這樣我們就定義好了xgb, lgb,以及融合的模型, 我們下面跑一些看看效果:

# LGB
model_lgb = bulid_modl_lgb(X, Y_ln)
val_lgb = model_lgb.predict(XTest)
MAE_lgb = mean_absolute_error(Ytrue, np.expm1(val_lgb))
print(MAE_lgb)   # 496.6811541144921

# XGB
model_xgb = bulid_modl_xgb(X,Y_ln)
val_xgb = model_xgb.predict(XTest)
MAE_xgb = mean_absolute_error(Ytrue, np.expm1(val_xgb))
print(MAE_xgb)   # 514.9826415367127

# Stacking
xgb = XGBRegressor(n_estimators=4000, learning_rate=0.05, gamma=0, subsample=0.8, colsample_bytree=0.9, max_depth=7)
lgb = LGBMRegressor(n_estimators=11000, leaves=200, learning_rate=0.05, objective='regression_l1')
lr = LinearRegression()
model_stack = build_model_stackReg(X, Y_ln, xgb, lgb, lr)
val_stack = model_stack.predict(np.array(XTest))
MAE_stack = mean_absolute_error(Ytrue, np.expm1(val_stack))
print(MAE_stack)    # 487.9388240608211

可以發現, 經過融合, 使得模型的效果有了提升, 這時候如果再考慮加權融合,或許效果會更好。

下面看看Stacking如何用到分類的任務中。

4.1.2 分類中的Stacking

這裏依然是以鳶尾花數據集分類爲例, 首先自己先實現一下stacking加深理解,然後使用mlxtend.classifier.StackingClassifier實現模型的融合。

from sklearn.ensemble import RandomForestClassifier
from sklearn.ensemble import ExtraTreesClassifier, GradientBoostingClassifier
from sklearn.metrics import roc_auc_score

# 創建數據集
iris =load_iris()
data_0 = iris.data
data = data_0[:100, :]

target_0 = iris.target
target = target_0[:100]

# 模型融合中用到的單個模型
clfs = [LogisticRegression(solver='lbfgs'),
        RandomForestClassifier(n_estimators=5, n_jobs=-1, criterion='gini'),
        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.3, random_state=2020)

dataset_blend_train = np.zeros((X.shape[0], len(clfs)))   # 每個模型的預測作爲第二層的特徵
dataset_blend_test = np.zeros((X_predict.shape[0], len(clfs)))

# 5折stacking
n_splits = 5
skf = StratifiedKFold(n_splits)
skf = skf.split(X, y)

for j, clf in enumerate(clfs):
    # 依次訓練各個單模型
    dataset_blend_test_j = np.zeros((X_predict.shape[0], 5))
    for i, (train, test) in enumerate(skf):
        # 5——fold交叉訓練,使用第i個部分作爲預測, 剩餘的部分來訓練模型, 獲得其預測的輸出作爲第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(solver='lbfgs')
clf.fit(dataset_blend_train, y)
y_submission = clf.predict_proba(dataset_blend_test)[:,1]

print("Val auc Score of Stacking: %f" % (roc_auc_score(y_predict, y_submission)))

## 結果如下:
val auc Score: 1.000000
val auc Score: 0.500000
val auc Score: 0.500000
val auc Score: 0.500000
val auc Score: 0.500000
Val auc Score of Stacking: 1.000000

上面這個是自己實現的兩層模型的融合, 可以發現寫起來還是挺麻煩的,關鍵就是那個兩層循環。 下面看看用工具來實現會不會代碼量會少一些。

StackingClassifierAPI及參數如下:

StackingClassifier(classifiers, meta_classifier, use_probas=False, average_probas=False, verbose=0, use_features_in_secondary=False), 這裏的參數和上面的StackingCVRegressor基本上差不多

  • classifiers: 基分類器, 數組形式[clf1, clf2, clf3], 每個基分類器的屬性被存儲在類屬性 self.clfs_.
  • meta_classifier: 目標分類器,即將前面分類器合起來的分類器
  • use_probas : bool (default: False) ,如果設置爲True, 那麼目標分類器的輸入就是前面分類輸出的類別概率值而不是類別標籤
  • average_probas : bool (default: False),用來設置上一個參數當使用概率值輸出的時候是否使用平均值。
  • verbose : int, optional (default=0)。用來控制使用過程中的日誌輸出,當 verbose = 0時,什麼也不輸出, verbose = 1,輸出迴歸器的序號和名字。verbose = 2,輸出詳細的參數信息。verbose > 2, 自動將verbose設置爲小於2的,
  • use_features_in_secondary : bool (default: False). 如果設置爲True,那麼最終的目標分類器就被基分類器產生的數據和最初的數據集同時訓練。如果設置爲False,最終的分類器只會使用基分類器產生的數據訓練。

方法: .fit(), .predict()常用

當然這裏我爲了有個交叉驗證的效果,用的StackingCVClassifier,參數和上面基本一樣,不過有個cv參數,可以實現交叉驗證的效果。

from mlxtend.classifier import StackingCVClassifier
# 上面的這個操作,如果換成StackingClassifier, 是這樣的形式:
clf1 = RandomForestClassifier(n_estimators=5, n_jobs=-1, criterion='gini')
clf2 = ExtraTreesClassifier(n_estimators=5, n_jobs=-1, criterion='gini')
clf3 = ExtraTreesClassifier(n_estimators=5, n_jobs=-1, criterion='entropy')
clf4 = GradientBoostingClassifier(learning_rate=0.05, subsample=0.5, max_depth=6, n_estimators=5)
clf5 = LogisticRegression(solver='lbfgs')

sclf = StackingCVClassifier(classifiers=[clf1, clf2, clf3, clf4], meta_classifier=clf, cv=3)
sclf.fit(X, y)
# 5這交叉驗證
#scores = cross_val_score(sclf, X, y, cv=3, scoring='accuracy')

y_submission = sclf.predict(X_predict)

print("Val auc Score of Stacking: %f" % (roc_auc_score(y_predict, y_submission)))

是不是代碼量少了很多了? 好了,到這裏就把Stacking的原理和基本實現都整理完了, 趁着熱乎,下面再介紹一個和Stacking很相似的一種方式, 叫做Blending,有人說Blending的融合是弱化版的Stacking,是切分樣本集爲不相交的子樣本然後用各個算法生成結果再融合,並且不適用交叉驗證,這種方法不能夠最大限度的利用數據,而Stacking是得到各個算法訓練全樣本的結果再用一個元算法融合這些結果,效果會比較好一些,它可以選擇使用網格搜索和交叉驗證。

是不是有點好奇了呢? 我們下面看看具體細節吧。

4.2 Blending

Blending是一種和Stacking很相像的模型融合方式,它與Stacking的區別在於訓練集不是通過K-Fold的CV策略來獲得預測值從而生成第二階段模型的特徵,而是建立一個Holdout集(留出集),例如30%的訓練數據(類似於把原始的訓練集先分成兩部分, 比如70%的數據作爲新的訓練集, 剩下的30%的數據作爲測試集), 但是通過查看資料,好像是有兩個版本, 下面分別介紹一下。

4.2.1 Blending版本一(單純的Holdout)

一個版本就是單純的Holdout集,就是我直接把訓練集分成兩部分,70%作爲新的訓練集, 30%作爲測試集,然後用這70%的訓練集分別訓練第一層的模型,然後在30%的測試集上進行預測, 把預測的結果作爲第二層模型的訓練集特徵,這是訓練部分。 預測部分就是把真正的測試集先用第一層的模型預測,把預測結過作爲第二層測試集的特徵進行第二層的預測。 過程圖長下面這個樣子:


看這個圖應該很容易理解這個意思了。 這種方法實現起來也比較容易。基本上和stacking的代碼差不多,只不過少了內層的循環而已,畢竟每個模型不用交叉驗證了。

#創建訓練的數據集
#創建訓練的數據集
data_0 = iris.data
data = data_0[:100,:]

target_0 = iris.target
target = target_0[:100]
 
#模型融合中使用到的各個單模型
clfs = [LogisticRegression(solver='lbfgs'),
        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.3, random_state=2020)

#切分訓練數據集爲d1,d2兩部分
X_d1, X_d2, y_d1, y_d2 = train_test_split(X, y, test_size=0.5, random_state=2020)
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):
    #依次訓練各個單模型
    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 = 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("Val auc Score of Blending: %f" % (roc_auc_score(y_predict, y_submission)))

# 結果:
Val auc Score of Blending: 1.000000

4.2.2 Blending版本二(Holdout交叉)

第二個版本的話依然是有一個Holdout集合,但是引入了交叉驗證的那種思想,也就是每個模型看到的Holdout集合並不一樣。即每個模型會看到這個30%的數據會不一樣,說白了,就是把Stacking流程中的K-Fold CV 改成 HoldOut CV。第二階段的stacker模型就基於第一階段模型對這30%訓練數據的預測值進行擬合。

  1. 我們在第一層中, 用70%的訓練集訓練多個模型, 然後去預測那30%的數據得到預測值Pi, 同時也預測test集得到預測值Ti。 這裏注意,那30%的數據每個模型並不是一樣,也是類似於交叉驗證的那種劃分方式,只不過stacking那裏是每個模型都會經歷K折交叉驗證,也就是有多少模型,就會有多少次K折交叉驗證,而blending這裏是所有模型合起來只經歷了一次K折交叉驗證(說的有點繞,看下圖就容易懂了)
  2. 第二層中,我們就直接用30%數據在第一層預測的結果Pi進行合併, 作爲新的訓練集train2, test集的預測值Ti合併作爲新的測試集test2, 然後訓練第二層的模型。

Blending的過程訓練和預測過程可以使用下圖來表示:
在這裏插入圖片描述
這個圖有沒有點似曾相識的感覺呢? 這個能看出和stacking之間的區別來嗎? 需要注意的是,網上很多文章在介紹Stacking的時候其實用的是Blending的圖, 所以這裏要看好了,兩者還是有些區別的。

那麼究竟有什麼區別呢?

Blending的優勢在於:

  • Blending比較簡單,而Stacking相對比較複雜;
  • 能夠防止信息泄露:generalizers和stackers使用不同的數據;

而缺點在於:

  • 只用了整體數據的一部分;
  • 最終模型可能對留出集(holdout set)過擬合;
  • Stacking多次交叉驗證要更加穩健。

有文章說這兩種技術所得的結果都相差不多,如何選擇取決於個人喜好。如果難以抉擇的話,可以同時使用兩種技術並來個第三層將其結果合併起來。當然,Blending沒有找到實現好的工具,得需要自己寫。

# 模型融合中用到的單個模型
clfs = [LogisticRegression(solver='lbfgs'),
        RandomForestClassifier(n_estimators=5, n_jobs=-1, criterion='gini'),
        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.3, random_state=2020)

dataset_blend_train = np.zeros((int(X.shape[0]/n_splits), len(clfs)))   # 每個模型的預測作爲第二層的特徵
dataset_blend_test = np.zeros((X_predict.shape[0], len(clfs)))

# 5折stacking
n_splits = 5
skf = StratifiedKFold(n_splits)
skf = skf.split(X, y)

fold = {}
for i, (train, test) in enumerate(skf):
    fold[i] = (X[train], y[train], X[test], y[test])
Y_blend = []
for j, clf in enumerate(clfs):
    # 依次訓練各個單模型
    dataset_blend_test_j = np.zeros((X_predict.shape[0], 5))
    
    # 5——fold交叉訓練,使用第i個部分作爲預測, 剩餘的部分來訓練模型, 獲得其預測的輸出作爲第i部分的新特徵。
    X_train, y_train, X_test, y_test = fold[j]
    clf.fit(X_train, y_train)
    dataset_blend_train[:, j] =  clf.predict(X_test)
    Y_blend.extend(y_test)
    
    # 對於測試集,直接用這k個模型的預測值作爲新的特徵
    dataset_blend_test[:, j] = clf.predict(X_predict)
        
    print("val auc Score: %f" % roc_auc_score(y_predict, dataset_blend_test[:, j]))

dataset_blend_train = dataset_blend_train.T.reshape(70, -1)
dataset_blend_test = np.mean(dataset_blend_test, axis=1).reshape(-1, 1)
Y_blend = np.array(Y_blend).reshape(-1, 1)

clf = LogisticRegression(solver='lbfgs')
clf.fit(dataset_blend_train, Y_blend)
y_submission = clf.predict(dataset_blend_test)

print("Val auc Score of Stacking: %f" % (roc_auc_score(y_predict, y_submission)))

## 結果:
Val auc Score of Stacking: 1.000000

到這裏, 基本上就把模型融合的重頭戲整理完了, 這部分內容還是有點多的, 最後總結部分會再次梳理邏輯。 其實理清楚邏輯這部分的知識就容易拎起來了, 以後需要的時候再回來查相應的部分知識就可以了。下面再介紹一些其他的方法。

5. 一些其他方法(Stacking變化)

模型融合的方式已經整理完了,在結束之前,還有一種比較好的訓練思路也介紹一下,就是將特徵放進模型中預測, 並將預測結果變化作爲新的特徵加入原有的特徵中, 再經過模型預測結果(Stacking變化), 可以反覆預測多次將結果加入最後的特徵中。

def Ensemble_add_feature(train,test,target,clfs):
    
    # n_flods = 5
    # skf = list(StratifiedKFold(y, n_folds=n_flods))

    train_ = np.zeros((train.shape[0],len(clfs*2)))
    test_ = np.zeros((test.shape[0],len(clfs*2)))

    for j,clf in enumerate(clfs):
        '''依次訓練各個單模型'''
        # print(j, clf)
        '''使用第1個部分作爲預測,第2部分來訓練模型,獲得其預測的輸出作爲第2部分的新特徵。'''
        # X_train, y_train, X_test, y_test = X[train], y[train], X[test], y[test]

        clf.fit(train,target)
        y_train = clf.predict(train)
        y_test = clf.predict(test)

        ## 新特徵生成
        train_[:,j*2] = y_train**2
        test_[:,j*2] = y_test**2
        train_[:, j+1] = np.exp(y_train)
        test_[:, j+1] = np.exp(y_test)
        # print("val auc Score: %f" % r2_score(y_predict, dataset_d2[:, j]))
        print('Method ',j)
    
    train_ = pd.DataFrame(train_)
    test_ = pd.DataFrame(test_)
    return train_,test_

data_0 = iris.data
data = data_0[:100,:]

target_0 = iris.target
target = target_0[:100]

x_train,x_test,y_train,y_test=train_test_split(data,target,test_size=0.3)
x_train = pd.DataFrame(x_train) ; x_test = pd.DataFrame(x_test)

#模型融合中使用到的各個單模型
clfs = [LogisticRegression(),
        RandomForestClassifier(n_estimators=5, n_jobs=-1, criterion='gini'),
        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)]

New_train,New_test = Ensemble_add_feature(x_train,x_test,y_train,clfs)

clf = LogisticRegression()
# clf = GradientBoostingClassifier(learning_rate=0.02, subsample=0.5, max_depth=6, n_estimators=30)
clf.fit(New_train, y_train)
y_emb = clf.predict_proba(New_test)[:, 1]

print("Val auc Score of stacking: %f" % (roc_auc_score(y_test, y_emb)))

好了,關於模型的融合技巧和思路就基本上搞定了, 馬上就到了尾聲, 再堅持一會, 在結束之前,還想介紹一點結果的部署相關的知識,說白了,就是保存模型和導入模型。 因爲我們訓練一次模型也挺不容易的,尤其是這個比賽中, 數據量很大,模型如果很複雜,稍微訓練一次就需要5-6個小時, 這時候我們最好是保存一下我們的模型, 以防下一次用的時候可以直接用。

5. 簡單的結果部署

結果部署這塊介紹兩種方式,一種是通過pickle序列化和反序列化機器學習模型,一種是joblib序列化和反序列化。

  1. 通過pickle序列化和反序列化機器學習的模型
    pickle是標準的Python序列化的方法, 可以通過它來序列化機器學習算法生成的模型。並且將其保存到文件當中,當需要對新數據進行預測時,將保存在文件中的模型發序列化,並用其來預測新數據的結果。

    import pickle
    
    """第一種, pickle的序列化和反序列化"""
    pickle.dump(model, open('./model/xgb1.pkl', 'wb'))
    model1 = pickle.load(open('./model/xgb1.pkl', 'rb'))
    model1.predict(dtest)
    
  2. 通過joblib序列化和反序列化機器學習的模型
    joblib是SciPy生態環境的一部分, 提供了通用的工具來序列化Python的對象和反序列化Python的對象。通過joblib序列化對象時採用Numpy的格式保存數據,對某些保存數據到模型的算法非常有效。

    """第二種模型的存儲與導入方式 - sklearn的joblib"""
    from sklearn.externals import joblib
    joblib.dump(model, './model/xgb.pkl')
    model2 = joblib.load('./model/xgb.pkl')
    model2.predict(dtest)
    

6. 總結

終於到總結了, 這篇文章作爲數據挖掘入門系列的壓軸, 內容還是很多的,先總的梳理一下這篇文章的內容, 這篇文章是基於已經調參好的模型去研究如何發揮出模型更大的性能。 從模型的結果, 樣本集的集成和模型自身融合三個方面去整理, 模型的結果方面,對於迴歸問題,我們可以對模型的結果進行加權融合等方式使得結果更好, 對於分類問題,我們可以使用Voting的方式去得到最終的結果。 樣本集的集成技術方面,我們學了Boosting和Bagging方式, 都是把多個弱分類器進行集成的技術, 但是兩者是不同的。 模型自身的融合方面, 我們學習了Stacking和Blending的原理及具體實現方法,介紹了mlxtend庫裏面的模型融合工具。 最後又介紹了點模型保存的知識。 下面依然是一張導圖把知識拎起來:
在這裏插入圖片描述
今天的這部分內容很多,不要奢望於讀一遍就能把它記住, 即使我這樣整理一遍,我再回想,依然是隻有一個框架和大體的原理知識,細節部分也沒法掌握住。 所以如果想熟記於心,我覺得最好的方式還是得不停的去用, 去實踐和思考。 然後遇到不理解知識的回來查,查完再去用,這樣反覆多遍,這些知識就可以變成自己的了。

下面是關於這部分的經驗總結(來自Datawhale團隊ML67大神)

比賽的融合這個問題,個人的看法來說其實涉及多個層面,也是提分和提升模型魯棒性的一種重要方法:

  1. 結果層面的融合,這種是最常見的融合方法,其可行的融合方法也有很多,比如根據結果的得分進行加權融合,還可以做Log,exp處理等。在做結果融合的時候,有一個很重要的條件是模型結果的得分要比較近似,然後結果的差異要比較大,這樣的結果融合往往有比較好的效果提升。
  2. 特徵層面的融合,這個層面其實感覺不叫融合,準確說可以叫分割,很多時候如果我們用同種模型訓練,可以把特徵進行切分給不同的模型,然後在後面進行模型或者結果融合有時也能產生比較好的效果。
  3. 模型層面的融合,模型層面的融合可能就涉及模型的堆疊和設計,比如加Staking層,部分模型的結果作爲特徵輸入等,這些就需要多實驗和思考了,基於模型層面的融合最好不同模型類型要有一定的差異,用同種模型不同的參數的收益一般是比較小的。

參考

PS: 本次數據挖掘學習,專題知識將在天池分享,詳情可關注公衆號Datawhale。

歷經15天,圍繞着一個二手車價格預測的比賽,終於把數據挖掘入門系列的知識點整理完畢, 這次多虧了Datawhale舉辦的數據挖掘這次專題學習,才讓我有這樣一個機會學習和整理,因爲數據挖掘知識點很雜, 所以之前就一直想着抽出時間來整理整理所學的知識,結果總是拖延和告訴自己沒有時間,這次終於在Datawhale組織下把這塊知識整理完畢, 至少有了一個基本的框架在這裏。 這15天走來,收穫很大,不僅是知識層面,比賽層面那麼簡單,更有幸認識了很多夥伴和志同道合的朋友, 依然是那句話一個人或許會走的很快,但一羣人可以走的更遠, 所以這次意識到組隊學習的重要性和有趣性。

關於本次系列, 我在這裏稍微的梳理一下邏輯,這個系列我稱之爲零基礎數據挖掘入門系列,圍繞着一個比賽進行展開的, 第一篇文章叫做賽題的理解,其實是一個熱身,只是熟悉了一下這個賽題在做什麼事情,如何分析一個賽題等。 第二篇文章叫做數據探索性分析, 從這裏開始進入正題, 這一塊從數據初識,感知,不惑,洞玄,知命五個角度去認識我們的數據,和數據交朋友,目的就是發現數據存在的一些問題。 第三篇文章叫做數據的清洗和轉換, 這一塊的任務是解決數據存在的一些問題, 比如缺失,異常,偏斜等。 第四篇文章叫做特徵工程, 這一塊主要是基於清洗好的數據進一步挖掘信息,基於背景知識,對特徵進行各種組合和挖掘,這一塊是非常重要的部分。 第五篇文章叫做模型的建立和調參, 這一塊任務是基於已經處理好的數據進行模型的建立和分析,選出優秀的模型並且調好參數,技巧也是很多,也是非常重要的一部分。 第六篇文章叫做模型的融合和結果部署, 是基於調參好的模型去研究如何進一步發揮模型的性能,通過各種組合的方式。 這就是這個系列的邏輯關係了,可以幫助更好的理解每一篇到底是幹什麼的以及每一篇之間的關係。

通過這六篇文章,應該是可以走進數據挖掘的大門並且去嘗試一個數據比賽了,當然,我們不能奢求簡單的讀一遍就可以記住這些知識,因爲每一篇都是萬字長文,每一篇都蘊含着豐富的知識點和細節,即使我這樣整理一遍,我也沒法全部熟記於心, 但我希望夥伴們讀完之後能有一個框架在自己的腦海中, 然後通過後面不斷的學習和鍛鍊, 不斷的查閱和思考,去彌補這個框架中的模糊,只有多動手,多嘗試才能最終變成自己的知識。 這些知識如果想記住,依然是那簡單的六個字無它, 唯手熟爾

當然,這個系列對我來說也並不意味着結束, 反而算是一個新的開始,學習這個東西是沒有止境的, 這個系列也只是整理了一些皮毛並且只涉及到了迴歸的一些內容且不全,還會繼續更新(後面有機會,還想着圍繞一個分類的比賽再整理一波分類的知識,這個看情況和時間)。 關於數據挖掘,後面還有更好玩的東西等着我們探索, 由於我目前也是小白,上面的知識點理解上難免會有疏漏和誤解, 如果夥伴們發現了,希望幫我指出來。 也希望能幫助到更多的夥伴, 如果對這些東西感興趣,也歡迎私信或者加V交流(學習知識是一方面, 多個朋友或者同路的人豈不是更好 😉)。

這個比賽的知識點就整理到這裏了, 由於這些天一直忙着整理這個系列知識, 二手車價格預測的比賽倒是沒好好研究呢,只是粗略的做了些東西,不過像我說的,掌握了這些知識之後並用到這個比賽,進前100是沒有問題的, 接下來的時間,就打算拿着這些知識去好好的實戰一波了, 也歡迎留言交流,咱們一起Rush! 😉

關於這個系列的代碼,我後期整理完畢會放上來!

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