習(xí)7天實戰(zhàn):Bagging、Boosting與Stacking全攻略)
簡介由杰森·布朗利編寫的《Python集成學(xué)習(xí)算法7天速成》中文翻譯版源自MachineLearningMastery系列面向已有Python、NumPy和scikit-learn基礎(chǔ)的開發(fā)者用七節(jié)課快速補齊集成學(xué)習(xí)核心技能。資源共1個PDF文件壓縮包大小481KB便于下載后離線閱讀文檔按課程01至07組織章節(jié)獨立性較強依次講解什么是集成學(xué)習(xí)、裝袋集成、隨機森林、AdaBoost、梯度提升、投票集成和堆疊集成每課設(shè)有明確任務(wù)并提供基于scikit-learn的完整代碼示例可照做預(yù)測建模實驗。針對模型方差、數(shù)據(jù)泄露和融合策略等容易出錯的環(huán)節(jié)書中給出精簡說明幫助讀者把多個模型組合成更可靠的預(yù)測方案也可遷移到Kaggle等競賽場景。目前已有108人學(xué)習(xí)瀏覽適合希望利用一周時間系統(tǒng)掌握集成學(xué)習(xí)并提升模型表現(xiàn)的開發(fā)者。1. 集成學(xué)習(xí) 7 天速成為什么“打群架”的模型通常比單打獨斗更強如果你正打算用 7 天時間把 Python 集成學(xué)習(xí)算法從只會調(diào)庫練到能講清原理、能上手調(diào)優(yōu)、能排查問題這篇筆記就是按這個目標(biāo)寫的。集成學(xué)習(xí)的核心思想一句話就能說透單個模型容易偏、容易抖但把一群各有毛病、又不太一樣的模型湊在一起投票或接力整體誤差通常會被磨平一大截。這也是 Kaggle 競賽里幾乎每支前排隊伍都會在最后一步用上 Stacking 或 Boosting 的原因。7 天速成不是讓你背公式而是按“并行集成 → 串行集成 → 集成之集成 → 避坑調(diào)優(yōu)”這條主線用 Python 生態(tài)里現(xiàn)成的庫把每個環(huán)節(jié)跑通。適合的對象很明確已經(jīng)會用 sklearn 訓(xùn)練單個模型、但對集成學(xué)習(xí)只停留在“聽過名字”階段的從業(yè)者以及想在項目里把模型精度再往上頂一檔的工程師。第一天到第三天你會感受到“多模型投票”帶來的穩(wěn)定收益第四到第六天進入 Boosting 與 Stacking 后更多的功夫要花在防止過擬合和評估偏差上最后一天請務(wù)必留給自己把整個實驗過程固化成可復(fù)現(xiàn)的記錄。接下來我們從最基礎(chǔ)的并行集成開始。2. 并行集成Voting 與 Bagging先讓模型學(xué)會“投團隊票”2.1 集成學(xué)習(xí)的理論地基偏差-方差分解與“三個臭皮匠”邏輯先回答一個新手必然要問的問題為什么多個模型放在一起效果反而比最好的單個模型還穩(wěn)這要從偏差-方差分解說起。一個模型的泛化誤差大致能拆成三項偏差bias、方差variance和不可約噪聲。偏差大說明模型對規(guī)律的理解不夠經(jīng)常欠擬合方差大說明模型對訓(xùn)練數(shù)據(jù)的波動太敏感換個數(shù)據(jù)集表現(xiàn)就劇烈起伏。單棵決策樹是典型的低偏差高方差模型而線性模型則常常反過來。集成學(xué)習(xí)的思路就是在這兩項之間做文章。Bagging 的核心操作是“并行訓(xùn)練 平均”它通過 Bootstrap 采樣讓每個子模型看到不同的訓(xùn)練子集子模型之間天然存在差異最后投票或平均時彼此的高方差部分會互相抵消。這背后的數(shù)學(xué)直覺是如果各基模型誤差是獨立的那么平均后的方差會按子模型數(shù)量下降哪怕基模型之間不完全獨立只要不是完全相關(guān)方差也能被顯著壓縮。這里有一張常用的誤差拆解表方便你對照自己的項目來判斷該用 Bagging 還是 Boosting當(dāng)前問題主要矛盾推薦的集成方向理由單模型欠擬合訓(xùn)練分很低高偏差Boosting / Stacking串行擬合殘差能系統(tǒng)性降低偏差單模型分?jǐn)?shù)不錯但測試集波動大高方差Bagging / RandomForest并行平均能壓低方差數(shù)據(jù)噪聲大特征冗余多方差與偏差都高先用 Bagging 穩(wěn)底再上 Boosting直接 Boosting 容易把噪聲也學(xué)進去“三個臭皮匠勝過諸葛亮”這句話在機器學(xué)習(xí)里是有前提的皮匠們得有自己的判斷不能全是同一個師傅教出來的。如果 10 個模型高度雷同集成只是在放大同一個錯誤毫無意義。這也是整篇筆記里反復(fù)會出現(xiàn)的一條主線多樣性是集成的靈魂。2.2 用 scikit-learn 跑通 Voting 與 Bagging最小可執(zhí)行代碼先動手跑一個最樸素的 Voting 分類器。Voting 分硬投票和軟投票硬投票是每個模型投一票、少數(shù)服從多數(shù)軟投票是每個模型輸出類別概率后加權(quán)平均再取概率最高的類別。數(shù)據(jù)量小、模型校準(zhǔn)良好時軟投票通常更穩(wěn)。下面這段代碼在一個模擬的二分類數(shù)據(jù)集上直接對比單模型與 Voting 的效果from sklearn.datasets import make_classification from sklearn.model_selection import train_test_split from sklearn.ensemble import VotingClassifier, RandomForestClassifier from sklearn.linear_model import LogisticRegression from sklearn.tree import DecisionTreeClassifier from sklearn.metrics import accuracy_score # 生成 2000 個樣本、20 個特征的模擬二分類數(shù)據(jù) X, y make_classification(n_samples2000, n_features20, n_informative15, random_state42) X_train, X_test, y_train, y_test train_test_split( X, y, test_size0.3, stratifyy, random_state42) # 三個差異明顯的基模型 lr LogisticRegression(max_iter1000, random_state42) dt DecisionTreeClassifier(max_depth5, random_state42) rf RandomForestClassifier(n_estimators50, max_depth8, random_state42) # 軟投票按類別概率加權(quán)平均 voting_clf VotingClassifier( estimators[(lr, lr), (dt, dt), (rf, rf)], votingsoft ) voting_clf.fit(X_train, y_train) for name, model in [(LR, lr), (DT, dt), (RF, rf), (Voting, voting_clf)]: acc accuracy_score(y_test, model.predict(X_test)) print(f{name}: {acc:.4f})這段代碼里最關(guān)鍵的是votingsoft參數(shù)。軟投票要求基模型都有predict_proba方法邏輯回歸和決策樹都滿足。如果你換了某些不支持概率輸出的模型就只能退回到votinghard。我一般會在跑 Baseline 時先用硬投票確認三個模型分?jǐn)?shù)接近后再切到軟投票看是否有提升。接下來是 Bagging 的最小示例。BaggingClassifier 本身是一個通用包裝器你可以把任意基模型塞進去它內(nèi)部通過 Bootstrap 采樣生成多個訓(xùn)練子集并行訓(xùn)練多個副本from sklearn.ensemble import BaggingClassifier from sklearn.tree import DecisionTreeClassifier # 用決策樹作為基模型訓(xùn)練 200 個副本 bag_clf BaggingClassifier( estimatorDecisionTreeClassifier(max_depth10, random_state42), n_estimators200, max_samples0.8, # 每個子模型采樣 80% 的樣本 max_features1.0, # 每個子模型使用全部特征 bootstrapTrue, # 有放回采樣 oob_scoreTrue, # 計算袋外分?jǐn)?shù) n_jobs-1, # 使用全部 CPU 核 random_state42 ) bag_clf.fit(X_train, y_train) print(fBagging OOB Score: {bag_clf.oob_score_:.4f}) print(fBagging Test Accuracy: {accuracy_score(y_test, bag_clf.predict(X_test)):.4f})max_samples0.8意味著每個子模型只看到 80% 的樣本剩下的 20% 沒參與訓(xùn)練正好可以用作天然的驗證集。oob_scoreTrue就是利用這些沒被采到的樣本計算一個袋外分?jǐn)?shù)它約等于交叉驗證的效果但省掉了重復(fù)訓(xùn)練的開銷。對新手來說如果訓(xùn)練集只有幾千條建議把max_samples調(diào)高到 0.9否則每個子模型看到的樣本太少基模型容易欠擬合。2.3 隨機森林Bagging 加特征隨機性以及 OOB 分?jǐn)?shù)的妙用隨機森林可以理解為 Bagging 的一個升級版它在每次節(jié)點分裂時不再從全部特征里找最優(yōu)切分點而是隨機抽取一部分特征再從中選最優(yōu)。這個改動很關(guān)鍵它讓每棵樹學(xué)到的“特長”不同。比如有的樹更依賴特征 A有的樹更依賴特征 B投票時彼此互補整體方差進一步下降。用 sklearn 訓(xùn)練隨機森林只需要改一行from sklearn.ensemble import RandomForestClassifier rf_clf RandomForestClassifier( n_estimators300, max_depthNone, # 不限制深度讓樹自由生長 min_samples_leaf2, # 葉子節(jié)點最少 2 個樣本限制過擬合 max_featuressqrt, # 分類問題默認取特征數(shù)的平方根 oob_scoreTrue, n_jobs-1, random_state42 ) rf_clf.fit(X_train, y_train) print(fRF OOB Score: {rf_clf.oob_score_:.4f}) print(fRF Test Accuracy: {accuracy_score(y_test, rf_clf.predict(X_test)):.4f}) # 查看特征重要性 importance rf_clf.feature_importances_ top_idx importance.argsort()[-5:][::-1] print(Top-5 重要特征索引:, top_idx)max_featuressqrt是分類問題的默認推薦值回歸問題一般用1.0或log2。這段代碼里我把max_depth設(shè)為None配合min_samples_leaf2來讓樹自由生長但葉子不能太純這個組合在中小數(shù)據(jù)集上往往表現(xiàn)不錯。特征重要性feature_importances_是基于“該特征在所有樹中帶來的不純度減少量”累加得到的注意它偏向數(shù)值型特征和取值多的特征只能用作粗篩不能當(dāng)作因果證據(jù)。OOB 分?jǐn)?shù)在這里還有額外價值你不必專門切一塊驗證集就能知道模型大概的水平。做法是先跑一遍不設(shè)置max_depth的隨機森林看 OOB 分?jǐn)?shù)再跑一遍有限制條件的版本對比 OOB 分?jǐn)?shù)漲了說明限制有效跌了說明限制過度。我習(xí)慣把 OOB 分?jǐn)?shù)當(dāng)作調(diào)參時的主要參照省下交叉驗證的大量時間。3. 串行集成Boosting 家族從 AdaBoost 到 GBDT 再到 XGBoost3.1 Boosting 的核心邏輯每個模型負責(zé)“上一輪沒做對的事”如果把 Bagging 比作“并行開會、獨立發(fā)言”Boosting 就是“接力攻堅、步步為營”。它訓(xùn)練一堆模型但順序有先后第 2 個模型重點關(guān)注第 1 個模型做錯的樣本第 3 個模型重點關(guān)注前兩個模型的殘差如此往復(fù)。AdaBoost 的做法是改變樣本權(quán)重——被分錯的樣本權(quán)重變大下一輪模型被迫把注意力集中在這些難樣本上。GBDT梯度提升決策樹換了一種更通用的表述每一輪新增的樹去擬合前面所有樹的負梯度也就是“殘差”。對于回歸任務(wù)殘差就是真實值與當(dāng)前預(yù)測值之差對于分類任務(wù)這個殘差被替換為概率空間上的梯度。為什么擬合殘差有效因為每一輪都在修正當(dāng)前的錯誤方向相當(dāng)于沿著損失函數(shù)下降的方向逐步逼近最優(yōu)解。這里有一個新手容易犯的概念混淆AdaBoost 和 GBDT 都叫 Boosting但 AdaBoost 通過樣本權(quán)重來體現(xiàn)“關(guān)注錯誤”GBDT 通過擬合殘差來體現(xiàn)。前者對異常值極其敏感因為異常點權(quán)重會被不斷放大后者在損失函數(shù)選擇上更靈活因此衍生出了 XGBoost、LightGBM 等一堆工程化實現(xiàn)。理解了這一點你就能明白為什么大多數(shù)比賽和工業(yè)項目最終都選了 GBDT 系算法而不是 AdaBoost。3.2 AdaBoost 與梯度提升的最小實現(xiàn)從 sklearn 到手動理解先用 sklearn 把 AdaBoost 跑通代碼很簡單from sklearn.ensemble import AdaBoostClassifier ada_clf AdaBoostClassifier( estimatorDecisionTreeClassifier(max_depth1, random_state42), # 樹樁 n_estimators200, learning_rate0.5, algorithmSAMME, random_state42 ) ada_clf.fit(X_train, y_train) print(fAdaBoost Test Accuracy: {accuracy_score(y_test, ada_clf.predict(X_test)):.4f})AdaBoost 的基模型在 sklearn 里默認就是深度為 1 的決策樹樁algorithmSAMME是為了兼容多分類的變體。learning_rate0.5表示每一步只按 0.5 的步長采納當(dāng)前模型的貢獻這樣后續(xù)模型還能有修正空間步長太大會導(dǎo)致模型在少數(shù)難樣本上用力過猛太小則訓(xùn)練緩慢。跑完這段代碼后你可以打印ada_clf.estimator_errors_看看每一輪的加權(quán)誤差誤差曲線如果后期開始震蕩說明步長沒調(diào)好或者樹樁太弱。再看回歸任務(wù)的 GBDT 實現(xiàn)同樣是 sklearn 一行搞定from sklearn.ensemble import GradientBoostingRegressor from sklearn.metrics import mean_squared_error gb_reg GradientBoostingRegressor( n_estimators200, learning_rate0.05, max_depth3, subsample0.8, random_state42 ) gb_reg.fit(X_train_reg, y_train_reg) # 回歸數(shù)據(jù)集 pred gb_reg.predict(X_test_reg) print(fGBDT RMSE: {mean_squared_error(y_test_reg, pred, squaredFalse):.4f})subsample0.8是隨機梯度提升的做法每輪只用 80% 的樣本訓(xùn)練當(dāng)前樹。這個參數(shù)不是為了省計算而是引入隨機性來降低與 Bagging 類似的方差同時加快訓(xùn)練。經(jīng)驗上learning_rate與n_estimators必須聯(lián)合調(diào)整學(xué)習(xí)率減半需要的樹數(shù)量大致翻倍。你想快速驗證這一點可以分別跑(0.1, 100)和(0.05, 200)兩組參數(shù)觀察測試分?jǐn)?shù)是否接近。3.3 GBDT 的三件套調(diào)參學(xué)習(xí)率、樹深度與子采樣GBDT 系模型在實踐中最需要關(guān)注的三個參數(shù)就是learning_rate、max_depth和subsample。它們之間是互相牽制的關(guān)系。learning_rate控制每棵樹貢獻的權(quán)重。數(shù)值越小模型越保守需要越多樹來擬合同樣的信號數(shù)值太大前幾棵樹就把訓(xùn)練集學(xué)滿了后面全是噪音擬合。我一般先用固定學(xué)習(xí)率 0.1 跑一遍記錄驗證集分?jǐn)?shù)曲線然后把學(xué)習(xí)率降到 0.05、樹數(shù)量翻倍再對比曲線。如果兩次效果接近說明模型容量足夠問題在別處如果 0.05 顯著更好說明 0.1 時已經(jīng)過擬合了。max_depth在 GBDT 中通常取值 3 到 6遠小于單棵決策樹的深度。原因是 Boosting 每次只擬合殘差不需要太復(fù)雜的樹深度過深反而讓當(dāng)前樹“想太多”把殘差里的噪聲也學(xué)進去。subsample是行采樣比例0.7 到 0.9 之間是常用區(qū)間它對防止過擬合的貢獻在數(shù)據(jù)量小的時候尤其明顯。這三個參數(shù)的正確調(diào)參順序很重要。我建議先固定learning_rate0.1用交叉驗證搜max_depth和subsample找到合適區(qū)間后再把學(xué)習(xí)率降下來、把樹數(shù)量補上去最后微調(diào)早停。順序反了會浪費時間因為你在一個過擬合狀態(tài)下調(diào)學(xué)習(xí)率結(jié)論沒有參考價值。3.4 工程化 BoostingXGBoost 與 LightGBM 該怎么選當(dāng)你從 sklearn 切到真正的工程化 Boosting 庫時先看這張對比表再決定用哪個對比維度XGBoostLightGBM分裂策略預(yù)排序 直方圖近似基于梯度的單邊采樣GOSS 互斥特征捆綁EFB訓(xùn)練速度中等大數(shù)據(jù)集上明顯更快內(nèi)存占用較高更低類別特征支持需手動編碼支持原生類別特征擅長場景中小數(shù)據(jù)、稀疏特征大規(guī)模數(shù)據(jù)、高維特征如果你只是想在 7 天速成里快速拿到可靠結(jié)果我的建議是中小數(shù)據(jù)集優(yōu)先 XGBoost因為它更穩(wěn)定、文檔更成熟數(shù)據(jù)量超過幾十萬行且特征稀疏時直接換 LightGBM速度差距會非常明顯。下面用 XGBoost 的 sklearn 接口做一個最小示例from xgboost import XGBClassifier from sklearn.metrics import roc_auc_score xgb_clf XGBClassifier( n_estimators300, learning_rate0.05, max_depth4, subsample0.8, colsample_bytree0.8, # 每棵樹隨機使用 80% 的特征 eval_metriclogloss, early_stopping_rounds30, random_state42 ) xgb_clf.fit( X_train, y_train, eval_set[(X_test, y_test)], verboseFalse ) print(fXGB AUC: {roc_auc_score(y_test, xgb_clf.predict_proba(X_test)[:, 1]):.4f})這段代碼里early_stopping_rounds30是防止過擬合的關(guān)鍵當(dāng)測試集合上的 logloss 連續(xù) 30 輪沒有提升訓(xùn)練自動終止best_iteration屬性會告訴你最佳樹數(shù)量。colsample_bytree0.8是隨機森林里特征隨機性的 Boosting 版本它迫使每棵樹只依賴部分特征增加多樣性。LightGBM 的接口略有不同它用n_estimators對應(yīng)同樣的概念需要額外設(shè)置num_leaves而不是max_depth。num_leaves理論上可以大于2^max_depth因此它能擬合更復(fù)雜的局部結(jié)構(gòu)但也更容易過擬合。我的經(jīng)驗是先用默認num_leaves31如果驗證集分?jǐn)?shù)不如 XGBoost再逐步調(diào)整而不是一上來就改得面目全非。4. 集成之集成Stacking 與 Blending讓模型預(yù)測值變成新特征4.1 Stacking 為什么能漲點元模型學(xué)的是“誰在哪些樣本上更可靠”Stacking 的思路比 Bagging 和 Boosting 又進了一步Bagging 讓基模型投票Boosting 讓基模型接力而 Stacking 干脆把基模型的預(yù)測結(jié)果當(dāng)作新特征再訓(xùn)練一個元模型來學(xué)習(xí)“在什么情況下應(yīng)該更相信哪個基模型”。舉例來說邏輯回歸可能在樣本 A 上很準(zhǔn)隨機森林在樣本 B 上很準(zhǔn)Stacking 的元模型會學(xué)到這個規(guī)律而不是簡單投票。這里最關(guān)鍵的操作是防止“數(shù)據(jù)泄漏”。如果直接用基模型在訓(xùn)練集上的預(yù)測結(jié)果作為元模型的輸入基模型已經(jīng)見過這些樣本預(yù)測結(jié)果會偏樂觀元模型學(xué)到的就是虛高信號。正確做法是用 K 折交叉驗證為每個訓(xùn)練樣本生成“袋外預(yù)測”O(jiān)ut-of-Fold簡稱 OOF比如 5 折每次用 4 折訓(xùn)練基模型預(yù)測剩下 1 折循環(huán) 5 次后每個訓(xùn)練樣本都拿到一個“沒見過它”的預(yù)測值。測試集上的處理同樣要注意每一折訓(xùn)練好的基模型都要對完整測試集做預(yù)測然后取平均得到測試集的最終預(yù)測特征。很多人第一版 Stacking 漲不了點幾乎都是因為這一步偷懶直接用全量訓(xùn)練模型預(yù)測測試集導(dǎo)致特征分布不一致。4.2 用交叉驗證生成 OOF 預(yù)測完整可復(fù)現(xiàn)代碼下面這段代碼手動實現(xiàn)了 5 折 Stacking 的全流程基模型用隨機森林和邏輯回歸元模型用邏輯回歸import numpy as np from sklearn.model_selection import StratifiedKFold from sklearn.ensemble import RandomForestClassifier from sklearn.linear_model import LogisticRegression from sklearn.metrics import accuracy_score # 準(zhǔn)備 OOF 矩陣形狀是 (訓(xùn)練樣本數(shù), 基模型個數(shù)) kf StratifiedKFold(n_splits5, shuffleTrue, random_state42) base_models [ (rf, RandomForestClassifier(n_estimators100, max_depth6, random_state42)), (lr, LogisticRegression(max_iter1000, random_state42)) ] # OOF 預(yù)測和測試集預(yù)測容器 oof_pred np.zeros((len(X_train), len(base_models))) test_pred np.zeros((len(X_test), len(base_models))) for idx, (name, model) in enumerate(base_models): test_fold_pred np.zeros(len(X_test)) # 累計測試集預(yù)測 for train_idx, val_idx in kf.split(X_train, y_train): # 拆出當(dāng)前折的訓(xùn)練集和驗證集 X_fold_train, X_fold_val X_train[train_idx], X_train[val_idx] y_fold_train, y_fold_val y_train[train_idx], y_train[val_idx] # 在當(dāng)前折上訓(xùn)練基模型 model.fit(X_fold_train, y_fold_train) # 對驗證集預(yù)測概率填充 OOF oof_pred[val_idx, idx] model.predict_proba(X_fold_val)[:, 1] # 對完整測試集預(yù)測概率累加后取平均 test_fold_pred model.predict_proba(X_test)[:, 1] / kf.get_n_splits() test_pred[:, idx] test_fold_pred # 訓(xùn)練元模型輸入是基模型的預(yù)測概率 meta_model LogisticRegression(max_iter1000, random_state42) meta_model.fit(oof_pred, y_train) final_pred meta_model.predict(test_pred) print(fStacking Test Accuracy: {accuracy_score(y_test, final_pred):.4f})這段代碼有幾個細節(jié)值得展開。第一StratifiedKFold保證每折的正負樣本比例和全量數(shù)據(jù)一致分類問題必須用分層采樣否則某些折里可能全是一個類別。第二OOF 矩陣的每一列對應(yīng)一個基模型在全部訓(xùn)練樣本上的“干凈預(yù)測”元模型拿到的特征不會包含“記憶過的答案”。第三測試集預(yù)測用的是每一折模型預(yù)測的平均值這一步不可省如果某一折因為隨機種子問題導(dǎo)致某模型沒收斂平均值能平滑掉異常波動。跑通這段代碼后建議你檢查一下 OOF 矩陣兩列之間的相關(guān)性。如果相關(guān)性超過 0.95說明兩個基模型幾乎沒有差異Stacking 提升空間有限。這時候與其繼續(xù)堆模型不如回頭改進基模型的多樣性。4.3 Blending 作為輕量替代五分鐘版 StackingBlending 是 Stacking 的簡化版它不做交叉驗證而是直接把訓(xùn)練集切出一部分比如 10%當(dāng)作“小驗證集”基模型只在剩余 90% 上訓(xùn)練然后對小驗證集和測試集做預(yù)測元模型用小驗證集的預(yù)測結(jié)果來訓(xùn)練。代碼如下from sklearn.model_selection import train_test_split # 從訓(xùn)練集中切出 10% 作為元模型的小驗證集 X_meta_train, X_meta_val, y_meta_train, y_meta_val train_test_split( X_train, y_train, test_size0.1, stratifyy_train, random_state42 ) def get_base_predictions(model, X_fit, y_fit, X_predict): model.fit(X_fit, y_fit) return model.predict_proba(X_predict)[:, 1] # 基模型在小驗證集和測試集上的預(yù)測 rf_meta_val get_base_predictions(RandomForestClassifier(n_estimators100, random_state42, max_depth6), X_meta_train, y_meta_train, X_meta_val) lr_meta_val get_base_predictions(LogisticRegression(max_iter1000, random_state42), X_meta_train, y_meta_train, X_meta_val) rf_test get_base_predictions(RandomForestClassifier(n_estimators100, random_state42, max_depth6), X_meta_train, y_meta_train, X_test) lr_test get_base_predictions(LogisticRegression(max_iter1000, random_state42), X_meta_train, y_meta_train, X_test) # 拼接特征后訓(xùn)練元模型 meta_features_val np.column_stack([rf_meta_val, lr_meta_val]) meta_features_test np.column_stack([rf_test, lr_test]) meta_model LogisticRegression(max_iter1000) meta_model.fit(meta_features_val, y_meta_val) blend_pred meta_model.predict(meta_features_test) print(fBlending Test Accuracy: {accuracy_score(y_test, blend_pred):.4f})Blending 的優(yōu)點是代碼量少、邏輯直白訓(xùn)練成本低適合在時間緊急時快速出一個集成結(jié)果。代價是它比 Stacking 多浪費了 10% 的訓(xùn)練數(shù)據(jù)給元模型小數(shù)據(jù)集上效果可能不如 OOF 方式。我的建議是7 天速成的第 5 天先跑 Blending 理解流程第 6 天再切到完整的 Stacking兩者對比一下就能直觀感受到 OOF 的價值。4.4 基模型多樣性與元模型選擇的實操建議Stacking 漲不漲點一半取決于基模型的選擇一半取決于元模型的設(shè)計。基模型不要全選同類算法否則學(xué)到的錯誤模式高度一致。我常用的組合是邏輯回歸簡單線性基線、隨機森林高方差、并行、GBDT高偏差、串行、XGBoost帶正則的 Boosting。這四個模型的預(yù)測概率相關(guān)性通常能控制在 0.85 以下相當(dāng)于提供了四種不同視角。元模型的選擇沒有統(tǒng)一答案。我的經(jīng)驗是先從邏輯回歸開始因為它訓(xùn)練快、結(jié)果穩(wěn)、不容易過擬合如果把 OOF 特征換成邏輯回歸后驗證分?jǐn)?shù)有明顯提升說明信息融合有效再試更復(fù)雜的元模型。如果你在元模型里加入原始特征比如把訓(xùn)練數(shù)據(jù)的原始數(shù)值特征也拼進 OOF 矩陣效果可能更好但也更容易過擬合因為元模型現(xiàn)在有了直接“看到”原始特征的能力對 OOF 特征的依賴會下降。這一步要不要做建議以驗證集分?jǐn)?shù)為準(zhǔn)。另外一個經(jīng)常被忽略的點如果基模型里有 XGBoost 或 LightGBM它們的predict_proba輸出在極端類別不均衡下可能非常集中比如全是 0.01 或 0.99這會干擾元模型的邏輯回歸擬合。解決辦法是先把 OOF 概率做標(biāo)簽編碼或分位變換再輸入元模型。這個細節(jié)在第 5 章的類別不平衡部分還會再提。5. 集成學(xué)習(xí)避坑指南5 個真實踩坑記錄與排查思路5.1 OOF 與驗證集劃分不當(dāng)導(dǎo)致線下漲點、線上翻車現(xiàn)象在本地用隨機劃分的訓(xùn)練測試集Stacking 之后準(zhǔn)確率提升了 2%一上真實環(huán)境或線上 A/B 測試效果反而比單模型還差。原因數(shù)據(jù)里可能存在時間依賴或分組結(jié)構(gòu)。比如同一條用戶的多條行為記錄被隨機分到了訓(xùn)練集和測試集模型等于“見過”這個人了OOF 預(yù)測和元模型訓(xùn)練都受到了泄漏信號的干擾。解決檢查數(shù)據(jù)里有沒有“同一實體多條記錄”的情況比如用戶 ID、設(shè)備 ID、訂單 ID。有的話用GroupKFold按實體分組切分保證同一個實體的數(shù)據(jù)全部落在同一折里。如果數(shù)據(jù)是按時間產(chǎn)生的直接按時間點切分訓(xùn)練集和測試集并且用時間前移的交叉驗證方式。排查時先畫一張數(shù)據(jù)的時間分布圖如果訓(xùn)練集和測試集的類別分布或特征分布有明顯漂移先處理分布問題再談集成。5.2 基模型多樣性不足集成活成了“復(fù)讀機”現(xiàn)象隨機森林 XGBoost LightGBM 三個模型做了 Stacking結(jié)果和直接用 XGBoost 幾乎一樣。原因這三個模型雖然算法不同但在同一個數(shù)據(jù)集上、用同一套特征工程學(xué)到的主要規(guī)律高度相似。它們對正確樣本的看法一致對錯誤樣本的錯誤方式也雷同元模型沒有差異化信息可用。解決先計算 OOF 預(yù)測之間的相關(guān)性矩陣如果相關(guān)系數(shù)普遍高于 0.9說明多樣性嚴(yán)重不足。這時優(yōu)先調(diào)整方向不是繼續(xù)加模型而是往特征子集、樣本子集或算法類型三個方向制造差異。常見做法是利用特征分組讓模型 A 只用數(shù)值特征模型 B 只用類別特征模型 C 用全部特征或者同一種算法換不同超參數(shù)比如深樹和淺樹。用這種手法把相關(guān)性壓到 0.8 以下Stacking 才有實際意義。5.3 早停與學(xué)習(xí)率n_estimators 加得越多越準(zhǔn)是錯覺現(xiàn)象訓(xùn)練 Boosting 模型時每次把n_estimators翻倍驗證集分?jǐn)?shù)都在漲直到某個點后開始劇烈下降但你看訓(xùn)練集分?jǐn)?shù)還在穩(wěn)步上升。原因Boosting 的每一輪都在擬合當(dāng)前殘差樹的數(shù)量越多模型對訓(xùn)練集的記憶越完整。驗證集分?jǐn)?shù)開始掉頭的那一刻就是模型開始把噪聲當(dāng)成規(guī)律的時刻。解決固定使用early_stopping_rounds同時把learning_rate降到 0.05 或更低。一個常用做法是先用 0.1 的學(xué)習(xí)率找出大致的最佳區(qū)間再把學(xué)習(xí)率減半、樹數(shù)量翻倍繼續(xù)觀察驗證曲線是否改善。注意早停是基于驗證集指標(biāo)不是訓(xùn)練集有些人習(xí)慣用訓(xùn)練集 logloss 做早停等于用開卷考試成績判斷學(xué)生水平不可取。5.4 類別不均衡下 Boosting 的“強者恒強”現(xiàn)象二分類問題正樣本只占 5%負樣本占 95%跑完 XGBoost 后準(zhǔn)確率 95%看起來很高但一看 F1 分?jǐn)?shù)慘不忍睹正樣本幾乎全被預(yù)測成負類。原因Boosting 在每一輪加權(quán)時會把更多權(quán)重放在被分錯的樣本上。但初始階段負樣本數(shù)量壓倒性占優(yōu)模型很容易把全部樣本預(yù)測為負類造成后來的輪次里正樣本權(quán)重雖然上升但整體決策邊界仍偏向負類一側(cè)。解決先換評估指標(biāo)不要用準(zhǔn)確率改用 F1、PR-AUC 或 ROC-AUC。然后給模型傳遞類別權(quán)重參數(shù)XGBoost 里是scale_pos_weight通常設(shè)為負樣本數(shù)除以正樣本數(shù)LightGBM 里用is_unbalanceTrue或scale_pos_weight。如果還不行對少數(shù)類做樣本加權(quán)或上采樣別急著用 SMOTE先看類別權(quán)重能不能把決策邊界拉回來。集成模型內(nèi)部機制復(fù)雜改完權(quán)重后一定要同時觀察訓(xùn)練集和驗證集的分差權(quán)重過大會讓模型在訓(xùn)練集上過擬合多數(shù)類。5.5 隨機種子與多線程集成結(jié)果像開盲盒現(xiàn)象同一份代碼、同一個參數(shù)配置每次運行得到的驗證集分?jǐn)?shù)都不一樣反復(fù)橫跳 0.5% 左右。原因隨機森林的行采樣、Boosting 的子采樣、交叉驗證的分割順序都依賴隨機數(shù)生成器同時n_jobs-1并行訓(xùn)練時線程調(diào)度的隨機性也會影響浮點數(shù)累加順序產(chǎn)生微小差異。解決在代碼里給所有模型統(tǒng)一設(shè)置random_state交叉驗證也用同一個隨機種子如果用了 XGBoost 或 LightGBM把它們的random_state也設(shè)成同一個整數(shù)。不光模型要固定數(shù)據(jù) shuffle 的順序也要固定。如果你發(fā)現(xiàn)固定了所有隨機種子后結(jié)果仍不穩(wěn)大概率是多線程浮點累加導(dǎo)致此時把n_jobs從-1調(diào)成1復(fù)測一次如果結(jié)果穩(wěn)定了說明是并行計算引入的數(shù)值波動。對上線模型我通常用固定隨機種子跑 3 次取均值把這個均值作為提交分?jǐn)?shù)。6. 7 天收尾手法用三份驗收報告鎖定模型效果別把調(diào)參過程當(dāng)黑匣子7 天速成的最后一天千萬不要急著去搜新模型而是做三件事。第一件事是回顧實驗記錄把 7 天里跑過的每一個模型組合、每一組超參數(shù)、對應(yīng)的驗證集分?jǐn)?shù)和 OOF 相關(guān)性整理成一張表。沒有記錄就等于沒做過因為回想不出哪組配置是因為改了哪個參數(shù)才漲的。我習(xí)慣用 Markdown 表格記錄每行是一次實驗列包括基模型列表、關(guān)鍵參數(shù)、驗證分?jǐn)?shù)、OOF 相關(guān)性、訓(xùn)練用時備注里寫當(dāng)時的判斷。這張表就是你下次面對同類問題時的最強參考。第二件事是穩(wěn)定性驗證用一個小腳本對最終選定的集成模型跑 5 次不同隨機種子記錄每次的驗證集分?jǐn)?shù)計算均值和標(biāo)準(zhǔn)差。標(biāo)準(zhǔn)差小于 0.003 才說明模型結(jié)果可復(fù)現(xiàn)如果標(biāo)準(zhǔn)差偏大說明模型容量或數(shù)據(jù)切分本身不穩(wěn)定這時候分析單次分?jǐn)?shù)沒有意義。穩(wěn)定性驗證的目的是讓你在匯報結(jié)果時心里有底漲的那 2% 是真實差異還是隨機波動。第三件事是特征重要性歸因把最終 Stacking 模型里每個基模型的 OOF 預(yù)測重要性輸出出來看看元模型到底在依賴誰。如果某個基模型的 OOF 特征在元模型里的系數(shù)接近零說明它對這個數(shù)據(jù)集沒有貢獻下次可以去掉如果某個特征在隨機森林里重要性極高但在 XGBoost 里很低說明它對不同算法有不同價值這個特征值得保留。用permutation_importance計算擾動每個特征后驗證分?jǐn)?shù)的下降量比內(nèi)置的重要性更直觀。最后分享一個我自己的教訓(xùn)有一次做時間序列預(yù)測花了三天調(diào)好了 Stacking線上效果異常優(yōu)秀但因為實驗中途換過兩次數(shù)據(jù)切分方式?jīng)]有統(tǒng)一記錄復(fù)盤時完全無法確認哪個版本對應(yīng)哪個結(jié)果最終只能重跑一組基礎(chǔ)實驗確認。從那以后我把“當(dāng)天實驗當(dāng)天記錄”定成鐵律調(diào)參過程絕不當(dāng)黑匣子。這 7 天的速成也許不能讓你變成集成學(xué)習(xí)專家但能讓你建立起一套從模型選型到實驗驗收的完整工作流。希望你也能在最后的驗收環(huán)節(jié)里找到自己最順手的那一套記錄格式。希望幫到你。本文還有配套的精品資源點擊獲取