alpha)
先交代一個問題很多人用 Lasso 的時候習(xí)慣先拍一個正則化參數(shù) alpha比如 0.01、0.1然后看效果效果不好再換一個。這么調(diào)十幾次運氣好能碰到一個還能用的模型運氣不好就是白忙一晚上。帶交叉驗證的 LassoCV 就是來解決這件事的——它把 alpha 的選擇變成一個自動化過程用交叉驗證去評估每個候選 alpha 的泛化能力最后直接告訴你哪個值最穩(wěn)。這篇東西就把 LassoCV 的原理、關(guān)鍵參數(shù)、完整流程和常見坑一次講透適合已經(jīng)跑過 sklearn 回歸但還沒系統(tǒng)用過 CV 版的人。1. Lasso 回歸到底在解決什么問題1.1 線性回歸的過擬合與特征選擇困境普通線性回歸的目標(biāo)很直白找一組系數(shù)讓預(yù)測值和真實值之間的平方誤差最小。但問題在于當(dāng)特征數(shù)量很多、樣本量又不夠大的時候模型會拼命記住訓(xùn)練集里的細(xì)節(jié)包括噪聲。結(jié)果就是訓(xùn)練集上 R2 很好看一到新數(shù)據(jù)上就開始崩。這就是過擬合。早期團(tuán)隊里搞過一個特征數(shù)幾百、樣本數(shù)幾十的小項目直接跑線性回歸系數(shù)亂七八糟有的特征系數(shù)大到離譜有的又互相抵消。誰都不敢拿這種模型上線。更麻煩的是線性回歸不會幫你做特征篩選它只會把所有特征都塞進(jìn)模型里不管這特征是不是真的有解釋力。所以實際場景里我希望模型自己把那些無關(guān)緊要的特征權(quán)重壓下去甚至直接變成 0。1.2 L1 正則化是怎么讓系數(shù)歸零的Lasso 的全稱是 Least Absolute Shrinkage and Selection Operator它在線性回歸的損失函數(shù)里加了一項所有系數(shù)絕對值之和也就是 L1 懲罰項。損失函數(shù)從純粹的均方誤差變成了MSE alpha * sum(|beta|)這里的 alpha 就是正則化參數(shù)控制懲罰力度。alpha 越大模型越不愿意讓系數(shù)保持較大的絕對值導(dǎo)致越來越多的系數(shù)被壓縮到接近 0 甚至精確等于 0。為什么 L1 能產(chǎn)生精確的 0而 L2嶺回歸用的那種平方和懲罰只能把系數(shù)壓縮到很小但不能歸零一個直觀理解是L2 懲罰讓系數(shù)平滑地變小像把一堆沙往中間推L1 懲罰形成的約束區(qū)域有尖銳的棱角最優(yōu)解容易落在棱角上這個棱角對應(yīng)的坐標(biāo)軸方向就是某些系數(shù)正好為 0 的位置。我習(xí)慣用坐標(biāo)軸去記這件事L1 的約束是一個菱形菱形的最優(yōu)接觸點經(jīng)常在頂點頂點正好讓一個或幾個系數(shù)歸零。這讓 Lasso 天然成為線性模型里的特征選擇工具。1.3 alpha 不是拍腦袋拍的它決定稀疏度和誤差的平衡在很多實際項目里alpha 是唯一需要認(rèn)真調(diào)的東西。選得太小Lasso 退化成接近普通線性回歸過擬合問題照舊選得太大模型會過度稀疏把所有特征都削成 0只留下一個截距項預(yù)測能力也就沒了。所以 alpha 本質(zhì)上是模型復(fù)雜度和預(yù)測誤差之間的一個旋鈕。問題是怎么知道該擰到哪兒。手動試的話我一般會先按數(shù)量級試0.001、0.01、0.1、1、10。但這樣做有兩個明顯的毛病第一靠肉眼比較 R2 和系數(shù)個數(shù)判斷標(biāo)準(zhǔn)很模糊第二每個 alpha 都要自己跑一遍交叉驗證代碼冗長不說如果數(shù)據(jù)預(yù)處理沒統(tǒng)一比較出來的結(jié)果根本沒有意義。這就是 LassoCV 出現(xiàn)的理由。它把“候選 alpha 生成 交叉驗證評估”打包成一步用一套標(biāo)準(zhǔn)流程替你把最佳 alpha 找出來。2. 手動調(diào) alpha 為什么不行LassoCV 到底做了什么2.1 手動網(wǎng)格搜索的三個坑手動網(wǎng)格搜索不是不行而是容易在細(xì)節(jié)上翻車。先說第一個坑評估指標(biāo)不統(tǒng)一。我最初調(diào)參的時候喜歡用訓(xùn)練集 R2 判斷后來發(fā)現(xiàn)訓(xùn)練集 R2 隨著 alpha 減小一路走高總覺得模型越調(diào)越好換到驗證集就完全不是一回事。改用驗證集評估后又因為劃分方式不固定每次對比都帶著隨機(jī)誤差很難判斷兩個接近的 alpha 到底誰更好。第二個坑是候選范圍靠猜。alpha 的最優(yōu)值跟數(shù)據(jù)本身的尺度有關(guān)特征量級不同合適的 alpha 可能相差幾個數(shù)量級。我見過有人固定從 0.01 到 1 里選結(jié)果真實最優(yōu)值在 0.001 到 0.01 之間整個搜索區(qū)間都沒覆蓋到。第三個坑是計算資源的浪費。手動跑多個 alpha 的話每次都要重新做一次訓(xùn)練和預(yù)測。如果數(shù)據(jù)量稍大哪怕單個 Lasso 很快幾十個 alpha 疊起來也很耗時。而 LassoCV 里有一套路徑算法能一次性計算出整條系數(shù)路徑上的多組解性能上遠(yuǎn)比手動循環(huán)好。2.2 交叉驗證的基本思路交叉驗證的本質(zhì)是把訓(xùn)練數(shù)據(jù)切成幾份輪流拿其中一份當(dāng)驗證集其余當(dāng)訓(xùn)練集最終用各輪驗證誤差的平均值來衡量某個 alpha 的泛化表現(xiàn)。LassoCV 默認(rèn)用的是 K 折交叉驗證。假如 cv5數(shù)據(jù)會被分成 5 份每次拿 4 份訓(xùn)練、1 份驗證跑 5 輪每一份數(shù)據(jù)都有機(jī)會當(dāng)一次驗證集。最后把 5 輪誤差平均得到一個穩(wěn)定評分。這么做相比單次劃分驗證集的好處很明顯單次劃分結(jié)果受隨機(jī)性影響太大如果劃分恰好讓驗證集分布不均評分就會失真。交叉驗證等于把隨機(jī)性平均掉選擇結(jié)果更可信。2.3 LassoCV 的候選 alpha 路徑機(jī)制LassoCV 內(nèi)部并不是盲目地在某個范圍里均勻取樣而是通過坐標(biāo)下降法沿正則化路徑計算出一組候選 alpha然后對這些候選逐一做交叉驗證。說人話就是它從大到小掃描 alpha對每個 alpha 都計算系數(shù)路徑然后結(jié)合交叉驗證給出誤差曲線。最終模型選的是交叉驗證誤差最小的 alpha如果參數(shù) selection 設(shè)置成 “random”模型還會在誤差均值上下一個標(biāo)準(zhǔn)差的最小值里做選擇這是為了防止過度追求極小值而過擬合。這一點在很多教程里被忽略但其實非常關(guān)鍵。LassoCV 不只是幫你跑了個交叉驗證它連 alpha 候選集都幫你生成好了你要做的只是把 max_iter 和 cv 這種參數(shù)設(shè)置合理。3. 用 LassoCV 選擇最佳正則化參數(shù)完整實操3.1 數(shù)據(jù)準(zhǔn)備與預(yù)處理順序用一個模擬的回歸數(shù)據(jù)集做演示重點看流程。import numpy as np import pandas as pd from sklearn.datasets import make_regression from sklearn.model_selection import train_test_split from sklearn.preprocessing import StandardScaler from sklearn.linear_model import LassoCV from sklearn.metrics import mean_squared_error X, y make_regression( n_samples300, n_features30, n_informative10, noise0.05, random_state42 ) X_train, X_test, y_train, y_test train_test_split( X, y, test_size0.2, random_state42 )預(yù)處理這一步很容易出問題。Lasso 對特征的量綱敏感別把標(biāo)準(zhǔn)化放在劃分?jǐn)?shù)據(jù)集之前。正確做法是先劃分訓(xùn)練集和測試集再在訓(xùn)練集上擬合標(biāo)準(zhǔn)化器然后分別轉(zhuǎn)換訓(xùn)練集和測試集。scaler StandardScaler() X_train_scaled scaler.fit_transform(X_train) X_test_scaled scaler.transform(X_test)順序?qū)Σ簧蠒胄畔⑿孤┤绻仍谡麄€數(shù)據(jù)集上做標(biāo)準(zhǔn)化再劃分那測試集的信息其實已經(jīng)參與了訓(xùn)練過程評估結(jié)果會比真實落地場景樂觀上線后成績縮水是必然的。這個坑我踩過后來每次寫預(yù)處理都要先劃分再標(biāo)準(zhǔn)化。3.2 核心調(diào)用與關(guān)鍵參數(shù)選擇接下來是 LassoCV 的核心調(diào)用。lasso_cv LassoCV( eps0.001, n_alphas100, cv5, max_iter100000, random_state42 ) lasso_cv.fit(X_train_scaled, y_train)這里幾個參數(shù)值得單獨說。eps 控制 alpha 路徑的最小值與最大值的比例默認(rèn)是 0.001意味著路徑會掃描到最大 alpha 的千分之一。n_alphas 是候選 alpha 的個數(shù)默認(rèn) 100數(shù)據(jù)量大時可以調(diào)大一點讓路徑更細(xì)。cv 是交叉驗證折數(shù)小型數(shù)據(jù)集用 5 或 10 都行數(shù)據(jù)量小的時候折數(shù)太多會讓每一折的訓(xùn)練集過小方差反而變大。max_iter 是坐標(biāo)下降的最大迭代次數(shù)如果收到收斂警告就要調(diào)大。random_state 固定隨機(jī)性讓復(fù)現(xiàn)結(jié)果一致。3.3 從訓(xùn)練結(jié)果里提取最佳 alpha 和啞變量擬合完以后最關(guān)心的幾個屬性是best_alpha lasso_cv.alpha_ best_score lasso_cv.score(X_test_scaled, y_test) print(Best alpha:, best_alpha) print(Test R2:, best_score) print(Number of nonzero coefficients:, int(np.sum(lasso_cv.coef_ ! 0)))alpha_ 就是模型最終選出來的最佳正則化參數(shù)。coef_ 是此時各特征的系數(shù)其中等于 0 的特征就是被 Lasso 剔除的特征??梢耘浜?pandas 把系數(shù)和特征名對應(yīng)起來看feature_names [ffeature_{i} for i in range(X.shape[1])] coef_df pd.DataFrame({ feature: feature_names, coef: lasso_cv.coef_ }) coef_df coef_df.sort_values(coef, keylambda col: col.abs(), ascendingFalse) print(coef_df.head(15))很多人在這一步會踩一個坑訓(xùn)練時用的是標(biāo)準(zhǔn)化之后的特征所以 coef_ 反映的標(biāo)準(zhǔn)化的尺度。要解釋回原始量綱就得把系數(shù)除以對應(yīng)特征的標(biāo)準(zhǔn)差或者干脆用標(biāo)準(zhǔn)化前的系數(shù)重新擬合一次。如果只是做預(yù)測和篩選用標(biāo)準(zhǔn)化后的系數(shù)即可。3.4 可視化交叉驗證誤差曲線判斷 LassoCV 是否選得靠譜最直接的方式是看 alpha 路徑圖。import matplotlib.pyplot as plt plt.figure(figsize(8, 5)) plt.plot(lasso_cv.alphas_, lasso_cv.mse_path_.mean(axis-1), labelCV MSE) plt.axvline(lasso_cv.alpha_, linestyle--, colorred, labelBest alpha) plt.xscale(log) plt.xlabel(alpha) plt.ylabel(Mean squared error) plt.legend() plt.show()mse_path_ 是一個二維數(shù)組形狀為 (n_alphas, n_folds)記錄了每個 alpha 在每一折上的均方誤差。平均值最低的位置就是最優(yōu) alpha 附近??催@張圖有兩個好處一是確認(rèn) alpha 的選擇區(qū)間是否覆蓋了最低點如果最低點在路徑邊緣說明 eps 或者 n_alphas 設(shè)置不合適需要擴(kuò)大搜索范圍二是看曲線是否平坦如果最低點左右兩條曲線的差距非常小說明 alpha 在一定范圍內(nèi)都不敏感這時候可以選更大的 alpha 換取更強(qiáng)的稀疏性。下圖不是必須的但線上說明文檔里這張圖通常比一長串?dāng)?shù)字更能說服人。4. 實際跑 LassoCV 時遇到的常見問題與排查4.1 收斂警告坐標(biāo)下降沒有走完用默認(rèn) max_iter 跑大特征集時經(jīng)常收到類似 “ConvergenceWarning: Objective did not converge” 的提示。這個問題的直接原因是迭代次數(shù)不夠坐標(biāo)下降算法還沒找到足夠好的解就被強(qiáng)制停了。遇到這個情況第一反應(yīng)是把 max_iter 調(diào)大比如從 1000 調(diào)到 50000。如果調(diào)大之后警告消失說明只是迭代次數(shù)不足。如果警告還在那要檢查特征是否有極端異常值或者特征標(biāo)準(zhǔn)化的過程中有沒有產(chǎn)生 NaN 或無窮大。我試過一次數(shù)據(jù)里混進(jìn)了一個異常大的值標(biāo)準(zhǔn)化之后標(biāo)準(zhǔn)差被拉高很多特征變成微小數(shù)值坐標(biāo)下降在數(shù)值上就變得非常不穩(wěn)定。先做異常值截斷再標(biāo)準(zhǔn)化整個訓(xùn)練就順暢了。4.2 數(shù)據(jù)標(biāo)準(zhǔn)化、缺失值和啞變量的處理順序先處理缺失值再標(biāo)準(zhǔn)化是基本原則。缺失值如果在標(biāo)準(zhǔn)化之后才補(bǔ)填充值的位置會產(chǎn)生偏差。尤其是用均值填充的時候標(biāo)準(zhǔn)化后的均值是 0填充值會直接落在均值位置倒是沒太大問題但用中位數(shù)或者其他方式填充標(biāo)準(zhǔn)化前后的結(jié)果就會不一樣。更需要注意的是啞變量編碼。分類型特征經(jīng)過 one-hot 編碼后如果再做標(biāo)準(zhǔn)化每一列只有 0 和 1標(biāo)準(zhǔn)化后會出現(xiàn)正負(fù)值這會破壞編碼的稀疏結(jié)構(gòu)。我的做法是數(shù)值型特征標(biāo)準(zhǔn)化啞變量保持 0/1 不參與標(biāo)準(zhǔn)化最后歸一化特征矩陣時用列合并。sklearn 里可以用 ColumnTransformer 來控制不同列使用不同的預(yù)處理流程。4.3 交叉驗證結(jié)果的隨機(jī)性和穩(wěn)定性LassoCV 的交叉驗證結(jié)果不是百分之百穩(wěn)定的原因在于每折數(shù)據(jù)劃分的隨機(jī)性。同樣一份數(shù)據(jù)cv5 但 random_state 不同選出的 alpha 可能略有差別。如果項目對穩(wěn)定性要求高我通常會把 random_state 固定成一個值用于復(fù)現(xiàn)同時把 cv 設(shè)置為 10 或者更多折折數(shù)越多評估均值越穩(wěn)定。另一種做法是重復(fù)多次 LassoCV 并觀察 alpha 的分布如果幾次結(jié)果都在同一個數(shù)量級那這個 alpha 區(qū)間基本可信如果分布極分散數(shù)據(jù)量太小或者特征噪聲太大單純調(diào) alpha 已經(jīng)救不了模型。4.4 稀疏結(jié)果是不是永遠(yuǎn)更好Lasso 把很多系數(shù)變成了 0看起來模型變精簡了但稀疏度高不等于預(yù)測好。有些場景里特征之間的共線性比較強(qiáng)Lasso 可能隨機(jī)選中其中一個相關(guān)特征剔除另一個導(dǎo)致系數(shù)不穩(wěn)定。這個時候可以回退看下嶺回歸的結(jié)果或者用 ElasticNet 這種同時包含 L1 和 L2 懲罰的模型它在稀疏性和穩(wěn)定性之間取了一個折中。我的經(jīng)驗是如果 Lasso 選出的特征每次跑都不太一樣優(yōu)先懷疑數(shù)據(jù)里有強(qiáng)相關(guān)特征組而不是 alpha 設(shè)置不合理。5. 讓 LassoCV 結(jié)果更穩(wěn)的幾個工程細(xì)節(jié)5.1 用信息準(zhǔn)則做交叉驗證的互補(bǔ)驗證交叉驗證不是唯一的選擇標(biāo)準(zhǔn)還可以用 BIC 或 AIC 來輔助判斷。sklearn 里雖然不直接提供 BIC 計算但可以基于擬合后的 Lasso 路徑自己算。思路很簡單對每一個候選 alpha取對應(yīng)系數(shù)計算訓(xùn)練集上的對數(shù)似然或殘差平方和再加上懲罰項。BIC 的懲罰項比 AIC 更重傾向于選擇更稀疏的模型。如果交叉驗證選出來的 alpha 和 BIC 選出來的 alpha 數(shù)量級接近說明這個選擇比較可靠如果差得很遠(yuǎn)就去檢查數(shù)據(jù)劃分和預(yù)處理環(huán)節(jié)。我通常在模型選型階段把這兩種方式的結(jié)果并排看一眼不需要當(dāng)成硬指標(biāo)但能提供額外的信心。5.2 特征量綱與縮放處理的統(tǒng)一之前提到過標(biāo)準(zhǔn)化要在劃分之后做還有一個細(xì)節(jié)是縮放器要保存下來。線上部署時新進(jìn)來的數(shù)據(jù)也要用同一個縮放器轉(zhuǎn)換不能重新 fit。保存縮放器的方式很簡單用 joblib 或者 pickle 把 StandardScaler 和訓(xùn)練好的 LassoCV 一起打包。我第一次部署時忘了保存縮放器只保存了模型結(jié)果線上預(yù)測值全部偏差排查半天才發(fā)現(xiàn)輸入沒做同樣的標(biāo)準(zhǔn)化。這個錯誤一點都不高級但確實常見。5.3 先做一次基準(zhǔn)線性回歸再對比不要一上來就跑 LassoCV。先訓(xùn)練一次普通線性回歸記錄它的測試集 R2 和均方誤差把這個當(dāng)成基準(zhǔn)線。然后跑 LassoCV 和嶺回歸三家對比。這個習(xí)慣的好處是能判斷正則化到底有沒有帶來提升。有些數(shù)據(jù)集特征本來就少共線性也低普通線性回歸已經(jīng)很好Lasso 強(qiáng)行加懲罰反而降低預(yù)測精度。這時選出來的最佳 alpha 可能很小接近于普通回歸但至少你知道自己為什么選它。5.4 嘗試隨機(jī)化的 alpha 選擇與穩(wěn)定性分析除了默認(rèn)的交叉驗證LassoCV 還支持 selectionrandom它會基于某條隨機(jī)化路徑選擇 alpha而不是單純選交叉驗證誤差最小的點。隨機(jī)化路徑的主要作用是在誤差曲線比較平坦的時候避免因為數(shù)值噪聲而選到一個過于復(fù)雜的模型。想更穩(wěn)妥地做穩(wěn)定性分析我建議對特征數(shù)據(jù)做 bootstrap 重采樣每次跑一次 LassoCV記錄被保留的特征集合。重復(fù)幾十次之后統(tǒng)計每個特征被選中的頻率。那些高頻率出現(xiàn)的特征才是真正值得信任的核心變量只會偶爾被選中的特征留著不加進(jìn)來對模型影響也不大。5.5 實際部署時別用訓(xùn)練時的全量數(shù)據(jù)重訓(xùn)模型調(diào)完之后很多人的習(xí)慣是用全量數(shù)據(jù)重新訓(xùn)練一次認(rèn)為這樣能把信息用足。對 LassoCV 來說這個操作要小心。用全量數(shù)據(jù)重訓(xùn)意味著交叉驗證的折內(nèi)評估結(jié)果只適合做 alpha 選擇參考最終模型并沒有跟著更新。如果你用全量數(shù)據(jù)重新 fit 一次選定的 alpha 不變但模型的系數(shù)會基于全量數(shù)據(jù)重新估計這沒問題。問題在于有些人重訓(xùn)之后又把 LassoCV 的交叉驗證評分當(dāng)成新模型的預(yù)期精度這是錯的預(yù)期精度應(yīng)該用之前單獨的測試集來評估。我一般流程是先用訓(xùn)練集跑 LassoCV 選 alpha再用這個 alpha 在訓(xùn)練集上重新訓(xùn)練一個 Lasso最后在測試集上評估并保存模型。這樣既統(tǒng)一了流程也不容易出現(xiàn)評估偏差。6. 一點個人經(jīng)驗項目里選擇正則化參數(shù)這件事看起來只是調(diào)一個數(shù)本質(zhì)上卻是在跟過擬合博弈。用過 LassoCV 之后我最大的感受是它把調(diào)參變成了一個標(biāo)準(zhǔn)動作省下來的時間可以花在更值得研究的問題上比如特征關(guān)系、預(yù)處理邏輯、模型部署的穩(wěn)定性。我踩過最貴的一次坑是換了數(shù)據(jù)分布卻忘了重新交叉驗證直接把舊模型的 alpha 拿過來用結(jié)果預(yù)測偏差大得離譜。后來養(yǎng)成一個習(xí)慣只要數(shù)據(jù)源有變化哪怕只是數(shù)據(jù)量變了也會重新跑一遍 LassoCV而不是迷信之前的參數(shù)。模型永遠(yuǎn)要跟著數(shù)據(jù)走這是用過 LassoCV 之后最真實的體會。