Python机器学习实战(二)--决策树
决策树是处理分类问题的一个非常直观的算法,决策树是一个类似流程图的树结构,树中的每个内部节点是一个特征的测试,每个分支是一个特性输出,每一个叶子节点是数据所属的一个类型。如下图结构(图片来源网络,仅供学习参考)
图中橙色节点即为数据类型:见,不见。
绿色节点表示对数据的某一特征进行测试,即对年龄,长相,收入,公务员几个特征进行测试。
每一个分支即表示了特征的具体值,即年龄值<=30或>30。
| 年龄是否大于30 | 长相是否丑 | 收入(高:1,中:0,低:-1) | 是否公务员 | 选择见面还是不见面 |
1 | 1 | 1 | 0 | 1 | 0 |
2 | 1 | 0 | 1 | 0 | 0 |
3 | 0 | 1 | -1 | 0 | 0 |
4 | 0 | 0 | 1 | 0 | 1 |
5 | 0 | 1 | 0 | 1 | 0 |
6 | 0 | 0 | 0 | 1 | 1 |
7 | 0 | 0 | 0 | 0 | 0 |
以上是一个训练集示例。可以对应创建一个简单数据集。
决策树算法通过训练集确定一个最优的决策树。
下面介绍如何构造决策树:
决策树的构造流程如下面伪代码所示
CreateBranch():
检测数据集中每一子项是否属于同一分类:
Ifture return 类标签
Else
继续划分数据集
创造分支节点
For每个划分的子集
调用函数CreateBranch并增加结果到分支节点
Return分支节点
即判断数据子集是否分类完成,否的话,对每个数据子集继续进行划分。
这里显然可以看到一个问题:对一个数据集分割时,应该选择哪一个特性进行划分?
下面介绍选择最优划分特性的方法:
依据信息论的理论基础,划分数据集的大原则是:将无序的数据变得更加有序。
那么如何度量是否有序,即通过计算数据信息的香农熵。
熵定义为信息的期望值,对一个数据集的每一个类型x的信息定义为:
L(x) = - log p(x)
P(x)为特征值为x的概率。
熵值即为所有类别的信息期望值:
H = -sum( p(x)*log p(x) )
即对每一个特征值计算- p(x) * L(x) ,然后求和。
下面代码给出了计算一个数据集的香农熵
def calcShannonEnt(dataSet): #计算香农熵 dataSet为数据集
numEntries = len (dataSet) #计算特征数
labelCounts = {} #创建一个类型:类型值的字典
for featVet in dataSet:
currentlabel = featVet[-1] #选取类型值
labelCounts[currentlabel] = labelCounts.get(currentlabel,0)+1
shannonEnt = 0.0 #初始化香农熵
for key in labelCounts: #对每个类型计算
prob = float(labelCounts[key])/numEntries
shannonEnt -= prob*log(prob,2)
return shannonEnt
知道了划分的标准,下面就可以给出选择最好划分集的方式:
先给出一个划分函数,该函数对输入的指定数据集,划分特征,特征值,返回对应的数据子集,同时返回的数据子集中已删除输入特征这一信息。
代码如下:
def splitDataSet(dataSet,axis,value): #分割数据集dataSet,返回第axis个特征值为value,同时删除该特征的数据
retDataSet = []
for featVet in dataSet:
if featVet[axis] == value: #判断第axis个特征是否为value
reducedFeatVec = featVet[:axis]
reducedFeatVec.extend(featVet[axis+1:]) #删除第axis个特征
retDataSet.append(reducedFeatVec) #将选择处理过的数据加入结果
return retDataSet
最好划分函数,该函数对输入数据集返回一个最好特征的索引值,基本思想即遍历每一个特征,比较划分后的香农熵,选出最小值即可。
代码如下:
def chooseBestFeatureToSplit(dataSet): #从dataSet数据集中选择最优的特性,返回特征的索引
numFeatures = len(dataSet[0])-1 #计算特征数
baseEntropy = calcShannonEnt(dataSet) #计算初始数据香农熵
bestInfoGain = 0.0 #熵能减小的最大值
bestFeature = -1 #初始化最优特征索引为-1
for i in range(numFeatures): #对每一个特征计算分割后的香农熵
newEntropy = 0.0
for example in dataSet:
featList = [example[i]] #第i个特征的所有值的列表
uniqueVals = set(featList) #用集合表示第i个特征值的集合
for value in uniqueVals:
subDataSet = splitDataSet(dataSet,i,value)
prob = float(len(subDataSet))/len(dataSet)
newEntropy += prob * calcShannonEnt(subDataSet)
infoGain = baseEntropy - newEntropy
if (infoGain > bestInfoGain):
bestInfoGain = infoGain
bestFeature = i
return bestFeature
另外针对对所有特征进行划分完后,数据依然不是同一个类别时,我们可以选择返回类别数最多的类别,如下面的函数所示:
def majorityCnt(classList):
classCount = collections.defaultdic(int)
for vote in classList:
classCount[vote] += 1
sortedclassCount = sorted(classCount.items(),key=operator.itemgetter(1),reverse=Ture)
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)
bestFeature = chooseBestFeatureToSplit(dataSet)
bestFeatureLabel = labels[bestFeature]
myTree = {bestFeatureLabel:{}}
del(labels[bestFeature])
featureValues = [example[bestFeature] for example in dataSet]
uniqueValues = set(featureValues)
for value in uniqueValues:
subLabels = labels[:]
myTree[bestFeatureLabel][value] = createTree(splitDataSet(dataSet,bestFeature,value),subLabels)
return myTree
以上就是构建一个决策树的过程
下面给出一个简单的测试,利用该决策树对数据进行分类
输入参数为决策树,特征标签列表,待分类数据,返回类别
函数中由决策树确定每次划分特征,可以通过对特征标签列表用index方法返回索引,进行确定数据中该特征的值。
代码如下:
def classify(inputTree,featLabels,testVec):
firstStr = 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