Skip to content

Latest commit

 

History

History
240 lines (184 loc) · 9.51 KB

simplifying-decision-tree-interpretation-decision-rules-python.md

File metadata and controls

240 lines (184 loc) · 9.51 KB

使用 Python 和 Scikit-learn 简化决策树的可解释性

原文:www.kdnuggets.com/2017/05/simplifying-decision-tree-interpretation-decision-rules-python.html

在讨论分类器时,与许多更复杂的分类器(尤其是黑箱类型的分类器)相比,决策树通常被认为是容易解释的模型。这通常是正确的。

尤其适用于从简单数据中创建的相对简单的模型。对于从大量(高维)数据中构建的复杂决策树,这一点就不那么适用了。即使是其他看似简单但深度和/或宽度较大的决策树,重分支的情况也可能很难追踪。


我们的前三个课程推荐

1. Google 网络安全证书 - 快速进入网络安全职业生涯。

2. Google 数据分析专业证书 - 提升你的数据分析技能

3. Google IT 支持专业证书 - 支持你在 IT 领域的组织


简洁的文本表示可以很好地总结决策树模型。此外,某些文本表示的功能超出了总结能力。例如,自动生成能够通过传递实例对未来数据进行分类的函数,在特定情况下可能会有所帮助。但我们不要偏离主题——可解释性是我们在这里讨论的目标。

本文将探讨几种简化决策树表示及其最终可解释性的方法。所有代码均为 Python,决策树建模使用 Scikit-learn。

构建分类器

首先,让我们使用我最喜欢的数据集,通过使用Scikit-learn 的决策树分类器在 Python 中构建一个简单的决策树,指定信息增益作为标准,其余则使用默认设置。由于在这篇文章中我们不关注对未见实例的分类,因此我们不会拆分数据,而是直接使用整个数据集来构建分类器。

import numpy as np
from sklearn import datasets
from sklearn import tree

# Load iris
iris = datasets.load_iris()
X = iris.data
y = iris.target

# Build decision tree classifier
dt = tree.DecisionTreeClassifier(criterion='entropy')
dt.fit(X, y)

视觉化模型的表示

解释决策树的最简单方法之一是通过视觉效果,这可以通过 Scikit-learn 和这几行代码实现:

dotfile = open("dt.dot", 'w')
tree.export_graphviz(dt, out_file=dotfile, feature_names=iris.feature_names)
dotfile.close()

将创建的文件(在我们的示例中为'dt.dot')的内容复制到graphviz 渲染工具,我们可以得到以下决策树表示:

决策树图

使用graphviz 渲染代理的决策树的可视化表示

将模型表示为一个函数

如本文开头所述,我们将探讨几种文本表示决策树的不同方法。

第一个是将决策树模型表示为一个函数

from sklearn.tree import _tree

def tree_to_code(tree, feature_names):
	"""
	Outputs a decision tree model as a Python function

	Parameters:
	-----------
	tree: decision tree model
		The decision tree to represent as a function
	feature_names: list
		The feature names of the dataset used for building the decision tree
	"""

	tree_ = tree.tree_
	feature_name = [
		feature_names[i] if i != _tree.TREE_UNDEFINED else "undefined!"
		for i in tree_.feature
	]
	print("def tree({}):".format(", ".join(feature_names)))

	def recurse(node, depth):
		indent = "  " * depth
		if tree_.feature[node] != _tree.TREE_UNDEFINED:
			name = feature_name[node]
			threshold = tree_.threshold[node]
			print("{}if {} <= {}:".format(indent, name, threshold))
			recurse(tree_.children_left[node], depth + 1)
			print("{}else:  # if {} > {}".format(indent, name, threshold))
			recurse(tree_.children_right[node], depth + 1)
		else:
			print("{}return {}".format(indent, tree_.value[node]))

	recurse(0, 1)

让我们调用这个函数并查看结果:

tree_to_code(dt, list(iris.feature_names))
def tree(sepal length (cm), sepal width (cm), petal length (cm), petal width (cm)):
  if petal length (cm) <= 2.45000004768:
    return [[ 50\.   0\.   0.]]
  else:  # if petal length (cm) > 2.45000004768
    if petal width (cm) <= 1.75:
      if petal length (cm) <= 4.94999980927:
        if petal width (cm) <= 1.65000009537:
          return [[  0\.  47\.   0.]]
        else:  # if petal width (cm) > 1.65000009537
          return [[ 0\.  0\.  1.]]
      else:  # if petal length (cm) > 4.94999980927
        if petal width (cm) <= 1.54999995232:
          return [[ 0\.  0\.  3.]]
        else:  # if petal width (cm) > 1.54999995232
          if petal length (cm) <= 5.44999980927:
            return [[ 0\.  2\.  0.]]
          else:  # if petal length (cm) > 5.44999980927
            return [[ 0\.  0\.  1.]]
    else:  # if petal width (cm) > 1.75
      if petal length (cm) <= 4.85000038147:
        if sepal length (cm) <= 5.94999980927:
          return [[ 0\.  1\.  0.]]
        else:  # if sepal length (cm) > 5.94999980927
          return [[ 0\.  0\.  2.]]
      else:  # if petal length (cm) > 4.85000038147
        return [[  0\.   0\.  43.]]

有趣。让我们看看通过剥离一些“不必要的功能”是否可以提高可解释性,前提是这些功能不是必需的。

将模型表示为伪代码

接下来,对上述代码的轻微修改实现了本文标题所承诺的目标:一组用于表示决策树的决策规则,以略微少一点 Python 风格的伪代码形式。

def tree_to_pseudo(tree, feature_names):
	"""
	Outputs a decision tree model as if/then pseudocode

	Parameters:
	-----------
	tree: decision tree model
		The decision tree to represent as pseudocode
	feature_names: list
		The feature names of the dataset used for building the decision tree
	"""

	left = tree.tree_.children_left
	right = tree.tree_.children_right
	threshold = tree.tree_.threshold
	features = [feature_names[i] for i in tree.tree_.feature]
	value = tree.tree_.value

	def recurse(left, right, threshold, features, node, depth=0):
		indent = "  " * depth
		if (threshold[node] != -2):
			print(indent,"if ( " + features[node] + " <= " + str(threshold[node]) + " ) {")
			if left[node] != -1:
				recurse (left, right, threshold, features, left[node], depth+1)
				print(indent,"} else {")
				if right[node] != -1:
					recurse (left, right, threshold, features, right[node], depth+1)
				print(indent,"}")
		else:
			print(indent,"return " + str(value[node]))

	recurse(left, right, threshold, features, 0)

让我们测试这个函数:

tree_to_pseudo(dt, list(iris.feature_names))
 if ( petal length (cm) <= 2.45000004768 ) {
   return [[ 50\.   0\.   0.]]
 } else {
   if ( petal width (cm) <= 1.75 ) {
     if ( petal length (cm) <= 4.94999980927 ) {
       if ( petal width (cm) <= 1.65000009537 ) {
         return [[  0\.  47\.   0.]]
       } else {
         return [[ 0\.  0\.  1.]]
       }
     } else {
       if ( petal width (cm) <= 1.54999995232 ) {
         return [[ 0\.  0\.  3.]]
       } else {
         if ( petal length (cm) <= 5.44999980927 ) {
           return [[ 0\.  2\.  0.]]
         } else {
           return [[ 0\.  0\.  1.]]
         }
       }
     }
   } else {
     if ( petal length (cm) <= 4.85000038147 ) {
       if ( sepal length (cm) <= 5.94999980927 ) {
         return [[ 0\.  1\.  0.]]
       } else {
         return [[ 0\.  0\.  2.]]
       }
     } else {
       return [[  0\.   0\.  43.]]
     }
   }
 }

这看起来也不错,在我计算机科学训练的思维中,使用恰当的 C 风格大括号使得代码比之前的尝试更具可读性。

这些珍宝让我想修改代码以获得真正的决策规则,我计划在完成本文后进行尝试。如果有任何值得注意的进展,我会回来在这里发布我的发现。

马修·梅奥@mattmayo13)是一名数据科学家,也是 KDnuggets 的主编,这是一个开创性的在线数据科学和机器学习资源。他的兴趣包括自然语言处理、算法设计与优化、无监督学习、神经网络和自动化机器学习方法。马修拥有计算机科学硕士学位和数据挖掘研究生文凭。他可以通过 editor1 at kdnuggets[dot]com 联系。

了解更多相关内容