一流水線解析)
簡介這套機器學習預測系統(tǒng)匯總面向希望系統(tǒng)掌握預測建模的學習者與開發(fā)者圍繞貝葉斯網(wǎng)絡、馬爾科夫模型、線性回歸、嶺回歸、多項式回歸、決策樹回歸和深度神經(jīng)網(wǎng)絡七類方法展開可用于課程設計、模型對比實驗與預測類項目入門。壓縮包共12個文件約1.3MB以Python腳本為主體配合Excel與CSV示例數(shù)據(jù)、UI界面文件及docx、md說明文檔既能查看各算法的實現(xiàn)思路也便于在統(tǒng)一數(shù)據(jù)上運行和比較結果。已有136人學習說明其具備一定的參考與復用價值。讀者可從中獲得從概率圖模型、統(tǒng)計回歸到深度學習的完整示例鏈路借助數(shù)據(jù)與界面代碼快速搭建演示系統(tǒng)并依據(jù)文檔理解參數(shù)設置、模型差異與適用場景為后續(xù)調參與集成學習打下基礎。1. 七個預測模型塞進一個壓縮包它們共享的其實只有一條流水線同一份回歸數(shù)據(jù)喂給線性回歸和深度神經(jīng)網(wǎng)絡預測誤差可能差出三倍同一份序列數(shù)據(jù)喂給馬爾科夫模型和決策樹回歸結論甚至方向相反。這種分歧往往不是調參沒調好而是每個模型的歸納偏置inductive bias在數(shù)據(jù)上留下的指紋。一份把線性回歸、嶺回歸、多項式回歸、決策樹回歸、貝葉斯網(wǎng)絡、馬爾科夫模型、深度神經(jīng)網(wǎng)絡預測收攏在一起的機器學習預測系統(tǒng)價值從來不在于模型數(shù)量多而在于它把七種前提假設擺在同一張桌子上哪些誤差來自模型結構哪些來自數(shù)據(jù)本身一眼就能對上號。認清這套底層機制等于拿到一張「模型為什么會錯」的地圖。后面幾章沿著這張地圖逐層往下拆先看最小二乘框架下的三種變體如何在數(shù)值層面分化再看兩種非線性路線在幾何上的本質差別然后是概率圖模型怎么把預測變成一次條件概率查詢最后落到七個模型接進同一套評估流程時最容易出問題的五個位置。2. 最小二乘的三副面孔正規(guī)方程的病態(tài)、嶺回歸的 λI 與多項式升維的代價2.1 正規(guī)方程解的到底是什么線性回歸只寫了一句話的假設目標值與特征之間是線性關系誤差獨立同分布且方差恒定。在這句假設下最小化殘差平方和 ||y ? Xw||2對 w 求梯度令其為零得到 X?Xw X?y。這個式子叫正規(guī)方程X?X 是 d×d 的 Gram 矩陣。關鍵在于Gram 矩陣的條件數(shù)是設計矩陣條件數(shù)的平方。設計矩陣條件數(shù) 103 在一堆量綱不一致的特征里非常常見平方之后 Gram 矩陣就到 10?float64 的有效十進制位大概 15 到 16 位平方一下只剩 7 到 8 位可信。所以教科書里那句w inv(X.T X) X.T y在真實數(shù)據(jù)上會給出離譜結果不是公式推錯了是數(shù)值路徑選錯了。工程上的做法是繞開顯式求逆。SVD 把 X 分解成 UΣV?代入可得 w VΣ?1U?y條件數(shù)只被放大一次而不是平方。np.linalg.lstsq走的就是這條路。還有一件容易被忽略的事當特征數(shù) d 大于樣本數(shù) n 時 Gram 矩陣必然奇異正規(guī)方程有無窮多組解lstsq 返回的是其中范數(shù)最小的一組——這個細節(jié)正是理解嶺回歸為什么「一定有唯一解」的入口。2.2 嶺回歸λ 加的不是噪聲是給奇異值墊了個地板把 SVD 代進嶺回歸的目標函數(shù) ||y ? Xw||2 λ||w||2解出來是這樣w_ridge Σ? [σ?2 / (σ?2 λ)] · (u??y / σ?) · v?方括號里那項是收縮因子。σ? 遠大于 √λ 的方向因子接近 1系數(shù)幾乎不動σ? 遠小于 √λ 的方向因子接近 0那個方向承載的信息被整體丟掉。這說明 λ 并不是「把所有權系數(shù)均勻壓小」而是按每個奇異值方向的信噪比做選擇性放棄。這就解釋了嶺回歸為什么在共線性嚴重的數(shù)據(jù)上格外好用。共線性意味著存在幾個極小的奇異值那些方向上的系數(shù)估計方差極大普通最小二乘會把噪聲放大成巨大的系數(shù)符號還會隨機翻轉嶺回歸直接按比例把這些方向壓掉代價是引入偏差。偏差換方差這就是它的全部邏輯沒有什么玄學成分。還有一個實現(xiàn)層面的細節(jié)截距項不參與懲罰。如果懲罰截距模型對 y 的整體平移就不再等變——目標值加一個常數(shù) c預測本該也加 c但被懲罰的截距做不到。所以手寫嶺回歸時要把 Gram 矩陣的第 0 個對角元置零用現(xiàn)成庫且開啟截距估計時內(nèi)部一般已經(jīng)這么處理了。λ 怎么定留一交叉驗證在嶺回歸上存在閉式解不需要真的訓練 n 次。另一個好用的量是有效自由度 trace(X(X?XλI)?1X?) Σ σ?2/(σ?2λ)它隨 λ 單調下降可以當成「模型實際用掉了幾參數(shù)」來讀比盯著 λ 的絕對值更有意義。2.3 多項式回歸換了基沒換模型多項式回歸經(jīng)常被當成另一類模型來講其實它還是線性回歸。參數(shù) w 仍然線性地進入預測變的只是設計矩陣的列從 [1, x] 換成 [1, x, x2, …, x^d]。所以它有閉式解也能直接套嶺回歸前面兩節(jié)的所有結論原樣成立。真正的麻煩在數(shù)值上。x 的各次冪之間高度相關拼出來的設計矩陣接近 Vandermonde 矩陣條件數(shù)隨階數(shù)快速上升。在 [-1, 1] 區(qū)間還算溫和一旦特征量綱是 [0, 100]degree 到 6 時 Gram 矩陣條件數(shù)能沖到 1012 量級解出來的系數(shù)符號在正負之間亂跳曲線在樣本點之間劇烈震蕩。這是踩坑重災區(qū)訓練集 R2 是 0.99測試集一塌糊涂。解決辦法有兩條。一條是把 x 中心化再標準化把數(shù)值范圍壓回 [-1, 1] 附近簡單但有效另一條是換正交多項式基Chebyshev、Legendre讓基函數(shù)在樣本上的內(nèi)積近似對角Gram 矩陣接近對角陣。第二條更徹底因為正交基的系數(shù)之間幾乎不相關每個系數(shù)都有相對獨立的含義不會出現(xiàn)「改一個系數(shù)整條曲線全變」的連鎖反應。數(shù)值分析里做函數(shù)逼近默認用正交基原因就在這里。2.4 三條路徑寫在同一套接口下import numpy as np def design_linear(x): # 截距列固定放第 0 列后續(xù)所有正則化都跳過它 x np.asarray(x, dtypefloat).reshape(len(x), -1) return np.hstack([np.ones((x.shape[0], 1)), x]) def fit_ols(X, y): # lstsq 走 SVD不顯式構造 X^T X條件數(shù)只被放大一次而不是平方 w, *_ np.linalg.lstsq(X, y, rcondNone) return w def fit_ridge(X, y, lam): d X.shape[1] reg lam * np.eye(d) reg[0, 0] 0.0 # 截距不懲罰保證對 y 的平移等變 # 用 solve 而不是 inv內(nèi)部走 Cholesky/LU數(shù)值上穩(wěn)一個量級 return np.linalg.solve(X.T X reg, X.T y) def design_poly(x, degree): x np.asarray(x, dtypefloat) # 先中心化再縮放不做這一步degree6 時 Gram 矩陣條件數(shù)可達 1e12 z (x - x.mean()) / (x.std() 1e-12) cols [np.ones_like(z)] [z ** k for k in range(1, degree 1)] return np.vstack(cols).Tdesign_linear和design_poly返回的都是設計矩陣形狀統(tǒng)一為 (n, d1)所以fit_ols/fit_ridge可以直接復用切換模型只是換一個建矩陣的函數(shù)。lam建議在 1e-3 到 1e3 之間取對數(shù)網(wǎng)格掃配合交叉驗證選點degree和lam必須聯(lián)合調單獨把 degree 往上推、λ 卻固定不變等于只放開了容量沒加任何約束。想驗證數(shù)值問題是否真的存在加一行np.linalg.cond(X.T X)打印出來看條件數(shù)過了 10? 就該考慮換基或者加正則過了 1012 基本可以判定結果不可信。3. 兩種非線性決策樹的軸對齊分裂與神經(jīng)網(wǎng)絡的表示學習差在哪3.1 回歸樹的不純度方差縮減就是平方誤差的再分配回歸樹在每個節(jié)點上只做一件事找一個特征和一個閾值把樣本切成兩半讓兩半內(nèi)部的平方誤差之和最小。父節(jié)點的平方誤差是 Σ(y? ? ?)2分裂后是 SSE_L SSE_R兩者之差就是這次分裂帶來的方差縮減。為什么回歸用方差而不是基尼系數(shù)或信息熵那兩者衡量的是類別分布的純度前提是標簽離散?;貧w里「多數(shù)類」這個概念沒有意義能讓平方誤差最小的常數(shù)預測是均值所以不純度自然定義成方差。由此推出一條常被忽略的結論葉節(jié)點的輸出是葉內(nèi)樣本的均值這個值只在平方損失下才最優(yōu)。如果業(yè)務上更在意絕對誤差葉節(jié)點該輸出中位數(shù)樹對離群點的敏感度會立刻降一個檔次。很多實現(xiàn)把這兩件事混著用——用 MSE 訓練、用 MAE 匯報——然后困惑為什么一個離群點能把整棵樹的預測拽偏。3.2 分裂點搜索排一次序前綴和把代價壓到 O(1)對某個特征而言候選切點只有 n?1 個排序后相鄰樣本的中點。暴力做法是對每個候選重新算左右兩邊的均值再算 SSE復雜度 O(n2)。實際實現(xiàn)用前綴和。核心恒等式是一段樣本的平方誤差和 Σy2 ? (Σy)2/n。只要預處理出 Σy 和 Σy2 的前綴和任意一段區(qū)間的 SSE 都是 O(1)。整體流程于是變成對特征排序 O(n log n)掃描一遍候選切點 O(n)單個特征 O(n log n)d 個特征 O(d·n log n)。import numpy as np def best_split(X, y): n, d X.shape # 父節(jié)點 SSE作為「這次分裂必須讓誤差下降」的初始門檻 best_sse float(((y - y.mean()) ** 2).sum()) best (None, None, best_sse) # (特征下標, 閾值, 子節(jié)點 SSE 之和) for j in range(d): order np.argsort(X[:, j]) xs, ys X[order, j], y[order] csum np.cumsum(ys) # Σy 的前綴和 csq np.cumsum(ys ** 2) # Σy2 的前綴和 k np.arange(n - 1) # 候選切點左子節(jié)點為 [0..k] nl, nr k 1, n - k - 1 sl, sr csum[:-1], csum[-1] - csum[:-1] ql, qr csq[:-1], csq[-1] - csq[:-1] # SSE Σy2 - (Σy)2/n左右兩邊各算一次再相加 sse (ql - sl ** 2 / nl) (qr - sr ** 2 / nr) i int(np.argmin(sse)) if sse[i] best[2]: best (j, (xs[i] xs[i 1]) / 2.0, sse[i]) return bestbest_sse的初值取父節(jié)點 SSE作用是讓「不降低誤差的分裂」直接被拒絕天然形成一個極簡的預剪枝。xs[i]和xs[i1]是排序后相鄰的兩個樣本取中點作為閾值是最穩(wěn)妥的做法——取其中任何一個都會讓邊界條件變得依賴浮點比較。返回值里帶上 SSE是為了讓上層遞歸能順便算出這次分裂帶來的方差縮減做后續(xù)的代價復雜度剪枝。3.3 軸對齊分裂的硬邊界分段常數(shù)函數(shù)的局限樹的預測函數(shù)是分段常數(shù)折痕永遠平行于坐標軸。真實關系如果是 y x?·x?等值線是一條條雙曲線樹只能用一堆小矩形去拼想達到固定精度所需的葉子數(shù)隨維度指數(shù)增長。這就解釋了兩個常見現(xiàn)象。一是樹對特征的單調變換免疫把 x 換成 log x 結果完全不變因為分裂只看順序不看數(shù)值二是樹對特征的線性組合極其敏感把 x? 換成 x? x? 之后整棵樹要推倒重學。這也是特征工程在樹模型上依然有效的原因只是形式和線性模型完全不同——線性模型需要的是讓關系變線性樹需要的是讓關系變軸對齊。隨機森林和梯度提升能緩解這個限制但不能消除它們靠集成降低方差折痕方向依然釘死在坐標軸上。理解了這一點就不會在明明需要做特征旋轉的問題上死磕樹模型。3.4 神經(jīng)網(wǎng)絡把軸對齊的折痕換成學出來的方向一個帶 ReLU 的多層感知機輸出是輸入空間上的分段線性函數(shù)。每個 ReLU 單元定義一張超平面激活就是在超平面兩側取不同的線性片。折痕的法向量由權重矩陣學出來不再綁定在坐標軸上所以 y x?·x? 這種對角結構可以用很少的參數(shù)表達。但「分段線性」這四個字值得停下來想一想它不是萬能光滑函數(shù)逼近器它是一族折痕方向可學習的分段線性函數(shù)。import numpy as np def mlp_forward(x, params): # x: (n, d)params: [(W1, b p a hrefhttps://download.csdn.net/download/qq_51320133/89279594 stylecolor:#ec7500;font-size:14px; 本文還有配套的精品資源點擊獲取 /a img altmenu-r.4af5f7ec.gif srchttps://csdnimg.cn/release/wenkucmsfe/public/img/menu-r.4af5f7ec.gif stylewidth:16px;margin-left:4px;vertical-align:text-bottom;cursor:text; /p