机器学习2(决策树)

142 阅读7分钟

写在前面

代码开发使用的运行环境是python 3.6.1的版本,配合vscode+ananconda的代码编写环境进行相关的代码编写任务,代码运行所需要的库及对应版本如下:

# Name               Version 
numpy                 1.13.1 
pandas                0.23.4

1. 算法简要介绍

决策树是一种基本的分类与回归方法,它通过对训练数据的学习来构建一棵树状结构,用于决策。

决策树的每个内部节点表示一个特征或属性,叶子节点表示一个类别。决策树的生成包括特征选择、树的剪枝等过程。决策树学习的目标是创建一个与训练数据拟合很好,并且复杂度小的决策树。

由于直接从所有可能的决策树中找到最优的决策树是NP完全问题,于是现实中我们采用启发式算法来学习次优的决策树。决策树的剪枝,往往从已生成的树上剪掉一些叶节点或叶节点以上的子树,并将其父节点或根节点作为新的叶节点,从而简化生成的决策树。

决策树分为分类树回归树两种,分类树对离散变量做决策树回归树对连续变量做决策树,这里我们只讨论分类树。

2.原理阐述

2.1 核心思想

在正式阐述决策树的核心思想之前先看一个小例子 假设我们现在有一堆西瓜要求从中挑选好瓜,通常会做一系列的判断或“子决策”: 先看“它是什么颜色?”,如果是“青绿色”,则再看看“根蒂是什么形态”,如果是“蜷缩”,再判断“它敲起来是什么声音”,最后,决策出这是好瓜

image.png

从上面的小例子中其实可以了解到决策树的一个重要思想那就是分而治之

一般的,一棵决策树包含一个根结点、若干个内部结点和若干个叶结点,叶结点对应于决策结果

其他每个结点则对应于一个属性测试;每个结点包含的样本集合根据属性测试的结果被划分到子结点中

根结点包含样本全集.从根结点到每个叶结点的路径对应了一个判定测试序列.决策树学习的目的是为了产生一棵泛化能力强,即处理未见示例能力强的决策树,其基本流程遵循简单且直观的“分而治之

那么如何去“分”就非常重要了,因此,下面介绍几种常用的用于节点属性选择的算法

2.2 决策树节点生成算法

2.2.1 信息增益

"信息熵"是度量样本集合纯度最常用的一种指标.假定当前样本集合D中第k类样本所占的比例为pk(k=1,2,…,|YI),则D的信息熵定义为

Ent(D)=k=1ypklog2pkEnt(D)=-\sum_{k=1}^{|y|}p_klog_2p_k

其中Ent(D)的值越小,则D的纯度越高

假定离散属性a有V个可能的取值{a¹,a²,……,a√},若使用a来对样本集D进行划分,则会产生V个分支结点,其中第v个分支结点包含了D中所有在属性a上取值为ava……v的样本,记为DvD^v.根据上面的信息熵公式计算出DvD^v的信息熵,再考虑到不同的分支结点所包含的样本数不同,给分支结点赋予权重 |DvD^v| /|D],即样本数越多的分支结点的影响越大,于是可计算出用属性a对样本集D进行划分所获得的“信息增益

Gain(D,a)=Ent(D)v=1VDvDEnt(Dv)Gain(D,a)=Ent(D)-\sum_{v=1}^V\frac{|D^v|}{|D|}Ent({D^v})

一般而言,信息增益越大,这意味着使用属性a来划分获得的“纯度提升”越大。因此,我们可用信息增益来进行决策树的划分属性选择。

2.2.2 增益率

上面信息增益虽然可以用于决策树的划分,但是信息增益对可取数目较多的属性有所偏好,因此,接下来介绍一种与信息增益相反的选择方式-----增益率。

信息增益是对可取数目较多的属性有所偏好,而增益率刚好相反,它是对于可取数目较少的属性有偏好,因此在实际算法实现的过程中更多的是这两种的选择方法结合使用,即----先从候选划分属性中找出信息熵高于平均水平的属性,再从中选择增益率最高的

增益率公式如下:

Gainratio(D,a)=Gain(D,a)IV(a)Gain_ratio(D,a)=\frac{Gain(D,a)}{IV(a)}

其中

IV(a=v=1VDvDlog2DvDIV(a)=-\sum_{v=1}^V\frac{|{D^v}|}{|D|}log_2\frac{|{D^v}|}{|D|}

2.2.3 基尼指数

基尼指数(基尼不纯度):表示在样本集合中一个随机选中的样本被分错的概率。

基尼系数越小,不纯度越低,特征越好。这和信息增益(率)正好相反。基尼指数可以用来度量任何不均匀分布,是介于0-1之间的数,0是完全相等,1是完全不相等。 数据集D的基尼指数值表示如下

Gini(D)=k=1ykskGini(D)=\sum_{k=1}^{|y|}\sum{k^s\not=k}

对于属性a的基尼指数定义为

Giniindex(D,a)=v=1VDvDGini(Dv)Gini_index(D,a)=\sum_{v=1}^V\frac{|{D^v}|}{|D|}Gini(D^v)

根据上述公式我们在候选属性集中,选择那个使得划分后基尼指数最小的属性作为最优划分属性。

至此,决策树节点生成常用的三种方法已经介绍完毕。

3.算法代码实现

3.1 前言

决策树python代码实现部分,属性的划分标准是采用基尼指数

3.2 数据准备

在读取数据集中数据数据的过程中,由于数据集是.csv格式的文件,因此使用pandas库中的函数进行数据读取

image.png

数据获取完整片段

# 数据准备 
def getData(path):
    names = ['sepal_length','sepal_width','petal_length','petal_width','species'] 
# 使用pandas中的函数读取.csv文件 
    trains = pd.read_csv(path,header=0,names=names) 
    mat = zeros((len(trains),4)) # 生成len(trains) * 4的矩阵 
    index = 0 label = [] # 存储标签
    for i in range(len(trains)): 
        label.append(trains[names[-1][i]]) 
        mat[index:] = trains[names[0:4]] 
    return norm(mat),label

3.3 基尼指数计算及选择节点


#计算基尼指数
def gini(data):
    data_label = data.iloc[:, -1]
    label_num = data_label.value_counts() #有几类,每一类的数量
    res = 0
    for k in label_num.keys():
        p_k = label_num[k]/len(data_label)
        res += p_k ** 2
    return 1 - res

# 计算每个特征取值的基尼指数,找出最优切分点
def gini_index(data,a):
    feature_class = data[a].value_counts()
    res = []
    for feature in feature_class.keys():
        weight = feature_class[feature]/len(data)
        gini_value = gini(data.loc[data[a] == feature])
        res.append([feature, weight * gini_value])
    res = sorted(res, key = lambda x: x[-1])
    return res[0]


3.4 决策树创建

#创建决策树
def create_tree(data):
    data_label = data.iloc[:,-1]
    if len(data_label.value_counts()) == 1: #只有一类
        return data_label.values[0]
    if all(len(data[i].value_counts()) == 1 for i in data.iloc[:,:-1].columns): #所有数据的特征值一样,选样本最多的类作为分类结果
        return get_most_label(data)
    best_feature, best_feature_value = get_best_feature(data) #根据信息增益得到的最优划分特征
    Tree = {best_feature:{}} #用字典形式存储决策树

    Tree[best_feature][best_feature_value] = create_tree(drop_exist_feature(data, best_feature, best_feature_value, 1)[1])
    Tree[best_feature]['Others'] = create_tree(drop_exist_feature(data, best_feature, best_feature_value, 2)[1])
    return Tree

def predict(Tree , test_data):
    first_feature = list(Tree.keys())[0] #第一个特征
    second_dict = Tree[first_feature] #第一个特征后面的字典
    input_first = test_data.get(first_feature) #预测输入的第一个特征值是多少
    input_value = second_dict[input_first] if input_first == first_feature else second_dict['Others'] #预测输入对应的字典
    if isinstance(input_value , dict): #判断分支还是不是字典
        class_label = predict(input_value, test_data)
    else:
        class_label = input_value
    return class_label

4. 算法实现过程中遇到的问题及总结

4.1 总结

决策树算法优缺点

优点:

  • 便于理解和解释。树的结构可视化
  • 训练需要的数据少,其他机器学习模型通常需要数据规范化,比如构建虚拟变量和移除缺失值
  • 由于训练决策树的数据点的数量导致了决策树的使用开销呈指数分布(训练树模型的时间复杂度是参加训练数据点的对数值)
  • 能够处理数值型数据和分类数据,其他的技术通常只能用来专门分析某一种的变量类型的数据集;
  • 能够处理多路输出问题;
  • 使用白盒模型。如果某种给定的情况在模型中是可以观察的,那么就可以轻易的通过布尔逻辑来解释这种情况,相比之下在黑盒模型中的结果就是很难说明清楚了;
  • 可以通过数值统计测试来验证该模型。这对解释验证该模型的可靠性成为可能
  • 即使是该模型假设的结果越真实模型所提供的数据有些违反,其表现依旧良好

缺点:

  • 决策树模型容易出现过拟合现象,使得模型的泛化能力很低。但是我们可以通过剪枝、设置每一个叶节点的最小样本数、设置树的最大深度来减小模型的复杂度,从而避免过拟合现象。
  • 决策树的稳定性较低。对数据集进行很小的改变就可能导致训练出完全不同的树。
  • 决策树会受到样本不平衡的影响。我们需要在训练模型之前平衡样本,避免出现某一个类别在dataset中占绝对多数的情况。