什么是决策树模型?
决策树模型就是通过树来做决策的模型。所有数据从根节点出发,每经过一个节点,就做一次分类判断,最终将所有数据落到叶节点中。例如以是否喜欢打游戏为标准进行分类。在这里构造一个简单的决策树。
这个决策树以年龄和性别进行了一个粗略分类。注:决策树中的判断先后顺序不可以轻易替换,决策树对于这个顺序是比较敏感的。如果替换,会对模型效果有影响。决策树既可以做分类问题也可以做回归问题。
决策树的组成
根节点:第一个选择点
非叶子节点和分支:中间过程
叶子节点:最终的决策结果
决策树的训练
决策树的训练就是对节点的选择,以及如何对节点切分。那么如何进行切分和选择呢?
首先,我们要选择分类效果最强的特征作为最开始的节点,然后再逐渐选择分类效果较弱的特征作为之后的节点。通过一种衡量标准来计算最强的特征——信息熵。
熵就是随机变量不确定性的度量(或混乱程度)信息熵的计算公式是
其中pi是各个元素在集合中的占比。例如集合A = [1,1,1,1,1,1,1,1,2,2],集合B=[1,2,3,4,5,6,7,8,9,10],显然集合A的熵值要更小。我们显然希望在分类之后所得到的各个类别信息熵要更小。当p = 0或p = 1时,熵值为0,当p = 0.5时,熵值最大。
这里补充一个定义:信息增益——表示特征X使Y的不确定性减少的程度。以上的描述就说明信息增益是最大的。
决策树解决回归问题,只需要将衡量标准从信息熵转化为方差即可。方差在一定程度上可以展现数据的相似性。
决策树算法
ID3
利用信息增益来选取节点。ID3自身存在问题,因为它没有考虑分类前自身的熵。从而导致选择节点时由于自身熵过小导致信息增益上限小的问题。
C4.5
利用信息增益率。解决ID3问题,考虑自身熵。
CART
使用GINI系数当作衡量标准。GINI系数计算公式:
决策树剪枝策略
为什么要剪枝?
决策树模型过拟合风险很大,理论上可以完全分开数据。因为如果节点过多,是可以将每一个样本分在一篇叶子上的。可以在训练集取得较好的效果,但是在测试集的效果并不好。因此,在构建好决策树模型之后,要采取一个剪枝的策略,使分类标准更强而有力,也可以使树更加简洁,加速运行效率,同时提升模型的适用程度。
预剪枝策略
预剪枝策略就是边建立决策树边剪枝的策略。具体而言,可以限制深度,叶子节点数量,叶子节点样本数,信息增益量等。
后剪枝策略
建立完决策树再来剪枝。通过一定的衡量标准。
其中是自身的一个GINI系数值。
是从某节点开始的叶子节点的个数。
是常量,表示对于叶子节点的数量的重视程度。
的值越大,损失越多,以这个标准判断要不要剪枝。
代码实现
首先我们创建一个数据集,并指定各列的标签。
from matplotlib.font_manager import FontProperties
import mathplotlib.pyplot as plt
from math import log
import operator
import pickle
def createDataSet():
dataSet = [[0,0,0,0,"no"],
[0,0,0,1,"no"]
[0,1,0,1,"yes"]
[0,1,1,0,"yes"]
[0,0,0,0,"no"]
[1,0,0,0,"no"]
[1,0,0,1,"no"]
[1,1,1,1,"yes"]
[1,0,1,2,"yes"]
[1,0,1,2,"yes"]
[2,0,1,2,"yes"]
[1,2,0,1,"yes"]
[2,1,0,2,"yes"]
[2,0,0,0,"no"]]
labels = ["F1-AGE","F2-WORD","F3-HOME","F4-LOAN"]
return dataSet, labels
创建树模型
#创建树
#dataset是数据集,labels为现数据集中可选用的特征,表示是否以满足要求,featlabels表示顺序
def createTree(dataset,labels,featlabels):
#取出数据集中的最后一列
classlist = [example[-1] for example in dataset]
#判断是否全部为同一个值,如果是,返回这一列的数据的值(yes或no),作为叶子节点
if classlist.count(classlist[0]) == len(classlist):
return classlist[0]
#判断是否已经用完了所有分类标签,如果是,返回这一列中占比最大的值
if len(dataset[0]) == 1:
return majorityCnt(classlist)
#计算熵值的函数,选出最强的特征,featlabels用来存储已经使用了的特征
bestfeat = chooseBestFeatureToSplit(dataset)
bestfeatlabel = labels[bestfeat]
featlabels.append(bestfeatlabel)
#创建树
myTree = {bestfeatlabel:{}}
#删除已经使用的特征
del labels[bestfeat]
#提取选出来的特征
featvalues = [example[bestfeat] for example in dataset]
uniquevals = set(featvalue)
for value in uniquevals:
sublabels = labels[:]
#递归调用函数
myTree[bestfeatlabel][value] = createTree(splitDataset(dataset,bestfeat,value),sublabels,value,featlabels)
return myTree
熵值的计算
#计算最多的类别
def majorityCnt(classlist):
classcount = {}
for vote in classlist:
if vote not in classcount.keys():classcount[vote] = 0
classcount[vote] += 1
sortedclasscount = sort(classcount.itesms(),key = operator.itemgetter(1),reverse = True)
return sortedclasscount[0][0]
以上代码定义了majorityCnt函数,利用字典对象进行排序,选出该节点数量最多的元素。
def chooseBestFeatureToSplit(dataset):
numfeatures = len(dataset[0]) - 1
baseentropy = calcShannonEnt(dataset)
baseinfogain = 0
basefeature = -1
for i in range(numfeatures):
featlist = [example[i] for example in dataset]
uniquevals = set(featlist)
newentropy = 0
for val in uniquevals:
subdataset = splitdataset(dataset,i,val)
prob = len(subdataset)/float(len(dataset))
newentropy += prob * calcShannonEnt(subdataset)
infogain = baseEntropy - newentropy
if (infogain > baseentropy):
bestinfogain = infogain
bestfeature = i
return bestfeature
def splitdataset(datset,axis,val):
retdataset = []
for featvec in dataset:
if featvec[axis] == val:
reducedfeatvec = featvec[:axis]
reducedfeatvec.extend(featvec[axis+1])
retdataset.append(reducedfeatvec)
return retdataset
#计算熵值
def calcShannonEnt(dataset):
numexamples = len(dataset)
labelcounts = {}
for featvec in dataset:
currentlabel = featvec[-1]
ifcurrentlabel not in lavelcounts.keys():
labelcounts[currentlabel] = 0
labelcounts[currentlabel] += 1
shannonEnt = 0
for key in labelcounts:
prop = float(labelcounts[key])/numexamples
shannonEnt -= prop*log(prop,2)
return shannonEnt