1.分类决策树模型是表示基于特征对实例进行分类的树形结构。决策树可以转换成一个if-then规则的集合,也可以看作是定义在特征空间划分上的类的条件概率分布。

2.决策树学习旨在构建一个与训练数据拟合很好,并且复杂度小的决策树。因为从可能的决策树中直接选取最优决策树是NP完全问题。现实中采用启发式方法学习次优的决策树。

决策树学习算法包括3部分:特征选择树的生成树的剪枝。常用的算法有ID3、 C4.5和CART。

3.特征选择的目的在于选取对训练数据能够分类的特征。特征选择的关键是其准则。常用的准则如下:
(1)样本集合 D 对特征 A 的信息增益(ID3)

决策树 - 图1

决策树 - 图2

决策树 - 图3
其中,H(D)是数据集 D的熵,决策树 - 图4是数据集决策树 - 图5的熵,决策树 - 图6是数据集 D对特征 A的条件熵。 决策树 - 图7是 D中特征 A取第 i个值的样本子集,决策树 - 图8是 D中属于第 k类的样本子集。n是特征 A取 值的个数,K是类的个数。

(2)样本集合D对特征 A的信息增益比(C4.5)
决策树 - 图9
其中,g(D,A)是信息增益,H(D)是数据集 D的熵。

(3)样本集合D的基尼指数(CART)
决策树 - 图10
特征A条件下集合D的基尼指数:
决策树 - 图11

4.决策树的生成。
通常使用信息增益最大、信息增益比最大或基尼指数最小作为特征选择的准则。决策树的生成往往通过计算信息增益或其他指标,从根结点开始,递归地产生决策树。这相当于用信息增益或其他准则不断地选取局部最优的特征,或将训练集分割为能够基本正确分类的子集。

5.决策树的剪枝。
由于生成的决策树存在过拟合问题,需要对它进行剪枝,以简化学到的决策树。决策树的剪枝,往往从已生成的树上剪掉一些叶结点或叶结点以上的子树,并将其父结点或根结点作为新的叶结点,从而简化生成的决策树。


  1. import numpy as np
  2. import pandas as pd
  3. import matplotlib.pyplot as plt
  4. %matplotlib inline
  5. from sklearn.datasets import load_iris
  6. from sklearn.model_selection import train_test_split
  7. from collections import Counter
  8. import math
  9. from math import log
  10. import pprint

例5.1

  1. # 书上题目5.1
  2. def create_data():
  3. datasets = [['青年', '否', '否', '一般', '否'],
  4. ['青年', '否', '否', '好', '否'],
  5. ['青年', '是', '否', '好', '是'],
  6. ['青年', '是', '是', '一般', '是'],
  7. ['青年', '否', '否', '一般', '否'],
  8. ['中年', '否', '否', '一般', '否'],
  9. ['中年', '否', '否', '好', '否'],
  10. ['中年', '是', '是', '好', '是'],
  11. ['中年', '否', '是', '非常好', '是'],
  12. ['中年', '否', '是', '非常好', '是'],
  13. ['老年', '否', '是', '非常好', '是'],
  14. ['老年', '否', '是', '好', '是'],
  15. ['老年', '是', '否', '好', '是'],
  16. ['老年', '是', '否', '非常好', '是'],
  17. ['老年', '否', '否', '一般', '否'],
  18. ]
  19. labels = [u'年龄', u'有工作', u'有自己的房子', u'信贷情况', u'类别']
  20. # 返回数据集和每个维度的名称
  21. return datasets, labels
  22. datasets, labels = create_data()
  23. train_data = pd.DataFrame(datasets, columns=labels)
  24. train_data

Out[5]:

年龄 有工作 有自己的房子 信贷情况 类别
0 青年 一般
1 青年
2 青年
3 青年 一般
4 青年 一般
5 中年 一般
6 中年
7 中年
8 中年 非常好
9 中年 非常好
10 老年 非常好
11 老年
12 老年
13 老年 非常好
14 老年 一般
  1. # 熵
  2. def calc_ent(datasets):
  3. data_length = len(datasets)
  4. label_count = {}
  5. for i in range(data_length):
  6. label = datasets[i][-1]
  7. if label not in label_count:
  8. label_count[label] = 0
  9. label_count[label] += 1
  10. ent = -sum([(p / data_length) * log(p / data_length, 2)
  11. for p in label_count.values()])
  12. return ent
  13. # def entropy(y):
  14. # """
  15. # Entropy of a label sequence
  16. # """
  17. # hist = np.bincount(y)
  18. # ps = hist / np.sum(hist)
  19. # return -np.sum([p * np.log2(p) for p in ps if p > 0])
  20. # 经验条件熵
  21. def cond_ent(datasets, axis=0):
  22. data_length = len(datasets)
  23. feature_sets = {}
  24. for i in range(data_length):
  25. feature = datasets[i][axis]
  26. if feature not in feature_sets:
  27. feature_sets[feature] = []
  28. feature_sets[feature].append(datasets[i])
  29. cond_ent = sum(
  30. [(len(p) / data_length) * calc_ent(p) for p in feature_sets.values()])
  31. return cond_ent
  32. # 信息增益
  33. def info_gain(ent, cond_ent):
  34. return ent - cond_ent
  35. def info_gain_train(datasets):
  36. count = len(datasets[0]) - 1
  37. ent = calc_ent(datasets)
  38. # ent = entropy(datasets)
  39. best_feature = []
  40. for c in range(count):
  41. c_info_gain = info_gain(ent, cond_ent(datasets, axis=c))
  42. best_feature.append((c, c_info_gain))
  43. print('特征({}) - info_gain - {:.3f}'.format(labels[c], c_info_gain))
  44. # 比较大小
  45. best_ = max(best_feature, key=lambda x: x[-1])
  46. return '特征({})的信息增益最大,选择为根节点特征'.format(labels[best_[0]])
  47. info_gain_train(np.array(datasets))

特征(年龄) - info_gain - 0.083
特征(有工作) - info_gain - 0.324
特征(有自己的房子) - info_gain - 0.420
特征(信贷情况) - info_gain - 0.363

特征(有自己的房子)的信息增益最大,选择为根节点特征

例5.3:利用ID3算法生成决策树

  1. # 定义节点类 二叉树
  2. class Node:
  3. def __init__(self, root=True, label=None, feature_name=None, feature=None):
  4. self.root = root
  5. self.label = label
  6. self.feature_name = feature_name
  7. self.feature = feature
  8. self.tree = {}
  9. self.result = {
  10. 'label:': self.label,
  11. 'feature': self.feature,
  12. 'tree': self.tree
  13. }
  14. def __repr__(self):
  15. return '{}'.format(self.result)
  16. def add_node(self, val, node):
  17. self.tree[val] = node
  18. def predict(self, features):
  19. if self.root is True:
  20. return self.label
  21. return self.tree[features[self.feature]].predict(features)
  22. class DTree:
  23. def __init__(self, epsilon=0.1):
  24. self.epsilon = epsilon
  25. self._tree = {}
  26. # 熵
  27. @staticmethod
  28. def calc_ent(datasets):
  29. data_length = len(datasets)
  30. label_count = {}
  31. for i in range(data_length):
  32. label = datasets[i][-1]
  33. if label not in label_count:
  34. label_count[label] = 0
  35. label_count[label] += 1
  36. ent = -sum([(p / data_length) * log(p / data_length, 2)
  37. for p in label_count.values()])
  38. return ent
  39. # 经验条件熵
  40. def cond_ent(self, datasets, axis=0):
  41. data_length = len(datasets)
  42. feature_sets = {}
  43. for i in range(data_length):
  44. feature = datasets[i][axis]
  45. if feature not in feature_sets:
  46. feature_sets[feature] = []
  47. feature_sets[feature].append(datasets[i])
  48. cond_ent = sum([(len(p) / data_length) * self.calc_ent(p)
  49. for p in feature_sets.values()])
  50. return cond_ent
  51. # 信息增益
  52. @staticmethod
  53. def info_gain(ent, cond_ent):
  54. return ent - cond_ent
  55. def info_gain_train(self, datasets):
  56. count = len(datasets[0]) - 1
  57. ent = self.calc_ent(datasets)
  58. best_feature = []
  59. for c in range(count):
  60. c_info_gain = self.info_gain(ent, self.cond_ent(datasets, axis=c))
  61. best_feature.append((c, c_info_gain))
  62. # 比较大小
  63. best_ = max(best_feature, key=lambda x: x[-1])
  64. return best_
  65. def train(self, train_data):
  66. """
  67. input:数据集D(DataFrame格式),特征集A,阈值eta
  68. output:决策树T
  69. """
  70. _, y_train, features = train_data.iloc[:, :-1], train_data.iloc[:,-1], train_data.columns[:-1]
  71. # 1,若D中实例属于同一类Ck,则T为单节点树,并将类Ck作为结点的类标记,返回T
  72. if len(y_train.value_counts()) == 1:
  73. return Node(root=True, label=y_train.iloc[0])
  74. # 2, 若A为空,则T为单节点树,将D中实例树最大的类Ck作为该节点的类标记,返回T
  75. if len(features) == 0:
  76. return Node(
  77. root=True,
  78. label=y_train.value_counts().sort_values(
  79. ascending=False).index[0])
  80. # 3,计算最大信息增益 同5.1,Ag为信息增益最大的特征
  81. max_feature, max_info_gain = self.info_gain_train(np.array(train_data))
  82. max_feature_name = features[max_feature]
  83. # 4,Ag的信息增益小于阈值eta,则置T为单节点树,并将D中是实例数最大的类Ck作为该节点的类标记,返回T
  84. if max_info_gain < self.epsilon:
  85. return Node(
  86. root=True,
  87. label=y_train.value_counts().sort_values(
  88. ascending=False).index[0])
  89. # 5,构建Ag子集
  90. node_tree = Node(
  91. root=False, feature_name=max_feature_name, feature=max_feature)
  92. feature_list = train_data[max_feature_name].value_counts().index
  93. for f in feature_list:
  94. sub_train_df = train_data.loc[train_data[max_feature_name] ==
  95. f].drop([max_feature_name], axis=1)
  96. # 6, 递归生成树
  97. sub_tree = self.train(sub_train_df)
  98. node_tree.add_node(f, sub_tree)
  99. # pprint.pprint(node_tree.tree)
  100. return node_tree
  101. def fit(self, train_data):
  102. self._tree = self.train(train_data)
  103. return self._tree
  104. def predict(self, X_test):
  105. return self._tree.predict(X_test)
  106. datasets, labels = create_data()
  107. data_df = pd.DataFrame(datasets, columns=labels)
  108. dt = DTree()
  109. tree = dt.fit(data_df)
  110. In [10]:
  111. tree
  112. Out[10]:
  113. {'label:': None, 'feature': 2, 'tree':
  114. {'否': {'label:': None, 'feature': 1, 'tree':
  115. {'否': {'label:': '否', 'feature': None, 'tree': {}},
  116. '是': {'label:': '是', 'feature': None, 'tree': {}}
  117. }
  118. },
  119. '是': {'label:': '是', 'feature': None, 'tree': {}}
  120. }
  121. }
  122. In [11]:
  123. dt.predict(['老年', '否', '否', '一般'])
  124. Out[11]:
  125. '否'

scikit-learn实例

  1. # data
  2. def create_data():
  3. iris = load_iris()
  4. df = pd.DataFrame(iris.data, columns=iris.feature_names)
  5. df['label'] = iris.target
  6. df.columns = [
  7. 'sepal length', 'sepal width', 'petal length', 'petal width', 'label'
  8. ]
  9. data = np.array(df.iloc[:100, [0, 1, -1]])
  10. # print(data)
  11. return data[:, :2], data[:, -1]
  12. X, y = create_data()
  13. X_train, X_test, y_train, y_test = train_test_split(X, y, test_size=0.3)
  14. from sklearn.tree import DecisionTreeClassifier
  15. from sklearn.tree import export_graphviz
  16. import graphviz
  17. clf = DecisionTreeClassifier()
  18. clf.fit(X_train, y_train,)
  19. Out[14]:
  20. DecisionTreeClassifier()
  21. clf.score(X_test, y_test)
  22. Out[15]:
  23. 0.9666666666666667
  24. tree_pic = export_graphviz(clf, out_file="mytree.pdf")
  25. with open('mytree.pdf') as f:
  26. dot_graph = f.read()
  27. graphviz.Source(dot_graph)

_