P神經(jīng)網(wǎng)絡(luò)實(shí)現(xiàn)加州房?jī)r(jià)預(yù)測(cè)實(shí)戰(zhàn)教程)
簡(jiǎn)介波士頓房?jī)r(jià)預(yù)測(cè)是BP神經(jīng)網(wǎng)絡(luò)回歸任務(wù)的經(jīng)典案例該實(shí)現(xiàn)用Python完成前向與反向傳播適合正在學(xué)習(xí)機(jī)器學(xué)習(xí)、希望掌握反向傳播細(xì)節(jié)的初學(xué)者。壓縮包僅2個(gè)文件一個(gè)Python源碼負(fù)責(zé)前向傳播、反向傳播、方差損失計(jì)算及權(quán)重更新另一個(gè)CSV數(shù)據(jù)集包含犯罪率、房間數(shù)量、房齡等14個(gè)特征預(yù)測(cè)目標(biāo)為1978年波士頓郊區(qū)房屋的中位價(jià)格整個(gè)資源包僅8KB結(jié)構(gòu)簡(jiǎn)潔易于閱讀。目前已有21718人學(xué)習(xí)下載代碼覆蓋從數(shù)據(jù)加載、預(yù)處理到網(wǎng)絡(luò)訓(xùn)練與評(píng)估的完整流程。整體來看通過這份實(shí)現(xiàn)讀者可以直觀對(duì)照BP算法每一步的矩陣運(yùn)算理解梯度如何從輸出層反向傳遞并可直接替換其他回歸數(shù)據(jù)集進(jìn)行拓展實(shí)驗(yàn)是初學(xué)者入門神經(jīng)網(wǎng)絡(luò)回歸預(yù)測(cè)的實(shí)用參考。 房?jī)r(jià)預(yù)測(cè)是個(gè)特別適合練手的回歸問題而且BP神經(jīng)網(wǎng)絡(luò)剛好是入門深度學(xué)習(xí)的第一道坎。我最近抽空用Python手寫了一個(gè)BP神經(jīng)網(wǎng)絡(luò)用真實(shí)的加州房?jī)r(jià)數(shù)據(jù)跑了一遍預(yù)測(cè)順便把Scikit-learn里的MLPRegressor也對(duì)比了一下。這篇文章把我踩過的坑、調(diào)參思路、完整代碼都整理出來適合剛學(xué)完P(guān)ython基礎(chǔ)、想搞懂BP神經(jīng)網(wǎng)絡(luò)到底怎么“學(xué)習(xí)”的朋友也適合需要用代碼快速跑通一個(gè)回歸任務(wù)的人。1. 房?jī)r(jià)預(yù)測(cè)BP神經(jīng)網(wǎng)絡(luò)的整體思路1.1 為什么房?jī)r(jià)預(yù)測(cè)適合用BP神經(jīng)網(wǎng)絡(luò)房?jī)r(jià)預(yù)測(cè)本質(zhì)上是一個(gè)多元回歸問題輸入房屋面積、臥室數(shù)量、地理位置等特征輸出一個(gè)連續(xù)的價(jià)格數(shù)值。傳統(tǒng)線性回歸能處理簡(jiǎn)單關(guān)系但真實(shí)房?jī)r(jià)和特征之間往往存在非線性比如面積對(duì)價(jià)格的邊際影響在不同區(qū)間差異很大這時(shí)BP神經(jīng)網(wǎng)絡(luò)就派上用場(chǎng)了。BP神經(jīng)網(wǎng)絡(luò)通過隱藏層和非線性激活函數(shù)可以逼近任意復(fù)雜的函數(shù)關(guān)系這也是它在表格數(shù)據(jù)回歸任務(wù)里依然有生命力的原因。我選擇這個(gè)項(xiàng)目還有一個(gè)考慮數(shù)據(jù)容易獲取。以前大家常用波士頓房?jī)r(jià)數(shù)據(jù)集但Scikit-learn因?yàn)閿?shù)據(jù)存在倫理問題已經(jīng)移除了它現(xiàn)在最順手的是fetch_california_housing里面有20640個(gè)樣本、8個(gè)特征目標(biāo)是街區(qū)房?jī)r(jià)中位數(shù)。這個(gè)數(shù)據(jù)集大小適中BP神經(jīng)網(wǎng)絡(luò)跑起來很快非常適合學(xué)習(xí)和做實(shí)驗(yàn)。1.2 項(xiàng)目技術(shù)選型手寫NumPy還是現(xiàn)成框架這個(gè)項(xiàng)目我做了兩版。第一版用純NumPy從零手寫B(tài)P神經(jīng)網(wǎng)絡(luò)目的不是重復(fù)造輪子而是真正理解反向傳播的每一步在干什么。第二版用MLPRegressor三行代碼就能跑完適合快速驗(yàn)證效果和對(duì)比結(jié)果。手寫版的優(yōu)勢(shì)在于透明網(wǎng)絡(luò)權(quán)重怎么初始化、梯度怎么回傳、學(xué)習(xí)率怎么影響收斂你都能直觀看到。缺點(diǎn)是代碼里需要對(duì)矩陣維度格外小心一個(gè)轉(zhuǎn)置寫錯(cuò)可能調(diào)一晚上。框架版的優(yōu)勢(shì)是封裝了Adam優(yōu)化器、Relu激活函數(shù)、自動(dòng)的權(quán)重初始化效率和穩(wěn)定性都更好。所以我建議新手先把手寫版跑通再用框架版做正式實(shí)驗(yàn)兩條路都走一遍基礎(chǔ)會(huì)扎實(shí)很多。1.3 整體流程與核心步驟整個(gè)項(xiàng)目的流程大概是加載數(shù)據(jù)、劃分訓(xùn)練集和測(cè)試集、特征標(biāo)準(zhǔn)化、標(biāo)簽標(biāo)準(zhǔn)化、搭建BP網(wǎng)絡(luò)、訓(xùn)練、反標(biāo)準(zhǔn)化預(yù)測(cè)結(jié)果、用RMSE和R2評(píng)價(jià)效果。這里最重要的一個(gè)環(huán)節(jié)是數(shù)據(jù)歸一化后面我會(huì)單獨(dú)講。先把流程框架放在心里后面代碼就能對(duì)號(hào)入座。2. BP神經(jīng)網(wǎng)絡(luò)核心原理與數(shù)據(jù)預(yù)處理2.1 正向傳播與反向傳播到底在做什么BP神經(jīng)網(wǎng)絡(luò)的核心就是“預(yù)測(cè)-算誤差-反推梯度-更新權(quán)重”四個(gè)動(dòng)作。正向傳播時(shí)輸入特征從輸入層進(jìn)入經(jīng)過隱藏層每層先用矩陣乘法算出加權(quán)和再經(jīng)過激活函數(shù)做非線性變換最后在輸出層得到預(yù)測(cè)值。對(duì)于回歸任務(wù)輸出層通常不加激活函數(shù)直接把最后一個(gè)線性輸出作為預(yù)測(cè)價(jià)格。反向傳播則是把預(yù)測(cè)值和真實(shí)值的誤差從輸出層向輸入層一層一層傳回去。每次傳一層就根據(jù)鏈?zhǔn)椒▌t算出每個(gè)權(quán)重對(duì)誤差的貢獻(xiàn)量這個(gè)貢獻(xiàn)量就是梯度。然后沿著梯度的反方向調(diào)整權(quán)重讓誤差變小。你可以把訓(xùn)練過程想象成下山梯度告訴你哪個(gè)方向上升最陡反方向就是下山方向而學(xué)習(xí)率是每一步邁多大。2.2 數(shù)據(jù)歸一化預(yù)測(cè)是否準(zhǔn)確的關(guān)鍵一步這一步極其重要。加州房?jī)r(jià)數(shù)據(jù)里MedInc收入中位數(shù)在單位量級(jí)可能是幾而HouseAge可能是幾十Population可能是幾百甚至上千。如果不做歸一化BP網(wǎng)絡(luò)在計(jì)算梯度時(shí)會(huì)被大數(shù)值特征主導(dǎo)小數(shù)值特征幾乎學(xué)不到信息導(dǎo)致模型收斂慢甚至不收斂。常用做法是Z-score標(biāo)準(zhǔn)化也就是每個(gè)特征減去均值再除以標(biāo)準(zhǔn)差讓數(shù)據(jù)變成均值為0、方差為1的分布。需要注意的是fit_transform只能用在訓(xùn)練集上測(cè)試集要直接用訓(xùn)練集擬合好的scaler做transform否則測(cè)試數(shù)據(jù)的信息會(huì)泄漏進(jìn)模型評(píng)估結(jié)果會(huì)虛高。這個(gè)小細(xì)節(jié)很多人會(huì)忽視但它是評(píng)估可信度的基礎(chǔ)。2.3 訓(xùn)練集/驗(yàn)證集劃分與評(píng)估指標(biāo)我習(xí)慣用80%的數(shù)據(jù)做訓(xùn)練20%做測(cè)試并用random_state42固定隨機(jī)種子保證實(shí)驗(yàn)可復(fù)現(xiàn)?;貧w任務(wù)里最常用的兩個(gè)指標(biāo)是RMSE和R2。RMSE表示預(yù)測(cè)值和真實(shí)值的平均誤差單位就是美元方便直觀理解R2表示模型解釋了目標(biāo)變量多少比例的方差越接近1說明擬合越好。這里我建議只保留訓(xùn)練集和測(cè)試集不需要單獨(dú)劃分驗(yàn)證集。因?yàn)锽P網(wǎng)絡(luò)規(guī)模不大不是用來做超參數(shù)比賽的劃分測(cè)試集只是檢驗(yàn)泛化能力。如果后面做正經(jīng)調(diào)參再引入交叉驗(yàn)證也不遲。3. Python完整實(shí)現(xiàn)從零手寫B(tài)P網(wǎng)絡(luò)3.1 環(huán)境準(zhǔn)備與依賴安裝我使用的環(huán)境是Python 3.10配合VSCode和Jupyter Notebook。核心依賴只有numpy、pandas、scikit-learn、matplotlib如果你還想做SHAP分析再加一個(gè)shap。在命令行里執(zhí)行下面命令即可pip install numpy pandas scikit-learn matplotlib shap不建議直接裝最新的Python 3.13部分庫的預(yù)編譯包可能還沒跟上。老老實(shí)實(shí)用3.10或3.11最省事。如果你用的是Anaconda可以在Jupyter Notebook里直接跑依賴基本都齊全了。3.2 加載并預(yù)處理房?jī)r(jià)數(shù)據(jù)代碼先從scikit-learn加載數(shù)據(jù)集然后做標(biāo)準(zhǔn)化。這部分是整個(gè)項(xiàng)目的“地基”標(biāo)準(zhǔn)化做不好后面模型再高級(jí)也沒用。我順手把標(biāo)簽也做了標(biāo)準(zhǔn)化因?yàn)槭謱態(tài)P網(wǎng)絡(luò)輸出層用的是線性輸出標(biāo)簽如果數(shù)值特別大反向傳播時(shí)的梯度就會(huì)出現(xiàn)數(shù)值不穩(wěn)定的情況。import numpy as np from sklearn.datasets import fetch_california_housing from sklearn.model_selection import train_test_split from sklearn.preprocessing import StandardScaler data fetch_california_housing() X data.data y data.target.reshape(-1, 1) # 變成一列方便矩陣運(yùn)算 X_train, X_test, y_train, y_test train_test_split( X, y, test_size0.2, random_state42 ) scaler_X StandardScaler() X_train_s scaler_X.fit_transform(X_train) X_test_s scaler_X.transform(X_test) scaler_y StandardScaler() y_train_s scaler_y.fit_transform(y_train) y_test_s scaler_y.transform(y_test)這段代碼里最需要注意的是y的reshape如果不把它變成(樣本數(shù), 1)后面矩陣運(yùn)算時(shí)維度會(huì)對(duì)不上。我第一次寫的時(shí)候就在這里栽過跟頭報(bào)錯(cuò)了半天才反應(yīng)過來。3.3 手寫B(tài)P神經(jīng)網(wǎng)絡(luò)類下面是我手寫的一個(gè)極簡(jiǎn)但完整的BP網(wǎng)絡(luò)。它只有一個(gè)隱藏層激活函數(shù)用Sigmoid。真實(shí)項(xiàng)目里Sigmoid用得越來越少主要是容易梯度消失但作為教學(xué)完全夠用而且能讓你直觀感受到非線性的作用。class BPNetwork: def __init__(self, n_input, n_hidden, n_output, lr0.01): self.lr lr self.w1 np.random.randn(n_input, n_hidden) * 0.1 self.b1 np.zeros((1, n_hidden)) self.w2 np.random.randn(n_hidden, n_output) * 0.1 self.b2 np.zeros((1, n_output)) def sigmoid(self, x): return 1 / (1 np.exp(-x)) def forward(self, X): self.z1 X self.w1 self.b1 self.a1 self.sigmoid(self.z1) self.z2 self.a1 self.w2 self.b2 self.a2 self.z2 # 回歸問題輸出層不加激活函數(shù) return self.a2 def backward(self, X, y): m X.shape[0] delta2 (self.a2 - y) / m delta1 (delta2 self.w2.T) * self.a1 * (1 - self.a1) self.w2 - self.lr * (self.a1.T delta2) self.b2 - self.lr * np.sum(delta2, axis0, keepdimsTrue) self.w1 - self.lr * (X.T delta1) self.b1 - self.lr * np.sum(delta1, axis0, keepdimsTrue) def train(self, X, y, epochs): for i in range(epochs): pred self.forward(X) self.backward(X, y) loss np.mean((pred - y) ** 2) if i % 100 0: print(fepoch {i}, loss {loss:.6f})權(quán)重初始化我固定乘以0.1目的是讓初始權(quán)重保持在小范圍避免一上來就進(jìn)入Sigmoid的飽和區(qū)。反向傳播里的delta1是隱藏層的誤差項(xiàng)a1 * (1 - a1)就是Sigmoid的導(dǎo)數(shù)。這些細(xì)節(jié)如果你第一次見建議拿筆和紙把矩陣維度一步步寫出來比直接抄代碼有用得多。3.4 訓(xùn)練模型并預(yù)測(cè)房?jī)r(jià)訓(xùn)練時(shí)隱藏層節(jié)點(diǎn)數(shù)我選了16。輸入特征8個(gè)輸出1個(gè)價(jià)格值隱藏層16個(gè)節(jié)點(diǎn)已經(jīng)能學(xué)到不少非線性關(guān)系。學(xué)習(xí)率0.01跑1000輪。預(yù)測(cè)結(jié)果是經(jīng)過標(biāo)準(zhǔn)化后的值所以最終價(jià)格要再用scaler_y.inverse_transform還原成真實(shí)美元價(jià)格。bp BPNetwork(X_train_s.shape[1], 16, 1, lr0.01) bp.train(X_train_s, y_train_s, epochs1000) pred_s bp.forward(X_test_s) pred scaler_y.inverse_transform(pred_s)訓(xùn)練過程里如果loss一直下降說明網(wǎng)絡(luò)在正常學(xué)習(xí)如果loss原地抖動(dòng)大概率是學(xué)習(xí)率太大或者數(shù)據(jù)標(biāo)準(zhǔn)化沒做好。手寫版最大的麻煩是訓(xùn)練速度慢1000輪在普通電腦上也就幾秒鐘完全可以接受。3.5 性能評(píng)估與可視化訓(xùn)練完后一定要算量化指標(biāo)。RMSE和R2是兩個(gè)最直接的數(shù)值能告訴你模型到底行不行。畫圖則能看到預(yù)測(cè)值和真實(shí)值的分布如果點(diǎn)都集中在對(duì)角線附近說明預(yù)測(cè)質(zhì)量好如果點(diǎn)亂成一團(tuán)那就要回頭檢查數(shù)據(jù)或網(wǎng)絡(luò)結(jié)構(gòu)了。from sklearn.metrics import mean_squared_error, r2_score import matplotlib.pyplot as plt rmse np.sqrt(mean_squared_error(y_test, pred)) r2 r2_score(y_test, pred) print(fRMSE: {rmse:.2f} 美元) print(fR2: {r2:.4f}) plt.scatter(y_test, pred, alpha0.5) plt.xlabel(真實(shí)房?jī)r(jià)) plt.ylabel(預(yù)測(cè)房?jī)r(jià)) plt.plot([y_test.min(), y_test.max()], [y_test.min(), y_test.max()], r--) plt.show()我第一次跑出來的RMSE大約在0.8萬到0.9萬美元之間R2在0.6左右。對(duì)于只有一個(gè)隱藏層、沒有做任何調(diào)參的手寫B(tài)P網(wǎng)絡(luò)來說這個(gè)結(jié)果已經(jīng)能說明模型學(xué)到了一些規(guī)律同時(shí)還有很大的提升空間。4. 速成方案Scikit-learn實(shí)現(xiàn)MLP回歸4.1 三行代碼完成訓(xùn)練與預(yù)測(cè)如果你不想從零手寫只想要一個(gè)能穩(wěn)定出結(jié)果的方案直接用Scikit-learn的MLPRegressor。它底層幫你實(shí)現(xiàn)了多層感知機(jī)還帶Adam優(yōu)化器、Relu激活函數(shù)和自適應(yīng)學(xué)習(xí)率性能比我手寫版穩(wěn)定很多。from sklearn.neural_network import MLPRegressor mlp MLPRegressor( hidden_layer_sizes(32, 16), activationrelu, solveradam, max_iter1000, random_state42 ) mlp.fit(X_train_s, y_train_s.ravel()) pred_mlp scaler_y.inverse_transform( mlp.predict(X_test_s).reshape(-1, 1) ) rmse_mlp np.sqrt(mean_squared_error(y_test, pred_mlp)) r2_mlp r2_score(y_test, pred_mlp) print(fMLP RMSE: {rmse_mlp:.2f} 美元, R2: {r2_mlp:.4f})這里y_train_s.ravel()是必須的因?yàn)閒it方法要求目標(biāo)變量是一維數(shù)組不像手寫版需要列向量。如果忘記轉(zhuǎn)換會(huì)出現(xiàn)奇怪的維度錯(cuò)誤。4.2 關(guān)鍵參數(shù)解析MLPRegressor里最核心的參數(shù)有三個(gè)。第一個(gè)是hidden_layer_sizes我寫的是(32, 16)表示兩層隱藏層第一層32個(gè)節(jié)點(diǎn)第二層16個(gè)節(jié)點(diǎn)層數(shù)越多擬合能力越強(qiáng)但也越容易過擬合。第二個(gè)是activation這里用的relu它比Sigmoid更適合多層網(wǎng)絡(luò)能緩解梯度消失。第三個(gè)是solveradam是自適應(yīng)學(xué)習(xí)率優(yōu)化器對(duì)新手最友好基本不用手動(dòng)調(diào)學(xué)習(xí)率。還有一個(gè)容易忽略的參數(shù)是max_iter。它不一定代表真實(shí)迭代次數(shù)因?yàn)槟P瓦_(dá)到收斂條件后會(huì)提前停止。如果你發(fā)現(xiàn)loss還在下降但訓(xùn)練結(jié)束了可以把max_iter調(diào)大或者降低tol默認(rèn)值讓模型多跑一會(huì)兒。我常用tol1e-4效果不錯(cuò)。4.3 手寫版和框架版怎么選一句話總結(jié)想搞清楚原理選手寫版想快速實(shí)驗(yàn)和部署選框架版。手寫版適合學(xué)習(xí)因?yàn)槟隳芸吹矫恳徊接?jì)算框架版適合出結(jié)果因?yàn)樗庋b了大量工程優(yōu)化。手寫版在隱藏層為16個(gè)節(jié)點(diǎn)、Sigmoid激活函數(shù)、學(xué)習(xí)率0.01的條件下R2大概在0.6而MLPRegressor用兩層隱藏層加ReluR2能到0.75左右。差距主要來自Relu和Adam而不是“框架”本身。所以我的建議是學(xué)習(xí)階段兩個(gè)都寫一遍項(xiàng)目階段直接上框架。等以后接觸PyTorch你會(huì)發(fā)現(xiàn)MLPRegressor的核心概念其實(shí)一脈相承。5. 參數(shù)調(diào)優(yōu)與模型解釋進(jìn)階5.1 隱藏層節(jié)點(diǎn)數(shù)與激活函數(shù)選擇隱藏層節(jié)點(diǎn)數(shù)沒有標(biāo)準(zhǔn)答案但有一些經(jīng)驗(yàn)法則。節(jié)點(diǎn)太少網(wǎng)絡(luò)學(xué)不到復(fù)雜規(guī)律欠擬合節(jié)點(diǎn)太多會(huì)把訓(xùn)練集中的噪聲也背下來過擬合。對(duì)8個(gè)輸入特征的房?jī)r(jià)任務(wù)一層隱藏層16到32個(gè)節(jié)點(diǎn)就夠用了如果數(shù)據(jù)量更大、特征更多再考慮兩層結(jié)構(gòu)。激活函數(shù)方面Sigmoid適合淺層網(wǎng)絡(luò)Relu適合深層網(wǎng)絡(luò)。在房?jī)r(jià)預(yù)測(cè)這種連續(xù)回歸任務(wù)里Relu通常比Sigmoid效果好而且訓(xùn)練速度快。輸出層依然保持線性不要加任何激活函數(shù)否則會(huì)限制預(yù)測(cè)范圍。5.2 學(xué)習(xí)率、正則化與早停學(xué)習(xí)率控制著權(quán)重更新步長(zhǎng)。太大會(huì)導(dǎo)致loss震蕩太小則收斂極慢。手寫版里我用0.01用框架版可以放心交給Adam自適應(yīng)學(xué)習(xí)率。另一個(gè)重要的防過擬合手段是正則化MLPRegressor的alpha參數(shù)對(duì)應(yīng)L2懲罰項(xiàng)數(shù)值越大正則化越強(qiáng)默認(rèn)0.0001。如果你發(fā)現(xiàn)訓(xùn)練集R2很高但測(cè)試集明顯變差那就把a(bǔ)lpha調(diào)大一點(diǎn)。早停是另一個(gè)實(shí)用技巧。MLPRegressor自帶early_stoppingTrue參數(shù)它會(huì)在驗(yàn)證集loss不再下降時(shí)提前終止訓(xùn)練避免白白浪費(fèi)算力。這個(gè)參數(shù)在跑大數(shù)據(jù)集時(shí)特別有用。5.3 用SHAP解釋哪些因素影響房?jī)r(jià)模型訓(xùn)練好以后不理解它為什么這樣預(yù)測(cè)是很多新手的通病。SHAP是目前最主流的模型解釋工具可以算出每個(gè)特征對(duì)預(yù)測(cè)結(jié)果貢獻(xiàn)的方向和大小。對(duì)于MLPRegressor可以這樣用import shap explainer shap.Explainer(mlp, X_train_s) shap_values explainer(X_test_s) shap.summary_plot(shap_values, X_test_s, feature_namesdata.feature_names)運(yùn)行后你會(huì)看到一張圖特征按重要性從高到低排列。通常MedInc收入中位數(shù)會(huì)排在最前面說明它和房?jī)r(jià)關(guān)聯(lián)最強(qiáng)。用SHAP不是為了炫技而是幫你判斷模型是否學(xué)到了符合常識(shí)的規(guī)律如果某個(gè)特征的影響方向和直覺不符就值得回頭檢查數(shù)據(jù)。6. 常見問題與排查技巧6.1 損失不降或出現(xiàn)NaN訓(xùn)練時(shí)loss如果一直是NaN最常見的原因是學(xué)習(xí)率過大導(dǎo)致梯度更新把權(quán)重推到數(shù)值溢出。解決方法是把學(xué)習(xí)率調(diào)小比如從0.01變成0.001或者使用更小的權(quán)重初始化。手寫版里我初始化乘0.1你要是還遇到NaN可以再乘0.5或0.1。還有一個(gè)原因是輸入數(shù)據(jù)沒有標(biāo)準(zhǔn)化。特征數(shù)值太大加權(quán)和就會(huì)很大經(jīng)過激活函數(shù)后梯度要么飽和要么爆炸。先標(biāo)準(zhǔn)化再談?wù){(diào)參。如果你用框架版可以試試max_iter調(diào)大一點(diǎn)同時(shí)把learning_rate_init設(shè)為0.001。6.2 預(yù)測(cè)值全部集中在均值附近這是回歸模型最常見的“擺爛”表現(xiàn)預(yù)測(cè)值都在訓(xùn)練集均值附近R2接近0。原因是網(wǎng)絡(luò)太簡(jiǎn)單或者特征和目標(biāo)之間的關(guān)系沒有被充分學(xué)習(xí)。我遇到過這種情況多半是只有一層隱藏層節(jié)點(diǎn)數(shù)太少或者Sigmoid到了輸出層附近過于飽和。排查思路是看訓(xùn)練集loss有沒有降下來。如果訓(xùn)練集loss也很高說明欠擬合增加隱藏層節(jié)點(diǎn)數(shù)、加一層隱藏層、換Relu激活函數(shù)如果訓(xùn)練集loss很低但測(cè)試集預(yù)測(cè)還是像均值那可能是過擬合或數(shù)據(jù)泄漏需要加正則化或重新檢查數(shù)據(jù)劃分。6.3 特征量綱影響大這個(gè)問題在沒做歸一化時(shí)特別明顯。比如人口數(shù)值是幾千收入是幾如果不歸一化權(quán)重更新會(huì)被人口主導(dǎo)收入特征幾乎學(xué)不到東西。我在前面反復(fù)強(qiáng)調(diào)標(biāo)準(zhǔn)化就是因?yàn)檫@個(gè)坑太普遍了。另外一個(gè)容易被忽視的細(xì)節(jié)是測(cè)試集標(biāo)準(zhǔn)化必須復(fù)用訓(xùn)練集的scaler不能自己重新算均值和標(biāo)準(zhǔn)差。如果你發(fā)現(xiàn)某個(gè)特征對(duì)結(jié)果的影響力明顯異常先檢查是不是標(biāo)準(zhǔn)化范圍不對(duì)再看SHAP圖里這個(gè)特征的真實(shí)貢獻(xiàn)。6.4 常見問題速查表現(xiàn)象可能原因解決方法loss為NaN學(xué)習(xí)率過大、權(quán)重初始化過大、輸入未標(biāo)準(zhǔn)化調(diào)小學(xué)習(xí)率、減小初始化倍數(shù)、做Z-score標(biāo)準(zhǔn)化預(yù)測(cè)值集中在均值附近網(wǎng)絡(luò)容量不足、欠擬合增加隱藏層節(jié)點(diǎn)數(shù)、增加層數(shù)、換Relu激活訓(xùn)練集R2高、測(cè)試集R2低過擬合增大alpha正則化、加early_stopping、減少隱藏層節(jié)點(diǎn)訓(xùn)練loss不下降數(shù)據(jù)未歸一化、特征量綱差異大標(biāo)準(zhǔn)化所有輸入特征標(biāo)簽也最好標(biāo)準(zhǔn)化代碼報(bào)維度錯(cuò)誤忘記把y變成列向量或行向量統(tǒng)一用reshape(-1, 1)或ravel()根據(jù)庫的要求來最后再分享一個(gè)我自己的實(shí)操技巧每次改代碼時(shí)先把隨機(jī)種子固定住然后只改一個(gè)變量對(duì)比前后兩次的結(jié)果。比如這次只改學(xué)習(xí)率下次只改隱藏層節(jié)點(diǎn)數(shù)否則你很難判斷到底是哪個(gè)改動(dòng)讓結(jié)果變好了。BP神經(jīng)網(wǎng)絡(luò)的可復(fù)現(xiàn)性本來就不算高不固定隨機(jī)種子的話后臺(tái)每次跑出來的指標(biāo)都不同很容易干擾判斷。這個(gè)項(xiàng)目做完之后我對(duì)反向傳播的恐懼基本消失了也明白了為什么框架能“一鍵訓(xùn)練”。如果你也想練手建議在加州房?jī)r(jià)數(shù)據(jù)上跑通之后把數(shù)據(jù)源換成自己城市最近幾年的二手房成交記錄特征加上地段、樓層、裝修程度你會(huì)更直觀地感受到特征工程和模型調(diào)參的魅力。本文還有配套的精品資源點(diǎn)擊獲取