Digits 候选模型公平比较
统一比较 Dummy、标准化逻辑回归、标准化 KNN、决策树以及随机森林或 SVM。
学习前需要掌握
背景与问题
模型比较不是把几次随意运行的最高分放在一张表里。候选看到的数据、验证折、指标和调参机会不同,结果就无法公平解释,尤其容易把划分波动误当作算法差异。
Digits 的像素尺度相同,但每个像素的分布不同。标准化会改变 KNN 和 SVM 的距离,也会改变逻辑回归的正则化语义;这是需要在协议内验证的设计,不是无条件的必选结论。
概念定义
模型注册表让所有候选共享同一 CV 与指标,同时保留各自必要的 Pipeline。
直观理解
把项目当作一条可复现、可审计的实验链,而不是追求一次最高分。
核心原理
先定义候选注册表,每个条目都是可以独立 fit/predict 的完整流程。Dummy 提供无信息基线,逻辑回归检查线性边界,KNN 检查邻域相似,树检查分段规则,SVM 或随机森林提供额外非线性候选。
所有候选共享开发集与保存的五折索引,macro F1 用于主要选择,同时报告训练分数、逐折分布与耗时。需要学习统计量的缩放放在 Pipeline 内;树通常不需要靠标准化改变阈值分裂语义。
固定默认配置的初筛与后续参数搜索要分开记录。KNN 的邻居数、树深、SVM 的 C 等参数影响容量;扩大某个候选的搜索预算却不给其他候选相近机会,会混合算法差异与选择预算差异。
训练分数接近满分而验证较低可能提示过拟合,二者都低则还要检查表示与容量。分数接近时应看成对折差异、推理成本和错误类别,不能凭第三位小数宣称确定优劣;折间标准差也不是独立置信区间。
选型阶段不读取保留测试结果。排行榜锁定后再做最终评价,不能在测试集上循环比较所有候选再挑最高者,也不能把小型 Digits 上的排序当作所有图像任务的通用排序。
数学表达
项目主线
这里表示选型在项目中的位置。公平性来自相同数据边界、指标和选择预算,不由箭头本身或候选数量保证。
变量含义
变量定义与本页概念定义一致,代码中的同名变量保持相同语义。
计算步骤
- 1注册 Dummy
- 2LR Pipeline
- 3KNN Pipeline 并比较 K
- 4树比较深度
代码对应位置
示例 1共享五折的五类候选比较:固定数据、折索引与候选配置,完整运行开发比较;不使用测试结果选模型,也不声称跨任务基准优势。
案例:同一份开发集比较五种固定候选
代码对 1437 行开发数据保存一组五折,分别运行 Dummy、标准化逻辑回归、标准化 KNN、深度受限树和标准化 RBF SVM。所有配置预先写定,输出训练与验证 macro F1,以及验证折间标准差。
cross_validate 还返回 fit_time、score_time,可在当前机器报告成本,但这里不把机器相关耗时写成固定预期。代码未预测测试集;如果接下来增加超参数候选,应作为新的开发实验记录,而不是暗中替换排行榜。
计算与实现步骤
- 注册 Dummy
- LR Pipeline
- KNN Pipeline 并比较 K
- 树比较深度
- 增加 RF/SVM
- 同一五折 cross_validate
- 锁定最终模型
代码实现
示例 1
共享五折的五类候选比较
example_01.py用途:固定数据、折索引与候选配置,完整运行开发比较;不使用测试结果选模型,也不声称跨任务基准优势。
import numpy as np
from sklearn.datasets import load_digits
from sklearn.dummy import DummyClassifier
from sklearn.linear_model import LogisticRegression
from sklearn.model_selection import StratifiedKFold, cross_validate, train_test_split
from sklearn.neighbors import KNeighborsClassifier
from sklearn.pipeline import make_pipeline
from sklearn.preprocessing import StandardScaler
from sklearn.svm import SVC
from sklearn.tree import DecisionTreeClassifier
X, y = load_digits(return_X_y=True)
train, test = train_test_split(np.arange(len(y)), test_size=0.2, stratify=y, random_state=42)
X_dev, y_dev = X[train], y[train]
folds = list(StratifiedKFold(5, shuffle=True, random_state=42).split(X_dev, y_dev))
models = {
"dummy": DummyClassifier(strategy="most_frequent"),
"lr": make_pipeline(StandardScaler(), LogisticRegression(max_iter=3000)),
"knn": make_pipeline(StandardScaler(), KNeighborsClassifier(n_neighbors=5)),
"tree": DecisionTreeClassifier(max_depth=8, random_state=42),
"svm": make_pipeline(StandardScaler(), SVC(C=10.0, kernel="rbf", gamma="scale")),
}
results = {}
for name, model in models.items():
result = cross_validate(model, X_dev, y_dev, cv=folds,
scoring="f1_macro", return_train_score=True)
results[name] = result
print(f"{name}: train={result['train_score'].mean():.3f}, cv={result['test_score'].mean():.3f}, std={result['test_score'].std(ddof=1):.3f}")
winner = max(results, key=lambda name: results[name]["test_score"].mean())
print("selected_from_development:", winner)
print("untouched_test_rows:", len(test))代码解析
解析始终位于完整代码下方,并按实际代码段逐项对应。
输入数据与任务
固定数据、折索引与候选配置,完整运行开发比较;不使用测试结果选模型,也不声称跨任务基准优势。
X- 完整特征矩阵,每行一个样本、每列一个特征。
y- 目标标签或连续目标值。
model- 组合预处理和估计器的模型对象。
mean- 训练特征逐列均值。
std- 训练特征逐列标准差。
Step 1 · 1–10 行
导入当前步骤需要的数值计算、预处理、模型或评价工具。依赖集中写在代码开头,便于复现。
import numpy as np
from sklearn.datasets import load_digits
from sklearn.dummy import DummyClassifier
from sklearn.linear_model import LogisticRegression
from sklearn.model_selection import StratifiedKFold, cross_validate, train_test_split
from sklearn.neighbors import KNeighborsClassifier
from sklearn.pipeline import make_pipeline
from sklearn.preprocessing import StandardScaler
from sklearn.svm import SVC
from sklearn.tree import DecisionTreeClassifierStep 2 · 12–31 行
按固定协议切分训练与测试数据;此处的数据边界决定后续哪些样本可以参与 fit。
X, y = load_digits(return_X_y=True)
train, test = train_test_split(np.arange(len(y)), test_size=0.2, stratify=y, random_state=42)
X_dev, y_dev = X[train], y[train]
folds = list(StratifiedKFold(5, shuffle=True, random_state=42).split(X_dev, y_dev))
models = {
"dummy": DummyClassifier(strategy="most_frequent"),
"lr": make_pipeline(StandardScaler(), LogisticRegression(max_iter=3000)),
"knn": make_pipeline(StandardScaler(), KNeighborsClassifier(n_neighbors=5)),
"tree": DecisionTreeClassifier(max_depth=8, random_state=42),
"svm": make_pipeline(StandardScaler(), SVC(C=10.0, kernel="rbf", gamma="scale")),
}
results = {}
for name, model in models.items():
result = cross_validate(model, X_dev, y_dev, cv=folds,
scoring="f1_macro", return_train_score=True)
results[name] = result
print(f"{name}: train={result['train_score'].mean():.3f}, cv={result['test_score'].mean():.3f}, std={result['test_score'].std(ddof=1):.3f}")
winner = max(results, key=lambda name: results[name]["test_score"].mean())
print("selected_from_development:", winner)
print("untouched_test_rows:", len(test))代码与数学原理
减去训练均值并除以训练标准差,对应代码中的 mean/std 或 StandardScaler。
预期输出或运行结果
本地 scikit-learn 1.8.0 的核对输出;分数仅属于下方固定教学实验: dummy: train=0.018, cv=0.018, std=0.000 lr: train=1.000, cv=0.968, std=0.008 knn: train=0.984, cv=0.976, std=0.011 tree: train=0.939, cv=0.838, std=0.028 svm: train=1.000, cv=0.980, std=0.007 selected_from_development: svm untouched_test_rows: 360
常见错误 · 4 条
- 不要在测试集上调用 fit 或 fit_transform;测试集只能复用训练参数。
- 不同候选使用不同随机划分、不同样本清洗或不同主指标。
- 在 CV 前全数据缩放,或只给某一模型大量调参机会却声称比较完全公平。
- 按测试成绩选冠军,或把训练高分、第三位小数差异当作可靠胜出证明。
实际应用
- 多模型实验
- 算法选型
常见错误
- 不同候选使用不同随机划分、不同样本清洗或不同主指标。
- 在 CV 前全数据缩放,或只给某一模型大量调参机会却声称比较完全公平。
- 按测试成绩选冠军,或把训练高分、第三位小数差异当作可靠胜出证明。
推荐学习资料
参考库不会生成虚假资源或无效外部链接。