对AutoML软件的期望¶
自动化机器学习(AutoML)采用了一种比大多数从业者习惯的更高级的机器学习方法,因此我们收集了一些关于运行TPOT等AutoML软件时的预期指南。
AUTOML 算法不打算只运行几分钟¶
当然,你可以只运行TPOT几分钟,它会为你的数据集找到一个相当不错的管道。然而,如果你运行TPOT的时间不够长,它可能无法为你的数据集找到最佳的管道。它甚至可能根本找不到任何合适的管道,在这种情况下,会引发一个RuntimeError('管道尚未优化。请先调用fit()。')。通常,长时间(几小时到几天)并行运行多个TPOT实例是值得的,以便TPOT能够彻底搜索你的数据集的管道空间。
自动化机器学习算法可能需要很长时间才能完成搜索¶
AutoML算法并不像在数据集上拟合一个模型那么简单;它们在包含多个预处理步骤(缺失值填补、缩放、PCA、特征选择等)的管道中考虑多种机器学习算法(随机森林、线性模型、支持向量机等),所有模型和预处理步骤的超参数,以及管道内集成或堆叠算法的多种方式。
因此,TPOT 在较大的数据集上运行会花费一些时间,但重要的是要理解其中的原因。使用默认的 TPOT 设置(100 代,每代 100 个种群大小),TPOT 在完成之前将评估 10,000 个管道配置。为了理解这个数字的意义,想象一下对一个机器学习算法进行 10,000 个超参数组合的网格搜索,以及这个网格搜索将花费多长时间。这意味着在 10 折交叉验证中评估 10,000 个模型配置,也就是说在一次网格搜索中,大约有 100,000 个模型在训练数据上进行拟合和评估。即使对于像决策树这样的简单模型,这也是一个耗时的过程。
典型的TPOT运行需要数小时到数天才能完成(除非是一个小数据集),但你随时可以中途中断运行并查看目前为止的最佳结果。TPOT还提供了warm_start和periodic_checkpoint_folder参数,让你可以从上次中断的地方重新启动TPOT运行。
AUTOML 算法可以为相同的数据集推荐不同的解决方案¶
如果你正在处理一个相对复杂的数据集或运行TPOT的时间较短,不同的TPOT运行可能会产生不同的管道推荐。TPOT的优化算法是随机的,这意味着它(部分地)使用随机性来搜索可能的管道空间。当两次TPOT运行推荐不同的管道时,这意味着由于时间不足,TPOT运行没有收敛,或者多个管道在你的数据集上表现大致相同。
这实际上比固定的网格搜索技术更有优势:TPOT旨在成为一个助手,通过探索你可能从未考虑过的管道配置,为你提供解决特定机器学习问题的思路,然后将微调留给更受限制的参数调优技术,如网格搜索或贝叶斯优化。
import tpot2
from tpot2 import TPOTClassifier
然后按如下方式创建TPOT的实例:
classification_optimizer = TPOTClassifier()
也可以使用TPOTRegressor类来处理回归问题。除了类名之外,TPOTRegressor的使用方式与TPOTClassifier相同。您可以在API文档中阅读更多关于TPOTClassifier和TPOTRegressor类的内容。
from tpot2 import TPOTRegressor
regression_optimizer = TPOTRegressor()
拟合一个TPOT模型的工作方式与任何其他sklearn估计器完全相同。一些带有自定义TPOT参数的示例代码可能如下所示:
import sklearn
import sklearn.datasets
import sklearn.metrics
import tpot2
classification_optimizer = TPOTClassifier(search_space="linear-light", max_time_mins=30/60, n_jobs=30, cv=5)
X, y = sklearn.datasets.load_breast_cancer(return_X_y=True)
X_train, X_test, y_train, y_test = sklearn.model_selection.train_test_split(X, y, random_state=1, test_size=0.2)
classification_optimizer.fit(X_train, y_train)
auroc_score = sklearn.metrics.roc_auc_score(y_test, classification_optimizer.predict_proba(X_test)[:,1])
print("auroc_score: ", auroc_score)
Generation: : 6it [00:32, 5.39s/it] /home/perib/miniconda3/envs/myenv/lib/python3.10/site-packages/sklearn/linear_model/_sag.py:349: ConvergenceWarning: The max_iter was reached which means the coef_ did not converge warnings.warn(
auroc_score: 0.9907407407407408
评分器、目标函数和多目标优化。¶
有两种方法可以将目标传递到TPOT2中。
scorers: 评分器是具有签名 (estimator, X_test, y_test) 的函数,并接受预期已拟合到训练数据的估计器。这些可以通过 sklearn.metrics.make_scorer 函数生成。此函数用于在交叉验证期间评估测试折叠(在cv参数中定义)。这些通过 scorers 参数传递给 TPOT2。它可以接受评分器本身或对应于评分函数的字符串(如这里所列)。TPOT2 还支持传入多个评分器列表以进行多目标优化。对于 CV 的每个折叠,TPOT 只拟合一次估计器,然后在循环中评估所有提供的评分器。other_objective_functions: TPOT2中的其他目标函数具有签名(estimator)并返回一个浮点数或浮点数列表。这些函数在交叉验证之外,会传递一个未拟合的估计器一次。用户也可以选择在此目标函数内拟合管道。
每个评分器和目标函数必须伴随一个与目标列表相对应的权重列表,这些分别是scorers_weights和other_objective_function_weights。默认情况下,TPOT2最大化目标函数(这可以通过bigger_is_better=False来改变)。正权重意味着TPOT2将寻求最大化该目标,而负权重对应于最小化。对于大多数选择器(以及默认选择器),只有符号重要。如果为优化算法使用自定义选择函数,则权重的规模可能很重要。零权重意味着该分数不会对选择算法产生影响。
这是一个使用两个评分器的示例
scorers=['roc_auc_ovr',tpot2.objectives.complexity_scorer],
scorers_weights=[1,-1],
这是一个带有评分器和次要目标函数的示例
scorers=['roc_auc_ovr'],
scorers_weights=[1],
other_objective_functions=[tpot2.objectives.number_of_leaves_objective],
other_objective_functions_weights=[-1],
TPOT 将始终根据最终结果数据框中列的函数名称自动命名评分器。TPOT 将使用函数名称作为 other_objective_functions 的列名。然而,如果您想指定自定义列名,可以将 objective_function_names 设置为一个名称列表(str),用于 other_objective_functions 中函数返回的每个值。如果您的附加函数每个函数返回多个值,这可能很有用。
可以让评分器或其他目标函数返回多个值。在这种情况下,只需确保scorers_weights和other_objective_function_weights的长度与返回的分数数量相同。
TPOT 附带了一些额外的内置目标函数,您可以使用。第一个表格是应用于拟合管道的目标,因此它们被传递到 scorers 参数中。第二个表格是用于 other_objective_functions 参数的目标函数。
评分者:
| 函数 | 描述 |
|---|---|
| tpot2.objectives.complexity_scorer | 估计管道中所有分类器和回归器的学习参数数量。此外,当前转换器增加1分,选择器增加0分(因为它们不影响“最终”预测管道的复杂性。) |
其他目标函数。
| 函数 | 描述 |
|---|---|
| tpot2.objectives.average_path_length | 计算从所有节点到根/最终估计器的平均最短路径(仅支持GraphPipeline) |
| tpot2.objectives.number_of_leaves_objective | 计算GraphPipeline中的叶子节点(输入节点)数量 |
| tpot2.objectives.number_of_nodes_objective | 计算管道中的节点数量(无论是scikit-learn管道、GraphPipeline、特征联合,还是之前相互嵌套的管道) |
测量模型复杂度¶
在运行TPOT时,包含一个衡量模型复杂度的次要目标有时是有益的。更复杂的模型可能会产生更高的性能,但这会以可解释性为代价。更简单的模型可能更易于解释,但通常预测性能较低。然而,有时复杂度的巨大增加只会略微提高预测性能。可能存在其他更简单且更易于解释的管道,尽管性能略有下降,但对于提高可解释性来说是可以接受的。然而,当纯粹为性能优化时,这些管道通常会被忽略。通过将性能和复杂度都作为目标函数,TPOT将尝试同时优化所有复杂度级别的最佳管道。优化后,用户将能够看到复杂度与性能之间的权衡,并决定哪种管道最适合他们的需求。
考虑衡量复杂性的两种方法将是tpot2.objectives.number_of_nodes_objective或tpot2.objectives.complexity_scorer。节点数量目标简单地计算管道中的步骤数量。这是一个简单的指标,然而它并不区分不同模型类型的复杂性。例如,一个简单的LogisticRegression与更复杂的XGBoost计算方式相同。复杂性评分器尝试估计管道中分类器和回归器包含的学习参数数量。如何准确量化和比较不同类别模型之间的复杂性是具有挑战性且可能主观的。然而,这个函数为进化算法提供了一个合理的启发式方法,至少从质量上区分出复杂性较高或较低的算法。虽然可能很难准确比较LogisticRegression和XGBoost的相对复杂性,例如,两者总是位于此函数返回的复杂性值的两端。这使得在帕累托前沿中,LogisticRegression位于一侧,而XGBoost位于另一侧。
这种分析的一个例子在下面的部分中进行了演示。
内置配置¶
TPOT 可用于优化超参数、选择模型以及优化模型管道,包括确定步骤的顺序。教程 2 详细介绍了如何使用自定义超参数范围、模型类型和可能的管道配置来自定义搜索空间。TPOT 还附带了一些默认的操作符和参数配置,我们认为这些配置在优化机器学习管道方面效果良好。以下是 TPOT 当前内置配置的列表。这些可以作为字符串传递给任何 TPOT 估计器的 search space 参数。
| 字符串 | 描述 |
|---|---|
| linear | 一个具有“选择器->(转换器+直通)->(分类器/回归器+直通)->最终分类器/回归器”结构的线性管道。对于转换器和内部估计器层,TPOT可以选择一个或多个转换器/分类器,或者可能不选择任何转换器/分类器。内部分类器/回归器层是可选的。 |
| linear-light | 与linear相同的搜索空间,但没有内部分类器/回归器层,并且使用了一组运行速度更快的估计器。 |
| graph | TPOT 将优化一个有向无环图形状的管道。图的节点可以包括选择器、缩放器、转换器或分类器/回归器(内部分类器/回归器可以选择不包括)。这将返回一个自定义的 GraphPipeline 而不是 sklearn 的 Pipeline。更多详细信息请参见教程 6。 |
| graph-light | 与图搜索空间相同,但没有内部分类器/回归器,并且使用了一组运行速度更快的估计器。 |
| mdr |TPOT 将搜索一系列特征选择器和多因素降维模型,以找到一系列最大化预测准确性的操作符。TPOT MDR 配置专门用于全基因组关联研究(GWAS),并在网上详细描述。
请注意,TPOT MDR 可能运行缓慢,因为特征选择例程在计算上非常昂贵,尤其是在大型数据集上。|
默认情况下,linear 和 graph 配置允许在管道中除了最终的分类器/回归器之外,还可以添加额外的堆叠分类器/回归器。如果你想禁用此功能,可以通过函数 tpot2.config.template_search_spaces.get_template_search_spaces 手动获取没有内部分类器/回归器的搜索空间,并将 inner_predictios=False。你可以将生成的搜索空间传递给 search space 参数。
import tpot2
from tpot2.search_spaces.pipelines import SequentialPipeline
from tpot2.config import get_search_space
stc_search_space = SequentialPipeline([
get_search_space("selectors"),
get_search_space("all_transformers"),
get_search_space("classifiers"),
])
est = tpot2.TPOTEstimator(
search_space = stc_search_space,
scorers=["roc_auc_ovr", tpot2.objectives.complexity_scorer],
scorers_weights=[1.0, -1.0],
classification = True,
cv = 5,
max_eval_time_mins = 10,
early_stop = 2,
verbose = 2,
n_jobs=4,
)
使用内置方法
est = tpot2.TPOTEstimator(
search_space = "linear",
scorers=["roc_auc_ovr", tpot2.objectives.complexity_scorer],
scorers_weights=[1.0, -1.0],
classification = True,
cv = 5,
max_eval_time_mins = 10,
early_stop = 2,
verbose = 2,
n_jobs=4,
)
TPOT使用的具体超参数范围可以在tpot2/config文件夹中的文件中找到。上面列出的模板搜索空间在tpot2/config/template_search_spaces.py中定义。单个模型的搜索空间可以在tpot2/config/get_configspace.py文件中获取(tpot2.config.get_search_space)。更多关于自定义搜索空间的详细信息可以在教程2中找到。
`tpot2.config.template_search_spaces.get_template_search_spaces`
Returns a search space which can be optimized by TPOT.
Parameters
----------
search_space: str or SearchSpace
The default search space to use. If a string, it should be one of the following:
- 'linear': A search space for linear pipelines
- 'linear-light': A search space for linear pipelines with a smaller, faster search space
- 'graph': A search space for graph pipelines
- 'graph-light': A search space for graph pipelines with a smaller, faster search space
- 'mdr': A search space for MDR pipelines
If a SearchSpace object, it should be a valid search space object for TPOT.
classification: bool, default=True
Whether the problem is a classification problem or a regression problem.
inner_predictors: bool, default=None
Whether to include additional classifiers/regressors before the final classifier/regressor (allowing for ensembles).
Defaults to False for 'linear-light' and 'graph-light' search spaces, and True otherwise. (Not used for 'mdr' search space)
cross_val_predict_cv: int, default=None
The number of folds to use for cross_val_predict.
Defaults to 0 for 'linear-light' and 'graph-light' search spaces, and 5 otherwise. (Not used for 'mdr' search space)
get_search_space_params: dict
Additional parameters to pass to the get_search_space function.
cross_val_predict_cv¶
此外,在训练具有内部分类器/回归器的模型时,使用cross_val_predict_cv可能会提高性能。如果设置了此参数,在模型训练期间,任何不是最终预测器的分类器或回归器将使用sklearn.model_selection.cross_val_predict将样本外预测传递到模型的后续步骤中。模型仍将拟合完整数据,这些数据将在训练后用于预测。在样本外预测上训练下游模型通常可以防止过拟合并提高性能。原因是这为下游模型提供了一个估计,即上游模型在未见数据上的表现如何。否则,如果上游模型严重过拟合数据,下游模型可能只会学会盲目信任看似预测良好的模型,从而将过拟合传播到最终结果。
缺点是cross_val_predict_cv在计算上要求显著更高,对于给定的数据集可能不是必要的。
linear_with_cross_val_predict_sp = tpot2.config.template_search_spaces.get_template_search_spaces(search_space="linear", classification=True, inner_predictors=True, cross_val_predict_cv=5)
classification_optimizer = TPOTClassifier(search_space=linear_with_cross_val_predict_sp, max_time_mins=30/60, n_jobs=30, cv=5)
终止优化(提前停止)¶
请注意,我们使用了一个较短的时间来进行快速示例,但在实际应用中,您可能需要运行TPOT更长的时间。默认情况下,TPOT设置了1小时的时间限制,每个管道的最大限制为5分钟。在实际应用中,您可能希望增加这些值。
有三种方法可以终止TPOT运行并结束优化过程。一旦满足其中一个条件,TPOT将立即终止。
max_time_mins: (默认值,60 分钟) 经过这么多分钟后,TPOT 将终止并返回它迄今为止找到的最佳管道。early_stop: 在没有看到性能改进的情况下,TPOT终止的代数。通常,大约5到20的值足以合理地确定性能已经收敛。generations: 进化算法运行的总代数。
默认情况下,TPOT 将一直运行,直到时间限制到达,没有生成或提前停止的限制。
最佳实践和提示:¶
- 当从 .py 脚本运行 tpot 时,使用
if __name__=="__main__":保护代码非常重要。这是因为 TPOT 如何处理与 Python 和 Dask 的并行化。
#my_analysis.py
from dask.distributed import Client, LocalCluster
import tpot2
import sklearn
import sklearn.datasets
import numpy as np
if __name__=="__main__":
scorer = sklearn.metrics.get_scorer('roc_auc_ovo')
X, y = sklearn.datasets.load_digits(return_X_y=True)
X_train, X_test, y_train, y_test = sklearn.model_selection.train_test_split(X, y, train_size=0.75, test_size=0.25)
est = tpot2.TPOTClassifier(n_jobs=4, max_time_mins=3, verbose=2, early_stop=3)
est.fit(X_train, y_train)
print(scorer(est, X_test, y_test))
Generation: : 15it [31:22, 125.48s/it]
0.9999480161532774
示例分析和Estimator类¶
这里我们使用 scikit-learn 中包含的一个示例数据集。我们将使用 light 配置和 complexity_scorer 来估计复杂度。
注意,对于这个简单的示例,我们设置了一个相对较短的运行时间。实际上,我们建议运行TPOT的时间更长,early_stop值大约在5到20之间(更多详细信息见下文)。
#my_analysis.py
from dask.distributed import Client, LocalCluster
import tpot2
import sklearn
import sklearn.datasets
import numpy as np
import tpot2.objectives
scorer = sklearn.metrics.get_scorer('roc_auc_ovr')
X, y = sklearn.datasets.load_breast_cancer(return_X_y=True)
X_train, X_test, y_train, y_test = sklearn.model_selection.train_test_split(X, y, train_size=0.75, test_size=0.25)
est = tpot2.TPOTClassifier(
scorers=[scorer, tpot2.objectives.complexity_scorer],
scorers_weights=[1.0, -1.0],
search_space="linear",
n_jobs=4,
max_time_mins=60,
max_eval_time_mins=10,
early_stop=2,
verbose=2,)
est.fit(X_train, y_train)
print(scorer(est, X_test, y_test))
Generation: : 6it [03:29, 34.94s/it] /home/perib/miniconda3/envs/myenv/lib/python3.10/site-packages/sklearn/ensemble/_weight_boosting.py:527: FutureWarning: The SAMME.R algorithm (the default) is deprecated and will be removed in 1.6. Use the SAMME algorithm to circumvent this warning. warnings.warn(
0.9985632183908046
您可以通过fitted_pipeline_属性访问由TPOT选择的最佳管道。这是具有最高交叉验证分数的管道(在第一个评分器上,或者如果没有提供评分器,则在第一个目标函数上)。
best_pipeline = est.fitted_pipeline_
best_pipeline
Pipeline(steps=[('passthrough', Passthrough()),
('selectfwe', SelectFwe(alpha=0.0012275167982)),
('featureunion-1',
FeatureUnion(transformer_list=[('skiptransformer',
SkipTransformer()),
('passthrough',
Passthrough())])),
('featureunion-2',
FeatureUnion(transformer_list=[('skiptransformer',
SkipTransformer()),
('passthrough',
Passthrough())])),
('adaboostclassifier',
AdaBoostClassifier(learning_rate=0.9052253032837,
n_estimators=273))])In a Jupyter environment, please rerun this cell to show the HTML representation or trust the notebook. On GitHub, the HTML representation is unable to render, please try loading this page with nbviewer.org.
Pipeline(steps=[('passthrough', Passthrough()),
('selectfwe', SelectFwe(alpha=0.0012275167982)),
('featureunion-1',
FeatureUnion(transformer_list=[('skiptransformer',
SkipTransformer()),
('passthrough',
Passthrough())])),
('featureunion-2',
FeatureUnion(transformer_list=[('skiptransformer',
SkipTransformer()),
('passthrough',
Passthrough())])),
('adaboostclassifier',
AdaBoostClassifier(learning_rate=0.9052253032837,
n_estimators=273))])Passthrough()
SelectFwe(alpha=0.0012275167982)
FeatureUnion(transformer_list=[('skiptransformer', SkipTransformer()),
('passthrough', Passthrough())])SkipTransformer()
Passthrough()
FeatureUnion(transformer_list=[('skiptransformer', SkipTransformer()),
('passthrough', Passthrough())])SkipTransformer()
Passthrough()
AdaBoostClassifier(learning_rate=0.9052253032837, n_estimators=273)
best_pipeline.predict(X_test)
array([1, 0, 1, 0, 0, 1, 1, 0, 1, 1, 1, 1, 1, 1, 1, 0, 1, 1, 1, 1, 1, 0,
1, 1, 1, 1, 0, 0, 1, 1, 0, 0, 1, 0, 1, 1, 0, 1, 1, 0, 0, 1, 0, 1,
0, 0, 1, 1, 0, 1, 1, 0, 0, 0, 1, 1, 1, 1, 1, 1, 0, 0, 1, 1, 0, 1,
1, 0, 0, 1, 1, 0, 0, 1, 0, 0, 1, 0, 0, 1, 1, 1, 0, 0, 1, 1, 0, 0,
0, 1, 0, 1, 0, 1, 1, 1, 1, 1, 0, 0, 0, 0, 0, 1, 1, 1, 1, 1, 1, 1,
1, 0, 1, 0, 1, 1, 1, 1, 0, 0, 0, 0, 0, 1, 1, 1, 1, 1, 1, 0, 1, 1,
1, 1, 0, 1, 1, 1, 1, 1, 0, 1, 1])
保存管道¶
我们建议使用dill或pickle来保存fitted_pipeline_的实例。请注意,我们不建议对TPOT对象本身进行pickle操作。
import dill as pickle
with open("best_pipeline.pkl", "wb") as f:
pickle.dump(best_pipeline, f)
#load the pipeline
import dill as pickle
with open("best_pipeline.pkl", "rb") as f:
my_loaded_best_pipeline = pickle.load(f)
evaluated_individuals 数据框 - 结果的进一步分析¶
evaluated_individuals 属性是 tpot 估计器对象的一个 Pandas 数据框,包含有关运行的信息。每一行对应于 tpot 探索的一个个体管道。数据框包含以下列:
| 列 | 描述 |
|---|---|
| 第一组列将对应于每个目标函数。这些列可以由TPOT自动命名,也可以由用户传入。 | |
| 父级 | 这包含一个元组,该元组包含当前管道的“父级”索引。例如,(29, 42) 表示索引为29和42的管道被用来生成该管道。 |
| Variation_Function | 应用于父代以生成新管道的函数 |
| 个体 | 表示特定管道和超参数配置的个体类。该类还包含用于变异和交叉的函数。要从个体中获取sklearn估计器/管道对象,您可以调用export_pipeline()函数。(例如,pipe = ind.export_pipeline()) |
| 生成 | 个体被创建的代数。(请注意,如果被选中,前几代中表现较好的管道可能仍然存在于当前“种群”中。) |
| 提交时间戳 | 时间戳,以秒为单位,表示管道被发送进行评估的时间。这是time.time()的输出,即“返回自纪元以来的时间,以浮点数表示,单位为秒。” |
| 完成时间戳 | 管道评估完成的时间戳,单位与提交时间戳相同 |
| Pareto_Front | 如果您有多个参数,如果管道性能落在帕累托前沿线上,则此列为True。这是具有严格优于不在线上的管道但彼此之间并不严格优于的管道得分的集合。 |
| 实例 | 这包含为此行评估的未拟合管道。(这是通过调用单个类的export_pipeline()函数返回的管道) |
#get the score/objective column names generated by TPOT
est.objective_names
['roc_auc_score', 'complexity_scorer']
df = est.evaluated_individuals
df
| roc_auc_score | complexity_scorer | 父代 | 变异函数 | 个体 | 世代 | 提交时间 | 完成时间 | 评估错误 | 帕累托前沿 | 实例 | |
|---|---|---|---|---|---|---|---|---|---|---|---|
| 0 | 0.964012 | 1745.5 | NaN | NaN | | 0.0 |
1.727568e+09 |
1.727568e+09 |
None |
NaN |
(Normalizer(norm='l1'), SelectPercentile(perce... |
|
| 1 | NaN | NaN | NaN | NaN | | 0.0 |
1.727568e+09 |
1.727568e+09 |
无效 |
NaN |
(MaxAbsScaler(), SelectFromModel(estimator=Ext... |
|
| 2 | NaN | NaN | NaN | NaN | | 0.0 |
1.727568e+09 |
1.727568e+09 |
无效 |
NaN |
(MaxAbsScaler(), VarianceThreshold(threshold=0... |
|
| 3 | NaN | NaN | NaN | NaN | | 0.0 |
1.727568e+09 |
1.727568e+09 |
无效 |
NaN |
(Normalizer(norm='l1'), RFE(estimator=ExtraTre... |
|
| 4 | 0.991667 | 24030.0 | NaN | NaN | | 0.0 |
1.727568e+09 |
1.727568e+09 |
None |
NaN |
(RobustScaler(quantile_range=(0.1798922078332,... |
|
| ... | ... | ... | ... | ... | ... | ... | ... | ... | ... | ... | ... |
| 345 | 0.992793 | 4374.0 | (237, 237) | ind_mutate | | 6.0 |
1.727568e+09 |
1.727568e+09 |
None |
NaN |
(Passthrough(), SelectFwe(alpha=0.022268001122... |
|
| 346 | 0.520972 | 9.0 | (128, 128) | ind_mutate | | 6.0 |
1.727568e+09 |
1.727568e+09 |
None |
NaN |
(MaxAbsScaler(), RFE(estimator=ExtraTreesClass... |
|
| 347 | NaN | NaN | (109, 85) | ind_crossover | | 6.0 |
1.727568e+09 |
1.727568e+09 |
无效 |
NaN |
(StandardScaler(), SelectPercentile(percentile... |
|
| 348 | 0.976466 | 21.0 | (296, 128) | ind_crossover , ind_mutate | | 6.0 |
1.727568e+09 |
1.727568e+09 |
None |
NaN |
(Passthrough(), RFE(estimator=ExtraTreesClassi... |
|
| 349 | 0.990725 | 14.0 | (297, 213) | ind_crossover | | 6.0 |
1.727568e+09 |
1.727568e+09 |
None |
NaN |
(MinMaxScaler(), SelectFwe(alpha=0.00016890355... |
|
350 行 × 11 列
让我们绘制不同管道的性能,包括帕累托前沿¶
在散点图中绘制多个目标的性能是可视化模型复杂性和预测性能之间权衡的有用方法。这在绘制帕累托前沿管道时最为明显,这些管道展示了在复杂性范围内的最佳性能管道。通常,更高复杂性的模型可能会产生更高的性能,但更难以解释。
import seaborn as sns
import matplotlib.pyplot as plt
#replace nans in pareto front with 0
fig, ax = plt.subplots(figsize=(5,5))
sns.scatterplot(df[df['Pareto_Front']!=1], x='roc_auc_score', y='complexity_scorer', label='other', ax=ax)
sns.scatterplot(df[df['Pareto_Front']==1], x='roc_auc_score', y='complexity_scorer', label='Pareto Front', ax=ax)
ax.title.set_text('Performance of all pipelines')
#log scale y
ax.set_yscale('log')
plt.show()
#replace nans in pareto front with 0
fig, ax = plt.subplots(figsize=(10,5))
sns.scatterplot(df[df['Pareto_Front']==1], x='roc_auc_score', y='complexity_scorer', label='Pareto Front', ax=ax)
ax.title.set_text('Performance of only the Pareto Front')
#log scale y
# ax.set_yscale('log')
plt.show()
#plot only the pareto front pipelines
sorted_pareto_front = df[df['Pareto_Front']==1].sort_values('roc_auc_score', ascending=False)
sorted_pareto_front
| roc_auc_score | complexity_scorer | 父代 | 变异函数 | 个体 | 世代 | 提交时间 | 完成时间 | 评估错误 | 帕累托前沿 | 实例 | |
|---|---|---|---|---|---|---|---|---|---|---|---|
| 330 | 0.995556 | 4373.0 | (237, 52) | ind_crossover | | 6.0 |
1.727568e+09 |
1.727568e+09 |
None |
1.0 |
(Passthrough(), SelectFwe(alpha=0.001227516798... |
|
| 144 | 0.995000 | 68.6 | (61, 61) | ind_mutate | | 2.0 |
1.727568e+09 |
1.727568e+09 |
无 |
1.0 |
(RobustScaler(quantile_range=(0.2808423658106,... |
|
| 320 | 0.994059 | 31.0 | (184, 184) | ind_mutate | | 6.0 |
1.727568e+09 |
1.727568e+09 |
None |
1.0 |
(MaxAbsScaler(), SelectFwe(alpha=0.01352548659... |
|
| 161 | 0.994028 | 23.2 | (123, 123) | ind_mutate | | 3.0 |
1.727568e+09 |
1.727568e+09 |
None |
1.0 |
(MaxAbsScaler(), SelectFromModel(estimator=Ext... |
|
| 297 | 0.992577 | 13.0 | (193, 193) | ind_mutate | | 5.0 |
1.727568e+09 |
1.727568e+09 |
None |
1.0 |
(MaxAbsScaler(), SelectFwe(alpha=0.00098089598... |
|
| 306 | 0.991165 | 8.0 | (167, 167) | ind_mutate | | 6.0 |
1.727568e+09 |
1.727568e+09 |
None |
1.0 |
(MaxAbsScaler(), SelectFwe(alpha=0.00057722163... |
|
| 106 | 0.965015 | 7.0 | (11, 85) | ind_crossover | | 2.0 |
1.727568e+09 |
1.727568e+09 |
None |
1.0 |
(StandardScaler(), SelectPercentile(percentile... |
|
| 195 | 0.945486 | 6.0 | (25, 25) | ind_mutate | | 3.0 |
1.727568e+09 |
1.727568e+09 |
None |
1.0 |
(MaxAbsScaler(), SelectFwe(alpha=0.00098089598... |
|
在某些情况下,您可能希望选择一个性能稍低但复杂度显著较低的管道。
#access the best performing pipeline with the lowest complexity
best_pipeline_lowest_complexity = sorted_pareto_front.iloc[-1]['Instance']
best_pipeline_lowest_complexity
Pipeline(steps=[('maxabsscaler', MaxAbsScaler()),
('selectfwe', SelectFwe(alpha=0.0009808959816)),
('featureunion-1',
FeatureUnion(transformer_list=[('skiptransformer',
SkipTransformer()),
('passthrough',
Passthrough())])),
('featureunion-2',
FeatureUnion(transformer_list=[('skiptransformer',
SkipTransformer()),
('passthrough',
Passthrough())])),
('kneighborsclassifier',
KNeighborsClassifier(n_jobs=1, n_neighbors=1, p=1,
weights='distance'))])In a Jupyter environment, please rerun this cell to show the HTML representation or trust the notebook. On GitHub, the HTML representation is unable to render, please try loading this page with nbviewer.org.
Pipeline(steps=[('maxabsscaler', MaxAbsScaler()),
('selectfwe', SelectFwe(alpha=0.0009808959816)),
('featureunion-1',
FeatureUnion(transformer_list=[('skiptransformer',
SkipTransformer()),
('passthrough',
Passthrough())])),
('featureunion-2',
FeatureUnion(transformer_list=[('skiptransformer',
SkipTransformer()),
('passthrough',
Passthrough())])),
('kneighborsclassifier',
KNeighborsClassifier(n_jobs=1, n_neighbors=1, p=1,
weights='distance'))])MaxAbsScaler()
SelectFwe(alpha=0.0009808959816)
FeatureUnion(transformer_list=[('skiptransformer', SkipTransformer()),
('passthrough', Passthrough())])SkipTransformer()
Passthrough()
FeatureUnion(transformer_list=[('skiptransformer', SkipTransformer()),
('passthrough', Passthrough())])SkipTransformer()
Passthrough()
KNeighborsClassifier(n_jobs=1, n_neighbors=1, p=1, weights='distance')
随时间绘制性能 + 从上次停止的地方继续运行¶
随着时间的推移绘制性能图是评估TPOT模型是否收敛的好方法。如果性能随着时间的推移趋于平稳,那么运行更长时间可能不会带来更多的性能提升。如果图表看起来仍在改进,那么可能值得运行TPOT更长时间。
在这种情况下,我们可以看到性能接近最佳并且已经放缓,因此更多的时间可能是不必要的。
#get columns where roc_auc_score is not NaN
scores_and_times = df[df['roc_auc_score'].notna()][['roc_auc_score', 'Completed Timestamp']].sort_values('Completed Timestamp', ascending=True).to_numpy()
#get best score at a given time
best_scores = np.maximum.accumulate(scores_and_times[:,0])
times = scores_and_times[:,1]
times = times - df['Submitted Timestamp'].min()
fig, ax = plt.subplots(figsize=(10,5))
ax.plot(times, best_scores)
ax.set_xlabel('Time (seconds)')
ax.set_ylabel('Best Score')
plt.show()
检查点¶
有两种方法可以恢复TPOT。
- 如果
warm_start参数设置为True,后续调用fit将继续从上次停止的地方进行训练(传统的scikit-learn默认行为是在后续调用fit时从头开始重新训练)。 - 如果设置了
periodic_checkpoint_folder,TPOT将定期将其当前状态保存到磁盘。如果TPOT被中断(作业取消、电脑关闭、崩溃),你可以从中断的地方恢复训练。检查点文件夹存储了所有评估过的管道的数据框。这个数据框可以加载并检查,以帮助在调试时诊断问题。
注意:TPOT不会清理检查点文件。如果设置了periodic_checkpoint_folder参数,即使输入数据发生了变化,训练也会从最后保存的点继续。一个常见的问题是在实验之间忘记更改此文件夹,导致TPOT继续从为另一个数据集优化的管道进行训练。如果您打算从头开始运行,必须删除该参数,提供一个空文件夹,或删除原始检查点文件夹。
通用参数¶
以下是一些最常见的自定义参数及其作用的子集。有关所有参数的完整文档,请参阅TPOTEstimator或TPOTEstimatorSteadyState的文档。
| 参数 | 类型 | 描述 |
|---|---|---|
| scorers | list, scorer | 用于交叉验证的评分器列表;参见 |
| scorers_weights | list | 在优化过程中应用于评分器的权重 |
| classification | bool | 问题类型:True 表示分类,False 表示回归 |
| cv | int, cross-validator | 交叉验证策略:整数表示折数或自定义交叉验证器 |
| max_depth | int | 最大管道深度 |
| other_objective_functions | list | 额外的目标函数;默认值:[average_path_length_objective] |
| other_objective_functions_weights | list | 额外目标函数的权重;默认值:[-1] |
| objective_function_names | list | 目标函数的名称;默认值:无(使用函数名称) |
| bigger_is_better | bool | 优化方向:True 表示最大化,False 表示最小化 |
| generations | int | 优化代数;默认值:50 |
| max_time_mins | float | 最大优化时间(分钟);默认值:无限 |
| max_eval_time_mins | float | 每个个体的最大评估时间(分钟);默认值:300 |
| n_jobs | int | 并行进程的数量;默认值:1 |
| memory_limit | str | 每个作业的内存限制;默认值:"4GB" |
| verbose | int | 优化过程的详细程度:0(无),1(进度),3(最佳个体),4(警告),5+(完整警告) |
| memory | str, memory object | 如果提供,管道将在使用 joblib.Memory 调用 fit 后缓存每个转换器。 |
| periodic_checkpoint_folder | str | 用于定期保存种群的文件夹。如果为None,则不会进行定期保存。如果提供,训练将从此检查点恢复。 |
防止过拟合¶
在小数据集上,TPOT 可能会过拟合交叉验证分数本身。这可能导致在保留数据集上的性能低于预期。TPOT 总是会返回具有最高 CV 分数的模型作为其最终的 fitted_pipeline。然而,如果通过交叉验证评估的最高性能模型实际上只是过拟合了 CV 分数,那么与帕累托前沿上的其他模型相比,它实际上可能表现更差。
* 使用次要的复杂性目标并评估整个帕累托前沿可能是有益的。在某些情况下,性能较低但复杂性较低的管道实际上在保留集上表现更好。这些可以在保留的验证集上进行评估和比较,或者有时,如果数据非常有限,简单地使用不同的种子来分割 CV 折也可以奏效。
* TPOT 可以自动执行此操作。validation_strategy 参数可以设置为在保留的验证集(由 validation_fraction 设置的数据百分比)或不同的种子来重新测试最终的帕累托前沿。可以通过将 validation_strategy 分别设置为 "split" 或 "reshuffled" 来选择这些选项。
* 增加交叉验证的折数可以缓解这种情况。
* 嵌套交叉验证也可以用于估计 TPOT 优化算法本身的性能。
* 从搜索空间中移除更复杂的方法可以减少过拟合的机会。
加速TPOT的技巧和窍门¶
TPOT 可能是一个计算要求较高的算法,因为它会在可能很大的数据集上拟合数千个复杂的机器学习管道。有几种策略可以通过减少所需的计算来改善运行时间。
TPOT中实施了三种主要策略,以减少冗余工作和/或防止在表现不佳的管道上浪费计算资源。
- TPOT管道通常会有完全相同的组件执行完全相同的计算(例如,管道的初始步骤保持不变,只有最终分类器的参数发生变化)。在这些情况下,第一种策略是简单地缓存这些重复计算,以便它们只发生一次。更多信息请参见下一小节。
- 连续减半。这个想法最初由Parmentier等人在"TPOT-SH: a Faster Optimization Algorithm to Solve the AutoML Problem on Large Datasets"中使用TPOT进行了测试。该算法分为两个阶段运行。最初,它使用一个小的数据子集和一个大的种群规模来训练早期世代。随后的世代则在更大甚至完整的数据部分上评估一组较小的有前景的管道。这种方法通过初步的粗略评估快速识别出表现最佳的管道配置,然后进行更全面的评估。更多关于此策略的信息请参见教程8。
- 大多数情况下,我们将使用交叉验证来评估管道。然而,我们通常可以在最初的几次折叠中判断出管道是否有合理的可能性优于之前的最佳管道。例如,如果到目前为止的最佳得分是0.92 AUROC,而我们当前管道的前五次折叠的平均得分仅为0.61左右,我们可以合理地确信接下来的五次折叠不太可能使这个管道领先于其他管道。通过不计算其余的折叠,我们可以节省大量的计算资源。TPOT可以使用两种策略来实现这一点(更多关于这些策略的信息见教程8)。
- 阈值修剪:管道必须在每次交叉验证(CV)折叠中达到预定义的百分位阈值(基于之前的管道得分)才能继续。
- 选择修剪:在每个种群中,只有排名前N%的管道(根据前一次CV折叠中的表现排名)被选中以在下一个折叠中进行评估。
TPOT中的管道缓存 (joblib.Memory)¶
使用内存参数,管道可以在拟合每个转换器后缓存其结果。此功能用于在优化过程中,如果参数和输入数据与另一个已拟合的管道相同,则避免管道内的转换器重复计算。TPOT允许用户指定自定义目录路径或joblib.Memory,以便在未来的TPOT运行(或热启动运行)中重新使用内存缓存。
在TPOT中启用内存缓存有三种方法:
from tpot2 import TPOTClassifier
from tempfile import mkdtemp
from joblib import Memory
from shutil import rmtree
# Method 1, auto mode: TPOT uses memory caching with a temporary directory and cleans it up upon shutdown
est = TPOTClassifier(memory='auto')
# Method 2, with a custom directory for memory caching
est = TPOTClassifier(memory='/to/your/path')
# Method 3, with a Memory object
memory = Memory(location='./to/your/path', verbose=0)
est = TPOTClassifier(memory=memory)
注意:如果用户设置了自定义目录路径或Memory对象,TPOT不会清理内存缓存。我们建议在不再需要时清理内存缓存。
高级并行化(HPC 和多节点训练)¶
有关使用Dask进行并行化的更多详细信息,包括使用多个节点的信息,请参见教程7。
常见问题与调试¶
如果您在使用TPOT时遇到问题,以下是一些常见问题及其解决方法。
- 性能低于预期。我该怎么办?
- TPOT 可能需要运行更长时间,增加
max_time_mins、early_stop或generations。 - 个别管道可能需要更多时间来完成拟合;增加
max_eval_time_seconds. - 配置可能不包含最佳模型类型或超参数范围,探索其他包含的模板,或自定义您的搜索空间(参见教程2!)
- 检查
periodic_checkpoint_folder是否正确设置。一个常见问题是忘记在实验之间更改此文件夹,TPOT 继续从为另一个数据集优化的管道进行训练。
- TPOT 可能需要运行更长时间,增加
- TPOT 运行太慢!它一直在运行,从未终止
- 检查是否至少设置了三个终止条件中的一个为合理水平。这些条件是
max_time_mins、early_stop或generations。此外,检查max_eval_time_seconds是否为大多数模型的训练提供了足够的时间,而不会过长。(一些估计器可能需要不合理的时间来拟合;此参数旨在防止它们减慢所有进程。根据我的经验,SVC 和 SVR 往往是罪魁祸首,因此将它们从搜索空间中移除也可能提高运行时间)。 - 设置
memory参数,以允许 TPOT 在使用 scikit-learn 管道或 TPOT GraphPipelines 时防止重复工作。 - 增加 n_jobs 以使用更多的进程/CPU 能力。有关高级 Dask 使用的信息,请参见教程 7,包括在 HPC 上的多个节点之间并行化。
- 使用特征选择,无论是 sklearn 方法的内置配置(参见教程 2),还是遗传特征选择(参见教程 3 和 5 以了解两种不同的策略)。
- 使用连续减半来减少计算负载(参见教程 8)。
- 检查是否至少设置了三个终止条件中的一个为合理水平。这些条件是
- 在evaluated_individuals数据框中的许多管道已经崩溃或变得无效!
- 这是正常的,并且是TPOT的预期行为。在某些情况下,TPOT可能会尝试无效的超参数组合,导致管道无法工作。其他时候,管道配置本身可能是无效的。例如,选择器可能由于其超参数而没有选择任何特征。另一个常见的例子是
MultinomialNB抛出错误,因为它期望正值,但先前的转换产生了负值。 - 如果您使用了自定义搜索空间,您可以使用
ConfigSpace条件来防止无效的超参数(由于TPOT使用交叉的方式,这种情况仍可能发生)。 - 设置
verbose=5将打印出所有失败管道的完整错误信息。这对于调试管道、自定义搜索空间模块或其他地方是否存在配置错误非常有用。
- 这是正常的,并且是TPOT的预期行为。在某些情况下,TPOT可能会尝试无效的超参数组合,导致管道无法工作。其他时候,管道配置本身可能是无效的。例如,选择器可能由于其超参数而没有选择任何特征。另一个常见的例子是
- TPOT 由于内存问题崩溃
- 设置
memory_limit参数,使得 n_jobs*memorylimit 小于您机器上的可用 RAM,再加上一些余量。这应该可以防止由于内存问题导致的崩溃。 - 如上所述,使用特征选择也可能改善内存使用情况。
- 移除那些导致高 RAM 使用的模块(例如多个 PolynomialFeatures 或高次数的 PolynomialFeatures)。
- 设置
- Why are my TPOT runs not reproducible when random_state is set?
- Check that
periodic_checkpoint_folderis set correctly. If this is set to a non-empty folder, TPOT will continue training from the checkpoint rather than start a new run from scratch. For TPOT runs to be reproducible, they have to have the same starting points. - If using custom search spaces, pass in a fixed
random_statevalue into the configspace of the scikit-learn modules that utilize them. TPOT does not check whether estimators do or do not take in a random state value (See Tutorial 2). - If using the pre-built search spaces provided by TPOT, make sure to pass in
random_statetotpot2.config.get_configspaceortpot2.config.template_search_spaces.get_template_search_spaces. This ensures all estimators that support it get a fixed random_state value. (See Tutorial 2). - If using custom Node and Pipeline types, ensure all random decisions utilize the rng parameter passed into the mutation/crossover functions.
- If
max_eval_time_minsis set, TPOT will terminate pipelines that exceed this time limit. If the pipeline evaluation happens to be very similar to the time limit, small random fluctuations in CPU allocation may cause a given pipeline to be evaluated in one run but not another. This slightly different result would throw off the random number generator throughout the rest of the run. Settingmax_eval_time_minsto None or a higher value may prevent this edge case. - If using
TPOTEstimatorSteadyStatewithn_jobs>1, it is also possible that random fluctuations in CPU allocation slightly change the order in which pipelines are evaluated, which will affect the downstream results.TPOTEstimatorSteadyStateis more reliably reproducible whenn_jobs=1(This is not an issue for the defaultTPOTEstimator,TPOTClassifier,TPOTRegressoras they used a batched generational approach where execution order does not impact results).
- Check that
- TPOT 没有使用我预期的所有 CPU 核心,尽管我设置了
n_jobs。- 默认的 TPOT 算法使用代际方法。这意味着 TPOT 在开始下一批之前需要评估
population_size(默认 50)个管道。在每一代结束时,TPOT 可能会在等待最后几个管道完成评估时留下未使用的线程。某些估计器或管道的评估速度可能比其他管道慢得多。可以通过以下几种方式解决:- 减少
max_eval_time_mins以提前终止长时间运行的管道评估。 - 移除容易导致非常慢收敛的估计器或超参数配置(通常是
SVC或SVR)。 - 或者,
TPOTEstimatorSteadyState使用了一种稍微不同的进化算法后端,它不使用代际方法。相反,一旦前一个管道完成,新的管道就会生成并评估。使用此估计器时,所有核心应始终被利用。 - 有时,将 n_jobs 设置为线程数的倍数可以帮助最小化线程在等待其他线程完成时处于空闲状态的可能性。
- 减少
- 默认的 TPOT 算法使用代际方法。这意味着 TPOT 在开始下一批之前需要评估
更多选项¶
tpot2.TPOTClassifier 和 tpot2.TPOTRegressor 有一组简化的超参数,并为分类和回归问题设置了默认值。目前,这两个都使用 tpot2.TPOTEstimator 类中的标准进化算法。如果您想要更多的控制,可以查看 tpot2.TPOTEstimator 或 tpot2.TPOTEstimatorSteadyState 类。
TPOT2 内置了两种进化算法,分别对应两种不同的估计器类。
tpot2.TPOTEstimator使用标准的进化算法,每一代都精确评估 population_size 个个体。这与 TPOT1 中的算法类似。下一代不会开始,直到上一代完全评估完毕。这会导致 CPU 时间未充分利用,因为核心在等待最后几个个体完成训练,但可能会保持种群的多样性。tpot2.TPOTEstimatorSteadyState的不同之处在于,它会在一个个体完成评估后立即生成并评估下一个个体。正在评估的个体数量由 n_jobs 参数决定。不再有世代的概念。population_size 参数现在指的是已评估父代列表的大小。当一个个体被评估后,选择方法会更新父代列表。这使得在使用多核时能够更高效地利用资源。
tpot2.TPOTEstimatorSteadyState¶
import tpot2
import sklearn
import sklearn.datasets
graph_search_space = tpot2.search_spaces.pipelines.GraphSearchPipeline(
root_search_space= tpot2.config.get_search_space(["KNeighborsClassifier", "LogisticRegression", "DecisionTreeClassifier"]),
leaf_search_space = tpot2.config.get_search_space("selectors"),
inner_search_space = tpot2.config.get_search_space(["transformers"]),
max_size = 10,
)
est = tpot2.TPOTEstimatorSteadyState(
search_space = graph_search_space,
scorers=['roc_auc_ovr',tpot2.objectives.complexity_scorer],
scorers_weights=[1,-1],
classification=True,
max_eval_time_mins=15,
max_time_mins=30,
early_stop=10, #In TPOTEstimatorSteadyState, since there are no generations, early_stop is the number of pipelines to evaluate before stopping.
n_jobs=30,
verbose=2)
scorer = sklearn.metrics.get_scorer('roc_auc_ovo')
X, y = sklearn.datasets.load_breast_cancer(return_X_y=True)
X_train, X_test, y_train, y_test = sklearn.model_selection.train_test_split(X, y, train_size=0.75, test_size=0.25)
est.fit(X_train, y_train)
print(scorer(est, X_test, y_test))
Evaluations: : 113it [00:21, 5.15it/s] /home/perib/miniconda3/envs/myenv/lib/python3.10/site-packages/sklearn/linear_model/_sag.py:349: ConvergenceWarning: The max_iter was reached which means the coef_ did not converge warnings.warn(
0.9957890070921986
fitted_pipeline = est.fitted_pipeline_ # access best pipeline directly
fitted_pipeline.plot()
#view the summary of all evaluated individuals as a pandas dataframe
est.evaluated_individuals.head()
tpot2.TPOTEstimator¶
import tpot2
import sklearn
import sklearn.datasets
est = tpot2.TPOTEstimator(
search_space = graph_search_space,
max_time_mins=10,
scorers=['roc_auc_ovr'], #scorers can be a list of strings or a list of scorers. These get evaluated during cross validation.
scorers_weights=[1],
classification=True,
n_jobs=1,
early_stop=5, #how many generations with no improvement to stop after
#List of other objective functions. All objective functions take in an untrained GraphPipeline and return a score or a list of scores
other_objective_functions= [ ],
#List of weights for the other objective functions. Must be the same length as other_objective_functions. By default, bigger is better is set to True.
other_objective_functions_weights=[],
verbose=2)
scorer = sklearn.metrics.get_scorer('roc_auc_ovo')
X, y = sklearn.datasets.load_breast_cancer(return_X_y=True)
X_train, X_test, y_train, y_test = sklearn.model_selection.train_test_split(X, y, train_size=0.75, test_size=0.25)
est.fit(X_train, y_train)
print(scorer(est, X_test, y_test))
回归示例
import tpot2
import sklearn
import sklearn.metrics
import sklearn.datasets
scorer = sklearn.metrics.get_scorer('neg_mean_squared_error')
X, y = sklearn.datasets.load_diabetes(return_X_y=True)
X_train, X_test, y_train, y_test = sklearn.model_selection.train_test_split(X, y, train_size=0.75, test_size=0.25)
est = tpot2.tpot_estimator.templates.TPOTRegressor(n_jobs=4, max_time_mins=30, verbose=2, cv=5, early_stop=5)
est.fit(X_train, y_train)
print(scorer(est, X_test, y_test))