机器学习决策树DecisionTree以及python代码实现

  • 1.基本算法原理
  • 2.选择最优特征进行划分
    • 2.1信息增益
    • 2.2信息增益率
    • 2.3基尼系数
  • 4.连续值以及缺失值的处理
    • 4.1连续值的处理
    • 4.2缺失值的处理
  • 5.python代码实现
  • 6.决策树的可视化实现
  • 7.总结
  • 8.回归树及其python实现(2020.11.30更新)
    • 8.1回归树的原理
    • 8.2 python实现

考虑到以后面试会遇到算法原理的知识,所以先学习记录下。
本文参考西瓜书以及mooc上面的视频…,数据集使用西瓜书上的数据。

1.基本算法原理

决策树的基本形状如下:其内部节点为属性(特征),叶节点则为输出类别。

算法的伪代码如下:

def createDecisionTree(dataSet,A):'''dataSet为训练集,A是属性(特征)集'''生成节点nodeif dataSet中的样本都是一个类别C:将node标记为叶节点,类别为C,returnif 当前A中特征已经遍历完:将node标记为叶节点,类别为dataSet所有样本中类别最多的类别,return从A中选出最优划分属性(特征)afor 遍历属性a能取的所有值a_v:为node生成一个分支从dataSet中划分出其属性a取值为a_v的所有样本子集sub_dataSetif sub_dataSet为空:将分支节点设置为叶节点,类别为dataSet中类别最多的类别,returnelse继续递归createDecisionTree(dataSet,A),并将其作为分支节点返回node

其基本思路就是从当前数据集的特征集中选择一个最优的特征,然后根据该特征不同取值进行对数据集进行分支,再将分支的子集重复上面的工作。什么时候结束分支呢?有以下几种情况:(1)特征被使用完;(2)当前分支的数据集都是同一个类别,没必要在进行划分;(3)当前分支对应的数据集为空,不能划分(这种情况主要发生在经过不断的分支之后,分支节点处的数据集不断变小,而当前进行划分所使用的特征的所有取值是根据全集确定的,而变小后的数据集在该特征的部分取值上可能并没有样本能取得该值)。
此外决策树中最核心的部分就是如何选择最优的特征进行划分?

2.选择最优特征进行划分

这里需要先引入一些前导知识:

2.1信息增益

信息熵(information entropy)是度量样本集合不纯度的一项指标,因为熵本来就是体系混乱程度,不确定程度的度量,这里用来表示集合的不纯度。假定集合D中第k类样本所占比例为 p k p_k pk​(k=1,2,3…,n),则D的信息熵定义如下:
E n t ( D ) = − ∑ k = 1 n p k l o g 2 p k Ent(D)=-\sum^{n}_{k=1}p_klog_{2}p_k Ent(D)=−k=1∑n​pk​log2​pk​
信息熵越小,集合D的不纯度越小,也就是集合纯度越高。例如假设D中共有两类样本, 当 p 1 = 0.5 , p 2 = 0.5 时 , E n t ( D ) = 1 当p_1=0.5, p_2=0.5时, Ent(D)=1 当p1​=0.5,p2​=0.5时,Ent(D)=1,而当 p 1 = 0 , p 2 = 1 时 , E n t ( D ) = 0 p_1=0, p_2=1时, Ent(D)=0 p1​=0,p2​=1时,Ent(D)=0,显然第二种情况只有第二类样本,此时纯度最高,信息熵也是更小。

条件熵是表示在一个条件下,集合D的不纯度。假设集合D使用属性 a a a( a = a 1 , a 2 , . . . , a V a=a_1, a_2,...,a_V a=a1​,a2​,...,aV​)进行划分这个条件,则其条件熵定义如下:
E n t ( D ∣ a ) = ∑ v = 1 V p v E n t ( D ∣ a = a v ) = ∑ v = 1 V ∣ D v ∣ ∣ D ∣ E n t ( D v ) Ent(D|a)=\sum^{V}_{v=1}p_vEnt(D|a=a_v)\\ =\sum^{V}_{v=1}\frac{|D^v|}{|D|}Ent(D^v) Ent(D∣a)=v=1∑V​pv​Ent(D∣a=av​)=v=1∑V​∣D∣∣Dv∣​Ent(Dv)
条件熵Ent(D|a)就是集合D对属性a的不同取值划分后的各个子集的信息熵的加权平均,权值就是各个子集在原集合中所占比例 ∣ D v ∣ ∣ D ∣ \frac{|D^v|}{|D|} ∣D∣∣Dv∣​。

信息增益(information gain)表示在一个条件下,集合D的不纯度减少的程度,其定义如下:
G a i n ( D , a ) = E n t ( D ) − E n t ( D ∣ a ) Gain(D,a)=Ent(D)-Ent(D|a) Gain(D,a)=Ent(D)−Ent(D∣a)
E n t ( D ) Ent(D) Ent(D)表示集合D的不存度, E n t ( D ∣ a ) Ent(D|a) Ent(D∣a)表示集合D在使用属性a划分条件下的不纯度,因此 E n t ( D ) − E n t ( D ∣ a ) Ent(D)-Ent(D|a) Ent(D)−Ent(D∣a)就是集合D在使用属性a划分条件下的不纯度减少的程度。信息增益 G a i n ( D , a ) Gain(D,a) Gain(D,a)越大,不纯度减少的越多,那么就可以使用属性a对D进行划分,因此信息增益也是最优划分属性选择的重要指标。

但是信息增益有一个问题,就是对可取值较多的属性有偏好,例如假设训练集D中有一个属性为“编号”,每一个样本“编号”都不同,那么使用该属性划分的信息增益会更大,但是这也有一个严重的问题,就是会产生过拟合,导致模型泛化能力差。

2.2信息增益率

信息增益率的定义如下:
G a i n _ r a t i o ( D , a ) = G a i n ( D , a ) I V ( a ) Gain\_ratio(D,a)=\frac{Gain(D,a)}{IV(a)} Gain_ratio(D,a)=IV(a)Gain(D,a)​
其中,
I V ( a ) = − ∑ v V ∣ D v ∣ ∣ D ∣ l o g 2 ∣ D v ∣ ∣ D ∣ IV(a)=-\sum^{V}_v\frac{|D^v|}{|D|}log_2\frac{|D^v|}{|D|} IV(a)=−v∑V​∣D∣∣Dv∣​log2​∣D∣∣Dv∣​

相比信息增益,信息增益率则是对可取值数目较少的属性有所偏好,因为属性a取值越多的时候,IV(a)就越大,信息增益率就越小。
因此通常是先从候选划分属性中找出信息增益高于平均水平的属性,在从中找出信息增益率最高的的属性作为最优化为属性。

2.3基尼系数

基尼系数的定义如下:
G i n i ( D ) = 1 − ∑ k = 1 n p k 2 Gini(D)=1-\sum^{n}_{k=1}p_k^2 Gini(D)=1−k=1∑n​pk2​
其中n是集合D中样本的类别数目, p k p_k pk​是第k类样本所占比例。直观上基尼系数反映了从数据集D中随机抽取两个样本,其类别标记不一致的概率。因为 p k ∗ p k p_k*p_k pk​∗pk​表示样本类别一致的概率,1减去所有种样本一致的概率之和就是不一致的概率。基尼系数越小,不一致的概率就越小,不纯度就越小。

4.连续值以及缺失值的处理

4.1连续值的处理

因为对与连续值而言取值较多,不可能像离散值那样对于每一个取值就进行划分数据集,而是选取一个最优的划分点将数据集D划分为两部分。其具体做法就是将连续属性a的所有取值从小到大排序得到{ a 1 , a 2 , . . . , a n a^1,a^2,...,a^n a1,a2,...,an},然后基于划分点t将数据集D划分为 D t − D_t^- Dt−​(包含那些在属性a上取值不大于t的样本)和 D t + D_t^+ Dt+​(那些在属性a上取值大于t的样本)。而t在区间 [ a i , a i + 1 ) [a^i, a^{i+1}) [ai,ai+1)中取任何值划分结果都相同,因此可以取中间点进行划分,即t的所有取值集合为 T a = { a i + a i + 1 2 ∣ 1 ≤ i ≤ n } T_a=\{\frac{a^i+a^{i+1}}{2}|1\leq i\leq n\} Ta​={2ai+ai+1​∣1≤i≤n},然后从所有集合中选取信息增益最大的t作为最优化分点,并将这个信息增益作为数据集D使用属性a进行划分的信息增益。

4.2缺失值的处理

在实际应用中以及一些比赛当中,缺失值都是不可避免,对于缺失值的处理方式也有很多,而对于决策树而言,因为在树的内部节点都是对数据集在某个属性a的不同取值进行划分,而对于那些在属性a上取值为空的样本则不能被正常处理。(1)首先需要解决的问题是“如何在属性值缺失的情况下进行划分属性选择?”,具体做法如下:
对于给定数据集D和属性a(取值为 a 1 , a 2 , . . , a n a^1,a^2,..,a^n a1,a2,..,an),令 D ^ \hat{D} D^表示数据集D在属性a上面没有缺失值的样本子集, D ^ v \hat{D}^v D^v表示 D ^ \hat{D} D^中在属性a上取值为 a v a^v av的样本子集, D ^ k \hat{D}_k D^k​表示 D ^ \hat{D} D^中属于第k类的样本子集。此外对于每个样本赋予一个权重 w x w_x wx​,其初始值为1。并定义如下:
p = ∑ x ∈ D ^ w x ∑ x ∈ D w x p ^ k = ∑ x ∈ D ^ k w x ∑ x ∈ D ^ w x r ^ v = ∑ x ∈ D ^ v w x ∑ x ∈ D ^ w x p=\frac{\sum_{x\in\hat{D}}w_x}{\sum_{x\in D}w_x}\\\hat{p}_k=\frac{\sum_{x\in\hat{D}^k}w_x}{\sum_{x\in \hat{D}}w_x}\\\hat{r}_v=\frac{\sum_{x\in\hat{D}_v}w_x}{\sum_{x\in \hat{D}}w_x} p=∑x∈D​wx​∑x∈D^​wx​​p^​k​=∑x∈D^​wx​∑x∈D^k​wx​​r^v​=∑x∈D^​wx​∑x∈D^v​​wx​​
其中, p p p表示数据集D在属性a上无缺失值的样本的比例, p ^ k \hat{p}_k p^​k​表示无缺失值样本中第k类样本的比例, r ^ v \hat{r}_v r^v​表示无缺失值样本子集中在属性a上取值为 a v a^v av的样本比例。相应的前面信息增益的公式变成如下形式:
G a i n ( D , a ) = p ∗ G a i n ( D ^ , a ) = p ∗ ( E n t ( D ^ ) − E n t ( D ^ ∣ a ) ) = p ∗ ( E n t ( D ^ ) − ∑ v = 1 V r ^ v ∗ E n t ( D ^ v ) ) Gain(D,a)=p*Gain(\hat{D},a)\\=p*(Ent(\hat{D})-Ent(\hat{D}|a))\\=p*(Ent(\hat{D})-\sum_{v=1}^V\hat{r}_v*Ent(\hat{D}_v)) Gain(D,a)=p∗Gain(D^,a)=p∗(Ent(D^)−Ent(D^∣a))=p∗(Ent(D^)−v=1∑V​r^v​∗Ent(D^v​))
其中信息熵的表达式为: E n t ( D ^ ) = − ∑ k = 1 y p ^ k l o g 2 p ^ k Ent(\hat{D})=-\sum_{k=1}^{y}\hat{p}_klog_2\hat{p}_k Ent(D^)=−∑k=1y​p^​k​log2​p^​k​,y为样本类别数目。
(2)第二个需要解决的问题是,“给定划分属性,若样本在该属性上有缺失,如何对样本进行划分?”。其解决方法为,将在划分属性上有缺失的样本划分到所有节点中,并且更新其权重为 r ^ v ∗ w x \hat{r}_v*w_x r^v​∗wx​。

5.python代码实现

import math
import pandas as pd
import numpy as np#带有连续值的数据集
def createDataset():df = pd.DataFrame()df["色泽"] = ["青绿","乌黑","乌黑","青绿","浅白","青绿","乌黑","乌黑","乌黑","青绿","浅白","浅白","青绿","浅白","乌黑","浅白","青绿"]df["根蒂"] = ["蜷缩", "蜷缩", "蜷缩", "蜷缩", "蜷缩", "稍蜷", "稍蜷", "稍蜷", "稍蜷", "硬挺", "硬挺", "蜷缩", "稍蜷", "稍蜷", "稍蜷", "蜷缩", "蜷缩"]df["敲声"] = ["浊响", "沉闷", "浊响", "沉闷", "浊响", "浊响", "浊响", "浊响", "沉闷", "清脆", "清脆", "浊响", "浊响", "沉闷", "浊响", "浊响", "沉闷"]df["纹理"] = ["清晰", "清晰", "清晰", "清晰", "清晰", "清晰", "稍糊", "清晰", "稍糊", "清晰", "模糊", "模糊", "稍糊", "稍糊", "清晰", "模糊", "稍糊"]df["脐部"] = ["凹陷", "凹陷", "凹陷", "凹陷", "凹陷", "稍凹", "稍凹", "稍凹", "稍凹", "平坦", "平坦", "平坦", "凹陷", "凹陷", "稍凹", "平坦", "稍凹"]df["触感"] = ["硬滑", "硬滑", "硬滑", "硬滑", "硬滑", "软粘", "软粘", "硬滑", "硬滑", "软粘", "硬滑", "软粘", "硬滑", "硬滑", "软粘", "硬滑", "硬滑"]df['密度'] = [0.697, 0.774, 0.634, 0.608, 0.556, 0.403, 0.481, 0.437, 0.666, 0.243, 0.245, 0.343, 0.639, 0.657, 0.360, 0.593, 0.719]df['含糖量'] = [0.460, 0.376, 0.264, 0.318, 0.215, 0.237, 0.149, 0.211, 0.091, 0.267, 0.057, 0.099, 0.161, 0.198, 0.370, 0.042, 0.103]df["好瓜"] = ["是", "是", "是", "是", "是", "是", "是", "是", "否", "否", "否", "否", "否", "否", "否", "否", "否"]print(df)df.to_csv('data/watermelon.csv', index=0)#包含缺失值的数据集
def createDatasetNULL():df = pd.DataFrame()df["色泽"] = [np.nan,"乌黑","乌黑","青绿",np.nan,"青绿","乌黑","乌黑","乌黑","青绿","浅白","浅白",np.nan,"浅白","乌黑","浅白","青绿"]df["根蒂"] = ["蜷缩", "蜷缩", "蜷缩", "蜷缩", "蜷缩", "稍蜷", "稍蜷", "稍蜷", np.nan, "硬挺", "硬挺", "蜷缩", "稍蜷", "稍蜷", "稍蜷", "蜷缩", np.nan]df["敲声"] = ["浊响", "沉闷", np.nan, "沉闷", "浊响", "浊响", "浊响", "浊响", "沉闷", "清脆", "清脆", np.nan, "浊响", "沉闷", "浊响", "浊响", "沉闷"]df["纹理"] = ["清晰", "清晰", "清晰", "清晰", "清晰", "清晰", "稍糊", np.nan, "稍糊", np.nan, "模糊", "模糊", "稍糊", "稍糊", "清晰", "模糊", "稍糊"]df["脐部"] = ["凹陷", "凹陷", "凹陷", "凹陷", "凹陷", np.nan, "稍凹", "稍凹", "稍凹", "平坦", "平坦", "平坦", "凹陷", "凹陷", "稍凹", "平坦", "稍凹"]df["触感"] = ["硬滑", np.nan, "硬滑", "硬滑", "硬滑", "软粘", "软粘", "硬滑", "硬滑", "软粘", np.nan, "软粘", "硬滑", "硬滑", "软粘", "硬滑", "硬滑"]df["好瓜"] = ["是", "是", "是", "是", "是", "是", "是", "是", "否", "否", "否", "否", "否", "否", "否", "否", "否"]print(df)df.to_csv('data/watermelon_null.csv', index=0)#计算信息熵
def calEntro(dataset):dataset = np.array(dataset)data_len = len(dataset)#labelCount记录各类样本数据的数量labelCount = {}for row in dataset:cur_label = row[-1]if cur_label not in labelCount.keys():labelCount[cur_label] = 0labelCount[cur_label] += 1result = 0for key in labelCount.keys():prob = labelCount[key]/data_lenresult -= prob*math.log2(prob)return result#计算含有缺失值的信息熵, 不同之处是每个样本带有权重
def calEntroWithNull(weight,d_v:pd.DataFrame,label='好瓜'):classlist = d_v[label].unique()ret_entro = 0for k in classlist:prob_k = sum(weight[(d_v[label]==k)]) / sum(weight)# print('p_k : ',sum(weight[(d_v[label]==k)]),'/',sum(weight),'=',prob_k)ret_entro -= prob_k*np.log2(prob_k)return ret_entro#根据信息增益从剩余特征选择信息增益最大的特征,使得根据该特征划分的数据子集纯度最大
def chooseBestFeatureToSplit(dataSet:pd.DataFrame, weight):''':param dataSet: 当前节点需要进行划分的数据集:param weight: 当前节点需要划分数据集其对应的权重'''# print(dataSet)# print('weight is ',weight)features = dataSet.columns.values[0:-1]ret_feat = ''ret_val = -999max_Gain = 0for feat in features:#计算在特征feat上取值非空集合的熵entro = calEntroWithNull(weight[dataSet[feat].notnull()], dataSet[dataSet[feat].notnull()])# print(feat,':')# print('entro: ',entro)# p表示数据集在属性feat上,无缺失值样本所占的比例p = sum(weight[dataSet[feat].notnull()]) / sum(weight)# print('p: ',sum(weight[dataSet[feat].notnull()]),'/',sum(weight),'=',p)#对于连续属性和离散属性分开处理if ('object' in str(dataSet[feat].dtype)) or ('int' in str(dataSet[feat].dtype)):#数据集dataSet在属性feat上没有缺失值的集合feat_values = dataSet[feat][dataSet[feat].notnull()].unique()#当前属性的信息增益feat_max_Gain = entrofor val in feat_values:# print('val is ',val)#d_v是数据集在属性feat上无缺失值的样本集合d_v = dataSet[dataSet[feat]==val][dataSet[feat].notnull()]#r_v表示d_v中属性feat取值为val的样本所占比例r_v = sum(weight[(dataSet[feat]==val)&(dataSet[feat].notnull())])/sum(weight[dataSet[feat].notnull()])# print('r_v is ',r_v)tmp_entro = calEntroWithNull(weight[(dataSet[feat]==val)&(dataSet[feat].notnull())], d_v)# print('entro is ',calEntroWithNull(weight[(dataSet[feat]==val)&(dataSet[feat].notnull())], d_v))feat_max_Gain -= r_v*tmp_entrofeat_max_Gain *= p# print('gain: ',feat_max_Gain,'\n')if feat_max_Gain>max_Gain:max_Gain = feat_max_Gainret_feat = featret_val = -999elif 'float' in str(dataSet[feat].dtype):#将连续属性的所有取值从小到大排序,然后取相邻元素的中间值作为分割点tmp_feat_values = sorted(dataSet[feat].unique())feat_values = []for i in range(len(tmp_feat_values)-1):feat_values.append((tmp_feat_values[i]+tmp_feat_values[i+1])/2)#遍历上面所有的分割点,将信息增益最大的分割点的信息增益作为该特征的信息增益feat_max_Gain = 0feat_val = -999 #分割点for val in feat_values:tmp_Gain = entrodf_left = dataSet[dataSet[feat]<=val][dataSet[feat].notnull()]df_right = dataSet[dataSet[feat]>val][dataSet[feat].notnull()]r_v_l = sum(weight[(dataSet[feat]<=val)&(dataSet[feat].notnull())])/sum(weight[dataSet[feat].notnull()])r_v_r = sum(weight[(dataSet[feat]>val)&(dataSet[feat].notnull())])/sum(weight[dataSet[feat].notnull()])tmp_entro_left = r_v_l*calEntroWithNull(weight[(dataSet[feat]<=val)&(dataSet[feat].notnull())],df_left)tmp_entro_right = r_v_r*calEntroWithNull(weight[(dataSet[feat]>val)&(dataSet[feat].notnull())],df_right)tmp_Gain -= (tmp_entro_left + tmp_entro_right)tmp_Gain *= pif tmp_Gain>feat_max_Gain:feat_max_Gain = tmp_Gainfeat_val = val# print(feat,' : ',feat_max_Gain)#找出当前数据集中所有属性信息增益最大的作为最优化分特征if feat_max_Gain>max_Gain:max_Gain = feat_max_Gainret_feat = featret_val = feat_valreturn max_Gain, ret_feat, ret_val#根据特征的取值划分数据集
def splitData(dataSet,null_dataSet, feat, value, weight):feat_values = dataSet.columns.valuesret_feats = [f for f in feat_values if f != feat]ret_dataSet = dataSet.loc[dataSet[feat]==value,ret_feats]ret_nulldataSet = null_dataSet[ret_feats]ret_weight = weight[dataSet[feat]==value]return ret_dataSet, ret_weight, ret_nulldataSet#当特征遍历完后从剩余样本中选出类别数最多的作为类别
def majorityCnt(classList):data = pd.Series(classList)return data.value_counts().index[0]#递归构建决策树
def createDecisionTree(dataSet, weight):classList = list(dataSet['好瓜'])#当子集中全部样本的类别相同时,停止递归if classList.count(classList[0]) == len(classList):return classList[0]#遍历完所有特征时if len(dataSet.columns.values) == 1:return majorityCnt(classList)#获取最优划分特征,以及划分点(针对连续变量)_, bestFeat,bestValSplit = chooseBestFeatureToSplit(dataSet, weight)# print('best feature is ',bestFeat,'\n')#这里要取到特征bestFeat的所有可能的取值,所以是在原始数据集df上进行取值feat_values = df[bestFeat].dropna().unique()#在bestFeat上取值为null的数据需要加入到每个子结点中,同时更新加入每个子结点的不同的权重null_dataSet = dataSet[dataSet[bestFeat].isnull()]decisionTree = {bestFeat:{}}#如果是离散变量,对所有取值进行划分,并且该特征后续不能再使用if ('object' in str(dataSet[bestFeat].dtype)) or ('int' in str(dataSet[bestFeat].dtype)):for val in feat_values:# print('val is ',val)sub_dataSet, sub_weight, sub_nulldataSet = splitData(dataSet,null_dataSet, bestFeat, val, weight)#将在bestFeat上取值为空的数据加入进来sub_dataSet = sub_dataSet.append(sub_nulldataSet)#更新加入的数据的权重r_v = sum(weight[(dataSet[bestFeat]==val)&(dataSet[bestFeat].notnull())])/sum(weight[dataSet[bestFeat].notnull()])# print('r_v is ',sum(weight[(dataSet[bestFeat]==val)&(dataSet[bestFeat].notnull())]),'/',sum(weight[dataSet[bestFeat].notnull()]),'=',r_v)add_weight = np.ones(len(null_dataSet)) * r_vsub_weight = np.append(sub_weight, add_weight)# print('sub weight is ',sub_weight)if len(sub_dataSet) == 0:decisionTree[bestFeat][val] = majorityCnt(classList)else:decisionTree[bestFeat][val] = createDecisionTree(sub_dataSet, sub_weight)#如果是连续属性,则使用前面的得到最优划分点进行划分,并且后续还可继续使用这个属性elif 'float' in str(dataSet[bestFeat].dtype):tmp_dataSet = [dataSet[dataSet[bestFeat]<=bestValSplit], dataSet[dataSet[bestFeat]>bestValSplit]]r_v_l = sum(weight[(dataSet[bestFeat] <= bestValSplit) & (dataSet[bestFeat].notnull())]) / sum(weight[dataSet[bestFeat].notnull()])r_v_r = sum(weight[(dataSet[bestFeat] > bestValSplit) & (dataSet[bestFeat].notnull())]) / sum(weight[dataSet[bestFeat].notnull()])# print('r_v is ',sum(weight[(dataSet[bestFeat]==val)&(dataSet[bestFeat].notnull())]),'/',sum(weight[dataSet[bestFeat].notnull()]),'=',r_v)r_v_list = [r_v_l, r_v_r]sub_weight_list = [weight[dataSet[bestFeat]<=bestValSplit], weight[dataSet[bestFeat]>bestValSplit]]symbols = ['-', '+']for i, sub_dataSet in enumerate(tmp_dataSet):# 将在bestFeat上取值为空的数据加入进来sub_nulldataSet = null_dataSet[[f for f in dataSet.columns.values if f != bestFeat]]sub_dataSet = sub_dataSet.append(sub_nulldataSet)#更新每个样本的权重r_v = r_v_list[i]add_weight = np.ones(len(null_dataSet)) * r_vsub_weight = np.append(sub_weight_list[i], add_weight)if len(sub_dataSet) == 0:decisionTree[bestFeat][str(bestValSplit)+symbols[i]] = majorityCnt(classList)else:decisionTree[bestFeat][str(bestValSplit)+symbols[i]] = createDecisionTree(sub_dataSet, sub_weight)return decisionTree# createDataset()
# createDatasetNULL()
df = pd.read_csv('data/watermelon_null.csv')
myTree = createDecisionTree(df, np.ones(len(df)))
print(myTree)

输出结果:

{'纹理': {'清晰': {'脐部': {'凹陷': '是', '稍凹': {'触感': {'硬滑': '是', '软粘': {'色泽': {'乌黑': '否', '青绿': '是', '浅白': '否'}}}}, '平坦': {'根蒂': {'蜷缩': '否', '稍蜷': '是', '硬挺': '否'}}}}, '稍糊': {'敲声': {'浊响': {'脐部': {'凹陷': '否', '稍凹': '是', '平坦': '是'}}, '沉闷': '否', '清脆': '否'}}, '模糊': {'色泽': {'乌黑': '是', '青绿': '否', '浅白': '否'}}}}

6.决策树的可视化实现

这里使用了Graphviz对上面的得到决策树进行可视化的实现。

import pygraphviz as pgv
labels = {'否':'坏瓜', '是':'好瓜'}
def drawTree(tree:{}, root_node,root_num, features:[]):global node_numfor val in tree[root_node].keys():if isinstance(tree[root_node][val], str): #如果是叶节点则停止递归leaf_node = labels[tree[root_node][val]]+'_'+str(node_num)A.add_node(leaf_node,fontname="Microsoft YaHei")A.add_edge(root_node+'_'+str(root_num), leaf_node, label=val,fontname="Microsoft YaHei")node_num += 1continueinner_node = list(tree[root_node][val].keys())[0]A.add_node(inner_node+'_'+str(node_num), fontname="Microsoft YaHei")A.add_edge(root_node+'_'+str(root_num), inner_node+'_'+str(node_num), label=val,fontname="Microsoft YaHei")node_num += 1drawTree(tree[root_node][val], inner_node,node_num-1, features)node_num = 0
root_node = list(myTree.keys())[0]
A=pgv.AGraph(directed=True,strict=False)
A.add_node(root_node + '_' + str(node_num),fontname="Microsoft YaHei")
node_num += 1
drawTree(myTree, root_node,node_num-1, df.columns.values)
A.layout('dot')
A.draw('tree2.png')

输出结果:

7.总结

1.决策树和其它部分模型不同,它可以处理包含缺失值的数据。
2.决策树主要分为ID3(使用信息增益选择特征),C4.5(使用信息增益率),CART决策树(使用信息增益,可用于分类和回归)。并且CART决策树对于离散值的处理和连续值处理一样,并且离散特征还可用于后续划分。
3.补充:sklearn中实现的是CART决策树,并且除了采用基尼系数之外还是实现了信息增益选择最优特征划分。

8.回归树及其python实现(2020.11.30更新)

上文都是关于决策树用于分类时相关理论及其实现,但是决策树除了分类还可以用于回归问题,其思想和分类决策树类似。

8.1回归树的原理

一颗回归树对应着输入空间(即特征空间)的一个划分以及在划分的单元上的输出值。假设已将输入空间划分为M个单元 R 1 , R 2 , . . . , R M R_1,R_2,...,R_M R1​,R2​,...,RM​,并在每个单元 R m R_m Rm​上有一个固定的输出值 c m c_m cm​,于是回归树模型可表示为:
f ( x ) = ∑ m = 1 M c m I ( x ∈ R m ) f(x)=\sum_{m=1}^{M}c_mI(x\in R_m) f(x)=m=1∑M​cm​I(x∈Rm​)
其中I为符号函数,当括号内的表达式为True时返回1,否则返回0。

整个过程我们只需要知道树是如何划分的(即最优划分特征的选择)以及划分后每个单元得输出值怎么确定?

(1)输出值 c m c_m cm​的确定:对于回归问题而言,可以使用均方损失函数来表示回归树训练的误差,训练的目标就是使得均方误差最小,即最小化 ∑ x i ∈ R m ( y i − f ( x i ) ) 2 \sum_{x_i\in R_m}(y_i-f(x_i))^2 ∑xi​∈Rm​​(yi​−f(xi​))2,将式子对 f ( x i ) f(x_i) f(xi​)求导,可得 c m = 1 l e n ( R m ) ∑ x i ∈ R m y i c_m=\frac{1}{len(R_m)}\sum_{x_i\in R_m}y_i cm​=len(Rm​)1​∑xi​∈Rm​​yi​,即最优的 c m c_m cm​是 R m R_m Rm​上所有实例对应的真实值得均值。

(2)最优划分特征的选择:分类问题选择特征主要根据信息增益、信息增益率和基尼系数来进行选择,而对于连续值的预测显然不能靠集合不纯度或熵来衡量,而对于回归问题而言,我们选择的特征j及其划分点s要能够使得划分后的误差最小。这里的划分点就是上面分类中对于连续值的处理,此外这里对于离散值属性的处理也是进行选择划分点而不是考虑其所有离散取值。

8.2 python实现

import numpy as np
import pandas as pd
import pygraphviz as pgv'''构建回归树'''
#计算均方误差
def cal_mse(feat_val_list,split_val,y_true):feat_val_list = np.array(feat_val_list)y_pred = np.zeros(len(y_true))c1 = np.mean(np.array(y_true)[feat_val_list<split_val])c2 = np.mean(np.array(y_true)[feat_val_list>split_val])y_pred[feat_val_list<split_val] = c1y_pred[feat_val_list>split_val] = c2return np.sum((y_pred-y_true)**2)/len(y_true),c1,c2#选择最优划分特征以及划分点
def select_best_feature(data:pd.DataFrame, y_true):''':param data: 待划分数据集:param y_true: 样本对应的真实值:return: 最优的划分特征以及划分点'''min_mse = np.inf #最小均方误差best_feat = '' #最优划分特征best_split_val = 0 #最优划分点best_c1 = 0 #最优划分后每个区域的取值best_c2 = 0for feat in data.columns.values:feat_val_list = list(data[feat]) #该属性下的所有取值,含重复取值sorted_feat_val_list = sorted(list(data[feat].unique()))#所有划分点,不含重复值split_val_list = [(sorted_feat_val_list[i]+sorted_feat_val_list[i+1])/2 for i in np.arange(len(sorted_feat_val_list)-1)]for split_val in split_val_list:#计算最小的划分空间,以及其均方误差cur_mse,c1,c2 = cal_mse(feat_val_list, split_val, y_true)if cur_mse<min_mse:min_mse = cur_msebest_feat = featbest_split_val = split_valbest_c1 = c1best_c2 = c2return best_feat, best_split_val, min_mse, best_c1, best_c2def build_regressionTree(data:pd.DataFrame,y_true,cur_depth,min_samples_leaf=1,min_samples_split=2,max_depth=3):#当待划分数据集大小小于最小划分大小时,或者达到树最大深度时,停止划分if (len(data) < min_samples_split) or cur_depth>max_depth:return {'isLeaf':True,'val':np.mean(y_true)}tree = {} #构建树best_feat, best_split_val, min_mse, best_c1, best_c2 = select_best_feature(data, y_true)l_tree_index = data[best_feat]<best_split_valr_tree_index = data[best_feat]>best_split_val# 当划分后的叶节点数据量大小小于最小叶节点数据量时,停止划分if(len(data[l_tree_index]) < min_samples_leaf) or (len(data[r_tree_index]) < min_samples_leaf):return {'isLeaf':True,'val':np.mean(y_true)}# print(best_feat, best_split_val)#继续递归划分tree['isLeaf'] = Falsetree['best_feat'] = best_feattree['split_val'] = best_split_valtree['l_tree'] = build_regressionTree(data[l_tree_index],np.array(y_true)[l_tree_index],cur_depth+1, min_samples_leaf, min_samples_split, max_depth)tree['r_tree'] = build_regressionTree(data[r_tree_index],np.array(y_true)[r_tree_index],cur_depth+1, min_samples_leaf, min_samples_split, max_depth)return treedef predict(tree:{}, data:pd.DataFrame):y_pred = np.zeros(len(data))for i in np.arange(len(data)):tmp_tree = treewhile(tmp_tree['isLeaf']==False):cur_feat = tmp_tree['best_feat']split_val = tmp_tree['split_val']if data.loc[i,cur_feat] <= split_val:tmp_tree = tmp_tree['l_tree']else:tmp_tree = tmp_tree['r_tree']y_pred[i] = tmp_tree['val']return y_preddef plotTree(tree:{}, father_node,depth,label):#如果当前是根节点if depth == 1:A.add_node(father_node)#如果既是根节点又是叶节点,即树桩if tree['isLeaf'] == True:A.add_edge(father_node,tree['val'],label=label)returnelse:plotTree(tree['l_tree'], father_node,depth+1,'<=')plotTree(tree['r_tree'], father_node,depth+1,'>')returnif tree['isLeaf'] == True:A.add_edge(father_node, tree['val'], label=label)returnA.add_edge(father_node, tree['best_feat']+':'+str(tree['split_val']), label=label)plotTree(tree['l_tree'], tree['best_feat']+':'+str(tree['split_val']), depth+1,'<=')plotTree(tree['r_tree'], tree['best_feat']+':'+str(tree['split_val']), depth+1,'>')if __name__ == '__main__':df = pd.DataFrame(columns=['x1'])df['x1'] = [1,2,3,4,5,6,7,8,9,10]y_true = [4.50,4.75,4.91,5.34,5.80,7.05,7.90,8.23,8.70,9.00]y_true = [5.56,5.70,5.91,6.40,6.80,7.05,8.90,8.70,9.00,9.05]tree = build_regressionTree(df, y_true, cur_depth=1,max_depth=3)print(tree)# print(select_best_feature(df, y_true))print(predict(tree,df))A = pgv.AGraph(directed=True, strict=False)plotTree(tree, tree['best_feat']+':'+str(tree['split_val']),depth=1,label='')A.layout('dot')  # layout with dotA.draw('tree2.png')  # write to file

后续有时间再补充剪枝部分的内容…

机器学习决策树DecisionTree以及python代码实现相关推荐

  1. 机器学习——决策树模型:Python实现

    机器学习--决策树模型:Python实现 1 决策树模型的代码实现 1.1 分类决策树模型(DecisionTreeClassifier) 1.2 回归决策树模型(DecisionTreeRegres ...

  2. 决策树原理实例(python代码实现)_决策树原理实例(python代码实现)

    决策数(Decision Tree)在机器学习中也是比较常见的一种算法,属于监督学习中的一种.看字面意思应该也比较容易理解,相比其他算法比如支持向量机(SVM)或神经网络,似乎决策树感觉"亲 ...

  3. 【机器学习】梯度下降 (python代码)

    梯度下降 (python代码) 文章介绍 1.前提 2.代码实现 文章介绍 文章配图来自网络 个人学习笔记,推荐直接买课去学习 得懂python,本文代码可直接跑,环境基于[ PyCharm 2022 ...

  4. 决策树留一法python代码_机器学习模型2 决策树-基于Python sklearn的实现

    1.模型原理 (一)原理 1.原理:引入信息熵(不确定程度)的概念,通过计算各属性下的信息增益程度(信息增益越大,则意味着使用该属性来进行划分所获得的"纯度提升"越大),增益程度最 ...

  5. 决策树剪枝python实现_决策树剪枝问题python代码

    决策树在生长过程中有可能长得过于茂盛,对训练集学习的很好,但对新的数据集的预测效果不好,即过拟合,此时生成的模型泛化能力较差.因此,我们需要对决策树进行剪枝,使得生成的模型具有较强的泛化能力. 为了检 ...

  6. python决策树剪枝_决策树剪枝问题python代码

    决策树在生长过程中有可能长得过于茂盛,对训练集学习的很好,但对新的数据集的预测效果不好,即过拟合,此时生成的模型泛化能力较差.因此,我们需要对决策树进行剪枝,使得生成的模型具有较强的泛化能力. 为了检 ...

  7. 一文速览机器学习的类别(Python代码)

    作者:泳鱼 来源:算法进阶 机器学习按照学习数据经验的不同,即训练数据的标签信息的差异,可以分为: *监督学习(supervised learning) *非监督学习(unsupervised lea ...

  8. 一文全览机器学习建模流程(Python代码)

    作者:泳鱼 来源:算法进阶 引言 随着人工智能时代的到来,机器学习已成为解决问题的关键工具,如识别交易是否欺诈.预测降雨量.新闻分类.产品营销推荐.我们接下来会详细介绍机器学习如何应用到实际问题,并概 ...

  9. 【机器学习】一文全览机器学习建模流程(Python代码)

    随着人工智能时代的到来,机器学习已成为解决问题的关键工具,如识别交易是否欺诈.预测降雨量.新闻分类.产品营销推荐.我们接下来会详细介绍机器学习如何应用到实际问题,并概括机器学习应用的一般流程. 1.1 ...

最新文章

  1. 结队编程-基于gui的四则运算生成器
  2. WindowsPhone基础琐碎总结-----数据绑定(一)
  3. Greys Java在线问题诊断工具
  4. “学霸”是怎样炼成的?
  5. c语言里的%p的作用,C语言中geiwei=m%10什么意思,求解!
  6. Dingo Api 入门
  7. 清华大学计算机专业学生埃朗读村,《朗读者~矣晓沅(清华大学计算机研究生  彝族)》...
  8. How to mount HFS EFI on macOS
  9. 升级到Java SE 8 和Java EE 7
  10. Leetcode每日一题:1122.relative-sort-array(数组的相对排序)
  11. antd中form自定义rules
  12. Java基础 IO流——第三部分
  13. 【UVA12230】Crossing Rivers(概率/期望)
  14. 图解sql内外连接和左连接left join和右连接right join
  15. 4399小游戏怎样下载到本地玩
  16. java设计模式学习-代理模式
  17. Technorati.com 被劫持
  18. 对于seo优化与sem竞价有什么不同的地方?哪个更适合?
  19. outlook服务器响应错误,outlook 错误代码解析与解决方法
  20. sql float保留两位

热门文章

  1. 【Discuz】去除注册时的邮箱
  2. 无人驾驶汽车系统入门(十)——基于运动学模型的模型预测控制
  3. python输入汉字的代码_基于python的汉字转GBK码实现代码
  4. 6.6 创建选项卡窗体(上)
  5. iframe嵌套网页去掉嵌套网页的滚动条
  6. python中 *和 **的用法
  7. ArcGIS中坐标系转换的那些事:用经度、投影代号、中央经线之间关系与转换
  8. Vikinger v1.9.3汉化版WordPress模板主题
  9. 基于C语言的五子棋游戏设计与实现 课程报告+项目源码及可执行exe文件
  10. 安兰德写作竞赛可以获得多少奖金?