假设有这么一组数据:
不浮出水面是否可以生存 | 是否有脚蹼 | 属于鱼类 | |
---|---|---|---|
1 | 是 | 是 | 是 |
2 | 是 | 是 | 是 |
3 | 是 | 否 | 否 |
4 | 否 | 是 | 否 |
5 | 否 | 是 | 否 |
特征包含:不浮出水面是否可以生存,是否有脚蹼。标签是属于鱼类。
我们通过构造这样一棵决策树对其进行分类,每个叶子节点代表对一个分类的判断:
相比knn,决策树具体考虑了样本的每一个特征。
那么,我们通过对哪些特征先后进行划分来构造这样一棵树呢?
首先,我们将数据集转化成符号表示:
def createDataSet():
dataSet = [[1, 1, 'yes'], [1, 1, 'yes'], [1, 0, 'no'], [0, 1, 'no'], [0, 1, 'no']]
labels = ['no surfacing', 'flippers']
return dataSet, labels
dataSet中1表示是,0表示不是,yes表示属于鱼类,no表示不属于鱼类。
labels中分别表示两个特征的名称:不浮出水面是否可以生存、是否有脚蹼。
接着我们计算这个数据集的信息熵:
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
信息熵定义为:
p为这个类别的概率。
拿这个例子来说,样本有两类yes和no,yes占样本的2/5,no占样本的3/5。则这个样本的信息熵为:
我们通过下面的代码测试下结果:
dataSet, labels = createDataSet()
print(calcShannonEnt(dataSet)) # 0.9709505944546686
信息熵代码了数据的规则程度,我们把第一个标签从yes改成maybe,再运行一下代码:
dataSet[0][-1] = 'maybe'
print(calcShannonEnt(dataSet)) # 1.3709505944546687
可以看到,信息熵从0.97上升到了1.37,代表信息熵越高则数据越混乱。
一个好的划分应该是划分过后每个数据集的信息熵之和要比划分前的数据集信息熵小的越多越好的,小的那部分叫做信息增益,即信息增益越大越好。
拿我们这个样本来说,假如通过第一个特征不浮出水面是否可以生存来划分,这个特征具备两个值,我们可以把数据集分成两个,然后计算这两个样本根据所占原先样本比重的信息熵的加权和,和划分之前的样本的信息熵计算信息增益。对于另一个特征也同样进行计算,最后选取信息增益最大的特征作为我们的划分。
写成代码如下:
def chooseBestFeatureToSplit(dataSet):
numFeatures = len(dataSet[0]) - 1 # 特征的个数
baseEntropy = calcShannonEnt(dataSet) # 计算当前数据集的熵
bestInfoGain, bestFeature = 0.0, -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 # 信息增益
if infoGain > bestInfoGain: # 选用信息增益最大的划分
bestInfoGain = infoGain
bestFeature = i
return bestFeature
print(chooseBestFeatureToSplit(dataSet)) # 0
结果我们可以看到根据第0个特征划分是信息增益最大的。
于是我们的样本就划分成了两个,产生了两个分支,如果一个分支中所有样本已经全部属于同一类,那么它就直接成为一个叶子节点。否则我们再次对分支进行同样的操作,直到划分完所有特征,构成我们的决策树。
不过有时候,我们划分完所有的特征,所有样本也并不一定是属于同一类,这时候我们一般通过多数表决选择当前样本中所占比重最大的一类作为这一样本的标签:
# 返回占大多数的类别的名称
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=lambda x: x[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] # str
# 如果已经没有特征可以划分了,就选择占多数的分类作为这个分类
if len(dataSet[0]) == 1:
return majorityCnt(classList) # str
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
print(createTree(dataSet, labels)) # {'no surfacing': {0: 'no', 1: {'flippers': {0: 'no', 1: 'yes'}}}}
从结果中我们能够看出{'no surfacing': {0: 'no', 1: {'flippers': {0: 'no', 1: 'yes'}}}},也就是开头那张图上的那棵树。