决策树一个直观的理解:比如一方脑海里想个事物,另一方去猜。猜的那方会提出几个问题,而领一方只能用是或者不是来回答。决策树的原理类似,就是构建一个属性的树状结构,根据属性的情况来得到最后的分类。决策树算是一种贪心算法,它并不关心能否达到全局最优。
决策树优点:计算复杂度不高,输出结果易于理解,对中间值的缺失不敏感,可以处理不相关的特征数据。
缺点:可能会产生过度匹配问题。
每个属性对划分的重要性不同,我们用每个属性的信息增益(information gain)来衡量,信息增益高的特征是分类的首要选择。
信息熵(information entropy):
信息增益(information gain):
本算法就是以信息增益为准则来选择属性划分的。
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]
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 #calculate the info gain
if (infoGain > bestInfoGain):
bestInfoGain = infoGain
bestFeature = i
return bestFeature
上面的Python代码就是用信息增益来选择最优的划分属性。
实际上信息增益对数目较多的属性有偏好,所以著名的C4.5使用增益率(gain ratio)来选择最优划分属性。
需要注意的是,增益率对可取值数目较少的属性有偏好,因此C4.5并不是直接选择增益率最大的,而是采用了一种启发式算法,先从候选划分属性中找出信息增益高于平均水平的属性,再从中选择增益率最高的。
CART(Classification And Regression Trees)决策树采用基尼系数(Gini index)来选择划分属性。基尼稀疏反映了从数据集中随机抽两个不是一类的概率。因此系数越小,数据集纯度越高。
我们选择那个使得划分后基尼系数最小的属性作为最优划分属性。
CART算法只做二元切分,所以用了固定的树结构,而不像ID3那样用字典构成。
def createTree(dataSet, leafType=regLeaf, errType=regErr, ops=(1,4)):
feat, val = chooseBestSplit(dataSet, leafType, errType, ops) if feat == None: return val return val
retTree = {}
retTree['spInd'] = feat
retTree['spVal'] = val
lSet, rSet = binSplitDataSet(dataSet, feat, val)
retTree['left'] = createTree(lSet, leafType, errType, ops)
retTree['right'] = createTree(rSet, leafType, errType, ops)
return retTree
实现的时候,我们为了寻找最佳的二元切分方式,我们使得总方差最小。
def chooseBestSplit(dataSet, leafType=regLeaf, errType=regErr, ops=(1,4)):
tolS = ops[0]; tolN = ops[1]
if len(set(dataSet[:,-1].T.tolist()[0])) == 1: #exit cond 1
return None, leafType(dataSet)
m,n = shape(dataSet)
S = errType(dataSet)
bestS = inf; bestIndex = 0; bestValue = 0
for featIndex in range(n-1):
for splitVal in set(dataSet[:,featIndex]):
mat0, mat1 = binSplitDataSet(dataSet, featIndex, splitVal)
if (shape(mat0)[0] < tolN) or (shape(mat1)[0] < tolN): continue
newS = errType(mat0) + errType(mat1)
if newS < bestS:
bestIndex = featIndex
bestValue = splitVal
bestS = newS
if (S - bestS) < tolS:
return None, leafType(dataSet) #exit cond 2
mat0, mat1 = binSplitDataSet(dataSet, bestIndex, bestValue)
if (shape(mat0)[0] < tolN) or (shape(mat1)[0] < tolN):
return None, leafType(dataSet)
return bestIndex,bestValue
用于对付过拟合。判断泛化能力,通常采用留出法,即预留一部分数据集来“做验证”。
预剪枝(pre-pruning):若当前节点的划分不能带来决策树泛化能力的提升,则停止划分并将其标记为叶节点。有欠拟合的风险。
后剪枝(post-pruning):从已经生成的决策树中自底向上对非叶节点考察,若将该节点替换成叶节点,能提升泛化能力,则将该子树替换为叶节点。一般泛化能力优于预剪枝,欠拟合风险也小,但训练开销大很多。
这里我们实现的是后剪枝,因为其性能要稍微好些:
def prune(tree, testData):
if shape(testData)[0] == 0: return getMean(tree) #if we have no test data collapse the tree
if (isTree(tree['right']) or isTree(tree['left'])):
lSet, rSet = binSplitDataSet(testData, tree['spInd'], tree['spVal'])
if isTree(tree['left']): tree['left'] = prune(tree['left'], lSet)
if isTree(tree['right']): tree['right'] = prune(tree['right'], rSet)
if not isTree(tree['left']) and not isTree(tree['right']):
lSet, rSet = binSplitDataSet(testData, tree['spInd'], tree['spVal'])
errorNoMerge = sum(power(lSet[:,-1] - tree['left'],2)) +\
sum(power(rSet[:,-1] - tree['right'],2))
treeMean = (tree['left']+tree['right'])/2.0
errorMerge = sum(power(testData[:,-1] - treeMean,2))
if errorMerge < errorNoMerge:
print "merging"
return treeMean
else: return tree
else: return tree
连续值:离散化,设置划分点。大于走右子树,小于走左子树(或者反过来)。
缺失值:让同一样本以不同概率划入到不同子结点中。
决策树形成的边界有轴平行的特点。多变量决策树是采用斜线划分。在这类决策树中,非叶节点不是某个属性,而是属性的集合。在学习过程中,也不是为每个非叶节点寻找一个最优划分属性,而是试图建立一个合适的线性分类器。
下面给一个决策树运行的代码与例子,我们用决策树预测了骑自行车速度与智商的关系。运行请在目录下输入:
python decisionTreeCART.py
这里面还包含了一个ID3算法的python实现,我们用这个算法根据隐形眼镜数据集来推荐隐形眼镜,并对树形结构做了可视化。
python decisionTreeID3.py
这里是代码下载地址