Skip to content

机器学习的一次完整流程:准确率 96.7% 的模型未必最好 ​

在鸢尾花数据集上,k=1 的 KNN 测试准确率 96.7%,看起来比 k=5 的 93.3% 更好。但换 20 种随机划分再跑一遍,k=1 的成绩在 86.7% 到 100% 之间来回跳。单次划分的测试成绩噪声很大,拿它来挑参数,挑出来的多半是运气。

本文用一个最小的分类任务走完「切分 → 预处理 → 训练 → 调参 → 评估」的流程,重点放在最容易做错的几步。所有数字都在 Python 3、scikit-learn 1.9.1 上实测,数据集是 scikit-learn 自带的鸢尾花(150 条样本、4 个特征、3 个类别),不需要下载。

一、先说结论 ​

  • 测试集要在最开始切出去,只在最后用一次。用它挑参数,测试成绩就不再是「没见过的数据」上的成绩。
  • 调参用交叉验证:训练集内部轮流留出一折做验证。实测 k=1 的单次测试成绩最高,交叉验证却选出 k=5。
  • 预处理要放进 Pipeline:标准化的均值和方差只能从训练集算。把一个特征的单位放大 1000 倍,不做标准化的 KNN 准确率从 93.3% 掉到 66.7%。
  • 训练准确率 100% 不是好消息:k=1 时每个样本最近的邻居就是它自己,训练成绩必然满分。
  • 简单数据集上模型差别不大:KNN、逻辑回归、决策树的交叉验证成绩都是 96.0%。数据和评估方法对结果的影响,往往比换模型大。

二、流程全貌 ​

150 条样本4 个特征训练集 120 条测试集 30 条先锁起来只在训练集上进行Pipeline:标准化 → KNN5 折交叉验证选 k最终模型k = 5只评估一次:准确率 0.933常见错误:先在全量数据上标准化再切分、用测试集挑参数——测试成绩因此偏乐观上线时要把整个 Pipeline 一起导出,否则线上特征处理和训练不一致
图 1 · 测试集在最开始就切出去,直到最后才用一次;标准化和模型放进同一个 Pipeline,调参只在训练集上用交叉验证完成

对应的代码很短,关键在于把标准化和模型包进同一个 Pipeline:

python
from sklearn.datasets import load_iris
from sklearn.model_selection import train_test_split, GridSearchCV
from sklearn.pipeline import make_pipeline
from sklearn.preprocessing import StandardScaler
from sklearn.neighbors import KNeighborsClassifier

X, y = load_iris(return_X_y=True)

# 1. 最先切出测试集;stratify 保证三个类别在两边比例一致
X_train, X_test, y_train, y_test = train_test_split(
        X, y, test_size=0.2, random_state=42, stratify=y)

# 2. 标准化和模型放在一起,交叉验证时每一折都只用该折的训练部分计算均值方差
pipeline = make_pipeline(StandardScaler(), KNeighborsClassifier())

# 3. 只在训练集上用 5 折交叉验证选参数
search = GridSearchCV(pipeline, {"kneighborsclassifier__n_neighbors": [1, 3, 5, 7, 9, 15, 25, 50]}, cv=5)
search.fit(X_train, y_train)
print(search.best_params_)               # {'kneighborsclassifier__n_neighbors': 5}

# 4. 测试集只用这一次
print(search.score(X_test, y_test))      # 0.933

环境准备只需要一个虚拟环境:python3 -m venv venv && ./venv/bin/pip install scikit-learn。NumPy、SciPy 会作为依赖一起安装。

三、实验一:单次划分的成绩不可信 ​

同一次划分下,逐个 k 值对比训练集准确率、测试集准确率和训练集上的 5 折交叉验证准确率:

k训练集测试集(30 条)5 折交叉验证
11.0000.9670.942 ± 0.042
30.9580.9330.958 ± 0.037
50.9750.9330.967 ± 0.031
70.9750.9670.958 ± 0.037
90.9580.9670.958 ± 0.026
150.9670.9670.958 ± 0.026
250.9580.9000.900 ± 0.033
500.8830.8330.858 ± 0.033
1.000.900.80k=1k=3k=5k=7k=9k=15k=25k=50训练集准确率5 折交叉验证准确率记住了训练集最佳区间欠拟合横轴不是等距刻度,只是按实验中的 k 值依次排列
图 2 · k=1 时训练准确率是 100%,因为每个样本最近的邻居就是它自己;交叉验证在 k=5 附近最高,k 再大就开始欠拟合

如果只看测试集,k=1、7、9、15 并列第一,很可能随手选了 k=1。但测试集只有 30 条,错一条就是 3.3 个百分点。把随机种子从 0 换到 19,重复 20 次划分,k=1 的测试准确率最低 0.867、最高 1.000、平均 0.953。单次测试成绩里差出来的那一两条,几乎全是划分方式带来的噪声。

交叉验证把训练集分成 5 份,轮流用 4 份训练、1 份验证,取平均。它用满了训练数据,还能给出波动范围(表中的 ± 是 5 折之间的标准差)。数据量小的时候,比较参数和模型都应该看交叉验证,测试集只用来报告最终成绩。

表里也能看到两种失败:

  • 过拟合:k=1 时训练准确率恒为 1.000,模型只是记住了训练集。
  • 欠拟合:k=50 时每次预测都要参考 50 个邻居,而训练集每类只有 40 条,边界被抹平,训练和验证成绩一起下降。

四、实验二:为什么要标准化,以及在哪里标准化 ​

KNN 按欧氏距离找邻居,数值范围大的特征会主导距离。鸢尾花的四个特征都是厘米,差异还不明显。模拟一次「单位不一致」:把花萼宽度乘以 1000,相当于这一列改用微米记录。

同一份数据、k=5测试准确率
不做标准化0.667
标准化后再训练0.933

不做标准化时,距离几乎只由被放大的那一列决定,而它恰好是四个特征里区分度最低的一个。业务数据里这种情况很常见,比如「金额(分)」和「评分(1–5)」放在一起。

标准化本身也会造成泄漏。常见的写法是先对全量数据 fit_transform,再切分训练集和测试集。这样算出来的均值和方差包含了测试集的信息,测试成绩因此偏乐观。把 StandardScaler 放进 Pipeline 以后,fit 只会看到训练部分,交叉验证的每一折也是如此。

五、实验三:准确率之外看混淆矩阵 ​

k=5 的模型在测试集上的混淆矩阵(行是真实类别,列是预测类别):

预测 Setosa预测 Versicolor预测 Virginica
真实 Setosa1000
真实 Versicolor0100
真实 Virginica028

准确率是 28 / 30 = 0.933,但错误全部集中在一个方向:两朵 Virginica 被判成了 Versicolor。分到类别上看:

  • Versicolor 的精确率 10 / 12 = 0.833(预测为它的样本里有两个不是),召回率 10 / 10 = 1.000。
  • Virginica 的精确率 8 / 8 = 1.000,召回率 8 / 10 = 0.800(有两个没找出来)。

这就是只看准确率的风险。在业务里,把欺诈交易判成正常和把正常交易判成欺诈,代价完全不同。选指标之前先问清楚哪一类错误更贵:漏报贵就盯召回率,误报贵就盯精确率,两者都要兼顾时看 F1。类别不平衡时(比如 99% 的交易都正常),准确率几乎没有参考价值。

六、实验四:换模型的收益 ​

在全部 150 条数据上做 5 折交叉验证:

模型交叉验证准确率
KNN(k=5,标准化)0.960
逻辑回归(标准化)0.960
决策树(不限深度)0.960
决策树(最大深度 2)0.933

三种原理完全不同的模型成绩相同。数据简单、特征有效的时候,模型选择不是主要矛盾。反过来,特征没有区分度的时候,换什么模型都救不回来。实验二里被放大的那一列,就是一个没区分度却被当成主力特征的例子。

七、放到工程里 ​

对服务端来说,一个训练好的模型就是一个函数加一份评估报告。接入时要关心的是这几件事:

  • 训练和线上的特征处理必须一致。标准化参数、编码方式、缺失值填充都属于模型的一部分,要和模型一起导出、一起版本化。线上重写一遍特征处理逻辑,是「离线效果好、线上效果差」的常见原因。
  • 评估集要固定下来。每次重新训练或调整参数,都在同一份评估集上跑,结果才可比。这和大模型接入时的评测集是同一个思路。
  • 上线后持续监控输入分布。模型只对和训练数据相似的输入负责,业务变化导致特征分布漂移时,准确率会悄悄下降。
  • 先做一个简单的基线。逻辑回归或规则系统的成绩就是后续复杂方案要超过的底线,也是复杂方案出故障时的降级方案。

八、常见误区 ​

  • 「测试集成绩最高的参数就是最好的」:测试集参与了选择,就不再能代表未见过的数据。
  • 「先标准化全量数据,再切分」:测试集的统计信息泄漏到了训练过程中。
  • 「训练准确率 100%,模型很好」:更可能是记住了训练集,要看验证成绩。
  • 「准确率高就行」:类别不平衡或错误代价不对称时,要看精确率、召回率和混淆矩阵。
  • 「效果不好就换更复杂的模型」:先检查特征、数据质量和评估方法。

小结 ​

机器学习流程里最容易出问题的不是模型本身,而是评估:测试集要最先切出、最后使用,调参用交叉验证,预处理放进 Pipeline,指标按错误代价来选。鸢尾花的实验里,这几条规则分别对应一个具体的数字:单次划分 86.7%~100% 的波动、66.7% 对 93.3% 的标准化差距、两个集中在同一方向的误判。

文本数据怎么变成模型输入,以及从词袋到 Transformer 的演进,见 从词袋到 Transformer。


配套实验

参考资料

文章以 CC BY-NC-SA 4.0 授权 · 代码片段以 MIT 授权