决策树与kNN算法一样,也是作为机器学习中的一类分类算法。与kNN算法不同的是,决策树是将原始数据看作为树的根节点,根据其特征值的不同,对数据进行一次又一次的划分,每个特征都可以作为一个子节点(决策点),最终在叶子节点可以达分类完成,分类结束的标志就是用完数据的所有特征值或者每个叶子节点都只包括同一类数据。
说起来可能比较抽象,看一个例子,《机器学习实战》一书中用以下例子作为解释:
no sufacing | flippers | fish? | |
1 | 1 | 1 | yes |
2 | 1 | 1 | yes |
3 | 1 | 0 | no |
4 | 0 | 1 | no |
5 | 0 | 1 | no |
决策树的一般流程为:
1、收集和准备数据:将数据以需要的格式整理好;
2、分析数据;
3、训练算法:构造决策树;
4、测试算法:用测试数据集对构造的决策树进行测试,计算错误率;
5、使用算法。
构造决策树的过程就是不断划分数据集的过程,而划分数据集的原则就是使无序的数据变得更加有序。因此,在划分数据集时,我们可以根据划分前后的信息增益,即划分后的信息熵减去划分前的信息熵,根据信息增益最大,得到最好的划分特征。关于什么是信息熵,可以查看一些信息论的教材,里面有具体讲解。
我们可以根据信息熵的定义编写以下代码计算:
def calcShannonEntropy(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 = shannonEnt - prob*log(prob, 2)
return shannonEnt
我们可以编写以下代码根据选定的特征值对数据集进行划分:
def splitDataSet(dataSet, axis, value):
retDataSet = []
for featVec in dataSet:
if featVec[axis] == value:
reducedFeatVec = featVec[:axis]
reducedFeatVec.extend(featVec[axis+1:])
retDataSet.append(reducedFeatVec)
return retDataSet
根据信息增益最大的原则,我们需要选定最好的数据集划分方式,即选定最好的特征
def chooseBestTeatureToSplit(dataSet):
numFeatures = len(dataSet[0]) - 1
baseEntropy = calcShannonEntropy(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*calcShannonEntropy(subDataSet)
infoGain = baseEntropy - newEntropy
if(infoGain > bestInfoGain):
bestInfoGain = infoGain
bestFeature = i
return bestFeature
选定最好的数据集划分方式后,我们就可以开始着手构建决策树,学习过数据结构,知道树这种数据结构,可以采用递归的方式进构建,递归最主要的就是,何时停止,构造决策树有两个递归停止条件:1、用完了数据集的所有特征,但还有节点的数据不是全部属于一个类别;2、每个叶子节点的数据都只有相同的类标签,即都同属于一个类别。针对第一个条件,我们需要对那些没有完全分类的节点中的数据进行计数,用里面多少类别的数据代替该节点的类别。
我们编写以下计数代码:
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 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 = chooseBestTeatureToSplit(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
创建决策树后,我们可以得到以下结果:
这里使用嵌套的字典来表示决策树,不够直观,因此我们可以用python,将树画出来,更加的观察所构建的决策树:
由上面所画出的决策树,可以很直观的看出,'no surfacing'这个特征是最优先用来划分数据集的,然后才到'flippers',因此也可以计算得到用'no surfacing'进行数据集划分的信息增益要大于'flippers'。由数据表格也可以很直观的得到这个结论。
构建了决策树后,就应该到用测试数据集对模型进行测试,计算错误率等,后续的测试算法在后续笔记中进行更新。