1.什么是决策树
我们对给定的训练集进行训练,可以得到的一种能够对新的示例进行分类的一个树形结构模型,就是决策树。分类就是这个模型的重点解决的问题,通过一系列的特征进行分类,最终得到我们需要的结果。
2.算法实现
显而易见,决策树就是一种树形结构,通过一个个特征分岔开来。图形确实好明白,但是如何转化为算法并且利用程序语言实现就是我们需要考虑的问题,决策树构建的一般过程如下:
- 利用每个特征的不同值对数据集进行划分
- 分别求划分后数据集的香农熵
- 对于同个特征的不同值按照比例相加得到该特征的信息熵
- 比较不同的信息熵大小,最小的为最好划分方式
- 递归1—5,直到遍历完所有特征,或是所有数据属于同一特征则结束
- 返回一颗递归树(包含所有属性的列表)
其实观察该算法不难发现,重点就是利用香农熵,最优的划分数据集,一层一层的对数据进行分类。
3.代码实现
3.1香农熵的实现
1.香农熵公式:
2.香农熵代码实现
# 3-1 香农熵的实现
# 香农熵是数据的多少,例如dataSet中种类越多,熵越高
# 实质是信息的混乱程度,混乱程度越低,熵越低,分类的效果越好
def calcShannonEnt(dataset):
numEntries = len(dataset)
lableCounts = {}
for featVec in dataset:
currentLabel = featVec[-1] # -1指的是最后一列
if currentLabel not in lableCounts.keys():
lableCounts[currentLabel] = 0 # 如果没有某个属性,则加入进字典的key {"yes":n}
lableCounts[currentLabel] += 1 # n++
shannonEnt = 0.0
for key in lableCounts:
prob = float(lableCounts[key])/numEntries
shannonEnt -= prob * log(prob, 2)
return shannonEnt
3.2特征选择及数据集的划分
1.按照给定列和值对数据集进行划分
# 3-2按照特征划分数据集
# 通过特征列及特征值,提取相关数据
def splitDataSet(dataset, axis, value):
retDataSet = []
datasetLen = len(dataset)
for featVec in dataset:
if featVec[axis] == value:
reducedFeatVec = featVec[:axis]
if -1 < axis < datasetLen+1:
reducedFeatVec.extend(featVec[axis+1:])
retDataSet.append(reducedFeatVec)
return retDataSet
2.对不同特征的划分分别计算香农熵并选出熵最低的特征划分
# 3-3 选择最好的数据集划分方式
# 分别选取不同的特征列,同列不同的值的香农熵相加,无序度最下的为 ,最好特征列
def chooseBestFeatureToSplit(dataset):
numFeature = len(dataset[0]) - 1
baseEntrop = calcShannonEnt(dataset)
bestInfoGain = 0.0
bestFeature = -1
for i in range(numFeature):
featList = [example[i] for example in dataset] # dataset中所有第i个数据
uniqueVals = set(featList) # featList所有不同取值-->即提取第i列的不同元素
newEntropy = 0.0
for value in uniqueVals: # 同列不同值分别求熵
subDataSet = splitDataSet(dataset, i, value)
prob = len(subDataSet)/float(len(dataset))
newEntropy += prob * calcShannonEnt(subDataSet) # 按照该特征中不同值的比例求信息熵
infoGain = baseEntrop - newEntropy # 熵减小的程度之和,即无序度的减少
if infoGain > bestInfoGain:
bestInfoGain = infoGain
bestFeature = i
return bestFeature
3.3 递归的创建一颗决策树
决策树采用递归的方式进行创建,在一般的决策树中,递归结束有两种情况,一是所有数据的类别完全相同,此时就直接返回该类别即可。而是所有特征都遍历完了但是依旧有不同的类别,此时就返回数据中最多的类别。
为了应对返回最多的类别这一问题,我们需要先构建一个投票函数
def majorityCnt(classList):
classCount = {}
for vote in classList:
if vote not in classCount.key():
classCount[vote] = 0
classCount[vote] += 1
sortedClassCount = sorted(classCount.iteritems(),
key=operator.itemgetter(1), reverse=True)
return sortedClassCount[0][0]
然后就可以递归的开始构造决策树,注意,此处返回的是一个嵌套字典
# 3-4创建树
def createTree(dataSet, labels):
classList = [example[-1] for example in dataSet] # 获取所有结果列
if classList.count(classList[0]) == len(classList): # 递归出口,左端是指第0个值的个数与整列值的个数是否相等,即类标签完全相同
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: # 按照属性进行分类迭代
subLabels = labels[:]
myTree[bestFeatLabel][value] = createTree(splitDataSet(dataSet, bestFeat, value), subLabels)
return myTree
3.4 测试该决策树
我们首先定义一个数据集函数
def creatDataset():
dataset = [[1, 1, 'yes'],
[1, 1, 'yes'],
[1, 0, 'no'],
[0, 1, 'no'],
[0, 0, 'no'],
]
labels = ['no surfacing', 'flippers']
return dataset, labels
再利用该函数进行测试
if __name__ == '__main__':
myDat, labels = tree.creatDataset()
myTree = tree.createTree(myDat, labels)
print(myTree)
结果如下:
就最好的构造了一颗决策树,书中还利用了Matplotlib库对该字典进行了可视化,本文主要探究决策树算法的实现,就不深究了。运行结果中的嵌套字典,由里向外就是树由上到下的延申
4.总结
决策树十分符合“分而治之”的实现,通过特征一步一步的对数据进行分类,最终和其属性匹配。而如何分则由信息熵来进行决定。但是决策树分的太过细致后,又会导致泛化能力减弱,过度匹配的问题,我们可以利用剪枝算法等对决策树进行修正,再有利用C4.6、CART算法实现决策树等,都会在未来的博文中一一讲到。