决策树
1.决策树的模型和学习策略
定义:分类决策树模型是一种描述对实例进行分类的树形结构,由节点和有向边组成,建立时由由不同的特征决定每层的分类依据,分类时,从根节点对每个实例进行测试并分配到子节点,直到分类到叶节点。
学习策略:决策树的学习本质上是从训练数据集中归纳出一组分类规则,最后得到一个与训练数据集矛盾较小同时泛化能力较强的决策树;从另一角度,决策树是由训练数据集估计条件概率模型,基于特征空间划分的类的条件概率模型有无穷多,而选择出来的条件概率模型不仅要对训练数据有很好的拟合同时要对未知数据有很好的预测。
下文会介绍几种常见决策树:ID3、C4.5、CART。
2.特征选择方法
- 信息增益准则:
熵:表示随机变量不确定性的度量,熵越大,随机变量的不确定性就越大,随机变量X的熵定义为:
条件熵:H(Y|X)表示在已知随机变量X的条件下随机变量Y的不确定性,定义为:
当熵和条件熵由数据估计得到时,它们分别称为经验熵和经验条件熵。
信息增益:特征A对训练数据集D的信息增益g(D,A),定义为集合D的经验熵H(D)与特征A给定条件D的经验条件熵H(D|A)之差。
根据信息增益准则的特征选择方法是:对于训练数据集D,计算每个特征的信息增益,并比较它们的大小,最后选择信息增益最大的特征。
- 信息增益比准则
特征A对训练数据集D的信息增益比,由信息增益除以特征的熵组成。这是为了矫正一个问题,因为基于信息增益趋向于选择取值更多的特征。
- GINI系数准则
在分类问题中,假设有K个类,样本点属于第k个类的概率为pk,则概率分布的基尼指数定义为:
如果样本集合D根据特征A是否取某一可能值a被分割成D1和D2两部分,则特征A条件下,集合D的Gini系数为:
这样,Gini系数越大,集合的不确定性就和越大,与熵类似。
3.决策树的生成方法
- ID3算法:核心是在决策树各个节点上应用信息增益准则选择特征,递归构建决策树。
输入:训练数据集D,特征集A,阈值e;
输出:决策树T
- 若D中所有实例属于同一类,则T为单节点树。
- 计算A中所有特征对D的信息增益,选择信息增益最大的特征Ag。
- 如果特征Ag的信息增益小于e,则置T为单节点树,并将D中实例数最多的类别最为节点类别。
- 否则,对Ag的每一个可能取值进行分割,构建子节点。
- 对每个子节点递归调用上述方法。
- C4.5的生成算法
C4.5算法与ID3算法相似,只是划分标准变为了信息增益率。
- CART算法
分类回归树(classification and regression tree)既可作分类也可做回归,主要由两部分组成:(1).决策树生成:基于训练数据集产生决策树,生成的决策树要尽可能大;(2).决策树剪枝:用验证数据集对已生成的树进行剪枝并选择最优子树,这时用损失函数最小作为剪枝的标准。这里提到了剪枝,所以我们需要先讲一下树的剪枝。
树的剪枝是为了防止前两种算法造成了枝叶过多发生过拟合的问题,决策树的剪枝通常通过极小化整体的损失函数来实现,设树的节点为|T|,t是树T的叶节点,该叶节点有Nt个样本点,其中k类的样本点有Ntk个,Ht(T)为叶节点t上面的经验熵,a为参数,则损失函数可定义为:
右式第一项表示了模型对训练数据的预测误差,|T|表示模型复杂度,参数a大于等于零控制着双方的影响度,而剪枝的过程就是:递归向上回缩,每次计算损失函数,直到损失函数不再减小为止。
CART树的生成对回归树采取平方误差最小的准则,对分类树采取基尼系数最小化准则,尤其是在回归树的建立上不好理解,我会详细讲解:
- 回归树的生成
(1).在训练数据集所在的输入空间中,递归地将每个区域划分成两个子区域并决定每个区域的输出值,那这个区域是由某个特征(切分变量j)和特征的一个值(切分点s)决定的,即分为了下列两个区域:
(2).那这个切分变量和切分点应该如何求呢?那自然是要让损失函数最小,我们刚刚说了损失函数是平方误差,假设R1区域的实际值是c1,R2区域的实际值为c2,我们就能得到:
(3).c1和c2实际上是R1和R2区域的均值,所以我们只需要固定j,遍历s,求的c1和c2,上式就可求解,继续上述步骤产生的回归树通常称为最小二乘树。
2. 分类树的生成类似于上文中前两个算法,只是基于基尼系数选择特征,不再赘述。
- CART剪枝
思路:首先从生成算法产生的决策树T的底端开始不断剪枝,直到T的根节点形成的一个子树序列{T0,T1......Tn};然后通过交叉验证法在独立的验证数据集上对子树序列进行测试,选出最优子树。
输入:决策树T;
输出:最优决策树Ta;
(1).设k=0,T=T0,a=正无穷
(2).自下而上计算各内部节点t的C(Tt)、|Tt|以及当C(t)和C(Tt)相等时a的取值,并把它赋值给a,第一次算出的树肯定是根节点。
(3).根据这个办法会得到不同的a对应不同的树序列,再交由交叉验证得到最好的子树。
4.实现
#这是脱离库实现ID3算法
from math import log
import operator
def calcShannonEnt(dataSet):
'计算香农熵'
# 数据集个数
numEntries = len(dataSet)
# 标签集合
labelCounts = {}
for featVec in dataSet: # 行遍历数据集
# 当前标签
currentLabel = featVec[-1]
# 加入标签集合
if currentLabel not in labelCounts.keys(): labelCounts[currentLabel] = 0
labelCounts[currentLabel] += 1
# 计算当前数据集的香农熵并返回
shannonEnt = 0.0
for key in labelCounts:
prob = float(labelCounts[key])/numEntries
shannonEnt -= prob * log(prob,2)
return shannonEnt
# ==============================================
# 输入:
# dataSet: 训练集文件名(含路径)
# axis: 用于划分的特征的列数
# value: 划分值
# 输出:
# retDataSet: 划分后的子列表
# ==============================================
def splitDataSet(dataSet, axis, value):
'数据集划分'
# 划分结果
retDataSet = []
for featVec in dataSet: # 逐行遍历数据集
if featVec[axis] == value: # 如果目标特征值等于value
# 抽取掉数据集中的目标特征值列
reducedFeatVec = featVec[:axis]
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): # 逐列遍历数据集
# 获取该列所有特征值
featList = [example[i] for example in dataSet]
# 将特征值列featList的值唯一化并保存到集合uniqueVals
uniqueVals = set(featList)
# 新划分法香农熵
newEntropy = 0.0
# 计算该特征划分下所有划分子集的香农熵,并叠加。
for value in uniqueVals: # 遍历该特征列所有特征值
subDataSet = splitDataSet(dataSet, i, value)
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
sortedClassCount = sorted(classCount.items(),key=operator.itemgetter(1),reverse=True)
return sortedClassCount[0][0]
def createTrees(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])
featValues = [example[bestFeat] for example in dataSet]
uniqueVals = set(featValues)
for value in uniqueVals:
subLables = labels[:]
myTree[bestFeatLabel][value] = createTrees(splitDataSet(dataSet,bestFeat,value),subLables)
return myTree
fr = open("lenses.txt")
lenses = [inst.strip().split('\t') for inst in fr.readlines()]
lenses_labels = ['age', 'prescript', 'astigmatic', 'tear_rate']
lenses_tree = createTrees(lenses,labels)
print(lenses_tree)
young myope no reduced no lenses