ARTICLE DETAIL

资讯详情

深耕网站视觉设计与运营推广的一线实战洞察。

TPOT自动机器学习库实战指南:从遗传编程到Pipeline优化

TPOT自动机器学习库实战指南:从遗传编程到Pipeline优化 1. 先看懂TPOT在自动什么遗传编程与pipeline搜索空间我最初对AutoML工具持保留态度总觉得它们把建模过程包装成一个黑盒出了结果也没法向业务方解释。后来被一个时间紧、特征杂的分类任务逼到墙角才认真上手了TPOT。TPOTTree-based Pipeline Optimization Tool是一个基于遗传编程的自动化机器学习库它的工作方式不是单纯帮你选模型调参而是直接进化出一整条可运行的sklearn流水线包括缺失值处理、特征缩放、降维、特征选择、建模乃至模型堆叠最终导出一份能脱离训练环境运行的Python脚本。如果你已经在用scikit-learn做建模希望从无尽的调参循环里解放出来或者你想知道遗传算法这样的组合优化思路在机器学习里能不能落地这篇文章应该对你有用。我下面会把我实际使用中踩过的坑、参数配置逻辑和上线经验都写出来而不是只堆API文档。1.1 一条流水线就是一个个体我见过不少朋友把TPOT当成一个加强版网格搜索以为它就是在几个模型里挑效果最好的跑完拿到个分数就算完事。这个理解不能说全错但漏掉了TPOT最有价值的部分——它在搜索的是一整条机器学习流水线而不是单个模型。你要真正用好它第一件事就是把它的搜索空间和进化逻辑搞清楚否则后面调参、看结果、排错都会一头雾水。TPOT基于遗传编程思想。每一轮进化里每个候选解不是一组超参数而是一棵可执行的分支结构树叶子是算子比如StandardScaler、PCA、RandomForestClassifier内部节点是组合方式比如make_pipeline顺序拼接、make_union并行合并。这棵树展开以后就是你很熟悉的sklearn pipeline先做Imputer补缺失值再做MinMaxScaler归一化同时并行走一个PCA分支最后把特征拼接喂给模型。TPOT要做的就是通过遗传算法把这棵树的形态、算子选择、算子参数一起优化出来。打个比方普通调参像是你在固定的几条流水线前面换套餐今天换算法、明天换超参来回试。TPOT则像把整条流水线当成一只活物让成千上万只流水线个体互相竞争、交配、变异进化出更适合你数据的那个结构。它的评估方式很朴素每个个体拉到训练集上做交叉验证分数高的个体更容易被选中进入下一代。遗传操作方面TPOT的默认设置是变异概率0.9、交叉概率0.1。注意这和很多遗传算法教程不太一样变异占了绝大多数。而且TPOT的变异不是只改一个超参数那么简单它可能替换掉树上的某个算子、调整算子的参数候选值、增删一个预处理步骤甚至改变并行分支的结构所以它能探索的空间比普通random search大很多代价当然也大很多。1.2 搜索空间里到底有哪些算子TPOT内置的搜索空间很全分类和回归是两套不同的候选算子集合。拿分类任务来说常见的候选包括类别典型算子作用数据清洗Imputer、ZeroCount补缺失值、处理零值列特征缩放StandardScaler、MinMaxScaler、RobustScaler让模型对尺度不敏感特征选择SelectKBest、SelectPercentile、VarianceThreshold砍掉无效特征降维PCA减少特征维度特征生成PolynomialFeatures生成交叉项和高次项类别编码OneHotEncoder将离散数值列转成独热向量模型LogisticRegression、RandomForestClassifier、GradientBoostingClassifier、XGBClassifier、SVC、KNeighborsClassifier等最终分类器集成StackingEstimator把一个模型的输出作为特征喂给另一个模型这里要特别提醒一下集成这个算子TPOT里的StackingEstimator会生成两层的stacking结构。也就是说它搜索的不光是单模型的pipeline还包括先用A模型预测把预测结果作为新特征再交给B模型这种复杂结构。这是TPOT能打的重要原因之一但也是它的运行速度比普通搜索慢一个量级的主要原因。你在规划时间预算时一定要考虑到这一点。回归任务则对应TPOTRegressor模型候选换成Lasso、Ridge、ElasticNet、SVR、RandomForestRegressor、GradientBoostingRegressor、XGBRegressor等整体进化机制完全一样只是算子池不同。理解了这些下面就能比较从容地配置参数了。2. 分钟级跑通第一个TPOT任务安装到最小用例2.1 环境要求与安装TPOT运行在Python 3.8以上环境依赖numpy、scipy、scikit-learn、pandas、joblib等。直接pip安装就行pip install tpot这里有一个容易忽略的事情TPOT搜索空间里包含XGBoost家族但如果你不装xgboost程序不会报错只是自动跳过这批候选算子。如果你确定想用XGB家族请提前安装pip install xgboost我在实际项目里习惯把所有依赖一次装齐避免跑两小时后才发现某个算子因为缺包被跳过那种浪费时间的体验很糟糕。另外TPOT对sklearn版本比较敏感建议用相对较新的版本老版本搭配新TPOT偶尔会有OneHotEncoder参数不兼容之类的报错。2.2 最小可用脚本以经典的iris数据集为例一个完整流程是这样的from tpot import TPOTClassifier from sklearn.datasets import load_iris from sklearn.model_selection import train_test_split X, y load_iris(return_X_yTrue) X_train, X_test, y_train, y_test train_test_split( X, y, test_size0.25, random_state42 ) tpot TPOTClassifier( generations5, population_size20, offspring_size20, mutation_rate0.9, crossover_rate0.1, scoringaccuracy, cv3, random_state42, verbosity2, n_jobs-1 ) tpot.fit(X_train, y_train) print(f测试集准确率: {tpot.score(X_test, y_test):.4f}) tpot.export(iris_best_pipeline.py)这段代码有几个细节值得说明。首先是generations和population_size在iris这种小数据集上配置小一点几十秒内就能跑完如果你在真实业务里照抄这套配置跑出来的分数往往远低于上限。合理用法是先用小配置验证流程没问题再逐步放大。其次是verbosity2它会让你看到每一代的最优分和耗时首次运行时你会看到类似Generation 1 - Current pipeline best score: 0.9818的输出这时候你心里大概有数它在干什么。verbosity3会输出更细的评估日志排错时很好用。2.3 首次运行后你会拿到什么跑完之后tpot.score()在测试集上的分数只是一个参考真正的产出是最后export出来的iris_best_pipeline.py。这个文件里是一段可以直接运行的sklearn pipeline代码内容大体是导入依赖、定义训练变量、构造pipeline对象然后训练、评估。下面是一个简化的示意不是完整文件import numpy as np from sklearn.linear_model import LogisticRegression from sklearn.model_selection import train_test_split from sklearn.pipeline import make_pipeline, make_union from sklearn.preprocessing import StandardScaler from tpot.builtins import StackingEstimator exported_pipeline make_pipeline( StackingEstimator(estimatorLogisticRegression()), StandardScaler(), LogisticRegression() ) exported_pipeline.fit(training_features, training_target_vals) results exported_pipeline.predict(X_test)看到这段代码你就明白为什么我说TPOT的产出是代码结构而不是一个模型文件它给你的是完整可读的pipeline描述方便你审查、修改、上线部署。这也是它比很多纯黑盒AutoML平台更适合工程师的原因。注意文件里包含from tpot.builtins import StackingEstimator这一点后面还会再提关系到生产部署时是否需要保留TPOT依赖。3. 真正决定搜索结果质量的参数组合generations、population_size和时间预算3.1 参数逐一拆解如果只记一条原则我建议是不要用默认参数跑真实数据。TPOT的默认值是population_size100、generations100意味着最多要评估超过一万条完整pipeline每条pipeline再乘以cv折数和单次拟合时间在中等规模数据上跑上几天几夜都是常事。所以你需要理解每个参数的意义再按数据规模给预算。generations是遗传进化的代数。每一代都会从上一代里挑表现好的个体进行变异和交叉产生新一版个体。代数越多搜索越充分但边际收益会递减后面的代数往往在做细节精调很难出现质的飞跃。population_size是每一代保留的个体数可以理解为同时探索的平行世界数量越大一次能覆盖的算子组合越多但每代的评估成本线性增加。offspring_size默认等于population_size表示每一代新生成的子代数量。总评估次数大致可以用这个公式估算总评估次数约等于 population_size generations × offspring_size举例generations10、population_size30、offspring_size30那大约要评估30 10×30 330条pipeline。注意每一条pipeline在交叉验证时还要拟合cv次模型所以真实耗时大约是330乘以cv次模型拟合。这个估算对规划时间非常有用。mutation_rate和crossover_rate决定遗传操作的类型比例TPOT默认是0.9和0.1意味着子代绝大多数来自变异而不是交叉。不少从经典遗传算法入门的人会不适应但这是TPOT官方推荐值我一般不会动它除非你明确知道自己要多保留父代结构。max_time_mins是整体搜索的时间上限。如果你不设TPOT会老老实实跑满generations设了以后它会根据已用时间和剩余代数调整这一代要评估的个体数量对真实项目控制成本非常关键。max_eval_time_mins是单条pipeline的评估时间上限数据量大时格外要关注某条pipeline如果是SVC配合高次PolynomialFeatures可能单折上就能耗掉你所有耐心对单条评估设上限可以让TPOT及时放弃走火入魔的个体。scoring和cv控制好的定义。scoring可以传accuracy、f1、roc_auc、neg_mean_squared_error等sklearn标准度量名cv默认是5也可以传KFold、StratifiedKFold对象。这两个参数直接决定进化方向务必按业务指标来设。如果你的分类任务是极度不平衡的却用accuracy做适应度TPOT进化出来的模型很可能只会瞎猜多数类上线后完全不能看。3.2 一套比较稳的起手配置我给自己用的起手配置大概是这样的tpot TPOTClassifier( generations8, population_size40, offspring_size40, mutation_rate0.9, crossover_rate0.1, scoringroc_auc, cv5, max_time_mins30, max_eval_time_mins3, random_state42, n_jobs-1, verbosity2 )在这套配置下数据上万行也能在半小时左右看到不错的候选。先跑一遍看分数和耗时再决定要不要放大population_size、延长max_time_mins。记住一个经验同一份数据反复修改配置跑TPOT时因为遗传算法有随机性不同random_state得到的结果差异可能不小。固定random_state能帮你对比两次实验的差异到底来自参数还是运气。如果条件允许还可以用多个随机种子各跑一次把几个导出pipeline的预测结果做投票融合通常能再涨一点稳定性。下面放一个参数速查表参数默认值作用我的建议generations100进化代数小数据5-10大数据先1-3看效果population_size100每代个体数20-50起步offspring_sizepopulation_size每代子代数默认即可mutation_rate0.9子代变异比例一般不调crossover_rate0.1子代交叉比例一般不调max_time_minsNone总时间上限真实项目务必设置max_eval_time_minsNone单条评估上限大数据建议设置scoringaccuracy适应度函数按业务指标设置cv5交叉验证折数小数据可加大random_stateNone随机种子对比实验务必固定4. 导出pipeline的正确姿势把它变成可生产化的代码4.1 export和fitted_pipeline_的差异TPOT训练结束后模型对象上有两个常用入口。一个是tpot.fitted_pipeline_它直接就是训练好的sklearn pipeline对象你可以立刻拿它predict、用joblib持久化。另一个是tpot.export(xxx.py)它把整个搜索到的最优pipeline结构导出成Python脚本。我的习惯是两者都用实验阶段用fitted_pipeline_做快速验证确定上线的方案后用export生成脚本再把脚本训练部分改造成加载历史数据、重训模型、保存参数。为什么不直接pickle fitted_pipeline_因为pickle文件跟库版本强绑定升级sklearn后经常打不开而导出脚本是纯代码你可以在新环境里重新训练可移植性好得多。export出的脚本有一个容易被忽略的点如果最终pipeline里用到了TPOT自带的StackingEstimator、ZeroCount等tpot.builtins算子那么脚本里会有from tpot.builtins import ...这意味着部署环境仍然需要安装TPOT库来提供builtins不是有些人想当然的完全脱离TPOT。如果你的最终结构没用到这些算子部署环境则只需要sklearn等常规库。4.2 生产环境的改造思路export出来的脚本含有一堆训练时的变量赋值直接扔到生产环境往往没法用。我在生产项目里的做法是把它抽成一个model.py只保留pipeline构造部分训练和预测接口自己写import joblib from sklearn.pipeline import make_pipeline # 把export文件里的pipeline构造行复制过来 pipeline make_pipeline(...) pipeline.fit(X_train, y_train) joblib.dump(pipeline, model_v1.pkl)这里最容易被忽略的是列名顺序。export脚本里的pipeline在fit时用的是完整DataFrame预测时如果接口传来新数据的列顺序不一致某些算子比如PolynomialFeatures虽然不会报错但特征语义已经错位了。所以生产环境一定要在预测入口加列检查按训练时的列顺序重排。还有一个实用技巧TPOT在进化时会把每个个体放在验证集上评分导出脚本里给出的分数是CV分数不是最终泛化分数。我习惯在导出后用独立的测试集再评估一次确认分数没有水分。如果CV分数很高但测试集分数掉得厉害多半是搜索过程里已经对验证集产生了过拟合这时需要用更保守的参数或换一个随机种子重新跑而不是硬着头皮上线。5. 实战中的三道坎时间、内存和类别特征过拟合5.1 时间预算和早停策略这里最大的问题是TPOT没有严格意义上的early stopping参数。虽然设max_time_mins可以硬性停止但它停止时可能正处于某个没跑完的代际结果未必理想。我的做法是结合日志判断把verbosity调到2每代打印最优分连续几代分数没有明显变化说明搜索曲线进入平台期这时即使还有时间预算也基本不值得继续等下去了。你可以手动中断然后用当前最优的export文件作为结果。这么做比硬跑满默认参数省下大量时间。在更大数据集上我建议先用sample比如随机抽2万行跑一次找出大致的pipeline结构和特征处理思路再在全量数据上用固定结构训练。这是TPOT在真实业务里最高效的使用方式——它在小样本上帮你探索结构和算子组合而不是直接在大数据上穷举。我踩过一次太深的坑一开始就把100万行全量数据喂进去跑了几个小时才意识到连一次像样的评估都没完成。5.2 内存爆炸多进程加复杂pipeline结构内存问题几乎一定会遇到。n_jobs-1会让每个worker fork一份数据副本如果你的数据是几GB的DataFrame几十个worker同时展开内存直接顶满机器卡到连kill命令都延迟。缓解方案有几条。第一n_jobs不要盲目开满最多开到物理核心数的一半给系统留点余量。第二数据量大的时候进TPOT之前先做一次保守特征筛选能砍的特征先砍掉。第三给每条pipeline设置max_eval_time_mins避免高复杂度算子长时间占用资源。如果内存实在不够还可以考虑把数据转成float32或者用Memory-mapped方式加载但这样做会牺牲部分算法兼容性不是首选。另外TPOT的早停日志里如果出现大量Timeout基本就是单条评估时间上限设得太低把有潜力的复杂结构都误杀了需要适当调高。5.3 类别特征的坑TPOT对类别特征的容忍度比人们以为的低得多。很多新手直接把带字符串列的数据丢给TPOTClassifier.fit()结果得到一堆ValueError。因为TPOT内部假设你给的所有特征都已经是数值编码它的OneHotEncoder面向的是数值型离散列而不是字符串类别列。所以进入TPOT之前你需要先用LabelEncoder或OrdinalEncoder把字符串列转成整数编码。如果需要保留原始业务解释可以在导出pipeline后把编码器单独保存预测时先编码再进pipeline。这个坑还有一个变体有些列本身是离散ID比如用户ID、城市编码。如果直接丢进TPOT可能被当成连续型数值参与距离计算也可能被OneHotEncoder处理成膨胀的高维稀疏矩阵。一般来说ID类特征应该在进TPOT之前就删掉或者至少在预处理阶段明确处理。可惜TPOT没有专门的categorical_features参数所以这一步要靠你自己在预处理阶段做好。我的经验是把类别特征和连续特征分两批进入pipeline用FeatureUnion这样的结构显式区分比让TPOT自己尝试编码要稳得多。5.4 评估偏差与hold-out验证遗传算法天然有一个问题进化过程和评估数据是耦合的。TPOT每一代都在同一份交叉验证数据上评估各种pipeline虽然它筛选的是CV分数高的个体但这些个体在CV数据上表现好不代表在未知数据上也一定好。这就是搜索过拟合也叫selection bias。标准解法是划分三层数据训练集、验证集、测试集。TPOT只在训练集上进化用验证集观察有没有过拟合最后用从未参与任何选择的测试集做一次最终评估。如果连这个步骤都做不到至少要严格区分train和test并保证test只在全部搜索结束后使用一次。别为了省数据把全部样本都丢给TPOT否则你最后汇报的测试分数根本不具备可信度。我在写项目总结时吃过这个亏当时汇报的准确率漂亮得很换到线上数据立刻掉了好几个点后来才意识到是选择偏差在作祟。6. 进阶玩法自定义算子、并行加速和其他AutoML的取舍6.1 把业务模型塞进搜索空间TPOT默认配置覆盖的模型有限如果你手里有一套业务上很灵的模型可以通过config_dict把它加进去。自定义配置的格式是字典外层键是sklearn类的完整路径内层键是参数名参数值是候选值列表。from tpot import TPOTClassifier my_config { lightgbm.LGBMClassifier: { n_estimators: [50, 100, 200], learning_rate: [0.05, 0.1, 0.2], num_leaves: [15, 31, 63] }, sklearn.ensemble.RandomForestClassifier: { n_estimators: [50, 100, 200], max_depth: [None, 5, 10] } } tpot TPOTClassifier( generations5, population_size20, scoringroc_auc, config_dictmy_config, random_state42, cv3, verbosity2 )注意自定义config_dict里只写模型类时TPOT生成的pipeline里可能不会有预处理步骤。如果你想保留特征处理算子可以先用tpot.config.classifier_config_dict作为基础再加自己的业务模型from tpot.config import classifier_config_dict as default_config default_config[lightgbm.LGBMClassifier] { n_estimators: [50, 100, 200], learning_rate: [0.05, 0.1, 0.2] }这样搜索空间就是默认全部算子加你的模型效果最好。缺点是候选总数量变大时间成本上升所以要配合max_time_mins一起使用。这个自定义能力是TPOT区别于很多闭源AutoML产品的重要特性你可以在不牺牲可解释性的前提下把团队里积累的领域模型塞进搜索空间让遗传算法帮你找到最合适的用法。6.2 分布式与sample优先TPOT的并行设计基于joblibn_jobs-1只是单机多核。如果你的数据量大到单机内存撑不住可以借助joblib对dask分布式后端的支持把pipeline评估分发到集群上。不过分布式环境下每个worker同样需要占用内存数据也要事先分发好并不存在无脑提速的魔法。对大多数团队来说先做采样搜索再全量固定结构训练性价比远高于搭一套分布式TPOT。如果采样都不现实那我建议把任务拆小缩小generations、缩小population_size或者把max_time_mins设为固定的1到2小时。宁可跑出一个次优结果也不想等到服务器OOM或者跑了一整夜发现配置错了。我自己的经验是TPOT这类自动搜索工具越是在资源紧张的时候越需要你先花几分钟想清楚约束条件而不是直接把预算当成默认值。给足时间但没给够内存、给了内存却没给时间这两种配置我都见过翻车。6.3 TPOT和其他AutoML的选型最后聊聊选型。我过去也在项目里用过AutoGluon、H2O AutoML和FLAML。它们各有取舍AutoGluon在表格数据上的集成效果非常强但最终模型复杂、推理较慢调试空间小H2O适合企业级Java生态和需要模型管理平台的场景但引入的依赖较重FLAML很轻快对小数据和高迭代频率场景很舒服但特征工程层面的探索相对克制。TPOT的定位更像是特征工程探索器加可解释基础模型生成器。它不是最快的AutoML也不是永远最强精度的AutoML但它产出的是一棵你能读懂的pipeline树。对于团队里需要沉淀特征处理逻辑、或者要把流程迁移到生产服务的场景这种可读性是实打实的资产。你可以用它来快速验证这个数据集上哪些预处理手段最有效再把手动经验固化到自己的标准流程里。最后说一点我的个人体会。我用了TPOT大约两年最大的感受是它的价值不在一键炼丹而在给你一份可读的搜索报告。每一次跑完我都会去读导出的pipeline看它选了哪些预处理算子、哪个模型、有没有堆叠结构然后反过来问自己为什么这个组合在这个数据上有效这个问题往往比分数本身更有收获。如果你也刚刚开始接触AutoML别急着拿它替代手里的全部建模工作先从一个小数据集跑通、读懂导出代码开始你会发现自动化工具和工程师经验之间的关系不是替代而是互补。
返回列表