目录
4.1决策树
4.11决策树介绍
决策树是一种常用的机器学习算法,它模拟人类在面对决策时的思维过程,通过一系列的分支条件来进行决策。它可以用于分类和回归任务。在决策树中,每个节点代表一个属性/特征,每个分支代表一个属性值的选择,而每个叶子节点代表一个类别标签(用于分类问题)或一个数值(用于回归问题)。
决策树的构建过程包括特征选择、决策树的生成和剪枝。特征选择是指从所有特征中选择一个最优的特征作为当前节点的划分属性,常用的特征选择方法有信息增益、信息增益比、基尼系数等。决策树的生成是递归地将数据集划分为子集,直到满足停止条件为止,通常情况下是节点中的样本属于同一类别或达到最大深度。
4.12决策树算法流程
1.收集数据:可以通过网上下载数据集,或者根据经验得到数据集
2.准备数据:树构造算法只适用于标称型数据,因此数值型数据必须离散化
3.分析数据:可以使用任何方式,构造树完成之后,应该检查构造完成的树是否符合预期
4.训练算法:通过不同算法,例如id3或者cart算法得到根据数据集所生成的决策树
5.测试算法:通过模型评估的方式,利用生成好的决策树进行类别的预测,计算生成的决策树预测类别的准确率
6.优化算法:根据模型评估得到的准确率,适当调整算法,对决策树算法进行优化
4.2决策树的构建
4.21数据集的准备
在本次决策树实验中,需要通过三个特征来预测数据的类别,三个标签分别为:shape(形状),color(颜色),taste(口感),每个特征分别有两个取值。
shape 0:水果形状为椭圆形 1:水果形状为圆形
color 0: 水果颜色为绿色 1:水果颜色为红色
taste 0: 水果口感不脆 1:水果口感较脆
并且将数据集的前五个数据作为训练集,将后五个数据作为测试集用于模型评估
图一:标签准备
图二:数据集准备
4.22导入数据集
根据上述数据集和标签的路径,分别获取数据集和标签集
def createData():
data_set_filepath = "D:\Py_project\DecisionTree_project\dataSet.txt"
data_labels_filepath = "D:\Py_project\DecisionTree_project\labels.txt"
dataSet = []
labels = []
with open(data_set_filepath,"r",encoding="UTF-8") as f:
for line in f:
list = line.split(",")
data_list = []
data_list.append(int(list[0]))
data_list.append(int(list[1]))
data_list.append(int(list[2]))
data_list.append((list[3].split("\n"))[0])
dataSet.append(data_list)
with open(data_labels_filepath,"r",encoding="UTF-8") as f:
for line in f:
labels.append(line.split(",")[0])
labels.append(line.split(",")[1])
labels.append(((line.split(","))[2].split("\n"))[0])
return dataSet,labels
4.23构建决策树
划分数据
#按照给定特征划分数据集
def splitDataSet(dataSet, axis, value):#数据集,特征索引,特征的取值
retDataSet = []
for featVec in dataSet:
if featVec[axis] == value:
# 以特征值为中心划分出两个列表,分别添加的列表retDataSet中
# 便于在递归构建决策树,不断把每次得到的最好的数据划分的特征并以此划分
reducedFeatVec = featVec[:axis]
reducedFeatVec.extend(featVec[axis+1:])
retDataSet.append(reducedFeatVec)
return retDataSet
在构建决策树时,需要通过每个特征地不同取值对数据集进行划分,得到不同的列表,并根据不同算法构建决策树,因此在通过特征值对数据集进行划分的时候,需要使用到该函数,以特征值为中心将列表划分成两个列表再合并,并返回划分后的列表。
id3算法构建决策树
id3算法的获取最优划分特征
# Id3算法选择最好的数据划分方式
def chooseBestFeatureToSplit1(dataSet):
# 统计特征数量,-1为了去掉标签
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]
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
# 如果划分后的信息熵更大,就说明以特征i来划分更合适
print('按第'+str(i)+'特征划分所得到的信息增益为:'+str(infoGain))
if (infoGain > bestInfoGain):
bestInfoGain = infoGain
bestFeature = i
return bestFeature
首先计算出初始数据集的香农熵,通过遍历训练集的每一个列表的每一个特征,并根据该特征的不同取值,对数据集进行划分,计算划分后的香农熵,并通过初始的香农熵和划分后的香农熵的差,计算信息增益,遍历完所有特征后,得到以该特征划分后的最大的信息增益,将其返回。
香农熵的计算公式
id3算法构建决策树
def Id3CreateTree(dataSet,labels):
classList = [example[-1] for example in dataSet]
# 如果数据集中所有类别完全相同,则无需划分
if classList.count(classList[0]) == len(classList):
return classList[0]
# 如果只有一个特征值,也无需划分,返回出现次数最多的类别
if len(dataSet[0]) == 1:
return majorityCnt(classList)
# 得到最优的数据划分方式
bestFeat = chooseBestFeatureToSplit1(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[:]
# 递归构建决策树
myTree[bestFeatLabel][value] = Id3CreateTree(splitDataSet(dataSet, bestFeat, value),subLabels)
return myTree
对于合理的数据集(不合理的根据情况的不同进行返回,例如类别完全相同,或是只有一个特征值)通过每次获取其最优的划分特征,得到其所对应的标签,递归构建成决策树
id3构建的决策树结果
图三:通过id3算法构建决策树运行结果
cart算法构建决策树
cart算法获取最优划分特征
def chooseBestFeatureToSplit2(dataSet):
numFeatures = len(dataSet[0]) - 1
bestGini = 999999.0
bestFeature = -1
for i in range(numFeatures):
featList = [example[i] for example in dataSet]
uniqueVals = set(featList)
gini = 0.0
for value in uniqueVals:
subDataSet = splitDataSet(dataSet,i,value)
prob = len(subDataSet)/float(len(dataSet))
subp = len(splitDataSet(subDataSet,-1,'apple'))/float(len(subDataSet))
gini += prob * (1.0 - pow(subp,2) - pow(1-subp,2))
# 基尼指数选择越小越好
print("Cart算法中第"+str(i)+"个特征的基尼值为:"+str(gini))
if(gini < bestGini):
bestGini = gini
bestFeature = i
return bestFeature
首先通过对不同特征进行数据集的划分,计算划分后的基尼指数,基尼指数的计算是通过,获取划分后‘apple’标签和‘pear’标签的出现的比例,再通过如下公式进行计算:
通过cart算法构建决策树,基尼指数的值并不像id3算法的信息增益一样,基尼指数是越小越好,因此得到划分后最小的 特征值进行返回
cart算法构建决策树
def CartCreateTree(dataSet,labels):
classList = [example[-1] for example in dataSet]
# 如果数据集中所有类别完全相同,则无需划分
if classList.count(classList[0]) == len(classList):
return classList[0]
# 如果只有一个特征值,也无需划分,返回出现次数最多的类别
if len(dataSet[0]) == 1:
return majorityCnt(classList)
# 得到最优的数据划分方式
bestFeat = chooseBestFeatureToSplit2(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[:]
# 递归构建决策树
myTree[bestFeatLabel][value] = CartCreateTree(splitDataSet(dataSet, bestFeat, value),subLabels)
return myTree
与利用id3算法一样,通过cart算法构建决策树,只需要将获取最优划分特征的函数改为cart算法的最优划分特征函数即可
cart算法构建决策树结果
图三:通过cart算法构建决策树运行结果
两种算法构建的决策树的差异
首先,在本次构建决策树的实验中,由于数据集的给定会导致构建的决策树不同,然而本次通过两种算法得到的决策树是一致的。
通过id3算法构建决策树:
通过id3算法构建决策树,首先需要获取最优的划分特征,对于该算法构建决策树,最优的划分特征就是通过循环遍历,得到根据某个特征的来划分,通过香农熵的计算公式会得到不同的香农熵,通过该香农熵与原始数据集的香农熵的差得到信息最大的信息增益,通过上述方式得到第一个最优特征,并存入字典中,接着再从剩下特征中通过上述的方式依次获取最优的划分方式,一直递归,直到得到完整的一颗决策树为止。
香农熵的计算公式
通过cart算法构建决策树:
通过cart算法构建决策树,与id算法一致,都需要得到最优的划分特征,不同的是,cart算法得到最优的划分特征,是通过计算最小的基尼指数,同样是通过遍历,遍历每一个特征,并根据这些特征进行数据集的划分,得到划分后数据集的不同类别出现的比例,并根据基尼指数的计算公式得到最小的基尼指数所对应的特征。并根据该方法递归得到一颗完整的决策树。
基尼指数计算公式
4.3对构建的决策树进行评估
4.31数据准备
在对构建的决策树进行评估时,用已经划分好的数据集,即用测试集来测试,用训练集所构建的决策树的准确性。
# 划分训练集和测试集
def splitData():
dataSet,labels = createData()
train_set = dataSet[:5]
test_set = dataSet[5:]
return test_set,train_set,labels
4.32模型评估
预测类别
# 遍历生成的决策树,得到划分特征的顺序
def traverse_tree(tree,myTest,value_list):
for key, value in tree.items():
if isinstance(value, dict):
if(key == 'taste' or key == 'shape' or key == 'color'):
value_list.append(key)
traverse_tree(value,myTest,value_list)
return value_list
# 通过决策树,根据传入的测试数据进行类别的预测
def tree_predict(tree,newTest,count):
if(type(tree) == dict):
# 如果value是字典就递归,不是则表示为字符串'apple','pear'可以直接返回
for key,value in tree.items():
if isinstance(value,dict):
return tree_predict(value[newTest[count]],newTest,count+1)
else:
return tree
首先遍历生成的决策树(由于两种算法得到的结果是一致的,因此任选一种构建好的决策树进行测试),因为初始标签集和决策树的划分特征顺序不一定相同,因此先获取划分特征的顺序,并将需要预测的数据,调整其特征的顺序(将顺序排序得与决策树构建的特征顺序一致)。再将其传入预测函数中,如果根据该标签所对应的结果是字符串说明得到结果将其返回,如果不是字符串而是字典,说明需要继续判断,继续递归得到标签。
准确率预测
# 通过构建出来的决策树进行数据的类别预测
def test_predict(tree,test_set,labels):
correct_predict = 0
total_count = len(test_set)
accuracy = 0
# 测试数据
for l in test_set:
value_list = []
myTest = l[:3]
correct_label = l[3]
newTest = [-1,-1,-1]
count = 0
# value_list用于存储特征的顺序
value_list = traverse_tree(tree,myTest,value_list)
# 将测试数据根据特征循序进行排放
for i in range(len(value_list)):
newTest[value_list.index(labels[i])] = myTest[i]
predict = tree_predict(tree,newTest,count)
if(predict == correct_label):
correct_predict += 1
print("测试数据"+str(myTest)+"根据决策树预测的结果是:"+predict+" 预测正确")
accuracy = correct_predict/total_count
print("模型准确率"+str(accuracy))
首先通过myTest得到每一个测试数据,调整其特征顺序后,传入预测函数进行预测,并将预测的结果和该数据本身的类别相比较,如果两者一致,那么说明该数据预测正确。
评估结果
图四:模型评估结果
4.4实验总结
在本次实验中,理解和学习到了决策树的基本思想和构建方式,决策树是一种常见的机器学习算法,用于分类和回归任务,在决策树的训练过程中,算法会根据训练数据选择最佳的特征来进行划分,直到达到预定义的停止条件为止,决策树的优点包括易于理解和解释,可以处理数值型和类别型数据,能够自动选择特征,对缺失值不敏感等。
通过两种不同的方式实现了决策树的构建,对于id3算法实现决策树的构建,,首先需要获取最优的划分特征,对于该算法构建决策树,最优的划分特征就是通过循环遍历,得到根据某个特征的来划分,会得到不同的香农熵,通过该香农熵与原始数据集的香农熵的差得到信息最大的信息增益,通过上述方式得到第一个最优特征,并存入字典中,接着再从剩下特征中通过上述的方式依次获取最优的划分方式,一直递归,直到得到完整的一颗决策树为止。
对于cart算法实现决策树的构建,与id算法一致,都需要得到最优的划分特征,不同的是,cart算法得到最优的划分特征,是通过计算最小的基尼指数,同样是通过遍历,遍历每一个特征,并根据这些特征进行数据集的划分,得到划分后数据集的不同类别出现的比例,并根据基尼指数的计算公式得到最小的基尼指数所对应的特征。并根据该方法递归得到一颗完整的决策树。
通过两种不同方式实现决策树的构建后,由于给定的数据集的差异,可能会出现两颗相同或者不同的决策树,本次实验通过两种不同的方式得到了相同的决策树,因为在最开始准备数据集的时候,就通过手动模拟的方式,自行通过不同特征的不同取值得到了一颗简单的决策树,因此实验在构建决策树的时候,由于已经准备好的数据集根据每个特征划分较为明显(信息增益较大或基尼指数较小)因此得到了一颗相同的树。
最后通过留出法的方式对决策树进行模型评估,将数据集分为训练集和测试集,通过训练集构建的决策树用测试集测试时都能得到其对应的标签,因此该决策树地构建是相对合理且有意义的。