CART同样由特征选择、树的生成、剪枝组成。既可以用于回归,又可以用于分类。
CART是在给定输入随机变量X条件下输出随机变量Y的条件概率分布的学习方法。
CART假设决策树是二叉树,内部节点特征的取值为“是“和“否“,左分支是取值为“是“的分支,右分支是取值为“否“的分支。这样的决策树等价于递归地二分每个特征,将输入空间即特征空间划分为有限个单元,并在这些单元上确定预测的概率分布,也就是在输入给定的条件下输出的条件概率分布。
创建分支的伪代码如下:
> if so return 类标签; else
> 寻找划分数据集的最好特征
> 划分数据集
> 创建分支结点
> for 每个分支结点
> 调用函数createBranch并增加返回结点到分支结点中//递归调用createBranch()
> return 分支结点
CART算法由以下两步组成:
(1)决策树生成:基于训练数据集生成决策树,生成的决策树要尽量大;
(2)决策树剪枝:用验证数据集对已生成的树进行剪枝并选择最优子树,这时用损失函数最小作为剪枝的标准。
一、分类树
1、基尼指数:分类问题中,假设有K个类,样本点属于第k类的概率为
2、对于二类分类问题:若样本点属于第1个类的概率是p,则概率分布的基尼指数为:
3、对于给定的样本集合D,其基尼指数为:
这里,
计算基尼指数代码实现:
#一、计算基尼指数
def calcGini(dataSet):
numEntries = len(dataSet)
labelCounts = {}
for featVec in dataSet: # 遍历每个实例,统计标签的频数
currentLabel = featVec[-1]
if currentLabel not in labelCounts.keys():
labelCounts[currentLabel] = 0
labelCounts[currentLabel] += 1
Gini = 1.0
for key in labelCounts:
prob = float(labelCounts[key]) / numEntries
Gini -= prob * prob # 以2为底的对数
return Gini
补充:
与熵的代码实现过程一样:
1、首先创建一个空字典,用来存储标签的频次;
2、遍历数据集得到各标签对应的频次,计算频率,计算熵。
4、如果样本集合
在给定特征条件下,集合的基尼指数代码实现:
#二、在给定特征条件下,集合的基尼指数:
def calcGiniWithFeat(dataSet, feature, value):
D0 = []; D1 = []
# 根据特征划分数据
for featVec in dataSet:
if featVec[feature] == value:
D0.append(featVec)
else:
D1.append(featVec)
Gini = len(D0) / len(dataSet) * calcGini(D0) + len(D1) / len(dataSet) * calcGini(D1)
return Gini
补充:
1、分割数据集:给定特征取值为value的那些数据存储为D0,其余部分存储为D1;
2、利用公式(1.1)计算。
基尼指数
基尼指数越大,样本集合的不确定性也就越大,这一点与熵相似。
算法一、CART分类生成算法:
输入:训练数据集D,停止计算的条件
**输出:**CART决策树
根据训练数据集,从根结点开始,递归地对每个结点进行以下操作,构建二叉决策树:
(1)设结点的训练数据集为D,计算现有特征对该数据集的基尼指数。此时,对每一个特征A,对其可能取得每个值a根据样本点对A=a 的测试为“是“或“否“将D分割成D_1和D_2两部分,利用式(1.1)计算A=a 时的基尼指数。
(2)在所有可能的特征A以及它们所有可能的切分点a中,选择基尼指数最小的特征以及其对应的切分点作为最优特征与最优切分点。依最优特征与最优切分点,从线结点生成两个子节点,将训练数据集依特征分配到两个子结点中去。
(3)对两个子结点递归地调用(1),(2),直至满足停止条件。
(4)生成CART决策树。
算法停止计算的条件是结点中的样本个数小于预定阈值,或样本集的基尼指数小于预定阈值(样本基本属于同一类),或者没有更多特征。
算法二、CATRT分类决策树生成代码如下:
#三、选择最好的数据集划分方式
def chooseBestSplit(dataSet):
numFeatures=len(dataSet[0])-1
bestFeat = 0
bestGini=float("inf") #初始值设为无穷大
newGini=0
for i in range(numFeatures): #遍历所有特征,并得到对应特征的所有不同取值
featList=[example[i] for example in dataSet]
uniqueVals=set(featList)
for splitVal in uniqueVals: #遍历要分割特征的所有取值,得到指定特征取值的基尼指数
newGini=calcGiniWithFeat(dataSet, i, splitVal)
if newGini<bestGini:
bestFeat=i
bestGini=newGini
return bestFeat
#四、采用多数表决的方法决定叶结点的分类
def majorityCnt(classList):
classCount={}
for vote in classCount:
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 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=chooseBestSplit(dataSet) #划分最优特征
bestFeatLabel=labels[bestFeat]
myTree={bestFeatLabel:{}}
del(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
#六、使用决策树的分类算法
'''
参数是:决策树、特征标签列表、测试向量
1、利用inputTree.keys()[0]获取第一个判断特征firsrStr,将其对应的值记为第二个字典;
2、在输入的标签列表中利用featLabels.index(firstStr)获取第一个判断特征的索引;
3、第二个字典的健实际上是第一个特征的取值,即遍历第一个特征的取值,找到测试向量相应特征的取值;
4、若测试向量相应特征的取值(第二个字典的健)对应的值的类型是一个字典,则递归这个分类算法;参数分别为:key对应的健值,特征标签列表,测试向量;
...对应值的类型是类标签,则测试数据的类型就是这个类标签。
'''
def classify(inputTree, featLabels, testVec): #输入决策树、特征标签列表、测试向量
firstStr=inputTree.keys()[0] #获取第一个判断特征
secondDict=inputTree[firstStr]
featIndex=featLabels.index(firstStr) #将第一个特征标签字符串转换为索引
for key in secondDict.keys(): #第一个特征对应的取值
if testVec[featIndex]==key:
if type(secondDict[key])._name_=='dict':
classLabel=classify(secondDict[key], featLabels, testVec)
else:
classLabel=secondDict[key]
return classLabel
#七、计算测试误差
def calcTestErr(myTree, testData, labels):
errorCount =0.0
for i in range(len(testData)):
if classify(myTree, labels, testData[i]!=testData[i][-1]):
errorCount+=1
return float(errorCount)
算法三、CART剪枝算法:
CART剪枝算法从“完全生长“的决策树的底端减去一些子树,使决策树变小(模型变简单),从而能够对未知数据有更准确的预测。
剪枝的过程就是一个动态规划的过程:从叶结点开始,自底向上地对内部结点计算预测误差以及剪枝后的预测误差,如果两者的预测误差是相等或者剪枝后预测误差更小,当然是剪掉的好。但是如果剪枝后的预测误差更大,那就不要剪了。剪枝后,原内部结点会变成新的叶结点,其决策类别由多数表决法决定。不断重复这个过程往上剪枝,直到预测误差最小为止。
CART剪枝算法由两步组成:
第一步、 首先从生成算法产生的决策树
T0 底端开始不断剪枝,直到T0 的根结点,形成一个子树序列{T0,T1,...,Tn} ;
第二步、然后通过交叉验证法在独立的验证数据集上对子序列进行测试,从中选择最优子树。
第一步:剪枝,形成一个子树序列
补充:
import copy
a = [1, 2, 3, 4, ['a', 'b']] #原始对象
b = a #赋值,传对象的引用
c = copy.copy(a) #对象拷贝,浅拷贝
d = copy.deepcopy(a) #对象拷贝,深拷贝
a.append(5) #修改对象a
a[4].append('c') #修改对象a中的['a', 'b']数组对象
print 'a = ', a
print 'b = ', b
print 'c = ', c
print 'd = ', d
输出结果:
a = [1, 2, 3, 4, ['a', 'b', 'c'], 5]
b = [1, 2, 3, 4, ['a', 'b', 'c'], 5]
c = [1, 2, 3, 4, ['a', 'b', 'c']]
d = [1, 2, 3, 4, ['a', 'b']]
1、copy.copy 浅拷贝: 只拷贝父对象,不会拷贝对象的内部的子对象,故内部的子对象会随着原来的改变。
2、copy.deepcopy深拷贝: 复制了对象所有的值。所以不论原对象怎么变化,deepcopy的结果都没有改变。
如果复制对象没有子对象,复制操作传递的是值,copy和deepcopy没啥区别。
如果复制对象中有子对象,为了达到复制数值目的,还是使用deepcopy比较好。
CART回归算法: