代碼實(shí)現(xiàn)指南:從公式推導(dǎo)到Python腳本的完整路徑)
簡介這份資源是周志華《機(jī)器學(xué)習(xí)》西瓜書課程習(xí)題的代碼實(shí)現(xiàn)合集面向正在學(xué)習(xí)機(jī)器學(xué)習(xí)基礎(chǔ)理論、需要?jiǎng)邮謴?fù)現(xiàn)課后習(xí)題的高校學(xué)生與自學(xué)者幫助讀者把聚類、線性模型、集成學(xué)習(xí)等章節(jié)的公式推導(dǎo)落到可運(yùn)行代碼上。壓縮包共90個(gè)文件約11.74MB以22個(gè)Python腳本為核心覆蓋KMeans、KNN、PCA、AdaBoost、Bagging、SVM、決策樹等典型算法實(shí)現(xiàn)并配有10份Markdown習(xí)題筆記、34張結(jié)果圖與6張示意圖另有csv、txt、mat等西瓜數(shù)據(jù)集及少量xml、ipynb配置文件便于對(duì)照實(shí)驗(yàn)與復(fù)現(xiàn)結(jié)果。目前已有934人學(xué)習(xí)下載。資源按章節(jié)組織目錄從模型評(píng)估與選擇到神經(jīng)網(wǎng)絡(luò)、貝葉斯分類、支持向量機(jī)、降維與度量學(xué)習(xí)逐章展開讀者可據(jù)此梳理算法脈絡(luò)、參考代碼實(shí)現(xiàn)思路并借助筆記與圖像驗(yàn)證運(yùn)行效果適合作為課程作業(yè)參考與機(jī)器學(xué)習(xí)入門實(shí)踐素材。1. 西瓜書作業(yè)代碼實(shí)現(xiàn)從公式到可運(yùn)行腳本的第一道坎很多人第一次翻開《機(jī)器學(xué)習(xí)》周志華著圈內(nèi)叫“西瓜書”時(shí)前幾章還能跟上到了第三章線性模型的對(duì)數(shù)幾率回歸、第四章決策樹的信息增益、第五章神經(jīng)網(wǎng)絡(luò)的誤差逆?zhèn)鞑ス揭欢嗑涂ㄗ×?。真正讓人翻車的不是?shù)學(xué)本身而是把公式翻譯成代碼這一步——書上寫的是矩陣求導(dǎo)落到 Python 里要處理維度對(duì)齊、數(shù)值溢出、收斂判斷。這篇筆記面向正在做西瓜書課程作業(yè)、想自己動(dòng)手把課后習(xí)題跑通的人也適合用吳恩達(dá)機(jī)器學(xué)習(xí)作業(yè)入門后想換一本教材鞏固的讀者。我會(huì)按章節(jié)把常見作業(yè)題的實(shí)現(xiàn)路徑拆開給出能直接抄的代碼骨架、參數(shù)設(shè)置和調(diào)試方法重點(diǎn)講清楚每一步為什么這么寫、哪里容易出錯(cuò)。西瓜書配套的習(xí)題答案網(wǎng)上版本很多但代碼實(shí)現(xiàn)質(zhì)量參差不齊與其抄一份跑不通的不如自己按下面的路子搭一遍。2. 線性模型作業(yè)對(duì)數(shù)幾率回歸與線性判別分析的代碼落地2.1 為什么先做線性模型梯度推導(dǎo)和代碼的對(duì)應(yīng)關(guān)系西瓜書第三章的作業(yè)核心是兩件事對(duì)數(shù)幾率回歸邏輯回歸和線性判別分析LDA。這兩個(gè)模型的共同點(diǎn)是都能寫成矩陣形式代碼量不大但正好用來建立“公式到代碼”的映射習(xí)慣。對(duì)數(shù)幾率回歸的模型是 $y \frac{1}{1e^{-(\mathbf{w}^T\mathbf{x}b)}}$對(duì)數(shù)似然最大化等價(jià)于最小化交叉熵?fù)p失。書上用牛頓法求解但實(shí)際寫作業(yè)時(shí)用梯度下降更直觀也更容易調(diào)參。關(guān)鍵推導(dǎo)只有一步對(duì) $w$ 求導(dǎo)得到 $\sum_i (\hat{y}_i - y_i)\mathbf{x}_i$這個(gè)形式非常干凈代碼里就是一個(gè)矩陣乘法。LDA 的作業(yè)通常要求實(shí)現(xiàn)二分類的投影方向求解。書上給的閉式解是 $\mathbf{w} \mathbf{S}_w^{-1}(\boldsymbol{\mu}_0 - \boldsymbol{\mu}_1)$其中 $\mathbf{S}_w$ 是類內(nèi)散度矩陣。代碼實(shí)現(xiàn)時(shí)最容易忽略的是 $\mathbf{S}_w$ 可能奇異需要加正則項(xiàng)或者用偽逆。2.2 對(duì)數(shù)幾率回歸的最小實(shí)現(xiàn)從數(shù)據(jù)生成到訓(xùn)練下面這段代碼用 numpy 從零實(shí)現(xiàn)對(duì)數(shù)幾率回歸不依賴 sklearn適合直接作為作業(yè)提交的基礎(chǔ)版本。import numpy as np def sigmoid(z): # 裁剪防止 exp 溢出這是數(shù)值穩(wěn)定的關(guān)鍵 z np.clip(z, -500, 500) return 1.0 / (1.0 np.exp(-z)) def train_logistic(X, y, lr0.1, epochs1000, tol1e-6): X: (n_samples, n_features) y: (n_samples,) 取值 0 或 1 lr: 學(xué)習(xí)率 epochs: 最大迭代輪數(shù) tol: 梯度范數(shù)收斂閾值 n, d X.shape w np.zeros(d) b 0.0 prev_loss float(inf) for epoch in range(epochs): z X w b y_hat sigmoid(z) # 交叉熵?fù)p失 loss -np.mean(y * np.log(y_hat 1e-12) (1 - y) * np.log(1 - y_hat 1e-12)) # 梯度 error y_hat - y grad_w X.T error / n grad_b np.mean(error) w - lr * grad_w b - lr * grad_b if abs(prev_loss - loss) tol: break prev_loss loss return w, b def predict(X, w, b): return (sigmoid(X w b) 0.5).astype(int)這段代碼的邏輯說明sigmoid里做clip是防止 $z$ 過大時(shí)exp溢出這是很多人第一次寫邏輯回歸會(huì)遇到的問題表現(xiàn)為 loss 變成 nan。梯度推導(dǎo)對(duì)應(yīng)error y_hat - y然后X.T error就是 $\sum_i (\hat{y}_i - y_i)\mathbf{x}_i$ 的矩陣形式。學(xué)習(xí)率lr默認(rèn) 0.1如果 loss 震蕩就降到 0.01epochs設(shè) 1000 通常夠用配合tol提前停止。參數(shù)b的梯度是np.mean(error)因?yàn)閷?duì)偏置求導(dǎo)沒有 $x$ 項(xiàng)。2.3 LDA 實(shí)現(xiàn)中的矩陣奇異問題與正則化處理LDA 的代碼短但坑集中在 $\mathbf{S}_w$ 求逆。下面是一個(gè)帶正則的版本。def train_lda(X, y, reg1e-4): X: (n_samples, n_features) y: (n_samples,) 取值 0 或 1 reg: 正則化系數(shù)防止類內(nèi)散度矩陣奇異 X0 X[y 0] X1 X[y 1] mu0 X0.mean(axis0) mu1 X1.mean(axis0) # 類內(nèi)散度矩陣 Sw np.zeros((X.shape[1], X.shape[1])) for x in X0: diff (x - mu0).reshape(-1, 1) Sw diff diff.T for x in X1: diff (x - mu1).reshape(-1, 1) Sw diff diff.T # 加正則項(xiàng)等價(jià)于嶺回歸思路 Sw reg * np.eye(X.shape[1]) w np.linalg.solve(Sw, (mu0 - mu1)) return w def predict_lda(X, w, threshold0.0): proj X w return (proj threshold).astype(int)邏輯說明Sw累加每個(gè)樣本的外積這一步用循環(huán)寫清楚實(shí)際作業(yè)里可以向量化。reg默認(rèn) 1e-4如果數(shù)據(jù)維度高、樣本少Sw接近奇異加大到 1e-2 或 1e-1。np.linalg.solve比直接求逆數(shù)值更穩(wěn)。閾值threshold通常取兩類投影均值的中點(diǎn)這里簡化成 0實(shí)際作業(yè)里應(yīng)該用(mu0 w mu1 w) / 2。2.4 線性模型作業(yè)的驗(yàn)證方法與常見誤用寫完代碼后用西瓜書第三章的西瓜數(shù)據(jù)集 3.0α 驗(yàn)證。這個(gè)數(shù)據(jù)集只有 17 個(gè)樣本2 個(gè)特征適合手算對(duì)照。常見誤用有兩個(gè)一是把y的標(biāo)簽設(shè)成 -1 和 1 卻套用 0/1 的交叉熵公式導(dǎo)致 loss 計(jì)算錯(cuò)誤二是 LDA 里忘記減均值直接對(duì)原始數(shù)據(jù)算散度矩陣。驗(yàn)證時(shí)打印前幾輪 loss正常應(yīng)該單調(diào)下降如果上下跳動(dòng)就是學(xué)習(xí)率太大。另外邏輯回歸的準(zhǔn)確率在西瓜數(shù)據(jù)集上大概 70% 左右不要期望太高這個(gè)數(shù)據(jù)集本身線性可分性一般。3. 決策樹作業(yè)信息增益、基尼指數(shù)與剪枝的代碼實(shí)現(xiàn)3.1 劃分準(zhǔn)則的選型信息增益 vs 基尼指數(shù)西瓜書第四章作業(yè)通常要求實(shí)現(xiàn) ID3 或 C4.5核心是劃分準(zhǔn)則。信息增益對(duì)取值多的屬性有偏好這是 ID3 的已知缺陷C4.5 用增益率修正但增益率對(duì)取值少的屬性有偏好所以書上建議先找信息增益高于平均的屬性再從中選增益率最高的?;嶂笖?shù)是 CART 用的計(jì)算更簡單沒有對(duì)數(shù)運(yùn)算。作業(yè)里如果沒指定準(zhǔn)則我一般用基尼指數(shù)因?yàn)榇a短、數(shù)值穩(wěn)定。如果題目明確要求信息增益就按書上公式來。下面以基尼指數(shù)為例。3.2 遞歸建樹的代碼骨架與停止條件import numpy as np from collections import Counter def gini(y): # y 是標(biāo)簽數(shù)組 if len(y) 0: return 0.0 counts Counter(y) probs np.array(list(counts.values())) / len(y) return 1.0 - np.sum(probs ** 2) def split_dataset(X, y, feature, value): # 按特征 feature 是否等于 value 劃分 mask X[:, feature] value return X[mask], y[mask], X[~mask], y[~mask] def choose_best_split(X, y): best_gain -1 best_feature None best_value None base_gini gini(y) n len(y) for feature in range(X.shape[1]): for value in np.unique(X[:, feature]): X_left, y_left, X_right, y_right split_dataset(X, y, feature, value) if len(y_left) 0 or len(y_right) 0: continue # 加權(quán)基尼 weighted (len(y_left) / n) * gini(y_left) (len(y_right) / n) * gini(y_right) gain base_gini - weighted if gain best_gain: best_gain gain best_feature feature best_value value return best_feature, best_value, best_gain def build_tree(X, y, depth0, max_depth10, min_samples2): # 停止條件純節(jié)點(diǎn)、樣本太少、達(dá)到最大深度 if len(set(y)) 1 or len(y) min_samples or depth max_depth: return Counter(y).most_common(1)[0][0] feature, value, gain choose_best_split(X, y) if feature is None or gain 0: return Counter(y).most_common(1)[0][0] X_left, y_left, X_right, y_right split_dataset(X, y, feature, value) tree { feature: feature, value: value, left: build_tree(X_left, y_left, depth 1, max_depth, min_samples), right: build_tree(X_right, y_right, depth 1, max_depth, min_samples) } return tree邏輯說明gini計(jì)算基尼值choose_best_split遍歷所有特征和取值選加權(quán)基尼最小的劃分。build_tree的停止條件有三個(gè)節(jié)點(diǎn)純、樣本數(shù)小于min_samples、深度達(dá)到max_depth。max_depth默認(rèn) 10西瓜數(shù)據(jù)集上 3 到 5 就夠min_samples默認(rèn) 2防止過擬合。注意gain 0時(shí)也停止避免無意義劃分。3.3 預(yù)剪枝與后剪枝的實(shí)現(xiàn)差異西瓜書第四章專門講了預(yù)剪枝和后剪枝。預(yù)剪枝在build_tree里加驗(yàn)證集判斷劃分前先在驗(yàn)證集上算準(zhǔn)確率劃分后再算如果沒提升就不劃分。后剪枝是先建完整棵樹再自底向上遍歷把子樹替換成葉節(jié)點(diǎn)看驗(yàn)證集準(zhǔn)確率是否提升。預(yù)剪枝代碼改動(dòng)小在choose_best_split之后加一段驗(yàn)證邏輯即可。后剪枝需要額外寫一個(gè)遍歷函數(shù)用遞歸返回剪枝后的樹。作業(yè)里如果要求對(duì)比建議兩種都實(shí)現(xiàn)用同一份驗(yàn)證集。注意預(yù)剪枝有欠擬合風(fēng)險(xiǎn)后剪枝計(jì)算量大但泛化通常更好。3.4 決策樹作業(yè)的調(diào)試連續(xù)值處理和缺失值西瓜數(shù)據(jù)集 3.0α 全是離散值但作業(yè)有時(shí)會(huì)給連續(xù)值版本。連續(xù)值處理方法是二分法對(duì)每個(gè)特征排序取相鄰值中點(diǎn)作為劃分點(diǎn)選基尼最小的。代碼里np.unique換成排序后的中點(diǎn)數(shù)組即可。缺失值處理書上講了權(quán)重法代碼實(shí)現(xiàn)復(fù)雜作業(yè)里如果沒要求可以跳過。如果要求核心是給每個(gè)樣本加權(quán)重劃分時(shí)按權(quán)重計(jì)算基尼缺失樣本同時(shí)進(jìn)入所有分支但權(quán)重按分支比例調(diào)整。這部分容易寫錯(cuò)建議先用小數(shù)據(jù)手工驗(yàn)證。4. 神經(jīng)網(wǎng)絡(luò)作業(yè)BP 算法的矩陣實(shí)現(xiàn)與梯度檢查4.1 為什么 BP 作業(yè)容易翻車維度對(duì)齊和鏈?zhǔn)椒▌t西瓜書第五章的 BP 神經(jīng)網(wǎng)絡(luò)作業(yè)翻車率最高。原因不是算法難而是矩陣維度容易對(duì)不上。書上公式用下標(biāo)表示代碼里要轉(zhuǎn)成矩陣乘法稍不注意就廣播錯(cuò)誤。另一個(gè)坑是鏈?zhǔn)椒▌t的中間項(xiàng)漏乘導(dǎo)致梯度方向?qū)Φ笮″e(cuò)訓(xùn)練很慢。我一般建議先寫一個(gè)單隱層網(wǎng)絡(luò)用西瓜數(shù)據(jù)集 3.0α 的 17 個(gè)樣本做二分類隱藏層 5 個(gè)神經(jīng)元這樣維度小出錯(cuò)容易查。4.2 單隱層 BP 網(wǎng)絡(luò)的完整代碼import numpy as np def sigmoid(z): z np.clip(z, -500, 500) return 1.0 / (1.0 np.exp(-z)) def sigmoid_deriv(a): # a 是 sigmoid 的輸出 return a * (1 - a) def train_bp(X, y, hidden5, lr0.5, epochs5000): n, d X.shape y y.reshape(-1, 1) # 初始化權(quán)重小隨機(jī)數(shù) np.random.seed(42) W1 np.random.randn(d, hidden) * 0.1 b1 np.zeros((1, hidden)) W2 np.random.randn(hidden, 1) * 0.1 b2 np.zeros((1, 1)) losses [] for epoch in range(epochs): # 前向 z1 X W1 b1 a1 sigmoid(z1) z2 a1 W2 b2 a2 sigmoid(z2) # 損失 loss -np.mean(y * np.log(a2 1e-12) (1 - y) * np.log(1 - a2 1e-12)) losses.append(loss) # 反向 dz2 a2 - y dW2 a1.T dz2 / n db2 np.mean(dz2, axis0, keepdimsTrue) da1 dz2 W2.T dz1 da1 * sigmoid_deriv(a1) dW1 X.T dz1 / n db1 np.mean(dz1, axis0, keepdimsTrue) # 更新 W2 - lr * dW2 b2 - lr * db2 W1 - lr * dW1 b1 - lr * db1 return W1, b1, W2, b2, losses邏輯說明W1是輸入到隱藏層形狀(d, hidden)W2是隱藏到輸出形狀(hidden, 1)。反向傳播里dz2 a2 - y是輸出層誤差dW2 a1.T dz2 / n對(duì)應(yīng)鏈?zhǔn)椒▌t。da1 dz2 W2.T把誤差傳回隱藏層再乘sigmoid_deriv(a1)得到dz1。學(xué)習(xí)率lr默認(rèn) 0.5BP 網(wǎng)絡(luò)對(duì)學(xué)習(xí)率敏感太大震蕩太小收斂慢。epochs設(shè) 5000配合觀察losses曲線。4.3 梯度檢查數(shù)值梯度驗(yàn)證解析梯度寫完反向傳播后用數(shù)值梯度檢查。對(duì)每個(gè)參數(shù)擾動(dòng)一小量算損失變化和解析梯度對(duì)比。def gradient_check(X, y, W1, b1, W2, b2, epsilon1e-4): # 只檢查 W1 的第一個(gè)元素作為示例 def loss_fn(W1, b1, W2, b2): a1 sigmoid(X W1 b1) a2 sigmoid(a1 W2 b2) return -np.mean(y * np.log(a2 1e-12) (1 - y) * np.log(1 - a2 1e-12)) # 解析梯度 a1 sigmoid(X W1 b1) a2 sigmoid(a1 W2 b2) dz2 a2 - y.reshape(-1, 1) dW2 a1.T dz2 / len(y) da1 dz2 W2.T dz1 da1 * sigmoid_deriv(a1) dW1 X.T dz1 / len(y) # 數(shù)值梯度 W1_plus W1.copy() W1_plus[0, 0] epsilon W1_minus W1.copy() W1_minus[0, 0] - epsilon num_grad (loss_fn(W1_plus, b1, W2, b2) - loss_fn(W1_minus, b1, W2, b2)) / (2 * epsilon) print(f解析梯度: {dW1[0, 0]:.6f}, 數(shù)值梯度: {num_grad:.6f})邏輯說明數(shù)值梯度用中心差分精度比前向差分高。解析梯度和數(shù)值梯度相對(duì)誤差小于 1e-4 算通過。如果差很多檢查鏈?zhǔn)椒▌t哪一項(xiàng)漏了。這個(gè)檢查在作業(yè)里很加分也能幫你快速定位 bug。4.4 BP 作業(yè)的調(diào)參經(jīng)驗(yàn)學(xué)習(xí)率、隱藏層節(jié)點(diǎn)和初始化學(xué)習(xí)率從 0.1 到 1.0 試西瓜數(shù)據(jù)集上 0.5 左右通??梢浴k[藏層節(jié)點(diǎn)數(shù)從 3 到 10 試太多會(huì)過擬合太少欠擬合。初始化用小的隨機(jī)數(shù)全零會(huì)導(dǎo)致對(duì)稱權(quán)重?zé)o法打破這是經(jīng)典坑。如果 loss 不下降先檢查sigmoid_deriv是不是用了a * (1 - a)而不是z * (1 - z)。5. 支持向量機(jī)作業(yè)對(duì)偶問題、核函數(shù)與 SMO 的簡化實(shí)現(xiàn)5.1 SVM 作業(yè)的難點(diǎn)從原始問題到對(duì)偶問題的代碼映射西瓜書第六章的 SVM 作業(yè)理論推導(dǎo)長代碼實(shí)現(xiàn)更難。書上先講原始問題再講對(duì)偶問題最后講 KKT 條件和 SMO。作業(yè)通常要求實(shí)現(xiàn)一個(gè)簡化版 SMO在西瓜數(shù)據(jù)集上跑通。核心公式對(duì)偶問題是 $\max_\alpha \sum_i \alpha_i - \frac{1}{2}\sum_{i,j}\alpha_i\alpha_j y_i y_j \mathbf{x}_i^T\mathbf{x}_j$約束是 $0 \le \alpha_i \le C$ 和 $\sum_i \alpha_i y_i 0$。SMO 每次選兩個(gè) $\alpha$ 更新固定其他。5.2 簡化 SMO 的代碼實(shí)現(xiàn)import numpy as np def smo_simple(X, y, C1.0, tol1e-3, max_iter100): n, d X.shape y y.astype(float) alpha np.zeros(n) b 0.0 # 核矩陣線性核 K X X.T for it in range(max_iter): alpha_pairs_changed 0 for i in range(n): # 計(jì)算誤差 f_i np.sum(alpha * y * K[:, i]) b E_i f_i - y[i] if (y[i] * E_i -tol and alpha[i] C) or (y[i] * E_i tol and alpha[i] 0): # 選第二個(gè) alpha簡化版隨機(jī)選 j (i 1) % n f_j np.sum(alpha * y * K[:, j]) b E_j f_j - y[j] alpha_i_old alpha[i] alpha_j_old alpha[j] # 計(jì)算上下界 if y[i] ! y[j]: L max(0, alpha[j] - alpha[i]) H min(C, C alpha[j] - alpha[i]) else: L max(0, alpha[i] alpha[j] - C) H min(C, alpha[i] alpha[j]) if L H: continue eta 2 * K[i, j] - K[i, i] - K[j, j] if eta 0: continue alpha[j] alpha_j_old - y[j] * (E_i - E_j) / eta alpha[j] np.clip(alpha[j], L, H) if abs(alpha[j] - alpha_j_old) 1e-5: continue alpha[i] alpha_i_old y[i] * y[j] * (alpha_j_old - alpha[j]) # 計(jì)算 b b1 b - E_i - y[i] * (alpha[i] - alpha_i_old) * K[i, i] - y[j] * (alpha[j] - alpha_j_old) * K[i, j] b2 b - E_j - y[i] * (alpha[i] - alpha_i_old) * K[i, j] - y[j] * (alpha[j] - alpha_j_old) * K[j, j] if 0 alpha[i] C: b b1 elif 0 alpha[j] C: b b2 else: b (b1 b2) / 2 alpha_pairs_changed 1 if alpha_pairs_changed 0: break return alpha, b邏輯說明K是核矩陣線性核直接X X.T。外層循環(huán)遍歷每個(gè)樣本內(nèi)層選第二個(gè) $\alpha$簡化版直接取下一個(gè)。eta是二階導(dǎo)數(shù)小于等于 0 時(shí)跳過。L和H是 $\alpha_j$ 的上下界分 $y_i \ne y_j$ 和相等兩種情況。b的計(jì)算分三種情況對(duì)應(yīng)書上公式。C默認(rèn) 1.0控制軟間隔tol默認(rèn) 1e-3KKT 違反容忍度。5.3 核函數(shù)的替換與參數(shù)選擇把K X X.T換成高斯核def rbf_kernel(X, sigma1.0): # 計(jì)算平方歐氏距離矩陣 sq np.sum(X ** 2, axis1, keepdimsTrue) dist sq sq.T - 2 * X X.T return np.exp(-dist / (2 * sigma ** 2))sigma控制核寬度太小過擬合太大欠擬合。西瓜數(shù)據(jù)集上從 0.5 到 2.0 試。多項(xiàng)式核類似改K (X X.T 1) ** degree。5.4 SVM 作業(yè)的驗(yàn)證支持向量和決策邊界訓(xùn)練完后alpha 1e-5的樣本就是支持向量。打印支持向量個(gè)數(shù)西瓜數(shù)據(jù)集上通常 5 到 8 個(gè)。決策邊界用w np.sum(alpha * y * X.T, axis1)算然后畫線。如果支持向量太多說明C太大或核參數(shù)不合適。6. 集成學(xué)習(xí)與聚類作業(yè)從 AdaBoost 到 K-means 的代碼收尾6.1 AdaBoost 的實(shí)現(xiàn)要點(diǎn)樣本權(quán)重更新和基學(xué)習(xí)器西瓜書第八章的 AdaBoost 作業(yè)核心是樣本權(quán)重 $D_t$ 的更新和基學(xué)習(xí)器權(quán)重 $\alpha_t$ 的計(jì)算?;鶎W(xué)習(xí)器通常用決策樹樁即深度為 1 的決策樹。def adaboost(X, y, T10): n len(y) D np.ones(n) / n models [] alphas [] for t in range(T): # 訓(xùn)練決策樹樁用加權(quán)基尼 stump build_stump(X, y, D) pred predict_stump(stump, X) err np.sum(D[pred ! y]) if err 0.5: break alpha 0.5 * np.log((1 - err) / (err 1e-12)) # 更新權(quán)重 D D * np.exp(-alpha * y * pred) D D / np.sum(D) models.append(stump) alphas.append(alpha) return models, alphas邏輯說明build_stump是帶權(quán)重的決策樹樁選加權(quán)基尼最小的劃分。err是加權(quán)錯(cuò)誤率大于等于 0.5 時(shí)停止。alpha是基學(xué)習(xí)器權(quán)重錯(cuò)誤率越低權(quán)重越大。D更新后歸一化。預(yù)測(cè)時(shí)加權(quán)投票。6.2 K-means 的坑初始化、空簇和收斂判斷西瓜書第九章的 K-means 作業(yè)代碼短但坑多。初始化用隨機(jī)選點(diǎn)可能選到同一個(gè)點(diǎn)導(dǎo)致空簇。解決辦法是用 K-means 初始化或者選到重復(fù)點(diǎn)時(shí)重新選。def kmeans(X, k, max_iter100, tol1e-4): n, d X.shape # K-means 初始化 centers [X[np.random.randint(n)]] for _ in range(1, k): dist np.min([np.sum((X - c) ** 2, axis1) for c in centers], axis0) prob dist / np.sum(dist) centers.append(X[np.random.choice(n, pprob)]) centers np.array(centers) for it in range(max_iter): # 分配 dist np.array([np.sum((X - c) ** 2, axis1) for c in centers]) labels np.argmin(dist, axis0) # 更新 new_centers np.array([X[labels i].mean(axis0) if np.sum(labels i) 0 else centers[i] for i in range(k)]) if np.sum((new_centers - centers) ** 2) tol: break centers new_centers return labels, centers邏輯說明K-means 初始化讓初始中心盡量分散。分配步驟算每個(gè)點(diǎn)到中心的距離取最小。更新步驟對(duì)每個(gè)簇求均值空簇保持原中心。tol默認(rèn) 1e-4中心變化小于這個(gè)值就停。6.3 聚類作業(yè)的評(píng)估輪廓系數(shù)和可視化K-means 沒有標(biāo)簽評(píng)估用輪廓系數(shù)。西瓜數(shù)據(jù)集上k 從 2 到 5 試選輪廓系數(shù)最大的??梢暬?PCA 降到二維畫散點(diǎn)圖看簇是否分開。如果簇重疊嚴(yán)重可能數(shù)據(jù)本身不適合 K-means換 DBSCAN 或高斯混合。6.4 集成學(xué)習(xí)作業(yè)的對(duì)比AdaBoost vs 隨機(jī)森林隨機(jī)森林是 Bagging 的代表和 AdaBoost 的 Boosting 思路不同。作業(yè)里如果要求對(duì)比用同一份數(shù)據(jù)隨機(jī)森林的基學(xué)習(xí)器用完整決策樹AdaBoost 用樹樁。隨機(jī)森林對(duì)噪聲更魯棒AdaBoost 對(duì)噪聲敏感因?yàn)殄e(cuò)誤樣本權(quán)重會(huì)不斷增大。西瓜數(shù)據(jù)集上兩者準(zhǔn)確率接近但隨機(jī)森林訓(xùn)練更快因?yàn)榭梢圆⑿小N易鐾赀@一輪作業(yè)最大的習(xí)慣是每寫一個(gè)模型先用小數(shù)據(jù)手工算一遍前向和反向確認(rèn)公式和代碼對(duì)得上再上完整數(shù)據(jù)。這個(gè)習(xí)慣幫我省了無數(shù)調(diào)試時(shí)間。希望幫到你。本文還有配套的精品資源點(diǎn)擊獲取