1、决策树算法
决策树用树形结构对样本的属性进行分类,是最直观的分类算法,而且也可以用于回归。不过对于一些特殊的逻辑分类会有困难。典型的如异或(XOR)逻辑,决策树并不擅长解决此类问题。
决策树的构建不是唯一的,遗憾的是最优决策树的构建属于NP问题。因此如何构建一棵好的决策树是研究的重点。
J. Ross Quinlan在1975提出将信息熵的概念引入决策树的构建,这就是鼎鼎大名的ID3算法。后续的C4.5, C5.0, CART等都是该方法的改进。
熵就是“无序,混乱”的程度。刚接触这个概念可能会有些迷惑。想快速了解如何用信息熵增益划分属性,可以参考这位兄弟的文章:http://blog.csdn.net/alvine008/article/details/37760639
如果还不理解,请看下面这个例子。
假设要构建这么一个自动选好苹果的决策树,简单起见,我只让他学习下面这4个样本:
样本中有2个属性,A0表示是否红苹果。A1表示是否大苹果。
那么这个样本在分类前的信息熵就是S = -(1/2 * log(1/2) + 1/2 * log(1/2)) = 1。
信息熵为1表示当前处于最混乱,最无序的状态。
本例仅2个属性。那么很自然一共就只可能有2棵决策树,如下图所示
显然左边先使用A0(红色)做划分依据的决策树要优于右边用A1(大小)做划分依据的决策树。
当然这是直觉的认知。定量的考察,则需要计算每种划分情况的信息熵增益。
先选A0作划分,各子节点信息熵计算如下:
0,1叶子节点有2个正例,0个负例。信息熵为:e1 = -(2/2 * log(2/2) + 0/2 * log(0/2)) = 0。
2,3叶子节点有0个正例,2个负例。信息熵为:e2 = -(0/2 * log(0/2) + 2/2 * log(2/2)) = 0。
因此选择A0划分后的信息熵为每个子节点的信息熵所占比重的加权和:E = e1*2/4 + e2*2/4 = 0。
选择A0做划分的信息熵增益G(S, A0)=S - E = 1 - 0 = 1.
事实上,决策树叶子节点表示已经都属于相同类别,因此信息熵一定为0。
同样的,如果先选A1作划分,各子节点信息熵计算如下:
0,2子节点有1个正例,1个负例。信息熵为:e1 = -(1/2 * log(1/2) + 1/2 * log(1/2)) = 1。
1,3子节点有1个正例,1个负例。信息熵为:e2 = -(1/2 * log(1/2) + 1/2 * log(1/2)) = 1。
因此选择A1划分后的信息熵为每个子节点的信息熵所占比重的加权和:E = e1*2/4 + e2*2/4 = 1。也就是说分了跟没分一样!
选择A1做划分的信息熵增益G(S, A1)=S - E = 1 - 1 = 0.
因此,每次划分之前,我们只需要计算出信息熵增益最大的那种划分即可。
2、数据集
为方便讲解与理解,我们使用如下一个极其简单的测试数据集:
- 1.5 50 thin
- 1.5 60 fat
- 1.6 40 thin
- 1.6 60 fat
- 1.7 60 thin
- 1.7 80 fat
- 1.8 60 thin
- 1.8 90 fat
- 1.9 70 thin
- 1.9 80 fat
3、Python实现
# -*- coding: utf-8 -*- #https://blog.csdn.net/lsldd/article/details/41223147博客的地址 import numpy as np import scipy as sp from sklearn import tree from sklearn.metrics import precision_recall_curve from sklearn.metrics import classification_report from sklearn.cross_validation import train_test_split ''''' 数据读入 ''' data = [] labels = [] with open("1.txt") as ifile: for line in ifile: tokens = line.strip().split(' ') data.append([float(tk) for tk in tokens[:-1]]) labels.append(tokens[-1]) x = np.array(data) labels = np.array(labels) y = np.zeros(labels.shape) ''''' 标签转换为0/1 ''' y[labels == 'fat'] = 1 ''''' 拆分训练数据与测试数据 ''' x_train, x_test, y_train, y_test = train_test_split(x, y, test_size=0.2) ''''' 使用信息熵作为划分标准,对决策树进行训练 ''' clf = tree.DecisionTreeClassifier(criterion='entropy') print(clf) clf.fit(x_train, y_train) ''''' 把决策树结构写入文件 ''' with open("tree.dot", 'w') as f: f = tree.export_graphviz(clf, out_file=f) ''''' 系数反映每个特征的影响力。越大表示该特征在分类中起到的作用越大 ''' print(clf.feature_importances_) '''''测试结果的打印''' answer = clf.predict(x_train) print(x_train) print(answer) print(y_train) print(np.mean(answer == y_train)) '''''准确率与召回率''' precision, recall, thresholds = precision_recall_curve(y_train, clf.predict(x_train)) answer = clf.predict_proba(x)[:, 1] print(classification_report(y, answer, target_names=['thin', 'fat']))输出的结果为:DecisionTreeClassifier(class_weight=None, criterion='entropy', max_depth=None,
max_features=None, max_leaf_nodes=None,
min_impurity_decrease=0.0, min_impurity_split=None,
min_samples_leaf=1, min_samples_split=2,
min_weight_fraction_leaf=0.0, presort=False, random_state=None,
splitter='best')
[0.45120506 0.54879494]
[[ 1.7 80. ]
[ 1.9 80. ]
[ 1.9 70. ]
[ 1.6 60. ]
[ 1.8 60. ]
[ 1.7 60. ]
[ 1.8 90. ]
[ 1.5 50. ]]
[1. 1. 0. 1. 0. 0. 1. 0.]
[1. 1. 0. 1. 0. 0. 1. 0.]
1.0
precision recall f1-score support
thin 0.80 0.80 0.80 5
fat 0.80 0.80 0.80 5
avg / total 0.80 0.80 0.80 10
这里还保存了tree.dot,树的结构文件
里面的内容为
digraph Tree { node [shape=box] ; 0 [label="X[1] <= 75.0\nentropy = 1.0\nsamples = 8\nvalue = [4, 4]"] ; 1 [label="X[0] <= 1.65\nentropy = 0.722\nsamples = 5\nvalue = [4, 1]"] ; 0 -> 1 [labeldistance=2.5, labelangle=45, headlabel="True"] ; 2 [label="X[0] <= 1.55\nentropy = 1.0\nsamples = 2\nvalue = [1, 1]"] ; 1 -> 2 ; 3 [label="entropy = 0.0\nsamples = 1\nvalue = [1, 0]"] ; 2 -> 3 ; 4 [label="entropy = 0.0\nsamples = 1\nvalue = [0, 1]"] ; 2 -> 4 ; 5 [label="entropy = 0.0\nsamples = 3\nvalue = [3, 0]"] ; 1 -> 5 ; 6 [label="entropy = 0.0\nsamples = 3\nvalue = [0, 3]"] ; 0 -> 6 [labeldistance=2.5, labelangle=-45, headlabel="False"] ; }
这里将python的基础知识再巩固一下
data=[] labels=[] with open("1.txt") as ifile: for line in ifile: tokens = line.strip().split(' ') print(tokens) data.append([float(tk) for tk in tokens[:-1]]) labels.append(tokens[-1]) print(data) print(labels) print(np.array(data)) print(np.array(labels)) 这里的strip()就是去除一些东西,当strip()括号中什么都不加的时候,默认时去除/n /t 等等 split('')是分割的意思,' '这个是按照空格进行分割的,有空格就将其分割为一个个的字符串, 并用逗号隔开,每一个刚分开的小字符串用单引号引着。 这里[:-1]这个是到倒数第二个,添加到data中,tokens[-1]最后一个放到labels中 这里将data转化为np.array的数组的形式,也将labels转化为了数组的形式,因为 numpy比python自带的数组更加高效在做计算时。
['1.5', '50', 'thin']
['1.5', '60', 'fat']
['1.6', '40', 'thin']
['1.6', '60', 'fat']
['1.7', '60', 'thin']
['1.7', '80', 'fat']
['1.8', '60', 'thin']
['1.8', '90', 'fat']
['1.9', '70', 'thin']
['1.9', '80', 'fat']
[[1.5, 50.0], [1.5, 60.0], [1.6, 40.0], [1.6, 60.0], [1.7, 60.0], [1.7, 80.0], [1.8, 60.0], [1.8, 90.0], [1.9, 70.0], [1.9, 80.0]]这个是data形式
['thin', 'fat', 'thin', 'fat', 'thin', 'fat', 'thin', 'fat', 'thin', 'fat']这个是labels形式。
[[ 1.5 50. ]这个时将data转为np.array的形式
[ 1.5 60. ]
[ 1.6 40. ]
[ 1.6 60. ]
[ 1.7 60. ]
[ 1.7 80. ]
[ 1.8 60. ]
[ 1.8 90. ]
[ 1.9 70. ]
[ 1.9 80. ]]
['thin' 'fat' 'thin' 'fat' 'thin' 'fat' 'thin' 'fat' 'thin' 'fat']将labels转化为np.array形式,比原来的labels少了逗号。