机器学习每日一练之决策树python原理及其基于sklearn代码实现

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. 1.5 50 thin  
  2. 1.5 60 fat  
  3. 1.6 40 thin  
  4. 1.6 60 fat  
  5. 1.7 60 thin  
  6. 1.7 80 fat  
  7. 1.8 60 thin  
  8. 1.8 90 fat  
  9. 1.9 70 thin  
  10. 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少了逗号。


猜你喜欢

转载自blog.csdn.net/gentelyang/article/details/80223435