赞
踩
目录
简单来说决策树就是一棵树,一颗决策树包含一个根节点、若干个内部结点和若干个叶结点,叶结点就是问题的决策结果。也就是说一棵树包括根节点、父节点、子节点、叶子节点。子节点由父节点分裂出来,然后子节点作为新的父节点继续分裂,直到得出最终结果。
假设给了一个动物的名字它的一些特征,然后我们需要根据这些特征来判断这个动物是否属于哺乳动物。
动物名称 | 体温 | 呼吸 | 胎生 | 毛发 | 哺乳动物 |
猫 | 恒温 | 肺 | 是 | 是 | 是 |
蛇 | 冷血 | 腮 | 否 | 否 | 否 |
决策树如下:
动物名称 | 食物 | 体温 | 呼吸方式 | 胎生 | 毛发 | 生活环境 | 哺乳动物 |
老虎 | 肉食 | 恒温 | 肺 | 是 | 是 | 草原 | 是 |
蛇 | 肉食 | 冷血 | 腮 | 否 | 否 | 森林 | 否 |
鱼 | 杂食 | 冷血 | 腮 | 否 | 否 | 水里 | 否 |
羊 | 草 | 恒温 | 肺 | 是 | 是 | 草原 | 是 |
长颈鹿 | 草 | 恒温 | 肺 | 是 | 是 | 草原 | 是 |
熊猫 | 杂食 | 恒温 | 肺 | 是 | 是 | 森林 | 是 |
大像 | 草 | 恒温 | 肺 | 是 | 否 | 森林 | 是 |
青蛙 | 肉 | 冷血 | 腮 | 否 | 否 | 水里 | 否 |
乌龟 | 肉 | 冷血 | 腮 | 否 | 否 | 水里 | 否 |
蓝鲸 | 肉 | 恒温 | 肺 | 是 | 否 | 水里 | 是 |
第一步:将所有的特征看成一个个节点。(特征:体温,呼吸方式,胎生,毛发等)
第二步:遍历当前特征的子节点,找到最适合的划分点。计算划分之后所有子节点的纯度信息。(比如:体温这个特征按照恒温和冷血来划分)
第三步:使用第二步遍历所有特征,选出该特征最优的划分方式,得出最终的子节点。
上述决策过程存在两个问题:
1.应该选择哪一个特征最适合划分?
2.什么时候停止划分?
信息熵:熵(entropy)表示混乱程度,熵越大,越混乱。假设有一个集合D,其中有X个随机变量,其中第i个元素在集合中出现的概率为Pi。
其中pi表示类i的数量占比。假设二分类问题A,B两类,如果A的数量等于B的数量,那么分类节点的纯度就会达到最低,熵等于1,只有当节点的信息都属于A类或者是B类时,熵=1。
信息增益:(ingormation gain),表示已知A的特征信息,而使得B类信息的不确定性降低的程度。计算公式如下:
对于一个数据集,将所有的特征属性进行划分操作,之后将划分操作的结果集的纯度进行比较,选择纯度高的特征属性作为当前的划分节点。
1.建立数据集:每行数据代表一个样本,每个样本的前6个数据代表6个特征,第7个数据代表样本的分类。下面的标签表示每个样本的这6个特征分别是啥,创建的这六个样本分别是老虎,蛇,鱼,羊,熊猫和蓝鲸。
- def createDataSet():
- dataSet = [[0,0,0,0,0,0,'是'],
- [1,1,1,1,0,1,'否'],
- [1,1,1,1,1,2,'否'],
- [0,0,0,0,2,0,'是'],
- [0,0,0,1,2,1,'是'],
- [0,0,0,1,0,2,'是']]
- #(0恒温,1冷血),(0肺,1腮),(0胎生,1非胎生),(0有毛发,1无毛发)
- #(0肉食,1杂食,2草),(0草原,1森林,2水里)
- labels = ['体温','呼吸方式','胎生','毛发','食物','生活环境']
- return dataSet, labels
2.根据公式计算信息熵:featVec[-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 * math.log(prob,2) #信息增益计算熵
- return shannonEnt
3.按类别划分数据集:
- def splitDataSet(dataSet,axis,value):
- retDataSet = []
- for featVec in dataSet:
- if featVec[axis] == value: #开始遍历数据集
- #featVec 是一维数组,下标为axis元素之前的值加入到reducedFeatVec
- reducedFeatVec = featVec[:axis]
- #下一行的内容axis+1之后的元素加入到reducedFeatVec
- reducedFeatVec.extend(featVec[axis+1:])
- retDataSet.append(reducedFeatVec)
- #返回划分好后的数据集
- return retDataSet
4.选择最后的数据特征作为划分标准:dataSet[0]代表第一个样本,[0,0,0,0,0,0,'是'],长度为7, 7-1=6,6表示该样本的特征数。
- 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
- print("第%d个特征的信息增益为%.1f"%(i,infoGain))
- if (infoGain > bestInfoGain): #选出最大的信息增益
- bestInfoGain = infoGain
- bestFeature = i
- return bestFeature
5.存储:用 字典储存统计出的每个属性)的各个值出现的次数
- 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]
6.最后一步,创建树:
- 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
7.结果:存在问题,当计算信息增益保留1位和保留4位小数时,前3个特征的信息增益无法比较,也就是说可以直接根据这个动物的体温直接判断这个动物是哺乳动物还是非哺乳动物,或者是胎生和毛发也可以确定这个动物是否是哺乳动物。出现这个情况的原因应该是数据样本数量较少,还有ID3这个算法的原因。
8.完整代码:
- import math
- import operator
- import numpy as np
- from numpy import tile
-
-
- def createDataSet():
- dataSet = [[0,0,0,0,0,0,'是'],
- [1,1,1,1,0,1,'否'],
- [1,1,1,1,1,2,'否'],
- [0,0,0,0,2,0,'是'],
- [0,0,0,1,2,1,'是'],
- [0,0,0,1,0,2,'是']]
- #(0恒温,1冷血),(0肺,1腮),(0胎生,1非胎生),(0有毛发,1无毛发)
- #(0肉食,1杂食,2草),(0草原,1森林,2水里)
- labels = ['体温','呼吸方式','胎生','毛发','食物','生活环境']
- return dataSet, 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 * math.log(prob,2) #信息增益计算熵
- return shannonEnt
-
-
- def splitDataSet(dataSet,axis,value):
- retDataSet = []
- for featVec in dataSet:
- if featVec[axis] == value: #开始遍历数据集
- #featVec 是一维数组,下标为axis元素之前的值加入到reducedFeatVec
- reducedFeatVec = featVec[:axis]
- #下一行的内容axis+1之后的元素加入到reducedFeatVec
- reducedFeatVec.extend(featVec[axis+1:])
- retDataSet.append(reducedFeatVec)
- #返回划分好后的数据集
- return retDataSet
-
- 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
- print("第%d个特征的信息增益为%.4f"%(i,infoGain))
- if (infoGain > bestInfoGain): #选出最大的信息增益
- bestInfoGain = infoGain
- bestFeature = i
- return bestFeature
-
-
- 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]
-
- 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
-
-
-
- if __name__ == '__main__':
-
- myDat,labels = createDataSet()
- print(createTree(myDat, labels))
优点:易于理解和解释,决策树分类很快,可以处理不相关特征数据。
缺点: 对缺失数据的数据集处理困难。它构建过程是一个递归的过程,需要确定停止条件,否则过程将不会结束。很容易出现过拟合问题。
ID3只适合在小规模数据集上使用。
原因:plt.的安装包出现问题。
Copyright © 2003-2013 www.wpsshop.cn 版权所有,并保留所有权利。