測(cè)實(shí)戰(zhàn):數(shù)據(jù)預(yù)處理與模型評(píng)估全流程)
簡(jiǎn)介這份資源面向機(jī)器學(xué)習(xí)入門(mén)者與需要掌握回歸建模的開(kāi)發(fā)者圍繞身高預(yù)測(cè)這一具體場(chǎng)景講解如何用線性回歸建立身高與年齡、體重、性別等因素之間的依賴(lài)關(guān)系幫助讀者理解連續(xù)值預(yù)測(cè)的完整流程。壓縮包共2個(gè)文件包含1個(gè)xlsx數(shù)據(jù)表與1個(gè)py腳本整體約80KB前者用于存放身高、年齡、性別、體重等樣本數(shù)據(jù)后者基于scikit-learn實(shí)現(xiàn)數(shù)據(jù)預(yù)處理、模型訓(xùn)練、評(píng)估與預(yù)測(cè)。內(nèi)容覆蓋數(shù)據(jù)清洗與標(biāo)準(zhǔn)化、分類(lèi)變量編碼、訓(xùn)練測(cè)試集劃分以及MSE、RMSE、R2等評(píng)估指標(biāo)的使用并延伸討論多項(xiàng)式回歸、嶺回歸、套索回歸與集成方法等改進(jìn)思路。已有115人學(xué)習(xí)下載適合希望用最小體量案例快速跑通線性回歸全流程、并對(duì)照代碼理解建模細(xì)節(jié)的讀者參考。1. 線性回歸身高預(yù)測(cè)三行代碼能跑通但數(shù)據(jù)沒(méi)洗對(duì)全白搭很多人第一次接觸機(jī)器學(xué)習(xí)都是從線性回歸開(kāi)始的。原因很簡(jiǎn)單它數(shù)學(xué)直覺(jué)清晰、代碼量少、結(jié)果可解釋。但真正上手做身高預(yù)測(cè)這類(lèi)任務(wù)時(shí)翻車(chē)往往不在模型本身而在數(shù)據(jù)。你拿到一份「身高預(yù)測(cè)參照表-1.xlsx」里面可能有年齡、性別、體重、身高幾列看起來(lái)干凈實(shí)際上缺失值、量綱差異、性別編碼方式都會(huì)直接影響模型能不能收斂、系數(shù)能不能解釋。這個(gè)資源包的核心價(jià)值在于它把「線性回歸算法」從公式拉到了一個(gè)具體可復(fù)現(xiàn)的場(chǎng)景里。LinearRegression_1.py是主腳本身高預(yù)測(cè)參照表-1.xlsx是訓(xùn)練數(shù)據(jù)整個(gè)流程覆蓋了數(shù)據(jù)預(yù)處理、模型訓(xùn)練、評(píng)估和預(yù)測(cè)四個(gè)環(huán)節(jié)。適合兩類(lèi)人一是正在做機(jī)器學(xué)習(xí)線性回歸實(shí)驗(yàn)的學(xué)生需要一份能跑通的參考實(shí)現(xiàn)二是剛轉(zhuǎn)行的工程師想用一個(gè)最小閉環(huán)理解 scikit-learn 的回歸流程。下面我會(huì)按實(shí)際拆包的順序把每一步的參數(shù)、坑和驗(yàn)證方法講清楚。2. 拆開(kāi)資源包數(shù)據(jù)表結(jié)構(gòu)和腳本骨架先看懂2.1 身高預(yù)測(cè)參照表里到底有什么拿到身高預(yù)測(cè)參照表-1.xlsx之后第一件事不是急著read_excel而是先用 pandas 把列名、類(lèi)型、缺失情況摸一遍。常見(jiàn)的身高預(yù)測(cè)數(shù)據(jù)集一般包含以下幾類(lèi)字段字段類(lèi)型典型列名數(shù)據(jù)類(lèi)型處理方式連續(xù)特征年齡、體重float/int標(biāo)準(zhǔn)化或直接使用分類(lèi)特征性別object/int獨(dú)熱編碼或標(biāo)簽編碼目標(biāo)變量身高float作為 y不參與特征縮放無(wú)關(guān)列編號(hào)、姓名object直接丟棄先跑這段代碼做體檢import pandas as pd df pd.read_excel(身高預(yù)測(cè)參照表-1.xlsx) print(df.shape) # 行數(shù)、列數(shù) print(df.dtypes) # 每列數(shù)據(jù)類(lèi)型 print(df.isnull().sum()) # 每列缺失值數(shù)量 print(df.describe()) # 連續(xù)列的均值、標(biāo)準(zhǔn)差、分位數(shù) print(df.head(10)) # 前10行肉眼檢查邏輯說(shuō)明shape告訴你樣本量夠不夠一般線性回歸至少需要幾十條以上才有統(tǒng)計(jì)意義dtypes決定后面要不要做類(lèi)型轉(zhuǎn)換比如性別如果是字符串必須編碼isnull().sum()是血淚經(jīng)驗(yàn)——很多人直接fit然后報(bào)錯(cuò)就是因?yàn)槟沉杏?NaNdescribe()幫你看量綱如果體重是 60 而年齡是 20差距不大但如果有一列是「克」另一列是「米」不標(biāo)準(zhǔn)化會(huì)讓系數(shù)失真。參數(shù)說(shuō)明read_excel默認(rèn)讀第一個(gè) sheet如果參照表有多個(gè) sheet需要加sheet_nameSheet1指定。如果列名有空格或中文后續(xù)引用時(shí)要用df[列名]而不是df.列名。2.2 LinearRegression_1.py 的典型骨架這個(gè)腳本的常見(jiàn)結(jié)構(gòu)是導(dǎo)入庫(kù) → 讀數(shù)據(jù) → 特征工程 → 劃分訓(xùn)練測(cè)試集 → 訓(xùn)練模型 → 評(píng)估 → 預(yù)測(cè)。我一般會(huì)把它拆成函數(shù)方便單獨(dú)調(diào)試每一步。核心依賴(lài)是scikit-learn和pandas安裝命令pip install scikit-learn pandas openpyxlopenpyxl是讀.xlsx的引擎不裝會(huì)報(bào)ImportError。腳本里最關(guān)鍵的幾行通常是from sklearn.linear_model import LinearRegression from sklearn.model_selection import train_test_split from sklearn.metrics import mean_squared_error, r2_score X df[[年齡, 體重, 性別編碼]] # 特征矩陣 y df[身高] # 目標(biāo)向量 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) print(MSE:, mean_squared_error(y_test, y_pred)) print(R2:, r2_score(y_test, y_pred)) print(系數(shù):, model.coef_) print(截距:, model.intercept_)邏輯說(shuō)明train_test_split的test_size0.2表示 20% 做測(cè)試random_state42保證每次劃分一致方便復(fù)現(xiàn)。fit做的就是最小化殘差平方和解析解直接算出系數(shù)。coef_告訴你每個(gè)特征對(duì)身高的影響方向和大小intercept_是基準(zhǔn)值。參數(shù)說(shuō)明LinearRegression默認(rèn)fit_interceptTrue即自動(dòng)計(jì)算截距如果數(shù)據(jù)已經(jīng)中心化可以設(shè)為False。n_jobs參數(shù)在普通線性回歸里沒(méi)用別被誤導(dǎo)。評(píng)估指標(biāo)里 MSE 單位是身高的平方RMSE 開(kāi)根號(hào)后才是「平均差多少厘米」更直觀。3. 從原始表到可訓(xùn)練矩陣預(yù)處理四步不能省3.1 缺失值和異常值處理身高數(shù)據(jù)里最常見(jiàn)的臟數(shù)據(jù)是身高填了 0 或 999、體重缺失、性別寫(xiě)成「男/女/未知」。處理順序應(yīng)該是先處理異常值再處理缺失值最后編碼。# 異常值身高合理范圍設(shè)為 100-250 cm df df[(df[身高] 100) (df[身高] 250)] # 缺失值數(shù)值列用中位數(shù)填充分類(lèi)列用眾數(shù)填充 df[體重] df[體重].fillna(df[體重].median()) df[性別] df[性別].fillna(df[性別].mode()[0])邏輯說(shuō)明用中位數(shù)而不是均值是因?yàn)樯砀唧w重類(lèi)數(shù)據(jù)可能有偏態(tài)均值容易被極端值拉偏。眾數(shù)填充性別適合缺失比例低的情況如果缺失超過(guò) 30%考慮直接刪列或做模型插補(bǔ)。參數(shù)說(shuō)明median()和mode()[0]都是 pandas 內(nèi)置方法mode()返回的是 Series取第一個(gè)值即可。3.2 性別編碼標(biāo)簽編碼還是獨(dú)熱編碼性別只有兩個(gè)取值時(shí)標(biāo)簽編碼男0女1就夠用線性回歸會(huì)把系數(shù)解釋為「性別從 0 變到 1 時(shí)身高的平均變化」。但如果性別有三個(gè)以上取值必須用獨(dú)熱編碼否則模型會(huì)誤以為類(lèi)別之間有大小順序。# 二分類(lèi)標(biāo)簽編碼 df[性別編碼] df[性別].map({男: 0, 女: 1}) # 多分類(lèi)獨(dú)熱編碼 # df pd.get_dummies(df, columns[性別], drop_firstTrue)邏輯說(shuō)明map是顯式映射比LabelEncoder更可控因?yàn)槟阒烂總€(gè)值對(duì)應(yīng)什么。get_dummies的drop_firstTrue會(huì)丟掉一個(gè)類(lèi)別作為基準(zhǔn)避免多重共線性。參數(shù)說(shuō)明如果參照表里性別已經(jīng)是 0/1跳過(guò)這步。注意map遇到未定義的鍵會(huì)返回 NaN所以映射前先unique()看一下實(shí)際取值。3.3 特征縮放標(biāo)準(zhǔn)化到底要不要做線性回歸的系數(shù)解對(duì)特征量綱敏感。如果年齡范圍 10-80體重范圍 30-120兩者差距不大不標(biāo)準(zhǔn)化也能跑。但如果加入「年收入」這種量級(jí)到萬(wàn)的列不標(biāo)準(zhǔn)化會(huì)導(dǎo)致系數(shù)數(shù)值極小解釋困難。from sklearn.preprocessing import StandardScaler scaler StandardScaler() X_train_scaled scaler.fit_transform(X_train) X_test_scaled scaler.transform(X_test)邏輯說(shuō)明fit_transform只在訓(xùn)練集上做測(cè)試集用transform這是防止數(shù)據(jù)泄露的鐵律。標(biāo)準(zhǔn)化后系數(shù)表示「特征每變化一個(gè)標(biāo)準(zhǔn)差身高變化多少」。參數(shù)說(shuō)明StandardScaler做的是 (x - 均值) / 標(biāo)準(zhǔn)差。如果數(shù)據(jù)有極端離群值改用RobustScaler更穩(wěn)。3.4 訓(xùn)練集測(cè)試集劃分的隨機(jī)種子random_state不設(shè)的話(huà)每次運(yùn)行劃分不同評(píng)估指標(biāo)會(huì)波動(dòng)你以為是模型改了其實(shí)是數(shù)據(jù)換了。固定一個(gè)種子比如 42 或 0是實(shí)驗(yàn)可復(fù)現(xiàn)的基本要求。X_train, X_test, y_train, y_test train_test_split( X, y, test_size0.2, random_state42 ) print(訓(xùn)練集:, X_train.shape, 測(cè)試集:, X_test.shape)邏輯說(shuō)明輸出 shape 確認(rèn)劃分比例對(duì)不對(duì)。如果樣本量很小比如不到 100 條test_size0.2可能只剩十幾條測(cè)試數(shù)據(jù)評(píng)估結(jié)果不穩(wěn)定可以考慮交叉驗(yàn)證。4. 訓(xùn)練、評(píng)估與預(yù)測(cè)指標(biāo)怎么看才不被忽悠4.1 擬合模型后先看系數(shù)和截距模型訓(xùn)練完不要只看 R2先把系數(shù)打出來(lái)。系數(shù)正負(fù)代表特征與身高的正負(fù)相關(guān)絕對(duì)值大小代表影響程度標(biāo)準(zhǔn)化后可比。model LinearRegression() model.fit(X_train, y_train) for name, coef in zip(X.columns, model.coef_): print(f{name}: {coef:.4f}) print(f截距: {model.intercept_:.4f})邏輯說(shuō)明如果「年齡」系數(shù)是正的說(shuō)明年齡越大身高越高在樣本范圍內(nèi)如果「體重」系數(shù)是正的說(shuō)明體重越大身高越高。如果出現(xiàn)與常識(shí)相反的符號(hào)先檢查數(shù)據(jù)里有沒(méi)有異常值或編碼錯(cuò)誤。參數(shù)說(shuō)明zip把列名和系數(shù)配對(duì)避免看錯(cuò)順序。:.4f保留四位小數(shù)方便對(duì)比。4.2 MSE、RMSE、R2 三個(gè)指標(biāo)各看什么from sklearn.metrics import mean_squared_error, r2_score import numpy as np y_pred model.predict(X_test) mse mean_squared_error(y_test, y_pred) rmse np.sqrt(mse) r2 r2_score(y_test, y_pred) print(fMSE: {mse:.2f}) print(fRMSE: {rmse:.2f} cm) print(fR2: {r2:.4f})邏輯說(shuō)明MSE 是均方誤差單位是 cm2不直觀RMSE 開(kāi)根號(hào)后單位是 cm可以直接說(shuō)「預(yù)測(cè)平均偏差多少厘米」R2 表示模型解釋了目標(biāo)變量多少比例的方差越接近 1 越好但為負(fù)說(shuō)明模型還不如直接用均值預(yù)測(cè)。參數(shù)說(shuō)明np.sqrt是 numpy 的開(kāi)根號(hào)函數(shù)。R2 在測(cè)試集上比訓(xùn)練集低很多說(shuō)明過(guò)擬合但線性回歸本身過(guò)擬合風(fēng)險(xiǎn)低更可能是數(shù)據(jù)分布不一致。4.3 用新樣本做預(yù)測(cè)的輸入格式預(yù)測(cè)時(shí)輸入的特征順序必須和訓(xùn)練時(shí)一致列名也要一致。常見(jiàn)錯(cuò)誤是手動(dòng)構(gòu)造 DataFrame 時(shí)列順序變了導(dǎo)致預(yù)測(cè)結(jié)果離譜。new_data pd.DataFrame({ 年齡: [25], 體重: [70], 性別編碼: [0] }) pred model.predict(new_data) print(f預(yù)測(cè)身高: {pred[0]:.1f} cm)邏輯說(shuō)明new_data的列順序和X.columns一致scikit-learn 內(nèi)部按位置對(duì)應(yīng)。如果訓(xùn)練時(shí)用了標(biāo)準(zhǔn)化預(yù)測(cè)前也要用同一個(gè)scaler.transform(new_data)。參數(shù)說(shuō)明單條預(yù)測(cè)傳入的是二維結(jié)構(gòu)[25]而不是25。pred[0]取出標(biāo)量結(jié)果。5. 避坑與排查這五類(lèi)報(bào)錯(cuò)我?guī)缀趺看味加龅?.1 報(bào)錯(cuò) Input contains NaN現(xiàn)象fit時(shí)拋出ValueError: Input contains NaN, infinity or a value too large。原因原始表里有空單元格或者map編碼時(shí)遇到未定義類(lèi)別產(chǎn)生了 NaN。解決在fit之前加print(df.isnull().sum())確認(rèn)哪列有缺失用fillna或dropna處理。編碼列用df[性別].unique()檢查實(shí)際取值。5.2 R2 為負(fù)數(shù)現(xiàn)象測(cè)試集 R2 小于 0模型看起來(lái)「還不如瞎猜」。原因測(cè)試集樣本太少、特征與身高無(wú)線性關(guān)系、或者訓(xùn)練集和測(cè)試集分布差異大。解決先看樣本量如果測(cè)試集不到 20 條改用交叉驗(yàn)證cross_val_score。再看散點(diǎn)圖確認(rèn)特征和身高是否有大致線性趨勢(shì)。如果確實(shí)非線性考慮多項(xiàng)式回歸。5.3 系數(shù)符號(hào)與常識(shí)相反現(xiàn)象年齡的系數(shù)是負(fù)的意味著年齡越大身高越矮。原因數(shù)據(jù)里混入了兒童和成年人兒童年齡小但身高在增長(zhǎng)期成年人年齡大但身高已固定整體不是單調(diào)關(guān)系。解決分年齡段建?;蛘呒尤肽挲g的平方項(xiàng)。也可以先畫(huà)年齡 vs 身高的散點(diǎn)圖肉眼確認(rèn)關(guān)系形態(tài)。5.4 預(yù)測(cè)值出現(xiàn)負(fù)數(shù)或離譜大數(shù)現(xiàn)象新樣本預(yù)測(cè)出身高 -30 cm 或 500 cm。原因新樣本特征超出了訓(xùn)練數(shù)據(jù)的取值范圍線性模型外推能力很差。解決預(yù)測(cè)前檢查輸入是否在訓(xùn)練集的min和max之間。如果必須外推考慮改用樹(shù)模型樹(shù)模型對(duì)外推更保守。5.5 中文列名導(dǎo)致的 KeyError現(xiàn)象df[身高]報(bào)KeyError。原因Excel 列名里有空格比如身高 或身高(cm)。解決讀入后先df.columns df.columns.str.strip()去掉首尾空格再用print(df.columns.tolist())確認(rèn)實(shí)際列名復(fù)制粘貼使用。6. 進(jìn)階技巧用交叉驗(yàn)證和殘差圖判斷模型該不該換線性回歸跑通不難難的是判斷它在這個(gè)數(shù)據(jù)上到底夠不夠用。我一般會(huì)做兩件事交叉驗(yàn)證看穩(wěn)定性殘差圖看模式。交叉驗(yàn)證把數(shù)據(jù)分成 K 份輪流做測(cè)試集能給出 R2 的均值和標(biāo)準(zhǔn)差。如果均值不錯(cuò)但標(biāo)準(zhǔn)差很大說(shuō)明模型對(duì)數(shù)據(jù)劃分敏感樣本量可能不夠。from sklearn.model_selection import cross_val_score scores cross_val_score(model, X, y, cv5, scoringr2) print(fR2 均值: {scores.mean():.4f}) print(fR2 標(biāo)準(zhǔn)差: {scores.std():.4f})邏輯說(shuō)明cv5表示五折交叉驗(yàn)證scoringr2指定評(píng)估指標(biāo)。標(biāo)準(zhǔn)差超過(guò) 0.1 就要警惕說(shuō)明模型不穩(wěn)定。參數(shù)說(shuō)明cross_val_score內(nèi)部會(huì)自動(dòng)劃分不需要提前train_test_split。如果數(shù)據(jù)有順序比如按時(shí)間排列要用TimeSeriesSplit。殘差圖是另一個(gè)黑匣子探測(cè)器。殘差 真實(shí)值 - 預(yù)測(cè)值。如果殘差隨機(jī)分布在 0 附近說(shuō)明線性假設(shè)合理如果殘差呈現(xiàn) U 形或喇叭形說(shuō)明關(guān)系非線性或方差不齊。import matplotlib.pyplot as plt residuals y_test - y_pred plt.scatter(y_pred, residuals) plt.axhline(y0, colorr, linestyle--) plt.xlabel(預(yù)測(cè)身高) plt.ylabel(殘差) plt.show()邏輯說(shuō)明橫軸是預(yù)測(cè)值縱軸是殘差。理想情況是點(diǎn)均勻分布在紅線上下沒(méi)有明顯形狀。如果點(diǎn)呈現(xiàn)曲線趨勢(shì)考慮加多項(xiàng)式項(xiàng)如果點(diǎn)越往右越散考慮對(duì)目標(biāo)變量做對(duì)數(shù)變換。參數(shù)說(shuō)明axhline畫(huà)一條 y0 的參考線。plt.show()在腳本里會(huì)彈窗在 Jupyter 里直接顯示。如果殘差圖顯示明顯非線性可以試一下多項(xiàng)式回歸from sklearn.preprocessing import PolynomialFeatures from sklearn.pipeline import make_pipeline poly_model make_pipeline( PolynomialFeatures(degree2, include_biasFalse), LinearRegression() ) poly_model.fit(X_train, y_train) print(多項(xiàng)式 R2:, poly_model.score(X_test, y_test))邏輯說(shuō)明PolynomialFeatures(degree2)會(huì)自動(dòng)生成特征的平方項(xiàng)和交叉項(xiàng)make_pipeline把預(yù)處理和模型串起來(lái)。如果 R2 明顯提升且殘差圖改善說(shuō)明線性假設(shè)確實(shí)不成立。參數(shù)說(shuō)明degree不要一上來(lái)就設(shè)太高2 或 3 足夠再高容易過(guò)擬合。include_biasFalse是因?yàn)長(zhǎng)inearRegression自己會(huì)算截距。從那以后我每次跑線性回歸都強(qiáng)制走一遍「缺失值檢查 → 散點(diǎn)圖 → 交叉驗(yàn)證 → 殘差圖」這四步不再只看一個(gè) R2 就下結(jié)論。這套流程幫我省了很多后悔藥也希望幫到你。本文還有配套的精品資源點(diǎn)擊獲取