一种树结构 if else来分类。使用决策树进行决策的过程就是从根节点开始,测试待分类项中相应的特征属性,并按照其值选择输出分支,直到到达叶子节点,将叶子节点存放的类别作为决策结果。
ID3算法:
根据信息论中期望信息越小,信息增益越大,从而纯度越高。所以ID3算法的核心思想就是以信息增益度量属性选择,选择分裂后信息增益最大的属性进行分裂。下面先定义几个要用到的概念。
设D为用类别对训练元组进行的划分,则D的熵(entropy)表示为:
(熵越大,随机变量的不确定性就越大)
其中pi表示第i个类别在整个训练元组中出现的概率,可以用属于此类别元素的数量除以训练元组元素总数量作为估计。熵的实际意义表示是D中元组的类标号所需要的平均信息量。
现在我们假设将训练元组D按属性A进行划分,则A对D划分的期望信息为:
而信息增益即为两者的差值:
选择 gain(A)较大者进行分裂
ID3算法思想描述:
1) 对当前例子集合,计算属性的信息增益;
2) 选择信息增益大的属性A
3) 把在A处取值相同的例子归于同一子集,A取几个值就得几个子集
4) 依次对每种取值情况下的子集,返回1)递归调用建树算法,
5) 若子集中只含有单个属性,则分支为叶子节点,判断其属性值并标上相应的符号,然后返回调用处。
例如样本
首先根据样本计算出信息增益大的属性:不浮出水面是否可以生存,然后再有该属性进行分裂直至没有分类。
算法开始:
1:创建数据集:
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
2:计算数据集中的香农熵
def calcShannonEnt(dataSet):
2. #calculate the shannon value
3. numEntries = len(dataSet)
4. labelCounts = {}
5. for featVec in dataSet: #create the dictionary for all of the data
6. currentLabel = featVec[-1]
7. if currentLabel not in labelCounts.keys():
8. labelCounts[currentLabel] = 0
#计算出分类属性各类别个数,例如”yes”=3,”no"=2
9. labelCounts[currentLabel] +=1
10. shannonEnt = 0.0
11. for key in labelCounts:
12. prob =float(labelCounts[key])/numEntries
13. shannonEnt -= prob*log(prob,2) #get thelog value
14. return shannonEnt
labelCounts 是存储所有label个数的字典,key为label,key_value为label个数。for循环计算label个数,并打印出字典值。函数返回熵值。
myDat, labels = createDataSet()
shannonEnt = calcShannonEnt(myDat)
计算结果为:
numEntries = 5
yes : 2
no : 3
shannonEnt = 0.970950594455
熵值越高,数据集越混乱(label越多,越混乱)。试着改变label值可以观察熵值的变化。
myDat[0][-1] = ‘maybe’
shannonEnt = calcShannonEnt(myDat)
输出结果:
numEntries = 5
maybe : 1
yes : 1
no : 3
shannonEnt = 1.37095059445
得到熵值后即可计算各属性信息增益值,选取最大信息增益值作为当前分类节点,知道分类结束。
3.划分数据集,按照给定的特征划分数据集
列表方法的extend和append的区别
其中featVec[:axis] 返回的是一个列表,其元素是featVec这个列表的索引从0到axis - 1的元素,
也就是不包括axis这个索引上的值,若axis为0,则返回空列表
其中featVec[axis + 1: ]返回的是一个列表,其元素是featVec这个列表的索引从axis + 1开始的所有元素
[python] view plain copy
def splitDataSet(dataSet, axis, value): #找出第axis元素为value的行
retDataSet = []
for featVec in dataSet: #dataset中各元素是列表,遍历每个列表
if featVec[axis] == value: #找出第axis元素为value的行
reducedFeatVec =featVec[:axis] #抽取符合特征的数据去掉axis对应的属性
reducedFeatVec.extend(featVec[axis+1:]) #把抽取出该特征以后的所有特征组成一个列表#
retDataSet.append(reducedFeatVec) #创建抽取该特征以后的dataset
print 'retDataSet = ',retDataSet
return retDataSet
splitDataSet函数参数为:dataSet为输入数据集,包含你label值;axis为每行的第axis元素,对应属性特征;value为对应元素的值,即特征的值。
函数功能:找出所有行中第axis个元素值为value的行,去掉该元素,返回对应行矩阵。
当需要按照某个特征值划分数据时,需要将所有符合要求的元素抽取出来,便于计算信息增益。
例如:
splitDataSet(myDat,0,1)
执行结果:
dataSet = [[1, 1, ‘yes’], [1, 1, ‘yes’], [1, 0, ‘no’], [0,1, ‘no’], [0, 1, ‘no’]]
retDataSet = [[1, ‘yes’], [1, ‘yes’], [0, ‘no’]]
splitDataSet(myDat,1,1)
执行结果:
dataSet = [[1, 1, ‘yes’], [1, 1, ‘yes’], [1, 0, ‘no’], [0,1, ‘no’], [0, 1, ‘no’]]
retDataSet = [[1, ‘yes’], [1, ‘yes’], [0, ‘no’], [0, ‘no’]]
即该函数实现不同属性下的分类,本例子中有不浮出水面和是否有脚蹼两种属性。
4:下面我们的函数为选择最好的数据集划分方式的特征,也就是信息增益最大的特征。函数代码:
#选取特征,划分数据集,计算得出最好的划分数据集的特征
defchooseBestFeatureToSplit(dataSet):
numFeatures = len(dataSet[0]) - 1 #剩下的是特征的个数
baseEntropy = calcShannonEnt(dataSet)#计算数据集的熵,放到baseEntropy中
bestInfoGain = 0.0;bestFeature = -1
for i in range(numFeatures):
featList = [example[i] for example in dataSet] #遍历所有属性
uniqueVals = set(featList) #python的set是一个无序不重复元素集
newEntropy = 0.0
for value in uniqueVals:#下面是计算每种划分方式的信息熵,特征i个,每个特征value个值
subDataSet = splitDataSet(dataSet,i ,value)
prob =len(subDataSet)/float(len(dataSet))
newEntropy = prob *calcShannonEnt(subDataSet)
infoGain = baseEntropy - newEntropy #计算i个特征的信息熵
#print(infoGain)
if(infoGain > bestInfoGain):
bestInfoGain = infoGain
bestFeature = i
returnbestFeature
先获取属性个数,dataset最后一列为label,所以需要-1。
for循环嵌套即用来计算信息增益。
外层for循环用于遍历所有特征。featList = [example[i] forexample in dataSet] 语句用于查找该属性下所有属性值,并使用set函数对属性值列表进行唯一化,防止重复计算。
内侧for循环用于遍历当前属性下所有属性值。计算每一个属性值对应的熵值并求和。结果与原始熵值的差即为信息增益。
信息增益越大,说明该特征越利于分类,即当前分类节点应该选择该属性。
函数返回用来分类的属性标号。
本函数实现不同属性下的增益值,然后可以选择最大增益的属性进行划分。
结果是第一个属性增益大。
到目前为止,已经做到了寻找划分数据集的最佳属性,接下来就是递归调用,不断划分下去。划分到什么时候结束呢?这里有两个依据,第一,划分后的某个数据集中所有数据都同属于一类,这个时候就没必要再划分了,再者,由于这里所讲的决策树是消耗属性的,所以当所有属性都用完了,划分也就停止了。可能有人会问,如果所有属性都用完了,某堆数据集中的数据仍不统一怎么办?解决方法就是少数服从多数,比如10个数据,8个属于A类,2个属于B类,那么就认为他们都属于A类。
下面递归创建树
5:用于找出出现次数最多的分类名称的函数,输入参数classList即为dataset的所有label号。sorted即对字典按降序排列,返回label次数最多的label。
[python]view plain copy
1.defmajorityCnt(classList): #少数服从多数
2. classCount = {} #创建字典,返回出现频率最高label
3. for vote in classList:
4. if vote not in classCount.keys():classCount[vote] = 0
5. classCount[vote] += 1 #该类标签下数据个数+1
6. sortedClassCount =sorted(classCount.iteritems(), key=operator.itemgetter(1), reverse=True) #对所有类标签下的数据个数排序
7. return sortedClassCount[0][0] #[0][0]就代表数据量最大的那个类标签
6:用于创建树的函数代码
def createTree(dataSet,labels):
classList = [example[-1] for example in dataSet]
print 'classList:',classList #获取所有label列表
#停止迭代1:classList中所有label相同,直接返回该label
if classList.count(classList[0]) == len(classList):
return classList[0]
#停止迭代2:用完了所有特征仍然不能将数据集划分成仅包含唯一类别的分组
if len(dataSet[0]) == 1:
return majorityCnt(classList) #否则执行少数服从多数程序
bestFeat = chooseBestFeatureToSplit(dataSet)
bestFeatLabel = labels[bestFeat]
#print 'bestFeat:',bestFeat
#print 'bestFeatLabel:',bestFeatLabel
myTree = {bestFeatLabel:{}}#多级字典的形式展现树,类似多层json结构
del(labels[bestFeat])
featValues = [example[bestFeat] for example in dataSet]
uniqueVals = set(featValues)
for value in uniqueVals:
subLabels = labels[:] #copyall of labels, so trees don't mess up existing labels
myTree[bestFeatLabel][value] = createTree(splitDataSet(dataSet,bestFeat, value),subLabels)
print 'myTree = ',myTree
returnmyTree
7:然后是在python 名利提示符号输入如下命令
[python]view plain copy
1.myDat, labels = trees.createDataSet()
2.myTree =trees.createTree(myDat,labels)
3.printmyTree
结果是:
{'nosurfacing': {0: 'no', 1: {'flippers': {0: 'no', 1: 'yes'}}}}
8:实用决策树进行分类的函数
[python]view plain copy
1.def classify(inputTree, featLabels,testVec):
2. firstStr = inputTree.keys()[0]
3. secondDict = inputTree[firstStr]
4. featIndex = featLabels.index(firstStr)
5. for key in secondDict.keys():
6. if testVec[featIndex] == key:
7. if type(secondDict[key]).__name__== 'dict':
8. classLabel =classify(secondDict[key], featLabels, testVec)
9. else: classLabel =secondDict[key]
10. return classLabel
9:在Python命令提示符,输入:
1.trees.classify(myTree,labels,[1,0])
得到结果:
'no'
Congratulation.Oh yeah. You did it.!!!