跳转至

P4-8.1 模型选择(model selection)

Section ID: P4-8.1 Version: v2026.07.24

在 P4-7 里,我们看过要保留什么输入,以及要把这些输入改造成什么表达。现在要进入下一个问题。

这些输入到底应该交给哪一类 model?

这个问题,正是模型选择(model selection)的出发点。

人们常常把模型选择理解成 挑一个最有名的算法。但实际情况更接近相反。模型选择是在同时看问题形态、数据性质、可解释性、计算成本、运营条件之后,再把候选范围缩小的工作。

在学术语境里,模型选择也不是附带选择。在统计(statistics)和机器学习(machine learning)中,给定观测数据和目标函数(objective)之后,应该在多个候选模型(model candidates)中采用哪一个,这本身就是一个独立主题。也就是说,模型选择不是 按算法名字做个人偏好选择,而是在决定 面对当前问题和约束,应该先测试哪种假设结构

模型选择就是为了解决问题而建立一组待测试的候选模型,并把这组候选缩减成可比较形式的工作。

这一节会说明 模型选择(model selection)候选模型(model candidate)、以及 根据问题形态和约束来缩小候选的判断。后面的章节会沿着这个抓手继续当前语境,而到底应该先打开哪一个“模型抽屉”的标准,也会通过这一节和 概念词汇表 再接回来。

最快的出发点是下面这张表。

当前看到的问题 给读者的第一个问题 最先想到的候选家族
分类 是在区分两个以上的 class 吗? 逻辑回归、决策树、k-NN
回归 是在预测连续值吗? 线性回归、树回归
聚类 是在没有标签的情况下找分组吗? k-means、DBSCAN

这张表的目的不是直接猜答案,而是让读者看到问题后知道 应该先打开哪个抽屉

本节范围

这一节回答下面这些问题。

  • 模型选择(model selection)是在选择什么?
  • 为什么只记住一个 model,现实问题就很难处理?
  • 根据问题类型和数据条件,可以先想到哪些 model 家族?
  • 除了性能之外,还有哪些标准会进入模型选择?

这一节先收束 根据问题和约束应该建立什么样的 model 候选群。交叉验证的基本作用,会在 P4-4.2 和 P4-9.2 再接回来;信息准则、AutoML、大规模搜索系统,则会在 P4-9.3 补充学习里,以 高级模型选择与自动化 的视角继续展开。

用模型选择(model selection)留下的判断标准

  • 能把模型选择解释成 在候选模型集合里,逐步缩小适合问题的选项 这件事。
  • 能说明问题类型、数据大小、特征表达、可解释性、速度要求都会影响模型选择。
  • 能使用 建立合理候选集合(candidate set) 的思考方式,而不是执着于找一个唯一正确模型。
  • 能说明这一节怎样与后面的 baseline model、超参数(hyperparameter)、算法入门连接起来。

学习背景

Part 4 到目前为止经过了下面这个流程。

  • P4-4: 数据应该怎样划分
  • P4-5: 为什么泛化会困难
  • P4-6: 应该用什么标准评价
  • P4-7: 应该保留什么输入,又该把它改造成什么表达

只有把这些先整理好,选择 model 的条件 才终于成立。

也就是说,模型选择是一节只有在前面章节都看过之后,才能真正讲稳的内容。如果没有数据划分就先选 model,评价会摇晃;没有评价标准就先选 model,就无法判断哪个更好;没有特征和预处理就先选 model,就可能做出和输入不匹配的选择。

因此,这一节在课程结构里承担下面这些作用。

课程位置 模型选择章节的作用
特征/预处理之后 让读者开始思考什么输入表达适合什么 model
baseline model 之前 为建立比较起点先整理候选家族
算法入门之前 提供一张大地图,说明后面学到的算法该用在什么地方

也就是说,这一节不是 算法百科全书,而是 算法地形图

从课程结构上看,这一节也是 Part 4 的流程从 公平处理数据的阶段 转到 实际准备开始什么实验 的边界。

  • P4-4 讲了数据怎么分。
  • P4-5 讲了泛化和过拟合。
  • P4-6 讲了该用什么标准评价。
  • P4-7 讲了该留下什么输入、怎样表达。

只有走到这里之后,下面这个问题才真正成立。

那么现在应该把哪些 model 家族列为候选?

也就是说,模型选择这一节是在把前面章节的结果收拢到一起,改造成 实验设计的第一组选择项。所以这一节会成为后面 P4-8.2 baseline、P4-9 超参数调优、以及 P4-10 之后算法入门章节的入口。

因此,模型选择并不是突然冒出来的一节,而是把数据划分、泛化、评价标准、特征设计聚在一起,用来设计下一轮实验的一节。之后的流程会继续进入 baseline 和调优,建立比较结构,再进入后面的算法章节。

这时,P4-6 里看过的评价标准必须原样带过来。建立候选家族时,不应该只问 谁的分数最高,而要一起记下 需要减少哪类错误混淆矩阵里哪一格尤其要压低之后和 baseline 比较时希望改进什么。这样后面的 baseline 比较和调优结果,才不会只被读成分数竞争,而会被读成 错误结构到底改变了多少

读者在记候选家族时,也应该把同样的标准直接写下来。

现在就要记下的项目 为什么要在这里一起记
最初 2 到 4 个候选 model 为了减少一上来就抓住一个 model 直接调优的错误
最简单的 baseline 为了提前明确在后面章节里,首先必须赢过什么
第一优先观察的评价指标 为了避免只看 accuracy、MAE 这类表面分数
要检查的错误场景 为了立刻跟踪混淆矩阵里的问题格子或大误差区间

也就是说,模型选择笔记不能只停在 候选名字清单,还要把 baseline 和错误解释的起点 一起记下来。

读者在这里自然会问下一个问题。

那么 baseline 要先知道什么,才能做出来?

要回答这个问题,比起立刻深入 baseline 本身,更自然的顺序是先知道:在建立 baseline 之前,什么必须先被固定。baseline 不是突然想到的一条规则,而是必须先定下 现在把什么看成一个样本正在解决什么问题准备用什么分数来比较,然后才能建立。

也就是说,P4-8.1 提前提到 baseline,是为了让读者看到一点:比较标准也必须和候选家族设计一起记下。至于 baseline 到底该怎么建,会交给下一节 P4-8.2 作为中心问题;但它所需的准备知识,在这里先固定住会更自然。

再多补上一点,模型选择笔记会牢固得多。也就是在写下候选 model 之前,同时记下 现在把什么看成一个样本是在直接使用原始日志,还是在使用一次动作的汇总表最近结果将和什么 baseline 比较。因为即使算法名字一样,只要输入结构不同,比较实验的意义就会发生很大变化。

在写 model 候选前要确认什么 为什么必须先确认
样本单位是什么 因为如果把一行和一个案例混淆,评价单位也会一起摇晃
是用原始时序,还是用一次动作汇总行 因为只要输入表达变了,同一个 model 也会变成完全不同的实验
最近结果要和什么 baseline 比较 因为只看绝对数值,很容易误读候选 model 的改进方向
是否区分告警和原因确认 为了避免夸大 model 输出的解释含义

也就是说,模型选择虽然是在打开算法抽屉,但在那之前,它也和固定 可比较的数据结构 这件事绑在一起。

事实上,建立 baseline 立刻需要的准备知识,也正好和这里重叠。

baseline 之前先要知道什么 为什么 baseline 先需要这个信息
问题类型是分类还是回归 因为这里会决定 baseline 是多数类预测,还是平均值/中位数预测
一个样本到底是什么 因为如果一行的单位不稳定,baseline 分数到底在预测什么也会一起不稳定
第一优先看的评价指标是什么 因为看 accuracy、recall、还是 MAE,会改变 baseline 的解释
当前数据分布是什么样 因为要判断什么算是 简单标准,必须先知道类不平衡或目标值分布
运营里重要的失败是什么 因为要判断“只是刚好赢过 baseline”到底有没有意义,必须先知道重要错误类型

也就是说,建立 baseline 并不需要突然多出什么全新的高级理论,它首先需要的是已经整理过的 问题定义样本单位评价指标数据分布错误成本。这一节结束时,读者真正要握住的也正是这些准备物。

特别是在处理“动作单位原始时序”的例子里,究竟是直接放入多个时点,还是改造成一次动作的汇总行,会让同一个 model 名称变成完全不同的比较实验。

这一点也再次把 Part 3 和 P4-7 的流程接在一起。Part 3 里,读者看过 一行到底表示什么为什么需要一次动作汇总行;P4-7 里,又看过该在这个汇总行里保留哪些输入格子作为特征。只有这样之后,模型选择才终于到达 对于这种输入表达,应该先测试哪类 model 的阶段。

也就是说,模型选择不是和特征选择竞争的阶段,而是在已经固定好的样本单位和输入表达之上,去建立候选家族的阶段。

这里必须固定下来的判断有下面三件事。

在这一节里现在就要钉住什么 紧接着下一节会继续看什么 在实际比较中必须一路保留下来的东西
建立第一批 2 到 4 个候选 model 看有了 baseline 之后首先该赢过什么 混淆矩阵中的问题格子、大误差区间、代表性失败案例
看着问题决定该先打开哪个候选家族抽屉 比较分数上升是否真的代表改进 记下下一轮实验还要继续减少什么失败

候选家族笔记最好按下面这种格式留下来,这样比较标准就不容易摇晃。

笔记项目 示例
样本单位 一次动作
输入表达 一次动作汇总行
第一批候选家族 逻辑回归、决策树、随机森林
baseline 最简单的规则或多数类预测
先看的失败 不能漏掉的错误类型、大误差区间

这样记下来后,留下来的就不只是模型名字,还会一起保留 用什么输入表达、想减少什么失败、正在做什么实验

如果这张表里的 baseline 项目现在还显得模糊,也没关系。这里不是在讲 如何立刻完成 baseline,而是在先抓住 建立 baseline 需要什么材料。下一节 P4-8.2 会继续看为什么 baseline 必须先出现,以及分数解释的原理;而接下来的 P4-8.3 补充学习,则会用这些材料具体说明在分类、回归、时序里可以先放上什么简单标准。

主要学习内容

模型选择是在选择什么

从学术上说,模型选择(model selection)是从候选 model 里挑出符合目标的选择问题。但这里的目标不一定只有一个。

  • 预测性能好吗?
  • 会不会更少过拟合?
  • 计算成本能不能承受?
  • 好不好解释?
  • 能不能在运营环境里部署?

也就是说,模型选择比 找一个高准确率的 model 要宽得多。

如果再用稍微理论一点的话来说,这里选择的对象并不只是程序名字,而是假设空间(hypothesis space)中的一部分。是先看线性 model、还是树家族、还是基于距离的 model,本质上都连到 想从数据里读出什么结构。这一节不会深挖这些数学细节,而是把重点放在让读者能把它们先区分成 候选家族性格的差别

模型选择,就是按问题和约束逐步缩小 model 候选的工作。

为什么不能只记住一个 model

即使是同一份数据,只要问题变了,适合的 model 就可能变。

例如,同样是客户数据,也可能出现完全不同的问题。

同样的数据 不同的问题 模型选择视角
客户活动记录 下个月会不会流失? 分类(classification)候选
客户活动记录 下个月营收会是多少? 回归(regression)候选
客户活动记录 能不能把相似客户聚成组? 聚类(clustering)候选

也就是说,模型选择首先不是从数据集名字出发,而是从 到底想做什么预测或判断 出发。

读者经常出现的错误起点,也要一起整理。

  • 先挑有名算法
  • 先挑自己用过的库
  • 先挑看起来最复杂的 model

这三种都属于 先看工具,后看问题。模型选择的顺序正好相反: 先看问题,再去缩小工具。

模型选择前要先问的四个问题

这里比起算法名字,更重要的是问题顺序。

1. 问题类型是什么

最先要问的是问题类型。

  • 是分类(classification)吗
  • 是回归(regression)吗
  • 是聚类(clustering)吗
  • 还是更接近排序(ranking)或推荐(recommendation)

只要问题类型变了,候选 model 家族本身也会跟着变。

2. 数据有多少、是什么形式

第二个是数据规模和表达形式。

  • 样本数多还是少
  • 特征数多还是少
  • 主要是数值型,还是有很多字符串/类别值
  • 是稀疏(sparse)还是稠密(dense)

例如,样本少但特征很多,和样本很多但特征很简单,这两种情况下的模型选择感觉就会不同。

3. 可解释性重要吗

有些问题并不是只要预测对就够了。

  • 需要说明为什么会得出这个判断吗
  • 有监管或审计要求吗
  • 需要和人一起复核吗

这类情况下,可解释的 model 可能会比复杂 model 更适合作为起点。

4. 速度和运营约束大吗

是否需要实时响应、推理成本是否受限、内存或部署环境是否紧张,也都很重要。

即使性能好,如果服务响应太慢,或者运营成本撑不住,那也不一定是好选择。

模型选择是在建立候选家族

人们经常会问:

所以到底哪个 model 才是正确答案?

但模型选择真正的起点,与其说是决定一个正确答案,不如说更接近于建立 第一批 shortlist

flowchart TD
  subgraph R1["从问题出发"]
    direction LR
    A["问题陈述"]
    B["数据与特征"]
    C["约束<br/>速度 / 成本 / 可解释性"]
  end

  subgraph R2["然后缩小选择范围"]
    direction LR
    D["候选模型"]
    E["比较并收敛"]
  end

  A --> B --> C
  C --> D --> E

这张图的核心是,先来的不是 model,而是问题和约束。

如果把实务流程再展开一点,可以读成下面这样。

flowchart TD
  subgraph Q1["把任务翻译成建模问题"]
    direction LR
    A["1. 业务问题<br/>要支持什么决策?"]
    B["2. 任务类型<br/>分类 / 回归 / 聚类"]
    C["3. 数据条件<br/>规模 / 特征类型 / 稀疏性"]
  end

  subgraph Q2["设定限制并列出候选"]
    direction LR
    D["4. 约束<br/>可解释性 / 速度 / 成本"]
    E["5. 候选短名单<br/>2 到 4 个模型家族"]
  end

  A --> B --> C
  C --> D --> E

这张图把模型选择展示成:不是 搜索算法,而是 把业务问题翻译成实验候选家族的过程

先应该把哪些 model 候选放上来

模型选择的第一步,不是 猜出最强 model,而是先弄清楚 为什么要优先比较这些候选

当前条件 先提出的候选家族 原因
可解释性重要,而且想把起点保持简单 线性 model、浅树 因为解释和 baseline 比较更容易
数值型特征相对已经整理好,而且距离概念重要 检查 k-NN、SVM 因为输入之间的距离和边界会直接变重要
想快速测试非线性关系和交互作用 决策树、随机森林 因为复杂关系可以相对更快地先试一下
先想在没有标签的情况下看看结构 k-means、DBSCAN 因为可以先看分组结构和异常簇
输入很多、运营约束也大,所以先做快速 baseline 比较 2 到 4 个简单 model shortlist 因为在深挖一个 model 之前,可以先建立比较结构

这张表不是拿来猜正确 model 的,而是一个起点表,用来根据 问题类型输入表达可解释性运营约束,合理地缩小候选家族。

按问题类型先想到的候选家族

与其一口气比较所有算法,不如先按问题类型建立第一批候选家族。

问题类型 最先想到的候选家族
分类 逻辑回归、决策树、k-NN、SVM
回归 线性回归、决策树回归、随机森林回归
聚类 k-means、DBSCAN 这类无监督候选

这张表不是答案表,而是 探索起点

如果再和下一节的 baseline 一起读,会更清楚。

问题场景 第一批候选家族示例 一起记下的 baseline 第一优先看的错误或比较点
客户流失分类 逻辑回归、决策树、随机森林 总是预测 不流失 漏掉流失客户的格子、recall 变化
房价回归 线性回归、树回归、随机森林回归 预测平均房价 大金额误差区间、MAE 与 RMSE 的差别
客户分群聚类 k-means、DBSCAN 没有,改为和简单规则分组及解释作比较 簇内密度、簇间分离度

这张表的目的,不是只停在 要选什么 model,而是让读者一次性把 要和什么比较要先看什么失败 也一起写下来。

这时,baseline 比较并不是再多加一张分数表,而是要作为起点去读:最近结果和现有基准相比,实际减少了什么样的错误结构。

后面的章节会依次处理这些候选。

  • P4-10 线性回归
  • P4-11 逻辑回归
  • P4-12 k-NN
  • P4-13 SVM
  • P4-14 决策树
  • P4-15 随机森林

也就是说,这一节会成为从 P4-10 到 P4-19 的算法章节入口。

细部学习内容

数据条件会怎样改变候选家族

即使问题类型一样,只要数据条件不同,选择的感觉也会变化。

数据条件 最先要想到的变化
特征少,而且可解释性重要 先从线性 model、小树这类简单 model 开始
距离概念很重要 更要意识到 k-NN、SVM 这样的候选
看起来存在很多非线性边界 把树家族、kernel 方法放进候选集合
类别值很多 一起考虑预处理负担和 model 适配性
实时响应很重要 先检查推理快的 model

也就是说,模型选择看的不是算法本身的高低,而是 问题和数据条件的组合

把这句话再压缩成给读者的话,可以这样说。

模型选择不是去找“好算法”,而是先排除那些和当前问题不太合适的候选。

可解释性和运营条件也是选择标准

即使性能相同,现场里也可能因为下面这些理由而做出不同选择。

标准 为什么更简单的 model 可能更有利
可解释性 更容易解释结果,也更容易复核
调试 出现奇怪预测时,更容易追踪原因
运营成本 推理成本和内存占用可能更小
部署简单性 pipeline 和 model 结构更简单,更容易管理

也就是说,模型选择不会只用性能表里的一行来结束。

案例及示例

案例 1. 即使是同一个流失预测,第一批候选家族也可能不同

一个订阅服务团队正在准备客户流失预测。人最先看的标准,是 最近访问次数咨询次数是否支付失败套餐变更记录 这些行为信号。

在最开始的会议里,有人说 先上现在强的 model,也有人说 需要容易解释的 model。数据是表格型的,一天做一次 batch 预测就够了,运营团队也希望能对为什么某个客户被判定为高风险给出一定解释。在这种条件下,与其一开始就选一个唯一正确 model,不如一起提出可解释的线性 model、规则型 model,以及更强的非线性候选。

在这个场景里,模型选择就不再是 挑有名算法,而是 把问题和约束翻译成候选家族。是否是分类问题、是不是表格数据、是否需要解释、是不是不要求实时推理,这些条件都会成为缩小候选家族的标准。所以先建立像逻辑回归、决策树、随机森林这样性格不同的两三个 model,会更合理。

可确认的结果会出现在候选家族表和比较实验里。把哪个 model 提供可解释性、哪个 model 更能处理非线性模式、哪个 model 连运营成本也能承受,这些并排比较之后,就能说明为什么 候选家族设计 是模型选择的第一步。

如果把这个场景画成图,就能一眼看出:即使是同一个流失预测问题,也会因为约束条件不同而分出不同候选家族。

flowchart TD
  A["流失预测任务"]
  B["表格型行为数据"]
  C["需要解释性"]
  D["每天批量推理"]
  E["线性候选<br/>逻辑 回归"]
  F["可解释规则候选<br/>决策树"]
  G["强非线性候选<br/>随机森林"]
  H["在调参前比较短名单"]

  A --> B
  B --> C
  B --> D
  C --> E
  C --> F
  D --> G
  E --> H
  F --> H
  G --> H

用一个小例子建立 model 候选

考虑下面这个问题。

条件 内容
问题 客户流失预测
输入 最近访问次数、咨询次数、支付金额、会员等级
目标 分类下个月是否流失
约束 结果需要有一定可解释性
运营 每天 batch 预测,不是实时

在这种情况下,入门层面可以像下面这样建立候选家族。

候选 为什么会成为候选
逻辑回归 适合解释,也适合建立 baseline
决策树 可以直观看到非线性规则
随机森林 可能成为更强的性能候选

反过来,如果一开始就只固定一个非常复杂的 model,比较标准反而容易消失。

练习与示例

练习 1. 把问题场景改写成候选家族备忘

看下面三个场景,每个场景至少亲自写出以下五项。

  • 问题类型
  • 样本单位
  • 第一批候选 2 到 4 个
  • 一起记下的 baseline
  • 第一优先看的错误或比较点
场景 读者先写的备忘
商城想提前找出 下周可能退货的订单。输入是订单金额、是否延迟配送、类别、过去退货次数。运营团队希望对为什么被看作高风险订单给出一定解释。 问题类型 / 样本单位 / 第一批候选 / baseline / 第一优先看的错误
房地产团队想估计 公寓预期成交价。输入是面积、房间数、建成年份、到车站距离。可解释性也重要,但更重要的是减少大金额误差。 问题类型 / 样本单位 / 第一批候选 / baseline / 第一优先看的错误
物流团队想先看看 配送模式相似的地区分组。输入是平均配送时间、再次配送比例、订单密度。现在还没有标签,目标是先看出结构。 问题类型 / 样本单位 / 第一批候选 / baseline 替代比较 / 第一优先看的比较点

对照下面的解说检查时,不要只看 是否写了候选名字,还要看自己是否写出了 为什么先比较这些候选 以及 要和什么比较

场景 解说示例
找退货风险订单 分类,样本单位是 1 笔订单,候选家族是逻辑回归、决策树、随机森林,baseline 是始终预测 不会退货,第一优先看的错误是漏掉真实退货订单的格子和 recall
估计公寓成交价 回归,样本单位是 1 笔公寓成交,候选家族是线性回归、树回归、随机森林回归,baseline 是预测平均成交价,第一优先看的比较点是大金额误差区间和 MAE/RMSE 差别
把配送模式相似的地区分组 聚类,样本单位是 1 个地区,候选家族是 k-means 和 DBSCAN,不单独设 baseline,而是和简单规则分组及解释作比较,第一优先看簇内密度和簇间分离度

这个练习的核心,是亲手写一次 问题类型 -> 样本单位 -> 候选家族 -> baseline -> 第一优先看的失败 这个顺序。如果不把这个顺序写出来,就很容易再次把模型选择误解成 挑一个有名算法名字

练习 2. 用语言解释应该先排除哪个候选

模型选择与其说是在 猜唯一最好的算法,不如说更接近于 先排除那些和当前问题不太适合的候选。在下面这些场景里,用一两句话解释为什么有些候选要先提上来,有些候选应放到后面。

场景 读者先回答的问题
这是表格数据分类问题,人必须复核预测依据,而且一天一次 batch 推理就够了。 为什么可以先提出逻辑回归或浅层树?
特征之间的距离概念很重要,而且希望相近案例得到相近判断。 为什么可以更主动地把 k-NN 或 SVM 放在候选家族里?
实时响应非常重要,而且部署内存很小。 为什么应该先检查推理更简单的候选,而不是更复杂的候选?

抓住下面这些简短解说就够了。

  • 如果解释和复核重要,就先提出更容易解释的候选,这样更容易读出它和 baseline 的差别。
  • 当距离概念重要时,基于距离的候选更值得注意,因为输入之间的接近本身就可能成为判断标准。
  • 当运营约束很大时,不只是高分可能性,推理成本和部署简单性也会成为缩小候选的标准。

示例. 比较错误的候选备忘和更好的候选备忘

下面两份备忘写的是同一个问题场景。

问题场景:

  • 想用银行卡支付记录去分类 是否是欺诈交易
  • 输入是交易金额、国家、时间段、历史欺诈记录、设备信息。
  • 漏掉欺诈会带来很大损失,运营团队也希望能看到一定的风险判断依据。

备忘 A:

项目 内容
候选 只有 XGBoost
理由 听说性能很好

备忘 B:

项目 内容
问题类型 分类
样本单位 1 笔交易
第一批候选家族 逻辑回归、决策树、随机森林
baseline 始终预测 正常交易
第一优先看的错误 漏掉真实欺诈交易的格子、recall 变化
追加备忘 因为解释性和运营复核重要,所以第一比较组里要包含可解释候选

先自己回答下面的问题。

  • 哪份备忘更接近模型选择?
  • 为什么另一份备忘还停留在 挑有名 model 名字,而不是 候选家族设计
  • 就算是备忘 B,在后面比较之前还想多补一项的话,会补什么?

解说可以这样读。

  • 备忘 B 更好。因为它把模型选择连到了 问题类型样本单位比较候选baseline第一优先看的错误
  • 备忘 A 只固定了一个候选,缺少要和什么比较,以及要减少什么失败,所以无法自然衔接到后面章节的 baseline 比较。
  • 备忘 B 还可以再补的项目包括输入表达、数据不平衡程度,以及在运营上最难接受的失败类型。

这个比较示例的目的不是找出 好的候选名字,而是让读者分辨什么才算 好的候选备忘

如果把同一份候选家族备忘缩小成实际比较流程,可以像下面这样看。下面的代码会在同一个分类数据上放上逻辑回归、k-NN、决策树、随机森林,然后用 5-fold cross-validation 比较 train score、CV score,以及 CV score 的摇晃程度。

from sklearn.datasets import make_classification
from sklearn.ensemble import RandomForestClassifier
from sklearn.linear_model import LogisticRegression
from sklearn.model_selection import cross_validate
from sklearn.neighbors import KNeighborsClassifier
from sklearn.pipeline import make_pipeline
from sklearn.preprocessing import StandardScaler
from sklearn.tree import DecisionTreeClassifier

X, y = make_classification(
    n_samples=240,
    n_features=10,
    n_informative=4,
    n_redundant=2,
    class_sep=0.9,
    flip_y=0.06,
    random_state=42,
)

models = {
    "logistic_regression": make_pipeline(StandardScaler(), LogisticRegression(max_iter=1000)),
    "knn_scaled": make_pipeline(StandardScaler(), KNeighborsClassifier(n_neighbors=7)),
    "decision_tree": DecisionTreeClassifier(max_depth=4, random_state=0),
    "random_forest": RandomForestClassifier(n_estimators=80, max_depth=5, random_state=0),
}

for name, model in models.items():
    scores = cross_validate(model, X, y, cv=5, scoring="accuracy", return_train_score=True)
    print(
        name,
        "train=", round(scores["train_score"].mean(), 3),
        "cv=", round(scores["test_score"].mean(), 3),
        "spread=", round(scores["test_score"].std(), 3),
    )

输出示例如下。

1
2
3
4
logistic_regression train= 0.706 cv= 0.688 spread= 0.053
knn_scaled train= 0.819 cv= 0.725 spread= 0.04
decision_tree train= 0.902 cv= 0.762 spread= 0.028
random_forest train= 0.984 cv= 0.796 spread= 0.048

这个输出展示的是 挑一个有名 model比较候选家族 的差异。在这份小数据里,random forest 的 CV score 最高,但 train score 也很高。decision tree 分数低一些,但摇晃也更小。logistic regression 简单、容易解释,但在这份数据上的分数较低。因此,模型选择备忘里不应该只留下分数,还要一起留下候选家族、比较方式、摇晃程度、可解释性这些判断轴。

练习 3. 检查当第一优先看的错误改变时,备忘是否也会变化

下面两个场景都是分类问题,第一批候选家族也可以从相似的地方开始。但如果第一优先看的错误不同,模型选择备忘也应该改变。

场景 第一优先看的错误应该怎么写?
医院分诊阶段里,不要漏掉高风险患者 更重要。 写出特别要减少哪一个格子。
广告推荐系统里,不要对没兴趣的人过度曝光 更成问题。 写出要减少哪一种错误的正类判断。

自己写完之后,再和下面的解说比较。

场景 解说示例
高风险患者分类 因为要先减少漏掉真实高风险患者的 false negative,所以记成先看 recall 和漏掉的案例更合适。
广告推荐分类 因为要先减少把没兴趣的人错判成正类的 false positive,所以记成先看 precision 一侧的错误和过度曝光案例更合适。

这个练习要抓住的是,写完候选家族还没有结束。即使候选家族相同,想优先减少什么失败不同,比较备忘也会随之改变。

检查清单

  • 你是不是先确定问题类型,再建立候选家族?
  • 你是不是写下样本单位和输入表达后,才开始比较候选?
  • 在马上调一个 model 之前,你是不是还保留了 2 到 4 个左右可比较的 shortlist?
  • 你记下来的不只是候选 model 名字,也包括 baseline 和第一优先看的错误场景吗?
  • 现在的问题是分类、回归,还是聚类?
  • 你有没有考虑输入表达和数据规模?
  • 你有没有把可解释性和运营约束放进标准里?
  • 你是否已经准备好建立并比较至少两三个候选家族?
  • 你是不是还在没有 baseline 的情况下,只抓着一个复杂 model 不放?
  • 你能不能说明,模型选择不是去点名一个正确算法,而是把问题类型、数据条件、可解释性、运营约束一起看后,建立合理候选家族的过程?

出处与参考资料

  • Jie Ding, Vahid Tarokh, Yuhong Yang, Model Selection Techniques -- An Overview, arXiv, 2018, 确认日期: 2026-06-26. https://arxiv.org/abs/1810.09583
  • Sebastian Raschka, Model Evaluation, Model Selection, and Algorithm Selection in Machine Learning, arXiv, 2018, 确认日期: 2026-06-26. https://arxiv.org/abs/1811.12808