数挖实验课的时候,老师让我们自己动手写决策树,还不能调用 scikit-learn 包,顿时感觉有点难(可能是我比较菜),想到网上找一找看能不能学一下,可是许多大佬都是调包做的,剩下的大佬们写的代码也无法短时间去理解,于是我只好照着书本以及参考网上大佬们的思想来自己动手写一写。

实验题目是这样的,要求使用 ID3 算法在鸢尾花卉 Iris 数据集上训练出一个基本的决策树模型,拿到题目时我是一点思路都没有的,以前从来没有自己动手实现过机器学习的那些经典算法(倒是拿着西瓜书匆匆地翻过一遍,现在真的是后悔了),所以只能一步一步来了,接下来我讲一讲我的思路,或许可以给朋友们提供一些参照。

步骤

  • 1 数据集的下载与处理
  • 2 随机划分数据集
  • 3 计算信息熵
  • 4 计算划分属性的信息增益
  • 5 递归生成决策树
  • 6 模型的预测与评估
  • 7 组合模块形成完整代码

1 数据集的下载与处理

首先,在网上找到鸢尾花卉数据集,一般像这样经典的数据集网上都是公开的可以直接下载,这里给出数据集网站地址:http://archive.ics.uci.edu/ml/datasets/Iris,根据以下标示下载数据集。


下载好数据后,我们发现它是一个文本格式的文件,我们来观察一下这个数据集,这个文件里存储了鸢尾花卉的四个属性——花萼长度(sepal length)、花萼宽度(sepal width)、花瓣长度(petal length)和花瓣宽度(petal width),以及它们的三个类别(class)——山鸢尾(Iris Setosa),变色鸢尾(Iris Versicolor)和维吉尼亚鸢尾(Iris Virginica)。以我们看到的第一个样本为例,“5.1,3.5,1.4,0.2”分别代表样本的四个属性的取值,“Iris-setosa”代表当前样本的类别,该数据集共有 150 个这样的样本,且每类样本分别有 50 个数据:

一般文本格式的不太好处理,所以最好转成 CSV 格式的文件,然后用 pandas 库处理,较为直接的办法就是重命名为 CSV 格式的文件,然后使用 Excel 打开文件,给每一列添加一个列名,也可以通过代码折腾一下来实现(对了,Windows 系统上面跑的话路径需要注意改一下):

import numpy as np
import pandas as pddef dataProcess(source_fpath, target_fpath):with open(source_fpath) as source_f:sample_list = []for line in source_f:content = line.strip().split(",")sample_list.append(np.array(content))csvdf = pd.DataFrame(sample_list)csvdf.columns = ["SepalLength", "SepalWidth", "PetalLength", "PetalWidth", "Class"]csvdf.to_csv(target_fpath, index=0) # 保存为 CSV 文件时不保留行索引if __name__ == "__main__":source_fpath = "iris.data"target_fpath = source_fpath.replace("data", "csv")dataProcess(source_fpath, target_fpath)

使用 pandas 读取刚刚生成的 CSV 文件,以 DataFrame 形式打印出来:

dataset = pd.read_csv("iris.csv")
print(datset)

我这里是把五个列名分别命名为 SepalLength、SepalWidth、PetalLength、PetalWidth 以及 Class 的,大家可以根据自己的命名喜好修改一下:

2 随机划分数据集

现在,数据集准备好了,我们要开始划分数据集来为模型训练、预测以及评估做准备,一般是以 8:2 的比例随机划分出训练集和测试集(其实也可以使用交叉验证,不过我觉得交叉验证框架主要是用来选取超参数的,单纯地被用来评估模型我个人感觉有点浪费了,而且加上我也有点懒,所以就没有整上去了,感兴趣的朋友们可以试一下)。

一开始,我是像下面这样直接划分数据集的:

def datasetPartitioning(dataset):# 打乱索引index = [i for i in range(len(dataset))]np.random.seed(1) # 设置随机种子为 1np.random.shuffle(index)# 以 8:2 划分训练集和测试集traindatasetlen = int(len(dataset) * 0.8)traindataset = dataset.loc[index[:traindatasetlen]]testdataset = dataset.loc[index[traindatasetlen:]]traindataset = traindataset.reset_index() # 重置索引testdataset = testdataset.reset_index() # 重置索引return traindataset, testdataset

但是,在后面进行模型评估的时候发现模型精确度不是太高,比如说设置随机数种子为 1 时,精确度都不到 0.9,不过我把随机数种子修改为其他值时,比如说,随机数种子设置为 2 时,精确度会在 0.95 以上,我就在想为什么设置不同随机数种子的模型之间的差别会像这样子比较大,讲道理,差别肯定是有的,但一般不会相差太大的,出现我这样的情况肯定有一些原因,后来在调试当中我发现,有的随机数种子会导致数据集划分的不均匀:

我们可以看到随机数种子为 1 时,训练数据中不同类别的数量不如随机数种子为 2 时均匀,这也导致模型精确度的较大差别:

因此,不能单纯地直接划分数据集,需要做一些改变,在这里,我选择了类似分层抽样的方法,因为我是按 8:2 划分数据集,因此每个类别随机划分 40 个样本给训练集,10 个样本给测试集,这样子既达到了随机划分数据集的目的,又避免了数据集划分的不均匀:

# 将数据集划分为训练集和测试集
def subdatasetPartitioning(dataset):# 打乱索引index = [i for i in range(len(dataset))]np.random.seed(1)np.random.shuffle(index)# 以 8:2 划分训练集和测试集traindatasetlen = int(len(dataset) * 0.8)traindataset = dataset.loc[index[:traindatasetlen]]testdataset = dataset.loc[index[traindatasetlen:]]return traindataset, testdataset# 类似于分层抽样,每个类别随机划分同样个数的样本给训练集
def datasetPartitioning(dataset):traindataset_list = []testdataset_list = []for i in range(3):subdataset = dataset.loc[i * 50 : (i + 1) * 50 - 1]subdataset = subdataset.reset_index() # 重置索引subtraindataset, subtestdataset = subdatasetPartitioning(subdataset)traindataset_list.append(subtraindataset)testdataset_list.append(subtestdataset)traindataset = pd.concat(traindataset_list, ignore_index=True) # 合并 DataFrame,ignore_index=True 表示忽略原索引,类似重置索引的效果testdataset = pd.concat(testdataset_list, ignore_index=True)return traindataset, testdataset

给大家看一看这样更改后的模型精确度,可以看到效果非常好,我这个展示的是随机数种子设为 1 的情况,其他的大家也可以试一下,我觉得结果不会像刚才那样差别巨大了:

3 计算信息熵

之前都是关于数据集处理的,到这里就要正式接触 ID3 算法了,关于 ID3 算法以及信息熵和信息增益的概念,我相信看到这篇博文的同学都基本学过,没学过网上也都有大量的教程可以去看看(这句话写的我自己都恶心了,相信朋友们看到也恶心,什么叫基本都学过,什么叫过网上都有大量的教程可以去看看,所以如果时间充裕的话,我之后会写一个关于决策树的博客,然后把链接放在这里,让大家看的舒心一点,不过有一点是真的,网上写的都是别人的,不管看书还是看教程只有自己学到的才是自己的)。

关于信息熵,大家主要记得一点就差不多可以了,就是如果一个数据集的信息熵越小,说明这个数据集的纯度越高,数据集中样本越有可能属于同一类别,在这里,先给出信息熵的公式:

E n t ( D ) = − ∑ k = 1 ∣ Y ∣ p k l o g 2 p k 其 中 , D 表 示 当 前 数 据 集 , p k 表 示 数 据 集 D 中 第 k 类 样 本 所 占 的 比 例 ( k = 1 , 2 , … , ∣ Y ∣ ) , ∣ Y ∣ 是 类 别 数 Ent(D) = - \sum_{k=1}^{|Y|} p_k log_2 p_k \\ 其中,D ~ 表示当前数据集,p_k ~ 表示数据集 ~ D ~ 中第 ~ k ~ 类样本所占的比例(k = 1, 2, \ldots , |Y|),|Y| ~ 是类别数 Ent(D)=−k=1∑∣Y∣​pk​log2​pk​其中,D 表示当前数据集,pk​ 表示数据集 D 中第 k 类样本所占的比例(k=1,2,…,∣Y∣),∣Y∣ 是类别数

然后,我们根据上面的公式写出计算信息熵的代码,给定数据集,返回其信息熵:

# 给定数据集,计算并返回其信息熵
def informationEntropy(dataset):entropysum = 0category_list = list(dataset["Class"])for category in set(dataset["Class"]):pk = category_list.count(category) / len(dataset)entropysum += pk * math.log(pk, 2)return (-1) * entropysum

4 计算划分属性的信息增益

我们已经知道,数据集的纯度和信息熵息息相关,而 ID3 算法就是基于信息熵,该算法想要达到一个目的,就是将数据集划分的越来越纯,让同一类别的样本尽可能在一起,以达到分类的目的,在决策树当中就是让子结点的信息熵加起来要小于父节点的信息熵,信息熵的减少量就是信息增益,ID3 算法就是要让信息增益越大越好,这里给出属性离散情况下的信息增益公式:

G a i n ( D , a ) = E n t ( D ) − ∑ v = 1 ∣ V ∣ ∣ D v ∣ ∣ D ∣ E n t ( D v ) 其 中 , D 为 当 前 数 据 集 , a 为 离 散 属 性 , 有 V 个 可 能 的 取 值 { a 1 , a 2 , … , a V } , 而 D v 即 是 数 据 集 D 中 在 属 性 a 上 取 值 为 a v 的 样 本 , 对 应 划 分 出 的 第 V 个 分 支 节 点 Gain(D , a) = Ent(D) - \sum_{v=1}^{|V|} \frac {|D^v|} {|D|} Ent(D^v) \\ 其中,D~为当前数据集,a ~为离散属性,有~V~个可能的取值~\{a^1,a^2,\ldots,a^V\},\\ 而~D^v ~ 即是数据集~D~中在属性~a~上取值为~a^v~的样本,对应划分出的第~V~个分支节点 Gain(D,a)=Ent(D)−v=1∑∣V∣​∣D∣∣Dv∣​Ent(Dv)其中,D 为当前数据集,a 为离散属性,有 V 个可能的取值 {a1,a2,…,aV},而 Dv 即是数据集 D 中在属性 a 上取值为 av 的样本,对应划分出的第 V 个分支节点

同一个数据集 D,划分属性 a 不同,对应的信息增益也不一样,我们要做的就是找出信息增益最大的划分属性,然后根据它划分数据集,生成分支节点,这将在下一步进行分析,现在根据上面的公式编写代码,给定数据集和离散类型属性,返回根据该属性划分数据集得到的信息增益:

# 给定数据集和离散类型属性,计算并返回根据该属性划分数据集得到的信息增益
def informationDiscreteGain(dataset, attribute):entropy = informationEntropy(dataset)entropysum = 0attribute_value_list = list(dataset[attribute])for attribute_value in set(dataset[attribute]):weight = attribute_value_list.count(attribute_value) / len(dataset)entropysum += weight * informationEntropy(dataset[dataset[attribute] == attribute_value])return entropy - entropysum

如果数据集中的属性是离散的,那么这一步基本就做完了,但是我们现在的数据集是鸢尾花卉数据集,它的数据的四个属性均是连续属性,所以我们要做不同的处理。

由于连续属性的可取值数目不再有限,因此,不能直接根据连续属性的可取值来对结点进行划分,此时,我们需要使用连续属性离散化技术,最简单的策略是采用二分法(bi-partition)对连续属性进行处理。

那么二分法是如何操作的呢?二分法采用的是基于划分点的方法,通过选取划分点 t 将数据集 D 分为子集 Dt 和 Dt+,其中 Dt 包含那些在属性 a 上取值不大于 t 的样本,而 Dt+ 则包含那些在属性 a 上取值大于 t 的样本。

那么如何选取划分点 t 呢?给点数据集 D 和连续属性 a,假定 a 在 D 上出现了 n 个不同的取值,将这些值从小到大进行排序,记为 { a1, a2, …, an },我们准备一个包含 n - 1 个元素的候选划分点集合:

T a = { a i + a i + 1 2 ∣ 1 ⩽ i ⩽ n − 1 } T_a = \{ \frac {a^i + a^{i+1}} 2 | 1 \leqslant i \leqslant n - 1 \} Ta​={2ai+ai+1​∣1⩽i⩽n−1}

也就是说,把每对相邻属性取值 ai 与 ai+1 的均值即区间 [ai, ai+1) 的中位点作为候选划分点,然后,我们就可以像考察离散值一样来考察这些划分点,选取最优的划分点来进行数据集的划分,接着,我们需要对信息增益公式改造一下,让其能处理连续属性:

G a i n ( D , a ) = max ⁡ t ∈ T a G a i n ( D , a , t ) = max ⁡ t ∈ T a { E n t ( D ) − ∑ λ ∈ { − , + } ∣ D t λ ∣ ∣ D ∣ E n t ( D t λ ) } 其 中 , G a i n ( D , a , t ) 是 数 据 集 D 基 于 划 分 点 t 二 分 后 的 信 息 增 益 Gain(D, a) = \max_{t \in T_a} Gain(D, a, t) = \max_{t \in T_a} \{ Ent(D) - \sum_{\lambda \in \{ -, + \} } \frac {| D_t^{\lambda} |} {| D |} Ent(D_t^{\lambda}) \} \\ 其中,Gain(D, a, t) 是数据集 ~D~ 基于划分点 ~t~ 二分后的信息增益 Gain(D,a)=t∈Ta​max​Gain(D,a,t)=t∈Ta​max​{Ent(D)−λ∈{−,+}∑​∣D∣∣Dtλ​∣​Ent(Dtλ​)}其中,Gain(D,a,t)是数据集 D 基于划分点 t 二分后的信息增益

我们认为使 Gain(D, a, t) 达到最大的划分点即为最优划分点,好了,既然思路比较清晰了,那就继续开始编写代码,给定数据集和连续类型属性,返回根据该属性划分数据集得到的信息增益以及该属性上的最优划分点:

# 给定数据集和连续类型属性,计算根据该属性划分数据集得到的信息增益,并返回在该属性上的划分点以及信息增益
def informationContinuousGain(dataset, attribute):entropy = informationEntropy(dataset)attribute_value_list = sorted(set(dataset[attribute]))if len(attribute_value_list) == 1:thresholds = [attribute_value_list[0]]else:thresholds = [(attribute_value_list[i] + attribute_value_list[i + 1]) / 2 for i in range(len(attribute_value_list) - 1)] # 候选划分点集合threshold_entropysum_dict = {}for threshold in thresholds:lessthreshold = dataset[dataset[attribute] <= threshold]morethreshold = dataset[dataset[attribute] > threshold]lessweight = len(lessthreshold) / len(dataset)moreweight = len(morethreshold) / len(dataset)entropysum = lessweight * informationEntropy(lessthreshold) + moreweight * informationEntropy(morethreshold)threshold_entropysum_dict[threshold] = entropysumthreshold_entropysum_sorted = sorted(threshold_entropysum_dict.items(), key=lambda item: item[1])minentropysum_threshold = threshold_entropysum_sorted[0][0]minentropysum = threshold_entropysum_sorted[0][1]return minentropysum_threshold, entropy - minentropysum

5 递归生成决策树

信息熵和信息增益模块都已经分别处理好了,现在到了最重要的一环了,我们要开始根据训练集学习并构造决策树,决策树学习的目的是为了产生一棵泛化能力强,即处理未见示例能力强的决策树,其基本流程遵循简单且直观的分而治之(divide-and-conquer)策略,在这里给出西瓜书上的一张图:

这张图我觉得脉络非常清晰,我当时学习的时候还根据这张图做了一张流程图,这里也给大家展示一下(我的这张图曾经被吐槽画得太 low 了,还是用 WPS 画得,我的同学们都觉得太捞了这张图,大家看到不要笑蛤):

现在,决策树构造流程有了,那应该构造成什么样呢,如果真的用数据结构弄出树形结构,我觉得有点麻烦,所以我采取了一个大佬的博客上的方法,构建一个决策树字典,这样即方便又直观,这里给出大佬博客地址:https://www.cnblogs.com/further-further-further/p/9429257.html,大家可以看一下,我觉得写得非常好。

准备工作已经做好了,现在开始编写代码,根据数据集和属性列表递归生成决策树结点,返回决策树模型字典:

# 计算数据集中类数量最多的类
def maxNumOutcome(dataset):category_list = list(dataset["Class"])category_dict = {}for category in set(dataset["Class"]):category_dict[category] = category_list.count(category)category_sorted = sorted(category_dict.items(), key=lambda item: item[1], reverse=True)return category_sorted[0][0]# 递归生成决策树结点,返回决策树模型字典
def treeNodeGenerate(dataset, attribute_list):if len(set(dataset["Class"])) == 1:node = list(set(dataset["Class"]))[0]elif len(attribute_list) == 0 or sum([len(set(dataset[attribute])) - 1 for attribute in attribute_list]) == 0:node = maxNumOutcome(dataset)else:attribute_gain_dict = {}for attribute in attribute_list:threshold, attribute_gain = informationContinuousGain(dataset, attribute)attribute_gain_dict[attribute] = threshold, attribute_gainattribute_gain_sorted = sorted(attribute_gain_dict.items(), key=lambda item: item[1][1], reverse=True)maxgain_attribute = attribute_gain_sorted[0][0]maxgain_threshold = attribute_gain_sorted[0][1][0]son_node_attribute_list = attribute_list.copy()son_node_attribute_list.remove(maxgain_attribute)left_node_dataset = dataset[dataset[maxgain_attribute] <= maxgain_threshold]if len(left_node_dataset) == 0:leftnode = maxNumOutcome(dataset)else:leftnode = treeNodeGenerate(left_node_dataset, son_node_attribute_list)right_node_dataset = dataset[dataset[maxgain_attribute] > maxgain_threshold]if len(right_node_dataset) == 0:rightnode = maxNumOutcome(dataset)else:rightnode = treeNodeGenerate(right_node_dataset, son_node_attribute_list)if leftnode == rightnode:node = leftnodeelse:node = {}node[(maxgain_attribute, maxgain_threshold)] = {"<=":leftnode, ">":rightnode}return node

这是我随机数种子设为 1 时在训练集上训练出来的决策树模型,大家要注意,不同随机数种子划分出的训练集上构造的决策树会有一定程度不同:

6 模型的预测与评估

决策树模型训练好了,到了最后一个版块了,我们要使用训练好的决策树模型去预测测试集上的样本,并对预测结果进行评估,这两个模块代码都比较好写,前者是通过递归得出样本预测结果,后者我只写了一个简单的精确度评估指标:

# 预测一条数据的结果
def predictOne(tree_train_model, testdata):if type(tree_train_model) == str:predict_value = tree_train_modelelif type(tree_train_model) == dict:key = list(tree_train_model)[0]if testdata[key[0]] <= key[1]:son_tree_train_model = tree_train_model[key]["<="]else:son_tree_train_model = tree_train_model[key][">"]predict_value = predictOne(son_tree_train_model, testdata)return predict_value# 进行模型预测,返回预测结果列表
def predict(tree_train_model, testdataset):predict_list = []for i in range(len(testdataset)):predict_value = predictOne(tree_train_model, testdataset.loc[i])predict_list.append((testdataset.loc[i]["Class"], predict_value))return predict_list

这是我的预测结果列表,大家参照着看一下就好:

然后对预测模型进行精确度评估:

# 对预测模型进行精确度评估
def predictAccuracy(predict_list):predict_true_num = 0for bigram in predict_list:if bigram[0] == bigram[1]:predict_true_num += 1accuracy = predict_true_num / len(predict_list)return accuracy

得出模型评估结果,这个大家刚才也已经看到了,不可思议的 1.0:

7 组合模块形成完整代码

最后,将前面的所有模块进行组合,并添加 main 函数,得到完整代码:

import pandas as pd
import math, sys, os
import numpy as np# 给定数据集,计算并返回其信息熵
def informationEntropy(dataset):entropysum = 0category_list = list(dataset["Class"])for category in set(dataset["Class"]):pk = category_list.count(category) / len(dataset)entropysum += pk * math.log(pk, 2)return (-1) * entropysum# 给定数据集和离散类型属性,计算并返回根据该属性划分数据集得到的信息增益
def informationDiscreteGain(dataset, attribute):entropy = informationEntropy(dataset)entropysum = 0attribute_value_list = list(dataset[attribute])for attribute_value in set(dataset[attribute]):weight = attribute_value_list.count(attribute_value) / len(dataset)entropysum += weight * informationEntropy(dataset[dataset[attribute] == attribute_value])return entropy - entropysum# 给定数据集和连续类型属性,计算根据该属性划分数据集得到的信息增益,并返回在该属性上的划分点以及信息增益
def informationContinuousGain(dataset, attribute):entropy = informationEntropy(dataset)attribute_value_list = sorted(set(dataset[attribute]))if len(attribute_value_list) == 1:thresholds = [attribute_value_list[0]]else:thresholds = [(attribute_value_list[i] + attribute_value_list[i + 1]) / 2 for i in range(len(attribute_value_list) - 1)] # 候选划分点集合threshold_entropysum_dict = {}for threshold in thresholds:lessthreshold = dataset[dataset[attribute] <= threshold]morethreshold = dataset[dataset[attribute] > threshold]lessweight = len(lessthreshold) / len(dataset)moreweight = len(morethreshold) / len(dataset)entropysum = lessweight * informationEntropy(lessthreshold) + moreweight * informationEntropy(morethreshold)threshold_entropysum_dict[threshold] = entropysumthreshold_entropysum_sorted = sorted(threshold_entropysum_dict.items(), key=lambda item: item[1])minentropysum_threshold = threshold_entropysum_sorted[0][0]minentropysum = threshold_entropysum_sorted[0][1]return minentropysum_threshold, entropy - minentropysum# 计算数据集中类数量最多的类
def maxNumOutcome(dataset):category_list = list(dataset["Class"])category_dict = {}for category in set(dataset["Class"]):category_dict[category] = category_list.count(category)category_sorted = sorted(category_dict.items(), key=lambda item: item[1], reverse=True)return category_sorted[0][0]# 递归生成决策树结点,返回决策树模型字典
def treeNodeGenerate(dataset, attribute_list):if len(set(dataset["Class"])) == 1:node = list(set(dataset["Class"]))[0]elif len(attribute_list) == 0 or sum([len(set(dataset[attribute])) - 1 for attribute in attribute_list]) == 0:node = maxNumOutcome(dataset)else:attribute_gain_dict = {}for attribute in attribute_list:threshold, attribute_gain = informationContinuousGain(dataset, attribute)attribute_gain_dict[attribute] = threshold, attribute_gainattribute_gain_sorted = sorted(attribute_gain_dict.items(), key=lambda item: item[1][1], reverse=True)maxgain_attribute = attribute_gain_sorted[0][0]maxgain_threshold = attribute_gain_sorted[0][1][0]son_node_attribute_list = attribute_list.copy()son_node_attribute_list.remove(maxgain_attribute)left_node_dataset = dataset[dataset[maxgain_attribute] <= maxgain_threshold]if len(left_node_dataset) == 0:leftnode = maxNumOutcome(dataset)else:leftnode = treeNodeGenerate(left_node_dataset, son_node_attribute_list)right_node_dataset = dataset[dataset[maxgain_attribute] > maxgain_threshold]if len(right_node_dataset) == 0:rightnode = maxNumOutcome(dataset)else:rightnode = treeNodeGenerate(right_node_dataset, son_node_attribute_list)if leftnode == rightnode:node = leftnodeelse:node = {}node[(maxgain_attribute, maxgain_threshold)] = {"<=":leftnode, ">":rightnode}return node# 预测一条数据的结果
def predictOne(tree_train_model, testdata):if type(tree_train_model) == str:predict_value = tree_train_modelelif type(tree_train_model) == dict:key = list(tree_train_model)[0]if testdata[key[0]] <= key[1]:son_tree_train_model = tree_train_model[key]["<="]else:son_tree_train_model = tree_train_model[key][">"]predict_value = predictOne(son_tree_train_model, testdata)return predict_value# 进行模型预测,返回预测结果列表
def predict(tree_train_model, testdataset):predict_list = []for i in range(len(testdataset)):predict_value = predictOne(tree_train_model, testdataset.loc[i])predict_list.append((testdataset.loc[i]["Class"], predict_value))return predict_list# 对预测模型进行精确度评估
def predictAccuracy(predict_list):predict_true_num = 0for bigram in predict_list:if bigram[0] == bigram[1]:predict_true_num += 1accuracy = predict_true_num / len(predict_list)return accuracy# 将数据集划分为训练集和测试集
def subdatasetPartitioning(dataset):# 打乱索引index = [i for i in range(len(dataset))]np.random.seed(1)np.random.shuffle(index)# 以 8:2 划分训练集和测试集traindatasetlen = int(len(dataset) * 0.8)traindataset = dataset.loc[index[:traindatasetlen]]testdataset = dataset.loc[index[traindatasetlen:]]return traindataset, testdataset# 类似于分层抽样,每个类别划分同样个数的样本给训练集
def datasetPartitioning(dataset):traindataset_list = []testdataset_list = []for i in range(3):subdataset = dataset.loc[i * 50 : (i + 1) * 50 - 1]subdataset = subdataset.reset_index() # 重置索引subtraindataset, subtestdataset = subdatasetPartitioning(subdataset)traindataset_list.append(subtraindataset)testdataset_list.append(subtestdataset)traindataset = pd.concat(traindataset_list, ignore_index=True) # ignore_index=True 表示忽略原索引,类似重置索引的效果testdataset = pd.concat(testdataset_list, ignore_index=True)return traindataset, testdatasetif __name__ == "__main__":# 读取数据dataset = pd.read_csv("iris.csv")# 将数据集以 8:2 划分为训练集和测试集(每一类别抽 40 个作训练集)traindataset, testdataset =  datasetPartitioning(dataset)# 使用训练集进行模型训练attribute_list = ["SepalLength", "SepalWidth", "PetalLength", "PetalWidth"]tree_train_model = treeNodeGenerate(traindataset, attribute_list)print("The Dict of Trained Model:")print(tree_train_model, "\n")# 使用训练好的模型在测试集上进行测试得到预测结果predict_list = predict(tree_train_model, testdataset)print("The List of Predicting Outcomes (Actual Label, Predicted Value) :")print(predict_list, "\n")# 对预测结果进行评估print("The Accuracy of Model Prediction: ", predictAccuracy(predict_list))

编写完成后,运行得到最终结果:

好了本文到此就快结束了,以上就是我自己动手写决策树的想法和思路,大家参照一下即可,重要的还是经过自己的思考来编写代码,文章中还有很多不足和不正确的地方,欢迎大家指正(也请大家体谅,写一篇博客真的挺累的,花的时间比我写出代码的时间还要长),我会尽快修改,之后我也会尽量完善本文,尽量写得通俗易懂,毕竟如果我写得大家看不懂那我还不如不写,回家种田算了。

博文创作不易,转载请注明本文地址:https://blog.csdn.net/qq_44009891/article/details/105787894

自己动手写决策树(一)——初步搭建决策树框架相关推荐

  1. 自己动手写操作系统0

    文章目录 自己动手写操作系统0 环境搭建 NASM VirtualBox 添加软盘启动 VMware 开启虚拟机 其他软件 Floppy 自己动手写操作系统0 参考余渊老师写的<自己动手写操作系 ...

  2. 手写代码,简单实现Spring框架

    Java核心编程高阶实战案例:MySpring 本博文通过学习 中国大学MOOC 平台上陈良育老师讲的 Java核心技术(高阶) 课程,因为老师视频中的讲解有些较为简略,于是我自己另外搜集资料,从老师 ...

  3. 小学生都能读懂的区块链原理和术语介绍(故事图文)-引自《从零开始自己动手写区块链》

    本文目录 1.前言 2.中心化 2.1 交易 2.2 数字货币 2.3 复式记账法 2.4 未消费交易输出 2.5 中心化 2.6 区块与区块链 2.6 创世区块 3.去中心化原理 3.1 分布式存储 ...

  4. 【转】ibatis的简介与初步搭建应用

    [转]ibatis的简介与初步搭建应用 一.ibatis的简介 ibatis是什么东西就不介绍了,自己去找谷老师. 这里讲下自己的使用体会.之前自己学过Hibernate,是看尚学堂的视频教学的,看完 ...

  5. java笔记:自己动手写javaEE

    写这篇博客前,我有个技术难题想请教大家,不知道谁有很好的建议,做过互联网的童鞋应该都知道,有点规模的大公司都会做用户行为分析系统,而且有些大公司还会提供专业的用户行为分析解决方案例如:百度分析,goo ...

  6. Django的学习需要掌握的一些基础和初步搭建自己的框架

    一.Django的学习需要掌握的一些基础 第一个需要注意的点:客户端发送过来的数据结构组成: 第二个需要注意的点:动态网页和静态网页 静态网页:用户发送请求,服务端找到对应的静态文件返回给浏览器,静态 ...

  7. 自己动手写一个印钞机 第二章

    2019独角兽企业重金招聘Python工程师标准>>> 作者:阿布? 未经本人允许禁止转载 ipython notebook git版本 目录章节地址: 自己动手写一个印钞机 第一章 ...

  8. 12_信息熵,信息熵公式,信息增益,决策树、常见决策树使用的算法、决策树的流程、决策树API、决策树案例、随机森林、随机森林的构建过程、随机森林API、随机森林的优缺点、随机森林案例

    1 信息熵 以下来自:https://www.zhihu.com/question/22178202/answer/161732605 1.2 信息熵的公式 先抛出信息熵公式如下: 1.2 信息熵 信 ...

  9. 自己动手写CPU(1)五级流水线及CPU第一条指令ori

    自己动手写CPU(1)五级流水线及CPU第一条指令ori 动机 不知为何研一的自由时间突然多起来,可能人一闲下来就容易焦虑吧,hhhhhh.正好之前看到一本<自己动手写CPU>,就按照此书 ...

最新文章

  1. 养成让自己进步的10个习惯
  2. Qt 周立功USBCAN总线上位机
  3. 网络编程在线英英词典之服务器代码框架搭建(二)
  4. Windows沙拉:开机时自动打开NumLock键背后的故事
  5. linux常用小知识点
  6. 香橙派 Ubuntu修改系统时间
  7. 触摸屏计算机技术参数,触摸屏硬件安装—— 触摸屏参数设置
  8. java+springboot学校小卖部超市收银系统maven
  9. ArrayList的三种遍历方法
  10. python函数ppt_如何用 Python 让你的PPT数据动起来
  11. 理科生浪漫java表白代码_数学公式表白-2020理科生专属浪漫表白句子大全
  12. QML改变TextInput或者其它输入框光标颜色
  13. 工资重要还是五险一金重要
  14. 计算Fisher信息之Part(二)
  15. WORD里,如何在同一个文档中为不同页面,设置不同的页眉和页脚
  16. numpy数组和图片互转
  17. 回文树(模板)+ 例题
  18. linux qt 背景图片,《转》qt中添加背景图片(stylesheet)
  19. C语言:递归解决年龄问题(精细版)
  20. qt 工具栏下加文字

热门文章

  1. 解密word找回打开密码
  2. leaflet入门——地图加载(以arcgis服务为例)
  3. EML转PST转换器
  4. C# excel工具栏菜单栏等操作
  5. 对nvidia optimus黑屏与背光调节问题的一点总结
  6. 教导,职业经理人最重要的能力
  7. linux进程与计划任务管理
  8. samsung.android.sdk,Android应用无法在Samsung Galaxy Apps上提交(如何包含Samsung SDK?)
  9. STM32 ADC的规则通道和注入通道有什么区别(转)
  10. idea自带database连接mysql失败问题