决策树
目录
- 决策树算法原理
- 代码实现
一、决策树算法原理
直接上图,直观的了解下决策树是什么?
矩形:表示判断模块
椭圆形:表示终止模块
我相信上图大家都看得懂吧~
决策树
优点: 计算复杂度不高、输出结果易于理解,对中间值的缺失不敏感,可处理不相关特征数据
缺点: 可能会产生过度匹配问题
适用数据范围: 数值型和标称型
- 信息增益
决策树就是在当前数据集上依据某个决定性属性特征来划分数据集,并在划分的子集上重复该过程,直到所有具有相同类型的数据在一个数据子集内。
我们划分数据集是有个大原则的: 将无序的数据变得更加有序。
这里引出几个概念(遇到公式不要怕):
信息增益:划分数据集之前之后信息发生的变化称为信息增益。
知道如何计算信息增益,我们就可以计算每个特征值划分数据集获得的信息增益,获得信息增益最高的特征就是最好的选择。
熵:定义为信息的期望值。
如果待分类的事物可能划分在多个类之中,则符号
x
i
x_i
xi的信息定义为:
l
(
x
i
)
=
−
l
o
g
2
p
(
x
i
)
l(x_i)=-log_2p(x_i)
l(xi)=−log2p(xi)
其中
p
(
x
i
)
p(x_i)
p(xi)是选择该分类的概率。
上面只是某个类别的信息,熵则是将所有类别求和:
H
=
−
∑
i
=
1
n
p
(
x
i
)
l
o
g
2
p
(
x
i
)
H=-\sum_{i=1}^{n}{p(x_i)log_2p(x_i)}
H=−i=1∑np(xi)log2p(xi)
代码实现
from math import log
# 计算香农熵
def calcShannonEnt(dataSet):
numEntries = len(dataSet) # 数组长度为5
labelCounts = {} # 类别标签数
for featVec in dataSet:
currentLabel = featVec[-1] # 每一条最后一列,也就是'yes'和'no'
print(currentLabel)
if currentLabel not in labelCounts.keys():
labelCounts[currentLabel] = 0 # 创建键
labelCounts[currentLabel] += 1
print(labelCounts) # {'yes': 2, 'no': 3}
shannonEnt = 0.0
for key in labelCounts:
prob = float(labelCounts[key])/numEntries # 0.4 和 0.6 每个类别占总类别的比例
shannonEnt -= prob*log(prob, 2) # 每个类别香农熵减等,公式带减号
return shannonEnt
# 测试数据集
def createDataset():
# 3列对应3个特征:
# 不浮出水面能否生存
# 是否有脚蹼
# 属于鱼类
dataSet = [
[1, 1, 'yes'],
[1, 1, 'yes'],
[1, 0, 'no'],
[0, 1, 'no'],
[0, 1, 'no']
]
labels = ['no surfacing', 'flippers']
return dataSet, labels
# 测试
myDat, labels = createDataset()
myEnt = calcShannonEnt(myDat)
print(myEnt)
得到熵之后,我们就可以按照获取最大信息增益的方法划分数据集,那么如何划分数据集以及如何度量信息增益呢?
插入python一个知识点:数组切片
test = [1, 2, 'yes'] # 取数组前n个 # test[:n] # 如:test[:1] # 结果:[1] # 不要前n个 # test[n:] # 如:test[2:] # 结果:['yes']
另一个知识点:extend()与append()
>>> a = [1, 2, 3] >>> b = [4, 5, 6] >>> a.append(b) >>> [1, 2, 3, [4, 5, 6]] >>> a.extend(b) >>> [1, 2, 3, 4, 5, 6]
# 按照给定特征划分数据集
# dataSet:待划分的数据集
# axis:数据集的第axis+1个特征,数组下标从0开始
# value:限定的特征值
# 如:axis=0,那就是第1个特征是否
# 等于你设定的要分类的value值
def splitDataset(dataSet, axis, value):
retDataSet = [] # 重新分类好的数据集
for featVec in dataSet:
print(featVec[axis])
if featVec[axis] == value: # 过滤
reducedFeatVec = featVec[:axis] # 取前0个元素,这里是[]
print(reducedFeatVec)
reducedFeatVec.extend(featVec[axis+1:]) # 去掉前1个元素,这里剩后面两个
print(featVec[axis+1:])
retDataSet.append(reducedFeatVec)
return retDataSet
# 结果
>>> retDataSet: [[1, 'yes'], [1, 'yes'], [0, 'no']]
>>> dataSet: [[1, 1, 'yes'], [1, 1, 'yes'], [1, 0, 'no'], [0, 1, 'no'], [0, 1, 'no']]
# 可以看出第一列特征为1的数据都被挑出
选择最好的数据集划分方式:
# 选择最好的数据集划分方式
# 选择信息增益大的特征来分类
def chooseBestFeatureToSplit(dataSet):
numFeatures = len(dataSet[0]) - 1 # 去掉类别那一列,剩下2列
baseEntropy = calcShannonEnt(dataSet) # 香农熵
bestInfoGain = 0.0 # 信息增益初始值
bestFeature = -1
for i in range(numFeatures):
featList = [example[i] for example in dataSet] # dataSet中每条数据的第i列数据取出来并拼成数组,如:i=0,得[1, 1, 1, 0, 0]
# print(featList)
uniqueVals = set(featList) # 去重{0, 1}
newEntropy = 0.0
# print(uniqueVals)
for value in uniqueVals:
subDataSet = splitDataSet(dataSet, i, value)
# print(subDataSet)
prob = len(subDataSet) / float(len(dataSet))
# print(newEntropy)
newEntropy += prob * calcShannonEnt(subDataSet) # 累加的过程
# print(newEntropy)
infoGain = baseEntropy - newEntropy # calculate the info gain; ie reduction in entropy
# print('infoGain:')
# print(infoGain)
if (infoGain > bestInfoGain): # compare this to the best gain so far
bestInfoGain = infoGain # if better than current best, set to best
bestFeature = i
# print(bestFeature)
return bestFeature # returns an integer
总结一下上面代码的思想:
1. 首先,计算原始数据集的香农熵(baseEntropy)
2. 按特征划分数据集(如:按第1列的特征划分为两组数据集)并计算各自香农熵,最后累加求得该列特征的香农熵
3. 计算信息增益(baseEntropy减去该列特征的香农熵)
4. 与之前计算的信息增益比较下,谁大谁活下来,并记录大的那个的特征是哪一列,从0起算
5. 每列特征如此迭代
接下来正式创建树啦~
# 返回频数最多的类别
# 该方法在为创建树方法服务啦~
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=operator.itemgetter(1), reverse=True)
return sortedClassCount[0][0]
真正创建树的方法是这个(递归):
# 递归创建决策树
def createTree(dataSet,labels):
print(dataSet)
print(labels)
classList = [example[-1] for example in dataSet]
print(classList)
if classList.count(classList[0]) == len(classList):
print("类别相同呀~")
return classList[0] # 类别相同则停止继续划分
if len(dataSet[0]) == 1: # 如果每列只有一个元素,如:['no', 'yes'],那就取频数大的
print("如果每列只有一个元素,那就取频数大的")
return majorityCnt(classList)
bestFeat = chooseBestFeatureToSplit(dataSet)
print('bestFeat:')
print(bestFeat)
bestFeatLabel = labels[bestFeat]
myTree = {bestFeatLabel: {}}
del(labels[bestFeat]) # 删除labes数组某元素,如del(labels[1]),即删除该数组下标1的数
featValues = [example[bestFeat] for example in dataSet] # 取某列并拼接数组
uniqueVals = set(featValues) # 去重
for value in uniqueVals:
subLabels = labels[:]
# print('a')
# print(subLabels)
myTree[bestFeatLabel][value] = createTree(splitDataSet(dataSet, bestFeat, value), subLabels)
return myTree
递归的话,可能需要真正跑一跑程序才能理解深刻,这里列一下整体思路:
1. 首先,明确一下两个特殊情况:
- 一是,类别都相同,如:[[‘yes’], [‘yes’]],那直接返回
- 另一是,每列只有一个元素,那就取频数大的
2. 如果不符合1的条件,那就开始分,那怎么分?chooseBestFeatureToSplit这个方法告诉我们选哪个特征来分
3. splitDataSet按2步骤得出的特征,划分数据,如此递归,直至结束
好啦,简单的决策树就是这样啦~基本原理大家应该都清楚了吧哈、、ヾ(◍°∇°◍)ノ゙
这兄弟也很全哟~
https://blog.csdn.net/jiaoyangwm/article/details/79525237#311__83
补充:使用决策树执行分类
这才是我们的最终目的呀~
但是创建决策树过程还是有点慢的,假如你数据量大的话
那么,就得保存创建好的树咯~
来,上代码:
# 使用python模块pickle序列化对象
# 可在磁盘上保存对象,并在需要的时候读取出来
# 任何对象都可以执行序列化操作,字典对象也不例外
def storeTree(inputTree, filename):
import pickle
fw = open(filename, 'w')
pickle.dump(inputTree, fw)
fw.close()
def grabTree(filename):
import pickle
fr = open(filename)
return pickle.load(fr)
测试分类:
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
myTree = retrieveTree(0)
myDat, labels = createDataset()
class0 = classify(myTree, labels, [1, 0])
print(class0)
>>>no