什么是决策树?
概念
决策树是一个预测模型,它代表的是对象属性与对象值之间的一种映射关系。树中每个节点表示某个对象,而每个分叉路径则代表某个可能的属性值,而每个叶节点则对应从根节点到该叶节点所经历的路径所表示的对象的值。因此,从数据产生决策树的机器学习技术,就是决策树。
图像模型
决策树是一树状结构,它的每一个叶节点对应着一个分类,非叶节点对应着在某个属性上的划分,根据样本在该属性上的不同取值将其划分成若干个子集。对于非纯的叶节点,多数类的标号给出到达这个节点的样本所属的类。构造决策树的核心问题是在每一步如何选择适当的属性对样本做拆分。对一个分类问题,从已知类标记的训练样本中学习并构造出决策树是一个自上而下,分而治之的过程。
目的
为产生一颗泛化能力强,即处理未见示例能力强的决策树。
决策树基本流程
决策树是一个由根到叶的递归过程,在每一个中间结点寻找划分属性,递归重要的是设置停止条件:
(1)当前结点包含的样本属于同一类别,无需划分;
(2)当前属性集为空,或是所有样本在所有属性上取值相同无法划分,简单理解就是当分到这一节点时,所有的属性特征都用完了,没有特征可用了,就根据label数量多的给这一节点打标签使其变成叶节点(其实是在用样本出现的后验概率做先验概率);
(3)当前结点包含的样本集合为空,不能划分。这种情况出现是因为该样本数据缺少这个属性取值,根据父结点的label情况为该结点打标记(其实是在用父结点出现的后验概率做该结点的先验概率)。
决策树涉及公式
根节点的信息熵
(求和符合上下部分实在研究不出来怎么打,只能用图片了)
增益率
Gain_ratio(D,a)=
IV(a)称为属性a aa的固有值。属性a aa的可能取值数目越多(V越大),则 I V ( a ) {\rm{IV}}(a)IV(a)的值通常越大。增益率准则对可取值数目较少的属性有所偏好,因此C4.5算法并不是直接选择增益率最大的候选划分属性,而是使用一个启发式:先从候选划分属性中找出信息增益高于平均水平的属性,再从中选择增益最高的。
基尼指数
Gain(D)反映了从数据集D中随机抽取两个样本,其类别标记不一致的概率。因此, G a i n ( D ) {\rm{Gain(}}D{\rm{)}}Gain(D)越小,数据集D DD的纯度越高。
决策树代码示例
我以西瓜数据为模板,改编了一个以('肤色', '个子', '性格', '情商', '智商', '品格', '优秀')为特征数据集,每个特征含有两个及以上的离散值。
import random
import pandas as pd
import numpy as np
import operator
# 选择最好的数据集划分方式
def chooseBestFeatureToSplit(dataSet):
baseEntropy = calcShannonEnt(dataSet)
bestInfoGain = 0.0
max_info_gain_ratio =0.0
bestFeature = -1
for i in range(dataSet.shape[1]- 1): # 对每个属性信息增益
levels = dataSet.iloc[:,i].value_counts().index #提取提取出这一列中每个取值的个数
newEntropy = 0.0
split_info = 0.0
for value in levels: # 对每一种取值计算信息增益
subDataSet = dataSet[dataSet.iloc[:,i] == value]
pro = subDataSet.shape[0]/dataSet.shape[0]
newEntropy += pro*calcShannonEnt(subDataSet)
split_info += -pro * np.log2(pro)
infoGain = baseEntropy - newEntropy
# if (split_info == 0):
# continue
info_gain_ratio = infoGain / split_info
if (infoGain > bestInfoGain): # 选择信息增益最大的属性
bestInfoGain = infoGain
bestFeature = i
# if (info_gain_ratio > max_info_gain_ratio): # 选择值最大的信息增益率
# max_info_gain_ratio = info_gain_ratio
# bestFeature = i
# print(f'最好的信息增益:{bestInfoGain}')
print(f'最大的信息增益率:{bestFeature}')
print(f"最好的特征索引{bestFeature}")
return bestFeature
# 计算信息熵
def calcShannonEnt(dataSet):
numEntries = dataSet.shape[0] # 样本数
iset = dataSet.iloc[:,-1].value_counts()
p = iset/numEntries
ent = (-p*np.log2(p)).sum()
return ent
# 划分数据集,axis:按第几个属性划分,value:要返回的子集对应的属性值
def splitDataSet(dataSet, axis, value):
col = dataSet.columns[axis]
retDataSet = dataSet.loc[dataSet[col] == value,:].drop(col,axis = 1)
return retDataSet
# 通过排序返回出现次数最多的类别
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 = list(dataSet.columns)
classList = dataSet.iloc[:,-1].value_counts() # 类别向量
if classList[0] == dataSet.shape[0] or dataSet.shape[1] ==1: # 如果只有一个类别,返回
return classList.index[0]
bestFeat = chooseBestFeatureToSplit(dataSet) # 最优划分属性的索引
bestFeatLabel = labels[bestFeat] # 最优划分属性的标签
myTree = {bestFeatLabel: {}}
del labels[bestFeat] # 已经选择的特征不再参与分类
uniqueValue = set(dataSet.iloc[:,bestFeat]) # 该属性所有可能取值,也就是节点的分支
for value in uniqueValue: # 对每个分支,递归构建树
myTree[bestFeatLabel][value] = createTree(
splitDataSet(dataSet, bestFeat, value))
return myTree
# 测试算法
def classify(inputTree, featLabels, testVec):
firstStr = next(iter(inputTree)) # 根节点
secondDict = inputTree[firstStr]#下一个索引
featIndex = featLabels.index(firstStr) # 跟节点对应的属性
for key in secondDict.keys(): # 对每个分支循环
if testVec[featIndex] == key: # 测试样本进入某个分支
if type(secondDict[key]) == dict: # 该分支不是叶子节点,递归
classLabel = classify(secondDict[key], featLabels, testVec)
else: # 如果是叶子, 返回结果
classLabel = secondDict[key]
return classLabel
def acc(train , test):
inputTree = createTree(train)
result = []
labels = list(train.columns)
for i in range(test.shape[0]): #对测试集中每条数据进行循环
testvec = test.iloc[i,:-1]
classlabel = classify(inputTree,labels,testvec)
result.append(classlabel) #将分类结果添加到result
test['predict'] = result #的最后一列
acc = (test.iloc[:,-1] == test.iloc[:,-2]).mean()
print(f'模型预测的准确率为{acc}')
return test
def creatdataset():
dataset = [
['红润', '小巧', '活泼', '聪明', '有才', '努力', '是'],
['黛黑', '小巧', '腼腆', '聪明', '有才', '努力', '是'],
['黛黑', '小巧', '活泼', '聪明', '有才', '努力', '是'],
['红润', '小巧', '腼腆', '聪明', '有才', '努力', '是'],
['白皙', '小巧', '活泼', '聪明', '有才', '努力', '是'],
['红润', '标致', '活泼', '聪明', '绝代', '稳重', '是'],
['黛黑', '标致', '活泼', '憨直', '绝代', '稳重', '是'],
['黛黑', '标致', '活泼', '憨憨', '绝代', '努力', '是'],
['黛黑', '标致', '腼腆', '憨憨', '绝代', '努力', '否'],
['红润', '高挑', '调皮', '聪明', '养晦', '稳重', '否'],
['白皙', '高挑', '调皮', '憨憨', '养晦', '努力', '否'],
['白皙', '小巧', '活泼', '憨憨', '养晦', '稳重', '否'],
['红润', '标致', '活泼', '憨直', '有才', '努力', '否'],
['白皙', '标致', '腼腆', '憨直', '有才', '努力', '否'],
['黛黑', '标致', '腼腆', '聪明', '绝代', '稳重', '否'],
['白皙', '小巧', '腼腆', '憨憨', '养晦', '努力', '否'],
['红润', '小巧', '腼腆', '憨直', '绝代', '努力', '否']
]
dataset = pd.DataFrame(dataset, columns=('肤色', '个子', '性格', '情商', '智商', '品格', '优秀'))
return dataset
#随机划分数据
def randsolit(dataset , rate):
l = list(dataset.index)
random.shuffle(l)
dataset.index = l
n =dataset.shape[0]
m = int(n*rate)
train = dataset.loc[range(m),:]
# train =dataset
test = dataset.loc[range(m,n),:]
dataset.index = range(dataset.shape[0])
test.index = range(test.shape[0])
return train, test
if __name__ == '__main__':
dataset = creatdataset()
train, test = randsolit(dataset,0.9)
Trees = createTree(train)
acc(train,test)
# 将创建的树保存
# np.save('myTree3.npy', Trees)
# 树的加载
# read_myTree = np.load('myTree.npy', allow_pickle=True).item()
print(Trees)
代码结果显示
决策树优缺点
优点:计算复杂度不高,输出结果易于理解,中间值的缺失不敏感,可以处理不相关特征数据;
缺点:可能会产生过度匹配问题;
适用数据范围:数值型和标称型。
实验遇到问题
1、TabError: inconsistent use of tabs and spaces in indentation
Tab错误,解决方法:调整代码格式。
2、模型准确率为0
某些特征的样本比例过大,调节样本权重
参考文献
一文看懂决策树(Decision Tree) - 知乎 (zhihu.com)