python决策树算法代码_分类决策树(python实现)

决策树是一个简单易用的机器学习算法,具有很好的实用性。在风险评估、数据分类、专家系统中都能见到决策树的身影。决策树其实是一系列的if-then规则的集合,它有可读性良好,分类速度快等优点。

我们可以把决策树看成是一些if-then规则的集合,在每一层树上根据属性的值判断走势,知道遇到叶节点,叶节点对应的就是该数据的归类。决策树生成算法遇到的第一个问题就是特征选择问题,即第一次、第二次...第n次应该根据哪个属性来划分数据。这里需要引入熵的概念。熵是衡量变量不确定性的一种度量方式。熵越大表示变量的不确定性越大,反之,则越小。定义熵的大小为:

gif.latex?H(X)=-\sum_{i=1}^{n}p_ilogp_i 。从熵的计算公式可以看出0≤H(X)≤logn,当

gif.latex?p_i等于0或1时,H(X)=0,所以说熵越小不确定性越小,当熵等于0时,就丧失了不确定性。这里还要引入条件熵的概念:条件熵表示在已知随机变量X的条件下随机变量Y的不确定性大小,记为:

X=x_i) 。信息增益定义为集合D的经验熵H(D)与特征A给定的条件下D的条件熵H(D/A)之差,g(D,A) = H(D)-H(D/A)。回到生成决策树上面,我们现在需要的是计算使得g(D,A)值最大的特征A,即求出

gif.latex?\underset{A}{argmax}g(D,A)  。上面H(D/A)计算公式中对应的 

gif.latex?x_i 表示特征A的一个取值变量。

除了上面介绍的信息增益,还有一种作为特征选择的准则:信息增益比。由于在计算信息增益的过程中,如果一个特征它的取值较多的话,对应的信息增益值也会比其他特征对应的信息增益值高,这样就会导致在特征选择上出现不公平的现象。后面将要介绍的C4.5算法就是在ID3的算法上进行改进,引入了信息增益比来作为特征选择的准则。信息增益比计算公式:

A)}{H_{A}(D)} 。其中,

gif.latex?H_{A(D)} = -\sum_{i=1}^{n}\frac{\left | D_i \right |}{\left | D \right |}log_{2}\frac{\left | D_i \right |}{\left | D \right |} 。

生成决策树的算法有三类:ID3、C4.5、CART。ID3是基础,C4.5算法是从ID3的基础上发展而来的。CART(分类回归树)是一种二叉决策树,它既能用于分类也可以用于回归。CART一般用基尼指数(Gini index)最小化准则进行特征选择。

这里给出《统计学习方法》里的C4.5算法:

C4.5生成算法:

输入:训练数据集D,特征集A,阈值

gif.latex?\varepsilon

输出:决策树T

如果D中的所有实例属于同一类

gif.latex?C_{k} ,则置T为单结点树,并将

gif.latex?C_{k}作为该结点的类,返回T;

如果A=∅,则置T为单节点树,并将D中实例数最大的类

gif.latex?C_{k}作为该结点的类,返回T;

否则,按

A)}{H_{A}(D)}计 算A中个特征对D的信息增益比,选择信息增益比最大的特征

gif.latex?A_{g}

如果

gif.latex?A_{g}的信息增益比小于阈值

gif.latex?\varepsilon,则置T为单结点树,并将D中实例数最大的类

gif.latex?C_{k}作为该结点的类,返回T;

否则,对

gif.latex?A_{g}的每一可能值

gif.latex?a_{i},依

gif.latex?A_{g} = a_{i} 将D分割为子集若干非空

gif.latex?D_{i} ,将

gif.latex?D_{i} 中实例树最大的类作为标记,构建子结点,由结点及其子结点构成树T,返回T;

对结点i,以

gif.latex?D_{i} 为训练集,以A - {

gif.latex?A_{g}}为特征集,递归地调用步(1)~步(5),得到子树

gif.latex?T_{i} ,返回

gif.latex?T_{i} .

在机器学习中有一个很常见的问题——过拟合,就是指模型在对训练数据的预测结果上往往能达到很高的准确率,但对新的未知数据却达不到那么高的准确率。在决策树的生成过程中往往会出现过拟合现象,所以需要对决策树进行剪枝。剪枝的过程就是通过极小化决策树的损失函数:

gif.latex?C_{\alpha }(T) = \sum_{t=1}^{\left | T \right |}N_tH_t(T)+\alpha\left | T \right | 来实现的,|T|表示叶结点的个数,t表示树的叶结点,

gif.latex?N_t代表叶结点t的样本点数,

gif.latex?H_t(T) 为叶结点t的经验熵,参数α用来控制模型与训练数据的拟合程度跟模型复杂度|T|之间的影响。

具体的剪枝算法这里参考《统计学习方法》里的剪枝算法:

树的剪枝算法:

输入:生成算法产生的整个树T,参数α

输出:修剪后的子树

gif.latex?T_{\alpha }

计算每个结点的经验熵

递归地从树的叶结点向上回缩

返回2,直至不能继续为止,得到损失函数最小的子树

gif.latex?T_{\alpha }

下面是用C4.5算法生成的决策树(未进行剪枝),训练数据集:irisTrain.txt ,测试数据集:irisTest.txt 。全部数据集和代码下载地址:Codes & datasets 。

#-*- coding:utf-8 -*-

#Author:Jorbe

#Date:2014.06.14 16:10

import sys

from math import log

import operator

from numpy import mean

def get_labels(train_file):

'''

返回所有数据集labels(列表)

'''

labels = []

for index,line in enumerate(open(train_file,'rU').readlines()):

label = line.strip().split(',')[-1]

labels.append(label)

return labels

def format_data(dataset_file):

'''

返回dataset(列表集合)和features(列表)

'''

dataset = []

for index,line in enumerate(open(dataset_file,'rU').readlines()):

line = line.strip()

fea_and_label = line.split(',')

dataset.append([float(fea_and_label[i]) for i in range(len(fea_and_label)-1)]+[fea_and_label[len(fea_and_label)-1]])

#features = [dataset[0][i] for i in range(len(dataset[0])-1)]

#sepal length(花萼长度)、sepal width(花萼宽度)、petal length(花瓣长度)、petal width(花瓣宽度)

features = ['sepal_length','sepal_width','petal_length','petal_width']

return dataset,features

def split_dataset(dataset,feature_index,labels):

'''

按指定feature划分数据集,返回四个列表:

@dataset_less:指定特征项的属性值<=该特征项平均值的子数据集

@dataset_greater:指定特征项的属性值>该特征项平均值的子数据集

@label_less:按指定特征项的属性值<=该特征项平均值切割后子标签集

@label_greater:按指定特征项的属性值>该特征项平均值切割后子标签集

'''

dataset_less = []

dataset_greater = []

label_less = []

label_greater = []

datasets = []

for data in dataset:

datasets.append(data[0:4])

mean_value = mean(datasets,axis = 0)[feature_index] #数据集在该特征项的所有取值的平均值

for data in dataset:

if data[feature_index] > mean_value:

dataset_greater.append(data)

label_greater.append(data[-1])

else:

dataset_less.append(data)

label_less.append(data[-1])

return dataset_less,dataset_greater,label_less,label_greater

def cal_entropy(dataset):

'''

计算数据集的熵大小

'''

n = len(dataset)

label_count = {}

for data in dataset:

label = data[-1]

if label_count.has_key(label):

label_count[label] += 1

else:

label_count[label] = 1

entropy = 0

for label in label_count:

prob = float(label_count[label])/n

entropy -= prob*log(prob,2)

#print 'entropy:',entropy

return entropy

def cal_info_gain(dataset,feature_index,base_entropy):

'''

计算指定特征对数据集的信息增益值

g(D,F) = H(D)-H(D/F) = entropy(dataset) - sum{1,k}(len(sub_dataset)/len(dataset))*entropy(sub_dataset)

@base_entropy = H(D)

'''

datasets = []

for data in dataset:

datasets.append(data[0:4])

#print datasets

mean_value = mean(datasets,axis = 0)[feature_index] #计算指定特征的所有数据集值的平均值

#print mean_value

dataset_less = []

dataset_greater = []

for data in dataset:

if data[feature_index] > mean_value:

dataset_greater.append(data)

else:

dataset_less.append(data)

#条件熵 H(D/F)

condition_entropy = float(len(dataset_less))/len(dataset)*cal_entropy(dataset_less) + float(len(dataset_greater))/len(dataset)*cal_entropy(dataset_greater)

#print 'info_gain:',base_entropy - condition_entropy

return base_entropy - condition_entropy

def cal_info_gain_ratio(dataset,feature_index):

'''

计算信息增益比 gr(D,F) = g(D,F)/H(D)

'''

base_entropy = cal_entropy(dataset)

'''

if base_entropy == 0:

return 1

'''

info_gain = cal_info_gain(dataset,feature_index,base_entropy)

info_gain_ratio = info_gain/base_entropy

return info_gain_ratio

def choose_best_fea_to_split(dataset,features):

'''

根据每个特征的信息增益比大小,返回最佳划分数据集的特征索引

'''

#base_entropy = cal_entropy(dataset)

split_fea_index = -1

max_info_gain_ratio = 0.0

for i in range(len(features)):

#info_gain = cal_info_gain(dataset,i,base_entropy)

#info_gain_ratio = info_gain/base_entropy

info_gain_ratio = cal_info_gain_ratio(dataset,i)

if info_gain_ratio > max_info_gain_ratio:

max_info_gain_ratio = info_gain_ratio

split_fea_index = i

return split_fea_index

def most_occur_label(labels):

'''

返回数据集中出现次数最多的label

'''

label_count = {}

for label in labels:

if label not in label_count.keys():

label_count[label] = 1

else:

label_count[label] += 1

sorted_label_count = sorted(label_count.iteritems(),key = operator.itemgetter(1),reverse = True)

return sorted_label_count[0][0]

def build_tree(dataset,labels,features):

'''

创建决策树

@dataset:训练数据集

@labels:数据集中包含的所有label(可重复)

@features:可进行划分的特征集

'''

#若数据集为空,返回NULL

if len(labels) == 0:

return 'NULL'

#若数据集中只有一种label,返回该label

if len(labels) == len(labels[0]):

return labels[0]

#若没有可划分的特征集,则返回数据集中出现次数最多的label

if len(features) == 0:

return most_occur_label(labels)

#若数据集趋于稳定,则返回数据集中出现次数最多的label

if cal_entropy(dataset) == 0:

return most_occur_label(labels)

split_feature_index = choose_best_fea_to_split(dataset,features)

split_feature = features[split_feature_index]

decesion_tree = {split_feature:{}}

#若划分特征的信息增益比小于阈值,则返回数据集中出现次数最多的label

if cal_info_gain_ratio(dataset,split_feature_index) < 0.3:

return most_occur_label(labels)

del(features[split_feature_index])

dataset_less,dataset_greater,labels_less,labels_greater = split_dataset(dataset,split_feature_index,labels)

decesion_tree[split_feature]['<='] = build_tree(dataset_less,labels_less,features)

decesion_tree[split_feature]['>'] = build_tree(dataset_greater,labels_greater,features)

return decesion_tree

def store_tree(decesion_tree,filename):

'''

把决策树以二进制格式写入文件

'''

import pickle

writer = open(filename,'w')

pickle.dump(decesion_tree,writer)

writer.close()

def read_tree(filename):

'''

从文件中读取决策树,返回决策树

'''

import pickle

reader = open(filename,'rU')

return pickle.load(reader)

def classify(decesion_tree,features,test_data,mean_values):

'''

对测试数据进行分类, decesion_tree : {'petal_length': {'<=': {'petal_width': {'<=': 'Iris-setosa', '>': {'sepal_width': {'<=': 'Iris-versicolor', '>': {'sepal_length': {'<=': 'Iris-setosa', '>': 'Iris-versicolor'}}}}}}, '>': 'Iris-virginica'}}

'''

first_fea = decesion_tree.keys()[0]

fea_index = features.index(first_fea)

if test_data[fea_index] <= mean_values[fea_index]:

sub_tree = decesion_tree[first_fea]['<=']

if type(sub_tree) == dict:

return classify(sub_tree,features,test_data,mean_values)

else:

return sub_tree

else:

sub_tree = decesion_tree[first_fea]['>']

if type(sub_tree) == dict:

return classify(sub_tree,features,test_data,mean_values)

else:

return sub_tree

def get_means(train_dataset):

'''

获取训练数据集各个属性的数据平均值

'''

dataset = []

for data in train_dataset:

dataset.append(data[0:4])

mean_values = mean(dataset,axis = 0) #数据集在该特征项的所有取值的平均值

return mean_values

def run(train_file,test_file):

'''

主函数

'''

labels = get_labels(train_file)

train_dataset,train_features = format_data(train_file)

decesion_tree = build_tree(train_dataset,labels,train_features)

print 'decesion_tree :',decesion_tree

store_tree(decesion_tree,'decesion_tree')

mean_values = get_means(train_dataset)

test_dataset,test_features = format_data(test_file)

n = len(test_dataset)

correct = 0

for test_data in test_dataset:

label = classify(decesion_tree,test_features,test_data,mean_values)

#print 'classify_label correct_label:',label,test_data[-1]

if label == test_data[-1]:

correct += 1

print "准确率: ",correct/float(n)

#############################################################

if __name__ == '__main__':

if len(sys.argv) != 3:

print "please use: python decision.py train_file test_file"

sys.exit()

train_file = sys.argv[1]

test_file = sys.argv[2]

run(train_file,test_file)

运行结果图(点击查看大图):decision_tree-400x250.png

从结果上来看准确率不高,这就是上面所说的由于没有对生成的决策树进行剪枝而造成的过拟合现象。

14 thoughts on “分类决策树(python实现)”

你好,参考写的代码,很详细,学习很多东西。就是发现你使用的是平均值,不知道是否测试过(i+(i+1))/i,以它来平分,看增益情况。这样是否准确率会提升

你好,我没有用到你说的这种方法进行测试过,不知道你这里的(i(i+1))/i指的是什么?

楼主您好,不知道您可以将具体运行方法告诉我吗?我在Python3.4.1上运行一直报错,不知道是为什么?谢谢楼主

我的Python代码是用2.7写的,Python3.4在语法上和python2.7上有很大不同,所以会报错。

楼主在计算信息增益比时,分母使用的函数是H(D),这样每个特征的信息增益比的分母都是一样的,不太合理。引入分母的目的,是为了减小特征个数对信息增益比的影响。因此分母使用Ha(D)函数更合理些

Helpful information. Lucky me I found your website by accident, and I'm surprised why this coincidence didn't took place in advance! I bookmarked it.

不好意思,剪枝的代码我这儿没有,你可以到网上搜搜看,应该很多。

楼主,对于连续的特征,可以多次使用,但是楼主的代码好像只能用一次吧

连续的特征最主要的就是找到一个分割点进行离散化,我不知道您指的多次使用时什么意思?

Having read this I believed it was very informative. I appreciate you spending some time and effort to put this content together. I once again find myself personally spending way too much time both reading and commenting. But so what, it was still worth it!

Simply want to say your article is as amazing. The clearness on your publish is simply spectacular and i could think you are knowledgeable in this subject. Well together with your permission allow me to grasp your feed to keep updated with forthcoming post. Thanks one million and please keep up the gratifying work.

I relish, lead to I discovered just what I used to be looking for. You've ended my 4 day lengthy hunt! God Bless you man. Have a nice day. Bye

Hello colleagues, its impressive piece of writing regarding tutoringand fully explained, keep it up all the time.

发表评论

电子邮件地址不会被公开。 必填项已用*标注

姓名 *

电子邮件 *

站点

评论

您可以使用这些HTML标签和属性:

  • 1
    点赞
  • 2
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值