原始数据从来都不干净
在前两篇指南里,你已经认识了什么是数据集,见过了特征与标签,也把数据切成了训练、验证、测试三份。但那次划分假设了一张行列整齐的表格。真实数据几乎从不这么乖。问卷里有空着没填的题;传感器一掉线就把温度记成 -999;某一列写着「Yes/No」,另一列却混着「yes」「y」和「YES」。在模型能学到任何东西之前,总得有人先把这张表整治得诚实可靠。这项工作就是数据清洗,而在大多数真实项目里,它吃掉的时间远比建模本身还多。
先弄清楚「干净」到底指什么会很有帮助。一张干净的表,每一列只有一个含义,每一行只对应你所研究的一个对象,单位统一,而且每个值都名副其实。清洗就是为达到这种状态而做的那些不起眼的手艺活:揪出不可能的取值、统一拼写、修正单位,并决定如何处置那些必然存在的缺口。这里头没有什么高深数学,但每一步都决定了你之后的模型,是建在磐石上还是建在沙地上。
缺失值:别一删了之
最常见的毛病是空洞——某个值压根就不在那里。偷懒的做法是把每一行有缺口的都删掉,但这可能丢掉你大部分数据,更糟的是它可能带来偏差:如果低收入者更常跳过薪资那一题,删掉这些行就悄悄把你的数据集向富裕人群倾斜了。更稳妥的替代方案是 插补——用一个合理的猜测把缺口填上。最简单的猜测,对数值是用该列的均值或中位数,对类别是用该列出现最多的取值。
但值为什么会缺?有时纯粹是随机噪声(表单页面卡了一下)。有时这种「缺」本身就是一个信号——空白的「最近一次购买日期」也许意味着这位顾客从未买过任何东西。这种情况下,诚实的做法不是凭空编一个数字,而是加一个标记列「这里原本是缺失的吗?」,好让模型能利用「缺失」这件事本身。对数值来说,中位数通常比均值更稳妥,因为少数几个极端值就能把均值拽得离典型值很远——而这些极端值,正好把我们引向下一个问题。
离群点:是错误,还是重要的罕见情形?
离群点是指远在常见范围之外的取值——年龄登记成 200 岁的人、标价一美元的房子、一笔一千万的交易。最关键的第一个问题是:*为什么*。200 岁几乎肯定是录入错误,应当更正或剔除。但一笔真实存在的巨额交易,也许恰恰就是你的模型本该抓出来的那桩欺诈。仅仅因为它「看着怪」就删掉,反而会把你最需要的信号一笔勾销。
所以要把离群点的处理当成一项决策,而非条件反射。如果它明显不可能,就更正或删除。如果它真实但极端,你还有更温和的选择:把它截顶到一个合理的上限(把所有超过第 99 百分位的值「裁剪」到那个百分位),或者对整列做变换(取对数能把一长串大值的尾巴拽回到大部队附近)。目的从来不是把数据弄得更好看——而是不让区区几个点主宰模型学到的一切。
把数值放到同一把尺子上
设想两个特征:年龄(大致 0–100)和年收入(大致 0–1,000,000)。在许多算法眼里,收入仅仅因为数字更大就显得「大」了一千倍,于是把年龄完全盖了过去——哪怕年龄其实更重要。对策就是特征缩放:重写各列,让它们的量级彼此可比。对基于距离的方法(k 近邻)、基于梯度的训练、以及任何带正则化的方法来说,这一步不是可选项;少了它,模型就被你随手选的单位悄悄带偏了。
归一化与标准化之下有两个主力配方。归一化(最小-最大缩放)把一列压进固定区间,通常是 0 到 1:减去最小值,再除以极差。标准化则把一列重新缩放成均值为 0、标准差为 1:减去均值,再除以标准差。归一化能把数值约束在整齐的范围内,但对离群点敏感(一个巨大的值就会把整个区间撑开);标准化对离散程度的处理更从容,也是更常用的默认选项。两者都不改变数据的*形状*——只改变它的尺度。
# Standardize, the leak-free way mean, std = compute_on(train) # fit on TRAIN only train = (train - mean) / std val = (val - mean) / std # reuse train's numbers test = (test - mean) / std # never refit here
把类别变成数字
缩放的前提是数字,可有很多列装的是文字:城市、血型、产品类别。分清类别特征与数值特征的区别,就赢了一半。数值特征带有真实的顺序和算术意义——30 度确实比 20 度热,而且差距正好是 10。类别特征则是标签:「红」「蓝」「绿」之间没有内在顺序,蓝也绝不是红的「两倍」。有些类别是有序的(小 < 中 < 大)——那叫定序,你可以把它映射成名次。但大多数并非如此。
经典陷阱是把「红=1、蓝=2、绿=3」这样标上去再喂进模型。于是模型就以为绿 > 蓝 > 红,还以为蓝恰好落在正中间——这套虚构的关系,是你不经意间自己造出来的。诚实的解法是 独热编码:把一个类别列拆成好几个「是/否」列,每个取值占一列。某一行是「蓝」,就变成 是红=0、是蓝=1、是绿=0。没有假的顺序,没有假的距离——只是为每一种可能性立一个明确无误的标记。
黄金法则:在训练集上拟合,再套用到所有数据
这里的每一步——插补值、缩放的均值与标准差、离群点的截顶界限,乃至「到底存在哪些类别」——都是从数据中学来的参数。还有一条铁律,把本篇指南和你的数据划分牢牢绑在一起:这些参数只能从训练集学,然后把冻结下来的结果套用到验证集和测试集。一旦偷看了测试集的统计量,你就让答案泄进了准备阶段;分数会好看得不行,到了真实世界却轰然崩塌。这是整条流水线里最常见的新手错误,没有之一。
- 先理解空洞:在填补任何东西之前,先弄清每个值*为什么*会缺。
- 逐类排查离群点——更正不可能的,保留有意义的罕见值,对其余的温和地截顶或变换。
- 对类别列做独热编码;对数值列做缩放或标准化。
- 每一种变换都在训练数据上拟合,再把完全相同的那组数字套用到验证集和测试集。
清洗谈不上光鲜,也很少有「彻底完工」的时候——随着你对问题理解加深,还会一次次回头打磨它。但好模型的胜负,往往就在这里悄悄分晓。手握一张干净、缩放过、诚实编码过的表格,你就为下一步做好了准备:把现有的这些列,雕琢成能让模型*更容易*看出规律的特征。