據(jù)預(yù)處理到模型部署的AI項(xiàng)目實(shí)戰(zhàn))
一個(gè)人剛接觸機(jī)器學(xué)習(xí)時(shí)最容易被帶偏的方向是直接撲向深度學(xué)習(xí)框架。但做了這些年實(shí)際項(xiàng)目我的觀點(diǎn)一直很明確真正該作為第一站、也值得長(zhǎng)期留在工具箱里的是scikit-learn。它不需要你寫(xiě)復(fù)雜的前向傳播腳本不需要盯著損失曲線懷疑人生只需要把數(shù)據(jù)整理干凈用一套統(tǒng)一的API把模型訓(xùn)練、評(píng)估、調(diào)參、部署整個(gè)流程串起來(lái)——這就是一條非常完整的AI模型開(kāi)發(fā)全流程。這篇文章我打算用一個(gè)房?jī)r(jià)回歸項(xiàng)目作為主線從環(huán)境準(zhǔn)備一直走到模型保存上線。中間會(huì)穿插大量我在實(shí)際項(xiàng)目中踩過(guò)的坑包括數(shù)據(jù)泄漏、參數(shù)搜索效率、Pipeline設(shè)計(jì)這些教科書(shū)很少講但工作中一定會(huì)遇到的問(wèn)題。無(wú)論你是剛?cè)腴T(mén)的新手還是已經(jīng)跑過(guò)幾個(gè)demo想系統(tǒng)化自己流程的開(kāi)發(fā)者這條鏈路應(yīng)該都能給你一些可復(fù)現(xiàn)的參考。1. 為什么選scikit-learn先把工具箱的脾氣摸透1.1 三個(gè)動(dòng)作統(tǒng)治一切APIscikit-learn最大的特點(diǎn)不是某個(gè)模型多強(qiáng)而是它把所有算法工具都收斂到了極少數(shù)幾個(gè)接口上。你只需要記住三個(gè)動(dòng)作fit學(xué)習(xí)、predict預(yù)測(cè)、transform變換。fit(X, y)訓(xùn)練模型或者讓預(yù)處理器從數(shù)據(jù)中學(xué)習(xí)參數(shù)predict(X)用訓(xùn)練好的模型對(duì)新樣本做預(yù)測(cè)transform(X)用學(xué)到的規(guī)則把數(shù)據(jù)轉(zhuǎn)成另一種形式典型的就是標(biāo)準(zhǔn)化。新手可能覺(jué)得這沒(méi)什么但真正用久了會(huì)意識(shí)到這個(gè)設(shè)計(jì)有多重要。換模型的時(shí)候代碼幾乎不用動(dòng)只是把類名換掉。今天用邏輯回歸明天換隨機(jī)森林只要你還在scikit-learn體系內(nèi)前后代碼結(jié)構(gòu)和流程完全一致。這和自己在PyTorch里寫(xiě)一套訓(xùn)練循環(huán)換模型就要改forward、改優(yōu)化器配置的體驗(yàn)完全不同。另一個(gè)好處是文檔質(zhì)量。官方文檔對(duì)每個(gè)參數(shù)都給出了說(shuō)明和默認(rèn)值示例代碼基本都能直接跑通。我當(dāng)年學(xué)這個(gè)庫(kù)的時(shí)候就是靠反復(fù)看官方示例把概念串起來(lái)的。1.2 和pandas、numpy的分工協(xié)作scikit-learn本身不負(fù)責(zé)數(shù)據(jù)讀取和處理它吃進(jìn)去的是一個(gè)二維數(shù)組或者類數(shù)組結(jié)構(gòu)。實(shí)際項(xiàng)目中通常是用pandas把CSV或Excel讀進(jìn)來(lái)做初步清洗然后轉(zhuǎn)換出特征矩陣X和目標(biāo)向量y給模型用。這里有一個(gè)新手必踩的坑pandas里某一列提取出來(lái)是Series是一維的直接當(dāng)成X傳給模型會(huì)報(bào)Expected 2D array, got 1D array instead之類的錯(cuò)誤。解決辦法也很簡(jiǎn)單import pandas as pd df pd.DataFrame({面積: [80, 120, 95], 房?jī)r(jià): [200, 300, 240]}) X df[[面積]] # 保持二維 y df[房?jī)r(jià)] # 一維向量我見(jiàn)過(guò)不少人在這里卡半天其實(shí)就是X必須保留特征維度的概念沒(méi)搞清楚。記住一句話X永遠(yuǎn)是二維的哪怕你只有一個(gè)特征。2. 一整條項(xiàng)目流程跑通房?jī)r(jià)回歸模型的從0到12.1 先看數(shù)據(jù)再定目標(biāo)這次拿到的原始數(shù)據(jù)長(zhǎng)什么樣我這里用自己之前在某個(gè)模擬項(xiàng)目里處理過(guò)的一份房?jī)r(jià)數(shù)據(jù)來(lái)演示字段風(fēng)格和很多公開(kāi)房產(chǎn)數(shù)據(jù)集很接近面積、房間數(shù)、房齡、層數(shù)、地段評(píng)分還有一些缺失率不等的列。目標(biāo)變量是房?jī)r(jià)。拿到數(shù)據(jù)做的第一件事不是建模而是用pandas快速看一眼結(jié)構(gòu)import pandas as pd df pd.read_csv(house_price_demo.csv) print(df.shape) print(df.head()) print(df.info()) print(df.describe())info()能一次看清每一列的類型和缺失數(shù)量describe()則給數(shù)值列的分布概覽。這一步能避免后面許多問(wèn)題。比如我們發(fā)現(xiàn)房齡存在缺失、地段評(píng)分是數(shù)值但范圍明顯不同這些都會(huì)直接影響后續(xù)的預(yù)處理策略。我在真實(shí)項(xiàng)目里見(jiàn)過(guò)太多人跳過(guò)EDA直接fit()結(jié)果模型報(bào)錯(cuò)才回頭查數(shù)據(jù)。Debug半天發(fā)現(xiàn)是列里有字符串混進(jìn)了數(shù)值列。所以哪怕是時(shí)間緊張至少也要把這幾個(gè)函數(shù)跑一遍。2.2 第一次訓(xùn)練用最簡(jiǎn)單的模型拿到baseline數(shù)據(jù)梳理完之后先不急著搞復(fù)雜的特征工程我要做的第一件事是切分訓(xùn)練集和測(cè)試集然后訓(xùn)練一個(gè)最普通的線性回歸模型跑出baseline。from sklearn.model_selection import train_test_split from sklearn.linear_model import LinearRegression from sklearn.metrics import mean_squared_error feature_cols [面積, 房間數(shù), 房齡, 層數(shù), 地段評(píng)分] X df[feature_cols] y df[房?jī)r(jià)] X_train, X_test, y_train, y_test train_test_split( X, y, test_size0.2, random_state42 ) model LinearRegression() model.fit(X_train, y_train) y_pred model.predict(X_test) rmse mean_squared_error(y_test, y_pred, squaredFalse) print(Baseline RMSE:, rmse)test_size0.2表示留出20%的樣本做最終評(píng)估random_state42是隨機(jī)數(shù)種子固定之后每次切分結(jié)果一樣方便復(fù)現(xiàn)對(duì)比。最基礎(chǔ)的線性回歸沒(méi)有超參數(shù)要調(diào)跑出來(lái)一個(gè)RMSE后面所有的工作都以這個(gè)數(shù)字為參照如果你花了半天做特征工程結(jié)果RMSE比baseline還高那你就要反思方向了。順便提醒一句mean_squared_error里面squaredFalse返回的是RMSE也就是均方根誤差單位與目標(biāo)變量一致更好解釋。默認(rèn)的squaredTrue返回MSE早期版本這個(gè)參數(shù)名不太一樣升級(jí)時(shí)容易踩兼容性的坑。2.3 為什么非要分訓(xùn)練集和測(cè)試集這個(gè)問(wèn)題看起來(lái)基礎(chǔ)但真的是理解機(jī)器學(xué)習(xí)的關(guān)鍵。模型要的不是在舊數(shù)據(jù)上背答案而是對(duì)新數(shù)據(jù)給出準(zhǔn)確預(yù)測(cè)。如果把全部數(shù)據(jù)都用來(lái)訓(xùn)練模型表現(xiàn)得再好也不能說(shuō)明任何問(wèn)題——考試題和練習(xí)題是同一套分?jǐn)?shù)高只能說(shuō)明記性好。訓(xùn)練集就是練習(xí)題測(cè)試集就是模擬考。模型在訓(xùn)練集上學(xué)規(guī)律在測(cè)試集上驗(yàn)證它是不是真的學(xué)會(huì)了規(guī)律而非死記硬背。這也是為什么切分集的時(shí)候要特別注意隨機(jī)性如果測(cè)試集恰好都來(lái)自同一片區(qū)域、同一個(gè)時(shí)間段評(píng)價(jià)結(jié)果就失真了。3. 數(shù)據(jù)預(yù)處理的三個(gè)隱藏坑標(biāo)準(zhǔn)化、缺失值與數(shù)據(jù)泄漏3.1 標(biāo)準(zhǔn)化為什么樹(shù)模型不縮放也能跑線性模型不縮放就崩特征之間的量綱差距很大。面積可能是幾十到幾百房齡可能是0到50放在同一數(shù)量級(jí)還好說(shuō)但如果有個(gè)特征是建造年份值是1990、2005這種直接喂給線性回歸梯度下降和正則化項(xiàng)就會(huì)被大數(shù)值特征牽著走。StandardScaler的作用是把每個(gè)特征變成均值接近0、標(biāo)準(zhǔn)差接近1的分布。用法上要注意fit和transform分兩步走from sklearn.preprocessing import StandardScaler scaler StandardScaler() X_train_scaled scaler.fit_transform(X_train) X_test_scaled scaler.transform(X_test)fit_transform等于先學(xué)均值和標(biāo)準(zhǔn)差再用它們轉(zhuǎn)換數(shù)據(jù)。測(cè)試集這里只能用transform絕對(duì)不能重新fit_transform。為什么因?yàn)闇y(cè)試集在真實(shí)場(chǎng)景里代表未來(lái)未知的數(shù)據(jù)你考察的是這個(gè)模型未來(lái)上線后的表現(xiàn)不能用未來(lái)的信息去偷看測(cè)試集的分布。這個(gè)問(wèn)題扯遠(yuǎn)一點(diǎn)就是數(shù)據(jù)泄漏后面專門(mén)展開(kāi)。有人會(huì)問(wèn)為什么隨機(jī)森林這類樹(shù)模型不搞標(biāo)準(zhǔn)化也能出結(jié)果因?yàn)闃?shù)模型做的是特征維度上的切分每次只按一個(gè)特征找閾值特征取值范圍的大小不直接影響分裂邏輯。但如果是線性模型、SVM、KNN這類基于距離或梯度的模型不標(biāo)準(zhǔn)化會(huì)導(dǎo)致模型訓(xùn)練極不穩(wěn)定甚至不收斂。3.2 缺失值處理中位數(shù)不是默認(rèn)選項(xiàng)而是策略選項(xiàng)缺失值處理沒(méi)有萬(wàn)能答案。SimpleImputer提供了幾種策略策略適用場(chǎng)景mean數(shù)據(jù)整體分布對(duì)稱無(wú)極端離群值median有離群值希望填充值更穩(wěn)健most_frequent類別型特征用出現(xiàn)最多的類別填充constant業(yè)務(wù)上明確缺失值有特殊含義比如用0或未知填充我實(shí)際處理中會(huì)先看缺失率。如果某個(gè)特征缺失率超過(guò)40%除非它有很強(qiáng)的業(yè)務(wù)含義否則更傾向于直接刪掉這一列而不是花精力去設(shè)計(jì)填充方案。缺失率低的情況下我會(huì)優(yōu)先用中位數(shù)而不是均值——原因很實(shí)際房?jī)r(jià)、收入這類數(shù)據(jù)經(jīng)常有極端值個(gè)別天價(jià)房會(huì)把均值拉高中位數(shù)更能代表大多數(shù)樣本的水平。要注意的是SimpleImputer也遵循fit和transform兩段式。填充值必須只從訓(xùn)練集中學(xué)習(xí)。有人圖省事對(duì)整個(gè)DataFrame做imputer.fit_transform(df)等于把測(cè)試集的信息提前泄露給了訓(xùn)練過(guò)程。3.3 數(shù)據(jù)泄漏那些看起來(lái)正常其實(shí)違反原則的做法數(shù)據(jù)泄漏是模型評(píng)估虛高的最大元兇。最常見(jiàn)的兩種先對(duì)整個(gè)X做標(biāo)準(zhǔn)化或填充再切分訓(xùn)練測(cè)試集用全量數(shù)據(jù)做特征選擇或統(tǒng)計(jì)匯總再喂給模型。第一種情況標(biāo)準(zhǔn)化把測(cè)試集的均值和標(biāo)準(zhǔn)差混進(jìn)了特征值里模型在訓(xùn)練時(shí)等于看過(guò)測(cè)試集的一部分統(tǒng)計(jì)信息評(píng)估出來(lái)的指標(biāo)會(huì)比真實(shí)水平好看但上線后立刻露餡。第二種更隱蔽。比如用全量數(shù)據(jù)算出某個(gè)特征與目標(biāo)的相關(guān)性篩選排名靠前的特征后再做訓(xùn)練。這個(gè)篩選過(guò)程本身就用到了測(cè)試集的信息同樣屬于泄漏。這里可以記住一個(gè)通用原則訓(xùn)練階段所有對(duì)數(shù)據(jù)的觀察和學(xué)習(xí)都只能在訓(xùn)練集上發(fā)生。測(cè)試集必須被當(dāng)作完全未知的數(shù)據(jù)隔離到最后。4. 模型選型與交叉驗(yàn)證讓證據(jù)替你做決策4.1 不按場(chǎng)景選模型等于閉著眼挑工具scikit-learn里的模型家族很多但根據(jù)任務(wù)類型可以很快收斂?;貧w任務(wù)我通常從LinearRegression、RandomForestRegressor、GradientBoostingRegressor里選分類任務(wù)則是LogisticRegression、RandomForestClassifier、GradientBoostingClassifier。線性模型和樹(shù)模型的本質(zhì)差異值得說(shuō)清楚。線性模型的假設(shè)是特征與目標(biāo)之間存在線性關(guān)系模型復(fù)雜度低、訓(xùn)練極快、結(jié)果可解釋但面對(duì)非線性關(guān)系時(shí)表現(xiàn)有限。樹(shù)模型通過(guò)不斷切分特征空間來(lái)逼近目標(biāo)能自動(dòng)處理特征間的交互和非線性但更容易過(guò)擬合、訓(xùn)練慢、模型解釋也更難。我的經(jīng)驗(yàn)是如果特征數(shù)量特別多但樣本量不大優(yōu)先線性模型因?yàn)闃?shù)模型在這種場(chǎng)景下非常容易過(guò)擬合如果特征數(shù)量和樣本量都比較健康而且存在明顯的非線性關(guān)系再上樹(shù)模型。不要一開(kāi)始就堆一個(gè)大集成模型那會(huì)讓后續(xù)排查變得很痛苦。4.2 交叉驗(yàn)證到底在交叉什么我們前面只切了一次訓(xùn)練集和測(cè)試集但單次切分是有運(yùn)氣成分的。某一次切分中測(cè)試集恰好都比較簡(jiǎn)單分?jǐn)?shù)虛高或者都比較難分?jǐn)?shù)偏低。交叉驗(yàn)證就是為了解決這個(gè)問(wèn)題。以5折交叉驗(yàn)證為例把訓(xùn)練集切成5份每次拿其中4份訓(xùn)練、1份做驗(yàn)證輪流5次最后把5次驗(yàn)證分?jǐn)?shù)取平均。這樣每一個(gè)樣本都當(dāng)過(guò)驗(yàn)證樣本評(píng)估結(jié)果更穩(wěn)定。from sklearn.model_selection import cross_val_score scores cross_val_score( model, X_train_scaled, y_train, cv5, scoringr2 ) print(CV R2:, scores.mean(), /-, scores.std())有個(gè)容易懵的點(diǎn)sklearn的scoring約定是越大越好所以像MSE、RMSE這類越小越好的指標(biāo)在cross_val_score里要用負(fù)數(shù)形式比如scoringneg_mean_squared_error。我第一次用的時(shí)候給scoringr2是很正常的但為了統(tǒng)一接口neg_mean_squared_error返回值是負(fù)的MSE數(shù)值越大說(shuō)明誤差越小。這塊文檔里寫(xiě)得很清楚但確實(shí)容易看混。4.3 模型對(duì)比的前三步baseline、默認(rèn)參數(shù)、交叉驗(yàn)證用交叉驗(yàn)證快速對(duì)比幾個(gè)候選模型比在單次切分下比分?jǐn)?shù)要靠譜得多。我習(xí)慣的做法是先跑baseline線性模型再看樹(shù)模型默認(rèn)參數(shù)下的交叉驗(yàn)證表現(xiàn)。模型CV R2均值CV R2標(biāo)準(zhǔn)差測(cè)試集RMSELinearRegression0.620.0532.4RandomForestRegressor0.790.0421.7GradientBoostingRegressor0.840.0317.9這是一份典型的虛構(gòu)對(duì)比結(jié)果但模式是真實(shí)存在的。看兩個(gè)東西均值和標(biāo)準(zhǔn)差。均值代表模型整體水平標(biāo)準(zhǔn)差代表穩(wěn)定性。單一一次測(cè)試分?jǐn)?shù)可能會(huì)騙你但5次交叉驗(yàn)證的均值和方差能把模型真實(shí)水平暴露得更徹底。在這個(gè)例子里GradientBoosting的均值最高、標(biāo)準(zhǔn)差還小顯然是更好的候選。5. Pipeline管線與自動(dòng)調(diào)參從手工流程到一鍵搜索5.1 為什么不能靠手工記住每一步到了這個(gè)階段流程已經(jīng)變成了填補(bǔ)缺失值、標(biāo)準(zhǔn)化、喂模型、預(yù)測(cè)。如果手工一步步來(lái)做很容易出兩個(gè)問(wèn)題。一是漏步驟。比如你在訓(xùn)練集上做了缺失值填充和標(biāo)準(zhǔn)化但預(yù)測(cè)線上新數(shù)據(jù)時(shí)忘記調(diào)用同一個(gè)imputer和scaler的transform模型接收到的數(shù)據(jù)分布跟訓(xùn)練時(shí)不一致預(yù)測(cè)結(jié)果會(huì)非常離譜。二是參數(shù)搜索不便。調(diào)參時(shí)如果每次都用一段手工腳本把預(yù)處理重新執(zhí)行一遍代碼會(huì)越來(lái)越長(zhǎng)改一處漏一處。Pipeline就是來(lái)解決這個(gè)問(wèn)題的。它把一系列步驟串成一條流水線fit的時(shí)候自動(dòng)按順序執(zhí)行predict的時(shí)候自動(dòng)對(duì)同一條管線做同樣處理from sklearn.pipeline import Pipeline from sklearn.impute import SimpleImputer from sklearn.preprocessing import StandardScaler from sklearn.ensemble import GradientBoostingRegressor pipeline Pipeline([ (imputer, SimpleImputer(strategymedian)), (scaler, StandardScaler()), (model, GradientBoostingRegressor(random_state42)) ]) pipeline.fit(X_train, y_train) y_pred pipeline.predict(X_test)整條流水線對(duì)外就是一個(gè)大的模型對(duì)象。訓(xùn)練時(shí)先中位數(shù)填充再標(biāo)準(zhǔn)化再訓(xùn)練梯度提升模型預(yù)測(cè)時(shí)同樣先填充、再標(biāo)準(zhǔn)化、再預(yù)測(cè)。這個(gè)對(duì)象可以拿去交叉驗(yàn)證、可以拿去GridSearchCV搜索、可以保存到磁盤(pán)非常省心。5.2 把預(yù)處理和模型一起調(diào)參GridSearchCV的正確打開(kāi)方式如果不用PipelineGridSearchCV默認(rèn)只能調(diào)模型那一個(gè)步驟的參數(shù)。一旦把預(yù)處理放進(jìn)Pipeline搜索空間就擴(kuò)到了全流程你可以同時(shí)搜缺失值策略、標(biāo)準(zhǔn)化方式、模型參數(shù)。參數(shù)名用步驟名__參數(shù)名的格式雙下劃線連接from sklearn.model_selection import GridSearchCV param_grid { imputer__strategy: [median, mean], model__n_estimators: [100, 300], model__max_depth: [3, 5], model__learning_rate: [0.05, 0.1], } grid GridSearchCV( pipeline, param_grid, cv5, scoringr2, n_jobs-1 ) grid.fit(X_train, y_train) print(grid.best_params_) print(grid.best_score_)n_jobs-1是讓所有CPU核心一起跑能省大量時(shí)間。best_score_是交叉驗(yàn)證下的最優(yōu)平均分?jǐn)?shù)best_params_是它對(duì)應(yīng)的參數(shù)組合。注意imputer__strategy這個(gè)寫(xiě)法很關(guān)鍵它說(shuō)明你要搜索的是Pipeline第一步里SimpleImputer的strategy參數(shù)。這個(gè)命名規(guī)則不熟悉的人容易拼錯(cuò)一旦某個(gè)參數(shù)名寫(xiě)錯(cuò)GridSearchCV會(huì)直接報(bào)錯(cuò)說(shuō)Invalid parameter那通常就是雙下劃線寫(xiě)成了單下劃線或者步驟名和類名搞混了。5.3 GridSearchCV與RandomizedSearchCV怎么選參數(shù)一旦多起來(lái)GridSearchCV會(huì)非常痛苦。假設(shè)3個(gè)參數(shù)各有4個(gè)候選值組合數(shù)是4^364次訓(xùn)練每次還跑5折交叉驗(yàn)證那就是320次模型擬合幾分鐘到幾十分鐘都有可能。RandomizedSearchCV的思路不是窮舉所有組合而是從參數(shù)空間里隨機(jī)采樣固定數(shù)量的組合。比如n_iter50就隨機(jī)嘗試50種組合同樣跑5折交叉驗(yàn)證。這個(gè)方法在我實(shí)踐中的效果出奇地好對(duì)大多數(shù)模型來(lái)說(shuō)真正有效的超參數(shù)組合往往集中在某個(gè)區(qū)域隨機(jī)搜索能高效地覆蓋到這些區(qū)域。我的建議是參數(shù)數(shù)量超過(guò)2個(gè)優(yōu)先用隨機(jī)搜索先粗找一遍確定大致范圍后用GridSearchCV在最優(yōu)附近做小范圍微調(diào)。這是性價(jià)比最高的做法硬懟全網(wǎng)格搜索容易讓人對(duì)調(diào)參這件事產(chǎn)生心理陰影。6. 上線前的最后檢查評(píng)估指標(biāo)、過(guò)擬合與模型保存6.1 回歸任務(wù)的三個(gè)指標(biāo)怎么配合看回歸任務(wù)最常用的是RMSE、MAE和R2。它們給出的信息角度不太一樣。指標(biāo)含義什么時(shí)候最有用RMSE預(yù)測(cè)誤差的平方均值開(kāi)根號(hào)對(duì)大誤差敏感希望懲罰嚴(yán)重偏離的預(yù)測(cè)MAE平均絕對(duì)誤差反映平均偏差大小希望直觀了解平均差多少錢(qián)R2模型解釋了多少目標(biāo)方差接近1越好衡量模型相對(duì)均值模型的提升光看一個(gè)指標(biāo)容易誤判。比如有一個(gè)離群樣本預(yù)測(cè)得極差RMSE會(huì)被拉高很多但MAE可能看起來(lái)沒(méi)那么糟糕。反過(guò)來(lái)R2接近0.85聽(tīng)起來(lái)不錯(cuò)但如果業(yè)務(wù)場(chǎng)景本身噪聲很大這個(gè)分?jǐn)?shù)可能已經(jīng)是天花板了。所以實(shí)際評(píng)估時(shí)我會(huì)把這三個(gè)一起打印出來(lái)看再結(jié)合業(yè)務(wù)判斷。分類任務(wù)也要額外提醒一句類別不平衡的時(shí)候準(zhǔn)確率是陷阱。比如99%的樣本是A類模型全部預(yù)測(cè)A類也有99%的準(zhǔn)確率看起來(lái)完美其實(shí)什么都沒(méi)學(xué)。這時(shí)要看精確率、召回率、F1尤其是少數(shù)類上這些指標(biāo)的變化。6.2 怎么用訓(xùn)練集和驗(yàn)證集的分?jǐn)?shù)差判斷過(guò)擬合調(diào)參之后我還會(huì)做一步額外的驗(yàn)證打印模型在訓(xùn)練集上的分?jǐn)?shù)和交叉驗(yàn)證分?jǐn)?shù)對(duì)比差距。如果訓(xùn)練集R2是0.97交叉驗(yàn)證R2只有0.72那基本可以斷定過(guò)擬合了——模型把訓(xùn)練集的特征背得太死換一批數(shù)據(jù)就露餡。緩解方向包括減小模型復(fù)雜度比如降低max_depth、調(diào)高正則化參數(shù)、增加更多訓(xùn)練數(shù)據(jù)、減少特征。但也不要追求兩者完全一致。訓(xùn)練分?jǐn)?shù)略高于驗(yàn)證分?jǐn)?shù)是一個(gè)正?,F(xiàn)象有點(diǎn)類似模擬考成績(jī)略高于練習(xí)題的合理差異。關(guān)鍵看差距是否在可容忍范圍內(nèi)而不是死盯數(shù)字相等。6.3 保存模型與真正上線的姿勢(shì)模型要上線第一件事是保存。scikit-learn官方推薦用joblib而不是pickleimport joblib joblib.dump(pipeline, house_price_pipeline.joblib)加載預(yù)測(cè)loaded_pipeline joblib.load(house_price_pipeline.joblib) new_data pd.DataFrame([{面積: 90, 房間數(shù): 2, 房齡: 8, 層數(shù): 3, 地段評(píng)分: 7.5}]) pred loaded_pipeline.predict(new_data)這里有一個(gè)我踩過(guò)的大坑有人只保存了最后的模型沒(méi)有保存預(yù)處理器。到了線上新數(shù)據(jù)直接裸著喂給模型而模型在訓(xùn)練時(shí)接收的是標(biāo)準(zhǔn)化之后的數(shù)據(jù)——兩邊分布對(duì)不上預(yù)測(cè)結(jié)果完全失真。把整條Pipeline保存下來(lái)等于把預(yù)處理邏輯也一起打包了線上加載后直接預(yù)測(cè)不會(huì)再出現(xiàn)漏變換的問(wèn)題。我個(gè)人的習(xí)慣是每次訓(xùn)練完都把Pipeline和評(píng)估指標(biāo)一起存進(jìn)一個(gè)帶日期的文件夾里線上出問(wèn)題能隨時(shí)回滾到歷史版本。這個(gè)習(xí)慣幫我省掉過(guò)不少臨時(shí)救火的麻煩。