1 决策树算法的总结(主要有ID3,C4.5和CART等)
决策树算法: 对一系列规则对数据进行分类的过程,按照样本中不同属性的最大信息增益等判别因素进行分类,如此循环会生成一棵树。 决策树分为分类树和回归树,分类树对离散变量最决策树,回归树对连续变量(有时连续变量可以做离散)做决策树。决策树的生成有时会牵扯到树生成过繁(即过拟合),因此就需要对树进行剪枝(剪枝又分为预剪枝和后剪枝--后面会说)。
不同算法分类的分类依据:
ID3 max(信息增益) 信息增益=标签的信息熵-样本条件信息熵
C4.5 max(信息增益率) 信息增益率= 信息增益/分裂信息度 分裂信息度量pInf(D,X)= -P1 log2(P1)-P2 log2(P)-,...,-Pn log2(Pn)相当于特征的熵
CART min(基尼信息增益) 基尼信息增益=
2 决策树适用场景和优缺点
应用场景:1)决策树的应用往往都是和某一应用分析目标和场景相关的,比如:金融行业可以用决策树做贷款风险评估,保险行业可以用决策树做险种推广预测,医疗行业可以用决策树生成辅助诊断处置模型等等;2)为其他模型筛选变量。决策树找到的变量是对目标变量影响很大的变量。所以可以作为筛选变量的手段。
决策树的优点:1)决策树算法中学习简单的决策规则建立决策树模型的过程非常容易理解;2)决策树模型可以可视化,非常直观;3)应用范围广,可用于分类和回归,而且非常容易做多类别的分类;4)能够处理数值型和连续的样本特征等。
决策树的缺点:1)很容易在训练数据中生成复杂的树结构,造成过拟合;2)学习一棵最优的决策树被认为是NP-Complete问题。实际中的决策树是基于启发式的贪心算法建立的,不能保证建立全局最优的决策树。Random Forest 引入随机能缓解这个问题等。
对ID3,C4.5和CART三者之间进行比较:
ID3:选取最大信息增益作为分裂标准,导致一种属性中特征较多的往往作为分裂点(原因:信息增益反映的给定一个条件以后不确定性减少的程度,必然是分得越细的数据集确定性更高,也就是条件熵越小,信息增益越大)。
C4.5:将信息增益改为信息增益率,以解决偏向取值较多的属性的问题。
CART:使用基尼指数(Gini)来选择最好的数据分割的特征,gini描述的是纯度,与信息熵的含义相似。
3 算法过程
3.1) 计算决策树的不同属性的分裂标准(如:ID3最大信息增益); 3.2)以最大信息增益为标准经行属性分裂,并将此属性做为根节点;3.3)重复上述过程直到所有分裂完成(完成的条件:达到最小节点数;熵或者基尼值小于阀值;所有特征已经使用完毕,不能继续进行分裂等)
4 具体实例
4.1 实现一个简单属性的标签划分
from math import log
import operator
def createDataSet():
dataSet = [[1, 1, 'yes'],
[1, 1, 'yes'],
[1, 0, 'no'],
[0, 1, 'no'],
[0, 1, 'no']]
labels = ['no surfacing','flippers']
#change to discrete values
return dataSet, labels
'''
#计算样本的熵
# dataSet:训练数据
'''
def calcShannonEnt(dataSet):
numEntries = len(dataSet) #读取样本数量
labelCounts = {} #创建数据字典,计算每个label出现的次数
for featVec in dataSet:
currentLabel = featVec[-1] # -1表示获取dataSet中最后一个元素,即当前的label
#如果标签不在新定义的字典里创建该标签值
if currentLabel not in labelCounts.keys(): labelCounts[currentLabel] = 0
labelCounts[currentLabel] += 1 #该类标签属于一类的数据加1
shannonEnt = 0.0
for key in labelCounts:
prob = float(labelCounts[key])/numEntries #同类标签出现的概率
shannonEnt -= prob * log(prob,2) #以2为底求对数
return shannonEnt #返回熵值
'''
#按照给定的特征划分数据集
# dataSet:待划分数据
# axis :划分数据的特征
# value :特征的返回值
'''
def splitDataSet(dataSet, axis, value):
retDataSet = [] #创建一个新列表对象
for featVec in dataSet:
if featVec[axis] == value: #判断此属性中的特征是否为设定的特征
#返回的是一个列表,其元素是featVec这个列表的索引从0到axis - 1的元素
reducedFeatVec = featVec[:axis]
#返回的是一个列表,其元素是featVec这个列表的索引从axis + 1开始的所有元素
reducedFeatVec.extend(featVec[axis+1:]) #返回除过该特征的列
retDataSet.append(reducedFeatVec) #将同一属性的特征放在同列表中
return retDataSet #返回除其特征的同列信息
'''
#对数据集(由列组成的列表,最后一列是类别标签)进行同一特征
#的划分,返回信息增益最大的属性
'''
def chooseBestFeatureToSplit(dataSet):
numFeatures = len(dataSet[0]) - 1 #读出除过标签外,的属性个数
baseEntropy = calcShannonEnt(dataSet) #计算数据中标签的信息熵
bestInfoGain = 0.0; bestFeature = -1 #定义属性的最大信息熵变量和返回该属性的标签
for i in range(numFeatures): #创建唯一的分类标签列表
#获取第i个特征所有的可能取值
featList = [example[i] for example in dataSet]
#从列表中创建集合,得到不重复的所有可能取值
uniqueVals = set(featList)
newEntropy = 0.0 #定义信息熵
for value in uniqueVals: #计算每种划分方式的信息熵
#以i为数据集特征,value为返回值,划分数据集
subDataSet = splitDataSet(dataSet, i, value)
#数据集特征为i的所占的比例
prob = len(subDataSet)/float(len(dataSet))
#计算不同属性下特征的信息熵(条件信息熵)
newEntropy += prob * calcShannonEnt(subDataSet)
infoGain = baseEntropy - newEntropy #计算属性的最大信息增益
if (infoGain > bestInfoGain): #选择最大信息增益
bestInfoGain = infoGain #最大熵的复制
bestFeature = i #返回最大熵的属性
return bestFeature
'''
#对叶子节点的分类(在属性已分类完,但标签还不唯一时进行叶子节点的分类)
'''
def majorityCnt(classList):
classCount={}
for vote in classList: # 遍历类别列表
#如果标签不在新定义的字典里创建该标签值
if vote not in classCount.keys(): classCount[vote] = 0
classCount[vote] += 1 #该类标签属于一类的数据加1
#按照升序排列
sortedClassCount = sorted(classCount.items(), key=operator.itemgetter(1), reverse=True)
return sortedClassCount[0][0] #返回出现次数最多的分类名称
'''
#创建决策树的函数
# dataSet :数据集
# labels :标签列表
'''
def createTree(dataSet,labels):
classList = [example[-1] for example in dataSet] #创建数据标签的列表变量
# 如果只剩同一类,返回该类标签,停止继续划分
if classList.count(classList[0]) == len(classList):
return classList[0]
#遍历完所有特征值时返回出现次数最多的
if len(dataSet[0]) == 1:
return majorityCnt(classList)
#选择最好的数据集划分方式,获得其属性下标
bestFeat = chooseBestFeatureToSplit(dataSet)
bestFeatLabel = labels[bestFeat] #获得对应属性名称
myTree = {bestFeatLabel:{}} # 以最佳特征为根节点创建子树
del(labels[bestFeat]) #清空labels[bestFeat],在下一次使用时清零
# 获得最佳特征的所有可能值
featValues = [example[bestFeat] for example in dataSet]
# 去除重复的值
uniqueVals = set(featValues)
for value in uniqueVals:
subLabels = labels[:] #复制类标签,将其存储
#递归调用创建决策树函数
myTree[bestFeatLabel][value] = createTree(splitDataSet(dataSet, bestFeat, value),subLabels)
return myTree #返回数结构信息的嵌套字典(决策树文本信息)
'''
#使用决策树的分类函数
# inputTree :输入的分类树模型
# featLabels:标签列表
# testVec :测试向量
'''
def classify(inputTree,featLabels,testVec):
firstStr = inputTree.keys()[0] # 获得分类树根节点名(特征名)
secondDict = inputTree[firstStr] # 获得根节点(代表的特征)的所有特征值
# 获得根节点名(特征名)在数据集中的下标
featIndex = featLabels.index(firstStr)
key = testVec[featIndex] #获得测试向量的特征
valueOfFeat = secondDict[key]
#判断对象valueOfFeat是否为字典,如果为真进入循环
if isinstance(valueOfFeat, dict):
#递归函数的运行
classLabel = classify(valueOfFeat, featLabels, testVec)
else: classLabel = valueOfFeat
return classLabel #返回测试数据的类别
以上所有函数可以在自己的jupyter中实现或者pycharm中调试,从其运行过程中加深对ID3决策树的分类过程。对于其它两种算法或者更复杂的例子有待学习更新。
5 树生长过繁需要修剪
5.1)预剪枝:通过提前停止树的构建而对树剪枝,一旦停止,节点就是树叶,该树叶持有子集元祖最频繁的类(方法:定义树的高度或定义一个分裂点的阈值等)。
5.2)后剪枝:首先构造完整的决策树,允许树过度拟合训练数据,然后对那些置信度不够的结点子树用叶子结点来代替,该叶子的类标号用该结点子树中最频繁的类标记。相比于先剪枝,这种方法更常用,正是因为在先剪枝方法中精确地估计何时停止树增长很困难。