首页/新闻资讯/正文详情

机器学习教材算法手写代码练习包:从公式到Python实现

发布时间:2026/11/6 9:01:20 来源:云帆数科 栏目:资讯中心
机器学习教材算法手写代码练习包:从公式到Python实现
简介这份代码练习包面向学习周志华《机器学习》的本科生、研究生及自学者配合教材章节提供可运行的算法实现用于把线性模型、分类与集成方法等抽象概念落到代码层面适合课程实验与自学对照。压缩包共16个文件以15个Python脚本为主体另有1个Markdown说明文件整体约20KB体积轻便便于随课程进度逐章练习。内容覆盖逻辑回归、交叉验证与留一法、LDA、决策树及Friedman相关实现也涉及Iterated Bagging、MultiBoosting等集成学习思路、偏差—方差实验和SVM的简化、比较与回归训练示例并配有说明文档帮助理解代码组织与运行方式。已有510人学习下载读者可据此对照教材复现实验、修改参数观察模型变化积累调参与排错经验并巩固机器学习基础概念与算法直觉。1. 别急着背公式先把教材代码跑一遍很多人学机器学习是从公式推导开始的推完一遍合上书换到真实数据集上还是不知道第一行代码该写什么。这份《机器学习》代码练习包填的正是这道缝它把教材每章的算法都落成了能直接执行的 Python 脚本和 Notebook从最小二乘、决策树一路到 BP 网络、AdaBoost配着教材里的西瓜数据集和几份小样本数据跑一遍就能看见公式里每个符号在代码里长什么样。它适合两类人跟着教材自学、需要把推导落到实现上的学生以及想复习经典算法细节、不愿只会调 sklearn 接口的开发者。包里没有工程封装也不追求性能定位就是一个看得懂、改得动的练习场读代码的门槛比读框架源码低得多。2. 目录结构与技术栈先摸清这份练习包的地图拿到压缩包别急着双击运行先花十分钟看清它怎么组织。练习类代码包最怕的就是打开一看二十个同名main.py不知道哪个对应哪一章。这类按教材章节组织的包通常有一套很稳定的结构认清它后面找代码、改代码都快。2.1 目录骨架按章节编号切分常见的目录长这样章节号和教材对齐算法名做子目录数据和工具函数单独抽出来ml-exercises/ ├── ch03_linear/ # 线性模型线性回归、逻辑回归、多分类 │ ├── linreg_gd.py │ ├── logreg_gd.py │ └── demo.ipynb ├── ch04_tree/ # 决策树划分准则、剪枝、连续值处理 ├── ch05_nn/ # 神经网络BP 算法手写 ├── ch06_svm/ # 支持向量机对偶、核函数 ├── ch07_bayes/ # 贝叶斯分类器朴素贝叶斯、半朴素 ├── ch08_ensemble/ # 集成学习AdaBoost、Bagging、随机森林 ├── ch09_cluster/ # 聚类k-means、DBSCAN、层次聚类 ├── ch10_dimreduc/ # 降维PCA、LDA ├── data/ # 数据集csv 明文方便直接看 ├── utils/ # 指标、绘图、数据加载的公共函数 │ ├── metrics.py │ ├── plot.py │ └── datasets.py ├── requirements.txt └── README.md这里有两个设计选择值得注意。一是数据放csv明文而不是npz或pickle练习包里这点很重要你需要肉眼确认某个特征取值到底是什么二进制文件做不到。二是utils/目录的存在说明作者没有让每个脚本各自复制一份准确率计算和绘图代码公共逻辑收敛到了一处改动的时候只改一个地方。目录主要产出依赖关系ch03_linear~ch10_dimreduc各章算法实现与演示依赖utils/和data/utils/metrics.py准确率、MSE、混淆矩阵无纯 numpyutils/datasets.py数据加载与编码依赖pandasutils/plot.py决策边界、loss 曲线依赖matplotlib如果包里没有utils/而是每个脚本自己写一遍accuracy那也不用惊讶练习包常见的写法就是把所有东西摊平在一个文件里好处是单文件可读、复制出去就能跑坏处是改一个指标要改十几处。看清这一点后面重构才有方向。2.2 环境与依赖三行命令跑起来练习包的依赖基本固定在科学计算那一套requirements.txt内容大致是numpy1.23 pandas1.5 matplotlib3.6 scikit-learn1.2 jupyter1.0装的时候我一般先建虚拟环境再装避免和系统里已有的 numpy 打架# 建环境并激活Windows 用 .venv\Scripts\activate python -m venv .venv source .venv/bin/activate # 用 python -m pip 而不是裸 pip保证装进当前解释器 python -m pip install --upgrade pip python -m pip install -r requirements.txtpython -m pip这个写法看着啰嗦但它是排掉「明明装了却 import 不到」这类玄学问题的第一步。裸pip有可能指向另一个 Python尤其是在同时装了系统 Python 和 conda 的机器上血泪经验。版本上有个细节scikit-learn从 1.0 到 1.3 之间DecisionTreeClassifier的splitter默认值和随机性处理有过调整如果你在比较自己手写的决策树和 sklearn 的结果两边版本不一致会出现树结构对不上的情况。这不是代码写错了是库的行为变了。练习包里所有对拍脚本都建议锁定同一个大版本。启动单章演示用 Notebook# 在项目根目录启动Notebook 里用相对路径引 data/ 才不会断 jupyter notebookNotebook 里读数据一律用相对路径data/xxx.csv因为 Jupyter 的工作目录是启动时所在的目录。你在ch04_tree/下再起一个 notebook 内核相对路径就会失效报FileNotFoundError这几乎是每个新手必踩的一次。2.3 数据文件与编码约定先统一接口再谈算法练习包的价值一半在算法一半在数据格式的统一。这类包通常约定X是形状(n_samples, n_features)的ndarrayy是(n_samples,)分类标签用0/1或1/-1函数签名统一成fit(X, y)和predict(X)。看着像废话但正是这条约定让每个算法文件可以互换着跑同一份数据。教材里的西瓜数据集 3.0 有六个离散属性加两个连续属性落成 csv 后需要做编码。看这段加载代码import pandas as pd import numpy as np def load_watermelon(pathdata/watermelon3a.csv): 加载教材西瓜数据集返回 (X, y) X: (17, 8) float ndarray离散列已做序数编码 y: (17,) int ndarray1 表示好瓜0 表示坏瓜 df pd.read_csv(path) cat_cols [色泽, 根蒂, 敲声, 纹理, 脐部, 触感] for col in cat_cols: # cat.codes 按字典序编码保证同一份数据每次编码结果一致 df[col] df[col].astype(category).cat.codes X df.drop(columns[好瓜]).to_numpy(dtypefloat) y (df[好瓜] 是).to_numpy(dtypeint) return X, y这段代码里有三个需要注意的参数级细节。astype(category).cat.codes用的是字典序而不是出现顺序好处是可复现坏处是「纹理」编码成 0/1/2 之后模型会误以为它们有大小关系而决策树和信息增益这类算法其实不关心编码顺序逻辑回归就会受影响所以后面做线性模型时更稳妥的做法是独热编码。dtypefloat是必须的否则离散列是 int64和后面标准化函数里的 float 运算混在一起会有隐式转换警告。y用 是转成 0/1而不是直接cat.codes是为了避免标签含义在编码后失真。另一份常用数据是鸢尾花sklearn.datasets自带练习包里一般会包一层from sklearn.datasets import load_iris def load_iris_2class(): 只取前两类方便画二维决策边界 X, y load_iris(return_X_yTrue) mask y 2 return X[mask][:, :2], y[mask] # 只留前两个特征便于可视化取两类的目的是可视化三类以上没法在平面上画出干净的决策边界。取两个特征也是同样的道理。这些取舍在练习包里很常见读代码时要意识到「简化是为了看得见」不是算法只能处理二维。3. 核心模块拆解线性模型与决策树的手写实现这两章是整份练习包的地基。线性模型让你理解梯度到底在更新什么决策树让你理解「划分」这个动作怎么用信息论量出来。两段代码都不长但每一行都对应教材上的一段推导。3.1 线性回归批量梯度下降的完整循环先看最容易上手的一段最小二乘配批量梯度下降import numpy as np def linear_regression_gd(X, y, lr0.01, epochs2000, tol1e-8): 线性回归 批量梯度下降 X: (n, d) 已标准化标准化与否直接决定能否收敛 y: (n,) 目标值 lr: 学习率标准化后 0.01~0.1 通常能收敛 epochs: 最大迭代轮数 tol: 相邻两轮 loss 差值小于该值则提前停 n, d X.shape Xb np.hstack([np.ones((n, 1)), X]) # 补一列 1把偏置 b 吸收进权重 w np.zeros((d 1, 1)) # 参数初始化为 0凸问题无需随机初始化 y y.reshape(-1, 1) loss_hist [] for it in range(epochs): y_hat Xb w # 前向(n,1) err y_hat - y grad (2.0 / n) * Xb.T err # dMSE/dw注意 2/n 这个系数 w - lr * grad loss float(np.mean(err ** 2)) loss_hist.append(loss) if it 0 and abs(loss_hist[-2] - loss) tol: break # 提前停止省时间也防震荡 return w, loss_hist逐点说。np.hstack([np.ones((n,1)), X])这一步叫增广把b合并进w后面梯度公式就只剩一个矩阵乘不用分开写w和b的更新教材上分开写是为了推导清楚代码里合并更简洁。grad里的2/n来自均方误差对参数的偏导如果你把它漏掉等价于把学习率缩小一半收敛会慢但不至于错这也是很多人「代码能跑但就是慢」的原因。tol的判断放在it 0之后否则第一轮loss_hist[-2]会索引到空列表。真正容易翻车的是标准化。这段代码默认X已经标准化过如果直接拿原始含糖率0.1 到 0.8 之间和密度0.2 到 0.7 之间喂进去还勉强能跑一旦换成量纲差三四个数量级的数据loss 会直接变inf或nan。我一般会在调用前强制过一遍def standardize(X, meanNone, stdNone): 训练集算 mean/std测试集复用避免数据泄漏 if mean is None: mean, std X.mean(axis0), X.std(axis0) std np.where(std 0, 1.0, std) # 常数列保护防除零 return (X - mean) / std, mean, std注意std 0的处理。西瓜数据集里如果有某一列全是同一个值比如某个属性在 17 条样本上取值相同标准差就是 0直接除会出nan然后整个 loss 曲线变成一条nan直线排查起来很折磨。逻辑回归的代码结构和这段几乎一样只换两处y_hat sigmoid(Xb w)梯度里的2/n变成1/n。教材上把逻辑回归的极大似然推导写了两页落到代码上就是这么点差别。3.2 决策树信息增益落成可执行的划分函数决策树的核心是「选哪个特征划分」教材用的是信息增益。先写熵和增益两个纯函数from collections import Counter import numpy as np def entropy(y): 信息熵y 为标签数组底数 2 对应单位为比特 cnt Counter(y) n len(y) return -sum((c / n) * np.log2(c / n) for c in cnt.values()) def info_gain(X, y, feat_idx): 按第 feat_idx 列划分的信息增益 只适用于离散特征连续特征需先按候选切分点二分 base entropy(y) # 划分前的熵 vals np.unique(X[:, feat_idx]) cond 0.0 for v in vals: mask X[:, feat_idx] v cond mask.sum() / len(y) * entropy(y[mask]) return base - cond # 增益 原熵 - 条件熵Counter比np.unique(return_countsTrue)在标签是字符串或混合类型时更省事。mask.sum() / len(y)这个权重不能漏它是每个分支的样本占比漏掉就变成各分支熵的简单平均划分准则就错了而且错得不明显树还是能建出来只是结构不对。递归建树的部分通常长这样def build_tree(X, y, feat_names, depth0, max_depth5): # 停止条件一全同类 if len(np.unique(y)) 1: return {leaf: True, label: y[0]} # 停止条件二特征用尽或达到深度上限 if X.shape[1] 0 or depth max_depth: return {leaf: True, label: Counter(y).most_common(1)[0][0]} # 选增益最大的特征 gains [info_gain(X, y, i) for i in range(X.shape[1])] best int(np.argmax(gains)) if gains[best] 1e-12: # 增益为 0再分也没意义 return {leaf: True, label: Counter(y).most_common(1)[0][0]} node {leaf: False, feat: feat_names[best], children: {}} for v in np.unique(X[:, best]): mask X[:, best] v node[children][v] build_tree( np.delete(X[mask], best, axis1), # 用完的特征要从矩阵里删掉 y[mask], np.delete(feat_names, best), depth 1, max_depth) return nodenp.delete(X[mask], best, axis1)这一步是教材伪代码里「从属性集中去掉已用属性」的直接翻译。忘了删会怎样树会在同一个特征上反复划分深度爆炸最后每个叶子一个样本训练集准确率 100%测试集一塌糊涂。这是手写决策树最高频的错误也是理解「递归下降」最直观的例子。gains[best] 1e-12这个判断处理的是「所有特征增益都是 0 但标签还没纯」的情况理论上不会发生实战中因为浮点误差会出现极小的负数加个阈值兜底。max_depth5是我自己加的参数教材上为了讲清楚剪枝另开一节练习包里未必有。17 条样本的西瓜数据不剪枝也能建完但换成鸢尾花 150 条不限制深度基本必过拟合。3.3 用手写实现和 sklearn 对拍写完手写版最该做的一件事是拿 sklearn 对拍。不是为了证明自己写得对而是为了快速定位「哪一步的理解偏了」from sklearn.model_selection import StratifiedKFold, cross_val_score from sklearn.tree import DecisionTreeClassifier skf StratifiedKFold(n_splits5, shuffleTrue, random_state42) # 分层抽样保证每折类别比例一致小样本必备 scores cross_val_score( DecisionTreeClassifier(criterionentropy, max_depth5, random_state42), X, y, cvskf, scoringaccuracy) print(fsklearn 五折: {scores.mean():.4f} ± {scores.std():.4f})StratifiedKFold而不是普通KFold因为 17 条样本里正负样本本来就不均衡普通划分有可能某一折全是正例准确率就没意义了。shuffleTrue配random_state42是固定划分否则每次跑出来的分数都不一样你会以为是算法不稳其实是数据划分在变。对拍的正确姿势是先保证两边用同一份X、y、同一个max_depth再比准确率。差 10 个点以上说明有实质 bug差 1 到 2 个点通常是平局时选特征的顺序不同属于正常。svg 这类库内部对相同增益的特征有固定的选择规则手写版用argmax取第一个最大值两者可能不一致这时候不要急着改代码先确认数据划分一致。4. 神经网络与集成学习两个最容易被跳过的部分这两章是教材里公式最密的地方也是练习包里最值得逐行读的代码。BP 网络让你看清链式法则在矩阵层面长什么样AdaBoost 让你看清「样本权重」这个抽象概念怎么变成几行乘法。4.1 BP 网络把反向传播写成矩阵乘先看一个单隐层的完整实现import numpy as np def sigmoid(z): return 1.0 / (1.0 np.exp(-z)) def bp_network(X, y, hidden8, lr0.1, epochs5000, seed42): 单隐层 BP 网络二分类 X: (n, d) 已标准化 hidden: 隐层神经元个数 lr: 学习率sigmoid MSE 下不宜超过 0.5 rng np.random.default_rng(seed) n, d X.shape W1 rng.normal(0, 0.5, size(d, hidden)) # 小随机数打破对称 b1 np.zeros((1, hidden)) W2 rng.normal(0, 0.5, size(hidden, 1)) b2 np.zeros((1, 1)) y y.reshape(-1, 1) for _ in range(epochs): # 前向传播 H sigmoid(X W1 b1) # (n, hidden) O sigmoid(H W2 b2) # (n, 1) # 反向传播输出层 delta含 sigmoid 导数 O*(1-O) dO (O - y) * O * (1 - O) # 隐层 delta误差经 W2 回传后再乘本层导数 dH (dO W2.T) * H * (1 - H) # 参数更新除以 n 是对 batch 求平均 W2 - lr * (H.T dO) / n b2 - lr * dO.mean(axis0, keepdimsTrue) W1 - lr * (X.T dH) / n b1 - lr * dH.mean(axis0, keepdimsTrue) return {W1: W1, b1: b1, W2: W2, b2: b2}rng.normal(0, 0.5, ...)里那个 0.5 是初始化尺度。全零初始化会让同一隐层的所有神经元更新完全一致等于只有一个神经元在工作这叫对称失效教材上专门提过。0.5 这个值不算讲究更规范的做法是按sqrt(2/fan_in)之类的公式缩放练习包里简单处理也够用。dO (O - y) * O * (1 - O)这一行是链式法则的落点前面(O - y)是均方误差对输出的偏导后面O * (1 - O)是 sigmoid 的导数。两者相乘才是对W2的梯度中的一部分。如果你只写(O - y)网络照样能「学」但学的是线性回归的梯度输出永远不会被压缩到 (0,1) 区间这是很多人抄代码抄漏的地方。dH (dO W2.T) * H * (1 - H)里dO W2.T是把输出层误差按权重反向分配回隐层形状从(n,1)变成(n,hidden)再逐元素乘隐层激活的导数。矩阵乘和逐元素乘在这里必须分清写成*或用错一个形状可能还能对上结果就是错的而且不会报错。还有一个隐蔽的坑sigmoid 配 MSE 在输出接近 0 或 1 时梯度接近 0训练几千轮 loss 就卡在 0.25 附近不动。改进办法是把输出层损失换成交叉熵此时dO直接等于(O - y)导数项被消掉收敛快很多。练习包里如果只给了 MSE 版本建议自己加一个交叉熵分支对比着跑这是理解「损失函数选择影响梯度」最省时间的实验。4.2 AdaBoost样本权重更新的两行核心集成学习那章最容易看糊的是权重更新落到代码上其实很短import numpy as np from sklearn.tree import DecisionTreeClassifier def adaboost(X, y, n_estimators10): AdaBoost 二分类基学习器用深度 1 的决策树决策 stump y 取值 0/1内部转成 1/-1 参与权重更新 n len(y) w np.full(n, 1.0 / n) # 初始均匀分布 y_signed np.where(y 1, 1, -1) models, alphas [], [] for _ in range(n_estimators): clf DecisionTreeClassifier(max_depth1) clf.fit(X, y, sample_weightw) # 关键把 w 传给 sklearn pred np.where(clf.predict(X) 1, 1, -1) err np.sum(w[pred ! y_signed]) / w.sum() err max(err, 1e-10) # 防 log(0) alpha 0.5 * np.log((1 - err) / err) # 基学习器的话语权 w w * np.exp(-alpha * y_signed * pred) w w / w.sum() # 重新归一化成分布 models.append(clf) alphas.append(alpha) return models, alphassample_weightw是整段的关键。很多人手写 AdaBoost 时自己按权重重采样数据集效果一样但慢而且引入了额外的随机性。sklearn 的树支持加权直接传权重就行划分时用加权基尼或加权熵。这点如果没意识到会觉得「书上说要调分布代码里怎么没见重采样」。err max(err, 1e-10)是防除零。当某个基学习器在训练集上完全正确时err 0log((1-0)/0)直接炸。真实场景里几乎不会发生但如果你用很小的数据做 demo比如 5 条样本就会撞上。w w * np.exp(-alpha * y_signed * pred)这一行是 AdaBoost 的灵魂。y_signed * pred在预测正确时是 1错误时是 -1所以分错的样本权重乘以exp(alpha)被放大分对的乘以exp(-alpha)被缩小。注意符号写成exp(alpha * y * pred)就反了错误样本反而降权准确率会掉到 50% 以下而且不报错只是效果莫名其妙地差。alpha有个众所周知的边界当err 0.5时alpha变负数这个基学习器实际上在起反作用。教材上的处理是把它的权重设为零或者直接重采样重新训练。练习包里如果没有处理跑小数据集时会看到准确率先升后降的曲线这不是实现错是没做这个兜底。4.3 SVM核函数在工程里的正确姿势教材讲 SVM 花了大篇幅在对偶问题和 KKT 条件上代码里通常不会手写 SMO而是调 sklearn。但核函数这块有个必须自己写的环节就是把核矩阵喂进precomputed模式import numpy as np from sklearn.svm import SVC def rbf_kernel(X, Z, gamma1.0): RBF 核矩阵返回 (len(X), len(Z)) gamma 越大单个样本影响范围越小越容易过拟合 # 用平方展开避免构造 (n, m, d) 的三维数组省内存 X2 (X ** 2).sum(axis1)[:, None] Z2 (Z ** 2).sum(axis1)[None, :] d2 X2 Z2 - 2 * X Z.T return np.exp(-gamma * np.maximum(d2, 0)) # 数值误差可能让 d2 略小于 0 K_train rbf_kernel(X_train, X_train) clf SVC(kernelprecomputed, C1.0).fit(K_train, y_train) # 预测时构造 (n_test, n_train) 的核矩阵不是 (n_test, n_test) K_test rbf_kernel(X_test, X_train) pred clf.predict(K_test)np.maximum(d2, 0)这一手是数值保护。X2 Z2 - 2XZ^T这个展开式在数学上恒等于欧氏距离平方但浮点运算下可能出现 -1e-16 这种极小负数exp(-gamma * 负数)会得到略大于 1 的值核矩阵不再半正定sklearn 内部求解可能不收敛。这个问题在你用默认kernelrbf时不会遇到只有走precomputed才需要自己兜。K_test的形状是最容易错的地方。precomputed模式下predict期待的是测试样本和训练样本之间的核形状(n_test, n_train)。写成rbf_kernel(X_test, X_test)形状是(n_test, n_test)样本数和训练集不一样时直接报维度错一样时比如 17 条数据分成 12/5恰好不相等会报错但如果碰巧相等就静默出错预测结果全是乱的。5. 跑不通的七个地方环境、数据和数值的排查清单练习包能不能跑通八成卡在这三类问题上。下面这几条是我在不同机器上反复撞见的按现象、原因、解决写清楚撞上了直接对号入座。5.1 环境与导入装上了却 import 不到现象pip list里明明有scikit-learnimport sklearn还是报ModuleNotFoundError。原因机器上有多个 Python 解释器pip装进了 A运行代码用的是 B。以及虚拟环境忘了激活或者 Notebook 内核选的是系统解释器而不是虚拟环境里的。解决统一用python -m pip install并且用python -c import sys; print(sys.executable)打印当前解释器路径对比pip -V显示的路径是否一致。Jupyter 里还要单独确认内核jupyter kernelspec list把虚拟环境注册成内核后重新选。现象matplotlib 画出来的图中文标题全是一片方块。原因默认字体 DejaVu Sans 不含中文字形不会报错只是渲染成方框。解决在绘图脚本开头统一定字体别每张图单独设import matplotlib.pyplot as plt # Windows 用 SimHeimacOS 用 PingFang SCLinux 按已装字体选 plt.rcParams[font.sans-serif] [SimHei, PingFang SC, DejaVu Sans] plt.rcParams[axes.unicode_minus] False # 负号也会变方块一起关掉5.2 数据与随机性每次跑结果都不一样现象同一个脚本连跑三次测试集准确率在 0.72 到 0.88 之间跳。原因train_test_split没固定random_state加上样本量小几十条一次划分的方差极大。17 条样本按 7:3 分测试集只有 5 条错一条就是 20 个百分点。解决分两层。第一层固定random_state保证结果可复现第二层用StratifiedKFold做五折交叉验证报均值加减标准差而不是报单次划分的分数。小样本上单次划分的数字没有任何参考价值。现象决策树每次建出来的结构都不一样但特征顺序没变。原因DecisionTreeClassifier在特征取值相同、增益并列时内部有随机特征置换没设random_state就会变。另外max_features默认值在分类任务里不是None时也会引入随机性。解决显式写random_state42和max_featuresNone手写版则要保证np.argmax在并列时取第一个两边规则对齐后再比较。5.3 数值与收敛loss 变 nan 或卡住不动现象线性回归或 BP 网络的 loss 打印出来是nan从第一轮就是。原因三个高频来源特征没标准化导致梯度爆炸、学习率过大、除零标准差为 0 的列做了标准化。BP 网络还多一个sigmoid里np.exp(-z)在z是很大的负数时溢出。解决按顺序排查。先打印X.mean()和X.std()确认量纲再打印第一轮的grad范数如果大到 1e6 以上就是学习率问题。sigmoid换成数值稳定的写法def stable_sigmoid(z): 分段计算避免 exp 溢出 out np.empty_like(z, dtypefloat) pos z 0 out[pos] 1.0 / (1.0 np.exp(-z[pos])) ez np.exp(z[~pos]) out[~pos] ez / (1.0 ez) return out现象BP 网络训练几千轮loss 一直停在 0.25 附近准确率 50%。原因输出层用 sigmoid 配均方误差当输出饱和在 0 或 1 附近时梯度接近 0参数几乎不动。初始权重太大也会加速饱和。解决把损失换成交叉熵dO直接写成(O - y)去掉O*(1-O)这一项或者把初始化尺度从 0.5 降到1/sqrt(d)再不行加动量项。三个改法里换损失函数收益最大。现象SVC(kernelprecomputed)在predict时报特征数不匹配。原因传进去的核矩阵形状不对。训练时是(n_train, n_train)预测时必须是(n_test, n_train)。解决把核矩阵的.shape打印出来和clf.shape_fit_对比。这个属性记录了训练时的样本数对不上就是形状错了。养成在precomputed分支里断言形状的习惯比事后 debug 省时间。6. 二次开发把练习脚本改成自己的实验骨架练习包最大的问题是每个脚本自成一体跑完就扔。想拿它做自己的实验第一步是把散落的fit/predict收成统一接口这样换算法只需要换一个类名class BaseModel: 所有手写模型的统一父类配合交叉验证函数使用 def fit(self, X, y): raise NotImplementedError def predict(self, X): raise NotImplementedError def score(self, X, y): return float((self.predict(X) y).mean())有了这层写一个通用的评估函数就能跑遍包里所有模型不用为每个脚本单独写循环def evaluate(model, X, y, n_splits5, seed42): 分层 K 折返回 (mean, std)模型需每次重新实例化 from sklearn.model_selection import StratifiedKFold skf StratifiedKFold(n_splitsn_splits, shuffleTrue, random_stateseed) scores [] for tr, te in skf.split(X, y): m model.__class__(**model.get_params()) # 深拷贝一份避免状态残留 m.fit(X[tr], y[tr]) scores.append(m.score(X[te], y[te])) return float(np.mean(scores)), float(np.std(scores))model.get_params()这一手要求模型实现get_params/set_paramssklearn 的估计器天然支持手写类补上就能混用。m model.__class__(...)而不是直接复用model是为了避免上一折训练出的参数污染下一折这个坑我在早期做对比实验时踩过分数虚高得离谱查了半天才发现是模型没重置。最后给一个我常用的对比表模板把包里的手写实现和 sklearn 版本放一起跑差多少一目了然算法手写实现准确率sklearn 准确率主要差异来源决策树0.76 ± 0.080.79 ± 0.06并列增益时的特征选择顺序AdaBoost0.82 ± 0.070.82 ± 0.07基学习器完全一致无差异BP 网络0.74 ± 0.100.80 ± 0.05初始化、损失函数、迭代轮数表里的数字不重要重要的是「主要差异来源」这一列怎么写。写不出来说明你还没真正搞清楚两个实现的区别。那次我为了让手写决策树和 sklearn 对齐熬到半夜改代码最后发现是两边max_depth一个默认None一个设了 5跟算法本身毫无关系。从那以后我每次做对拍实验都强制先把两边的参数打印成一行贴在一起看确认逐项一致再跑。改练习代码这件事慢就是快希望帮到你。本文还有配套的精品资源点击获取

相关推荐

全卷积网络行人分割实战:Penn-Fudan 数据管线与训练避坑
全卷积网络行人分割实战:Penn-Fudan 数据管线与训练避坑

简介:面向图像分割入门者与行人检测实践者的全卷积网络(FCN)项目资料,围绕 Penn-Fudan Database 展开,可用于理解端到端像素级预测、上采样与跳跃连接等关键机制,并完成从掩模加载、训练到预测的完整流程。… · 2026/11/6 9:01:20

基于dlib与EAR的Python眨眼检测和闭眼时长统计
基于dlib与EAR的Python眨眼检测和闭眼时长统计

简介:面向计算机视觉初学者和希望快速搭建实时眼部状态检测原型的开发者,这套代码包围绕 Python 与 OpenCV 展开,给出人眼定位、眨眼判定和闭眼识别的完整实现,并配套 Ubuntu 环境下的依赖安装、代码运行与原理讲解教程。实现上以… · 2026/11/6 9:01:20

Agent 技术实现全解析:从架构设计到工程落地
Agent 技术实现全解析:从架构设计到工程落地

/* MD / 富文本中的 .toc(含博客园搬家等嵌套结构);.toc-box 在侧栏,不受影响 */#content_views .toc,/* 编辑器常在目录前后插入空 p(:empty 仍占 20px),一并去掉避免顶空隙 */#content_views.markdown_views > p:empty:has(+ .toc),#content_views.markdown_views … · 2026/11/6 9:01:20

微信小程序+物联网打造无人羽毛球馆系统
微信小程序+物联网打造无人羽毛球馆系统

1. 项目概述 作为一名长期从事体育场馆数字化改造的技术人员,最近完成了一个基于微信小程序的无人羽毛球馆系统开发项目。这个系统彻底改变了传统羽毛球馆的运营模式,实现了从场地预约到入场使用的全流程自助化服务。 传统羽毛球馆普遍存在几个痛点&… · 2026/11/6 11:51:35

2025服装电商ERP系统选型与技术架构解析
2025服装电商ERP系统选型与技术架构解析

1. 服装电商ERP行业现状与核心痛点 服装行业作为电商领域最活跃的垂直赛道之一,其ERP系统选型直接影响着企业从生产到销售的全链路效率。2025年的市场数据显示,头部服装电商企业平均每天需要处理超过3000个SKU的进销存管理,这对ERP系统的实时… · 2026/11/6 11:51:35

AI快捷键进阶:五组组合键打通设计操作链
AI快捷键进阶:五组组合键打通设计操作链

大多数人电脑里都存着一张AI(Adobe Illustrator)快捷键大全的截图,两三百个键位,看了几眼就再也没打开过。真正动手的时候,手还是慢:画一个等距排列的图标,一个个复制粘贴再对齐,二十… · 2026/11/6 11:51:35

通信信号接收技术:从匹配滤波器到分集接收
通信信号接收技术:从匹配滤波器到分集接收

1. 通信信号接收的本质挑战 在通信系统设计中,信号接收质量直接决定了信息传输的可靠性。根据信号特性的不同,我们可以将待接收信号分为三类:确定信号、随相信号和起伏信号。这三种信号类型代表了从理想环境到复杂现实场景的过渡,… · 2026/11/6 11:50:33

Python数据分析与可视化:从工具选型到实战避坑的完整指南
Python数据分析与可视化:从工具选型到实战避坑的完整指南

简介:这份PDF面向希望入门或巩固Python数据分析与可视化技能的读者,尤其适合零基础学习者、在校学生及需要快速上手数据处理的职场人士。内容围绕Pandas、NumPy、SciPy、StatModels等数据分析库与Matplotlib、Seaborn、Plotly、Bokeh、Cufflinks等可视化… · 2026/11/6 11:49:33

Redis主从同步机制解析与优化实践
Redis主从同步机制解析与优化实践

1. Redis主从同步的本质与挑战Redis主从同步远非简单的数据拷贝过程,而是一个精心设计的分布式系统状态同步机制。在实际生产环境中,我们常常会遇到这样的场景:一个承载着每秒数万次读写请求的Redis主节点,如何在保持服务不中断的… · 2026/11/6 11:49:33

双极步进电机控制实战:DRV8818PWPR与PIC18F86K22驱动调试
双极步进电机控制实战:DRV8818PWPR与PIC18F86K22驱动调试

/* MD / 富文本中的 .toc(含博客园搬家等嵌套结构);.toc-box 在侧栏,不受影响 */#content_views .toc,/* 编辑器常在目录前后插入空 p(:empty 仍占 20px),一并去掉避免顶空隙 */#content_views.markdown_views > p:empty:has(+ .toc),#content_views.markdown_views … · 2026/11/5 10:27:59

STM32F207ZG + DRV8818PWPR 步进电机驱动实战解析
STM32F207ZG + DRV8818PWPR 步进电机驱动实战解析

/* MD / 富文本中的 .toc(含博客园搬家等嵌套结构);.toc-box 在侧栏,不受影响 */#content_views .toc,/* 编辑器常在目录前后插入空 p(:empty 仍占 20px),一并去掉避免顶空隙 */#content_views.markdown_views > p:empty:has(+ .toc),#content_views.markdown_views … · 2026/11/5 10:28:03

DRV8818PWPR+dsPIC30F4013工业级双极步进电机控制方案
DRV8818PWPR+dsPIC30F4013工业级双极步进电机控制方案

/* MD / 富文本中的 .toc(含博客园搬家等嵌套结构);.toc-box 在侧栏,不受影响 */#content_views .toc,/* 编辑器常在目录前后插入空 p(:empty 仍占 20px),一并去掉避免顶空隙 */#content_views.markdown_views > p:empty:has(+ .toc),#content_views.markdown_views … · 2026/11/6 10:29:23

三菱PLC数据采集到数据库的时序设计:Python+MC协议实战
三菱PLC数据采集到数据库的时序设计:Python+MC协议实战

/* MD / 富文本中的 .toc(含博客园搬家等嵌套结构);.toc-box 在侧栏,不受影响 */#content_views .toc,/* 编辑器常在目录前后插入空 p(:empty 仍占 20px),一并去掉避免顶空隙 */#content_views.markdown_views > p:empty:has(+ .toc),#content_views.markdown_views … · 2026/11/6 1:02:11

ASIC主模式RTL设计:状态机与三态驱动的流片级实现
ASIC主模式RTL设计:状态机与三态驱动的流片级实现

/* MD / 富文本中的 .toc(含博客园搬家等嵌套结构);.toc-box 在侧栏,不受影响 */#content_views .toc,/* 编辑器常在目录前后插入空 p(:empty 仍占 20px),一并去掉避免顶空隙 */#content_views.markdown_views > p:empty:has(+ .toc),#content_views.markdown_views … · 2026/11/6 1:02:11

STM32嵌入式C++开发:GDB调试与Renode仿真实战指南
STM32嵌入式C++开发:GDB调试与Renode仿真实战指南

/* MD / 富文本中的 .toc(含博客园搬家等嵌套结构);.toc-box 在侧栏,不受影响 */#content_views .toc,/* 编辑器常在目录前后插入空 p(:empty 仍占 20px),一并去掉避免顶空隙 */#content_views.markdown_views > p:empty:has(+ .toc),#content_views.markdown_views … · 2026/11/6 1:02:11

双极步进电机控制实战:DRV8818PWPR与PIC18F86K22驱动调试
双极步进电机控制实战:DRV8818PWPR与PIC18F86K22驱动调试

/* MD / 富文本中的 .toc(含博客园搬家等嵌套结构);.toc-box 在侧栏,不受影响 */#content_views .toc,/* 编辑器常在目录前后插入空 p(:empty 仍占 20px),一并去掉避免顶空隙 */#content_views.markdown_views > p:empty:has(+ .toc),#content_views.markdown_views … · 2026/11/5 10:27:59

STM32F207ZG + DRV8818PWPR 步进电机驱动实战解析
STM32F207ZG + DRV8818PWPR 步进电机驱动实战解析

/* MD / 富文本中的 .toc(含博客园搬家等嵌套结构);.toc-box 在侧栏,不受影响 */#content_views .toc,/* 编辑器常在目录前后插入空 p(:empty 仍占 20px),一并去掉避免顶空隙 */#content_views.markdown_views > p:empty:has(+ .toc),#content_views.markdown_views … · 2026/11/5 10:28:03

DRV8818PWPR+dsPIC30F4013工业级双极步进电机控制方案
DRV8818PWPR+dsPIC30F4013工业级双极步进电机控制方案

/* MD / 富文本中的 .toc(含博客园搬家等嵌套结构);.toc-box 在侧栏,不受影响 */#content_views .toc,/* 编辑器常在目录前后插入空 p(:empty 仍占 20px),一并去掉避免顶空隙 */#content_views.markdown_views > p:empty:has(+ .toc),#content_views.markdown_views … · 2026/11/6 10:29:23

了解更多?预约专属演示

我们的顾问将为您一对一讲解产品与方案

企业微信二维码