【问题标题】:How to explore a decision tree built using scikit learn如何探索使用 scikit learn 构建的决策树
【发布时间】:2015-12-07 00:57:38
【问题描述】:

我正在使用

构建决策树
clf = tree.DecisionTreeClassifier()
clf = clf.fit(X_train, Y_train)

这一切都很好。但是,我该如何探索决策树呢?

例如,我如何找到 X_train 中的哪些条目出现在特定叶子中?

【问题讨论】:

  • 遇到了类似的问题。您可能会发现我的回答 here(以及那里提到的演练)很有帮助。它使用 0.18 版本中的方法 decision_path。如果有兴趣查看训练样本,请在几个位置将 X_test 替换为 X_train
  • 我看到了决策树的最佳可视化之一...这里...github.com/parrt/dtreeviz/blob/master/notebooks/…

标签: python machine-learning scikit-learn decision-tree


【解决方案1】:

您需要使用预测方法。

训练树后,您输入 X 值来预测它们的输出。

from sklearn.datasets import load_iris
from sklearn.tree import DecisionTreeClassifier
clf = DecisionTreeClassifier(random_state=0)
iris = load_iris()
tree = clf.fit(iris.data, iris.target)
tree.predict(iris.data) 

输出:

>>> tree.predict(iris.data)
array([0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
       0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
       0, 0, 0, 0, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1,
       1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1,
       1, 1, 1, 1, 1, 1, 1, 1, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2,
       2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2,
       2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2])

要获取树结构的详细信息,我们可以使用tree_.__getstate__()

树结构翻译成“ASCII艺术”图片

              0  
        _____________
        1           2
               ______________
               3            12
            _______      _______
            4     7      13   16
           ___   ______        _____
           5 6   8    9        14 15
                      _____
                      10 11

作为数组的树结构。

In [38]: tree.tree_.__getstate__()['nodes']
Out[38]: 
array([(1, 2, 3, 0.800000011920929, 0.6666666666666667, 150, 150.0),
       (-1, -1, -2, -2.0, 0.0, 50, 50.0),
       (3, 12, 3, 1.75, 0.5, 100, 100.0),
       (4, 7, 2, 4.949999809265137, 0.16803840877914955, 54, 54.0),
       (5, 6, 3, 1.6500000953674316, 0.04079861111111116, 48, 48.0),
       (-1, -1, -2, -2.0, 0.0, 47, 47.0), 
       (-1, -1, -2, -2.0, 0.0, 1, 1.0),
       (8, 9, 3, 1.5499999523162842, 0.4444444444444444, 6, 6.0),
       (-1, -1, -2, -2.0, 0.0, 3, 3.0),
       (10, 11, 2, 5.449999809265137, 0.4444444444444444, 3, 3.0),
       (-1, -1, -2, -2.0, 0.0, 2, 2.0), 
       (-1, -1, -2, -2.0, 0.0, 1, 1.0),
       (13, 16, 2, 4.850000381469727, 0.042533081285444196, 46, 46.0),
       (14, 15, 1, 3.0999999046325684, 0.4444444444444444, 3, 3.0),
       (-1, -1, -2, -2.0, 0.0, 2, 2.0), 
       (-1, -1, -2, -2.0, 0.0, 1, 1.0),
       (-1, -1, -2, -2.0, 0.0, 43, 43.0)], 
      dtype=[('left_child', '<i8'), ('right_child', '<i8'), 
             ('feature', '<i8'), ('threshold', '<f8'), 
             ('impurity', '<f8'), ('n_node_samples', '<i8'), 
             ('weighted_n_node_samples', '<f8')])

地点:

  • 第一个节点 [0] 是根节点。
  • 内部节点有left_child 和right_child 引用具有正值且大于当前节点的节点。
  • 叶子的左右子节点的值为-1。
  • 节点 1,5,6, 8,10,11,14,15,16 是叶子。
  • 节点结构是使用深度优先搜索算法构建的。
  • 特征字段告诉我们节点中使用了哪些 iris.data 特征来确定该样本的路径。
  • 阈值告诉我们用于根据特征评估方向的值。
  • 杂质在叶子处达到 0... 因为一旦您到达叶子处,所有样本都属于同一类。
  • n_node_samples 告诉我们有多少样本到达每个叶子。

使用这些信息,我们可以通过遵循脚本上的分类规则和阈值,轻松地将每个样本 X 跟踪到它最终到达的叶子。此外,n_node_samples 将允许我们执行单元测试,以确保每个节点获得正确数量的样本。然后使用 tree.predict 的输出,我们可以将每个叶子映射到关联的类。

【讨论】:

  • 谢谢。这告诉我类,但不告诉我每个项目在决策树的哪个叶子中。如果我可以以某种方式提取到达每个叶子所需的规则,我可以在数据上重新运行这些规则。
  • 当您说要查看叶子时,您的意思是要查看树在每个节点处使用的规则吗?如果是这种情况,那么也许这会有所帮助:stackoverflow.com/questions/20224526/…
  • 对于给定的叶子,我想查看决策树将放置在该叶子上的训练数据。换句话说,每个叶子都与一系列规则(比较)相关联。如果您应用这些规则,我希望看到您获得的数据子集。
  • 只是为了确保我们使用相同的术语。决策树由没有传入边的根节点组成。具有传入和传出边以及叶子的内部节点(也称为终端或决策节点)每个叶子都被分配一个类。 ise.bgu.ac.il/faculty/liorr/hbchap9.pdf 当你说你想看到叶子而不是类时,你的意思是如果 2 个叶子被分配了同一个类,你想区分一个类中通过不同路径到达该类的不同实例?
  • tree.tree_.__getstate__()['nodes'] 的最后两列是什么?
【解决方案2】:

注意:这不是答案,只是对可能解决方案的提示。

我最近在我的项目中遇到了类似的问题。我的目标是为某些特定样本提取相应的决策链。我认为您的问题是我的一个子集,因为您只需要记录决策链中的最后一步。

到目前为止,似乎唯一可行的解​​决方案是在 Python 中编写一个自定义的 predict 方法来跟踪整个过程中的决策。原因是 scikit-learn 提供的 predict 方法无法做到这一点(据我所知)。更糟糕的是,它是 C 实现的包装器,很难定制。

自定义对我的问题来说很好,因为我正在处理一个不平衡的数据集,而且我关心的样本(正样本)很少。所以我可以先使用 sklearn predict 将它们过滤掉,然后使用我的自定义获取决策链。

但是,如果您拥有大型数据集,这可能不适合您。因为如果您解析树并在 Python 中进行预测,它将在 Python 速度下运行缓慢并且不会(轻松)扩展。您可能不得不回退到自定义 C 实现。

【讨论】:

  • 包含尽可能多的研究的部分答案仍然是可以接受的。
  • 谢谢。没有时间实现这个想法。希望有代码的人很快就会出现。
【解决方案3】:

我对 Drew 博士发布的内容进行了一些更改。
以下代码,给定一个数据框和拟合后的决策树,返回:

  • rules_list:规则列表
  • values_path:条目列表(通过路径的每个类的条目)

    import numpy as np  
    import pandas as pd  
    from sklearn.tree import DecisionTreeClassifier 
    
    def get_rules(dtc, df):
        rules_list = []
        values_path = []
        values = dtc.tree_.value
    
        def RevTraverseTree(tree, node, rules, pathValues):
            '''
            Traverase an skl decision tree from a node (presumably a leaf node)
            up to the top, building the decision rules. The rules should be
            input as an empty list, which will be modified in place. The result
            is a nested list of tuples: (feature, direction (left=-1), threshold).  
            The "tree" is a nested list of simplified tree attributes:
            [split feature, split threshold, left node, right node]
            '''
            # now find the node as either a left or right child of something
            # first try to find it as a left node            
    
            try:
                prevnode = tree[2].index(node)           
                leftright = '<='
                pathValues.append(values[prevnode])
            except ValueError:
                # failed, so find it as a right node - if this also causes an exception, something's really f'd up
                prevnode = tree[3].index(node)
                leftright = '>'
                pathValues.append(values[prevnode])
    
            # now let's get the rule that caused prevnode to -> node
            p1 = df.columns[tree[0][prevnode]]    
            p2 = tree[1][prevnode]    
            rules.append(str(p1) + ' ' + leftright + ' ' + str(p2))
    
            # if we've not yet reached the top, go up the tree one more step
            if prevnode != 0:
                RevTraverseTree(tree, prevnode, rules, pathValues)
    
        # get the nodes which are leaves
        leaves = dtc.tree_.children_left == -1
        leaves = np.arange(0,dtc.tree_.node_count)[leaves]
    
        # build a simpler tree as a nested list: [split feature, split threshold, left node, right node]
        thistree = [dtc.tree_.feature.tolist()]
        thistree.append(dtc.tree_.threshold.tolist())
        thistree.append(dtc.tree_.children_left.tolist())
        thistree.append(dtc.tree_.children_right.tolist())
    
        # get the decision rules for each leaf node & apply them
        for (ind,nod) in enumerate(leaves):
    
            # get the decision rules
            rules = []
            pathValues = []
            RevTraverseTree(thistree, nod, rules, pathValues)
    
            pathValues.insert(0, values[nod])      
            pathValues = list(reversed(pathValues))
    
            rules = list(reversed(rules))
    
            rules_list.append(rules)
            values_path.append(pathValues)
    
        return (rules_list, values_path)
    

下面是一个例子:

df = pd.read_csv('df.csv')

X = df[df.columns[:-1]]
y = df['classification']

X_train, X_test, y_train, y_test = train_test_split(X, y, test_size=0.2)

dtc = DecisionTreeClassifier(max_depth=2)
dtc.fit(X_train, y_train)

拟合的决策树生成了以下树:Decision Tree with width 2

此时,只是调用函数:

get_rules(dtc, df)

这是函数返回的内容:

rules = [  
    ['first <= 63.5', 'first <= 43.5'],  
    ['first <= 63.5', 'first > 43.5'],  
    ['first > 63.5', 'second <= 19.700000762939453'],  
    ['first > 63.5', 'second > 19.700000762939453']
]

values = [
    [array([[ 1568.,  1569.]]), array([[ 636.,  241.]]), array([[ 284.,  57.]])],
    [array([[ 1568.,  1569.]]), array([[ 636.,  241.]]), array([[ 352.,  184.]])],
    [array([[ 1568.,  1569.]]), array([[  932.,  1328.]]), array([[ 645.,  620.]])],
    [array([[ 1568.,  1569.]]), array([[  932.,  1328.]]), array([[ 287.,  708.]])]
]

显然,在值中,对于每条路径,也有叶子值。

【讨论】:

  • 函数get_rules中,我觉得需要切换dtc和df?
  • 另一件事是,应该是 return(rules_list,values_path) 而不是 return(r,values_path)?
  • 抱歉 Nivi 迟到了,我刚刚看到了 cmets。首先,在 get_rules 是的,对不起,它必须被切换,我编辑它。第二个也是正确的,我很抱歉错误,我更新了答案
【解决方案4】:

下面的代码应该生成你的前十个特征的图:

import numpy as np
import matplotlib.pyplot as plt

importances = clf.feature_importances_
std = np.std(clf.feature_importances_,axis=0)
indices = np.argsort(importances)[::-1]

# Print the feature ranking
print("Feature ranking:")

for f in range(10):
    print("%d. feature %d (%f)" % (f + 1, indices[f], importances[indices[f]]))

# Plot the feature importances of the forest
plt.figure()
plt.title("Feature importances")
plt.bar(range(10), importances[indices],
       color="r", yerr=std[indices], align="center")
plt.xticks(range(10), indices)
plt.xlim([-1, 10])
plt.show()

取自here,稍作修改以适应DecisionTreeClassifier

这并不能完全帮助您探索树,但它确实告诉您有关树的信息。

【讨论】:

  • 谢谢,但我想看看哪些训练数据属于每个叶子,例如。目前我必须绘制决策树,写下规则,编写脚本来使用这些规则过滤数据。这不可能是正确的方式!
  • 您的数据是否足够小,可以手动或在电子表格中运行这些计算?我假设这是一个类,在这种情况下,最好不要只运行算法并复制结构。也就是说,我想有一些方法可以从 sci-kit 中获取树的结构。这是 DecisionTreeClassifier 的来源:github.com/scikit-learn/scikit-learn/blob/master/sklearn/tree/…
  • 不是上课用的!我有大约 1000000 个项目,所以我通过编写一个单独的 python 脚本来做到这一点。但是我什至不知道如何自动提取每个叶子的规则。有办法吗?
  • 这是我目前用来绘制决策树的。我可以为点文件编写一个解析器,但看起来很尴尬。
【解决方案5】:

此代码将完全按照您的要求进行。这里,nX_train 中的观察数。最后,(n,number_of_leaves) 大小的数组 leaf_observations 在每一列中保存布尔值,用于索引到 X_train 以获取每个叶子中的观察值。 leaf_observations 的每一列对应leaves 中的一个元素,该元素具有叶子的节点ID。

# get the nodes which are leaves
leaves = clf.tree_.children_left == -1
leaves = np.arange(0,clf.tree_.node_count)[leaves]

# loop through each leaf and figure out the data in it
leaf_observations = np.zeros((n,len(leaves)),dtype=bool)
# build a simpler tree as a nested list: [split feature, split threshold, left node, right node]
thistree = [clf.tree_.feature.tolist()]
thistree.append(clf.tree_.threshold.tolist())
thistree.append(clf.tree_.children_left.tolist())
thistree.append(clf.tree_.children_right.tolist())
# get the decision rules for each leaf node & apply them
for (ind,nod) in enumerate(leaves):
    # get the decision rules in numeric list form
    rules = []
    RevTraverseTree(thistree, nod, rules)
    # convert & apply to the data by sequentially &ing the rules
    thisnode = np.ones(n,dtype=bool)
    for rule in rules:
        if rule[1] == 1:
            thisnode = np.logical_and(thisnode,X_train[:,rule[0]] > rule[2])
        else:
            thisnode = np.logical_and(thisnode,X_train[:,rule[0]] <= rule[2])
    # get the observations that obey all the rules - they are the ones in this leaf node
    leaf_observations[:,ind] = thisnode

这需要这里定义的辅助函数,它从指定节点开始递归地遍历树来构建决策规则。

def RevTraverseTree(tree, node, rules):
    '''
    Traverase an skl decision tree from a node (presumably a leaf node)
    up to the top, building the decision rules. The rules should be
    input as an empty list, which will be modified in place. The result
    is a nested list of tuples: (feature, direction (left=-1), threshold).  
    The "tree" is a nested list of simplified tree attributes:
    [split feature, split threshold, left node, right node]
    '''
    # now find the node as either a left or right child of something
    # first try to find it as a left node
    try:
        prevnode = tree[2].index(node)
        leftright = -1
    except ValueError:
        # failed, so find it as a right node - if this also causes an exception, something's really f'd up
        prevnode = tree[3].index(node)
        leftright = 1
    # now let's get the rule that caused prevnode to -> node
    rules.append((tree[0][prevnode],leftright,tree[1][prevnode]))
    # if we've not yet reached the top, go up the tree one more step
    if prevnode != 0:
        RevTraverseTree(tree, prevnode, rules)

【讨论】:

    【解决方案6】:

    我认为一个简单的选择是使用经过训练的决策树的应用方法。训练树,应用训练数据并根据返回的索引构建查找表:

    import numpy as np
    from sklearn.tree import DecisionTreeClassifier
    from sklearn.datasets import load_iris
    
    iris = load_iris()
    clf = DecisionTreeClassifier()
    clf = clf.fit(iris.data, iris.target)
    
    # apply training data to decision tree
    leaf_indices = clf.apply(iris.data)
    lookup = {}
    
    # build lookup table
    for i, leaf_index in enumerate(leaf_indices):
        try:
            lookup[leaf_index].append(iris.data[i])
        except KeyError:
            lookup[leaf_index] = []
            lookup[leaf_index].append(iris.data[i])
    
    # test
    unkown_sample = [[4., 3.1, 6.1, 1.2]]
    index = clf.apply(unkown_sample)
    print(lookup[index[0]])
    

    【讨论】:

      【解决方案7】:

      您是否尝试过将您的 DecisionTree 转储到 graphviz 的 .dot 文件 [1],然后使用 graph_tool [2] 加载它。:

      import numpy as np
      from sklearn.tree import DecisionTreeClassifier
      from sklearn.datasets import load_iris
      from graph_tool.all import *
      
      iris = load_iris()
      clf = DecisionTreeClassifier()
      clf = clf.fit(iris.data, iris.target)
      
      tree.export_graphviz(clf,out_file='tree.dot')
      
      #load graph with graph_tool and explore structure as you please
      g = load_graph('tree.dot')
      
      for v in g.vertices():
         for e in v.out_edges():
             print(e)
         for w in v.out_neighbours():
             print(w)
      

      [1]http://scikit-learn.org/stable/modules/generated/sklearn.tree.export_graphviz.html

      [2]https://graph-tool.skewed.de/

      【讨论】:

      猜你喜欢
      • 2019-07-01
      • 2017-02-23
      • 2020-04-05
      • 2014-06-26
      • 2017-03-26
      • 1970-01-01
      • 2019-12-26
      • 2013-12-12
      相关资源
      最近更新 更多