1. 導言
在前幾個章節中,我們學習了關于回歸和分類的算法,同時也讨論了如何将這些方法內建為強大的算法的內建學習方式,分别是Bagging和Boosting。本章我們繼續讨論內建學習方法的最後一個成員–Stacking,這個內建方法在比賽中被稱為“懶人”算法,因為它不需要花費過多時間的調參就可以得到一個效果不錯的算法,同時,這種算法也比前兩種算法容易了解的多,因為這種內建學習的方式不需要了解太多的理論,隻需要在實際中加以運用即可。 stacking嚴格來說并不是一種算法,而是精美而又複雜的,對模型內建的一種政策。Stacking內建算法可以了解為一個兩層的內建,第一層含有多個基礎分類器,把預測的結果(元特征)提供給第二層, 而第二層的分類器通常是邏輯回歸,他把一層分類器的結果當做特征做拟合輸出預測結果。在介紹Stacking之前,我們先來對簡化版的Stacking進行讨論,也叫做Blending,接着我們對Stacking進行更深入的讨論。
2. Blending內建學習算法
不知道大家小時候有沒有過這種經曆:老師上課提問到你,那時候你因為開小差而無法立刻得知問題的答案。就在你彷徨的時候,由于你平時人緣比較好,是以周圍的同學向你伸出援手告訴了你他們腦中的正确答案,是以你對他們的答案加以總結和分析最終的得出正确答案。相信大家都有過這樣的經曆,說這個故事的目的是為了引出內建學習家族中的Blending方式,這種內建方式跟我們的故事是十分相像的。 如圖:(圖檔來源:https://blog.csdn.net/maqunfi/article/details/82220115)

下面我們來詳細讨論下這個Blending內建學習方式:
- (1) 将資料劃分為訓練集和測試集(test_set),其中訓練集需要再次劃分為訓練集(train_set)和驗證集(val_set);
- (2) 建立第一層的多個模型,這些模型可以使同質的也可以是異質的;
- (3) 使用train_set訓練步驟2中的多個模型,然後用訓練好的模型預測val_set和test_set得到val_predict, test_predict1;
- (4) 建立第二層的模型,使用val_predict作為訓練集訓練第二層的模型;
- (5) 使用第二層訓練好的模型對第二層測試集test_predict1進行預測,該結果為整個測試集的結果。
(圖檔來源:https://blog.csdn.net/sinat_35821976/article/details/83622594)
在這裡,大佬們來梳理下這個過程:
在(1)步中,總的資料集被分成訓練集和測試集,如80%訓練集和20%測試集,然後在這80%的訓練集中再拆分訓練集70%和驗證集30%,是以拆分後的資料集由三部分組成:訓練集80%* 70%
、測試集20%、驗證集80%* 30% 。訓練集是為了訓練模型,驗證集是為了調整模型(調參),測試集則是為了檢驗模型的優度。
在(2)-(3)步中,我們使用訓練集建立了K個模型,如SVM、random forests、XGBoost等,這個是第一層的模型。 訓練好模型後将驗證集輸入模型進行預測,得到K組不同的輸出,我們記作 A 1 , . . . , A K A_1,...,A_K A1,...,AK,然後将測試集輸入K個模型也得到K組輸出,我們記作 B 1 , . . . , B K B_1,...,B_K B1,...,BK,其中 A i A_i Ai的樣本數與驗證集一緻, B i B_i Bi的樣本數與測試集一緻。如果總的樣本數有10000個樣本,那麼使用5600個樣本訓練了K個模型,輸入驗證集2400個樣本得到K組2400個樣本的結果 A 1 , . . . , A K A_1,...,A_K A1,...,AK,輸入測試集2000個得到K組2000個樣本的結果 B 1 , . . . , B K B_1,...,B_K B1,...,BK 。
在(4)步中,我們使用K組2400個樣本的驗證集結果 A 1 , . . . , A K A_1,...,A_K A1,...,AK作為第二層分類器的特征,驗證集的2400個标簽為因變量,訓練第二層分類器,得到2400個樣本的輸出。
在(5)步中,将輸入測試集2000個得到K組2000個樣本的結果 B 1 , . . . , B K B_1,...,B_K B1,...,BK放入第二層分類器,得到2000個測試集的預測結果。
以上是Blending內建方式的過程,接下來我們來分析這個內建方式的優劣:
其中一個最重要的優點就是實作簡單粗暴,沒有太多的理論的分析。但是這個方法的缺點也是顯然的:blending隻使用了一部分資料集作為留出集進行驗證,也就是隻能用上資料中的一部分,實際上這對資料來說是很奢侈浪費的。
關于這個缺點,我們以後再做改進,我們先來用一些案例來使用這個內建方式。
# 加載相關工具包
import numpy as np
import pandas as pd
import matplotlib.pyplot as plt
plt.style.use("ggplot")
%matplotlib inline
import seaborn as sns
# 建立資料
from sklearn import datasets
from sklearn.datasets import make_blobs
from sklearn.model_selection import train_test_split
data, target = make_blobs(n_samples=10000, centers=2, random_state=1, cluster_std=1.0 )
## 建立訓練集和測試集
X_train1,X_test,y_train1,y_test = train_test_split(data, target, test_size=0.2, random_state=1)
## 建立訓練集和驗證集
X_train,X_val,y_train,y_val = train_test_split(X_train1, y_train1, test_size=0.3, random_state=1)
print("The shape of training X:",X_train.shape)
print("The shape of training y:",y_train.shape)
print("The shape of test X:",X_test.shape)
print("The shape of test y:",y_test.shape)
print("The shape of validation X:",X_val.shape)
print("The shape of validation y:",y_val.shape)
The shape of training X: (5600, 2)
The shape of training y: (5600,)
The shape of test X: (2000, 2)
The shape of test y: (2000,)
The shape of validation X: (2400, 2)
The shape of validation y: (2400,)
# 設定第一層分類器
from sklearn.svm import SVC
from sklearn.ensemble import RandomForestClassifier
from sklearn.neighbors import KNeighborsClassifier
# 将輸出結果輸出機率 劃分節點是基尼系數
clfs = [SVC(probability = True),RandomForestClassifier(n_estimators=5, n_jobs=-1, criterion='gini'),KNeighborsClassifier()]
# 設定第二層分類器
from sklearn.linear_model import LinearRegression
lr = LinearRegression()
# 輸出第一層的驗證集結果與測試集結果
val_features = np.zeros((X_val.shape[0],len(clfs))) # 初始化驗證集結果
test_features = np.zeros((X_test.shape[0],len(clfs))) # 初始化測試集結果
for i,clf in enumerate(clfs):
clf.fit(X_train,y_train)
val_feature = clf.predict_proba(X_val)[:, 1]
test_feature = clf.predict_proba(X_test)[:,1]
val_features[:,i] = val_feature
test_features[:,i] = test_feature
print(val_feature)
[2.03768927e-04 5.14759728e-04 9.99999841e-01 ... 9.99999888e-01
9.99999390e-01 9.99999861e-01]
[0. 0. 1. ... 1. 1. 1.]
[0. 0. 1. ... 1. 1. 1.]
val_features
array([[2.03768927e-04, 0.00000000e+00, 0.00000000e+00],
[5.14759728e-04, 0.00000000e+00, 0.00000000e+00],
[9.99999841e-01, 1.00000000e+00, 1.00000000e+00],
...,
[9.99999888e-01, 1.00000000e+00, 1.00000000e+00],
[9.99999390e-01, 1.00000000e+00, 1.00000000e+00],
[9.99999861e-01, 1.00000000e+00, 1.00000000e+00]])
2000
# 将第一層的驗證集的結果輸入第二層訓練第二層分類器
lr.fit(val_features,y_val)
# 輸出預測的結果
from sklearn.model_selection import cross_val_score
cross_val_score(lr,test_features,y_test,cv=5)
array([1., 1., 1., 1., 1.])
可以看到,在每一折的交叉驗證的效果都是非常好的,這個內建學習方法在這個資料集上是十分有效的,不過這個資料集是我們虛拟的,是以大家可以把他用在實際資料上看看效果。
小總結
1> predict_proba是預測機率的,這裡是二分類問題,預測的是0,1的機率,結果是一個機率矩陣,這是取出預測結果為1的機率,其實也沒有為什麼,就是有點像內建政策裡面的硬投票而已
2> 也可以取出0的機率來預測
3 > predict_proba 是預測機率的,predict是預測類别結果,有點像硬投票,舉例
模型預測
0的機率是0.1,
1的機率是0.9,
那麼predict_proba 輸出的就是[0.1,0.9]
predict輸出的就是1
随機森林,KNN,predict_proba這裡輸出的是機率,都是0或1這裡,應該預測的機率就是這樣,下面作業的鸢尾花預測機率雖然大部分是0或1,但是還是有類似0.2的出現
作業:
我們剛剛的例子是針對人造資料集,表現可能會比較好一點,
因為我們使用Blending方式對iris資料集進行預測,并用第四章的決策邊界畫出來,找找規律。
import numpy as np
import matplotlib.pyplot as plt
plt.style.use("ggplot")
%matplotlib inline
import seaborn as sns
from sklearn import datasets
from sklearn.model_selection import train_test_split
import warnings
warnings.filterwarnings("ignore")
# 導入鸢尾花資料集
iris = datasets.load_iris()
data = iris.data
target = iris.target
# 建立訓練集和測試集
X_train1,X_test,y_train1,y_test = train_test_split(data,target,test_size = 0.2,random_state = 2021)
# 建立訓練集和驗證集
X_train,X_val,y_train,y_val = train_test_split(X_train1,y_train1,test_size = 0.3,random_state = 2021)
print("The shape of training X:",X_train.shape)
print("The shape of training y:",y_train.shape)
print("The shape of test X:",X_test.shape)
print("The shape of test y:",y_test.shape)
print("The shape of validation X:",X_val.shape)
print("The shape of validation y:",y_val.shape)
The shape of training X: (84, 4)
The shape of training y: (84,)
The shape of test X: (30, 4)
The shape of test y: (30,)
The shape of validation X: (36, 4)
The shape of validation y: (36,)
# 設定第一層分類器
from sklearn.svm import SVC
from sklearn.ensemble import RandomForestClassifier
from sklearn.neighbors import KNeighborsClassifier
# 将輸出結果輸出機率 劃分節點是基尼系數
clfs = [SVC(probability = True),RandomForestClassifier(n_estimators=5, n_jobs=-1, criterion='gini'),KNeighborsClassifier()]
# 設定第二層分類器
from sklearn.linear_model import LinearRegression
lr = LinearRegression()
# 輸出第一層的驗證集結果與測試集結果
val_features = np.zeros((X_val.shape[0],len(clfs))) # 初始化驗證集結果
test_features = np.zeros((X_test.shape[0],len(clfs))) # 初始化測試集結果
for i,clf in enumerate(clfs):
clf.fit(X_train,y_train)
val_feature = clf.predict_proba(X_val)[:, 1]
test_feature = clf.predict_proba(X_test)[:,1]
val_features[:,i] = val_feature
test_features[:,i] = test_feature
print(val_feature)
[0.00493364 0.83773677 0.04197246 0.27763374 0.94622907 0.00439715
0.95711089 0.03135599 0.05062256 0.0354973 0.03128457 0.95089986
0.06723653 0.71473882 0.02139152 0.00629482 0.95373221 0.03370935
0.03114566 0.02598184 0.13446994 0.80883523 0.04162672 0.0334936
0.92910467 0.04939443 0.77074718 0.03357751 0.01916813 0.00806051
0.74661525 0.00519089 0.41896984 0.02434585 0.03614896 0.00392579]
[0. 1. 0. 0. 1. 0. 1. 0. 0. 0. 0. 1. 0. 1. 0.2 0. 1. 0.
0. 0. 0. 1. 0. 0. 0.8 0. 1. 0. 0. 0. 1. 0. 1. 0. 0. 0. ]
[0. 1. 0. 0.6 1. 0. 1. 0. 0. 0. 0. 1. 0. 1. 0. 0. 1. 0.
0. 0. 0.2 1. 0. 0. 1. 0. 0.8 0. 0. 0. 0.8 0. 0.4 0. 0. 0. ]
# 将第一層的驗證集的結果輸入第二層訓練第二層分類器
lr.fit(val_features,y_val)
# 輸出預測的結果
from sklearn.model_selection import cross_val_score
from sklearn.model_selection import cross_validate
cross_validate(lr,test_features,y_test,cv=5,scoring='accuracy')
{'fit_time': array([0.00099993, 0.0010004 , 0.00100231, 0. , 0.00099921]),
'score_time': array([0.00100064, 0.0009985 , 0. , 0.00099945, 0.00100064]),
'test_score': array([nan, nan, nan, nan, nan])}
小總結
這是cross_val_score(lr,test_features,y_test,cv=5,scoring=‘accuracy’)預測不知道為什麼輸出的是控制,用cross_validate(lr,test_features,y_test,cv=5,scoring=‘accuracy’)有結果輸出,猜測是交叉驗證這裡Blending好像将資料劃分了兩次吧
cross_val_score的 scoring參數值解析
畫圖邊界
參考
# 畫出單層決策樹與Adaboost的決策邊界
x_min = X_train[:,0].min() - 1 # 10.03
x_max = X_train[:,0].max() + 1 # 15.34
y_min = X_train[:,1].min() - 1 # 0.27
y_max = X_train[:,1].max() + 1 # 4.68
xx,yy = np.meshgrid(np.arange(x_min,x_max,0.1),np.arange(y_min,y_max,0.1)) # 相當于在弄了很多點(x,y)出來
f,axarr = plt.subplots(nrows=1,ncols=2,sharex='col',sharey='row',figsize = (12,6))
for idx,clf,tt in zip([0,1],[tree,ada],['Decision tree','Adaboost']):
clf.fit(X_train,y_train)
Z = clf.predict(np.c_[xx.ravel(),yy.ravel()])
Z = Z.reshape(xx.shape) # alpha 介于0(透明)和1(不透明)之間。
axarr[idx].contourf(xx, yy, Z, alpha=0.38)
axarr[idx].scatter(X_train[y_train==0,0],X_train[y_train==0,1],c='blue',marker='^')
axarr[idx].scatter(X_train[y_train==1,0],X_train[y_train==1,1],c='red',marker='o')
axarr[idx].set_title(tt)
axarr[0].set_ylabel('Alcohol',fontsize=12)
plt.tight_layout()
plt.text(0,-0.2,s='0D280/0D315 of diluted wines',ha='center',va='center',fontsize=12,transform=axarr[1].transAxes)
plt.show()
有點沒研究出東西,先留個坑,考完試來看看或到時參考參考大佬們的
參考:
- Datawhale GitHub開源