2.15 sklearn实战与机器学习实验流程
1.sklearn的API设计:换一个模型几乎不用改代码
说起来,前面那些章节我们聊的多半是神经网络,从零搭网络、写训练循环、调学习率,一手一脚都得自己来。这一章我们换个角度,看看更上面一层的东西,也就是怎么把一个机器学习实验从头到尾跑顺。我们用的工具是sklearn(scikit-learn),它是Python里做表格类机器学习最常用的库。表格类数据,说穿了就是那种一行一个样本、一列一个特征、能塞进Excel里看的数据。前面几章讲过的逻辑回归、KNN、决策树、随机森林、梯度提升,在sklearn里都有现成的实现,调一下就能用。
sklearn最讨喜的地方,是它把所有模型都设计成几乎一样的外壳。你不管用逻辑回归、随机森林,还是支持向量机,三步就能跑通。第一步把模型建出来,第二步调fit方法把训练数据喂进去,第三步调predict方法拿到预测结果。fit就是去拟合,predict就是去预测,这两个名字全场通用。我们记 表示特征矩阵(一行一个样本、一列一个特征), 表示标签(每个样本对应的一个目标值)。fit吃进去的是 这一对,predict吃进去的是新的 ,吐出来的是预测值 ,这里 就是字母 头上戴一顶小帽子,表示模型对真值 的估计。换个模型,这三步几乎一字不改,只要把模型那一行换掉就行。
这种统一接口带来的好处是实打实的。我记得小张刚学那会儿,从逻辑回归换到随机森林,就改了导入那一行和建模型那一行,剩下全部照旧,两分钟就跑通了。要是每个模型各写一套调用方式,光是改语法就够忙半天。
还有一类方法叫transform,它属于预处理那一类。标准化、降维、缺失值填补,这些步骤都遵循同一个套路。先fit学一点统计量(比如均值和方差),再transform把数据按学到的规则改写一遍。fit和transform也能合起来一步走,叫fit_transform,先学后用一气呵成。说到底,整个sklearn的设计哲学就是fit、predict、transform这三个动词,记住它们,大半个库的用法就到手了。
2.一个完整的实验流程:从一份数据到一份评估
光知道接口还不够,我们还得把一个实验该怎么组织讲清楚。我把它拆成几步,一步一步说。
第一步是加载数据。常用的做法是用pandas读一份csv文件,得到一个DataFrame(一种表格结构)。拿泰坦尼克生存预测来举例,乘客信息是一行一个人,列里有年龄、票价、舱位这些。
第二步是探索数据。这一步常被新手跳过,其实最值钱的就是它。先看看每一列的类型,是数值还是文字。再看看有多少缺失,年龄这一列在泰坦尼克里大概缺了两成。还要看标签分布,幸存和遇难的比例大概是多少。这些数字会直接决定后面怎么做预处理。我记得有本讲数据科学的老书里说,一个好习惯是先盯着数据看半小时再动手写代码,深以为然。
第三步是预处理。常见的有三种活要干。第一种是补缺失值,数值列一般用中位数填,文字列一般用一个占位标记填。第二种是标准化,把数值列调到均值为 、方差为 的范围,这里均值记作 (希腊字母,读作mu),方差记作 (sigma的平方, 是标准差)。为什么要标准化呢,因为有些模型(比如支持向量机和KNN)对数值尺度特别敏感,一个特征要是量级太大,会把别的特征彻底盖过去。第三种是编码,把文字标签变成数字,常用的是独热编码,一个取值就新开一列,是它就填 ,不是就填 。
第四步是特征工程。这一步是用人的先验去构造新列。比如从出生日期里推出年龄段,从票价位数里推出是不是头等舱。说起来,在表格数据上,一个好特征带来的提升往往比换一个更复杂的模型还要大,这点和大模型时代的玩法不太一样。
第五步是训练模型。先挑一个简单的当baseline(基线),逻辑回归或者随机森林都行,这一步我们下面还会展开。
第六步是交叉验证评估。交叉验证的做法是把训练数据切成 份(一般 或 ),每次留一份当验证集、其余 份当训练集,轮换 次,把 个得分取平均。这样得到的结果比单切一次要稳得多。我们记一次的得分为 , 是第几轮的编号,那平均分就是:
这里 就是字母 上面加一横,表示 个得分的平均。 是求和符号,意思是从第 份到第 份,把每一轮的 加起来再除以 。我记得有个跑中长跑的运动员说过,单看一次成绩容易看走眼,得多跑几场取平均才看得出真实水平。交叉验证就是这个道理,多切几刀、多跑几轮,估出来的分数才靠得住。
第七步是调参。常用的是网格搜索,把想试的几组超参数列出来,让程序自动把每一组都跑一遍交叉验证,最后挑分数最高的那组。还有更省事的贝叶斯优化,它会根据前面跑过的结果,猜下一组该试哪里。
第八步是在测试集上给最终结果。测试集从头到尾都没碰过,到这一步才能动它一次,给出最终的准确率或者AUC。这一步只能跑一次,不能反复调参反复跑,否则就等于把测试集当验证集用了,分数会虚高,上了真场反而垮。
3.Pipeline和ColumnTransformer:把流程串成一条
刚才那八步里,预处理和模型其实可以串成一条流水线,sklearn里叫Pipeline。把标准化、编码、模型这几步依次塞进去,最后得到的对象还是一个有fit和predict的东西,调一次fit就从头跑到尾,调一次predict就把整条链路走完。这样写既省事又不容易漏步骤。
更妙的是ColumnTransformer。一份数据里,数值列要做标准化,文字列要做独热编码,两类列的预处理方式不一样。ColumnTransformer就是用来按列分流的,你告诉它哪些列走哪条预处理,它自动各走各的,最后拼回一张表。
这两样东西合起来最大的好处,其实是防泄漏,少写代码倒还在其次。什么叫数据泄漏呢,就是在做交叉验证的时候,标准化如果用到了整份数据的均值和方差,那验证集的信息就偷偷漏进训练里了,分数会虚高,一旦换成独立的测试集就现原形。一旦用上Pipeline,每一折里的标准化只在那一折的训练部分上fit,验证部分只transform,信息泄漏这条路就被堵死了。
举个小例子。小明之前手写预处理,整个训练集一起算均值和方差,再切交叉验证,结果交叉验证AUC有0.92,到了测试集掉到0.80,差了0.12,挺大一个坑。换成Pipeline之后,交叉验证AUC降到0.82,和测试集的0.80基本对得上,这才是这份数据的真实水平。那0.10多的虚高分数,多半就是泄漏送出来的。
4.模型怎么选:先看上限再折腾
新手最容易一上来就堆复杂模型,这其实不划算。稳妥的做法是先搭一个简单的baseline,看一眼这份数据到底好不好做。逻辑回归、随机森林这两种,我建议都先跑一遍。逻辑回归能告诉你特征是不是大致线性可分,随机森林能顺便给出特征重要性。这两个跑完,你大概心里就有数了。
一个朴素的实用经验是,表格数据先试梯度提升树(比如XGBoost、LightGBM),这几样我们在第27、28章都细讲过。在大多数Kaggle表格赛里,前排的方案多半是梯度提升的变种。深度学习在表格数据上反而不占便宜,因为表格特征之间没什么空间结构,神经网络那种层层抽象的优势发挥不出来。图像、文本、序列才是深度学习的强项,这点我们前面讲CNN和Transformer的时候都提到过。
调参也别一上来就大动干戈。先试默认参数,再看哪里不对劲。随机森林里树的棵数从100加到500通常还有点用,梯度提升里学习率从0.1降到0.01,一般要配着增加树的棵数。盲目把所有超参数都铺开做网格搜索,跑一晚上未必比一组手挑的默认值强。
5.可复现:固定种子和记录配置
实验跑完了不等于结束,还得让别人(包括一周后的自己)能复现。第一件事是固定随机种子。sklearn里有个参数叫random_state,numpy里有个函数叫seed,统一设成一个固定的数(比如42,程序员的旧梗),这样每次跑出来的数据划分、参数初始化都一样。
第二件事是记录每一次实验的配置和结果。哪个数据集、用了哪几个特征、跑的是什么模型、超参数设成多少、交叉验证得分多少、测试集得分多少,都写下来。我看过一部讲赛车的动漫,主角车队的工程师每场比赛都把调校参数和圈速记在小本子上,回头翻一翻,哪场调对了、哪场调错了,一目了然。机器学习实验也一样,没记下来的实验等于白做,一周后你自己都忘了跑的是什么。
6.从零到评估的小骨架
最后我把上面这些串成一个具体的小项目,用泰坦尼克生存预测做例子。数据是乘客信息,目标是预测一个人有没有幸存。
先加载csv,得到一个DataFrame。瞄一眼前几行,发现年龄缺了两成、舱位缺了七成、登船港口缺了不到一成。标签是幸存与否,比例大约38%对62%,稍微有点不平衡,但不严重。
接着搭预处理。数值列挑年龄和票价,用中位数填缺失再做标准化。文字列挑性别、舱位、登船港口,用众数填缺失再做独热编码。再用ColumnTransformer把这两路并起来,外面套一层Pipeline,最后一段接随机森林。
交叉验证用5折,得分用准确率,跑出来大概0.80。再换逻辑回归跑一遍,大概0.79,差别不大。换成梯度提升,调一下学习率和树的棵数,交叉验证上到0.82左右。
最后在测试集上跑一次,AUC大约0.85,准确率0.80出头。这个成绩在Kaggle公开榜上排不到前面,但作为一个从零开始的baseline已经够看了。后面想再提分,多半得靠特征工程,比如把名字里的头衔提出来当作新列、把家庭规模算进去。说穿了,到这一步,拼的往往是看你对这份数据理得有多细,模型花不花哨倒是其次。
练习
Q1. sklearn 的模型几乎都遵循哪三步调用套路?换模型时要改什么?
三步:先建模型,再调 fit(X, y) 把训练数据喂进去拟合,最后调 predict(X) 拿预测值。换模型时通常只改导入那一行和建模型那一行,剩下的 fit、predict 写法几乎不变。预处理类的则用 fit 学统计量、transform 改写数据(或一步走的 fit_transform)。
Q2. 什么是数据泄漏?为什么把预处理塞进 Pipeline 能防止它?
数据泄漏指做交叉验证时,标准化如果用了整份数据的均值方差,验证集的信息就偷偷漏进训练里,交叉验证分数虚高、到了独立测试集就现原形。把预处理和模型塞进 Pipeline 后,每一折里的标准化只在那一折的训练部分上 fit、验证部分只 transform,信息泄漏这条路就被堵死,交叉验证分数才反映真实水平。
Q3. 做表格数据实验,为什么建议先跑逻辑回归和随机森林这两个 baseline?测试集能反复跑吗?
逻辑回归能告诉你特征是不是大致线性可分,随机森林能顺便给出特征重要性,两个跑完心里就有数了,再决定要不要上梯度提升这种更复杂的模型。测试集从头到尾不能碰,训练彻底结束后只跑一次给最终分;反复调参反复跑测试集等于把它当验证集用,分数虚高、上线就垮。