式擬合正弦曲線:從最小二乘到過(guò)擬合的Python實(shí)戰(zhàn))
簡(jiǎn)介面向機(jī)器學(xué)習(xí)初學(xué)者與課程實(shí)驗(yàn)者的多項(xiàng)式擬合正弦曲線完整實(shí)現(xiàn)基于Python編寫覆蓋最小二乘法、正則化2范數(shù)懲罰項(xiàng)、梯度下降與共軛梯度優(yōu)化并通過(guò)加入噪聲的數(shù)據(jù)展示過(guò)擬合現(xiàn)象及改進(jìn)方法。資源包共4個(gè)文件包含3個(gè)帶詳細(xì)注釋的.py腳本分別負(fù)責(zé)數(shù)據(jù)生成與加噪、梯度下降迭代求解、最小二乘解析求解和1份Word實(shí)驗(yàn)報(bào)告整體壓縮包僅581KB結(jié)構(gòu)精簡(jiǎn)。報(bào)告內(nèi)完整記錄了實(shí)驗(yàn)?zāi)康?、環(huán)境配置Windows10/Python3.7/PyCharm、實(shí)驗(yàn)步驟、結(jié)果分析并針對(duì)不同數(shù)據(jù)量、超參數(shù)與多項(xiàng)式階數(shù)進(jìn)行過(guò)擬合對(duì)比便于讀者對(duì)照代碼理解從損失函數(shù)構(gòu)建到參數(shù)求解的全過(guò)程。已有182人學(xué)習(xí)適合用作機(jī)器學(xué)習(xí)課程設(shè)計(jì)、實(shí)驗(yàn)報(bào)告參考以及想從零實(shí)現(xiàn)優(yōu)化算法、深入理解正則項(xiàng)作用的學(xué)習(xí)者。1. 多項(xiàng)式擬合正弦曲線為什么這個(gè)實(shí)驗(yàn)是“機(jī)器學(xué)習(xí)入門”繞不開的一道坎很多人在入門機(jī)器學(xué)習(xí)時(shí)第一個(gè)困惑是算法原理看了好幾遍卻不知道模型在訓(xùn)練時(shí)到底“學(xué)”了什么。多項(xiàng)式擬合正弦曲線這個(gè)實(shí)驗(yàn)恰好把“學(xué)習(xí)”這個(gè)詞拉下神壇——它用一套不到兩百行的 Python 代碼告訴你所謂訓(xùn)練本質(zhì)上是在一組候選函數(shù)里找一條離數(shù)據(jù)點(diǎn)最近的曲線所謂泛化是這條曲線在沒見過(guò)的位置也能貼近真實(shí)規(guī)律。用正弦曲線作為擬合對(duì)象是刻意為之它光滑、非單調(diào)、形狀鮮明低階多項(xiàng)式擬合出來(lái)的是一條“大致走勢(shì)”高階多項(xiàng)式擬合出來(lái)的是一條“精確穿過(guò)每個(gè)點(diǎn)卻劇烈抖動(dòng)”的曲線——欠擬合和過(guò)擬合這兩個(gè)機(jī)器學(xué)習(xí)里最重要的概念可以在這一張圖上看穿。這套實(shí)驗(yàn)適合正在做課程作業(yè)的學(xué)生也適合想驗(yàn)證自己對(duì)最小二乘、正則化和模型選擇理解程度的從業(yè)者。它需要的只是 numpy、matplotlib 和一點(diǎn)點(diǎn)線性代數(shù)基礎(chǔ)。2. 先看懂要擬合什么多項(xiàng)式、正弦曲線與最小二乘的數(shù)學(xué)關(guān)系2.1 多項(xiàng)式的“次數(shù)”到底意味著什么多項(xiàng)式擬合的目標(biāo)很簡(jiǎn)單給定一組采樣點(diǎn) (xi, yi)找一個(gè)次數(shù)為 d 的多項(xiàng)式讓它在這些點(diǎn)上的取值盡量接近 yi。這個(gè)多項(xiàng)式一般寫成f(x) w0 w1·x w2·x2 ... wd·x?這里的 w 是待求的系數(shù)也就是“模型參數(shù)”。次數(shù) d 決定了這個(gè)函數(shù)家族的表達(dá)能力d1 是一條直線只能表達(dá)單調(diào)趨勢(shì)d2 是一條拋物線能表達(dá)一個(gè)彎d越大函數(shù)能拐的彎越多形狀越復(fù)雜。多項(xiàng)式擬合的核心工作是解出系數(shù) w而對(duì)正弦曲線做擬合時(shí)d 這個(gè)數(shù)字會(huì)直接決定結(jié)果是“太簡(jiǎn)單”還是“太復(fù)雜”。這里要特別提醒一個(gè)初學(xué)者容易混淆的點(diǎn)多項(xiàng)式函數(shù)是關(guān)于變量 x 的非線性函數(shù)但它關(guān)于參數(shù) w 是線性的——也就是說(shuō)把 x 的各次冪看成特征擬合問(wèn)題就變成了一個(gè)標(biāo)準(zhǔn)的線性回歸問(wèn)題。這個(gè)認(rèn)知非常關(guān)鍵因?yàn)楹竺娴淖钚《私夥ㄍ耆⒃凇瓣P(guān)于參數(shù)線性”這個(gè)性質(zhì)上。2.2 最小二乘與正規(guī)方程擬合背后的閉式解“離數(shù)據(jù)點(diǎn)最近”需要一個(gè)定量標(biāo)準(zhǔn)。最常見的定義是均方誤差把所有擬合值與真實(shí)值的差的平方加起來(lái)取平均最小化這個(gè)目標(biāo)函數(shù)。寫成數(shù)學(xué)形式就是J(w) (1/N) · Σ(yi - f(xi))2最小化這個(gè)函數(shù)不需要梯度下降迭代因?yàn)樗膶?dǎo)數(shù)是關(guān)于 w 的線性方程。求導(dǎo)并令其為 0可以得到一組線性方程組寫成矩陣形式就是著名的正規(guī)方程w (X?X)?1X?y這里的 X 是設(shè)計(jì)矩陣第 i 行是 [1, xi, xi2, ..., xi?]y 是觀測(cè)值向量。numpy 里一行代碼就能解出 w。不過(guò)在實(shí)現(xiàn)時(shí)我一般不用 (X?X)?1 直接求逆而是用偽逆函數(shù) pinv它能處理矩陣奇異或近奇異的情況數(shù)值上更穩(wěn)定后面在避坑部分會(huì)展開講。2.3 為什么選正弦曲線實(shí)驗(yàn)設(shè)計(jì)里的“刻意安排”用正弦曲線做擬合對(duì)象不是隨便挑的。sin(x) 的性質(zhì)讓這個(gè)實(shí)驗(yàn)天然具備教學(xué)價(jià)值第一它是非線性的任何低次多項(xiàng)式都無(wú)法完美逼近所以你能看到欠擬合的真實(shí)形態(tài)第二它在一個(gè)周期內(nèi)先升后降再升形狀不是單調(diào)的這迫使高階多項(xiàng)式用大幅度擺動(dòng)去擬合過(guò)擬合現(xiàn)象會(huì)非常明顯第三正弦函數(shù)有一個(gè)真實(shí)的低維結(jié)構(gòu)你知道“正確答案”是什么這樣就能區(qū)分誤差來(lái)自噪聲還是來(lái)自模型本身的偏差。數(shù)據(jù)生成這一步通常是自己做采樣然后人為加高斯噪聲。加噪聲是必要的——如果數(shù)據(jù)完美落在正弦曲線上低階擬合的效果看上去也不錯(cuò)過(guò)擬合的反差就不夠強(qiáng)烈。噪聲幅度一般取 0.05 到 0.1 之間能觀察到清晰的效果太小看不出區(qū)別太大會(huì)把所有擬合曲線都?jí)旱貌幌裨挕?. 用 Python 實(shí)現(xiàn)多項(xiàng)式擬合從零手寫最小二乘到 polyfit 一行出結(jié)果3.1 環(huán)境與數(shù)據(jù)準(zhǔn)備環(huán)境方面只需要 numpy 和 matplotlib這兩個(gè)是機(jī)器學(xué)習(xí)實(shí)驗(yàn)最基礎(chǔ)的依賴。如果你在本機(jī)還沒裝好用 pip install numpy matplotlib 或者在 Anaconda 里直接創(chuàng)建環(huán)境就行至于具體的 Python 安裝教程和環(huán)境配置vscode 里裝好 Python 插件、在設(shè)置里選中解釋器路徑就可以跑起來(lái)不用額外折騰。首先生成實(shí)驗(yàn)數(shù)據(jù)在一段區(qū)間內(nèi)采樣加上高斯噪聲。import numpy as np np.random.seed(42) # 固定隨機(jī)種子保證實(shí)驗(yàn)可復(fù)現(xiàn) n_samples 12 x np.linspace(0, 2 * np.pi, n_samples) # 在 0 到 2π 之間均勻取 12 個(gè)點(diǎn) y_true np.sin(x) # 真實(shí)的正弦曲線 y y_true np.random.normal(0, 0.1, n_samples) # 添加高斯噪聲標(biāo)準(zhǔn)差 0.1這段代碼里np.linspace 負(fù)責(zé)在指定區(qū)間內(nèi)均勻采樣第三個(gè)參數(shù)是樣本數(shù)量。固定隨機(jī)種子 np.random.seed(42) 這一步很重要——做實(shí)驗(yàn)必須可復(fù)現(xiàn)不然每次跑出來(lái)的圖和數(shù)值都不一樣實(shí)驗(yàn)報(bào)告也沒法寫。觀測(cè)值 y 是真實(shí)正弦值加噪聲噪聲用 np.random.normal 生成0 是均值0.1 是標(biāo)準(zhǔn)差。3.2 手寫最小二乘不把一切交給黑匣子雖然 numpy 提供了現(xiàn)成的擬合函數(shù)我仍然建議你至少手寫一遍正規(guī)方程。這個(gè)代碼能讓你真正看到“訓(xùn)練”是在算什么def polyfit_manual(x, y, degree): # 構(gòu)建設(shè)計(jì)矩陣 X每一列是 x 的 0 次冪、1 次冪……degree 次冪 X np.vstack([x**i for i in range(degree 1)]).T # 用偽逆求解正規(guī)方程 w (X^T X)^{-1} X^T y w np.linalg.pinv(X.T X) X.T y return w # 用 4 次多項(xiàng)式擬合帶噪聲數(shù)據(jù) w_manual polyfit_manual(x, y, degree4) print(擬合系數(shù), w_manual)邏輯說(shuō)明np.vstack 把 x 的 0 次方到 4 次方按行堆疊轉(zhuǎn)置后得到一個(gè)形狀為 (12, 5) 的矩陣——12 個(gè)樣本5 個(gè)特征從常數(shù)項(xiàng)到 x?。np.linalg.pinv 是偽逆比直接算逆矩陣更穩(wěn)。 是矩陣乘法運(yùn)算符X.T X 得到的是 5×5 的格拉姆矩陣再與 X.T y 相乘就得到了系數(shù)向量。參數(shù)說(shuō)明degree 是多項(xiàng)式次數(shù)也是這個(gè)函數(shù)里唯一需要關(guān)心的超參數(shù)。degree 太小擬合不夠靈活太大數(shù)值容易出問(wèn)題。12 個(gè)樣本點(diǎn)degree 一般不超過(guò) 8超過(guò)之后矩陣接近奇異偽逆雖然能出結(jié)果但數(shù)值已經(jīng)不可靠了。3.3 用 np.polyfit 快速實(shí)現(xiàn)省力但要懂參數(shù)含義手寫版驗(yàn)證了原理之后實(shí)際實(shí)驗(yàn)里直接用 numpy 封裝好的函數(shù)即可# degree4 的多項(xiàng)式擬合返回系數(shù)從高次到低次排列 w_polyfit np.polyfit(x, y, deg4) # 生成密集采樣點(diǎn)用于繪制光滑的擬合曲線 x_smooth np.linspace(0, 2 * np.pi, 200) y_smooth np.polyval(w_polyfit, x_smooth)邏輯說(shuō)明np.polyfit 返回的系數(shù)順序與前面手寫版相反——最高次項(xiàng)系數(shù)在前常數(shù)項(xiàng)在最后。np.polyval 是多項(xiàng)式求值函數(shù)給定系數(shù)和自變量一次算出所有對(duì)應(yīng)函數(shù)值。用 x_smooth 這個(gè)密集點(diǎn)集畫圖曲線才會(huì)光滑直接用原始 12 個(gè) x 畫出來(lái)的折線會(huì)顯得很生硬。參數(shù)說(shuō)明np.polyfit 的第三個(gè)參數(shù) deg 就是多項(xiàng)式次數(shù)如果只填前兩個(gè)參數(shù)會(huì)默認(rèn)擬合 1 次多項(xiàng)式。它內(nèi)部用的算法是基于 SVD 的最小二乘解數(shù)值穩(wěn)定性比我手寫的那版更好所以日常實(shí)驗(yàn)優(yōu)先用 polyfit 是沒問(wèn)題的。但這個(gè)函數(shù)的輸出是一個(gè)系數(shù)數(shù)組不像 sklearn 里的模型有現(xiàn)成的 predict 方法初學(xué)者容易忘了用它配套的 np.polyval 去求值。3.4 可視化欠擬合與過(guò)擬合在一張圖上顯形數(shù)據(jù)、擬合曲線都準(zhǔn)備好了接下來(lái)的關(guān)鍵步驟是把它們畫在同一張坐標(biāo)系里import matplotlib.pyplot as plt plt.figure(figsize(8, 5)) plt.scatter(x, y, label帶噪聲的觀測(cè)點(diǎn), colorblack, zorder3) plt.plot(x_smooth, np.sin(x_smooth), label真實(shí)正弦曲線, linestyle--, colorgreen) plt.plot(x_smooth, y_smooth, label4 次多項(xiàng)式擬合, colorred) plt.xlabel(x) plt.ylabel(y) plt.legend() plt.title(多項(xiàng)式擬合正弦曲線degree4) plt.savefig(polyfit_deg4.png, dpi150) plt.show()這段代碼用 plt.scatter 畫原始采樣點(diǎn)用兩條曲線分別表示真實(shí)正弦和擬合結(jié)果。black 點(diǎn)和綠色虛線是“基準(zhǔn)答案”紅色實(shí)線是你模型給出的預(yù)測(cè)三者對(duì)比一眼就能看出擬合質(zhì)量。zorder3 的作用是讓散點(diǎn)顯示在最上層不被曲線遮住。dpi150 保證導(dǎo)出圖片在實(shí)驗(yàn)報(bào)告里足夠清晰。到這一步你已經(jīng)跑通了最小實(shí)驗(yàn)閉環(huán)。但只畫一張圖沒有說(shuō)服力因?yàn)槟憧床怀龆囗?xiàng)式次數(shù)的選擇對(duì)結(jié)果的影響。下一章會(huì)把這個(gè)實(shí)驗(yàn)往深度推——對(duì)比不同階數(shù)、量化誤差、引入驗(yàn)證集和正則化。這些才是“實(shí)驗(yàn)報(bào)告”里真正能拿分的內(nèi)容。4. 階數(shù)、噪聲與驗(yàn)證集把擬合做到能解釋、能比較、能選參4.1 一組對(duì)比實(shí)驗(yàn)把 1 到 12 次多項(xiàng)式全部跑一遍擬合質(zhì)量不能靠肉眼判斷要靠數(shù)值和對(duì)比。最常見的做法是在同一個(gè)數(shù)據(jù)集上分別用 1 次到 12 次多項(xiàng)式擬合計(jì)算每個(gè)模型在訓(xùn)練集上的均方誤差然后畫一條“誤差隨階數(shù)變化”的曲線。代碼可以這樣寫train_errors [] degrees range(1, 13) for d in degrees: w np.polyfit(x, y, degd) y_pred np.polyval(w, x) # 在原始訓(xùn)練點(diǎn)上做預(yù)測(cè) mse np.mean((y - y_pred) ** 2) # 均方誤差 train_errors.append(mse) plt.figure(figsize(8, 5)) plt.plot(list(degrees), train_errors, markero, colorblue) plt.xlabel(多項(xiàng)式次數(shù) degree) plt.ylabel(訓(xùn)練集均方誤差 (MSE)) plt.title(訓(xùn)練誤差隨多項(xiàng)式次數(shù)的變化) plt.savefig(train_error_vs_degree.png, dpi150) plt.show()這段代碼的要點(diǎn)是對(duì)每個(gè) degree 都做一次完整的“訓(xùn)練-預(yù)測(cè)-評(píng)估”循環(huán)。mse 是回歸任務(wù)最常用的誤差指標(biāo)對(duì)誤差取平方既避免了正負(fù)相消又放大了大誤差的懲罰。你會(huì)看到訓(xùn)練誤差隨 degree 增大而單調(diào)下降degree 越大多項(xiàng)式越靈活它在訓(xùn)練點(diǎn)上的近似能力越強(qiáng)到 degree11 時(shí)12 個(gè)樣本點(diǎn)被一個(gè) 11 次多項(xiàng)式完全穿過(guò)訓(xùn)練誤差趨近于 0。這就是過(guò)擬合的數(shù)學(xué)表現(xiàn)。4.2 驗(yàn)證集與測(cè)試集誤差的“U 形曲線”才是最關(guān)鍵的圖訓(xùn)練誤差單調(diào)下降不代表模型就好。一個(gè)真正有價(jià)值的實(shí)驗(yàn)必須引入驗(yàn)證集看模型在沒見過(guò)的數(shù)據(jù)上表現(xiàn)如何。常見做法是把數(shù)據(jù)集劃分為訓(xùn)練集和驗(yàn)證集在訓(xùn)練集上擬合在驗(yàn)證集上評(píng)估誤差。更嚴(yán)謹(jǐn)一點(diǎn)可以用 K 折交叉驗(yàn)證但對(duì)這個(gè)實(shí)驗(yàn)來(lái)說(shuō)簡(jiǎn)單隨機(jī)劃分或留出法就夠用。from sklearn.model_selection import train_test_split x_train, x_val, y_train, y_val train_test_split( x, y, test_size0.4, random_state0 ) val_errors [] for d in degrees: w np.polyfit(x_train, y_train, degd) y_val_pred np.polyval(w, x_val) val_mse np.mean((y_val - y_val_pred) ** 2) val_errors.append(val_mse)train_test_split 把 12 個(gè)樣本分成 7 個(gè)訓(xùn)練和 5 個(gè)驗(yàn)證test_size0.4 表示驗(yàn)證集占 40%。固定 random_state0 保證每次運(yùn)行劃分方式一致。此時(shí)如果把訓(xùn)練誤差和驗(yàn)證誤差畫在同一個(gè)圖上你會(huì)看到一條經(jīng)典的“U 形”曲線訓(xùn)練誤差一路下降驗(yàn)證誤差先下降、在某個(gè)階數(shù)附近到達(dá)最低點(diǎn)然后掉頭向上猛增。驗(yàn)證誤差的最低點(diǎn)對(duì)應(yīng)的階數(shù)就是在這個(gè)數(shù)據(jù)規(guī)模和數(shù)據(jù)噪聲下比較合適的選擇。這個(gè)現(xiàn)象是整個(gè)實(shí)驗(yàn)的“靈魂”。它是機(jī)器學(xué)習(xí)入門中被引用最多的一張圖也是面試?yán)锍1蛔穯?wèn)的問(wèn)題——“為什么訓(xùn)練誤差低但模型不好”。親眼見過(guò) U 形曲線比背十遍過(guò)擬合的定義都有用。4.3 噪聲幅度一個(gè)被忽視但影響極大的參數(shù)數(shù)據(jù)生成時(shí)的噪聲標(biāo)準(zhǔn)差決定了一個(gè)擬合問(wèn)題本身的難度。噪聲越大數(shù)據(jù)點(diǎn)離真實(shí)曲線的離散程度越高越容易被高階多項(xiàng)式“強(qiáng)行穿過(guò)”過(guò)擬合的 U 形曲線會(huì)越明顯反之噪聲接近 0 時(shí)即使高階多項(xiàng)式也能在樣本點(diǎn)之間合理插值過(guò)擬合現(xiàn)象會(huì)變得難以察覺。你在做這個(gè)實(shí)驗(yàn)時(shí)可以用以下代碼對(duì)比三檔噪聲for noise in [0.01, 0.1, 0.5]: y_noisy y_true np.random.normal(0, noise, n_samples) errors [] for d in degrees: w np.polyfit(x, y_noisy, degd) y_pred np.polyval(w, x) errors.append(np.mean((y_noisy - y_pred) ** 2)) plt.plot(list(degrees), errors, labelfnoise{noise}) plt.legend() plt.show()注意這里每個(gè)噪聲檔位都要重新生成一組 y而不是在同一組數(shù)據(jù)上改標(biāo)簽。np.random.normal(0, noise, n_samples) 的第二個(gè)參數(shù)是標(biāo)準(zhǔn)差不是方差——如果想用方差表示要取平方。經(jīng)驗(yàn)上noise0.01 的結(jié)果幾乎貼著正弦曲線noise0.5 的驗(yàn)證誤差曲線會(huì)在更小的 degree 處就開始反彈說(shuō)明高噪聲場(chǎng)景下模型更容易過(guò)擬合也更需要偏低的模型復(fù)雜度。4.4 正則化兜底當(dāng)高階數(shù)不可避免時(shí)該怎么辦有些實(shí)驗(yàn)要求強(qiáng)制對(duì)比高次多項(xiàng)式比如固定 degree9的表現(xiàn)這時(shí)候過(guò)擬合幾乎不可避免。一個(gè)標(biāo)準(zhǔn)解法是引入正則化項(xiàng)把優(yōu)化目標(biāo)從“只減小誤差”改成“誤差 懲罰項(xiàng)”懲罰的是系數(shù)的大小。嶺回歸是最常見的實(shí)現(xiàn)它的目標(biāo)函數(shù)是J(w) (1/N)Σ(yi - f(xi))2 λ·Σw?2sklearn 里可以直接調(diào)用from sklearn.linear_model import Ridge from sklearn.preprocessing import PolynomialFeatures from sklearn.pipeline import make_pipeline model make_pipeline( PolynomialFeatures(degree9, include_biasFalse), Ridge(alpha0.01) ) model.fit(x.reshape(-1, 1), y) x_smooth_2d x_smooth.reshape(-1, 1) y_ridge model.predict(x_smooth_2d)PolynomialFeatures(degree9, include_biasFalse) 把單個(gè) x 特征擴(kuò)展成 9 個(gè)多項(xiàng)式特征Ridge(alpha0.01) 是帶 L2 懲罰的線性回歸。alpha 是正則化強(qiáng)度——alpha 越大系數(shù)被壓得越小擬合曲線越平滑但逼近能力也越弱。正則化適合“必須用高次多項(xiàng)式但不想讓它劇烈擺動(dòng)”的場(chǎng)景實(shí)驗(yàn)報(bào)告里把它作為過(guò)擬合補(bǔ)救措施來(lái)寫是很大的加分項(xiàng)。5. 擬合實(shí)驗(yàn)避坑手冊(cè)五個(gè)我真實(shí)踩過(guò)的坑與排查路徑5.1 擬合出來(lái)的曲線瘋狂震蕩甚至出現(xiàn)巨大的正負(fù)尖峰現(xiàn)象是degree 設(shè)為 9 或更高時(shí)擬合曲線在采樣點(diǎn)之間像發(fā)了瘋一樣上下亂穿數(shù)值可以達(dá)到幾十甚至上百完全不像正弦曲線。原因是典型的數(shù)值不穩(wěn)定。多項(xiàng)式次數(shù)升高后設(shè)計(jì)矩陣 X 的各列之間高度相關(guān)X?X 的行列式趨近于 0條件數(shù)急劇增大直接求逆會(huì)放大微小的浮點(diǎn)誤差。另一個(gè)原因是過(guò)擬合本身高階多項(xiàng)式為了穿過(guò)每個(gè)樣本點(diǎn)被迫在區(qū)間邊緣做出大擺動(dòng)來(lái)滿足邊界條件。解決辦法有三條路一是降階這是最直接的二是改用 np.polyfit它內(nèi)部基于 SVD數(shù)值上比手寫正規(guī)方程穩(wěn)定三是引入正則化用 Ridge 壓制系數(shù)大小。排查的時(shí)候可以先打印系數(shù)你會(huì)看到高階項(xiàng)的系數(shù)動(dòng)輒是 103 量級(jí)這就是病灶所在。5.2 訓(xùn)練誤差接近 0但驗(yàn)證誤差大得離譜現(xiàn)象是把 polyfit 的 deg 設(shè)為樣本數(shù)減 1比如 12 個(gè)樣本設(shè) 11訓(xùn)練誤差幾乎等于 0但換一組新數(shù)據(jù)預(yù)測(cè)誤差爆炸式上升。原因是模型把訓(xùn)練數(shù)據(jù)里的噪聲也當(dāng)成了規(guī)律記了下來(lái)這正是過(guò)擬合的定義。要確認(rèn)這一點(diǎn)把訓(xùn)練誤差和驗(yàn)證誤差畫在同一張圖上如果兩條曲線在某個(gè)節(jié)點(diǎn)之后明顯分離、誤差方向相反就是過(guò)擬合的鐵證。解決方法是用驗(yàn)證誤差曲線的谷底選擇階數(shù)如果還需要高次數(shù)就加正則化同時(shí)增加樣本量也能緩解。這個(gè)現(xiàn)象是這次實(shí)驗(yàn)最核心的知識(shí)點(diǎn)也是最值得在實(shí)驗(yàn)報(bào)告里展開討論的內(nèi)容。5.3 噪聲標(biāo)準(zhǔn)差設(shè)成 0.1但結(jié)果有時(shí)候好有時(shí)候差現(xiàn)象是相同代碼、相同參數(shù)多次運(yùn)行后誤差數(shù)值和曲線形狀差異很大尤其是 degree 較高時(shí)“好的時(shí)候很好壞的時(shí)候離譜”。原因是隨機(jī)種子沒有固定。np.random.normal 每次調(diào)用都會(huì)生成不同的噪聲數(shù)據(jù)變了模型自然就變了。解決方法是在生成數(shù)據(jù)之前調(diào)用 np.random.seed(42)之后所有隨機(jī)操作包括 train_test_split 里的 random_state都會(huì)按確定序列運(yùn)行。這也是實(shí)驗(yàn)報(bào)告寫作的基本要求——可復(fù)現(xiàn)性。實(shí)驗(yàn)報(bào)告的“環(huán)境與參數(shù)說(shuō)明”部分應(yīng)當(dāng)寫清楚隨機(jī)種子否則評(píng)審老師復(fù)現(xiàn)不出來(lái)會(huì)直接質(zhì)疑結(jié)果。5.4 用 matplotlib 在中文系統(tǒng)上畫圖出現(xiàn)方框亂碼現(xiàn)象是圖例和標(biāo)題里的中文全部變成空心方塊導(dǎo)出到實(shí)驗(yàn)報(bào)告里非常難看嚴(yán)重影響分?jǐn)?shù)。原因是 matplotlib 默認(rèn)字體不含中文字符。解決方法是在畫圖腳本頂部顯式指定支持中文的字體比如 Windows 上用 SimHeimacOS 上用 Arial Unicode MS或者干脆在畫圖時(shí)不寫中文改用英文標(biāo)簽然后在實(shí)驗(yàn)報(bào)告里用文字說(shuō)明含義。我的習(xí)慣是實(shí)驗(yàn)代碼里用英文標(biāo)簽報(bào)告里用中文描述這樣既避免了亂碼問(wèn)題又保持了代碼的通用性。5.5 驗(yàn)證集劃分不當(dāng)導(dǎo)致誤差曲線劇烈跳動(dòng)現(xiàn)象是把 12 個(gè)樣本按順序劃分成前 7 后 5驗(yàn)證誤差曲線出現(xiàn)很多毛刺谷底位置不清晰甚至多次運(yùn)行結(jié)果不一致。原因有兩個(gè)一是樣本量太小任何劃分方式都會(huì)帶來(lái)方差二是按順序切分導(dǎo)致訓(xùn)練集和驗(yàn)證集的 x 分布不均勻——比如訓(xùn)練集集中在區(qū)間左半邊驗(yàn)證集集中在右半邊模型看不到完整分布驗(yàn)證誤差自然不穩(wěn)。解決方法是用 train_test_split 而不是手工切片并且設(shè)置 stratify 參數(shù)雖然回歸任務(wù)里 stratify 不常用但可以按 x 是否大于中位數(shù)分層如果樣本量允許做 K 折交叉驗(yàn)證取平均值曲線會(huì)平滑很多。對(duì) 12 個(gè)樣本來(lái)說(shuō)更推薦把實(shí)驗(yàn)擴(kuò)充到 4060 個(gè)樣本再談劃分。6. 把源代碼整理成能交的實(shí)驗(yàn)報(bào)告結(jié)構(gòu)與畫圖的落地經(jīng)驗(yàn)代碼跑通了、圖也畫出來(lái)了最后一步是把這些整理成一份像樣的實(shí)驗(yàn)報(bào)告。很多學(xué)生的通病是把代碼整段粘貼進(jìn) Word 文檔再配一兩張圖就算交差——這恰恰丟掉了實(shí)驗(yàn)報(bào)告的核心價(jià)值。實(shí)驗(yàn)報(bào)告是用來(lái)證明你理解了這個(gè)實(shí)驗(yàn)不是用來(lái)展示代碼長(zhǎng)度的。我的建議是章節(jié)按這樣的順序組織實(shí)驗(yàn)?zāi)康囊粌删湓捳f(shuō)明要觀察什么現(xiàn)象、實(shí)驗(yàn)原理多項(xiàng)式擬合的數(shù)學(xué)形式 最小二乘目標(biāo)函數(shù) 正規(guī)方程推導(dǎo)、實(shí)驗(yàn)環(huán)境Python 版本、numpy/sklearn/matplotlib 版本、實(shí)驗(yàn)內(nèi)容與結(jié)果數(shù)據(jù)生成方式、階數(shù)對(duì)比實(shí)驗(yàn)、誤差曲線、可視化圖、討論與分析欠擬合過(guò)擬合的觀察結(jié)論、噪聲影響、正則化效果、附錄完整代碼和運(yùn)行說(shuō)明。畫圖時(shí)注意三個(gè)細(xì)節(jié)第一所有圖的橫縱坐標(biāo)必須有標(biāo)簽圖例必須和曲線一一對(duì)應(yīng)圖片分辨率不低于 150 dpi第二對(duì)比實(shí)驗(yàn)的圖要放在一起比如 degree1、4、11 三條擬合曲線放在同一個(gè)圖里這樣欠擬合和過(guò)擬合能直接對(duì)照第三誤差變化曲線要標(biāo)注谷底位置這是整篇報(bào)告的“論點(diǎn)”沒有標(biāo)注的圖等于讓讀者自己找重點(diǎn)。我印象最深的是自己做這個(gè)實(shí)驗(yàn)時(shí)花了一個(gè)多小時(shí)糾結(jié)為什么 degree11 時(shí)曲線會(huì)沖到 2000 多后來(lái)打印出系數(shù)才發(fā)現(xiàn)高階項(xiàng)數(shù)值已經(jīng)失真了而正則化那一節(jié)當(dāng)時(shí)沒寫進(jìn)報(bào)告——現(xiàn)在回頭看那才是整篇報(bào)告里最有價(jià)值的討論內(nèi)容。希望這篇筆記能幫你把邊界、參數(shù)和坑一次看懂少走這幾步彎路。本文還有配套的精品資源點(diǎn)擊獲取