决策树算法(C4.5算法)
1.1 题目的主要研究内容
熟悉和掌握决策树的分类原理、实质和过程,掌握决策树典型算法(ID3、C4.5、CART)的核心思想和实现过程。
(2)自己工作的主要描述(宋体小四号不加粗1.5倍行距)
首先进行决策树中C4.5算法的概述及其C4.5算法的思想、说明C4.5算法的原理及其推导过程、C4.5算法的流程、C4.5算法就ID3算法的差异、C4.5算法改进的优缺点。
1.2 C4.5算法概述
C4.5算法之所以是最常用的决策树算法,是因为它继承了ID3算法的所有优点并对ID3算的进行了改进和补充。C4.5算法采用信息增益率作为选择分支属性的标准,克服了ID3算法中信息增益选择属性时偏向选择取值多的属性的不足,并能够完成对连续属性离散化是处理,还能够对不完整数据进行处理。C4.5算法属于基于信息论(Information Theory)的方法,它是以信息论为基础,以信息熵和信息增益度为衡量标准,从而实现对数据的归纳分类。
1.3 C4.5算法的思想
C4.5算法与ID3算法生成决策树的步骤过程基本相同,主要区别在于连续型属性和属性度量的计算,ID3算法不能处理连续型属性,而C4.5算法可以先离散化连续型属性,然后进行属性选择计算;在属性度量计算时,ID3算法利用信息增益进行属性选择计算,C4.5 算法则运用信息增益率计算。
1.4 C4.5算法相对于ID3算法改进之后有如下优点:
(1) C4.5算法的最大改进就是不在用信息增益来选择属性,而是采用信息增率,这么做就可以避免那些样本数量多但却对分类贡献少的属性作为根节点,提高了算法准确率。
(2)对树进行前剪枝,发现数据有问题可以及时处理,不用等到树建完后在对其剪枝,这样就大大提高了算法效率。
(3)能够通过对数据进行泛化,使连续数据离散化,从而增加了对连续数据的处理能力。
(4)在面对有缺失的数据时,C4.5算法依然能够有效处理。
1.5 C4.5算法信息增益率
信息增益率就是对信息增益进行了规范化,即C4.5算法思想运用信息增益率公式替换了ID3算法中的信息增益的计算思想。
信息增益的规范化用到了“分裂信息(split information)”的概念。
在训练集T中,公式表达了属性A的分裂信息。
与信息增益不同的是,信息增益率是用来计量相同的划分所获得的信息。
即为属性A的增益率的计算公式。
由上面公式公式可知C4.5算法的具体具体公式。
在用C4.5算法构造决策树时,信息增益率最大的属性即为当前节点的分裂属性,随着递归计算,被计算的属性的信息增益率会变得越来越小,到后期则选择相对比较大的信息增益率的条件属性作为分裂属性。
1.6 C4.5算法流程图
1.7 C4.5算法生成决策树的步骤
Stepl:创建一个节点N。
Step2:IF训练数据集为空,THEN返回单个节点N作为空的叶子节点。
Step3:IF训练集中的所有样本都属于同一个类C,THEN返回节点N为叶节点并将该节点标记为类C。
Step4:IF训练集的属性列表为空,THEN返回N作为叶节点,并标记为数据集中样本多的类别。
Step5:IF属性是连续型的,THEN对该属性进行离散化。
Step6:根据公式(2.8)计算属性列表中属性的信息增益率。
Step7:选择最高的信息增益率的属性A,并把节点N标记为属性A
Step8:递归方式循环以上步骤,得到初步决策树。
Step9:利用更大的训练数据集对决策树进行修剪(优化)。
在进行决策树构造时,会根据数据集中的信息判断是否满足停止建树的条件,否则继续迭代。一般情况下,结束的条件主要有:属性列表为空; 数据集中样本都已经归类;所剩样本都属于同一个类。满足其中一个条件便结束建树,得到初始的决策树。接着运用后剪枝的策略进行剪枝,简化决策树。
1.8主要程序代码
import numpy as np
import pandas as pd
import time
from sklearn.metrics import accuracy_score
from utils.plotDecisionTree import *
#计算经验熵
def calcEntropy(dataSet):
mD = len(dataSet) # mD表示数据集的数据向量个数
dataLabelList = [x[-1] for x in dataSet] # 数据集最后一列 标签
dataLabelSet = set(dataLabelList) # 转化为标签集合,集合不重复,所以转化
ent = 0
for label in dataLabelSet: # 对于集合中的每一个标签
mDv = dataLabelList.count(label) # 统计它出现的次数
prop = float(mDv) / mD # 计算频率
ent = ent - prop * np.math.log(prop, 2) # 计算条件熵,见算法预备知识
return ent
#计算经验条件熵
def calcCondEntropy(dataSet,featureSet,i):
mD = len(dataSet)
ent=0
for feature in featureSet:
# 拆分数据集,去除第i行数据特征
splitedDataSet = splitDataSet(dataSet, i, feature)
mDv = len(splitedDataSet)
ent = ent + float(mDv) / mD * calcEntropy(splitedDataSet)
return ent
# 拆分数据集
# index 要拆分的特征的下标
# feature 要拆分的特征
# 返回值 dataSet中index所在特征为feature,且去掉index一列的集合
def splitDataSet(dataSet, index, feature):
splitedDataSet = []
mD = len(dataSet)
for data in dataSet:
if(data[index] == feature): # 将数据集拆分
sliceTmp = data[:index] # 取[0,index)
sliceTmp.extend(data[index + 1:]) # 扩展(index,len]
splitedDataSet.append(sliceTmp)
return splitedDataSet
# 根据信息增益比,选择最佳的特征,并且返回最佳特征的下标
def chooseBestFeature_C45(dataSet):
entD = calcEntropy(dataSet) # 计算经验熵
featureNumber = len(dataSet[0]) - 1
maxGainRatio = -100 # 最大增益比
maxIndex = -1 # 最大增益比下标
GainRatio=0
for i in range(featureNumber):
featureI = [x[i] for x in dataSet] # 数据集合中的第i列特征
featureSet = set(featureI) # 特征集合
# 计算信息增益比
GainRatio = (entD - calcCondEntropy(dataSet,featureSet,i)) / entD
if(maxIndex == -1):
maxGainRatio = GainRatio #利用GainRatio选择子树的根节点
maxIndex = i
elif(maxGainRatio < GainRatio): # 记录最大的信息增益和下标
maxGainRatio = GainRatio
maxIndex = i
return maxIndex # 返回下标
# 寻找最多的特征,作为标签
def mainLabel(labelList):
labelRec = labelList[0]
maxLabelCount = -1
labelSet = set(labelList)
for label in labelSet:
if(labelList.count(label) > maxLabelCount):
maxLabelCount = labelList.count(label)
labelRec = label
return labelRec
# 生成决策树
# dataSet:数据集, featureNames:数据属性类别, featureNamesSet:属性类别集合, labelListParent:父节点标签列表
def createFullDecisionTree(dataSet, featureNames, featureNamesSet, labelListParent):
labelList = [x[-1] for x in dataSet]
if(len(dataSet) == 0): # 如果数据集为空,返回父节点标签列表的主要标签
return mainLabel(labelListParent)
elif(len(dataSet[0]) == 1): # 没有可划分的属性,选出最多的label作为该数据集的标签
return mainLabel(labelList)
elif(labelList.count(labelList[0]) == len(labelList)): # 全部都属于同一个Label,返回labList[0]
return labelList[0]
# 不满足上面的边界情况则需要创建新的分支节点
bestFeatureIndex = chooseBestFeature_C45(dataSet) # 根据信息增益,选择数据集中最好的特征下标
bestFeatureName = featureNames.pop(bestFeatureIndex) # 取出属性类别
myTree = {bestFeatureName: {}} # 新建节点,一个字典
featureList = featureNamesSet.pop(bestFeatureIndex) # 取出最佳属性的类别
featureSet = set(featureList) # 剔除属性类别集合
for feature in featureSet: # 遍历最佳属性所有取值
featureNamesNext = featureNames[:]
featureNamesSetNext = featureNamesSet[:][:]
splitedDataSet = splitDataSet(dataSet, bestFeatureIndex, feature) # 剔除最佳特征
# 递归地生成新的节点
# featureNames:数据属性类别, featureNamesSet:属性类别集合, labelListParent:父节点标签列表
# 一个二叉树
myTree[bestFeatureName][feature] = createFullDecisionTree(splitedDataSet, featureNamesNext, featureNamesSetNext, labelList)
return myTree
# 读取数据集
def readDataSet(path):
ifile = open(path,"r",encoding="utf-8")
#表头
featureName = ifile.readline()
featureName = featureName.rstrip("\n")
#类别,属性
featureNames = (featureName.split(' ')[0]).split(',')
#读取文件
lines = ifile.readlines()
#数据集
dataSet = []
for line in lines:
tmp = line.split('\n')[0]
tmp = tmp.split(',')
dataSet.append(tmp)
#获取标签
labelList = [x[-1] for x in dataSet]
#获取featureNamesSet
featureNamesSet = []
for i in range(len(dataSet[0]) - 1):
col = [x[i] for x in dataSet]
colSet = set(col)
featureNamesSet.append(list(colSet))
#返回 数据集,属性名,所有属性的取值集合,以及标签列表
return dataSet, featureNames, featureNamesSet,labelList
def tree_predict(tree, data):
#print(data)
feature = list(tree.keys())[0] #取树第一个结点的键(特征)
#print(feature)
label = data[feature] #该特征下的属性
next_tree = tree[feature][label] #取下一个结点树
if type(next_tree) == str: #如果是个字符串,说明已经到达叶节点返回分类结果
return next_tree
else: # 否则继续如上处理
return tree_predict(next_tree, data)
def main():
#获取训练集,所有属性名称,每个属性的类别,所有标签
dataTrain, featureNames, featureNamesSet,labelList = readDataSet("data/ex3data.csv")
print("dataTrain: \n",dataTrain,"featureNames:\n",featureNames,"featureNamesSet:\n",featureNamesSet,"labelList:\n",labelList)
#获取测试集
train= pd.read_csv("data/ex3data.csv")
test = pd.read_csv("data/ex3data.csv")
print("train:\n",train[:10])
print("test:\n",test[:10])
#生成决策树
t0 = time.time()
tree=createFullDecisionTree(dataTrain, featureNames,featureNamesSet,labelList)
t1 = time.time()
print("C4.5算法生成决策树的时间开销:",(t1 - t0)*(10**6),"us")
createPlot(tree,"fig/C45.png")
predictTrain = train.apply(lambda x: tree_predict(tree, x), axis=1)
label_list = train.iloc[:, -1]
score = accuracy_score(label_list, predictTrain)
print('训练补全分支准确率为:' + repr(score * 100) + '%')
#预测
y_predict = test.apply(lambda x: tree_predict(tree, x), axis=1)
label_list = test.iloc[:, -1]
score = accuracy_score(label_list, y_predict)
print('测试集补全分支准确率为:' + repr(score * 100) + '%')
if __name__ == "__main__":
main()
决策树是一种类似流程图的树结构,其中每个内部节点(非树叶节点)表示在一个属性上的测试,
每个分枝代表一个测试输出,而每个树叶节点存放一个类标号。
一旦建立好决策树,对于一个未给定类标号的元组,跟踪一条有根节点到叶节点的路径,该叶节点就存放着该元组的预测。
1.9 运行结果及分析
如上图显示dataTrain数据及我们训练的数据train
上图显示我们测试的数据及C4.5算法生成决策树的时间开销和训练不全分支准确率、测试集补全分支准确率
上图为程序运行结果图,我们可以根据是否周末、天气、是否有促销等特征判断销量的高低。