6.6 模型评估:训练集、测试集与交叉验证

🎯 教学目标与重难点…

【三维目标】

  • 📚 知识目标
    1. 理解为什么要将数据集划分为训练集测试集(防止“作弊”评估)。
    2. 掌握 train_test_split 的标准用法与 random_state 的作用。
    3. 理解K折交叉验证(K-Fold Cross-Validation) 的核心思想:用多轮“模拟考”取代单次“期末考”,使评估更稳定。
    4. 了解分层交叉验证、分组交叉验证和时间序列交叉验证的存在(知道名字和适用场景)。
  • ⚙️ 能力目标
    1. 能使用 sklearn.model_selection.train_test_split 正确划分数据集。
    2. 能使用 cross_val_score 一键完成 K 折交叉验证。
  • 💡 素养目标
    1. 建立“模型评估必须用未见过的数据”的铁律意识(杜绝数据泄露)。
    2. 理解“评估稳定比评估好看更重要”的工程哲学。

【重点与难点】

  • 🟢 教学重点
    1. train_test_split 的使用(参数 test_size, random_state)。
    2. K折交叉验证的概念与 cross_val_score 的使用。
  • 🟡 教学难点
    1. 理解“K折交叉验证”为什么能比单次划分更可靠(减少偶然性)。
    2. 分层交叉验证(StratifiedKFold)在分类问题中的必要性。

📌 一、 课程导入(5 分钟)

情景模拟:考前做模拟卷,到底怎么测才准?

高三学生小明,数学老师给了他一本 “必刷题”题库(共1000道题)

Scenario A(错误做法): 小明把1000道题全部做了一遍,然后拿这1000道题的原题再考自己一次,得了100分。 👉 老师能说“小明高考能考100分”吗? 👉 不能!因为答案都背下来了,这叫“过拟合”!

Scenario B(正确做法): 老师从1000道题里抽出200道作为“模拟考卷”(测试集),小明只能用剩下的800道题(训练集)来练习。最后用那200道从未见过的新题来测试他。 👉 这个分数才接近高考真实水平!

Scenario C(更稳健的做法——多次模拟考): 只考一次模拟考,万一那天小明感冒了,考砸了怎么办?万一出的题正好都是小明擅长的,考高了怎么办? 对策:把题库分成5份,做5次模拟考,每次用不同的一份当考卷,最后取平均分。这就叫 K折交叉验证

引出核心结论:评估一个模型好不好,绝对不能拿训练过的数据来测,必须用“没见过的”数据。而且考一次不准,要多考几次取平均!

  • 👨‍🏫 教师活动:展示“小明刷题”的三步PPT,每讲一步问一句:“你们觉得这个做法公平吗?”
  • 🧑‍🎓 学生活动:学生用“作弊”、“可能偏科”等口语化词汇回应,老师顺势引出专业术语:过拟合、测试集、交叉验证。

用高三学生最熟悉的“模拟考”场景,将“训练/测试/交叉验证”三个抽象概念一步打透。职高学生完全能共情,且能立刻理解“多次取平均”的科学性。


📖 二、 解决问题过程(一):训练集与测试集划分——单次模拟考

1. 核心概念(公式化记忆)

将全部数据按比例切分:训练集(用来学) + 测试集(用来考)

2. sklearn 标准代码(一行搞定)

 1from sklearn.model_selection import train_test_split
 2from sklearn.datasets import load_iris
 3from sklearn.linear_model import LogisticRegression
 4
 5# 加载数据
 6X, y = load_iris(return_X_y=True)
 7
 8# 划分:80% 训练,20% 测试
 9X_train, X_test, y_train, y_test = train_test_split(
10    X, y, 
11    test_size=0.2,        # 测试集占 20%
12    random_state=42       # 随机种子,让每次划分结果固定
13)
14
15print("训练集大小:", X_train.shape[0])  # 120
16print("测试集大小:", X_test.shape[0])   # 30
17
18# 训练模型(只接触训练集)
19model = LogisticRegression(max_iter=1000)
20model.fit(X_train, y_train)
21
22# 评估(用未见过的测试集)
23train_score = model.score(X_train, y_train)  # 训练集得分(通常很高)
24test_score = model.score(X_test, y_test)     # 测试集得分(真实水平)
25print(f"训练集准确率: {train_score:.4f}")
26print(f"测试集准确率: {test_score:.4f}")

3. 两个关键参数(职高必记)

参数 作用 典型取值
test_size 测试集所占比例 0.2(20%)、0.25、0.3
random_state 随机种子(固定划分结果) 任意整数(42是经典值)

4. ⚠️ 三大铁律(课堂高亮)

  1. 测试集绝对不参与训练fit 只用 X_train, y_train)。
  2. random_state 必须固定,否则每次运行结果不同,无法复现实验。
  3. 预处理(标准化/归一化)必须先 fit 训练集,再 transform 测试集(已在6.1强调过,此时再次强调)。
  • 👨‍🏫 教师活动
    1. 在黑板上画一个矩形代表全部数据,用粉笔画出 80%/20% 的分割线。
    2. 演示代码,故意把 random_state 去掉运行两次,让学生看到划分结果不一样,从而理解“固定种子”的必要性。
  • 🧑‍🎓 学生活动
    1. 学生跟着敲代码,观察 train_scoretest_score 的差异(训练集通常接近 1.0,测试集略低)。
    2. 学生尝试修改 test_size=0.5,观察数据集大小变化。

train_test_split 是所有建模的第一步,必须让每个学生闭着眼睛都能敲出来。强调“固定随机种子”的工程习惯,这是企业代码规范的一部分。


💻 三、 解决问题过程(二):K折交叉验证——多次模拟考取平均

1. 单次划分的隐患(继续考试比喻)

  • 隐患:万一测试集里恰好全是“难题”或“简单题”,这次评估就运气成分太大。
  • 解决方案:把数据分成 K 份(比如 5 份),轮流拿其中一份当测试集,其余 K-1 份当训练集,重复 K 次,最后取 K 个分数的平均值

2. 图解 K=5 折交叉验证

1第1轮: [训练] [训练] [训练] [训练] [测试]
2第2轮: [测试] [训练] [训练] [训练] [训练]
3第3轮: [训练] [测试] [训练] [训练] [训练]
4第4轮: [训练] [训练] [测试] [训练] [训练]
5第5轮: [训练] [训练] [训练] [测试] [训练]

每一轮都得到一个准确率,最后算 平均值标准差

3. 代码实战(cross_val_score)

 1from sklearn.model_selection import cross_val_score
 2from sklearn.datasets import load_iris
 3from sklearn.linear_model import LogisticRegression
 4
 5X, y = load_iris(return_X_y=True)
 6model = LogisticRegression(max_iter=1000)
 7
 8# 5 折交叉验证:自动划分 5 次,返回 5 个分数
 9scores = cross_val_score(model, X, y, cv=5)
10
11print("5 次评估分数:", scores)          # 例如 [0.9667, 1.0, 0.9333, 0.9667, 1.0]
12print("平均准确率:", scores.mean())     # 0.9733
13print("标准差:", scores.std())          # 0.0249(分数波动越小越稳定)

4. 交叉验证 vs 单次划分(核心对比)

对比维度 单次 train_test_split K折交叉验证
评估次数 1 次 K 次(通常 5 或 10)
稳定性 容易受一次运气影响 更稳定、更可靠
计算量 小(只训练 1 次) 大(训练 K 次)
适用场景 数据量极大时快速验证 数据量中等,追求可靠评估
职高推荐 初学先用这个 调参优化时再用
  • 👨‍🏫 教师活动
    1. 在黑板上画出 5 个方框的轮流示意图,用手依次指“哪份是测试集”。
    2. 运行 cross_val_score,打印 scores 数组,让学生看到 5 个不同的分数,然后算平均。
  • 🧑‍🎓 学生活动
    1. 学生修改 cv=10(10折),观察分数变成了 10 个,平均分可能更接近真实水平。
    2. 学生讨论:“如果 5 次分数波动很大(比如 0.5 到 1.0),说明什么?”(答案:说明模型不稳定,或者数据太少了)。

通过“5份轮流当考卷”的可视化示意图,让K折交叉验证的原理一目了然。学生不需要记复杂的交叉验证类(如KFold),只需掌握 cross_val_score 这个“一键函数”即可。


📖 四、 解决问题过程(三):拓展视野——其他交叉验证策略

以下内容属于**“听说过,有个印象”**级别,不需要全部记住,但需要知道“某些特殊场景不能用普通的 K 折”。

1. 6.4.3 分层交叉验证(Stratified K-Fold)——分类问题专用

  • 问题:如果数据中正类只有 10%,负类 90%,随机划分可能导致某一折里全是正类或全是负类。
  • 对策:分层交叉验证保证每一折中各类别的比例与原始数据集一致
  • 代码cross_val_score(model, X, y, cv=5) 在分类问题中默认就是分层的,所以不用额外操心!

2. 6.4.4 分组交叉验证(Group K-Fold)——同一来源不能分开

  • 场景:100 张人脸照片来自 10 个人,每人 10 张。如果把同一个人的照片既放训练集又放测试集,模型相当于“认得这个人”再考他,数据泄露了!
  • 对策:分组交叉验证确保同一个人的照片不会同时出现在训练集和测试集中。
  • 代码GroupKFold(n_splits=5)(知道有这个东西就行)。

3. 6.4.5 时间序列交叉验证(TimeSeriesSplit)——时间顺序不能打乱

  • 场景:预测股票价格、天气预报、销量预测。未来的数据不能用来预测过去
  • 对策:用时间序列交叉验证,只往前看,不往后看
  • 代码TimeSeriesSplit(n_splits=5)(知道有这个东西就行)。

⚠️ 职高学生记住这句话就够了:

“默认的 5 折交叉验证能满足 90% 的场景。如果数据是分类(分层),sklearn 自动帮你做了。如果数据是人脸/用户ID(分组)或者股票日期(时间序列),请告知业务专家,需要特殊处理。”

  • 👨‍🏫 教师活动:用 PPT 快速展示三个特殊场景的配图(同一个人脸出现在训练和测试集、用未来预测过去),用 3 分钟快速过一遍。
  • 🧑‍🎓 学生活动:学生只需在笔记本上写下:“普通场景用 KFold,分类自动分层;分组和时间序列不能乱划分”。

这部分内容对职高学生属于“科普级”。不需要写代码,只需要建立“有的数据不能随便乱分”的风险意识,将来工作中遇到这类数据时知道去查文档,而不是乱用。


✍️ 五、 解决问题过程(四):课堂实战——手动实现一次交叉验证流程

📝 任务一:房价预测模型评估(单次 vs 交叉验证对比)…

背景与题目: 使用 sklearn 自带的 load_diabetes 糖尿病数据集(10 个特征,回归问题),使用线性回归模型,分别用:

  1. 单次 train_test_split(测试集 20%)计算 R² 分数。
  2. 5 折交叉验证计算 R² 分数的平均值。

对比两种评估方式的差异。

 1from sklearn.datasets import load_diabetes
 2from sklearn.linear_model import LinearRegression
 3from sklearn.model_selection import train_test_split, cross_val_score
 4import numpy as np
 5
 6# 加载数据
 7X, y = load_diabetes(return_X_y=True)
 8model = LinearRegression()
 9
10# 方法1:单次划分
11X_train, X_test, y_train, y_test = train_test_split(
12    X, y, test_size=0.2, random_state=42
13)
14model.fit(X_train, y_train)
15single_score = model.score(X_test, y_test)
16
17# 方法2:5折交叉验证
18cv_scores = cross_val_score(model, X, y, cv=5, scoring='r2')
19
20print(f"单次测试集 R²: {single_score:.4f}")
21print(f"5折交叉验证 R² 列表: {cv_scores}")
22print(f"交叉验证平均 R²: {cv_scores.mean():.4f} (+/- {cv_scores.std():.4f})")
🔍 查看参考结果与解析…

运行结果示例

1单次测试集 R²: 0.4523
25折交叉验证 R² 列表: [0.4856, 0.4231, 0.5012, 0.3897, 0.4125]
3交叉验证平均 R²: 0.4424 (+/- 0.0458)

解析

  • 单次划分 0.4523 和交叉验证平均 0.4424 比较接近,说明这次划分比较“公平”。
  • 但注意交叉验证的 5 个分数从 0.3897 到 0.5012,波动较大,说明这个模型在不同数据子集上表现不太稳定,可能存在某些特殊样本。
  • 结论:交叉验证提供了更全面的评估视野,能发现“模型在某些情况下会变差”的风险。

📝 六、 课堂小结(5 分钟)

flowchart LR
    root["📊 6.4 模型评估方法论"]

    subgraph C1 ["📌 单次划分 train_test_split"]
        direction TB
        A1["训练集 (80%)"]
        A2["测试集 (20%)"]
        A3["⚠️ 测试集绝不参与训练!"]
    end

    subgraph C2 ["🔄 K折交叉验证 cross_val_score"]
        direction TB
        B1["数据分 K 份"]
        B2["轮流当测试集"]
        B3["评估结果 = K个分数的平均值"]
    end

    subgraph C3 ["🔧 特殊变种(拓展)"]
        direction TB
        C1_node["StratifiedKFold(分类自动分层)"]
        C2_node["GroupKFold(同一来源不能拆分)"]
        C3_node["TimeSeriesSplit(时间顺序不能乱)"]
    end

    root --> C1
    root --> C2
    root --> C3

    style root fill:#4b6cb7,stroke:#253b6e,color:#fff
    style C1 fill:#e3f2fd,stroke:#2196f3
    style C2 fill:#e8f5e9,stroke:#4caf50
    style C3 fill:#f3e5f5,stroke:#9c27b0

✏️ 随堂检测与互动练习

点击展开:随堂测试题(带解析)

一、 单选题

  1. train_test_split 中设置 random_state=42 的主要目的是什么?
  • A. 让测试集占 42%
  • B. 让每次运行代码时的数据划分结果固定不变
  • C. 随机删除 42 个样本
  • D. 将数据按 4:2 比例划分
【答案】

【解析】Brandom_state 是随机种子,固定后每次运行划分结果一样,便于实验复现。

  1. K折交叉验证中,如果设置 cv=5,模型会被训练多少次?
  • A. 1 次
  • B. 2 次
  • C. 4 次
  • D. 5 次
【答案】

【解析】D。K折交叉验证会进行 K 轮训练和评估,每轮用不同的数据子集作为测试集。

  1. 以下哪个场景不适合使用普通的随机 K 折交叉验证?
  • A. 鸢尾花分类(150 个样本,3 类)
  • B. 房价预测(1000 个样本,10 个特征)
  • C. 股票价格预测(按日期排列的 5000 个交易日数据)
  • D. 手写数字识别(1797 个样本,64 维)
【答案】

【解析】C。股票价格是时间序列数据,未来的数据不能用于预测过去,必须使用时间序列专用的交叉验证,打乱顺序会造成数据泄露。

二、 代码填空题

题目:现有 X, y 数据,请补全代码完成 5 折交叉验证,并打印平均准确率。

1from sklearn.model_selection import cross_val_score
2from sklearn.tree import DecisionTreeClassifier
3
4model = DecisionTreeClassifier()
5
6# 请补全代码
7scores = _________________________________
8print(f"平均准确率: {scores.mean():.4f}")
【答案】

cross_val_score(model, X, y, cv=5)


📮 七、 课后作业与拓展

📮 课后作业…
  1. 基础代码题:加载 load_wine 葡萄酒数据集,使用逻辑回归模型,分别用 test_size=0.3 的单次划分和 5 折交叉验证进行评估,对比两种方法得到的准确率差异。
  2. 思考题:如果数据量很小(比如只有 50 个样本),用 test_size=0.3(即 15 个测试样本)评估模型,结果可信吗?应该用什么方法?(提示:交叉验证更合适,因为每个样本都能轮流当测试集)。
  3. 辨析题:以下操作会导致“数据泄露”吗?请说明原因。
    • (a) 先对全部 1000 个样本做标准化,再划分训练集和测试集。
    • (b) 先划分训练集和测试集,再用训练集的均值去标准化测试集。
    • (c) 在交叉验证的每一轮中,用当轮训练集的均值去标准化当轮的测试集。
  4. 预习任务:从下一章(第8章)起,我们将学习具体的回归算法评估指标(MSE、MAE、R²)。预习教材 8-2 机器学习模型评估(回归指标)。
  5. 职高衔接拓展:你在一家医疗 AI 公司工作,公司只有 200 份癌症筛查数据(非常珍贵)。老板要求评估模型效果,你会建议用单次划分还是交叉验证?为什么?(答案:交叉验证,因为数据少,交叉验证能让每个样本都被测试到,评估更充分利用有限数据)。

📋 八、 板书设计

🛠️ 板书设计…
 1第六章 模型评估
 26.4 训练集、测试集与交叉验证
 3
 4一、为什么要划分?
 5   -> 不能用训练过的数据去评估(那是作弊!)
 6   -> 必须用“没见过的”数据测试真实水平
 7
 8二、单次划分(train_test_split)
 9   from sklearn.model_selection import train_test_split
10   X_train, X_test, y_train, y_test = train_test_split(X, y, test_size=0.2, random_state=42)
11   - test_size: 测试集比例(0.2 ~ 0.3)
12   - random_state: 固定种子,复现结果
13
14三、多次模拟考(K折交叉验证)
15   from sklearn.model_selection import cross_val_score
16   scores = cross_val_score(model, X, y, cv=5)
17   - 分 K 份,轮流当测试集
18   - 最终得分 = scores.mean()(更稳定可靠)
19
20四、拓展(特殊场景)
21   - 分类问题 -> 自动分层(StratifiedKFold)
22   - 同人照片/用户ID -> 分组交叉验证(GroupKFold)
23   - 时间序列(股票/天气)-> 时间序列交叉验证(TimeSeriesSplit)

本课用到的单词

单词 发音 专业英语解释(中文)
Train-Test Split /treɪn test splɪt/ 训练测试集划分。将数据集随机分为训练集和测试集的过程。
Test Size /test saɪz/ 测试集比例。分配给测试集的数据占总数据的百分比。
Random State /ˈrændəm steɪt/ 随机种子。控制随机过程的整数,固定后可复现结果。
Cross-Validation /krɔːs ˌvæljuˈeɪʃən/ 交叉验证。通过多次划分数据并轮流评估来获得稳定模型性能的方法。
K-Fold /keɪ foʊld/ K折。将数据分为 K 等份,每次用 1 份作测试集、K-1 份作训练集。
Mean Score /miːn skɔːr/ 平均分。K 折交叉验证中 K 次评估分数的算术平均值。
Data Leakage /ˈdeɪtə ˈliːkɪdʒ/ 数据泄露。测试集信息在训练过程中被意外引入,导致评估结果虚高的问题。