Chefboost:一个轻量级的决策树框架

2021-07-01 11:06:31 浏览数 (1)

我在Twitter上偶然遇到了chefboost,因为我之前从未听说过它,所以我决定快速查看并测试它。在本文中,我将简要介绍这个库,并提到它与常用库scikit-learn的主要区别,并展示一个在实践中使用chefboost的快速示例。

chefboost简介

我认为在库的GitHub repo中提供了最好的描述:“chefboost是一个轻量级的Python决策树框架,具有类别特征支持”。

与scikit-learn相比,chefboost有三个突出的特点:

支持类别特征,这意味着我们不需要对它们进行预处理,例如,独热编码。

使用chefboost训练的决策树作为if-else语句存储在专用的Python文件中。通过这种方式,我们可以很容易地看到树做出什么样的决定来达到给定的预测。

我们可以从多个算法中选择一个来训练决策树。

在最后一点之后,chefboost提供了三种用于分类树的算法(ID3、C4.5和CART)和一种用于回归树的算法。老实说,我并不完全确定scikit-learn目前实现的是哪种算法,所以我查看了文档(其中也提供了算法的漂亮而简洁的总结)。事实证明,scikit-learn使用了CART算法的优化版本,但是没有对类别特征的支持。

在我们已经介绍的基础上,chefboost还提供了一些更先进的基于树的方法,如随机森林,梯度增强和Adaboost。

Python的一个例子

和往常一样,我们从导入库开始。

代码语言:javascript复制
from chefboost import Chefboost as chef
from sklearn.model_selection import train_test_split
from sklearn.tree import DecisionTreeClassifier
import pandas as pd

对于本例,我们将使用成人收入数据集。你以前可能已经遇到过这个问题,但简而言之,我们的目标是预测一个成年人的年收入是高于还是低于5万美元。为了做到这一点,我们从1994年人口普查数据库中选取了一些数字和分类特征。

代码语言:javascript复制
X = pd.read_csv("../data/adult.csv")
X = X.rename(columns={"income": "Decision"})

chefboost的一个奇怪的地方是对目标变量的处理方法——它必须存储在与特性相同的dataframe中,它必须被称为Decision,并且必须是dataframe的最后一列。很奇怪,但可能有一些好的理由。

我们还将把数据分成训练集和测试集。但是,这种非标准的数据结构要求scikit-learn的train_test_split函数的使用稍有不同。即使数据集不是高度不平衡的,我们使用目标列分层分割。

代码语言:javascript复制
X_train, X_test = train_test_split(X, test_size=0.2, random_state=42, stratify=X["Decision"])

通常,我们也会将类别特性编码为布尔伪变量,但chefboost可以直接处理它们。这就是我们继续训练模型的原因。

为了训练模型,我们使用fit函数并传递数据帧(包含正确格式的数据)和配置字典作为参数。这一次,我们只表示希望使用CART算法。

考虑到我们的数据同时包含类别和数字特征,我们也可以使用C4.5算法,而不是ID3,因为它不能处理数字特征。

代码语言:javascript复制
config = {'algorithm': 'CART'}
model = chef.fit(X_train, config = config)

训练完成后我们看看结果

很高兴看到这么多现成的指标,但最突出的是训练时间。训练这棵树花了10分钟!可以通过在配置字典中将enableParallelism设置为True来并行化训练。通过这种方式,树的分支被并行地训练。然而,这样做并没有实际提高训练速度,至少在我的机器上没有。

另外,与scikit-learn的另一个区别是,chefboost主要使用函数而不是类。

对模型进行训练后创建了一个新文件——> rules.py。正如引言中提到的,它以嵌套if- lift -else语句的形式包含决策树的整个结构。

下面您可以看到部分脚本,整个脚本有20.5k行。一方面,使用这种嵌套结构可以很清楚地遵循决策的逻辑。但另一方面,如果不设置树的最大深度(我认为chefboost中的决策树不可能做到这一点),我们便很难遵循决策路径。

训练模型之后,我们可以将它存储在一个pickle文件中,或者使用restoreTree函数直接从rules.py文件中加载它。

为了得到预测,我们使用预测函数。

代码语言:javascript复制
prediction = chef.predict(model, X_test.iloc[0])

# '<=50K'

您可能已经注意到,我们只向函数传递了一行数据。不幸的是,这是chefboost进行预测的唯一方法。我们可以自然地循环整个数据,但这不如scikit-learn的预测方法方便。

我们可以做的是使用evaluate函数运行一个求值。

代码语言:javascript复制
evaluation = chef.evaluate(model, X_test, task="test")

我们得到的输出与我们从训练中得到的输出类似。但是我们不会花太多时间分析树的性能,因为这不是本文的目标

该库提供的另一个特性是对特性重要性的分析。我不会详细说明它是如何计算的(你可以在这里找到它们)。为了获得重要性,我们需要使用feature_importance函数,并提供rules.py文件的路径作为参数。

代码语言:javascript复制
rules = "outputs/rules/rules.py"
fi = chef.feature_importance(rules).set_index("feature")
fi.plot(kind="barh", title="Feature Importance");

研究结果表明,年龄是预测一个人年收入是否超过5万美元的最重要特征。

最后,我想比较一下chefboost和scikit-learn的速度。当然,后一个库中的决策树需要不同格式的数据,因此我们相应地准备数据。

代码语言:javascript复制
X_sk = pd.get_dummies(X, drop_first=True)
y_sk = X_sk.pop("Decision_>50K")
X_train_sk, X_test_sk, y_train_sk, y_test_sk = train_test_split(X_sk, y_sk, 
                                                                test_size=0.2, 
                                                                random_state=42, 
                                                                stratify=y_sk)

我们使用了与之前相同的分割设置,以确保公平的比较。然后,我们使用%time魔法函数来看看训练模型需要多长时间。

代码语言:javascript复制
%time

tree = DecisionTreeClassifier()
tree.fit(X_train_sk, y_train_sk)

结果如下:

代码语言:javascript复制
CPU times: user 1e 03 ns, sys: 0 ns, total: 1e 03 ns 
Wall time: 3.1 µs

这是相当不同的……我不确定这是什么原因,我打赌创建的if-else树表示。

总结

Chefboost是训练基于树的模型的替代库,

突出的主要特性是对类别特性的支持,以及以嵌套if-else语句的形式输出模型,

与scikit-learn相比,这种训练速度要慢得多,而且要调优的超参数的选择非常有限。

你可以在我的GitHub上找到本文使用的代码。此外,欢迎任何建设性的反馈。你可以在推特上或者评论里联系我。

https://github.com/erykml/medium_articles/blob/master/Machine Learning/chefboost.ipynb

作者:Eryk Lewinson

0 人点赞