P4-7.2 预处理(preprocessing)¶
Section ID:
P4-7.2Version:v2026.07.20
在 P4-7.1 里,我们看的是 要保留什么输入。现在要进入下一步:不是把保留下来的输入原样扔给 model,而是先整理成 model 更容易读取的形式。这个阶段就是预处理(preprocessing)。
这一节的核心不是复杂的库语法,而是不要只把预处理理解成 数据清扫,而要把它理解成 把输入表达改造成 model 能处理的形式。
这一节会说明 预处理(preprocessing)、缺失值处理(imputation)、尺度(scale)调整、类别编码(encoding) 的基本含义。后面的章节会沿着这个抓手继续当前判断,而输入表达变换的基本含义,也会以这一节和 概念词汇表 为基准再次接回。
还有一个重要原因。人们常常先学算法,再把预处理当成后面补上的辅助工作。但实际情况更接近相反。如果输入表达没有先整理好,后面要学的线性回归(linear regression)、逻辑回归(logistic regression)、k-NN、SVM 这些算法的性质也很难真正读清。
这一节不是某个特定算法的附录,而是整理 算法到底接收什么输入 的共同基础章节。
从学术上看,预处理也不是外围工作。在数据挖掘(data mining)、模式识别(pattern recognition)、机器学习(machine learning)的教材和工具文档里,预处理通常被当作一个独立阶段:把 原始数据(raw data) 移到 特征空间(feature space) 或 模型输入(model input)。预处理不是贴在 model 前面的杂务,而是 构造可学习表达的阶段。
这个视角之所以重要,原因很简单。model 学的不是现实本身,而是经过预处理后得到的输入表达。因此,预处理怎么做,会直接影响 boundary、distance、optimization 和 interpretability。
本节范围¶
这一节回答下面这些问题。
- 预处理(preprocessing)是在改变什么?
- 为什么缺失值、尺度、类别值如果原样放着会出问题?
- 哪些 model 对尺度(scale)更敏感,哪些相对没那么敏感?
- 在 train data 和 test data 里,预处理应该怎样分开处理?
这一节先收束 怎样把保留下来的输入改造成可计算、可比较的表达。各种 encoder 和 scaler 的细部设置,会在 P4-9.1、P4-9.2 的超参数和调优语境里再接回来;降维的目的和直觉,则会在 P4-18.1、P4-18.2 继续展开。
用预处理(preprocessing)留下的判断标准¶
- 能把预处理解释成
把原始输入改造成更合适表达的阶段。 - 能说明为什么缺失值修正、尺度调整、类别编码是必要的。
- 能区分预处理里的
fit和transform,并说明为什么不能对 test data 做fit。 - 能在入门层面理解为什么 pipeline 和 ColumnTransformer 在实务里经常出现。
学习背景¶
Part 4 前面的章节是按下面这个流程接过来的。
- P4-4: 应该怎样划分数据
- P4-5: 为什么泛化(generalization)很难
- P4-6: 应该用什么标准评价
- P4-7.1: 应该保留什么输入
走到这里之后,自然会出现下一个问题。
保留下来的输入,到底要以什么形式交给 model?
这正是预处理章节所在的位置。在课程结构里,这一节承担下面这些作用。
| 课程位置 | 预处理章节的作用 |
|---|---|
| 特征选择之后 | 整理保留下来的输入应该改成什么表达 |
| 模型选择之前 | 为理解不同 model 更偏好什么输入表达做准备 |
| 算法入门之前 | 连接为什么距离、边界、优化会对输入表达敏感 |
预处理就是连接 输入设计 和 理解 model 的章节。
从课程结构上看,这一节也是 Module 2 和 Module 3 性质发生变化的边界。Module 2 里讲的数据划分、泛化、评价指标,处理的是 为了公平比较 model,必须先整理什么。而 Module 3 处理的是 实际要用什么输入和什么 model 组合来开始实验。预处理就正好落在这个转折点上。
经过这一节之后,读者就会从单纯把数据收集起来的状态,走向 先构造可比较的输入表达,再建立 model 候选 的流程。所以 P4-7.2 会成为 P4-8 模型选择(model selection)、P4-9 超参数调优(hyperparameter tuning)、以及 P4-10 之后算法章节的共同基础。
主要学习内容¶
第一次听到预处理时可以先这样抓¶
第一次听到 preprocessing 这个词,可能会觉得有点难。但非常短地说,它就是 把 model 不容易直接读的输入,先改造成能读的输入 的准备工作。
人是可以直接读下面这张表的。
| age | income | city |
|---|---|---|
| 29 | 3200 | Seoul |
| 无 | 6100 | Busan |
| 35 | 无 | Incheon |
但很多 model 很难把这张表原样拿来算。
- 有
无这种值,计算就会断掉。 3200和29的数值尺度差太大。Seoul、Busan这样的字符串,很难直接参与计算。
所以通常可以把预处理理解成下面这几件事。
- 用某种规则处理空值。
- 把数值型的比较基准对齐,让比较更公平。
- 把字符串或类别改造成可计算的表达。
经过这个过程,同样的数据大致会变成下面这种输入形式。
| age_filled | income_scaled | city_search | city_busan | city_incheon |
|---|---|---|---|---|
| 29 | ... | 1 | 0 | 0 |
| 35 | ... | 0 | 1 | 0 |
| 35 | ... | 0 | 0 | 1 |
也就是说,预处理不是在制造新的正确答案,而是在做 把原来的输入重写成 model 能计算的格式 这件事。
这一节会把这个想法拆成三个代表场景来读。
- 缺失值处理: 空着的值要怎么处理
- 尺度调整: 数值轴大小差异要怎么对齐
- 编码: 字符串和类别要怎样改造成可计算的形式
预处理是在做什么¶
scikit-learn 的预处理文档说明,它提供了多种函数和 transformer,用来把 raw feature vectors 转换成更适合 downstream estimator 的表达。把这句话改写成入门层面的意思,大概就是下面这样。
预处理是把原始输入改造成 model 更容易处理的输入表达的阶段。
预处理不是在改 label,而是在改输入表达。
如果再用稍微学术一点的话来说,预处理是一组对输入空间(input space)表达进行变换的操作,让学习算法能够更稳定地工作。这里面可以包括缺失值处理、scaling、encoding,有时也会包含 normalization、feature construction、dimensionality reduction 这样更宽的类别。这一节只聚焦于最常见的三类。
这一节会把预处理大致看成三部分。
- 缺失值处理(imputation)
- 数值变换与缩放(scaling / normalization)
- 类别表达变换(encoding)
这三件事的共同点都是 它们在改变输入表达。不过,改的东西并不一样。
| 预处理类型 | 它在改变什么 | 为什么要改 |
|---|---|---|
| 缺失值处理 | 填空值的规则 | 为了让计算成为可能 |
| 尺度调整 | 数字的大小和基准 | 为了稳定比较和优化 |
| 编码 | 把类别值变成数值表达 | 为了匹配 model 输入形式 |
不过,如果把实务里的预处理类型看得更宽一点,读者最好还要带着下面这张大地图。
| 预处理大类 | 它在做什么 | 这一节的处理程度 |
|---|---|---|
| 缺失值处理(imputation) | 按规则修正或标记空值 | 详细讲 |
| 数值尺度调整(scaling/normalization) | 对齐数值轴的大小和基准 | 详细讲 |
| 类别编码(encoding) | 把字符串/类别值改成可计算表达 | 详细讲 |
| 异常值处理(outlier handling) | 让极端值不要过度扰动学习 | 只提概念 |
| 特征构造(feature construction) | 从原始列再造更有用的输入表达 | 只作为大类提及,本书主要和 P4-7.1 相连 |
| 降维(dimensionality reduction) | 把很多输入压缩成更小的表达 | 在 P4-18 再讲 |
| 文本/图像等特殊表达变换 | 把非结构化数据改成数值表达 | 在后续 Part 作为单独的输入表达处理 |
这张表的目的,不是把所有预处理技术都塞进一节里,而是先让读者抓住:预处理 这个词并不只是一个狭义的“用平均值填缺失”的动作。
不过,为了在本书里把边界放清楚,一开始到底保留哪些输入、又把什么表达做成新特征候选,主要放在 P4-7.1 的特征设计一侧;这一节则集中在 把保留下来的输入改造成可计算、可比较的形式。也就是说,从大范围看,特征构造也可以算在预处理大类里,但这一节的中心责任是缺失值处理、尺度调整、编码,以及同样规则的复用。
在入门层面,像下面这样记也已经够用了。
- 填值的预处理
- 对齐数值轴的预处理
- 改变表达形式的预处理
- 缓和过大数值或减少输入数量的预处理
也就是说,预处理不是一个技术,而是 让输入表达可计算、可比较的多种规则集合。
第一次理解预处理时,最快的方法通常还是画出 原始输入的一行,在预处理后会变成什么样。
| 预处理前的一行 | 预处理后的一行示例 |
|---|---|
age=无, income=5800, city=Seoul | age=用中位数修正, income=尺度调整后的值, city=[0,0,1] |
age=35, income=无, city=Incheon | age=保留 35, income=中位数修正后再缩放, city=[0,1,0] |
这组对比展示出来的核心很简单。
- 预处理不会改 label。
- 预处理会把一行输入重写成 model 能计算的形式。
- 同样的规则必须重复应用到其他行上。
所以,与其说预处理是在 修数据,不如说它更接近 把输入行再翻译一遍。
例如,可能会出现下面这些问题。
| 原始输入状态 | 为什么 model 会为难 | 预处理的作用 |
|---|---|---|
| 值是空的 | 无法直接计算 | 缺失值修正 |
| 数值大小差异过大 | 某些特征可能会有过大的影响力 | 尺度调整 |
| 值是城市名这样的字符串 | 很多 model 不能直接算 | 类别编码 |
| train 和 test 用了不同规则 | 评价会被扭曲 | 保持同样的变换规则 |
因此,预处理既是 整理,也是 表达设计。
如果再按输入问题把预处理重新归类,可以像下面这样读。
| 输入问题 | 先想到的预处理类型 | 示例 |
|---|---|---|
| 值是空的 | 缺失值处理 | 平均值、中位数、众数、缺失标记 |
| 数值大小差太多 | 尺度调整 | 标准化、最小-最大缩放 |
| 值是字符串或类别 | 编码 | one-hot encoding |
| 某些值跳得太厉害 | 异常值处理 | 评估 robust scaling 或 clipping |
| 列太多了 | 降维/表达压缩 | 后续章节再讲 PCA 等方法 |
| 原始列本身信号太弱 | 特征构造 | 生成比率、差值、聚合特征 |
这样看,预处理就不是一个以 缺失值处理 + 尺度 + 编码 结束的清单,而是根据 当前输入到底有什么表达问题 往不同方向分开的工作。
从理论上看,预处理会同时从下面两个视角去读。
- 表达变换(representation transformation)
把同一件事实换成别的数值表达 - 假设匹配(assumption matching)
让输入更接近 model 或学习过程所期待的条件
例如,标准化(standardization)并不只是把数字排一排,而是在尝试对齐输入的中心和方差,让某些算法更容易工作。one-hot encoding 也不是强行把字符串变成数字,而是在改造成可计算的特征表达。
从学术上,可以把预处理理解成 把放在 model 前面的输入表达做变换,移到学习和推理都可行的空间里去的阶段。
这里最重要的是,预处理不是 把数据弄得好看一点,而是在 重新给 model 将要看到的世界坐标化。同样一份客户数据,可能有些值是空的,有些值的数轴特别大,有些值是字符串,根本无法计算。预处理就是把这些东西重新调成 可比较、可计算的表达。
所以,预处理不是辅助杂务,而是决定 model 能把什么读成同类信息 的阶段。
如果想更短地抓住这个观念,下面这组对照会有帮助。
| 问题 | 更接近特征选择的回答 | 更接近预处理的回答 |
|---|---|---|
| 要把什么留下当输入 | 采用哪些列 | 已经留下来的列要怎么改 |
| 什么不该交给 model | ID、泄漏列、事后信息 | 在 test 上 fit 出来的规则、错误的编码方式 |
| 什么需要先变得可计算 | 整理输入候选 | 填值、对齐数轴、改变表达 |
也就是说,如果说特征选择是在 决定入口,那么预处理就是 把已经进入入口的值翻译成 model 的语言。
要真正理解预处理,还得同时抓住:这种翻译通常会按下面的顺序发生。
flowchart LR
A["原始表<br/>缺失值 / 尺度混杂 / 类别文本"]
B["先划分数据<br/>训练 / 验证 / 测试"]
C["只在 训练 上学习预处理规则<br/>填充值 / 缩放统计量 / 类别映射"]
D["用同一套规则转换各行"]
E["可直接送入模型的矩阵"]
A --> B --> C --> D --> E
这个流程的核心有四点。
- 不会把原始表直接送进 model。
- 先切分数据,规则只在 train 上学习。
- 已学到的规则会重复应用到其他行。
- 这样得到的结果,才是 model 的输入矩阵(matrix)。
也就是说,预处理不是 稍微修一下值,而是 把原始表重构成 model 能读的矩阵。
没有预处理会立刻坏掉什么¶
理解预处理必要性最快的方法,就是看 没有预处理时会发生什么。
| 原始输入状态 | 如果不做预处理直接塞进去会发生什么 | 为什么是问题 |
|---|---|---|
| 有缺失值 | 某些 model 和计算会直接停住或报错 | 它不能成为可计算的输入行 |
| 数值尺度差异很大 | 大单位的轴会过度支配距离和优化 | 特征比较不公平 |
| 混有字符串类别 | 很多 model 无法直接计算 | 输入表达没法进入数值空间 |
| train 和 test 用不同规则 | 评价分数可能看起来比实际更好,或者波动很大 | 比较和可复现性会一起崩掉 |
也就是说,预处理不是为了把性能再抬高一点的可选项,在很多情况下,它是 让 model 至少能读懂输入的最低条件。
如果把同样的话翻成更实务的说法,就是下面这样。
- 不能计算的输入,就无法学习。
- 比较不公平的输入,会制造错误的边界和距离。
- 不能复现的规则,会让部署后无法重新生成同样的输入。
所以,预处理不是贴在 model 后面的化妆,而更像是在 model 前面把输入世界先盖起来的基础工程。
特征选择和预处理是怎样接起来的¶
如果说 P4-7.1 里的特征选择是在决定 保留什么,那么预处理就是在决定 把保留下来的东西表达成什么样。
核心是:即使经过了 选择,输入表达还是要继续处理。保留下来的特征必须再经过缺失值处理、尺度调整、编码等变换,才能真正变成 model 更容易读取的输入形式。
如果把预处理看成不是单一技术,而是 调节输入表达的一组变换,那么立刻就会出现下面这些输入检查问题。
- 这个值可计算吗?
- 这个值的大小差异会不会扭曲学习?
- 这种表达会怎样影响距离(distance)、边界(boundary)、优化(optimization)?
- 训练时学到的变换规则,在推理时还能不能原样复现?
这些问题之所以重要,是因为后面会遇到的算法,会以不同方式对输入表达敏感。
如果把“同样的输入也会分岔出好几类预处理问题”用表记下来,就没那么容易混乱。
| 同样输入里出现的问题 | 最先出现的问题 | 更接近的预处理判断 |
|---|---|---|
| 值是空的 | 计算本身可不可以做 | 缺失值处理 |
| 数值大小差太多 | 哪个列在支配距离或优化 | 尺度调整 |
| 值是字符串 | model 能不能直接计算这种表达 | 编码 |
| train/test 的规则不同 | 评价和运营里是否在复现同一规则 | 分开 fit 和 transform |
这张表会让读者重新把预处理读成:不是技术名,而是 到底在诊断哪一种输入问题。
再往前走一步,预处理的判断顺序通常可以固定成下面这样。
| 顺序 | 先看什么 | 为什么先看 |
|---|---|---|
| 1 | train/test 是否先分开了 | 为了防止学规则时发生泄漏 |
| 2 | 值是不是可计算 | 因为缺失值和字符串问题要先解决 |
| 3 | 值之间比较是否公平 | 因为尺度差异会扭曲学习 |
| 4 | 同样规则是否会重复应用 | 因为评价和部署都必须可复现 |
抓住这个顺序之后,预处理会更稳定地被理解成 输入表达设计流程,而不是 套工具的检查清单。
把预处理前后放在一个场景里看¶
如果想更具体地理解预处理,最好一次性看到同一张小表是怎么变化的。
| customer | age | income | channel | label |
|---|---|---|---|---|
| A | 29 | 3200 | search | 0 |
| B | 无 | 6100 | ad | 1 |
| C | 35 | 无 | direct | 0 |
| D | 41 | 5800 | search | 1 |
这张表对人来说是能读的,但从 model 的角度看,直接拿来用会很困难。
无会打断计算。age和income处在不同数轴上。channel是字符串,难以直接计算。
经过预处理之后,同一张表会大致变成下面这样的输入。
| customer | age_filled | income_filled | age_scaled | income_scaled | channel_search | channel_ad | channel_direct | label |
|---|---|---|---|---|---|---|---|---|
| A | 29 | 3200 | ... | ... | 1 | 0 | 0 | 0 |
| B | 35 | 6100 | ... | ... | 0 | 1 | 0 | 1 |
| C | 35 | 5800 | ... | ... | 0 | 0 | 1 | 0 |
| D | 41 | 5800 | ... | ... | 1 | 0 | 0 | 1 |
这里真正该看的,不是具体数值,而是结构变化。
- 缺失值按规则被填上了。
- 数值型被移到了共同的比较轴上。
- 类别值被展开成一组可计算的列。
- 结果就是,每一行都被整理成同样结构的输入向量(vector)。
也就是说,预处理的结果不是 更干净的表,而是 model 能按同样结构读取的输入矩阵。
抓住这个场景之后,读者就更容易把预处理理解成不是 三个零碎技术,而是 把不同类型的值搬进同一计算世界的整合工作。
为什么缺失值(missing value)要先处理¶
现实数据里经常会有空值。
- 用户没有回答的值
- sensor 没能读到的值
- 只有后来才会记录的值
- 因为采集失败而漏掉的值
如果把这些值原样放着,很多 model 和运算就无法继续。所以在预处理的第一步里,通常会先决定 这些空值要怎么处理。
scikit-learn 的 SimpleImputer 文档给出了用平均值(mean)、众数(most frequent)、常数(constant)等策略填补缺失值的例子。
| 场景 | 常见的入门策略 |
|---|---|
| 数值列 | 平均值、中位数(median) |
| 类别列 | 众数,或者 "unknown" 这样的常数 |
| 缺失本身可能有意义 | 把“是否缺失”保留成额外信号 |
这里重要的不是 无条件填上,而是 为什么要用这个规则去填。
例如,收入(income)这一列可能有很多极端值,因此中位数往往比平均值更合适。相反,如果像考试分数这种分布比较平整的列,平均值也许更直观。
如果换成业务场景来看,会更容易理解。
| 场景 | 缺失值意味着什么 | 入门式判断示例 |
|---|---|---|
| 医院预约数据 | 预约路径没有被记录 | 可以保留成 "unknown" 这样的单独类别 |
| 商城客户数据 | 收入信息未填写 | 评估用中位数修正,或补一个缺失信号 |
| 传感器数据 | 测量失败 | 可能更应该当作采集故障信号,而不只是简单修正 |
缺失值不是单纯的空格,而是必须连同 为什么会空 一起读的输入状态。
缺失值处理的方式,粗略地也可以分成下面几类。
| 方式 | 它在做什么 | 入门层面的优点 | 注意点 |
|---|---|---|---|
| 平均值/中位数修正 | 用代表值填数值型缺失 | 实现简单、速度快 | 可能把分布压得过平 |
| 众数/常数修正 | 用最常出现的值或 "unknown" 填类别缺失 | 字符串列也容易处理 | 可能把真实意义过度简化 |
| 增加缺失标记 | 把“原来是空的”单独留成一列 | 当缺失本身就是信号时有用 | 列数会增加,解释也会更复杂 |
| 删除行 | 直接删掉有缺失的行或列 | 规则简单 | 可能带来很大数据损失 |
也就是说,缺失值处理不只是 用什么填 的问题,也是在 要不要保留空白痕迹、要不要直接丢掉、要不要用代表值近似 之间做选择。
缺失值处理错了会发生什么¶
如果太快就把缺失值理解成 用平均值补上就结束,就会漏掉很多关键差别。
| 缺失情况 | 草率处理 | 更好的初学者判断 |
|---|---|---|
| 单纯输入漏填 | 不解释原因,直接用平均值填 | 先看为什么会空 |
| 缺失本身可能是行为信号 | 把缺失痕迹完全抹掉 | 同时评估缺失标记 |
| sensor 采集失败 | 修成像正常值一样 | 先判断它是不是采集故障信号 |
也就是说,缺失值处理与其说是在遮住空白,不如说更接近于解释 这个空白到底是什么事件。
还有一个重要点:一旦把缺失值填上,那个值就会开始看起来像 原本就观测到的值。所以读者最好始终意识到:这个值是观测值,还是修正值。因为预处理不是在原样保存现实,而是在构造可计算的近似表达。
因此,在缺失值处理里,通常最好顺手加上下面这些问题。
- 这一列的缺失,是偶然漏填,还是某种行为/采集事件?
- 用代表值来填,会不会造成太大的意义扭曲?
- 缺失这件事本身,要不要留成额外信号?
少了这三个问题,缺失值处理就很容易只剩技术动作;加上它们之后,才会变成输入解释。
为什么尺度(scale)会制造问题¶
不是所有数值型特征都在同一个单位上变化。
例如,可以想下面这两列。
- 年龄(age): 20, 35, 41
- 月收入(monthly_income): 2,000,000 / 4,500,000 / 8,000,000
这两列都是数字,但取值范围差非常大。有些算法如果原样接收这种差异,就很容易把大数值那条轴看得更重要。
scikit-learn 的预处理文档说明,线性模型(linear models)等很多学习算法会受益于标准化(standardization),而且如果特征方差差异很大,某些特征就可能在目标函数(objective function)里过度支配。
尺度调整不是把数字弄得更漂亮,而是在重新平衡各个特征之间的影响力。
这个解释也会直接连到后面的理论。
- 在 k-NN 里,距离计算会改变。
- 在 SVM 里,边界可能会改变。
- 在线性模型和逻辑回归里,优化往往会更稳定。
尺度调整不是一个零散的小技巧,它也是理解后面算法章节的准备阶段。
如果换成实务场景,可以像下面这样读。
| 场景 | 不做尺度调整可能发生什么 |
|---|---|
| 贷款审批 | 像收入这样的大数值轴可能会压过其他特征 |
| 用户聚类 | 距离计算可能被某个大单位列牵着走 |
| 异常检测 | 重要但变化幅度小的列可能被埋掉 |
哪些 model 对尺度更敏感¶
并不是所有 model 对尺度都同样敏感。
| model 家族 | 尺度影响 |
|---|---|
| k-NN、SVM、基于距离的方法 | 通常比较敏感 |
| 线性模型、逻辑回归、基于梯度下降的方法 | 经常敏感 |
| 决策树、随机森林等树模型 | 相对没那么敏感 |
这个差别会在后面的章节里变得更清楚。
- P4-10 线性回归(linear regression)
- P4-11 逻辑回归(logistic regression)
- P4-12 k-NN
- P4-13 SVM
- P4-14、P4-15 树与集成
预处理并不是在所有 model 里都以同样权重发挥作用。根据 model 的类型,它会变得更重要,也可能相对没那么关键。
如果做一个简短对照,尺度问题会更清楚。
| 特征组合 | 尺度调整前的读法 | 尺度调整后的读法 |
|---|---|---|
income, visits_30d | 大数字的 income 轴很容易压倒判断 | 两个特征会在更相近的基准上比较 |
sensor_peak, sensor_std | 可能只有大单位轴被过度看见 | 更容易同时读出波动性和大小 |
也就是说,尺度调整不是 把小数字放大 的技术,而是 当多个特征一起被比较时,调节哪条轴说话太大声 的技术。
因此,尺度调整不能只当成一个独立技术,而要连着 model 到底用什么比较方式 一起看。用距离的 model 往往更敏感,用切分规则的树家族则相对没那么敏感。预处理始终不只是 数据本身的问题,也是 数据和 model 之间相互作用的问题。
如果把尺度调整的类型非常粗略地分开,可以看成下面这样。
| 方式 | 对齐什么 | 什么时候常会想到 | 注意点 |
|---|---|---|---|
| 标准化(Standardization) | 把值调到平均 0、方差 1 附近 | 线性模型、逻辑回归、SVM、k-NN | 可能仍受异常值影响 |
| 最小-最大缩放(Min-Max) | 把值压进固定范围 | 想把范围控制在固定区间时 | 容易被极端值牵引 |
| Robust scaling | 按中位数和分位数对齐 | 异常值很显眼时 | 解释起来可能没那么直观 |
目标不是把这张表背下来,而是要抓住:scaling 也不是一个单一技术名,而是在选择 用什么标准重新对齐数轴。
如果再像下面这样读,尺度调整也会更不容易混淆。
| 问题 | 尺度调整能回答的部分 | 它回答不了的部分 |
|---|---|---|
| 数值范围差太大,导致比较不公平吗 | 是 | 这个特征本身有没有用 |
| 大数字轴在支配距离和优化吗 | 是 | 这个数字里有没有混进泄漏 |
| model 会不会学得更稳定 | 经常会 | 性能一定会不会提高 |
也就是说,尺度调整是用来对齐 比较基准 的工具,不是拿来代替特征选择或泄漏检查的工具。
为什么类别(categorical)值不能直接塞进去¶
像城市名、会员等级、商品类别这样的类别值,在现实里很自然,但很多 model 不能直接计算字符串本身。
因此,类别值通常要先经过编码(encoding),再改成数值表达。
类别编码,就是把每个类别改造成可计算的数值表示的过程。
| 原始值 | 编码后的示例 |
|---|---|
city = Seoul | [1, 0, 0] |
city = Busan | [0, 1, 0] |
city = Incheon | [0, 0, 1] |
这种方式通常就叫 one-hot encoding。
重要的不是 把字符串翻成了数字,而是 把表达改造成了 model 能计算的形式。
这里初学者常见的混淆是:变成数字之后,就自动带有顺序了。但如果像 Seoul=1、Busan=2、Incheon=3 这样随便给数字,model 就可能误以为这里存在本来并不存在的大小关系。所以,one-hot encoding 更适合被理解成:它保留类别差异,但不强行制造不存在的顺序。
也就是说,编码不是 把字母变成数字,而是在决定 类别之间的差异,要用什么样的数值结构表现出来。如果漏掉这一点,预处理看起来就像格式转换,实际上却会错过它在处理“意义保存”这个问题。
在入门层面,编码方式可以粗略区分成下面这样。
| 方式 | 它在做什么 | 什么时候比较自然 | 注意点 |
|---|---|---|---|
| one-hot encoding | 每个类别拆出一列 | 没有顺序的一般类别值 | 类别很多时,列数会增加 |
| 类顺序编码(ordinal-like) | 给类别放上有序值 | 类别本来就真的有顺序意义时 | 如果造出了不存在的顺序,就会扭曲 |
| 频率/统计型变换 | 把类别改成别的统计值 | 高级场景里可能会用 | 需要和泄漏风险一起检查 |
这一节虽然以 one-hot encoding 为中心,但关键习惯是先区分 这个类别到底有没有真实顺序。
例如,下面这两种情况表面上都是字符串,但读法会不同。
| 类别示例 | 性质 | 更自然的入门判断 |
|---|---|---|
bronze/silver/gold | 可能真的带有顺序 | 要谨慎判断该如何反映顺序意义 |
Seoul/Busan/Incheon | 没有顺序的区分类别 | 更自然的是像 one-hot encoding 这样不带顺序的表达 |
也就是说,编码不是 消灭字符串的技术,而是 把类别意义保存在可计算结构里的技术。
换成业务场景可以这样看。
| 原始值 | 为什么难以原样保留 | 改完之后得到什么 |
|---|---|---|
会员等级 gold/silver/bronze | 很多 model 无法直接计算字符串 | 变成按等级拆开的特征列 |
配送地区 Seoul/Busan/Incheon | 地区名本身不容易做数值运算 | 可以分别读出地区模式 |
设备类型 ios/android/web | 有类别差异,但没有大小比较意义 | 用可计算向量表达类别区分信号 |
细部学习内容¶
在实务里,预处理通常会以什么问题的形式出现¶
预处理很容易被记成“填平均值”“套 scaler”“选 encoder”这样的单个技术名。但在实务里,人们通常是反过来,从 到底出现了什么输入问题 出发。前面看到的缺失值、尺度、类别表达问题,本质上都只是这个现场问题的不同样子。
也就是说,在现场里,通常会按下面这个顺序思考。
- 现在进来的值里,什么地方不稳定?
- 这种不稳定会怎样扭曲学习和评价?
- 应该用什么预处理规则来降低这种扭曲?
把这个流程画成最简单的图,大概就是下面这样。
flowchart TB
A["原始输入<br/>缺失 / 尺度差异 / 类别文本"]
B["诊断输入问题<br/>能计算吗?<br/>比较公平吗?<br/>规则可复用吗?"]
C["选择预处理规则<br/>填补 / 缩放 / 编码"]
D["一致的模型输入<br/>训练 和 测试 使用同一规则"]
A --> B
B --> C
C --> D
这张图的核心是:预处理不是 背技术应用顺序,而是 诊断输入问题 -> 选择变换规则 -> 重构一致输入 的流程。
如果把典型场景整理成表,会像下面这样。
| 业务场景 | 输入里看见的问题 | 先想到的预处理判断 |
|---|---|---|
| 电商流失预测 | 购买金额很大、访问次数很小、来源渠道是字符串 | 检查数值缩放 + 编码渠道 |
| 广告点击预测 | device、region、campaign 等类别很多,而且部分值为空 | 类别缺失处理 + 编码 |
| 医院预约爽约预测 | 预约路径和问诊信息部分缺失 | 先判断缺失是单纯空白还是额外信号 |
| 制造传感器异常检测 | sensor 单位彼此差异很大,而且有采集失败区间 | 分开制定缩放规则和采集失败规则 |
| 客户聚类(clustering) | 年龄、销售额、访问频率的单位差异很大 | 距离计算前优先检查尺度调整 |
| 贷款审批辅助 | 解释很重要,且缺失和类别输入并存 | 防止泄漏 + 保持可解释的变换规则 |
这张表的目的不是再添加新技术,而是让读者把前面看过的缺失值处理、尺度调整、编码,重新和实际业务里会出现的问题连起来读。
如果再具体一点看,实务例子通常会重新收拢成下面三个判断轴。
| 实务判断轴 | 现场会问的问题 | 连接到的预处理工作 |
|---|---|---|
| 可计算性 | model 能直接计算这个值吗 | 缺失值处理、编码 |
| 可比较性 | 和其他特征放在一起时,大小比较公平吗 | 尺度调整、归一化 |
| 可复现性 | 训练时用的规则,在线上还能一模一样复现吗 | 分开 fit/transform、构造 pipeline |
因此,预处理不是单纯的准备工作,而是 把现实数据变成可计算、可比较、可复现输入 的设计阶段。
预处理中经常出现的误解¶
在实务初期,下面这些误解尤其常见。
| 常见误解 | 为什么有问题 | 更准确的理解 |
|---|---|---|
| 预处理只要清洗好就结束 | 会漏掉 model 和输入表达之间的相互作用 | 预处理是在设定 model 将要读取的坐标 |
| 所有数值型都必须一律 scaling | 连像树模型这样没那么敏感的家族也会被硬套同一规则 | 不同 model 家族的敏感度不同 |
| 缺失值先填上就解决了 | 缺失本身可能就是信号 | 先解释为什么会缺 |
| 只要把字符串变成数字,编码就结束 | 可能造出不存在的顺序并导致扭曲 | 要同时看可计算性和意义保存 |
为什么 train data 和 test data 必须使用同样规则¶
预处理中最常见的错误之一,就是把 train data 和 test data 混在一起。
scikit-learn 的 common pitfalls 文档强烈建议下面这些做法。
- 先把数据划分成 train/test
- 只在 train data 上做
fit和fit_transform - 对 test data 只做同一规则下的
transform - 用 pipeline 会更容易守住这个边界
test data 是拿来评价的,不是拿来学习预处理规则的。
如果简化来看,大致就是下面这样。
flowchart TD
A["训练 划分"]
B["拟合预处理<br/>学习填充值 / 缩放 / 编码规则"]
C["转换 训练"]
D["测试 划分"]
E["转换 测试<br/>只能复用已学到的规则"]
A --> B --> C
B --> E
D --> E
这张图最核心的是:fit 只会在训练数据上发生一次。
这个差别实际上会制造性能幻觉。
| 错误流程 | 为什么有问题 |
|---|---|
| 先看全体数据算平均值,再切 train/test | test 信息混进了训练规则 |
| 先看全体数据整理编码类别,再做评价 | 结果可能比部署前的真实情况更乐观 |
| 连 test data 也一起参与缩放基准计算 | 评价可能会显得比实际更好 |
也就是说,即使不改 model 结构,预处理里的泄漏(leakage)也足以扭曲评价数字。
如果更直接地看这个差别,可以像下面这样读。
| 流程 | 表面结果 | 实际解释 |
|---|---|---|
| 只在 train 上学习平均值、尺度、类别规则 | 验证分数也许更保守,但更可信 | 更接近部署前的真实情况 |
| 用全体数据学规则后再评价 | 分数可能更漂亮 | 这可能是 test 信息混入造成的幻觉 |
也就是说,预处理规则到底是在哪里学出来的,和 model 选择一样,是非常重要的评价条件。
为什么 pipeline 和 ColumnTransformer 经常出现¶
在实务里,数值列和类别列经常要分开处理。
- 数值列: 缺失值修正 + 尺度调整
- 类别列: 缺失值修正 + one-hot encoding
scikit-learn 的 Pipeline 文档展示了怎样把 fit 和 transform 这些步骤连起来,而 ColumnTransformer 文档则展示了如何对不同的列组应用不同变换。
| 工具 | 入门层面的意思 |
|---|---|
| Pipeline | 把变换阶段和 model 学习阶段串成一条线 |
| ColumnTransformer | 按列的种类分开应用不同预处理规则 |
也就是说,预处理不只是知道单个技术就够了,还必须进一步走向 可以重复应用同样规则的结构。
从课程结构上看,这个结构也很重要。因为后面的 model selection 和 tuning 章节里,实际比较的往往不是 只有 model,而是 预处理 + model 的组合。
应该先抛出哪些预处理问题¶
比起背技术名,预处理首先更重要的是迅速诊断:当前输入到底在制造什么问题。
| 输入状态 | 最先抛的问题 | 优先检查的预处理 |
|---|---|---|
| 值是空的 | 为什么会空,空本身是不是信号 | 缺失值处理、缺失标记评估 |
| 数值尺度差很大 | 距离或优化是不是被某一列拖着走 | 标准化、归一化评估 |
| 混有字符串/类别 | model 能不能直接计算这个值 | one-hot encoding 等类别变换 |
| train/test 可能规则不同 | 规则在哪里学、在哪里重用 | 分开 fit/transform、pipeline |
| 线上输入质量经常波动 | 部署后还能不能复现同样变换 | 按列固定规则、结构化 pipeline |
这张表会帮助读者把预处理读成:不是 套工具的清单,而是 对输入问题的回应规则。
一个好的预处理说明,通常至少要能被概括成下面这几句话。
- 输入里到底有什么问题?
- 为什么这个问题会妨碍 model 计算?
- 用什么规则把它改了?
- 同样规则是怎样在 train 和 test 上一致复用的?
如果这四句话说不出来,预处理很可能只剩技术名,而判断理由是空的。
再补上一句话,会更完整。
- 这个规则在哪些 model 家族里尤其重要?
例如,尺度调整在 k-NN、SVM、线性模型里更敏感,而编码在大多数不能直接读字符串的 model 里几乎是必需的。也就是说,预处理说明虽然从输入问题开始,但最后一定要接到 model 的连接上,密度才会够。
案例及示例¶
案例 1. 即使是同一份客户数据,没有预处理规则时比较也会崩掉¶
一个电商团队正在做客户流失预测实验。人最先看的标准,是 最近购买金额、访问次数、来源渠道、会员等级 这样的输入。
问题在于,这些值彼此并不统一。购买金额是几千量级的数字,访问次数可能只有个位数,来源渠道是字符串,而有些客户的收入或地区信息还是空的。这样直接送进 model 的话,有些列根本无法计算,有些列则会在距离计算或优化里获得过大的影响力。
这时,预处理就成了 把输入改造成 model 能处理表达的规则。你要先决定缺失值按什么标准来填,数值型要不要做尺度调整,类别型要怎样编码,然后把同样规则应用到训练数据和测试数据上。尤其重要的是,不能在测试数据上 fit,而只能复用训练数据上学出来的规则。
能验证的结果会体现在预处理前后对比和数据切分流程里。比如,尺度调整前后的性能差异、字符串列有没有编码、训练数据上算出来的中位数有没有原样用在测试数据上。检查这些之后,就能说明为什么预处理不是单纯清扫,而是输入表达设计。
如果把这个案例画成流程图,就更容易看出:预处理不是清洗一次数据,而是 按列状态立规则,再重复使用同一规则的过程。
flowchart TD
A["客户数据表<br/>混合了缺失值 / 数值 / 类别"]
B["缺失值规则<br/>用 训练 统计量填补"]
C["数值规则<br/>缩放选定列"]
D["类别规则<br/>编码渠道 / 等级"]
E["同一套变换规则<br/>复用到 验证 / 测试"]
F["可比较的模型输入"]
A --> B
A --> C
A --> D
B --> E
C --> E
D --> E
E --> F
案例及示例¶
用一个小例子读出预处理场景¶
考虑下面这份数据。
| age | income | city | label |
|---|---|---|---|
| 29 | 3200 | Seoul | 0 |
| 41 | 6100 | Busan | 1 |
| 无 | 5800 | Seoul | 0 |
| 35 | 无 | Incheon | 1 |
读者在这里最先会看到的问题有下面这些。
- 空着的值该怎么办?
age和income的大小差得很远,要不要调?city是字符串,要怎样表达?
比较自然的入门判断可以是下面这样。
| 列 | 预处理判断 |
|---|---|
age | 可以用中位数修正缺失 |
income | 先用中位数修正缺失,再评估尺度调整 |
city | 用众数修正或原样保留后做 one-hot encoding |
这个例子并不是在强迫只有一个正确答案。重要的是:不同列要抛不同问题。
如果想象把同样数据送给不同 model,判断会更清楚。
| model 候选 | 预处理中最先该注意的点 |
|---|---|
| 逻辑回归 | 数值型尺度和类别型编码 |
| k-NN | 距离计算前的尺度平衡 |
| 决策树 | 编码需要,但尺度相对没那么敏感 |
因此,预处理不是 只看数据就结束的章节,它也是 要和 model 候选一起再读一遍的章节。
这一点重要的原因是,即使是同一份数据,真正被拿来比较的实验单位常常是 预处理 + model 的组合。也就是说,说自己在比较逻辑回归,很多时候实际比较的是 缺失值处理 + 尺度调整 + 编码 + 逻辑回归 这个整套组合。
练习与示例¶
用 Python 例子按顺序看缺失值、尺度、编码¶
下面这个例子是为了说明概念,用纯 Python 简化出来的预处理流程。
问题场景:
- 预处理不是一下子结束的魔法,而是会依次经过缺失值处理、尺度调整、编码的过程
输入:
- 混有缺失值的行列表
rows
期待输出:
- 缺失值填补后的结果
- 做过尺度调整的数值型
- 做过编码的类别值
要确认的概念:
- 预处理是把数据改造成可计算输入矩阵的顺序工作
- 分开输出每一步的结果,会更清楚看到发生了什么变化
执行结果如下。
这个例子一次展示了三件事。
- 缺失值被填了
- 数值型被移到了可比较的尺度上
- 类别值被改造成了可计算的向量
也就是说,预处理不是把数据弄得更漂亮,而是在把它变成 可计算的输入矩阵(matrix)。
用 Python 例子看尺度如何改变距离计算¶
如果直接去算距离(distance),就会更直观地看出为什么尺度调整重要。
问题场景:
- 在基于距离的 model 里,哪条轴的取值范围更大,会扭曲“谁更接近”的判断
输入:
- 原始点
point_a,point_b,point_c - 做完尺度调整后的点
scaled_a,scaled_b,scaled_c
期待输出:
- 原始距离
raw_ab,raw_ac - 调整尺度后的距离
scaled_ab,scaled_ac
要确认的概念:
- 同样的样本,在尺度调整前后,距离关系可能会改变
- 预处理不是把 model 输入变好看,而是在对齐计算基准
执行结果如下。
这个例子展示的是:在原始距离计算里,收入(income)轴几乎压倒了一切;而在尺度调整之后,年龄(age)和收入就可能以更相近的权重被读取。
因此,在 k-NN、SVM、线性模型这些章节里,预处理说明会再次变得重要。
细部学习内容补充¶
在 scikit-learn 里经常看到的预处理工具名¶
没必要把所有名字都背下来,但至少知道有哪些类别,后面章节的例子会更容易读。
| 类别 | 常见名字 |
|---|---|
| 缺失值处理 | SimpleImputer |
| 标准化 | StandardScaler |
| 范围缩放 | MinMaxScaler, MaxAbsScaler |
| 对异常值没那么敏感的缩放 | RobustScaler |
| 类别编码 | OneHotEncoder |
| pipeline | Pipeline, make_pipeline |
| 按列变换 | ColumnTransformer |
这里要留下的标准不是 API 记忆,而是先理解每个工具到底是为了解决 什么表达问题 才出现的。
检查清单¶
- 你现在遇到的输入问题,能不能先分到缺失值、尺度、类别表达中的哪一类?
- 你有没有把
fit和transform分开,避免 test data 混进预处理规则学习? - 你能不能把预处理解释成不是 model 前的清扫,而是
可复现的输入表达设计? - 你能不能说明预处理是把输入改成更合适表达的阶段,而且缺失值、尺度、类别表达是不同问题?
- 你能不能说明为什么
fit只能在训练数据上做,而测试数据只能用同样规则做transform? - 你能不能说明 pipeline 和 column transformer 是让预处理规则可重复使用的结构?
出处与参考资料¶
- scikit-learn,
8.3. Preprocessing data, scikit-learn User Guide, 确认日期: 2026-06-26. https://scikit-learn.org/stable/modules/preprocessing.html - scikit-learn,
8.4. Imputation of missing values, scikit-learn User Guide, 确认日期: 2026-06-26. https://scikit-learn.org/stable/modules/impute.html - scikit-learn,
8.1. Pipelines and composite estimators, scikit-learn User Guide, 确认日期: 2026-06-26. https://scikit-learn.org/stable/modules/compose.html - scikit-learn,
12. Common pitfalls and recommended practices, scikit-learn User Guide, 确认日期: 2026-06-26. https://scikit-learn.org/stable/common_pitfalls.html - scikit-learn,
12. Common pitfalls and recommended practices, scikit-learn User Guide, 确认日期: 2026-06-26. https://scikit-learn.org/stable/common_pitfalls.html