1304 综合练习

Part A:给定数据集的精讲精练(共 4 题)

练习 1:【手把手入门】“品酒师助手”—— 葡萄酒数据集三模型基准测试

🎯 目标:跑通决策树、随机森林、线性 SVM 的完整代码,建立“准确率 + 训练时间”的双重评估习惯。 📂 数据集sklearn.datasets.load_wine(葡萄酒化学成分 -> 3个品种分类)

【详细操作步骤(Step-by-Step)】

  1. 导入模块:导入 numpypandasmatplotlib,以及 sklearn 下的 datasetsmodel_selectiontreeensemblesvmmetrics
  2. 加载数据:使用 load_wine(return_X_y=True) 获取特征 X 和标签 y。打印 X.shape 观察有多少个样本和特征。
  3. 划分数据集:使用 train_test_split,设置 test_size=0.3random_state=42(保证每次结果一致)。
  4. 初始化三个模型(均使用默认参数)
    • DecisionTreeClassifier(random_state=42)
    • RandomForestClassifier(n_estimators=100, random_state=42)
    • SVC(kernel='linear', random_state=42) —— 注意:线性核在特征较少的化学数据上通常表现极佳。
  5. 训练并计时
    • 使用 import time,在 model.fit() 前后记录 time.time(),计算训练耗时。
  6. 评估并记录
    • 分别计算三个模型在测试集上的 accuracy_score
  7. 输出对比表格(打印美观):
    • 使用 pandas.DataFrame 制作一张表格,列名为 ['模型', '测试准确率', '训练耗时(秒)']
  8. 结论:观察哪个模型在这个小数据集上“性价比”最高。

练习 2:【手把手入门】“线性不可分魔咒”—— 月亮形状数据与 RBF 核初探

🎯 目标:直观感受线性核的局限性,掌握 gamma 参数对决策边界“平滑度”的影响。 📂 数据集sklearn.datasets.make_moons(生成两个交错的半月形,典型线性不可分)

【详细操作步骤(Step-by-Step)】

  1. 生成数据X, y = make_moons(n_samples=300, noise=0.15, random_state=42)
  2. 划分数据X_train, X_test, y_train, y_test = train_test_split(...)
  3. 定义可视化函数(可直接复用课堂代码):
    • 函数名 plot_svm_boundary(clf, X, y, title),内部使用 meshgrid 绘制决策背景色,并高亮支持向量。
  4. 实验 1(线性核 - 对照组)
    • 训练 SVC(kernel='linear', C=1)
    • 调用可视化函数,观察模型如何“硬切”半月形,导致大量蓝色点落入红色区域。
  5. 实验 2(RBF 核 - 实验组,调整 gamma)
    • 分别训练三个 RBF 核模型:
      • SVC(kernel='rbf', gamma=0.1, C=1)
      • SVC(kernel='rbf', gamma=1.0, C=1)
      • SVC(kernel='rbf', gamma=100, C=1)
    • 并排绘制三张图 + 线性核的图(共 4 张子图)。
  6. 填写观察日志
    • 记录每个模型的测试集准确率。
    • 现象描述gamma=0.1 时边界太____(平滑/扭曲),gamma=100 时每个点周围出现“”(气泡/直线),说明模型(过/欠)拟合。
  7. 结论:对于月亮数据,推荐将 gamma 设置在哪个数量级(如 0.1 ~ 1 还是 10 ~ 100)?

练习 3:【独立实战】“癌症筛查调参战”—— 乳腺癌数据的 GridSearch 网格搜索

🎯 目标:脱离“一步步指导”,学会使用 GridSearchCV 自动寻找最优的 Cgamma 组合。 📂 数据集sklearn.datasets.load_breast_cancer(569 个样本,30 个特征)

任务要求

  1. 加载数据,不做标准化(SVM 对尺度敏感,但本练习暂不引入标准化,只做调参观察)。
  2. 仅使用 RBF 核 SVM
  3. 设定参数候选字典:param_grid = {'C': [0.1, 1, 10, 100], 'gamma': [0.01, 0.1, 1, 10]}
  4. 使用 GridSearchCV(estimator=SVC(), param_grid=param_grid, cv=5, scoring='accuracy', n_jobs=-1) 进行 5 折交叉验证。
  5. 打印输出:
    • 最佳参数组合 grid.best_params_
    • 最佳交叉验证得分 grid.best_score_
    • 使用最优模型在测试集上的准确率。
  6. 对比实验:将 SVC(kernel='linear') 的默认结果与搜索出的最佳 RBF 核结果进行对比。
  7. 提交物:截图保存网格搜索的运行过程日志。

练习 4:【独立实战】“三英战吕布”—— 噪声环境下全模型压力测试

🎯 目标:在更复杂的分类数据上,对比决策树、随机森林、线性 SVM、RBF-SVM 的抗噪能力。 📂 数据集sklearn.datasets.make_classification(生成带有标签噪声的复杂分布)

任务要求

  1. 生成数据:make_classification(n_samples=1000, n_features=2, n_redundant=0, n_clusters_per_class=1, class_sep=0.8, flip_y=0.05, random_state=42)
    • flip_y=0.05 代表 5% 的样本标签被随机翻转(模拟现实标注错误)。
  2. 分别训练四个模型:
    • 决策树(不限深度)。
    • 随机森林(100棵树)。
    • 线性 SVM(C=1)。
    • RBF-SVM(通过练习 3 的逻辑,凭感觉给一个 C=10, gamma=0.5)。
  3. 重点关注
    • 哪个模型的测试集准确率最高?(通常是随机森林或 RBF-SVM)
    • 哪个模型的训练集准确率极高但测试集掉点严重?(通常是决策树)
  4. 可视化要求:由于数据是 2 维的,画出四个模型的决策边界分屏图。观察线性 SVM 的边界是直线,而 RBF-SVM 和随机森林的边界是弯曲的。

Part B:不给定数据集的开放性设计挑战(共 3 题)

(以下题目只给“背景需求”和“验收标准”,不提供数据加载代码,要求学生使用 make_* 函数自己造数据。)

练习 5:【造数据挑战】“客户分层个性化推荐”—— 制造可控重叠度的数据

背景:电商平台想把用户分为“高价值”和“低价值”两类。业务专家认为这两类用户的“年消费金额”和“月均登录天数”有一定区分度,但存在重叠。

要求

  • 不给定任何数据。你需要使用 make_blobsmake_classification 自己构造一个 2 维特征的数据集,要求:
    1. 总共 500 个样本。
    2. 两个类别的中心点距离较近(class_sep=1.0),让数据有约 15%~20% 的重叠。
  • 建模任务
    1. 使用 线性 SVMRBF-SVM 分别建模。
    2. 调整 C 值,使得线性 SVM 在测试集上的准确率尽可能接近 RBF-SVM。
  • 提交物:解释你选择的数据生成参数(random_stateclass_sep),并附上两个模型的决策边界对比图。

练习 6:【造数据挑战】“异或逻辑门模拟”—— 探究多项式核的威力

背景:经典 XOR(异或)问题在早期神经网络中被广泛讨论。你需要生成一个二维平面上的 XOR 分布数据(四个象限对角相同)。

要求

  • 手动造数据(不使用 make_classification,而是使用 numpy 手动堆叠):
    • [0,0][1,1] 附近生成 50 个正类样本。
    • [0,1][1,0] 附近生成 50 个负类样本。
    • 添加少量高斯噪声(np.random.normal(0, 0.1, size))。
  • 建模任务
    1. 使用线性 SVM,观察其准确率(约为 50%)。
    2. 使用 RBF 核,观察其准确率(通常能到 95% 以上)。
    3. 使用 多项式核 SVC(kernel='poly', degree=2)(二阶多项式),观察其准确率。对比二阶与三阶多项式的效果差异。
  • 深度问题:为什么二阶多项式核能完美解决 XOR 问题?(提示:二阶多项式相当于引入了特征组合 x1*x2)。

练习 7:【综合设计挑战】“甲方预算有限,要求 5 分钟出方案”

背景:一家小型物流公司要做“货车晚点预测”。他们能提供的数据特征很少(只有 3 个特征:出发时间、天气指数、司机工龄),且数据量只有 800 条。公司技术总监给出约束:

  1. 必须能在普通笔记本电脑上 1 秒内完成预测(推断速度要快)。
  2. 要求模型能打印出可视化的决策依据(给业务人员看)。
  3. 准确率做到 85% 就算过关,不追求极致精度。

任务要求

  1. 生成模拟数据:生成 800 条、3 个特征的二分类数据,为了满足“1 秒推断”,你必须舍弃集成模型中较慢的选项。
  2. 选型论证
    • 请从决策树、随机森林(n_estimators=10 vs 100)、线性 SVM、RBF-SVM 中选择最适合的一个。
    • 提示:RBF-SVM 需要存储大量支持向量,推断会变慢;随机森林树太多也慢;线性 SVM 和决策树推断极快。
  3. 可视化交付
    • 由于特征有 3 个,无法直接画 3D 决策边界,请输出决策树的 plot_tree,或者输出线性 SVM 的权重系数(coef_),作为交给业务人员的“公式卡片”。

好的,这是专门为 练习 7 定制的两份完整文档,可以直接用于期末机考或小组项目答辩。


文档一:《甲方需求说明书》

以下内容可直接打印,作为“甲方任务书”分发给学生。

📄 物流公司“货车晚点预测”项目 —— 需求说明书(甲方版)

项目编号:Proj-2026-07 密级:公开 版本:V2.0(最终版) 发布日期:2026 年 7 月 29 日

1. 项目背景

本公司(一路通物流)主营城际干线运输,共有 800 辆 自有货车。当前业务痛点:无法提前预判某趟运输任务是否会晚点,导致仓库调度被动、客户投诉率居高不下。

公司已完成前期数据采集,现委托你(数据分析团队)开发一套 “晚点预测模型” ,要求能够根据发车前的静态特征,输出“晚点(1)”或“准时(0)”的二分类结果。

2. 数据说明

项目 内容
样本总量 800 条(公司历史运输记录)
特征数量 3 个(仅以下三项,不增加额外特征)
特征列表 depart_hour:出发时间(整点,取值范围 0~23)
weather_index:天气指数(0 ~ 10,越大表示天气越恶劣)
driver_exp:司机工龄(年,取值范围 0.5 ~ 25)
标签 is_late:1 = 晚点(延误超过 30 分钟),0 = 准时
数据特点 数据存在少量噪声与重叠,属于非极端非线性分布。

⚠️ 注意:甲方不提供真实数据集,你需要使用 make_classificationmake_blobs 按照上述特征描述自行模拟生成符合业务逻辑的数据。生成时需设置 random_state=42 以保证可复现。

3. 硬性技术约束(不可违反)

编号 约束条款 解释
C-01 模型在普通笔记本电脑(2.0GHz CPU,8GB 内存)上的单次预测推断时间必须 ≤ 1 秒(针对单条新样本) 业务系统要求实时返回结果,不能超时。
C-02 必须向业务人员(非技术人员)提交可视化的决策依据,不可使用黑盒模型糊弄 业务人员需要理解“为什么这趟车会晚点”,以便人工复核。
C-03 不允许使用任何深度学习模型或需要 GPU 加速的算法 公司无 GPU 算力资源。
C-04 测试集上的准确率 ≥ 85% 即为验收合格,不追求 100% 精度 允许一定的误判,以换取推断速度和可解释性。

4. 模型选型指导性意见(甲方技术顾问提示)

  • 对于 推断速度要求极高 的场景,应优先考虑 结构简单的模型
  • 模型推断时间的排序(从快到慢)大致为:
    • 最快:线性 SVM(仅有权重向量 + 偏置,计算仅需一次点积)≈ 决策树(仅需沿树路径做 if-else 判断)
    • 中等:随机森林(需遍历多棵树取平均)> RBF-SVM(需计算新样本与所有支持向量的距离)
    • 最慢:深度学习网络(绝对禁止)

请根据以上提示,结合 C-02(可解释性)C-01(推断速度) 两条硬约束,选择合适的模型。如果选择了不合适的模型(如 RBF-SVM),此项将直接判定为不合格。

5. 交付物清单(乙方必须提交)

序号 交付物 格式要求
模型训练代码(含数据生成、划分、训练) .ipynb.py,需包含详细注释
模型选型论证报告(≤ 300 字) .md 或 Word,说明“为什么选 A,不选 B、C、D”
可视化决策依据(至少一种) - 决策树:输出 plot_tree 图像
- 线性 SVM:输出决策边界图(二维)或权重系数 coef_ 的柱状图
推断速度测试(代码 + 截图) 使用 time.time() 测量 单条样本 的预测耗时,打印输出
最终成绩表(含训练集/测试集准确率) 表格形式输出

6. 验收标准(甲方盖章生效)

评审项 合格标准
模型选型 选择了线性 SVM 或决策树(不含 RBF-SVM,不含 n_estimators>50 的大随机森林)
推断速度 单次预测 < 1 秒(实测值必须打印输出)
准确率 测试集准确率 ≥ 85%
可解释性 提供了可视化决策依据,且业务人员(老师)能看懂
代码规范性 代码可运行,关键步骤有中文注释

一票否决项:若选择了 RBF-SVM 或深度学习模型,无论准确率多高,本需求直接判定为 “不满足甲方需求” ,需重新提交。

文档二:《评分标准对照表》

以下内容可作为教师评分的内部标准,也可与学生同步公开,作为“比赛规则”。

📊 练习 7 评分标准对照表(满分 100 分)

适用项目:货车晚点预测 —— 模型选型与交付 评分形式:现场 Demo 演示 + 代码审查 + 报告评审

一、 核心硬指标(基础分 60 分,未达标则不及格)

评分维度 分值 得分条件(详细分层) 扣分/判定规则
1. 模型选型正确性 20 分 20 分:选择 线性 SVM决策树(max_depth ≤ 5)
⚠️ 10 分:选择随机森林但 n_estimators ≤ 10(勉强接受,但论证需充分)。
0 分:选择 RBF-SVM、多项式核 SVM、随机森林(n>50) 或任何深度学习模型。
若出现 0 分项,即使代码全对,总成绩最高不超过 59 分(不及格)。
2. 推断速度实测 20 分 20 分:单条样本预测时间 < 0.01 秒(极快)。
15 分:单条样本预测时间 0.01 ~ 0.5 秒
⚠️ 10 分:单条样本预测时间 0.5 ~ 1.0 秒(擦边及格)。
0 分> 1.0 秒(违反硬约束)。
必须提供 time.time() 的打印输出截图,无截图此项 0 分。
3. 测试集准确率 20 分 20 分≥ 90%
15 分85% ~ 90%(满足甲方底线)。
⚠️ 8 分80% ~ 85%(未达要求,但接近)。
0 分< 80%

二、 软实力与交付质量(满分 40 分)

评分维度 分值 得分条件 评分细则
4. 选型论证报告(300字) 15 分 必须清晰对比“为什么选 A,放弃 B、C、D”,并结合约束条件分析。 13-15分:逻辑严密,明确对比了至少 3 种模型,并逐一排除,紧扣“速度+可解释性”。
8-12分:仅简单说明“我选了 X”,缺乏对比分析。
0-7分:论证空洞或与事实矛盾(例如说“RBF 推断快”)。
5. 可视化决策依据 15 分 提供清晰的 plot_tree 图像 或 权重系数柱状图。 13-15分:图像清晰,有标题、图例,且附带文字解读(如“权重最大的是 weather_index,说明天气对晚点影响最大”)。
8-12分:有图像但未配文字解读。
0-7分:图像模糊或无法理解(如 3D 投影混乱),或未提供。
6. 代码质量与注释 10 分 代码可复现,结构清晰。 9-10分:代码分块清晰(数据生成、训练、评估、测速),关键行有中文注释。
6-8分:有基本注释,但排版混乱。
0-5分:无注释,变量名随意(如 a, b, x1),难以阅读。

三、 加分项(最多 +5 分,总分不超过 100)

加分项 分值 触发条件
GridSearch 调参 +2 分 对选定模型(如决策树的 max_depth)进行了交叉验证寻优,并打印了最佳参数。
特征重要性分析 +2 分 使用 permutation_importance 或模型内置重要性,分析三个特征对晚点的影响排序。
错误分析 +1 分 从测试集中找出 1-2 个被错分的样本,分析可能原因(如“该样本 weather_index 极高,但实际未晚点,可能是数据标注错误”)。

四、 总分计算表(快速打分用)

学生姓名 选型(20) 速度(20) 准确率(20) 论证报告(15) 可视化(15) 代码质量(10) 加分(≤5) 总分 是否通过验收
示例:张三 20 20 15 13 14 9 +2 93 ✅ 通过
示例:李四 0 20 20 10 12 8 0 70(强制挂科) ❌ 不通过
练习 7 的参考实现代码(含答案)…
  1# ================================================================
  2# 练习 7 参考实现代码(教师用)
  3# 项目:物流公司“货车晚点预测”
  4# 选型方案:决策树(满足速度 + 可解释性双重要求)
  5# ================================================================
  6
  7import numpy as np
  8import pandas as pd
  9import matplotlib.pyplot as plt
 10import time
 11from sklearn.datasets import make_classification
 12from sklearn.model_selection import train_test_split, GridSearchCV
 13from sklearn.tree import DecisionTreeClassifier, plot_tree
 14from sklearn.svm import SVC
 15from sklearn.metrics import accuracy_score, classification_report
 16from sklearn.inspection import permutation_importance
 17
 18# ================================================================
 19# 第一步:数据生成(模拟甲方业务数据)
 20# ================================================================
 21print("=" * 60)
 22print("第一步:生成模拟数据集(符合甲方特征描述)")
 23print("=" * 60)
 24
 25# 生成 800 条样本,3 个特征,二分类
 26# 设置 flip_y=0.05 模拟 5% 的标注噪声(更贴近真实场景)
 27X, y = make_classification(
 28    n_samples=800,
 29    n_features=3,
 30    n_redundant=0,
 31    n_informative=3,
 32    n_clusters_per_class=1,
 33    flip_y=0.05,
 34    random_state=42,
 35    class_sep=1.2
 36)
 37
 38# 将特征名称映射为业务字段(甲方可理解的名称)
 39feature_names = ['depart_hour', 'weather_index', 'driver_exp']
 40X_df = pd.DataFrame(X, columns=feature_names)
 41
 42# 对 depart_hour 做整数化处理(出发时间为整点,取值范围 0~23)
 43X_df['depart_hour'] = (X_df['depart_hour'] * 2 + 12).clip(0, 23).astype(int)
 44
 45# weather_index 映射到 0~10 区间
 46X_df['weather_index'] = (X_df['weather_index'] * 2 + 5).clip(0, 10)
 47
 48# driver_exp 映射到 0.5~25 年区间
 49X_df['driver_exp'] = (X_df['driver_exp'] * 3 + 6).clip(0.5, 25)
 50
 51# 恢复 numpy 数组格式(供 sklearn 使用)
 52X = X_df.values
 53
 54print(f"数据集形状:{X.shape}")
 55print(f"正例(晚点)占比:{y.sum() / len(y):.2%}")
 56print("\n前 5 条样本预览:")
 57print(X_df.head())
 58print("\n特征统计描述:")
 59print(X_df.describe())
 60
 61# ================================================================
 62# 第二步:划分训练集和测试集
 63# ================================================================
 64print("\n" + "=" * 60)
 65print("第二步:划分训练集(70%)与测试集(30%)")
 66print("=" * 60)
 67
 68X_train, X_test, y_train, y_test = train_test_split(
 69    X, y, test_size=0.3, random_state=42
 70)
 71
 72print(f"训练集样本数:{len(X_train)}")
 73print(f"测试集样本数:{len(X_test)}")
 74
 75# ================================================================
 76# 第三步:模型选型(核心决策)
 77# ================================================================
 78print("\n" + "=" * 60)
 79print("第三步:模型选型论证与训练")
 80print("=" * 60)
 81
 82# -------- 3.1 决策树(最终选型) ----------
 83# 使用 GridSearchCV 自动寻找最优 max_depth(体现调参意识,加分项)
 84print("\n【决策树】正在训练...")
 85param_grid_dt = {'max_depth': [3, 4, 5, 6, 7, 8, None]}
 86dt_grid = GridSearchCV(
 87    DecisionTreeClassifier(random_state=42),
 88    param_grid=param_grid_dt,
 89    cv=5,
 90    scoring='accuracy',
 91    n_jobs=-1
 92)
 93
 94start_time = time.time()
 95dt_grid.fit(X_train, y_train)
 96dt_train_time = time.time() - start_time
 97
 98best_dt = dt_grid.best_estimator_
 99print(f"  最佳 max_depth:{dt_grid.best_params_['max_depth']}")
100print(f"  交叉验证最佳准确率:{dt_grid.best_score_:.4f}")
101
102# -------- 3.2 线性 SVM(对比组,用于论证"为什么不选 SVM") ----------
103print("\n【线性 SVM】正在训练(对比参照)...")
104svm = SVC(kernel='linear', random_state=42)
105
106start_time = time.time()
107svm.fit(X_train, y_train)
108svm_train_time = time.time() - start_time
109
110# -------- 3.3 推断速度测试(核心指标) ----------
111print("\n" + "=" * 60)
112print("第四步:推断速度测试(单条样本预测耗时)")
113print("=" * 60)
114
115# 测试决策树单次预测速度
116sample = X_test[0:1]  # 取一条样本
117iterations = 1000  # 执行 1000 次取平均,消除偶然误差
118
119# 预热(确保 JIT 等机制不干扰计时)
120for _ in range(100):
121    best_dt.predict(sample)
122
123start_time = time.time()
124for _ in range(iterations):
125    best_dt.predict(sample)
126dt_infer_time = (time.time() - start_time) / iterations
127
128# 测试线性 SVM 单次预测速度(对比)
129for _ in range(100):
130    svm.predict(sample)
131
132start_time = time.time()
133for _ in range(iterations):
134    svm.predict(sample)
135svm_infer_time = (time.time() - start_time) / iterations
136
137print(f"✅ 决策树 单条预测耗时:{dt_infer_time * 1000:.4f} 毫秒(< 1 秒 ✅)")
138print(f"✅ 线性 SVM 单条预测耗时:{svm_infer_time * 1000:.4f} 毫秒(< 1 秒 ✅)")
139print(f"📊 两者推断速度处于同一量级,均满足约束 C-01")
140
141# ================================================================
142# 第四步:模型评估(准确率)
143# ================================================================
144print("\n" + "=" * 60)
145print("第五步:模型评估与对比")
146print("=" * 60)
147
148# 决策树评估
149y_pred_dt = best_dt.predict(X_test)
150dt_acc = accuracy_score(y_test, y_pred_dt)
151dt_train_acc = best_dt.score(X_train, y_train)
152
153# 线性 SVM 评估
154y_pred_svm = svm.predict(X_test)
155svm_acc = accuracy_score(y_test, y_pred_svm)
156svm_train_acc = svm.score(X_train, y_train)
157
158print("\n【性能对比表】")
159print("-" * 50)
160print(f"模型          | 训练集准确率 | 测试集准确率 | 训练耗时(s)")
161print("-" * 50)
162print(f"决策树        | {dt_train_acc:.4f}       | {dt_acc:.4f}       | {dt_train_time:.4f}")
163print(f"线性 SVM      | {svm_train_acc:.4f}       | {svm_acc:.4f}       | {svm_train_time:.4f}")
164print("-" * 50)
165
166print(f"\n✅ 决策树测试集准确率:{dt_acc:.2%} {'(≥ 85% ✅ 验收通过)' if dt_acc >= 0.85 else '(< 85% ❌)'}")
167print(f"✅ 线性 SVM 测试集准确率:{svm_acc:.2%}")
168
169# ================================================================
170# 第五步:可解释性交付 —— 可视化决策依据
171# ================================================================
172print("\n" + "=" * 60)
173print("第六步:交付可视化决策依据(满足约束 C-02)")
174print("=" * 60)
175
176# -------- 5.1 导出决策树图像(主交付物) ----------
177plt.figure(figsize=(16, 10))
178plot_tree(
179    best_dt,
180    feature_names=feature_names,
181    class_names=['准时 (0)', '晚点 (1)'],
182    filled=True,
183    rounded=True,
184    fontsize=10,
185    proportion=True
186)
187plt.suptitle("📋 货车晚点预测决策树(max_depth=最佳值)", fontsize=14, fontweight='bold')
188plt.tight_layout()
189plt.savefig('decision_tree_visualization.png', dpi=300, bbox_inches='tight')
190print("✅ 决策树图像已保存为:decision_tree_visualization.png")
191plt.show()
192
193# -------- 5.2 特征重要性柱状图(辅助交付物) ----------
194# 方式一:使用决策树内置的重要性
195importance = best_dt.feature_importances_
196
197plt.figure(figsize=(8, 5))
198plt.barh(feature_names, importance, color='steelblue')
199plt.xlabel('特征重要性(基于基尼系数减少量)')
200plt.title('📊 影响货车晚点的特征重要性排序')
201plt.grid(axis='x', linestyle='--', alpha=0.7)
202
203# 在条形末端显示数值
204for i, v in enumerate(importance):
205    plt.text(v + 0.01, i, f'{v:.3f}', va='center')
206
207plt.tight_layout()
208plt.savefig('feature_importance.png', dpi=300, bbox_inches='tight')
209print("✅ 特征重要性柱状图已保存为:feature_importance.png")
210plt.show()
211
212print("\n【特征重要性解读】")
213for name, imp in sorted(zip(feature_names, importance), key=lambda x: x[1], reverse=True):
214    print(f"  · {name}{imp:.3f}")
215
216# -------- 5.3 决策树规则提取(纯文本规则,业务人员最爱) ----------
217print("\n【决策规则提取(if-else 路径)】")
218
219
220def print_tree_rules(tree, feature_names, class_names, max_depth=3):
221    """递归打印决策树规则(仅打印前几层,避免过深)"""
222    tree_ = tree.tree_
223    feature_name = [
224        feature_names[i] if i != -2 else "未定义(叶子节点)"
225        for i in tree_.feature
226    ]
227
228    def recurse(node, depth, prefix):
229        if depth > max_depth:
230            print(prefix + "└── (继续分裂,已省略...)")
231            return
232        if tree_.feature[node] == -2:  # 叶子节点
233            class_label = np.argmax(tree_.value[node][0])
234            samples = tree_.n_node_samples[node]
235            print(prefix + f"└── 预测结果:{class_names[class_label]}(样本数:{samples})")
236            return
237        # 内部节点
238        threshold = tree_.threshold[node]
239        print(prefix + f"├── {feature_name[node]}{threshold:.2f}?")
240        recurse(tree_.children_left[node], depth + 1, prefix + "│   ")
241        print(prefix + f"└── {feature_name[node]} > {threshold:.2f}?")
242        recurse(tree_.children_right[node], depth + 1, prefix + "    ")
243
244    print("\n决策树规则(前 3 层):")
245    recurse(0, 0, "")
246
247
248print_tree_rules(best_dt, feature_names, ['准时 (0)', '晚点 (1)'])
249
250# ================================================================
251# 第六步:选型论证报告(自动生成框架,学生需手动撰写完整版)
252# ================================================================
253print("\n" + "=" * 60)
254print("第七步:选型论证总结(供学生参考的论证框架)")
255print("=" * 60)
256
257print("""
258【选型结论】选择【决策树】作为最终交付模型。
259
260【选型理由(逐一对比)】
2611. 【决策树 vs RBF-SVM(被否决)】
262   - RBF-SVM 在预测阶段需计算新样本与所有支持向量的距离(涉及高维核函数),
263     当支持向量数量 > 100 时,单次预测耗时可能 > 1 秒,违反 C-01。
264   - RBF-SVM 是典型的黑盒模型,无法向业务人员解释“为什么判断会晚点”,违反 C-02。
265
2662. 【决策树 vs 大随机森林(被否决)】
267   - 随机森林(n_estimators=100)需遍历 100 棵树取平均,推断耗时约为决策树的 100 倍。
268   - 即使设 n_estimators=10,推断速度依然慢于决策树,且可解释性低于单棵决策树。
269
2703. 【决策树 vs 线性 SVM(备选,但未最终选择)】
271   - 线性 SVM 推断速度同样极快(< 1 秒),且可通过权重系数解释特征影响力。
272   - 但决策树能提供更直观的 if-else 路径(业务人员可以“照着念”),在可解释性上更胜一筹。
273   - 两者准确率接近(均 ≥ 85%),故优先选择可解释性更强的决策树。
274
275【最终结论】决策树在满足「准确率 ≥ 85%」的前提下,同时满足了「推断速度 < 1 秒」
276和「可解释性」两条硬约束,是本项目的最优解。
277""")
278
279# ================================================================
280# 第七步:错误分析(加分项)
281# ================================================================
282print("\n" + "=" * 60)
283print("第八步:错误分析(加分项示例)")
284print("=" * 60)
285
286# 找出预测错误的样本
287misclassified_idx = np.where(y_pred_dt != y_test)[0]
288print(f"测试集中共有 {len(misclassified_idx)} 条被错分的样本")
289
290if len(misclassified_idx) > 0:
291    # 取第一个错误样本进行分析
292    idx = misclassified_idx[0]
293    print(f"\n【错例分析 #{idx}】")
294    print(f"  真实标签:{'晚点' if y_test[idx] == 1 else '准时'}")
295    print(f"  预测标签:{'晚点' if y_pred_dt[idx] == 1 else '准时'}")
296    print(f"  特征值:")
297    for name, val in zip(feature_names, X_test[idx]):
298        print(f"    · {name}{val:.2f}")
299    print("\n  可能原因分析:")
300    print("    - 该样本可能处于决策边界附近(特征组合与典型类别有重叠)")
301    print("    - 数据生成时设置的 5% 标注噪声(flip_y=0.05)可能导致部分标签不准确")
302    print("    - 天气指数虽然较高,但司机工龄较长,经验弥补了天气影响(特征交叉效应)")
303
304# ================================================================
305# 最终验收总结
306# ================================================================
307print("\n" + "=" * 60)
308print("✅ 验收总结")
309print("=" * 60)
310
311pass_status = []
312if dt_acc >= 0.85:
313    pass_status.append("✅ 准确率 ≥ 85%")
314else:
315    pass_status.append("❌ 准确率 < 85%")
316
317if dt_infer_time < 1.0:
318    pass_status.append("✅ 推断速度 < 1 秒")
319else:
320    pass_status.append("❌ 推断速度 > 1 秒")
321
322pass_status.append("✅ 已提供可视化决策依据(决策树图像 + 特征重要性图)")
323pass_status.append("✅ 未使用深度学习或 RBF-SVM")
324
325print("\n".join(pass_status))
326print("\n📦 交付物已生成:")
327print("  ① decision_tree_visualization.png  (决策树可视化)")
328print("  ② feature_importance.png           (特征重要性柱状图)")
329print("  ③ 本代码文件(含完整注释)")
330print("  ④ 选型论证报告(需学生手动撰写完整版)")
331print("\n✅ 项目验收通过!")

📌 建议

  1. 评分侧重点
    • 练习 1-4(给定数据):重点看 代码是否跑通表格数据是否记录准确
    • 练习 5-7(自造数据):重点看 选型理由是否站得住脚(比如练习 7 里选了 RBF 就算错,选了线性 SVM 或浅层决策树才算对)。
  2. 时间分配
    • 练习 1-2 为 课堂随堂实训(2 课时)。
    • 练习 3-4 为 课后一周作业
    • 练习 5-7 可能用于 期末机考/小组项目选题(3 选 1)。