决策树
决策树(Decision Tree)是在已知各种情况发生概率的基础上,通过构成决策树来求取净现值的期望值大于等于零的概率,评价项目风险,判断其可行性的决策分析方法,是直观运用概率分析的一种图解法。由于这种决策分支画成图形很像一棵树的枝干,故称决策树。
本文将使用目前常用的几种决策树算法对美国人群收入数据进行建模,并比较几种不同决策树的特点。
- ID3 Iterative Dichotomiser 3
- C4.5
- CART Classification and Regression Trees
import pandas as pd
import numpy as np
import matplotlib.pyplot as plt
import seaborn as sns
ID3
ID3算法的核心是在决策树各个节点上应用 信息增益 准则选择特征,递归的构建决策树。具体方法是:从根结点开始,对结点计算所有可能的特征的信息增益,选择信息增益最大的特征作为结点的特征,由该特征的不同取值建立子结点;再对子结点递归的调用以上方法,构建决策树;直到所有特征的信息增益均很小或没有特征可以选择为止。ID3相当于用极大似然法进行概率模型的选择。
ID3算法的缺点:
- 不能对连续数据进行处理,只能通过连续数据离散化进行处理;
- 采用信息增益进行数据分裂容易偏向取值较多的特征,准确性不如信息增益率;
- 缺失值不好处理。
- 没有采用剪枝,决策树的结构可能过于复杂,出现过拟合。
https://blog.csdn.net/choven_meng/article/details/82878018
这里从https://blog.csdn.net/u011437229/article/details/53219435 引用了ID3的代码,
#!/usr/bin/python
#ID3.py created by lixin 20161118
from numpy import *
import math
import copy
class ID3DTree(object):
def __init__(self):
self.tree = {} #generating tree
self.dataSet = []
self.labels = []
def loadDataSet(self,path,labels):
recordlist = []
fp = open(path,"rb")#读取文件内容
content = fp.read()
rowlist = content.splitlines()#按行转为一维表
recordlist = [row.split(','.encode()) for row in rowlist if row.strip()]
self.dataSet = recordlist
self.labels = labels
def train(self):
labels = copy.deepcopy(self.labels)
self.tree = self.buildTree(self.dataSet,labels)
# main function
# (1)构建决策树:创建决策树主程序
def buildTree(self,dataSet,labels):
cateList = [data[-1] for data in dataSet]# 抽取源数据集的决策标签列
#程序终止条件1:如果classList只有一种决策标签,停止划分,返回这个决策标签
if cateList.count(cateList[0]) == len(cateList):
return cateList[0]
#程序终止条件2:如果数据集的第一个决策标签只有一个,则返回这个决策标签
if len(dataSet[0]) == 1:
return self.maxCate(cateList)
#core algo
bestFeat = self.getBestFeat(dataSet)# 返回数据集的最优特征轴
bestFeatLabel = labels[bestFeat]
tree = {bestFeatLabel:{}}
del(labels[bestFeat])
#抽取最有特征轴的列向量
uniqueVals = set([data[bestFeat] for data in dataSet])#去重
for value in uniqueVals: # 决策树递归生长
subLabels = labels[:] # 将删除后的特征类别集建立子类别集
# 按最优特征列和值分隔数据集
splitDataset = self.splitDataSet(dataSet,bestFeat,value)
subTree = self.buildTree(splitDataset,subLabels)#构建子树
tree[bestFeatLabel][value] = subTree
return tree
#(2)计算出现次数最多的类别标签
def maxCate(slef,catelist):
items = dict([(catelist.count(i),i) for i in catelist])
return items[max(items.keys())]
#(3)计算最有特征
def getBestFeat(self,dataSet):
#计算特征向量维,其中最后一列用于类别标签,因此要减去
numFeatures = len(dataSet[0])-1 # 特征向量维数 = 行向量维度-1
baseEntropy = self.computeEntropy(dataSet) # 基础熵,源数据的香农熵
bestInfoGain = 0.0 # 初始化最优的信息增益
bestFeature = -1 # 初始化最优的特征轴
# 外循环: 遍历数据集各列,计算最优特征轴
# i为数据集列索引:取值范围0~(numFeatures-1)
for i in range(numFeatures): #抽取第i列的列向量
uniqueVals = set([data[i] for data in dataSet])
newEntropy = 0.0 #初始化该列的香农熵
for value in uniqueVals: #内循环:按列和唯一值计算香农熵
# 按指定列i和唯一值分隔数据集
subDataSet = self.splitDataSet(dataSet,i,value)
prob = len(subDataSet)/float(len(dataSet))
newEntropy += prob * self.computeEntropy(subDataSet)
infoGain = baseEntropy - newEntropy #计算最大增益
if (infoGain > bestInfoGain): #如果信息增益>0
bestInfoGain = infoGain #用当前信息增益值替代之前的最优增益值
bestFeature = i #重置最优特征为当前列
return bestFeature
#(4)计算信息熵
def computeEntropy(self,dataSet):
datalen = float(len(dataSet))
cateList = [data[-1] for data in dataSet] #从数据集中得到类别标签
# 得到类别为key,出现次数为value的字典
items = dict([(i,cateList.count(i)) for i in cateList])
infoEntropy = 0.0 #初始化香农熵
for key in items:
prob = float(items[key])/datalen
infoEntropy -= prob*math.log(prob,2)
return infoEntropy
#(5)划分数据集;分隔数据集;删除特征轴所在的数据列,返回剩余的数据集
def splitDataSet(self,dataSet,axis,value):
rtnList = []
for featVec in dataSet:
if featVec[axis] == value:
rFeatVec = featVec[:axis]
rFeatVec.extend(featVec[axis+1:])
rtnList.append(rFeatVec)
return rtnList
tree=ID3DTree()
tree.loadDataSet('datasets/Adult/adult.data', ["age", "workclass", "fnlwgt", "education", "education-num", "marital-status",
"occupation", "relationship", "race", "sex", "capital-gain", "capital-loss",
"hours-per-week", "native-country","salary"])
tree.train()
print (tree.tree)
{'fnlwgt': {b' 205601': b' >50K', b' 274809': b' <=50K', b' 151506': b' <=50K', b' 163090': b' <=50K', b' 170350': b' <=50K', b' 251730': b' >50K', b' 155150': b' <=50K', b' 269060': b' <=50K', b' 54800': b' <=50K', b' 129573': {'education': {b' Assoc-acdm': b' 251694': b' <=50K', b' 69151': b' <=50K', b' 52537': b' <=50K', b' 327434': b' <=50K', b' 337664': b' <=50K', b' 208613': {'education': {b' Prof-school': b' >50K', b' Bachelors': b' <=50K'}}, b' 52603': b' >50K', b' 153326': {'age': {b'34': b' >50K', b'32': b'
。。。(省略10万字)
可以看到ID3做出来的树非常的复杂,不支持连续值的分割,对于年龄,收入等标量是对每个样本的取值都进行了分割,所以如果要使用ID3算法的话,对标量需要自己进行分箱。由于生成的树过于庞大,非常容易产生过拟合(决策树本来就是一种容易过拟合的模型)。
虽然ID3现在已经不怎么用了,但它体现了决策树最原始的思想,源码值得学习。
C4.5 & C5.0
C4.5算法是ID3的优化版,去除了必须手动对连续变量进行分箱的限制,C4.5会根据信息熵损失自动进行分箱,并将ID3输出的树转换成if-else逻辑,根据不同的顺序得到的准确率,决定哪个特征应该放在最前面。并且C4.5支持剪枝,那些对整体训练准确度没有提高的分支就会被剪掉。
C5.0是对C4.5在性能和内存上的提升
由于这两个算法都是ID3的进阶版,代码就不展示了
-
优点:产生的规则易于理解;准确率较高;实现简单;
-
缺点:对数据进行多次顺序扫描和排序,效率较低;只适合小规模数据集,需要将数据放到内存中。
CART
CART树改进了C4.5,同时支持了解决回归问题,并且不再生成规则集,而是生成一棵二叉树,每次根据阀值和信息增益选择分支条件,sklearn学习包里的tree模块实现的就是CART树,但目前不支持离散变量的输入。
源代码可以参考
https://github.com/Choven-Meng/ML_Algorithm/tree/master/DT