器學(xué)習(xí)模型評(píng)估與調(diào)優(yōu)全指南:從指標(biāo)選擇到交叉驗(yàn)證)
從標(biāo)題“機(jī)器學(xué)習(xí)10”來(lái)看這應(yīng)該是系列教程的第十篇。到了這個(gè)階段基礎(chǔ)算法、數(shù)據(jù)預(yù)處理、特征工程這些內(nèi)容基本都講過了再往后應(yīng)該聊點(diǎn)真正影響模型上線效果的東西。我打算把這一篇定位在模型評(píng)估與調(diào)優(yōu)上——訓(xùn)練出幾個(gè)模型不難難的是搞清楚哪個(gè)模型靠譜、為什么靠譜、上線之后能不能穩(wěn)住。這一篇就把這些事兒掰開揉碎講清楚適合已經(jīng)掌握基本建模流程、正在往實(shí)戰(zhàn)方向進(jìn)階的讀者。1. 模型評(píng)估為什么值得單獨(dú)拿出來(lái)講很多同學(xué)在第九篇左右的時(shí)候已經(jīng)能跑通一個(gè)完整的建模流程讀數(shù)據(jù)、做清洗、訓(xùn)練模型、看準(zhǔn)確率。但說實(shí)話大多數(shù)人卡在下一步——換了個(gè)模型之后準(zhǔn)確率反而低了加了特征之后效果沒提升調(diào)了半天參數(shù)也不知道有沒有過擬合。這些問題看著是調(diào)參問題本質(zhì)上是評(píng)估體系沒搭好。評(píng)估這件事最常被誤解的一點(diǎn)是準(zhǔn)確率高了就代表模型好。實(shí)際項(xiàng)目里根本不是這么回事。拿分類問題來(lái)說如果正樣本只占5%一個(gè)把所有樣本都判成負(fù)類的模型準(zhǔn)確率照樣能到95%。這種模型上線就是災(zāi)難。所以評(píng)估要做的不只是算一個(gè)數(shù)字而是用一套完整的體系去回答幾個(gè)問題這個(gè)模型的預(yù)測(cè)能力到底怎么樣它在哪類樣本上容易出錯(cuò)我該用哪個(gè)指標(biāo)來(lái)衡量它訓(xùn)練集上的表現(xiàn)到底可不可信這一篇我會(huì)按實(shí)戰(zhàn)流程來(lái)講從設(shè)計(jì)評(píng)估方案、選指標(biāo)、做交叉驗(yàn)證到診斷過擬合、調(diào)超參數(shù)一條線走下來(lái)。里面的代碼都用常見的庫(kù)來(lái)寫思路適用于任何建模框架。2. 先搞清楚要回答什么問題再談評(píng)估方案2.1 業(yè)務(wù)目標(biāo)決定評(píng)估指標(biāo)選什么指標(biāo)不是看哪個(gè)數(shù)字好看而是看業(yè)務(wù)上最不能接受哪類錯(cuò)誤。拿一個(gè)信用風(fēng)險(xiǎn)模型來(lái)說把高風(fēng)險(xiǎn)用戶誤判成低風(fēng)險(xiǎn)損失的是真金白銀把低風(fēng)險(xiǎn)用戶誤判成高風(fēng)險(xiǎn)損失的是客戶體驗(yàn)。兩類錯(cuò)誤成本不一樣評(píng)估指標(biāo)的權(quán)重就應(yīng)該不一樣。再比如做商品推薦系統(tǒng)的排序模型用戶不在乎你推薦了多少個(gè)在乎的是前排有沒有他想要的。這時(shí)候用召回率、精確率反而不如用排序類指標(biāo)合適。所以我開頭講的第一個(gè)點(diǎn)永遠(yuǎn)是建模之前先跟業(yè)務(wù)方確認(rèn)哪些錯(cuò)誤是致命的哪些錯(cuò)誤只是麻煩。這個(gè)確認(rèn)結(jié)果直接決定了下游所有評(píng)估工作的走向。2.2 先把數(shù)據(jù)切分方案定下來(lái)評(píng)估方案里最基礎(chǔ)但最容易出錯(cuò)的一步是數(shù)據(jù)切分。很多人常規(guī)做法是直接拿一份數(shù)據(jù)7成訓(xùn)練3成測(cè)試跑完拉到。但實(shí)際業(yè)務(wù)里有幾個(gè)坑一是時(shí)序類數(shù)據(jù)不能隨機(jī)切按時(shí)間順序切分才合理不然就是拿未來(lái)的數(shù)據(jù)教過去成績(jī)虛高二是有分組結(jié)構(gòu)的數(shù)據(jù)比如同一個(gè)用戶的多條行為記錄如果隨機(jī)切同一個(gè)用戶同時(shí)出現(xiàn)在訓(xùn)練集和測(cè)試集里模型等于開卷考試。正確的做法是先看數(shù)據(jù)的生成方式再?zèng)Q定切分策略。沒有時(shí)間依賴和個(gè)體依賴的問題可以用常規(guī)的隨機(jī)切分有時(shí)序依賴的按時(shí)間點(diǎn)前80%訓(xùn)練后20%測(cè)試有用戶分組的按用戶維度切分保證一個(gè)用戶的所有記錄只出現(xiàn)在一邊。這個(gè)原則很多人一開始不當(dāng)回事到上線才發(fā)現(xiàn)問題代價(jià)大得多。3. 指標(biāo)選擇與計(jì)算別只看準(zhǔn)確率3.1 分類指標(biāo)逐個(gè)過分類問題里準(zhǔn)確率是最直觀的指標(biāo)但也是最容易誤導(dǎo)人的。除了前面說的樣本不平衡場(chǎng)景還有一個(gè)問題是準(zhǔn)確率對(duì)概率閾值敏感。很多模型輸出的是概率值默認(rèn)按0.5切分但業(yè)務(wù)場(chǎng)景里可能0.3就該觸發(fā)預(yù)警了。所以真正能看模型本身能力的是跟閾值無(wú)關(guān)的指標(biāo)比如AUC。精確率和召回率是一對(duì)需要放在一起看的指標(biāo)。精確率是“你判為正類的樣本里有多大比例真是正類”召回率是“所有正類樣本里你抓回來(lái)多少”。這兩個(gè)天然互斥調(diào)一個(gè)閾值拉高精確率召回率必然降。實(shí)際項(xiàng)目里我習(xí)慣的做法是畫出P-R曲線看曲線下的面積和形狀再結(jié)合業(yè)務(wù)成本選一個(gè)平衡點(diǎn)。F1值是精確率和召回率的調(diào)和平均數(shù)適合兩者都重要的時(shí)候用。但注意F1值的前提是精確率和召回率同等重要如果業(yè)務(wù)上偏向某一方F1反而不如加權(quán)版本實(shí)用。AUC這個(gè)指標(biāo)值得多說一句。它的直觀含義是隨機(jī)抽一個(gè)正樣本和一個(gè)負(fù)樣本模型給正樣本打分高于負(fù)樣本的概率。這個(gè)性質(zhì)決定了AUC不受樣本不平衡比例影響也不受閾值選擇影響非常適合做模型能力基線比較。但是AUC只看排序不管絕對(duì)分?jǐn)?shù)兩個(gè)模型AUC一樣在實(shí)際觸發(fā)閾值下的表現(xiàn)可能差很遠(yuǎn)所以AUC用于選模型階段閾值選擇還得另算。3.2 回歸指標(biāo)速查回歸問題也有類似的問題。均方誤差MSE對(duì)大誤差敏感因?yàn)檎`差被平方放大適合我特別不希望出現(xiàn)大偏差的場(chǎng)景。平均絕對(duì)誤差MAE更穩(wěn)健適合有離群值的時(shí)候用。R2乍看像“解釋了多少方差”但樣本量小的時(shí)候R2很容易虛高。我自己的習(xí)慣是回歸任務(wù)里MSE和MAE一起報(bào)告再畫一張預(yù)測(cè)值和真實(shí)值的散點(diǎn)圖看形狀。只看任何一個(gè)數(shù)值指標(biāo)都可能漏掉系統(tǒng)性偏差的問題。3.3 一個(gè)動(dòng)手算指標(biāo)的例子用一段常見的分類模型代碼來(lái)演示完整流程from sklearn.metrics import ( accuracy_score, precision_score, recall_score, f1_score, roc_auc_score, roc_curve ) # y_true 為真實(shí)標(biāo)簽y_pred 為閾值0.5下的預(yù)測(cè)標(biāo)簽 # y_prob 為模型輸出的正類概率 acc accuracy_score(y_true, y_pred) prec precision_score(y_true, y_pred) rec recall_score(y_true, y_pred) f1 f1_score(y_true, y_pred) auc roc_auc_score(y_true, y_prob) print(f準(zhǔn)確率: {acc:.4f}) print(f精確率: {prec:.4f}) print(f召回率: {rec:.4f}) print(fF1值: {f1:.4f}) print(fAUC: {auc:.4f})跑完這些代碼你對(duì)模型的認(rèn)識(shí)就從“一個(gè)數(shù)字”變成“一組畫像”。比如AUC有0.92但召回率只有0.4這說明模型排序能力很強(qiáng)但閾值切太高了把很多正樣本壓在了0.5以下。這時(shí)候正確的動(dòng)作不是放棄模型而是調(diào)閾值。4. 交叉驗(yàn)證實(shí)操?gòu)膯未吻蟹值椒€(wěn)健評(píng)估4.1 為什么要做K折交叉驗(yàn)證單次切分的缺點(diǎn)是評(píng)估結(jié)果方差太大這次運(yùn)氣好分到的測(cè)試集容易準(zhǔn)確率就高下次分到的難準(zhǔn)確率就崩。K折交叉驗(yàn)證把數(shù)據(jù)分成K份輪流拿1份做測(cè)試、其余做訓(xùn)練所有樣本都有機(jī)會(huì)當(dāng)測(cè)試集最終結(jié)果取K次平均。這樣做出來(lái)的評(píng)估更接近模型的真實(shí)水平。K怎么選也有講究。K太小比如2或3每次訓(xùn)練數(shù)據(jù)不夠評(píng)估出來(lái)的模型跟最終用全量數(shù)據(jù)訓(xùn)練的模型差距大。K太大比如20訓(xùn)練集之間重疊度高評(píng)估結(jié)果的方差會(huì)變小但計(jì)算成本成倍增加。實(shí)際項(xiàng)目中5折和10折用得最多數(shù)據(jù)量小的時(shí)候用10折更穩(wěn)數(shù)據(jù)量大的時(shí)候5折就夠訓(xùn)練太慢就降K。4.2 分層K折與分組K折普通的K折交叉驗(yàn)證有個(gè)隱患如果正負(fù)樣本比例是9比1隨機(jī)分出來(lái)的某幾折里可能正樣本特別少甚至沒有那一折的評(píng)估就失真。解決辦法是分層K折每一折都保持和原始數(shù)據(jù)相同的類別比例這也是默認(rèn)該用的方案。再往前一步如果數(shù)據(jù)里有用戶ID這種分組結(jié)構(gòu)就要用分組K折保證同一個(gè)組的所有樣本被分到同一折避免信息泄漏。這個(gè)細(xì)節(jié)在Kaggle競(jìng)賽里很常見業(yè)務(wù)項(xiàng)目里反而容易被忽略。4.3 交叉驗(yàn)證代碼模板from sklearn.model_selection import StratifiedKFold from sklearn.ensemble import RandomForestClassifier import numpy as np skf StratifiedKFold(n_splits5, shuffleTrue, random_state42) auc_list [] acc_list [] for train_idx, test_idx in skf.split(X, y): X_train, X_test X.iloc[train_idx], X.iloc[test_idx] y_train, y_test y.iloc[train_idx], y.iloc[test_idx] model RandomForestClassifier(n_estimators100, random_state42) model.fit(X_train, y_train) y_prob model.predict_proba(X_test)[:, 1] y_pred (y_prob 0.5).astype(int) auc_list.append(roc_auc_score(y_test, y_prob)) acc_list.append(accuracy_score(y_test, y_pred)) print(fAUC均值: {np.mean(auc_list):.4f} ± {np.std(auc_list):.4f}) print(f準(zhǔn)確率均值: {np.mean(acc_list):.4f} ± {np.std(acc_list):.4f})注意看輸出里我加了均值和標(biāo)準(zhǔn)差。標(biāo)準(zhǔn)差是很多人不看的但它恰恰告訴你評(píng)估穩(wěn)不穩(wěn)。如果5折的AUC分別是0.91、0.90、0.92、0.86、0.85均值0.888但方差挺大說明模型在部分?jǐn)?shù)據(jù)子集上表現(xiàn)明顯偏差需要進(jìn)一步查是不是某些特征在某些群體上失效了。提示交叉驗(yàn)證的結(jié)果用于選模型和估參數(shù)確定最終模型之后最好再用一份完全沒碰過的測(cè)試集做一次最終驗(yàn)收這樣報(bào)告出來(lái)的性能才有說服力。5. 過擬合診斷學(xué)習(xí)曲線和驗(yàn)證曲線的實(shí)際用法5.1 怎么判斷模型是真的好還是背下來(lái)了訓(xùn)練集表現(xiàn)很好、測(cè)試集表現(xiàn)差是建模里最經(jīng)典的現(xiàn)象。問題在于很多人看到這個(gè)現(xiàn)象只知道“噢過擬合了”但不知道怎么定位是哪層環(huán)節(jié)出的問題。我的排查順序是這樣先看訓(xùn)練集分?jǐn)?shù)和測(cè)試集分?jǐn)?shù)的差距。如果訓(xùn)練集分?jǐn)?shù)高但測(cè)試集分?jǐn)?shù)低典型過擬合優(yōu)先考慮降低模型復(fù)雜度、加正則化或者加數(shù)據(jù)。如果訓(xùn)練集分?jǐn)?shù)也不高那就是欠擬合模型的容量不夠或者特征沒到位這時(shí)候調(diào)參沒意義應(yīng)該先換更強(qiáng)的模型或者做特征工程。5.2 畫學(xué)習(xí)曲線學(xué)習(xí)曲線能直觀回答“再加數(shù)據(jù)還有沒有用”的問題。橫軸是訓(xùn)練樣本量縱軸是模型得分同時(shí)畫訓(xùn)練集得分和交叉驗(yàn)證得分兩條曲線。from sklearn.model_selection import learning_curve train_sizes, train_scores, cv_scores learning_curve( model, X, y, cv5, train_sizesnp.linspace(0.1, 1.0, 8), scoringroc_auc, random_state42 ) train_mean train_scores.mean(axis1) cv_mean cv_scores.mean(axis1)如果兩條曲線隨著樣本量增加逐漸靠攏說明當(dāng)前瓶頸是數(shù)據(jù)量繼續(xù)收集數(shù)據(jù)有效果。如果兩條曲線始終并排保持較大差距說明模型的方差太大單純加數(shù)據(jù)效果有限得靠正則化或降低復(fù)雜度來(lái)解決。這個(gè)判斷直接影響你要不要花費(fèi)巨大精力去擴(kuò)數(shù)據(jù)。5.3 用驗(yàn)證曲線觀察參數(shù)敏感度驗(yàn)證曲線用來(lái)觀察某個(gè)超參數(shù)對(duì)模型性能的影響。以隨機(jī)森林的樹深度為例from sklearn.model_selection import validation_curve param_range range(3, 15) train_scores, test_scores validation_curve( RandomForestClassifier(n_estimators100, random_state42), X, y, param_namemax_depth, param_rangeparam_range, cv5, scoringroc_auc )畫出來(lái)的曲線里如果某個(gè)參數(shù)在很寬的范圍內(nèi)性能都差不多說明模型對(duì)這個(gè)參數(shù)不敏感你不需要花大力氣去精調(diào)它。如果曲線有個(gè)明顯的峰峰值兩邊都在掉這個(gè)參數(shù)就值得放進(jìn)調(diào)優(yōu)范圍里細(xì)摳。很多同學(xué)一上來(lái)就全參數(shù)網(wǎng)格搜索其實(shí)先跑一遍驗(yàn)證曲線把敏感參數(shù)篩出來(lái)能省大量時(shí)間。6. 超參數(shù)調(diào)優(yōu)網(wǎng)格搜索、隨機(jī)搜索與貝葉斯優(yōu)化的取舍6.1 網(wǎng)格搜索為什么慢網(wǎng)格搜索的原理是把每個(gè)參數(shù)的候選值列出來(lái)笛卡爾積組合后全部跑一遍交叉驗(yàn)證。參數(shù)少的時(shí)候好用參數(shù)一多就指數(shù)爆炸。假設(shè)有4個(gè)參數(shù)各給10個(gè)候選值那就是一萬(wàn)個(gè)組合每個(gè)組合跑5折交叉驗(yàn)證五萬(wàn)次訓(xùn)練時(shí)間成本直接失控。所以網(wǎng)格搜索適合參數(shù)少、每個(gè)參數(shù)候選值也少的情況更多時(shí)候我拿它做最后一輪的精細(xì)搜索而不是從一開始就用它探路。6.2 隨機(jī)搜索是性價(jià)比之選隨機(jī)搜索的思路是每個(gè)參數(shù)按分布隨機(jī)采樣采樣一定次數(shù)后取效果最好的組合。它不保證覆蓋所有組合但實(shí)踐中有個(gè)很有意思的現(xiàn)象在許多參數(shù)組合里真正對(duì)性能影響大的往往只有一兩個(gè)參數(shù)隨機(jī)采樣更容易碰到這些關(guān)鍵參數(shù)附近的區(qū)域。在同樣的預(yù)算下隨機(jī)搜索找到好參數(shù)組合的概率遠(yuǎn)高于網(wǎng)格搜索。我的習(xí)慣是先用隨機(jī)搜索把大概區(qū)域摸出來(lái)再用網(wǎng)格搜索在小范圍內(nèi)精調(diào)。兩層配合效果比單獨(dú)用任何一種都好。6.3 貝葉斯優(yōu)化的進(jìn)階玩法貝葉斯優(yōu)化跟前面兩種搜索的邏輯不一樣它會(huì)把之前跑過的參數(shù)組合和對(duì)應(yīng)效果作為先驗(yàn)建立一個(gè)代理模型預(yù)測(cè)哪些參數(shù)組合最有可能帶來(lái)高分?jǐn)?shù)然后優(yōu)先去試這些組合。思路很像一個(gè)有經(jīng)驗(yàn)的人調(diào)參前幾次隨機(jī)試后面越來(lái)越有方向?,F(xiàn)在很多庫(kù)都封裝了貝葉斯優(yōu)化最常見的比如Optuna。用起來(lái)比GridSearchCV順手的地方在于它可以定義搜索的迭代預(yù)算還能中途剪枝把表現(xiàn)差的參數(shù)組合提前終止節(jié)省大量訓(xùn)練時(shí)間。import optuna def objective(trial): n_estimators trial.suggest_int(n_estimators, 50, 300) max_depth trial.suggest_int(max_depth, 3, 20) min_samples_split trial.suggest_int(min_samples_split, 2, 10) model RandomForestClassifier( n_estimatorsn_estimators, max_depthmax_depth, min_samples_splitmin_samples_split, random_state42 ) cv_scores cross_val_score(model, X, y, cv5, scoringroc_auc) return cv_scores.mean() study optuna.create_study(directionmaximize) study.optimize(objective, n_trials50)跑完之后直接看study.best_params拿最優(yōu)參數(shù)。注意調(diào)參之后必須重新評(píng)估一次不要在調(diào)參過程中用到的交叉驗(yàn)證結(jié)果上直接報(bào)最終性能那屬于“看著答案考試”分?jǐn)?shù)虛高。注意調(diào)參的本質(zhì)是在有限的數(shù)據(jù)上尋找最優(yōu)復(fù)雜度。如果數(shù)據(jù)量特別小調(diào)參過細(xì)反而會(huì)把噪聲學(xué)進(jìn)去導(dǎo)致驗(yàn)證分?jǐn)?shù)高但上線性能差。參數(shù)差不多能用就行別追求極致的交叉驗(yàn)證分?jǐn)?shù)。7. 常見問題與排查技巧實(shí)錄7.1 為什么加了特征準(zhǔn)確率反而下降典型特征工程誤區(qū)。新特征跟已有特征高度相關(guān)模型把權(quán)重分散到冗余特征上等于引入了噪聲。另外某些特征只在訓(xùn)練集里有效在測(cè)試集里分布完全不一樣直接把模型帶偏。排查方式很簡(jiǎn)單加了特征之后先看訓(xùn)練集表現(xiàn)有沒有明顯提升如果訓(xùn)練集沒提升、測(cè)試集還在掉說明特征是噪聲如果訓(xùn)練集提升了但測(cè)試集掉了說明過擬合。7.2 交叉驗(yàn)證分?jǐn)?shù)漂移很大怎么處理5折的結(jié)果一折0.95一折0.80通常不是模型的問題而是數(shù)據(jù)里存在某些特定子群體模型沒學(xué)好。我會(huì)把這幾個(gè)表現(xiàn)差的折的數(shù)據(jù)單獨(dú)拉出來(lái)看它們的類別分布、特征分布跟其他折有沒有明顯差異有時(shí)候是某個(gè)時(shí)間段的數(shù)據(jù)或者某個(gè)渠道的數(shù)據(jù)質(zhì)量差定位之后針對(duì)處理比盲目調(diào)參有效得多。7.3 線上預(yù)測(cè)效果和線下評(píng)測(cè)差距大這種情況最頭疼。常見原因有幾個(gè)一是訓(xùn)練數(shù)據(jù)和線上真實(shí)輸入的數(shù)據(jù)分布不一致也就是沒有做特征分布的監(jiān)控二是數(shù)據(jù)泄漏線下切分時(shí)把本該是未來(lái)的信息漏進(jìn)去了導(dǎo)致線下虛高三是線上使用的特征管線跟訓(xùn)練時(shí)不一致可能少處理了一個(gè)字段或者歸一化參數(shù)不一樣。排查順序建議先檢查推理代碼的數(shù)據(jù)預(yù)處理流程再重新審查數(shù)據(jù)切分邏輯最后才是懷疑分布漂移。7.4 一個(gè)我踩過的調(diào)參坑有次隨機(jī)森林模型線下AUC很好看上線后每天凌晨效果暴跌。排查了很久發(fā)現(xiàn)訓(xùn)練的時(shí)候?qū)θ笔е底隽斯潭ㄖ堤畛涞€上凌晨時(shí)段的數(shù)據(jù)缺失率高很多填充方式不變但特征分布變了。后來(lái)在評(píng)估階段專門加了一個(gè)模擬線上數(shù)據(jù)分布的驗(yàn)證集每次訓(xùn)練完都拿它做一次驗(yàn)收。從那之后我再也不敢只用隨機(jī)切分的交叉驗(yàn)證分?jǐn)?shù)做上線決策。8. 實(shí)操心得我的評(píng)估與調(diào)優(yōu)完整流程最后分享一下我實(shí)際項(xiàng)目的固定流程這套流程跑熟了之后每次建模心里都很有底。第一步先花時(shí)間跟業(yè)務(wù)方確認(rèn)核心指標(biāo)搞清楚哪種錯(cuò)誤成本高。第二步按數(shù)據(jù)生成方式設(shè)計(jì)切分策略洗數(shù)據(jù)前就把驗(yàn)證方案定好。第三步在基準(zhǔn)模型上跑交叉驗(yàn)證拿到評(píng)估基線。第四步畫學(xué)習(xí)曲線判斷瓶頸在數(shù)據(jù)還是模型。第五步用驗(yàn)證曲線篩敏感參數(shù)再用隨機(jī)搜索或者貝葉斯優(yōu)化找最優(yōu)區(qū)域。第六步在最優(yōu)區(qū)域用小范圍網(wǎng)格精調(diào)。第七步用留出測(cè)試集做最終驗(yàn)收同時(shí)檢查特征分布和預(yù)測(cè)分布是否有異常。第八步記錄實(shí)驗(yàn)日志包括數(shù)據(jù)版本、參數(shù)組合、指標(biāo)結(jié)果和當(dāng)時(shí)判斷后續(xù)復(fù)盤全靠它。這個(gè)流程最大的價(jià)值是每一步都留下可追溯的決策依據(jù)。我不看玄學(xué)式的調(diào)參也不看拍腦袋的模型對(duì)比一切改動(dòng)都建立在評(píng)估結(jié)果之上。評(píng)估體系搭起來(lái)了建模項(xiàng)目的交付質(zhì)量和效率都會(huì)明顯提升。后續(xù)有精力的話還可以往自動(dòng)化機(jī)器學(xué)習(xí)的方向擴(kuò)展把參數(shù)搜索和評(píng)估流程封裝成標(biāo)準(zhǔn)模板每次新項(xiàng)目直接套用。