1. 问题描述
由于隐形眼镜类型较多,患者选择方式众多,且患者眼部观察数据类型较多,因此有经验的医生也不能很好地给出适合患者佩戴的隐形眼镜类型。针对这种情况,如果有一些已知的患者眼部观察数据及其所佩戴的隐形眼镜类型的数据,则可通过这些数据构建决策树代替医生来判断患者所适合的隐形眼镜类型。
2. 数据准备
隐形眼镜数据集来自于文本文档“lenses.txt”,其中每一行即为一个患者眼部状况的观察条件和他所佩戴的隐形眼镜类型。第一列为患者年龄,其后每一列分别为患者患病类型、是否散光等最后一列为患者所佩戴隐形眼镜类型。
3. 模型原理
3.1.决策树构建
引入信息熵概念,依据某一标签分类后的数据集信息熵减少量作为判断原则,划分后数据集信息熵减少最多的标签作为首选决策标签,以此作为决策树的分支节点。重复该操作直到决策树各分支下数据类型一致或数据分类完成。
3.2.数据预测
输入已构建的决策树和数据在各类标签下的取值,通过决策树的分支条件进行判断,最终得到该数据的类型。
4. 算法实现
4.1.信息熵计算
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
4.2.数据集划分
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
4.3.决策树分支选择
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
if (infoGain > bestInfoGain):
bestInfoGain = infoGain
bestFeature = i
return bestFeature
4.4.冲突决策
def majorityCnt(classList):
classCount={}
for vote in classList:
if vote not in classCount.keys(): classCount[vote] = 0
classCount[vote] += 1
sortedClassCount = sorted(classCount.iteritems(),
key=operator.itemgetter(1),
reverse=True)
return sortedClassCount[0][0]
4.5.决策树构建
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 = 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
4.6.数据预测
def classify(inputTree,featLabels,testVec):
firstStr = list(inputTree.keys())[0]
secondDict = inputTree[firstStr]
featIndex = featLabels.index(firstStr)
key = testVec[featIndex]
valueOfFeat = secondDict[key]
if isinstance(valueOfFeat, dict):
classLabel = classify(valueOfFeat, featLabels, testVec)
else: classLabel = valueOfFeat
return classLabel
5. 以构建的决策树可视化
由决策树结构可以看出,最优划分标签是tearRate,其次为astigmatic,然后是prescript和age。同时可以看出在标签prescript和age划分后仍不可统一数据类型,故其子节点还引入了其他标签。
6. 测试方法及结果
由于数据集较小,在构建决策树后能用于测试的数据较少,故针对数据集每次删除一个实例进行决策树构建,其后使用该数据进行测试,得到测试结果计算平均正确率。
1 |
2 |
3 |
4 |
5 |
6 |
7 |
8 |
9 |
10 |
11 |
12 |
13 |
14 |
15 |
16 |
17 |
18 |
19 |
20 |
21 |
22 |
23 |
24 |
平均 |
0 |
0 |
0 |
0 |
0 |
0 |
0 |
1 |
0 |
0 |
0 |
1 |
0 |
0 |
0 |
1 |
0 |
1 |
0 |
0 |
0 |
1 |
0 |
0 |
0.792 |
表格中第一行为删除的数据示例,第二行为通过剩余数据构建的决策树对该数据的预测。其中0表示结果正确,1表示结果错误。可以得到平均正确率在80%左右。由于测试方法的局限性及数据集大小限制,测试结果可能并不能较为正确地反应算法优劣。7. 总结
本文采用书上的相应算法、通过引入信息熵这一概念进行决策树的构建,使用隐形眼镜数据集进行训练,算法合理成熟。但由于数据集大小限制,得到的决策树可能并不十分合理,针对这一情况,应扩大数据集进行训练。