决策树用树形结构对样本的属性进行分类,是最直观的分类算法,而且也可以用于回归。
1.决策树算法的优点:
1:可处理具有不相关特征的数据,容易构造出易于理解的规则,理解和解释起来简单,且决策树模型可以想象
2:需要准备的数据量不大,而其他的技术往往需要很大的数据集,需要创建虚拟变量,去除不完整的数据,但是该算法对于丢失的数据不能进行准确的预测
3:决策树算法的时间复杂度(即预测数据)是用于训练决策树的数据的对数
4:能够处理数字和数据的类别(需要做相应的转变),而其他算法分析的数据集往往是只有一种类型的变量
5:能够处理多输出的问题
6:可能使用统计检验来验证模型,这是为了验证模型的可靠性
2.决策树算法的缺点:
1:决策树算法学习者可以创建复杂的树,但是没有推广依据,这就是所谓的过拟合,为了避免这种问题,出现了剪枝的概念,即设置一个叶子结点所需要的最小数目或者设置树的最大深度
2:决策树的结果可能是不稳定的,因为在数据中一个很小的变化可能导致生成一个完全不同的树,这个问题可以通过使用集成决策树来解决
3:决策树学习算法是基于启发式算法,如贪婪算法,寻求在每个节点上的局部最优决策。这样的算法不能保证返回全局最优决策树。
4:决策树学习者很可能在某些类占主导地位时创建有有偏异的树,因此建议用平衡的数据训练决策树
5:处理缺失数据困难,过度拟合以及忽略数据集中属性之间的相关性等。
3.数据集
![](https://i-blog.csdnimg.cn/blog_migrate/f6b86849f0ecc906e0e75668bcbe15d4.png)
我们的任务就是训练一个决策树分类器,输入身高和体重,分类器能给出这个人是胖子还是瘦子。
4.python实现
# -*- coding: utf-8 -*-
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("tree.txt") as ifile:
for line in ifile:
tokens = line.strip().split(' ')
#print tokens
data.append([float(tk) for tk in tokens[:-1]])
#print data
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=)
#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))
print('-----------')
result = clf.predict(x_test)
print(x_test)
print(result)
print(y_test)
print(np.mean(result==y_test))
'''准确率与召回率'''
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']))
输出结果如下
5.补充
拆分训练数据与测试数据
这样做是为了方便做交叉检验。交叉检验是为了充分测试分类器的稳定性。
代码中的0.2表示随机取20%的数据作为测试用。其余80%用于训练决策树。
也就是说10个样本中随机取8个训练。本文数据集小,这里的目的是可以看到由于取的训练数据随机,每次构建的决策树都不一样。
特征的不同影响因子
样本的不同特征对分类的影响权重差异会很大。分类结束后看看每个样本对分类的影响度也是很重要的。
在本例中,身高的权重为0.20,体重为0.79,可以看到重量的重要性远远高于身高。对于胖瘦的判定而言,这也是相当符合逻辑的。
准确率与召回率
这2个值是评判分类准确率的一个重要标准。比如代码的最后将所有10个样本输入分类器进行测试的结果:
为thin的准确率为0.83。是因为分类器分出了6个thin,其中正确的有5个,因此分为thin的准确率为5/6=0.83。
分为thin的召回率为1.00。是因为数据集中共有5个thin,而分类器把他们都分对了(虽然把一个fat分成了thin!),召回率5/5=1。
分为fat的准确率为1.00。不再赘述。
分为fat的召回率为0.80。是因为数据集中共有5个fat,而分类器只分出了4个(把一个fat分成了thin!),召回率4/5=0.80。
很多时候,尤其是数据分类难度较大的情况,准确率与召回率往往是矛盾的。你可能需要根据你的需要找到最佳的一个平衡点。
比如本例中,你的目标是尽可能保证找出来的胖子是真胖子(准确率),还是保证尽可能找到更多的胖子(召回率)。
代码还把决策树的结构写入了tree.dot中。打开该文件,很容易画出决策树,还可以看到决策树的更多分类信息。
本文的tree.dot如下所示。
digraph Tree {
0 [label="X[1] <= 55.0000\nentropy = 0.954434002925\nsamples = 8", shape="box"] ;
1 [label="entropy = 0.0000\nsamples = 2\nvalue = [ 2. 0.]", shape="box"] ;
0 -> 1 ;
2 [label="X[1] <= 70.0000\nentropy = 0.650022421648\nsamples = 6", shape="box"] ;
0 -> 2 ;
3 [label="X[0] <= 1.6500\nentropy = 0.918295834054\nsamples = 3", shape="box"] ;
2 -> 3 ;
4 [label="entropy = 0.0000\nsamples = 2\nvalue = [ 0. 2.]", shape="box"] ;
3 -> 4 ;
5 [label="entropy = 0.0000\nsamples = 1\nvalue = [ 1. 0.]", shape="box"] ;
3 -> 5 ;
6 [label="entropy = 0.0000\nsamples = 3\nvalue = [ 0. 3.]", shape="box"] ;
2 -> 6 ;
}
根据这个信息,决策树应该长的如下这个样子: