相关的决策树文章:
-
说到树,相信很多人都会想到二叉树,红黑树啊,都是一些让人头皮发麻的东西。那什么叫做决策树呢?——用来做决策的树?没错,决策树就是用来做决策的树,举个例子,比如:你去相亲,人家丈母娘会问你:你有房吗?如果你回答没有,那你就被pass了,如果你说有,她就会进一步的问你:你有车吗?你回答没有,也被pass了。像这样,一步一步做出选择,不同的选择有不同的结果,这样就叫做决策树。
那我们来想想,既然要根据数据来创建决策树,那我们是不是要考虑三点:
-
数据是怎么分裂的
-
如何选择分类的属性
-
什么时候停止分裂
ok,在了解了决策树的思想,首先当然要掌握一下决策树应该学习的基础知识了。
决策树伪代码
输入:训练集 D=${(x_1,y_1),(x_2,y_2),...,(x_m,y_m)};$
属性集 A=${a_1,a_2,...,a_d}
过程:函数TreeGenerate(D,A)
生成结点 node;
if D 中样本全属于同一个类别C then
将node 标记为C类的叶节点;return
end if
if A = null or D中样本在A的取值相同 then
将node 标记为叶结点,其类别标记为D中样本最多的类;return
end if
从A中选择最优的划分属性a*;
for a*的每一个值a*v do
为node生成一个分支,令Dv表示D在a*上取值为a*v的样本子集;
if Dv为空 then
将分支点标记为叶节点,其类别标记为D中样本最多的类;return;
else
以 TreeGenerate(Dv, A、{a*}}为分支节点
end if
end for
输出:以node为节点的决策树
问题一:什么时候停止分裂?
- 当前节点包含的样本全属于同一个类别,无需划分
- 当前属性集为空,或是所有样本在所有属性上取值相同,无法划分
- 当前节点包含的样本集合为空,不能划分
问题二:如何划分?
这个是决策树的重点,也是最难点。一般来说,随着划分过程不断进行,我们希望决策树的分支节点所包含的样本尽可能属于同一个类别。即纯度越来越高。这时候就要引出信息熵这个概念了。
预备知识
1.信息熵
(1)信息熵:用来度量一个属性的信息量
假定S为训练集,s的目标属性c具有m个可能的类标号值,c={
c
1
c_1
c1,
c
2
c_2
c2,
c
3
c_3
c3,…,
c
m
c_m
cm},假定训练集s中,
c
i
c_i
ci在所有的样本中出现的频率为(i=1,2,3,…,m),则该训练集s所包含的信息熵定义为:
E n t r o y ( s ) = E n t r o y ( p 1 , p 2 , . . . , p m ) = − ∑ i = 1 m p i l o g 2 p i Entroy(s) = Entroy(p_1,p_2,...,p_m)=-\displaystyle\sum_{i=1}^{m}p_ilog_2p_i Entroy(s)=Entroy(p1,p2,...,pm)=−i=1∑mpilog2pi
熵越小表示样本对目标属性的分布越纯,反之熵越大表示样本对目标属性分布越混乱。
2.信息增益
(2)信息增益
信息增益是划分前样本数据集的不纯程度(熵)和划分后样本数据集的不纯程度(熵)的差值
- 假设划分前样本数据集为S,并用属性A来划分样本集S,则按属性A划分S的信息增益Gain(A)为样本集s的熵减去按属性A划分s后的样本子集的熵
G a i n ( S , A ) = E n t r o p y ( S ) − E n t r o p y A ( S ) Gain(S,A) = Entropy(S) - Entropy_A(S) Gain(S,A)=Entropy(S)−EntropyA(S)
按属性A划分S后的样本子集的熵定义如下:假定属性A有k个不同的取值,从而将s划分为k个样本子集{ s 1 , s 2 , . . . , s k s_1,s_2,...,s_k s1,s2,...,sk},则按属性A划分s后的样本子集信息熵为:
E n t r o y A ( s ) = E n t r o y ( p 1 , p 2 , . . . , p m ) = − ∑ i = 1 k ∣ S i ∣ ∣ S ∣ E n t r o p y ( S i ) Entroy_A(s) = Entroy(p_1,p_2,...,p_m)=-\displaystyle\sum_{i=1}^{k}\frac{|S_i|}{|S|}Entropy(S_i) EntroyA(s)=Entroy(p1,p2,...,pm)=−i=1∑k∣S∣∣Si∣Entropy(Si)
其中| S i S_i Si|(i=1,2,…,k)为样本子集 S i S_i Si中包含的样本数,|S|为样本集S中包含的样本数。 ∣ S i ∣ ∣ S ∣ \frac{|S_i|}{|S|} ∣S∣∣Si∣赋予了权重。信息熵越大,说明使用属性A划分后的样本子集越纯,越有利于分类。
所谓的ID3决策树算法的核心,对各个节点上的信息增益进行计算,选择信息增益最大的特征作为节点。具体做法为:
- 从根节点开始,对节点计算所有可能特征的信息增益,选择信息增益最大的特征作为节点
- 对子节点递归地调用以上方法,构建决策树
- 直到所有特征地信息增益均很小或者没有特征可以选择。
ok,关于ID3决策的原理就是这些了。我们来想一下,信息增益越大,是不是代表着决策后的结果越纯,是不是我们更应该选择信息增益大的。那么如何划分和如何选择属性的问题解决了。接下来就是根据思路进行编写代码
代码
# -*- coding: utf-8 -*-
"""
决策树ID3的实现
"""
from math import log
import operator
import numpy as np
import plotTrees
# 输入:训练集
# D =${(x_1, y_1), (x_2, y_2), ..., (x_m, y_m)};$
# 属性集
# A =${a_1, a_2, ..., a_d}
# 过程:函数TreeGenerate(D,A)
# 生成结点
# node;
# if D 中样本全属于同一个类别C then
# 将node
# 标记为C类的叶节点;return
# end if
# if A = null or D中样本在A的取值相同 then
# 将node
# 标记为叶结点,其类别标记为D中样本最多的类;return
# end if
# 从A中选择最优的划分属性a *;
# for a * 的每一个值a * v do
# 为node生成一个分支,令Dv表示D在a * 上取值为a * v的样本子集;
# if Dv为空 then
# 将分支点标记为叶节点,其类别标记为D中样本最多的类;return;
# else
# 以
# TreeGenerate(Dv, A、{a *}}为分支节点
# end if
# end
# for
# 输出:以node为节点的决策树
def majorityCnt(classList):
"""
找到最大频繁向量(多数表决器)
:param classList:训练集
:return:最大值
"""
classCounts = {}
for value in classList: #遍历训练集中的每一个变量
if (value not in classCounts.keys()): #如果变量不在列表中
classCounts[value] = 0 #新建一个字典的键
classCounts[value] += 1 #数量加一
sortedClassCount = sorted(classCounts.items(), key=operator.itemgetter(1), reverse=True) #排序
return sortedClassCount[0][0] #输出第一个,即最大值
def splitDataSet(dataSet, axis, value):
"""
以靠指定列的指定值来划分数据集,比如划分西瓜瓜皮形状为椭圆的数据集
:param axis: 索引列,即形状
:param value: 索引列的特定值,即椭圆
:return:
"""
retDataSet = []
for festdataVal in dataSet:
if festdataVal[axis] == value:
reducedFeatVal = festdataVal[:axis] #这两行去掉索引列
reducedFeatVal.extend(festdataVal[axis+1:])
retDataSet.append(reducedFeatVal)
return retDataSet
def calcShannonEnt(dataSet):
"""
计算香农熵
:param dataSet:
:return:
"""
numEntries = len(dataSet) #获得数据集的长度
labelCounts = {} #计算标签的字典
for featDataVal in dataSet:
currentLabels = featDataVal[-1] #取最后一个标签值
if currentLabels not in labelCounts.keys(): #判断有没有在标签里面
labelCounts[currentLabels] = 0
labelCounts[currentLabels] += 1
shannonEnt = 0.0
for key in labelCounts.keys(): #key有几个遍历几次
prob = labelCounts[key]/float(numEntries) #计算频率
shannonEnt -= prob*log(prob, 2)
return shannonEnt
def chooseBestFeatureToSplit(dataSet):
"""
选择信息增益最大的特征值
:param dataSet:数据集
:return:
"""
numFeatures = len(dataSet[0]) - 1 #看数据集而定,数据中如果最后一行为标签,则删去
baseEntropy = callable(dataSet) #计算所有数据集的香农熵
bestFeaturesindex = 0 #最佳特征的索引
bestEntropy = 0.0 #最佳信息熵
for i in range(numFeatures): #有几个特征值循环几次
featList = [] #特征值列表
for example in dataSet: #获得这个列的值
featList.append(example[i])
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 > bestEntropy: #更改最大经验熵
baseEntropy = infoGain
bestFeaturesindex = i
return bestFeaturesindex #输出最大经验熵的索引
def createTree(dataSet, label):
"""
创建树
:param dataSet:
:param label:
:return:
"""
classList = [] #获得每一个标签
for classVal in dataSet:
classList.append(classVal[-1])
if classList.count(classList[0]) == len(classList): #如果全部标签都相同
return classList[0] #返回该标签
if len(dataSet[0]) == 1: #如果一列只有一个特征
return majorityCnt(classList)
#获取最优的索引值
bestFeatureIndex = chooseBestFeatureToSplit(dataSet)
#获取最优索引值的名称
bestFeatureLabel = label[bestFeatureIndex]
mytree = {bestFeatureLabel:{}} #创建根节点
del(label[bestFeatureIndex]) #删去用过的最优节点
bestFeature = [] #最优的特征
for example in dataSet:
bestFeature.append(example[bestFeatureIndex])
uniquesVal = set(bestFeature) #最优特征的种类
for val in uniquesVal:
subLabel = label[:] #创建个子标签
mytree[bestFeatureLabel][val] = createTree(splitDataSet(dataSet, bestFeatureIndex, val), subLabel) #递归
return mytree
def classify(inputTree, featLable, testVec):
"""
获取分类的结果(算法的使用器)
:param inputTree:决策树字典
:param featLable: 标签列表
:param testVec: 测试向量
:return:
"""
#获取根节点的名称,并且把根节点变成列表
firstSide = list(inputTree.keys())
#根节点名称string类型
firstStr = firstSide[0]
#获得根节点对应得子节点
secondDict = inputTree[firstStr]
#获得子节点在标签列表得索引
featIndex = featLable.index(firstStr)
#获得测试向量的值
key = testVec[featIndex]
#获取树干向量后的变量
valueOfFeat = secondDict[key]
#判断是子结点还是叶子节点:子结点就回调分类函数,叶子结点就是分类结果
if isinstance(valueOfFeat, dict):
classLabel = classify(valueOfFeat, featLable, testVec)
else:
classLabel = valueOfFeat
return classLabel
def storeTree(inputTree, filename):
#写入文件
import pickle
fw = open(filename, 'wb+')
pickle.dump(inputTree,fw)
fw.close()
def grabTree(filename):
#读取数
import pickle
fr = open(filename, 'rb')
return pickle.load(fr)
import pandas as pd
def loadDataSet(filename):
"""
函数说明:从文件中下载数据,并将分离除连续型变量和标签变量
:parameter:
data - Iris数据集
attributes - 鸢尾花的属性
type - 鸢尾花的类别
sl-花萼长度 , sw-花萼宽度, pl-花瓣长度, pw-花瓣宽度
:return:
"""
iris_data = pd.read_csv(filename) #打开文件
iris_data = pd.DataFrame(data=np.array(iris_data), columns=['sl', 'sw', 'pl', 'pw', 'type'], index=range(149)) #给数据集添加列名,方便后面的操作
dataSet = iris_data.values.tolist()
return dataSet
if __name__ == "__main__":
# 因为数据集寻找比较复杂,所以采用自己随机建立数组
dataSet = [[1, 1, 3, 4, 'yes'],
[0, 1, 3, 3, 'yes'],
[1, 0, 4, 3, 'no'],
[0, 1, 2, 4, 'no'],
[0, 0, 3, 3, 'no']]
labels = ['no surfacing', 'flippers', 'table', 'type']
#复制
label1 = labels.copy()
# #创建树
# dataSet = loadDataSet('iris.data')
# labels = ['sl', 'sw', 'pl', 'pw']
# label1 = labels.copy()
mytree = createTree(dataSet, label1)
print(mytree)
#使用树的操作
# a = creatTree.classify(mytree, labels, [0,1, 3, 4])
# print(a)
plotTrees.createPlot(mytree)
决策树的源代码大多就这些,接下来我们看看代码运行的结果:
我们来就看一看树的结构:一开始的节点是no surfacing 如果有为1,则跳到了flippers。依次类推,决策树是由多重字典组成。相信在这边比较难看出来,在之后我会推出决策树的绘图方式。
再来看看结果:当我们数据为【0, 1, 3, 4】的时候,输出了yes,验证一下,并没有错误。