目录
一.什么是决策树
1.1 决策树的生成
1.2 如何选择最优划分属性
方法二:信息增益率
方法三:基尼指数
1.2.1 划分数据集
1.2.2 选择最好的数据集划分方式
二.创建树
2.2 使用决策树的分类函数
2.3 存储决策树
决策树,就是一个类似于流程图的树形结构,树内部的每一个节点代表的是对一个特征的测试,树的分支代表该特征的每一个测试结果,而树的每一个叶子节点代表一个类别。树的最高层是就是根节点。如下图就是个决策树
(1)特征选择:特征选择是指从训练数据中众多的特征中选择一个特征作为当前节点的分裂标准,如何选择特征有着很多不同量化评估标准标准,从而衍生出不同的决策树算法。
(2)决策树生成: 根据选择的特征评估标准,从上至下递归地生成子节点,直到数据集不可分则停止决策树停止生长。 树结构来说,递归结构是最容易理解的方式。
(3)剪枝:决策树容易过拟合,一般来需要剪枝,缩小树结构规模、缓解过拟合。剪枝技术有预剪枝和后剪枝两种。
预剪枝:生成树过程中预判。类似于贪心算法,生成前后,验证精度减少或相等则不生成树。
后剪枝:先建树,再判断是否剪枝
注:在构造决策树时,我们需要解决的第一个问题是:确定当前数据集上的决定性特征,为了得到该决定性特征,必须评估每个特征,完成测试之后,原始数据集就被划分为几个数据子集,这些数据子集会分布在第一个决策点的所有分支上,如果某个分支下的数据属于同一类型,则当前无序阅读的垃圾邮件已经正确的划分数据分类,无需进一步对数据集进行分割,如果不属于同一类,则要重复划分数据子集,直到所有相同类型的数据均在一个数据子集内。
1.2 创建分支的伪代码createBranch()如下:
If so return 类标签:
Else
寻找划分数据集的最好特征
划分数据集
创建分支节点
for 每个划分的子集
调用函数createBranch()并增加返回结果到分支节点中
return 分支节点
方法一:信息增益 (即是在划分数据前后信息发生的变化称为信息增益)。信息增益既可以用熵也可以用GINI系数来计算
信息熵:度量样本集合纯度的指标之一,信息熵(Ent(D))的值越小,则样本集合(D)的纯度越高,Ent(D)最小为0,最大值为log2|y|
计算公式:
信息熵代码实现:
def calcShannonEnt(dataSet):
numEntries = len(dataSet)
labelCounts = {}#字典
for featVec in dataSet: #the the number of unique elements and their occurance
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 * log(prob,2) #log base 2
return shannonEnt
信息增益基于信息熵(Ent(D))。一般而言,信息增益越大,则意味着该属性越适合作为划分属性;信息增益对可取值数目较多的属性有所偏好。
特点:增益率准则对可取数目较少的属性有所偏好
IV(a):属性a的固有值,属性a的可能取值数目越大,IV(a)的值通常越大
增益率基于IV(a),公式如图:
代码实现:
def create_tree_C45(self,dataset,feat_labels):
#print("本节点特征",labels)
classList=list(dataset['label'])#获得类别列表
#若是所有样本属于同一类别,则返回这一类别做节点标记,停止划分
if classList.count(classList[0])==len(classList):
#print("该节点上所有样本为同一类")
return classList[0]
#若特征集为空,则返回数据集中样本数最多的类作为节点标记,停止划分
if len(dataset.iloc[0])==1:
#print("特征集为空")
return self.majorityCnt(classList)
bestFeatLabel,bestFeatIndex=self.choose_best_feature_C45(dataset,feat_labels) #选择最优特征
print("最佳划分特征(createtree)",bestFeatLabel,bestFeatIndex)
myTree={bestFeatLabel:{}} #分类结果以字典形式保存
#如果最佳划分特征不为空则继续划分
if(bestFeatLabel!=''):
del(feat_labels[bestFeatIndex])
featValues=dataset[bestFeatLabel] #最好划分特征的所有取值
#print(featValues)
uniqueVals=set(featValues)
for value in uniqueVals:
subLabels=feat_labels[:]
#print(bestFeatLabel,"取值为:",value)
values_head=subLabels
newdataset=self.split_dataset(dataset,bestFeatLabel,bestFeatIndex,value,values_head)
myTree[bestFeatLabel][value]=self.create_tree_C45(newdataset,subLabels)
return myTree
分类问题中,设D中有K个类,样本点属于第K类的概率为Pk,则概率分布的基尼值定义为
给定数据集D,属性a的基尼指数定义为:
所以Gini(D)越小,数据集D的纯度越高,因此在选择划分属性时,选择那个使得划分后基尼指数最小的属性作为最优划分属性
import numpy as np
def calcGini(data_y): #根据基尼指数的定义,根据当前数据集中不同标签类出现次数,获取当前数据集D的基尼指数
m = data_y.size #获取全部数据数量
labels = np.unique(data_y) #获取所有标签值类别(去重后)
gini = 1.0 #初始基尼系数
for i in labels: #遍历每一个标签值种类
y_cnt = data_y[np.where(data_y==i)].size / m #出现概率
gini -= y_cnt**2 #基尼指数
return gini
def splitDataSet(dataSet, axis, value):
retDataSet = []
for featVec in dataSet:
if featVec[axis] == value:
#判断featVec数组的第axis个是否等于value
reducedFeatVec = featVec[:axis]
#从0到axis #chop out axis used for splitting
reducedFeatVec.extend(featVec[axis+1:])
#从axis到最后 所以reducedFeatVec就是一列除去特征值的数组
retDataSet.append(reducedFeatVec)
#retDataSet就是符合条件featVec[axis] == value:并所有除去特征值的数组的集合
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]#create a list of all the examples of this feature
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 > bestInfoGain):
bestInfoGain = infoGain
bestFeature = i
return bestFeature
代码实现:
def createTree(dataSet, labels):
classList = [example[-1] for example in dataSet]
if classList.count(classList[0]) == len(classList):
return classList[0]#stop splitting when all of the classes are equal
if len(dataSet[0]) == 1: #stop splitting when there are no more features in dataSet
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[:] #copy all of labels, so trees don't mess up existing labels
myTree[bestFeatLabel][value] = createTree(splitDataSet(dataSet, bestFeat, value), subLabels)
return myTree
def classify(inputTree, featLabels, testVec):
firstStr = list(inputTree)[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
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 trees
import treePlotter
fr=open('lenses.txt')
lenses=[inst.strip().split('\t') for inst in fr.readlines() ]
lensesLabels=['age','prescript','astigmatic','taerRate']
lensesTree = trees.createTree(lenses,lensesLabels)
treePlotter.createPlot(lensesTree)
实验结果如图:
三.总结
参考博客:
机器学习(三)——决策树_doubaijj的博客-CSDN博客