如何强制Python决策树每次只在一个节点上继续分裂(每次形成一个node/leaf)

How to force Python decision tree to continue splitting on only one node each time (one node/leaf formed each time)

我想使用 Python 构建一个决策树分类器,但我想强制树,无论它认为什么是最好的,每次只将一个节点分成两个叶子。也就是说,每一次,一个节点都会分裂成一个终端叶子和另一个将继续分裂的内部节点,而不是分裂成两个本身可以分裂的内部节点。我希望其中一个拆分每次都以终止结束,直到您最终得到两个低于最小数量的叶子。

例如下面的树就满足这个要求

但第二个没有:

我想这样做的原因是为了获得一组嵌套的观察分割。我在另一个post()上看到可以找到观察的节点ID,这个很关键。我意识到我可以通过构建一棵没有这种限制的树并将其中一个叶节点上升到顶部来做到这一点,但这可能无法提供足够的观察结果,我基本上希望在所有观察结果中获得这种嵌套结构数据集。

在我的应用程序中,我实际上并不关心分类任务,我只想获得由特征拆分形成的嵌套观察集。我曾计划让目标变量随机生成,这样特征上的分割就没有意义了(这是违反直觉的,这是我想要的,但我将它用于不同的目的)。或者,如果有人知道 Python 中有类似的二进制拆分方法可以达到相同的目的,请告诉我。

我意识到一种方法是构建 max_depth=1 的决策树。这将执行分裂成两片叶子。然后挑出杂质最高的叶子继续分裂,再次将决策树拟合到这个子集上,如此重复。为确保层次结构清晰可见,我重新标记了 leaf_ids 以便很明显,当您在树中向上移动时,ID 值会下降。这是一个例子:

import numpy as np
from sklearn.tree import DecisionTreeClassifier
import pandas as pd

def decision_tree_one_path(X, y=None, min_leaf_size=3):
    nobs = X.shape[0]

    # boolean vector to include observations in the newest split
    include = np.ones((nobs,), dtype=bool)
    # try to get leaves around min_leaf_size
    min_leaf_size = max(min_leaf_size, 1)
    # one-level DT splitter
    dtmodel = DecisionTreeClassifier(splitter="best", criterion="gini", max_depth=1, min_samples_split=int(np.round(2.05*min_leaf_size)))             
    leaf_id = np.ones((nobs,), dtype='int64')
    iter = 0

    if y is None:
        y = np.random.binomial(n=1, p=0.5, size=nobs)

    while nobs >= 2*min_leaf_size:

        dtmodel.fit(X=X.loc[include], y=y[include])
        # give unique node id
        new_leaf_names = dtmodel.apply(X=X.loc[include])
        impurities = dtmodel.tree_.impurity[1:]
        if len(impurities) == 0:
            # was not able to split while maintaining constraint
            break

        # make sure node that is not split gets the lower node_label 1
        most_impure_node = np.argmax(impurities)
        if most_impure_node == 0: # i.e., label 1
            # switch 1 and 2 labels above
            is_label_2 = new_leaf_names == 2
            new_leaf_names[is_label_2] = 1
            new_leaf_names[np.logical_not(is_label_2)] = 2

        # rename leaves
        leaf_id[include] = iter + new_leaf_names
        will_be_split = new_leaf_names == 2

        # ignore the other one
        tmp = np.ones((nobs,), dtype=bool)
        tmp[np.logical_not(will_be_split)] = False
        include[include] = tmp

        # now create new labels
        nobs = np.sum(will_be_split)
        iter = iter + 1

    return leaf_id

leaf_id 因此是按顺序观察的叶子 ID。因此,例如 leaf_id==1 是第一个被拆分为终端节点的观察结果。 leaf_id==2 是下一个从生成 leaf_id==1 的拆分中拆分出来的终端节点,如下所示。因此有k+1片叶子。

#0
#|\
#1 .
#  |\
#  2 .
#.......
#
#     |\ 
#     k (k+1) 
   

不过,我想知道是否有一种方法可以在 Python 中自动执行此操作。