如果你做过哪怕一次机器学习项目,就一定不会对“把模型一个一个试过来”这件事陌生。我手头最常见的情况是:数据集清理干净后,光是要不要做标准化、选哪个分类器、把几个弱模型叠起来,就能折腾好几个晚上。后来我认真用了 AutoML 库里的 TPOT,才觉得前面有一段时间的效率,完全是被自己浪费掉的。
TPOT 的全称是 Tree-based Pipeline Optimization Tool,翻译过来就是“基于树结构的管道优化工具”。它能做的是在给定数据和时间限制下,自动帮你搜索一套完整的机器学习管道,包括缺失值填补、特征缩放、特征选择、模型选择、超参数调优,甚至多个模型的叠加组合。比起 AutoGluon、H2O 那类偏“重的 AutoML 平台”,TPOT 最大的吸引力在于它跑完会把最优方案导出一份完整的 Python 代码,你可以直接拿去改、拿去部署,而不是被圈在一个黑盒环境里。适合的人是:有一定 sklearn 基础,想省去重复试参时间,又不想放弃对最终模型的掌控力。这篇指南,我会把 TPOT 的原理、安装、实操细节和我在真实项目里踩过的坑一次讲完。
1. 为什么用 TPOT 做 AutoML,而不是继续赌模型玄学
1.1 它解决的核心痛点
普通机器学习项目里,最浪费时间的一环不是训练,而是“选择”。你要决定用随机森林还是 XGBoost,要不要做 PCA,特征选择用 SelectKBest 还是递归消除,超参数取什么范围。这些选择之间是组合爆炸的关系,人工一个个试,本质是在用直觉做搜索。
TPOT 把“机器学习管道搭建”这件事变成了一个自动搜索问题。它由进化算法驱动,每一代生成一批管道,用交叉验证评估这些管道的表现,再通过选择、交叉、变异产生下一代。你用不着在标准化和 MinMaxScaler 之间纠结,也无需事先决定用哪个模型,TPOT 会把这类决策放进管道的搜索空间里,一起找。
我从个人体验出发,它最值钱的一点是:能搜出你的直觉之外有效的组合。比如某个表格数据我试了 LightGBM、XGBoost 都打不到目标分数,TPOT 最后搜出来的是“RobustScaler + 多项式特征 + 线性SVC + 随机森林概率融合”,这种组合我正常情况根本不会手工去试。搜索空间大,并不代表漫无边际,它把特征工程步骤固定成几类算子,把模型固定成常见算法,在凸空间里做启发式搜索,比人工盲试靠谱得多。
1.2 和别的 AutoML 工具对比,TPOT 的位置在哪
用过 AutoGluon 的话,你会觉得它很“霸道”:直接把一堆模型和 stacking 封装好,给定数据就能跑出很强结果。但副作用是,模型和特征处理完全被封装成一块黑砖,想拆开看逻辑很费劲。H2O AutoML 则是典型的企业级平台,功能全面但相对笨重,适合有集群资源的人。Auto-Sklearn 的思路和 TPOT 比较接近,但它依赖元学习(meta-learning)做初始推荐,安装依赖也更重,而且导出管道代码的灵活性没那么强。
TPOT 的真实定位应该是“可解释的 AutoML 搜索器”。它的准确性不见得在每个数据集上都吊打 AutoGluon,但它产出的不是固定的模型文件,而是一个人类可读的 sklearn Pipeline 代码。这意味着,搜索结束后,你能进入管道内部做进一步分析,把黑盒重新变回白盒。
我一般这样选:数据量大、要快速拿高分、不在乎可解释性,用 AutoGluon;有现成分布环境、要做企业级自动建模,选 H2O;但如果是中小规模表格数据,我想在自动化和可控性之间找平衡,同时还想保留后续对模型的直接修改权,TPOT 是性价比最高的。
2. 安装与一次完整 run:TPOT 第一跑要避开的坑
2.1 环境准备和版本陷阱
TPOT 的底层依赖包括 numpy、scipy、scikit-learn、pandas、deap、joblib、tqdm、stopit 这些。绝大多数情况下,一句 pip install tpot 就能装好,但我要特别提醒:版本敏感。
TPOT 对 scikit-learn 的版本兼容并没那么宽松。如果你用的是最新版 sklearn,而 TPOT 还没完全适配,很容易在运行时报一些莫名其妙的错误,比如模块路径变化、某些 estimator 参数被移除。我踩过最典型的一次是 TPOT 在导入阶段就报错,原因就是 sklearn 的impute模块内部结构调整。后来我把项目环境固定到了 TPOT 官方支持的 sklearn 版本范围内,问题才消失。
所以,如果遇到安装完成后无法运行,先别怀疑代码,优先把 sklearn 降到 TPOT 兼容的版本,或者直接在虚拟环境里重新装。我建议用 conda 或 venv 单独给 TPOT 开一个环境,避免和主项目互相污染。安装时也不建议用 conda 自带的老版本 TPOT,很多旧包缺失新特性,直接用pip install -U tpot拿最新稳定版更省事。
2.2 最小可用代码:先让流程转起来
第一次用 TPOT,不要上来就搞大搜索空间。先用最朴素的代码把流程跑通,看输出长什么样,再逐步加复杂度。下面是分类任务的最小示例:
import pandas as pd from sklearn.model_selection import train_test_split from tpot import TPOTClassifier df = pd.read_csv('my_data.csv') X = df.drop('target', axis=1) y = df['target'] X_train, X_test, y_train, y_test = train_test_split( X, y, test_size=0.2, random_state=42 ) tpot = TPOTClassifier( generations=3, population_size=10, cv=5, scoring='accuracy', verbosity=2, random_state=42 ) tpot.fit(X_train, y_train) print("验证集得分:", tpot.score(X_test, y_test)) tpot.export('best_pipeline.py')注意这里的 generations=3、population_size=10 是故意压小的。n 个个体,g 代,大约要评估 个体数 加上 每代后代数 乘以 代数 这么多个管道,哪怕这么小的配置,在 5 折交叉验证下也要训练上百次模型,所以首跑要控制时间。
跑完之后,你的工作目录会多一个 best_pipeline.py,内容是一段完整的 sklearn Pipeline 代码。另外两个你需要注意的东西是tpot.fitted_pipeline_和tpot.evaluated_individuals_:前者是你当前最优管道实例,可以直接用于预测;后者是一个挺大的字典,记录了所有评估过的管道和得分,适合做进一步分析。
2.3 首跑必须注意的细节
首跑时最容易忽略的是数据预处理。TPOT 虽然自带了一些填补缺失值的算子,但如果原始数据里有大量不该出现的 NaN,或者类别特征没有编码成数值,TPOT 的搜索空间会浪费很大一部分时间在尝试“填补和编码”上,而不是真正优化模型。我现在的习惯是:凡是要进 TPOT 的数据,先在外部完成最基本的清洗,包括缺失值处理、类别编码、样本去重,TPOT 在管道里做特征缩放和模型选择就够了。
还有一点,如果数据不平衡,TPOT 的交叉验证指标会失真。后面我会专门讲不平衡场景怎么处理。
3. 遗传编程在 TPOT 里到底怎么工作
3.1 从“管道”到“个体”
TPOT 的核心不是深度学习那一套梯度优化,而是遗传编程(Genetic Programming, GP)。GP 的操作对象是树结构,TPOT 把机器学习管道抽象成一棵树:根节点通常是最终分类器或回归器,分支节点是预处理、特征选择、特征构造等操作。一个“个体”就是一棵管道树。
比如一个简单管道可能是这样:特征数据进入 StandardScaler,然后送到 ExtraTreesClassifier,这棵树就是两个节点。复杂一点,数据先进入 RobustScaler,再分给 PCA 和 SelectKBest,两个分支的结果拼接成新特征,最后送到随机森林。这种分叉结构在人类手动建模里很难想到要试,但 GP 的交叉变异很容易生成。
TPOT 内置了“算子库”,包括 sklearn 中的常见预处理器和模型,还包含少量内置算子,比如均衡分类用的 ClassBalancer、指标计算用的 FeatureSet 等。搜索过程就是从算子库里随机组合出一棵树,一个算子对应一个超参配置。
3.2 选择、交叉、变异如何协同
TPOT 每一代都重复这几个步骤:
- 选择:按适应度得分给所有个体排序,高分个体被选中作为“父母”。适应度用的是你指定的交叉验证得分,比如 accuracy 或 roc_auc。
- 交叉:从两个父母管道树上各取一个子树片段,互相交换,产生两个新个体。这保证了后代能继承效率较高的特征处理和模型组合。
- 变异:随机替换个体中某个算子,或调整某个超参数,让搜索不会停留在局部最优。
同时,TPOT 实行精英策略,每代最优秀的那部分个体直接进入下一代,不会因为交叉变异而丢失。这也是为什么它的收敛曲线通常是阶梯式上升,而不是平滑曲线。
3.3 核心参数怎么理解才能调明白
很多人把 TPOT 参数背下来了,却不理解含义,这里我拆开讲:
| 参数 | 含义 | 我的常用起步值 |
|---|---|---|
| generations | 进化代数 | 5-10 |
| population_size | 每代保留的个体数 | 20-50 |
| offspring_size | 每代新生后代数量,默认等于 population_size | 默认或略小 |
| mutation_rate | 变异概率,默认 0.9 | 默认 |
| crossover_rate | 交叉概率,默认 0.1 | 默认 |
| cv | 交叉验证折数 | 5 |
| scoring | 优化目标 | 分类用 roc_auc,回归用 neg_mean_squared_error |
| max_time_mins | 总搜索时间上限(分钟) | 根据资源定 |
| max_eval_time_mins | 单个管道评估时间上限 | 30-60 分钟级问题设 1-2 |
| n_jobs | 并行数量 | -1 |
| early_stop | 连续多少代无提升就提前停 | 3 |
| config_dict | 自定义算子搜索空间 | 按需定制 |
| periodic_checkpoint_folder | 断点保存目录 | 建议开启 |
计算评估规模时有个简单公式:如果 offspring_size 等于 population_size,每一代要评估 population_size + offspring_size 个个体。那最早那个 3 代 * 10 个体的最小配置,三代总共评估大约 10 + 3 * 10 = 40 个管道(忽略精英直通),每个管道做 5 折交叉验证,实际模型拟合次数大约是 40 * 5 = 200 次。虽然模型通常都是轻量级的树模型,但在大表数据上也会跑一会儿,所以首跑请务必压下规模。
4. 实操:用 TPOT 跑一个分类任务的全流程
4.1 数据处理放在前头,别把搜索时间花在重复拟合脏数据上
TPOT 内置的管道可以包含缺失值填补和编码器,但我不建议把数据清洗完全交给它。原因很简单:搜索空间是有限的,每增加一个预处理算子,搜索维度就增加,时间成本成倍上升。最好的策略是在 TPOT 外部解决缺失值和编码,在 TPOT 内部只留特征缩放、特征选择、模型选择这些对分数影响更大的环节。
类别特征我一般先做标签编码或直接转换为 category 类型;缺失值直接按业务逻辑填充,比如中位数填数值列,众数填类别列。如果特征列太多,先在外部做一次基于业务或简单相关性的粗筛,把明显没用的列去掉,TPOT 的特征选择算子用来做细筛,这样效率更高。
4.2 一个相对完整且有参考价值的配置
以下是我做二分类项目时常用的模板:
import pandas as pd from sklearn.model_selection import train_test_split from sklearn.preprocessing import StandardScaler from tpot import TPOTClassifier df = pd.read_csv('data.csv') df = df.drop(['id', 'timestamp'], axis=1) for col in df.select_dtypes(include=['object']).columns: df[col] = df[col].astype('category').cat.codes X = df.drop('label', axis=1) y = df['label'] X_train, X_test, y_train, y_test = train_test_split( X, y, test_size=0.2, random_state=42, stratify=y ) tpot = TPOTClassifier( generations=10, population_size=30, offspring_size=20, mutation_rate=0.9, crossover_rate=0.1, cv=5, scoring='roc_auc', max_time_mins=30, max_eval_time_mins=2, n_jobs=-1, random_state=42, early_stop=3, config_dict='TPOT light', periodic_checkpoint_folder='./tpot_checkpoint', verbosity=2 ) tpot.fit(X_train, y_train) print("测试集 AUC:", tpot.score(X_test, y_test)) tpot.export('final_pipeline.py')这里有个关键选择:config_dict='TPOT light'。TPOT 默认的搜索空间非常大,包含几十个模型和预处理算子,代数少时根本搜不透。TPOT light 是一个精简配置,只包含少数常用模型,适合快速验证。等你看清 baseline 大概在什么水平后,再换成默认配置做最终搜索,效率会高很多。
另外,stratify=y这行很重要。如果二分类样本分布不平衡,不做分层抽样,测试集可能和训练集分布相差很大,最终 score 看着高,上线却拉胯。分层是 sklearn 切分数据时被我当成铁律的一条。
4.3 结果解读与导出代码二次使用
TPOT 跑完,先别急着欢呼。第一看tpot.fitted_pipeline_里的最优管道结构是怎么嵌套的,如果它选了某种特征组合,想一下业务上是否说得通。第二看tpot.evaluated_individuals_里是否有表现接近的其他管道,如果存在好几个得分差不多的结构,说明数据本身对模型类型不敏感,这时更稳妥的做法是选结构更简单的管道。
导出的 best_pipeline.py 内容大概长这样:
import numpy as np import pandas as pd from sklearn.ensemble import RandomForestClassifier from sklearn.model_selection import train_test_split from sklearn.pipeline import make_pipeline, make_union from sklearn.preprocessing import MinMaxScaler from tpot.builtins import StackingEstimator pipeline = make_pipeline( MinMaxScaler(), StackingEstimator(estimator=LogisticRegression()), RandomForestClassifier(n_estimators=200) )注意,TPOT 导出的是一段管道构建代码,不是已经训练好的模型文件。你需要把它复制到自己的训练脚本里,重新调用 fit,再用 joblib 把它 dump 成模型文件供线上加载。这一步我建议你做成一个标准的“导出管道→编译成独立训练脚本”流程,不要把 TPOT 搜索环境直接带到生产环境里。
5. 自定义搜索空间,喂给 TPOT 你想搜的模型
5.1 为什么迟早要自定义搜索空间
默认配置虽然全,但有两个问题:一是模型太多导致每代搜索广度不够,信息被稀释;二是一些模型在你的业务场景里根本不能用,比如可解释性要求高的场景,树模型或线性模型之外的复杂 stacking 方案很难落地。这时候就得自定义 config_dict,把搜索范围限制在业务可接受的模型里。
我当年做一个风控项目,客户明确要求不能用黑盒模型,默认搜索空间里的 XGBoost、神经网络全部不能用。我自定义了一个只包含决策树、随机森林、逻辑回归、线性 SVM 和少量特征预处理步骤的空间,最后 TPOT 仍然找到了不错的组合,而且解释性完全达标。
5.2 config_dict 怎么写才正确
TPOT 的自定义配置是一个 Python 字典,键是全限定类名,值是参数搜索数组。写法有一定的约定,否则 TPOT 会报错:
custom_config = { 'sklearn.tree.DecisionTreeClassifier': { 'criterion': ['gini', 'entropy'], 'max_depth': [3, 5, 8, 10], 'min_samples_split': [2, 5, 10], 'min_samples_leaf': [1, 2, 4] }, 'sklearn.linear_model.LogisticRegression': { 'C': [0.1, 1.0, 10.0], 'penalty': ['l1', 'l2'] }, 'sklearn.preprocessing.StandardScaler': {}, 'sklearn.preprocessing.MinMaxScaler': {}, 'sklearn.feature_selection.SelectKBest': { 'k': [10, 20, 30, 'all'] } } tpot = TPOTClassifier(generations=10, population_size=20, config_dict=custom_config, cv=5, scoring='accuracy')键名要写全限定路径,比如sklearn.ensemble.RandomForestClassifier,不能简写。值为空字典表示该算子没有可调参数,直接作为固定步骤参与搜索。TPOT 会根据字典自动通过 importlib 导入模块,所以类路径拼错最常见的结果就是报ImportError,排查时先检查路径拼写。
5.3 自定义空间常见坑
一个大坑是:某些模型的参数之间存在互斥关系。比如LogisticRegression的 penalty='l1' 时,solver 只能用 liblinear 或 saga;如果你把 solver 也放进搜索空间,并指定了 lbfgs,TPOT 会生成一堆无效个体,浪费大量评估时间。解决办法是固定兼容参数,或在自定义空间里只放确定不冲突的参数组合。
另一个坑是版本更新导致参数名变化。以前RandomForestClassifier的n_estimators叫n_estimators,某些模型参数在 sklearn 新版本里被重命名或淘汰,自定义空间里的旧参数名会直接让管道构建失败。遇到这种情况,通常看报错信息里的fit阶段 error,把参数名对着官方文档改掉即可。
还有一个更实用的经验:自定义空间里建议保留一两个特征缩放器。很多模型对特征尺度非常敏感,如果完全没有缩放器参与搜索,TPOT 只能生成未经缩放的管道,最终分数可能明显偏低。哪怕你的人工 baseline 不缩放也能跑,在自动搜索里留出缩放选项,总能让它对不同模型各取所需。
6. 回归任务、不平衡数据和更快的搜索体验
6.1 回归场景的配置差异
TPOTRegressor 和 TPOTClassifier 的用法几乎一模一样,差别只在评估指标和模型搜索空间。回归任务我常用的参数是这样:
from tpot import TPOTRegressor tpot = TPOTRegressor( generations=8, population_size=25, cv=5, scoring='neg_mean_squared_error', max_time_mins=20, max_eval_time_mins=1, early_stop=3, n_jobs=-1, random_state=42 )scoring 参数一定不要用默认的负均方误差就满足了。回归场景不同,业务关注点完全不一样:房价预测看绝对误差,销售预测看百分比误差。按需换成neg_root_mean_squared_error、neg_mean_absolute_error或r2都行。TPOT 底层用的是 sklearn 的 scoring 机制,所以凡是 sklearn.metrics.make_scorer 支持的指标都能传进去。
回归任务里特征缩放的重要性比分类还高。如果特征尺度跨了几个数量级,比如一个特征是 0-1 的小数,另一个是几百万的数,很多线性模型和距离类模型的表现会剧烈波动。TPOT 默认配置里有缩放器,但为了让它优先选择缩放器,我会在自定义空间里故意把缩放器放在配置字典前面,并减少其他预处理算子,让搜索路径更集中。
6.2 不平衡数据:别指望 TPOT 自动救你
TPOT 官方说明里没有内置 SMOTE。它的内置ClassBalancer算子做的是简单的重采样,思路是在每个评估管道里,对少数类样本做有放回抽样,让类别比例趋近均衡。这个机制在不平衡比例不极端(比如 10:1)的时候有效,但如果是 100:1 的极端不平衡数据,它生成的多数类丢弃或者少数类复制策略都不够精细。
我在真实项目里的做法是:先在外面用 SMOTE-NC 或 ADASYN 生成增强后的训练集,再把 TPOT 的搜索重点放到模型和特征选择上。但这里有个细节必须注意——如果在 fit 之前就做了 SMOTE,TPOT 内部的交叉验证是基于增强后的数据进行的,会带来一定的数据泄露风险。稳妥做法是把 SMOTE 放到一个管道外面,或者干脆只做训练集的预先增强,并接受这个 baseline 会有轻微乐观偏差,再在最终测试集上重新验证,模型效果如果撑得住,就说明不过拟合。
另一种更符合流程的做法是用带Pipeline的包装类,把 SMOTE 和 model 放在一起传给 TPOT 的自定义配置,但这就需要你实现一个自定义 Transformer 或估计器,让 TPOT 能够 import 和使用。如果你不想写类,优先用“训练集预先增强 + 测试集独立验证”的方案,实测最省力。
6.3 提速思路:断点、light 配置和并行
TPOT 跑起来慢是正常的,但它慢得有讲究。你可以一步步把时间“榨”下来:
- 开启
periodic_checkpoint_folder,每代结束保存中间结果,就算电脑死机或停电,也可以在相同配置下从断点继续,不会推倒重来。 - 设置
max_time_mins给整个搜索上一道锁,同时设置max_eval_time_mins防止某个单管道卡死。 - 在超大数量特征下,先用主成分或者特征重要性粗筛,把特征压到几百甚至几十维,再进 TPOT。
- 并行方面,
n_jobs=-1是常规操作,但如果内存紧张,建议设成 CPU 核心数的一半。因为每个个体占一块内存,并行度过高可能 OOM,反而拖垮搜索。 - 如果数据量很大,TPOT 支持通过 Dask 来做分布式并行。这需要额外配置一个 Dask Client,然后把
use_dask=True传入 TPOT。实际项目里我一般只在几千行×几百列的规模以上才启用,普通中小规模数据用本地多进程就够了。
7. 常见问题与排查技巧实录
7.1 问题速查表
| 现象 | 原因 | 补救措施 |
|---|---|---|
| 安装后 import 报错 | sklearn 或依赖版本不兼容 | 固定 TPOT 支持的 sklearn 版本区间,用虚拟环境隔离 |
| 搜索时间无限拉长 | 未设时间上限 | 必须设置 max_time_mins 和 max_eval_time_mins |
| 每次跑出的最优管道都不一样 | 交叉验证和交叉变异的随机性 | 固定 random_state,多次运行取结果众数 |
| 导出的代码单独运行报错 | 缺少导入语句或依赖库 | 检查导出文件头部 import,补齐模块 |
| 特征名全部丢失 | pipeline 内部特征变换后索引丢失 | 用 fitted_pipeline_ 中的 steps 手动映射特征 |
| 自定义配置报 ImportError | 类路径拼写错误 | 检查键名是否为全限定名,如 sklearn.ensemble.RandomForestClassifier |
| 不平衡数据下 AUC 虚高 | 交叉验证未做分层 | 外部使用 StratifiedKFold,或在 train_test_split 里 stratify |
| 大表直接 OOM | 并行度过高或数据体积大 | 降低 n_jobs、减少 population_size、先降采样 |
7.2 几个非常值得记录的细节
关于“每次跑结果不同”这件事,我多说一句。TPOT 本质是启发式随机搜索,固定 random_state 也不能保证不同机器上结果完全一致,只能保证同一机器同一配置基本可复现。实际项目里,我跑完一次搜索后会再把最终管道用固定种子重训几次,取交叉验证均值最高的那版部署。不要试图让搜索结果一模一样,要关注的是最终管线在测试集上的稳定性。
第二个细节是关于导出的代码。TPOT 导出的代码不一定包含所有 import,你会发现如果它用了tpot.builtins.StackingEstimator,文件顶部当然会 import,但如果是sklearn.preprocessing.PolynomialFeatures这种较为冷门的类,偶尔会因为依赖环境不同而缺包。导出后先在你自己的脚本环境里跑一遍,缺什么补什么,别等上了线才发现。
第三个细节是 eval 阶段卡住不退出。我曾经遇到某个个体在拟合时长时间不结束,整个搜索像死机一样。解决方案是max_eval_time_mins设置合理值,比如 1-2 分钟。TPOT 内部用的是 stopit 库来强制超时中断,这个参数实测非常有效,建议每个项目都配。
7.3 从 TPOT 到生产环境的迁移思路
TPOT 只负责“搜索”,不负责“部署”。我遇到很多人把 TPOT 拟合完的实例直接序列化扔上线,这是不推荐的。TPOT 实例本身包含整个种群和评估历史,体积大,而且反序列化依赖的类可能随版本漂移崩掉。正确做法是:训练结束后,把导出的管道代码整理成独立脚本,用最佳参数重新实例化一个 pipeline,在完整训练集上重新训练,再 joblib dump 这个 pipeline。
线上推理时还要考虑一致性。如果原始特征是一列带单位的房价,训练前手动做了 log 变换,那线上预测函数里也必须做同样的 log 变换。把这些预处理步骤写成一个统一的 transform 函数,与 TPOT 搜索出的 pipeline 串成一条链,才算是把自动化的成果稳稳接进了生产环境。
写在最后的一些经验
我在实际项目里用 TPOT 的频率并不高,但它基本被我当作一个“快速试探数据潜力”的沙盘工具。拿到新数据集,先用 TPOT 跑一次小规模搜索,看它给出的管道结构和分数,就能快速判断这个数据到底适合什么模型族,特征工程方向是什么。这个信息比从头开始手动 baseline 节省大量时间。
最近网络上有一些奇怪的搜索联想词,比如“ttft tpot”“tpot 大模型指标”,我看到的时候也很迷惑。这里说句实在话:TPOT 是面向传统表格数据的 AutoML 工具,跟大模型、Transformer 那些方向没有必然关系。如果你看到有人把 TPOT 和大模型指标硬凑在一起,大概率是标题党。你只要记住一个原则就够了:TPOT 解决的是结构化数据建模中的模型选择与管道优化问题,用对场景,它确实能让你的效率翻倍。\n
\ 我最后再分享一个小技巧:如果你刚开始用 TPOT,不要一上来就追求最高的 CV 分数,先用最小配置跑通流程,把导出的代码读懂,再逐步扩大搜索空间。你会发现,从“让 TPOT 帮我找模型”到“让 TPOT 帮我验证想法”,这个过程本身,就是自动化工具最有价值的地方。