器學(xué)習(xí)入門實(shí)踐:線性回歸模型實(shí)現(xiàn)波士頓房價(jià)預(yù)測全流程)
如果你想自學(xué)機(jī)器學(xué)習(xí)又不知道從哪個(gè)項(xiàng)目下手我勸你別一上來就碰深度學(xué)習(xí)、圖像識別那些花活兒。先把最簡單的線性回歸模型在一份公開數(shù)據(jù)集上完整跑通一遍比看十遍理論都有用。今天要拆的這個(gè)項(xiàng)目——機(jī)器學(xué)習(xí)入門實(shí)踐線性回歸模型波士頓房價(jià)預(yù)測就是很多人公認(rèn)的“機(jī)器學(xué)習(xí)第一站”我前后帶過不少新人做這個(gè)練習(xí)每次都能聊出點(diǎn)新東西今天干脆把它掰開揉碎講一遍。這個(gè)項(xiàng)目要解決的事情很樸素給你一堆房子的歷史數(shù)據(jù)比如房間數(shù)量、犯罪率、到市中心的距離、房齡等等然后讓你預(yù)測這棟房子的房價(jià)。這里面的核心模型是線性回歸它既是統(tǒng)計(jì)學(xué)里的老朋友也是機(jī)器學(xué)習(xí)里最基礎(chǔ)的模型之一。適合誰呢剛看完機(jī)器學(xué)習(xí)課程理論、想動手敲代碼的同學(xué)或者已經(jīng)會用Python但對“訓(xùn)練、評估、調(diào)參”沒有完整感知的初學(xué)者。項(xiàng)目本身門檻低、見效快跑完你會對機(jī)器學(xué)習(xí)流程有一個(gè)特別清晰的骨架。下面我按自己做項(xiàng)目時(shí)的思路從設(shè)計(jì)、原理、實(shí)操、避坑四個(gè)部分展開。1. 項(xiàng)目整體設(shè)計(jì)與思路拆解1.1 為什么機(jī)器學(xué)習(xí)入門第一課都是線性回歸很多人覺得線性回歸太簡單不值得花時(shí)間。恰恰相反正因?yàn)楹唵嗡攀亲詈玫摹敖虒W(xué)載體”。線性回歸的本質(zhì)是“特征加權(quán)求和”你理解了它后續(xù)學(xué)的邏輯回歸、支持向量機(jī)、神經(jīng)網(wǎng)絡(luò)本質(zhì)上都是在“加權(quán)求和”外面套了不同的殼。拿生活里的例子打比方你想估算一套房子的租金腦子里自然會想——面積大一點(diǎn)租金高一點(diǎn)離地鐵近一點(diǎn)租金高一點(diǎn)樓齡老一點(diǎn)租金低一點(diǎn)。這個(gè)過程就是線性回歸在做的給每個(gè)因素分配一個(gè)權(quán)重然后把所有因素的貢獻(xiàn)加起來得到最終預(yù)測值。這個(gè)項(xiàng)目的另一個(gè)價(jià)值在于“完整流程”的體驗(yàn)。從加載數(shù)據(jù)、清洗數(shù)據(jù)、特征分析到劃分訓(xùn)練集測試集、訓(xùn)練模型、評估指標(biāo)、再迭代優(yōu)化這整條流水線在深度學(xué)習(xí)項(xiàng)目里會被各種細(xì)節(jié)掩蓋但在線性回歸項(xiàng)目里特別清晰。波士頓房價(jià)預(yù)測的經(jīng)典之處就在這里數(shù)據(jù)量不大、特征維數(shù)適中、任務(wù)直觀新手不會因?yàn)閿?shù)據(jù)太大跑不動也不會因?yàn)槿蝿?wù)太抽象而迷茫。1.2 波士頓房價(jià)數(shù)據(jù)集全解析13個(gè)特征與1個(gè)目標(biāo)波士頓房價(jià)數(shù)據(jù)集包含506個(gè)樣本每個(gè)樣本是一塊區(qū)域的房屋信息一共14列。其中13列是特征1列是目標(biāo)MEDV即該地區(qū)自住房屋房價(jià)的中位數(shù)單位是千美元。比如MEDV等于24意味著該地區(qū)有一半的房子價(jià)格在24000美元以下——注意這是幾十年前的數(shù)據(jù)價(jià)格水平別拿今天的行情去對標(biāo)。這13個(gè)特征分別是CRIM城鎮(zhèn)人均犯罪率、ZN占地面積超過25000平方英尺的住宅用地比例、INDUS非零售商業(yè)用地比例、CHAS是否鄰近查爾斯河、NOX一氧化氮濃度、RM平均房間數(shù)、AGE建于1940年前的自住單位比例、DIS到五大就業(yè)中心的加權(quán)距離、RAD到環(huán)狀高速公路的便利指數(shù)、TAX每10000美元房產(chǎn)稅、PTRATIO師生比例、B黑人比例相關(guān)的統(tǒng)計(jì)量、LSTAT低收入人群比例。每個(gè)特征都有自己的取值范圍和分布形狀比如RM基本在3到9之間LSTAT從1%到38%不等CHAS則是0/1的二值變量。說句實(shí)在話這個(gè)數(shù)據(jù)集雖然經(jīng)典但它采集于上世紀(jì)70年代部分特征的設(shè)計(jì)放到今天來看確實(shí)存在一些爭議所以近幾年主流機(jī)器學(xué)習(xí)庫已經(jīng)不再把它默認(rèn)打包進(jìn)安裝包里了。我理解那些爭議但從教學(xué)角度說它的數(shù)值形態(tài)、特征間的相關(guān)性、與目標(biāo)變量的關(guān)系都非常適合拿來理解回歸模型。如果你遇到代碼里直接load_boston()報(bào)錯不要慌用本地CSV文件方式同樣能復(fù)現(xiàn)后面實(shí)操部分我會給出具體方案。1.3 項(xiàng)目方案選型手寫梯度下降還是直接調(diào)庫做這個(gè)項(xiàng)目時(shí)很多人問我直接用sklearn一行代碼做線性回歸不就行了為什么要費(fèi)勁去寫梯度下降我的答案是兩個(gè)都要做。手寫梯度下降能讓你理解模型在“學(xué)習(xí)”什么調(diào)庫能讓你體會工程上的高效與規(guī)范。先手寫再調(diào)庫順序不要反。手寫版我一般控制在五六十行以內(nèi)核心就是三件事前向計(jì)算預(yù)測值、計(jì)算損失、沿梯度方向更新參數(shù)。調(diào)庫版本用LinearRegression一行訓(xùn)練、一行預(yù)測幾行代碼就能出指標(biāo)。兩者對比之后你會發(fā)現(xiàn)手寫版跑出來的結(jié)果和庫版本差不多但你對“權(quán)重到底怎么更新”“學(xué)習(xí)率調(diào)大了會發(fā)生什么”有了切膚之感。另外我還建議把交叉驗(yàn)證也加上訓(xùn)練集再從中切出一部分作驗(yàn)證而不是只在固定的一次訓(xùn)練測試集劃分上看結(jié)果這一點(diǎn)對后續(xù)理解模型穩(wěn)定性非常有幫助。2. 線性回歸的原理從公式到損失函數(shù)2.1 線性回歸的數(shù)學(xué)本質(zhì)特征加權(quán)求和線性回歸的數(shù)學(xué)形式最初是大家都會的一元一次方程y ax b一個(gè)特征x時(shí)就是一條直線兩個(gè)特征時(shí)是一個(gè)平面超過三個(gè)特征人就想象不出來了只能靠矩陣。樣本有13個(gè)特征模型就是y_pred w1x1 w2x2 ... w13*x13 b用矩陣寫更干凈y_pred X w b其中X是樣本矩陣w是權(quán)重向量b是偏置。寫代碼時(shí)我習(xí)慣把b并到w里給X加一列全1這樣整條公式就簡化成y_pred X theta代碼更少循環(huán)更清晰。每個(gè)權(quán)重w_i的含義值得說透在其他特征固定不變的情況下該特征每增加一個(gè)單位預(yù)測值就變化w_i個(gè)單位。比如RM房間數(shù)對應(yīng)的權(quán)重如果接近3就表示房間數(shù)每多1間預(yù)測房價(jià)大約多3000美元。這就是線性回歸的可解釋性也是它在金融風(fēng)控、醫(yī)療預(yù)測等領(lǐng)域仍然被大量使用的原因。2.2 損失函數(shù)與優(yōu)化為什么是MSE梯度下降怎么走也許你已經(jīng)知道線性回歸常用的損失函數(shù)是均方誤差MSE它的公式是MSE (1/m) * Σ(y_i - y_pred_i)^2為什么不用絕對誤差MAE直觀上兩者都衡量預(yù)測值和真實(shí)值的差距但MSE對“差得遠(yuǎn)”的樣本懲罰更狠。比如一個(gè)樣本誤差是2另一個(gè)是10平方之后分別是4和100后者在損失里占據(jù)主導(dǎo)地位模型會更用力去修正那個(gè)“離譜”的錯誤。MSE還有個(gè)好處是處處可導(dǎo)求梯度方便。有了損失函數(shù)優(yōu)化就是一個(gè)“下山”的過程。把損失函數(shù)看成一座山我們要找到山腳的最低點(diǎn)。梯度就是最陡的方向沿著反方向邁一步就下降一點(diǎn)。這“一步”的大小就是學(xué)習(xí)率lr。學(xué)習(xí)率太大一步跨過山腳來回震蕩甚至發(fā)散學(xué)習(xí)率太小半天走不到頭。我手寫梯度下降時(shí)經(jīng)常把lr設(shè)為0.05到0.1觀察損失曲線如果前幾百輪穩(wěn)定下降、最后平緩說明這個(gè)學(xué)習(xí)率可用如果損失爆成nan先減小學(xué)習(xí)率再試。這里有個(gè)細(xì)節(jié)標(biāo)準(zhǔn)的線性回歸其實(shí)有閉式解即( X?X )?1X?y一步算出來不用迭代。那為什么還要梯度下降因?yàn)殚]式解涉及矩陣求逆當(dāng)特征數(shù)量到幾千幾萬甚至更大時(shí)求逆的計(jì)算量爆炸梯度下降按批次迭代成本可控是深度學(xué)習(xí)時(shí)代的主流優(yōu)化范式。入門階段把梯度下降跑通等于給后面學(xué)神經(jīng)網(wǎng)絡(luò)打地基。2.3 評估指標(biāo)MAE、RMSE、R2怎么配合著看訓(xùn)練完模型怎么判斷好壞波士頓房價(jià)項(xiàng)目里我一般同時(shí)看四個(gè)指標(biāo)MAE、MSE、RMSE、R2。MAE平均絕對誤差最直觀預(yù)測值和真實(shí)值差多少的絕對值平均一下。比如MAE等于3就意味著平均每個(gè)預(yù)測房價(jià)和真實(shí)房價(jià)差3000美元。MSE和RMSE本質(zhì)一樣都是對較大誤差更敏感的指標(biāo)RMSE開根號后單位跟MAE一致方便比較。R2稍微抽象點(diǎn)它衡量的是“模型比直接猜平均值好了多少”R2等于0.9可理解為模型解釋了90%的波動R2接近0說明模型基本沒學(xué)到東西不如一律預(yù)測平均值。必須提醒的是單一指標(biāo)都有盲區(qū)。R2高不代表模型處處都準(zhǔn)可能局部存在明顯偏差MAE低也可能被大量普通樣本“平均”掉了個(gè)別極端房價(jià)差得離譜。所以在項(xiàng)目里我都是把幾張指標(biāo)放一起輸出再補(bǔ)一張預(yù)測值vs真實(shí)值的散點(diǎn)圖一句“R2是0.7多”永遠(yuǎn)不如一張圖來得直觀。3. 完整實(shí)操流程從數(shù)據(jù)到模型評估3.1 環(huán)境準(zhǔn)備與數(shù)據(jù)還原先說一下環(huán)境。我建議你用Python 3.9以上版本裝上numpy、pandas、matplotlib、scikit-learn這四個(gè)庫就夠了。環(huán)境裝好后最大的坎是數(shù)據(jù)獲取。由于前面提到新版工具庫不內(nèi)置波士頓房價(jià)數(shù)據(jù)最省心的做法是直接用本地CSV文件把它放在項(xiàng)目的data目錄下列名包含CRIM、ZN、INDUS、CHAS、NOX、RM、AGE、DIS、RAD、TAX、PTRATIO、B、LSTAT、MEDV。如果你是從舊教程里看到load_boston()報(bào)錯只要換成pd.read_csv()讀取本地文件即可。我經(jīng)歷過一個(gè)特別常見的現(xiàn)場學(xué)生照著網(wǎng)上教程抄代碼前面運(yùn)行得好好的到加載數(shù)據(jù)一步直接紅字報(bào)錯然后整個(gè)人就卡住了。所以這個(gè)項(xiàng)目第一步反而是培養(yǎng)“遇到問題查文檔、換方案”的能力。數(shù)據(jù)準(zhǔn)備好后用下面這段代碼確認(rèn)一下import pandas as pd df pd.read_csv(data/boston.csv) print(df.shape) print(df.head())輸出會顯示(506, 14)和一個(gè)數(shù)據(jù)表格說明數(shù)據(jù)加載成功。如果沒有輸出先檢查CSV路徑是否正確再檢查文件名是否完全一致。3.2 數(shù)據(jù)探索與可視化拿到數(shù)據(jù)別急著開訓(xùn)先做探索性分析EDA這是很多新手跳過的關(guān)鍵步驟。我用df.info()查看缺失值和數(shù)據(jù)類型再用df.describe()查看每列的均值、標(biāo)準(zhǔn)差、最小最大值大致了解量綱差異。比如TAX是幾百到幾千的量級RM是3到9的量級CHAS是0和1。這種量綱不同為后面的標(biāo)準(zhǔn)化提前做了鋪墊。可視化上第一步是畫目標(biāo)變量MEDV的分布直方圖看看房價(jià)是不是正態(tài)分布。實(shí)際畫出來會發(fā)現(xiàn)它有點(diǎn)“右偏”尾部有一些高房價(jià)樣本。第二步是畫特征和房價(jià)的關(guān)系圖我重點(diǎn)看兩個(gè)RM房間數(shù)散點(diǎn)圖應(yīng)該能看到明顯的正相關(guān)LSTAT低收入人群比例散點(diǎn)圖負(fù)相關(guān)。這兩個(gè)特征對這個(gè)預(yù)測任務(wù)影響很大后面算權(quán)重時(shí)系數(shù)也會比較大。我還會順手畫一張相關(guān)性熱力圖不僅看特征與MEDV的相關(guān)性還要看特征與特征之間的相關(guān)性。你可能發(fā)現(xiàn)RAD和TAX的相關(guān)性極高這意味著兩個(gè)特征攜帶了相似的信息同時(shí)放入模型可能導(dǎo)致系數(shù)不穩(wěn)定。這些觀察在后面做特征篩選時(shí)都用得上。3.3 數(shù)據(jù)預(yù)處理與訓(xùn)練集測試集劃分準(zhǔn)備工作兩大步劃分?jǐn)?shù)據(jù)和標(biāo)準(zhǔn)化。先劃分。用train_test_split把數(shù)據(jù)切成80%訓(xùn)練集、20%測試集并設(shè)置random_state42。42這個(gè)數(shù)字是很多示例代碼里的“慣例種子”目的是讓結(jié)果可復(fù)現(xiàn)。如果你不設(shè)隨機(jī)種子每次運(yùn)行切出來的數(shù)據(jù)都不一樣模型指標(biāo)每次也都不一樣很難判斷改代碼是變好了還是隨機(jī)波動。再標(biāo)準(zhǔn)化。線性回歸本身對量綱不敏感算出來的權(quán)重照樣有解釋性但是一旦你后面做正則化、做梯度下降特征尺度不一致就會出問題。比如TAX數(shù)值到幾百RM數(shù)值只有個(gè)位數(shù)梯度下降時(shí)更新方向會被大尺度特征主導(dǎo)收斂變慢。用StandardScaler把每個(gè)特征變成均值0、標(biāo)準(zhǔn)差1的分布from sklearn.model_selection import train_test_split from sklearn.preprocessing import StandardScaler X df.drop(MEDV, axis1) y df[MEDV] X_train, X_test, y_train, y_test train_test_split( X, y, test_size0.2, random_state42 ) scaler StandardScaler() X_train_scaled scaler.fit_transform(X_train) X_test_scaled scaler.transform(X_test)注意一個(gè)關(guān)鍵動作fit_transform只對訓(xùn)練集調(diào)用測試集只用transform。原理很簡單fit操作會計(jì)算訓(xùn)練集每列的均值和標(biāo)準(zhǔn)差如果測試集也fit_transform相當(dāng)于讓模型“偷看”了測試集的分布信息這在實(shí)際生產(chǎn)中等于作弊會讓評估結(jié)果虛高。這一條算是我?guī)马?xiàng)目時(shí)反復(fù)強(qiáng)調(diào)的“數(shù)據(jù)泄漏”問題你在所有涉及預(yù)處理的機(jī)器學(xué)習(xí)流程里都要記得。3.4 訓(xùn)練模型與結(jié)果評估先手寫一個(gè)梯度下降版代碼如下import numpy as np def train_pure_gd(X, y, lr0.05, epochs500): m, n X.shape theta np.zeros(n) loss_history [] for _ in range(epochs): y_pred X theta error y_pred - y gradient (X.T error) / m theta - lr * gradient loss np.mean((y - y_pred) ** 2) loss_history.append(loss) return theta, loss_history X_bias_train np.c_[np.ones(X_train_scaled.shape[0]), X_train_scaled] X_bias_test np.c_[np.ones(X_test_scaled.shape[0]), X_test_scaled] theta, loss_list train_pure_gd(X_bias_train, y_train.values, lr0.05, epochs500)跑完后看一眼損失列表里最后一個(gè)值再打印前50輪和后50輪的損失變化你會發(fā)現(xiàn)前期下降快、后期趨緩。這是梯度下降收斂的正常形態(tài)。如果損失不減反增基本就是學(xué)習(xí)率太大或者數(shù)據(jù)沒標(biāo)準(zhǔn)化。再上sklearn版本from sklearn.linear_model import LinearRegression from sklearn.metrics import mean_absolute_error, mean_squared_error, r2_score model LinearRegression() model.fit(X_train_scaled, y_train) y_pred model.predict(X_test_scaled) mae mean_absolute_error(y_test, y_pred) mse mean_squared_error(y_test, y_pred) rmse np.sqrt(mse) r2 r2_score(y_test, y_pred) print(fMAE: {mae:.3f}) print(fRMSE: {rmse:.3f}) print(fR2: {r2:.3f})在我隨機(jī)種子42、20%測試集的劃分下運(yùn)行結(jié)果大約是R2在0.65到0.75之間RMSE在4.5到6之間。這個(gè)分?jǐn)?shù)在波士頓數(shù)據(jù)上屬于正常水平不要指望一上來就訓(xùn)出R20.99畢竟影響房價(jià)的因素遠(yuǎn)遠(yuǎn)不止這13個(gè)特征。接下來我還會在訓(xùn)練集上做5折交叉驗(yàn)證from sklearn.model_selection import cross_val_score from sklearn.pipeline import make_pipeline pipe make_pipeline(StandardScaler(), LinearRegression()) scores cross_val_score(pipe, X_train, y_train, cv5, scoringr2) print(scores) print(f平均 R2: {scores.mean():.3f}標(biāo)準(zhǔn)差: {scores.std():.3f})為什么用Pipeline因?yàn)榻徊骝?yàn)證時(shí)每一折都要重新做標(biāo)準(zhǔn)化直接把標(biāo)準(zhǔn)化放在Pipeline里可以避免“每一折偷看全量訓(xùn)練集統(tǒng)計(jì)量”的問題。這個(gè)細(xì)節(jié)我一開始也忽略過后來對比了一次發(fā)現(xiàn)泄漏和非泄漏的交叉驗(yàn)證結(jié)果差了0.03左右看似不多但在嚴(yán)謹(jǐn)評估時(shí)是不能接受的。4. 常見問題與優(yōu)化方向4.1 波士頓房價(jià)項(xiàng)目里必踩的坑我先整理一個(gè)踩坑速查表都是我見過或自己掉進(jìn)去過的問題現(xiàn)象原因解決辦法load_boston()報(bào)錯ImportError或加載失敗新版工具庫移除了該數(shù)據(jù)集改用CSV本地讀取結(jié)果每次都不一樣每次運(yùn)行指標(biāo)浮動大沒有固定隨機(jī)種子train_test_split里設(shè)random_state梯度下降損失變成nan損失列表出現(xiàn)空值學(xué)習(xí)率過大或數(shù)據(jù)未標(biāo)準(zhǔn)化先標(biāo)準(zhǔn)化再調(diào)低學(xué)習(xí)率測試集R2虛高驗(yàn)證效果好到可疑標(biāo)準(zhǔn)化時(shí)對測試集也做了fit測試集只用transform權(quán)重系數(shù)很怪符號與常識相反特征間存在共線性看相關(guān)性矩陣考慮刪特征其中“測試集數(shù)據(jù)泄漏”和“不固定隨機(jī)種子”是新人最隱蔽的兩個(gè)坑。前者只要記住“所有基于訓(xùn)練集的變換測試集都不許重新fit”就能避開后者則是一個(gè)習(xí)慣設(shè)置隨機(jī)種子不僅幫助復(fù)現(xiàn)還能讓你在調(diào)參時(shí)更公平地比較結(jié)果。4.2 從基線模型繼續(xù)優(yōu)化多項(xiàng)式、正則化與特征篩選跑通基線之后項(xiàng)目還可以往下走我一般給新人三條路多項(xiàng)式特征、正則化、特征篩選。多項(xiàng)式特征是給線性模型增加非線性能力比如把RM變成RM2、RM3等加入模型。我用PolynomialFeatures(degree2)試過訓(xùn)練集R2會明顯提高但測試集R2提升有限甚至可能下降這就是過擬合模型把訓(xùn)練數(shù)據(jù)里的噪聲都背下來了。此時(shí)用Ridge正則化能有效懲罰過大系數(shù)from sklearn.linear_model import Ridge ridge Ridge(alpha1.0) ridge.fit(X_train_scaled, y_train)alpha越大懲罰越重模型越保守。我一般會寫一個(gè)循環(huán)遍歷alpha從0.1到10畫一張R2隨alpha變化的折線圖找一個(gè)平衡點(diǎn)。再用Lasso做特征選擇——它能把不重要的特征系數(shù)直接壓成0。跑完后打印lasso.coef_你會發(fā)現(xiàn)某些列系數(shù)變0了這比人工刪特征更客觀。不過Lasso的特性是隨機(jī)選擇相關(guān)性高的特征中的一個(gè)所以結(jié)果不一定唯一理解它的行為比糾結(jié)具體刪了哪列更重要。4.3 數(shù)據(jù)集的局限性與遷移思路最后必須說清楚一件事波士頓房價(jià)數(shù)據(jù)本身已經(jīng)過時(shí)用它訓(xùn)練出來的模型放在今天沒有實(shí)際預(yù)測價(jià)值。它最大的價(jià)值是“練習(xí)品”。就像練字用字帖字帖內(nèi)容不是讓你直接拿去做合同而是讓你練好筆畫結(jié)構(gòu)。理解了這一點(diǎn)你會發(fā)現(xiàn)這套流程完全可以平移到任何回歸任務(wù)上。我在帶新人完成波士頓項(xiàng)目后通常會讓他們把同一套代碼遷移到另一個(gè)回歸數(shù)據(jù)集上比如加利福尼亞房價(jià)數(shù)據(jù)、二手車價(jià)格數(shù)據(jù)或者干脆自己爬一批本地租房數(shù)據(jù)。操作一模一樣加載數(shù)據(jù)、EDA、標(biāo)準(zhǔn)化、訓(xùn)練、評估、畫散點(diǎn)圖唯一要改的是特征名和目標(biāo)列名。做了這個(gè)遷移你才算真正把流程內(nèi)化了而不是只會抄波士頓案例。我個(gè)人在實(shí)際操作中還有一個(gè)很喜歡的小習(xí)慣把所有測試集樣本的“真實(shí)房價(jià)”和“預(yù)測房價(jià)”放進(jìn)同一張表格新增一列“絕對誤差”按誤差從大到小排序然后找出誤差最大的前十名逐行分析是哪些特征導(dǎo)致了預(yù)測失真。這個(gè)動作比單純盯R2有用得多你會看到模型在哪些情況下系統(tǒng)性低估、哪些情況下健壯性差也更能體會數(shù)據(jù)質(zhì)量對模型上限的約束。學(xué)完波士頓房價(jià)預(yù)測再回頭看這個(gè)過程你會覺得機(jī)器學(xué)習(xí)入門這件事其實(shí)沒那么多玄學(xué)就是認(rèn)真把一個(gè)流程吃透。