MADlib——基于SQL的数据挖掘解决方案(24)——分类之决策树

2019-05-25 19:38:42 浏览数 (1)

一、决策树简介

1. 决策树的基本概念

决策树(Decision Tree)又称为分类树(Classification Tree),是最为广泛的归纳推理算法之一,处理类别型或连续型变量的分类预测问题,可以用图形和if-then的规则表示模型,可读性较高。决策树模型通过不断地划分数据,使因变量的差别最大,最终目的是将数据分类到不同的组织或不同的分枝,在因变量的值上建立最强的归类。

决策树是一种监督式的学习方法,产生一种类似流程图的树结构。决策树对数据进行处理是利用归纳算法产生分类规则和决策树,再对新数据进行预测分析。树的终端节点“叶节点”(Leaf Node),表示分类结果的类别(Class),每个内部节点表示一个变量的测试,分枝(Branch)为测试输出,代表变量的一个可能数值。为达到分类目的,变量值在数据上测试,每一条路径代表一个分类规则。

决策树在数据挖掘领域应用非常广泛,尤其在分类问题上是很有效的方法。除具备图形化分析结果易于了解的优点外,决策树还具有以下优点:

  • 决策树模型可以用图形或规则表示,而且这些规则容易理解和解释。容易使用,而且很有效。
  • 可以处理连续型或类别型的变量。以最大信息增益选择分割变量,模型显示变量的相对重要性。
  • 面对大的数据集也可以处理得很好,此外因为树的大小和数据库大小无关,因此计算量较小。当有很多变量被引入模型时,决策树仍然适应。

2. 决策树的构建步骤

决策树构建的主要步骤有三个:第一是选择适当的算法训练样本构建决策树,第二是适当地修剪决策树,第三则是从决策树中萃取知识规则。

(1)决策树的分隔

决策树是通过递归分割(Recursive Partitioning)建立而成,递归分割是一种把数据分割成不同小的部分的迭代过程。构建决策树的归纳算法如下:

  1. 将训练样本的原始数据放入决策树的树根。
  2. 将原始数据分成两组,一部分为训练组数据,另一部分为测试组资料。
  3. 使用训练样本来建立决策树,在每一个内部节点依据信息论(Information Theory)来评估选择哪一个属性继续做分割的依据,又称为节点分割(Splitting Node)。
  4. 使用测试数据来进行决策树修剪,修剪到决策树的每个分类都只有一个节点,以提升预测能力与速度。也就是经过节点分割后,判断这些内部节点是否为叶节点,如果不是,则以新内部节点为分枝的树根来建立新的次分枝。
  5. 不断递归第1至第4步,一直到所有内部节点都是树叶节点为止。当决策树完成分类后,可将每个分枝的树叶节点萃取出知识规则。

如果有以下情况发生,决策树将停止分割:

  • 该群数据的每一笔数据都已经归类到同一类别。
  • 该群数据已经没有办法再找到新的属性来进行节点分割。
  • 该群数据已经没有任何尚未处理的数据。

一般来说,决策树分类的正确性有赖于数据来源的多寡,若是透过庞大数据构建的决策树,其预测和分类结果往往是符合期望的。

决策树学习主要利用信息论中的信息增益(Information Gain),寻找数据集中有最大信息量的变量,建立数据的一个节点,再根据变量的不同值建立树的分枝,每个分枝集中重复建树的下层结果和分枝的过程,一直到完美建立整株决策树。决策树的每一条路径代表一个分类规则,与其它分类模型相比,决策树的最大优势在于模型图形化,让使用者容易理解,模型解释也更容易。

在树的每个节点上,使用信息增益选择测试的变量,信息增益是用来衡量给定变量区分训练样本的能力,选择最高信息增益或最大熵(Entropy)简化的变量,将之视为当前节点的分割变量,该变量促使需要分类的样本信息量最小,而且反映了最小随机性或不纯性(Impurity)。

若某一事件发生的概率是p,令此事件发生后所得的信息量为I(p),若p=1,则I(p)=0,因为某一事件一定会发生,因此该事件发生不能提供任何信息。反之,如果某一事件发生的概率很小,不确定性很大,则该事件发生带来的信息很多,因此I(p)为递减函数,并定义I(p)=-log(p)。

给定数据集S,假设类别变量Am个不同的类别

。利用变量A将数据集分为m个子集

,其中

表示在S中属于

的样本。在分类过程中,对于每个样本,对应m种可能发生的概率为

,记第i 种结果的信息量为

,称为分类信息的熵。熵是测量一个随机变量不确定性的测量标准,可以用来测量训练数据集内纯度(Purity)的标准。熵的函数表示如下式:

其中,

是任意样本属于

的概率,对数函数以2为底,因为信息用二进制编码。

变量训练分类数据集的能力,可以利用信息增益来测量。算法计算每个标量的信息增益,具有最高信息增益的变量选为给定集合S 的分割变量,产生一个节点,同时以该变量为标记,对每个变量值产生分枝,以此划分样本。

(2)决策树的剪枝

决策树学习可能遭遇模型过度适配(Overfitting)的问题,过度适配是指模型过度训练,导致模型记住的不是训练集的一般性,而是训练集的局部特性。模型过度适配,将导致模型预测能力不准确,一旦将训练后的模型运用到新数据,将导致错误预测。因此,完整的决策树构造过程,除了决策树的构建外,还应该包含树剪枝(Tree Pruning),解决和避免模型过度适配的问题。

当决策树产生时,因为数据中的噪声或离群值,许多分枝反映的是训练资料中的异常情形,树剪枝就是在处理这些过度适配的问题。树剪枝通常使用统计测量值剪去最不可靠的分枝,可用的统计测量有卡方值或信息增益等,如此可以加速分类结果的产生,同时也可提高测试数据能够正确分类的能力。

树剪枝有两种方法:先剪枝(Prepruning)和后剪枝(Postpruning)。先剪枝是通过提前停止树的构造来对树剪枝,一旦停止分类,节点就成为树叶,该树叶可能持有子集样本中次数最高的类别。在构造决策树时,卡方值和信息增益等测量值可以用来评估分类的质量,如果在一个节点划分样本,将导致低于预先定义阈值的分裂,则给定子集的进一步划分将停止。选取适当的阈值是很困难的,较高的阈值可能导致过分简化的树,但是较低的阈值可能使得树的简化太少。后剪枝是由已经完全生长的树减去分枝,通过删减节点的分枝剪掉树节点,最底下没有剪掉的节点成为树叶,并使用先前划分次数最多的类别作标记。对于树中每个非树叶节点,算法计算减去该节点上的子树可能出现的期望错误率。再使用每个分枝的错误率,结合每个分枝观察的权重评估,计算不对该节点剪枝的期望错误率。如果减去该节点导致较高的期望错误率,则保留该子树,否则剪去该子树。产生一组逐渐剪枝后的树,使用一个独立的测试集评估每棵树的准确率,就能得到具有最小期望错误率的决策树。也可以交叉使用先剪枝和后剪枝形成组合式,后剪枝所需的计算比先剪枝多,但通常产生可靠的树。

3. 表示属性测试条件的方法

决策树归纳算法必须为不同类型的属性提供表示属性测试条件和其对应输出的方法。

(1)二元属性

二元属性的测试条件产生两个可能的输出,如图1所示。

图1 二元属性的测试条件

(2)标称属性

由于标称属性有多个属性值,它的测试条件可以用两种方法表示。如图2所示。对于多路划分(图2a),其输出数取决于该属性不同值的个数。例如,如果属性婚姻状况有三个不同的属性值——单身、已婚、离异,则它的测试条件就会产生一个三路划分。另一方面,某些决策树算法(如CART)只产生二元划分,它们考虑创建k个属性值的二元划分的所有

种方法。图2b显示了把婚姻状况的属性划分为两个子集的三种不同的分组方法。

(a)多路划分

(b)二元划分(通过属性值分组)

图2 标称属性的测试条件

(3)序数属性

序数属性也可以产生二元或多路划分,只要不违背序数属性值的有序性,就可以对属性值进行分组。图3显示了按照属性“衬衣尺码”划分训练记录的不同方法。图3a和图3b中的分组保持了属性值间的序关系,而图3c所示的分组则违反了这一性质,因为它把“小号”和“大号”分为一组,把“中号”和“大号”放在另一组。

图3 序数属性值分组的不同方式

(4)连续属性

对于连续属性来说,测试条件可以是具有二元输出的比较测试

,也可以是具有形如

输出的范围查询,图4显示了这些方法的差别。对于二元划分,决策树算法必须考虑所有可能的划分点

,并从中选择产生最佳划分的点。对于多路划分,算法必须考虑所有可能的连续值区间。可以采用离散化的策略,离散化之后,每个离散化区间赋予一个新的序数值,只要保持有序性,相邻的值还可以聚集成较宽的区间。

图4 连续属性的测试条件

4. 选择最佳划分的度量

有很多度量可以用来确定划分记录的最佳方法,这些度量用划分前和划分后记录的类分布定义。

表示给定节点t中属于类i的记录所占的比例,有时,我们省略节点t,直接用

表示该比例。在两类问题中,任意节点的类分布都可以记作

,其中

。例如,考虑图5中的测试条件,划分前的类分布是(0.5,0.5),因为来自每个类的记录数相等。如果使用“性别”属性来划分数据,则子节点的类分布分别为(0.6,0.4)和(0.4,0.6),虽然划分后两个类的分布不再平衡,但是子节点仍然包含两个类的记录;按照第二个属性“车型”进行划分,将得到纯度更高的划分。

图5 多路划分与二元划分

选择最佳划分的度量通常是根据划分后子节点的不纯性的程度。不纯的程度越低,类分布就越倾斜。例如,类分布为(0,1)的节点具有零不纯性,而均衡分布(0.5,0.5)的节点具有最高的不纯性。不纯性度量的例子包括:

其中c是类的个数,并且在计算熵时,

。下面给出三种不纯性度量方法的计算实例。

从上面的例子可以看出,不同的不纯性度量是一致的。根据计算,节点

具有最低的不纯性度量值,接下来依次是

。虽然结果一致,但作为测试条件的属性选择仍然因不纯性度量的选择而异。

为了确定测试条件的效果,我们需要比较父节点(划分前)的不纯程度和子节点(划分后)的不纯程度,它们的差越大,测试条件的效果越好。增益

是一种可以用来确定划分效果的标准:

其中,

是给定节点的不纯性度量,N 是父节点上的记录数,k 是属性值的个数,

是与子节点

相关联的记录个数。决策树归纳算法通常选择最大化增益

的测试条件,因为对所有的测试条件来说,

是一个不变的值,所以最大化增益等价于最小化子节点的不纯性度量的加权平均值。当选择熵(entropy)作为不纯性度量时,熵的差就是所谓信息增益(informationgain)

熵和Gini指标等不纯性度量趋向有利于具有最大不同值的属性。图5显示了三种可供选择的测试条件。第一个测试条件“性别”与第二个测试条件“车型”相比,容易看出“车型”似乎提供了更好的划分数据的方法,因为它产生更纯的派生节点。然而,如果将这两个条件与“顾客ID”相比,后者看来产生更纯的划分,但“顾客ID”却不是一个有预测性的属性,因为每个样本在该属性上的值都是唯一的。即使在不太极端的情形下,也不会希望产生大量输出的测试条件,因为与每个划分相关联的记录太少,以致不能做出可靠的预测。

解决该问题的策略有两种。第一种策略是限制测试条件只能是二元划分,CART这样的决策树算法采用的就是这种策略;另一种策略是修改评估划分的标准,把属性测试条件产生的输出数也考虑进去。例如,决策树算法4.5采用称作增益率(gain ratio)的划分标准来评估划分。增益率定义如下:

其中,划分信息

,而k 是划分的总数。例如,如果每个属性值具有相同的记录数,则

,而划分信息等于

。这说明如果某个属性产生了大量的划分,它的划分信息将会很大,从而降低了增益率。

5. 决策树算法

决策树算法基本上是一种贪心算法,采取由上至下的逐次搜索方式,渐次产生决策树模型结构。划分数据集的最大原则是:使无序的数据变得有序。如果一个训练数据中有20个特征,那么选取哪个作为划分依据?这时必须采用量化的方法来判断,常用的量化划分方法是“信息论度量信息分类”。基于信息论的决策树算法有ID3、C4.5和CART等算法,其中C4.5和CART两种算法从ID3算法中衍生而来。

C4.5和CART支持数据特征为连续分布时的处理,主要通过使用二元切分来处理连续型变量,即求一个特定的值-分裂值:特征值大于分裂值就走左子树,否则就走右子树。这个分裂值的选取原则是使得划分后的子树中的“混乱程度”降低,具体到C4.5和CART算法则有不同的定义方式。

ID3算法由Ross Quinlan发明,建立在“奥卡姆剃刀”的基础上:越是小型的决策树越优于大的决策树(be simple简单理论)。ID3算法中根据信息论的信息增益评估和选择特征,每次选择信息增益最大的特征做判断属性。ID3算法可用于划分标称型数据集,没有剪枝的过程,为了去除过度数据匹配的问题,可通过裁剪合并相邻的无法产生大量信息增益的叶子节点(例如设置信息增益阀值)。使用信息增益有一个缺点,那就是它偏向于具有大量值的属性,就是说在训练集中,某个属性所取的不同值的个数越多,那么越有可能拿它来作为分裂属性,而这样做有时候是没有意义的,最典型的就是自增ID序列。另外ID3不能处理连续分布的数据特征,于是就有了C4.5算法。

C4.5是ID3的一个改进算法,它继承了ID3算法的优点。C4.5算法用信息增益率来选择属性,克服了用信息增益选择属性时偏向选择取值多的属性的不足,在树构造过程中进行剪枝;能够完成对连续属性的离散化处理;也能对不完整数据进行处理。C4.5算法产生的分类规则易于理解、准确率较高,但效率低,因树构造过程中,需要对数据集进行多次的顺序扫描和排序。也是因为必须多次数据集扫描,C4.5只适合于能够驻留于内存的数据集。

CART算法的全称是Classification And Regression Tree,采用的是Gini指数(选Gini指数最小的特征s)作为分裂标准,同时它也包含后剪枝操作。ID3算法和C4.5算法虽然在对训练样本集的学习中可以尽可能多地挖掘信息,但其生成的决策树分支较大。为了简化决策树的规模,提高生成决策树的效率,就出现了根据Gini系数来选择测试属性的决策树算法CART。MADlib中的决策树训练函数使用的就是CART算法。

6. 决策树特点

决策树最为显著的优点在于,利用它来解释一个受训模型是非常容易的,而且算法将最为重要的判断因素都很好地安排在了靠近树根位置。这意味着,决策树不仅对分类很有价值,而且对决策过程的解释也很有帮助。像贝叶斯分类器一样,可以通过观察内部结构来理解它的工作方式,同时这也有助于在分类过程之外进一步作出其它的决策。

因为决策树要寻找能够使信息增益最大化的分界线,因此它也可以接受数值型数据作为输入。能够同时处理分类数据和数值数据,对于许多问题的处理都是很有帮助的——这些问题往往是传统的统计方法(比如回归)所难以应对的。另外,决策树并不擅长对数值结果进行预测。一棵回归树可以将数据拆分成一系列具有最小方差的均值,但是如果数据非常复杂,则树就会变得非常庞大,以至于我们无法借此来做出准确的决策。

与贝叶斯决策相比,决策树的主要优点是它能够很容易地处理变量之间的相互影响。一个用决策树构建的垃圾邮件过滤器可以很容易地判断出:“online”和“pharmacy”在分开时并不代表垃圾信息,担当它们组合在一起时则为垃圾信息。

二、MADlib的决策树相关函数

MADlib中有三个决策树函数,分别为训练函数、预测函数和显示函数。训练函数接收输入的训练数据进行学习,生成决策树模型。预测函数用训练函数生成的决策树模型预测数据的所属分类。显示函数用来显示决策树模型。

1.训练函数

(1)语法

代码语言:javascript复制
tree_train
( training_table_name,  
    output_table_name,  
    id_col_name,  
    dependent_variable,  
    list_of_features,  
    list_of_features_to_exclude,  
    split_criterion,  
    grouping_cols,  
    weights,  
    max_depth,  
    min_split,  
    min_bucket,  
    num_splits,  
    pruning_params,  
    surrogate_params,  
    verbosity )

(2)参数

参数名称

数据类型

描述

training_table_name

TEXT

训练数据输入表名

output_table_name

TEXT

包含决策树模型的输出表名,如果表已经存在则报错。输出表列如表2所示。

id_col_name

TEXT

训练数据中,含有ID信息的列名。这是一个强制参数,用于预测和交叉验证。每行的ID值应该是唯一的。

dependent_variable

TEXT

包含用于训练的输出列名。分类的输出列是boolean、integer或text类型,回归的输出列是double precision类型。决策树的因变量可以为多个,训练函数的时间和空间复杂度,会随着因变量数量的增加呈线性增长。

list_of_features

TEXT

逗号分隔字符串,用于预测的特征列名,也可以用‘*’表示所有列都用于预测(除下一个参数中的列名外)。特征列的类型可以是boolean、integer、text或double precision。

list_of_features_to_exclude

TEXT

逗号分隔字符串,不用于预测的列名。如果自变量是一个表达式(包括列的类型转换),那么这个列表中应该包括用于自变量表达式的所有列名,否则那些列将被包含在特征中。

split_criterion

TEXT

缺省值为‘gini’,用于分类,而‘mse’用于回归。不纯度函数计算用于分裂的特征值。分类树支持的标准有‘gini’、‘entropy’或‘misclassification’,回归树的分裂标准总是使用‘mse’。

grouping_cols(可选)

TEXT

缺省值为NULL,逗号分隔字符串,分组的列名。将为每个分组产生一棵决策树。

weights(可选)

TEXT

权重列名

max_depth(可选)

INTEGER

缺省是10。最终决策树的最大深度,根的深度为0。

min_split(可选)

INTEGER

缺省值为20。一个试图被分裂的节点中,必须存在的元组的最小数量。此参数的最佳值取决于数据集的元组数目。

min_bucket(可选)

INTEGER

缺省值为min_split/3。任何叶节点对应的最小元组数量。如果min_split和min_bucket只指定一个,那么min_split设置成min_bucket*3,或者min_bucket设置成min_split/3。

num_splits(可选)

INTEGER

缺省值为100。为计算分割边界,需要将连续特征值分成离散型分位点。此全局参数用于计算连续特征的分割点,值越大预测越准,处理时间也越长。

pruning_params(可选)

TEXT

逗号分隔的键-值对,用于决策树剪枝,当前接受的值为: cp:缺省值为0。cp全称为complexity parameter,指某个点的复杂度,对每一步拆分,模型的拟合优度必须提高的程度。试图分裂一个节点时,分裂增加的精确度必须提高cp,才进行分裂,否则剪枝该节点。该参数值用于在运行检查验证前,创建一棵初始树。 n_folds:缺省值为0。用于计算cp最佳值的交叉验证褶皱数。为执行交叉验证,n_folds的值应该大于2。执行交叉验证时,会产生一个名为<model_table>_cv的输出表,其中包含估计的cp值和交叉验证错误。输出表中返回的决策树对应具有最少交叉错误的cp(如果多个cp值有相同的错误数,取最大的cp)。

surrogate_params

TEXT

逗号分隔的键值对,控制替代分裂点的行为。替代变量是与主预测变量相关的另一种预测变量,当主预测变量的值为NULL时使用替代变量。此参数当前接受的值为: max_surrogates 缺省值为0,每个节点的替代变量数。

verbosity

BOOLEAN

是否提供训练结果的详细输出,缺省值为FALSE。

表1 tree_train函数参数说明

训练函数生成的模型表具有以下列:

列名

数据类型

描述

<...>

TEXT

当提供了grouping_cols入参时,该列存储分组列,依赖于grouping_cols入参的值,可能有多列,类型与训练表相同。

tree

BYTEA8

二进制格式存储的决策树模型。

cat_levels_in_text

TEXT[]

分类变量的层次。

cat_n_levels

INTEGER[]

每个分类变量的层号。

tree_depth

INTEGER

剪枝前的决策树最大深度(根的深度为0)。

pruning_cp

FLOAT8[]

用于剪枝决策树的复杂性成本参数。如果使用交叉验证,该值应与pruning_params入参的值不同。

表2 tree_train函数输出模型表列

生成模型表的同时还会生成一个名为<model_table>_summary的概要表,具有以下列:

列名

数据类型

描述

method

TEXT

值为‘tree_train’。

is_classification

BOOLEAN

用于分类时为TRUE,用于回归时为FALSE。

source_table

TEXT

源表名。

model_table

TEXT

模型表名。

id_col_name

TEXT

ID列名。

dependent_varname

TEXT

因变量。

independent_varname

TEXT

自变量。

cat_features

TEXT

逗号分隔字符串,分类特征名称列表。

con_features

TEXT

逗号分隔字符串,连续特征名称列表。

grouping_col

TEXT

分组列名。

num_all_groups

INTEGER

训练决策树时的总分组数。

num_failed_groups

INTEGER

训练决策树时失败的分组数。

total_rows_processed

BIGINT

所有分组处理的总行数。

total_rows_skipped

BIGINT

所有分组中因为缺少值或失败而跳过的总行数。

dependent_var_levels

TEXT

对于分类,因变量的不同取值。

dependent_var_type

TEXT

因变量类型。

input_cp

FLOAT8[]

交叉验证前,用于剪枝决策树的复杂度参数。与pruning_params入参输入的值相同。

independent_var_types

TEXT

逗号分隔字符串,自变量类型。

表3 tree_train函数输出概要表列

(3)提示

  1. MADlib决策树训练函数的很多参数设计与流行的R语言函数‘rpart’相似。它们的一个重要区别是,对特征和分类变量,MADlib使用整型作为变量值类型,而rpart认为它们是连续的。
  2. 不使用替代变量时(max_surrogates=0),用于训练的特征值为NULL的行,在训练和预测时都被忽略。
  3. 不使用交叉验证时(n_folds=0),决策树依赖输入的cost-complextity(cp)进行剪枝。使用交叉验证时,所有节点cp都要大于参数cp。在进行交叉验证时,训练函数使用cp入参建立一个初始树,并探索所有可能的子树(直到单节点树),计算每个节点的cp进行剪枝,得到优化的子树。优化的子树及其相应的cp被放在输出表中,分别对应输出表的tree和pruning_cp列。
  4. 影响内存使用的参数主要是树的深度、特征的数量和每个特征的不同值的数量。如果遇到VMEM限制,要考虑降低这些参数的值。

2. 预测函数

(1)语法

代码语言:javascript复制
tree_predict(tree_model,  
             new_data_table,  
             output_table,  
             type)

(2)参数

参数名称

数据类型

描述

tree_model

TEXT

包含决策树模型的表名,应该是决策树训练函数的输出表。

new_data_table

TEXT

包含被预测数据的表名。该表应该和训练表具有相同的特征,也应该包含用于标识每行的id_col_name。

output_table

TEXT

预测结果的输出表名,如果表已经存在则报错。表中包含标识每个预测的id_col_name列,以及每个因变量的预测列。 如果type = 'response',表有单一预测列。此列的类型依赖于训练时使用的因变量的类型。 如果type = 'prob',每个因变量对应多列,每列表示因变量的一个可能值。列标识为‘estimated_prob_dep_value’,其中dep_value表示对应的因变量值。

type(可选)

TEXT

缺省值为‘response’。对于回归树,输出总是因变量的预测值。对于分类树,变量类型可以是‘response’或‘prob’。

表4 tree_predict函数参数说明

3. 显示函数

显示函数输出一个决策树的格式化表示。输出可以是dot格式,或者是一个简单的文本格式。dot格式可以使用GraphViz等程序进行可视化。

(1)语法

代码语言:javascript复制
tree_display(tree_model,dot_format, verbosity)

还有一个显示函数输出为每个内部节点选择的替代分裂点:

代码语言:javascript复制
tree_surr_display(tree_model)

(2)参数

参数名称

数据类型

描述

tree_model

TEXT

含有决策树模型的表名。

dot_format

BOOLEAN

缺省值为TRUE,使用dot格式,否则输出文本格式。

verbosity

BOOLEAN

缺省值为FALSE。如果设置为TRUE,dot格式输出中包含附加信息,如不纯度、样本大小、每个因变量的权重行数、被剪枝的分类或预测等。输出总是返回文本形式,对于dot格式,可以重定向输出到客户端文件,然后使用可视化程序处理。

表5 tree_display函数参数说明

三、决策树示例

我们将利用MADlib的决策树相关函数解决根据天气情况预测是否打高尔夫球的问题。问题描述及其已知数据参见“MADlib——基于SQL的数据挖掘解决方案(21)——分类之KNN”。

1. 准备输入数据

创建dt_golf表,将14条数据插入dt_golf表中。

代码语言:javascript复制
drop table if exists dt_golf;  
create table dt_golf 
( id integer not null,  
    "outlook" text,  
    temperature double precision,  
    humidity double precision,  
    windy text,  
    class text );  
  
copy dt_golf (id,"outlook",temperature,humidity,windy,class) from stdin with delimiter '|';  
1|sunny|85|85|'false'|'Don't Play'  
2|sunny|80|90|'true'|'Don't Play'  
3|overcast|83|78|'false'|'Play'  
4|rain|70|96|'false'|'Play'  
5|rain|68|80|'false'|'Play'  
6|rain|65|70|'true'|'Don't Play'  
7|overcast|64|65|'true'|'Play'  
8|sunny|72|95|'false'|'Don't Play'  
9|sunny|69|70|'false'|'Play'  
10|rain|75|80|'false'|'Play'  
11|sunny|75|70|'true'|'Play'  
12|overcast|72|90|'true'|'Play'  
13|overcast|81|75|'false'|'Play'  
14|rain|71|80|'true'|'Don't Play'  
.

2. 运行决策树训练函数

代码语言:javascript复制
drop table if exists train_output, train_output_summary, train_output_cv;    
select madlib.tree_train(
 'dt_golf',         -- 训练数据表    
 'train_output',    -- 输出模型表    
 'id',              -- id列    
 'class',           -- 因变量是分类    
 '"outlook", temperature, humidity, windy',   
 -- 四个属性是特征,注意加了双引号的outlook,要区分大小写    
 null::text,        -- 没有排除列    
 'gini',            -- 分裂标准使用gini    
 null::text,        -- 无分组    
 null::text,        -- 无权重    
 5,                 -- 因为只有4个特征,设置最大深度为5,防止过拟合    
 3,                 -- 最小分裂元组数    
 1,                 -- 最小桶数,min_split/3    
 10,                -- 每个连续性变量的离散型分位点数量  
 'cp=0,n_folds=6'   -- 初始cp=0,6折验证
 );

3. 查看输出

(1)查询概要表

代码语言:javascript复制
dm=# x  
Expanded display is on.
dm=# select * from train_output_summary;  
-[ RECORD 1 ]--------- -----------------------------------------------
method                | tree_train
is_classification     | t
source_table          | dt_golf
model_table           | train_output
id_col_name           | id
dependent_varname     | class
independent_varnames  | "outlook", windy, temperature, humidity
cat_features          | "outlook",windy
con_features          | temperature,humidity
grouping_cols         | 
num_all_groups        | 1
num_failed_groups     | 0
total_rows_processed  | 14
total_rows_skipped    | 0
dependent_var_levels  | "'Don't Play'  ","'Play'  "
dependent_var_type    | text
input_cp              | 0
independent_var_types | text, text, double precision, double precision

说明:

  • is_classification为t,指的是函数用于预测而不是回归。
  • "outlook",windy是分类特征,temperature,humidity是连续特征。
  • 因变量为文本类型,有‘Don'tPlay’和‘Play’两种取值。
  • 通过pruning_params参数设置剪枝使用的初始cp为0,交叉检验折皱数为6。

(2)查询决策树表

代码语言:javascript复制
dm=# x  
Expanded display is off.
dm=# select madlib.tree_display('train_output', false);  
                                   tree_display                                
------------------------------------------------------------------------------
   - Each node represented by 'id' inside ().                                     
   - Each internal nodes has the split condition at the end, while each               
       leaf node has a * at the end.                                              
   - For each internal node (i), its child nodes are indented by 1 level
       with ids (2i 1) for True node and (2i 2) for False node. 
   - Number of (weighted) rows for each response variable inside [].'
       The response label order is given as ['"'Don't Play'"', '"'Play'"']. 
   For each leaf, the prediction is given after the '-->'                  
      
 -------------------------------------                                          
 (0)[5 9]  "outlook" in {overcast}
    (1)[0 4]  * --> "'Play'"
    (2)[5 5]  temperature <= 75
       (5)[3 5]  temperature <= 65
          (11)[1 0]  * --> "'Don't Play'"
          (12)[2 5]  temperature <= 70
              (25)[0 3]  * --> "'Play'"
              (26)[2 2]  temperature <= 72
                  (53)[2 0]  * --> "'Don't Play'"
                  (54)[0 2]  * --> "'Play'"
       (6)[2 0]  * --> "'Don't Play'"
 -------------------------------------  
(1 row)

说明:

  • 输出为简单文本格式。
  • 节点号以0开始,代表根节点。如果树没有剪枝,后面的节点号应该是连续的1、2...n。而树被剪枝后,节点号是不连续的,正如上面查询结果中所显示的。带*号的节点是叶子节点,其它是内部测试节点。
  • 顺序值[x y]表示其测试节点上[“Don't play” “Play”]所占的行数。例如,在根节点0,有5行“Don't play”,9行“Play”。如果以特征“outlook”=overcast作为测试条件,结果有0行“Don't play”,4行“Play”,节点1为叶子结点。剩下5行“Don't play”与5行“Play”在节点2上测试temperature<=75。以此类推,从上之下解读输出结果。
  • 虽然我们的输入数据中提供了四个特征自变量,但训练函数输出的结果决策树中只体现出天气状况和气温,相对湿度与是否有风没有作为测试条件。

(3)以dot格式显示决策树

代码语言:javascript复制
t           -- 不显示表头  
o test.dot  -- 将查询结果输出到文件  
select madlib.tree_display('train_output', true, true);  -- 输出决策树详细信息 o  
t

生成dot文件后,使用第三方图形软件GraphViz画出决策树,执行以下shell命令:

代码语言:javascript复制
# 安装GraphViz  
yum -y install graphviz  
# 将dot文件转成jpg文件输出  
dot -Tjpg test.dot -o test.jpg

生成的决策树如图6所示。

图6 图形化显示dot格式决策树

图中显示的决策树与文本的输出一致,矩形为叶子节点,椭圆形为内部测试节点。除了文本输出的信息外,图中还多了一个impurity,代表不纯度,是指将来自集合中的某种结果随机应用在集合中,某一数据项的预期误差率。不纯度越小,集合的有序程度越高,分类的效果越好。叶子节点的不纯度为0。

本例中的四个自变量中,天气情况是具有三个值的标称属性,温度、湿度是连续数值属性,而是否有风为二元属性。下面我们手工计算Gini指标,验证上图决策树的划分。

1. 整个训练样本集关于类属性的Gini值

代码语言:javascript复制
1-(5/14)^2-(9/14)^2=0.459184

2. 天气情况的信息增益

正如前面提到的,标称属性可以产生二元划分和多路划分,由于MADlib使用的是CART算法,因此采用二元划分。天气情况有sunny、overcast、rain三种取值,因此有三种二元划分:{sunny} {overcast,rain},{sunny,overcast} {rain},{sunny,rain} {overcast}。每种划分的Gini指标计算如下:

天气情况

sunny

overcast,rain

Play数

2

7

Don’t Play数

3

2

Gini

0.48

0.3457

加权平均的Gini

5/14*0.48 9/14*0.3457 = 0.3937

天气情况

sunny,overcast

rain

Play数

6

3

Don’t Play数

3

2

Gini

0.4444

0.48

加权平均的Gini

9/14*0.4444 5/14*0.48 = 0.4571

天气情况

sunny,rain

overcast

Play数

5

4

Don’t Play数

5

0

Gini

0.5

0

加权平均的Gini

10/14*0.5 4/14*0 = 0.3571

第三种划分得到的Gini值最小,信息增益最大,因此如果按照“天气情况”,应该按第三种划分。

3. 是否有风的信息增益

是否有风

TRUE

FALSE

Play数

3

6

Don’t Play数

3

2

Gini

0.5

0.375

加权平均的Gini

6/14*0.5 8/14*0.375 = 0.4286

由于Gini指标大于天气情况划分,增益小于天气划分,故不作为优先的划分方法。

4. 温度增益

排序后的温度

划分点

Play数

Don’t Play数

Gini

加权平均的Gini

64

<=

1

0

0.0000

0.4396

>

8

5

0.4734

65

<=

1

1

0.5000

0.4523

>

8

4

0.4444

68

<=

2

1

0.4444

0.4589

>

7

4

0.4628

69

<=

3

1

0.3750

0.4500

>

6

4

0.4800

70

<=

4

1

0.3200

0.4317

>

5

4

0.4938

71

<=

4

2

0.4444

0.4583

>

5

3

0.4688

72

<=

5

3

0.4688

0.4583

>

4

2

0.4444

75

<=

7

3

0.4200

0.4429

>

2

2

0.5000

80

<=

7

4

0.4628

0.4589

>

2

1

0.4444

81

<=

8

4

0.4444

0.4523

>

1

1

0.5000

83

<=

9

4

0.4260

0.3956

>

0

1

0.0000

85

<=

9

5

0.4592

0.4592

>

0

0

0.0000

显然,每种温度划分的信息增益都小于天气情况({sunny,rain} {overcast})。

5. 湿度增益

排序后的湿度

划分点

Play数

Don’t Play数

Gini

加权平均的Gini

65

<=

1

0

0.0000

0.4396

>

8

5

0.4734

70

<=

3

1

0.3750

0.4500

>

6

4

0.4800

75

<=

4

1

0.3200

0.4317

>

5

4

0.4938

78

<=

5

1

0.2778

0.4048

>

4

4

0.5000

80

<=

7

2

0.3457

0.3937

>

2

3

0.4800

85

<=

7

3

0.4200

0.4429

>

2

2

0.5000

90

<=

8

4

0.4444

0.4523

>

1

1

0.5000

95

<=

8

5

0.4734

0.4396

>

1

0

0.0000

96

<=

9

5

0.4592

0.4592

>

0

0

0.0000

显然,每种湿度划分的信息增益都小于天气情况({sunny,rain} {overcast})。

根据以上信息增益情况,MADlib的决策树函数首次选择{sunny,rain} {overcast}划分。同理,按照同样的Gini指标方式计算,递归构造出整颗决策树。

(4)查询交叉验证结果表

代码语言:javascript复制
dm=# select * from train_output_cv; 
 cp  |      cv_error_avg      |              cv_error_stddev                 
----- ------------------------ --------------------------------------------  
   0 | 0.54861111111111111111 | 0.1827757821265895499515345089798222389039  
 0.2 | 0.58333333333333333333 | 0.2297341458681703628002325108823829393492  
(2 rows)

可以看到,随着cp值的增大,剪枝增多,精度降低。

5. 分析决策树

分类树算法可以通过特征,找出最好地解释因变量class(是否打高尔夫球)的方法。

  • 变量outlook的范畴被划分为两组:多云和其它。我们得出第一个结论:如果天气是多云,人们总是选择玩高尔夫。
  • 在不是多云天气时,以气温划分,当气温高于华氏75度或低于华氏65度时,没人打高尔夫;65到70度之间人们选择玩高尔夫,70到72度之间没人打;72到75度会打。

这就通过分类树给出了一个解决方案。小王在晴天、雨天并且气温过高或过低时解雇了大部分员工,因为这种天气不会有人打高尔夫。而其他的天气会有很多人打高尔夫,因此可以雇用一些临时员工来工作。

6. 用决策树模型进行预测

从交叉验证结果看,即便是初始cp=0,得到的标准差仍然较大,这和我们的样本数据过少有关。从本示例的分析中就可以看到,65到70度、72到75度会打高尔夫,而70到72度之间没人打,这个预测显然有悖常理。现在就用此模型预测一下原始数据,再和实际情况对比一下。这里只是演示一下如何用模型进行预测,实践中训练数据集与预测数据集相同意义不大。

代码语言:javascript复制
drop table if exists prediction_results;  
select madlib.tree_predict
('train_output',          -- 决策树模型  
 'dt_golf',               -- 被预测的数据表  
 'prediction_results',    -- 预测结果表  
 'response');             -- 预测结果  
select t1.*,t2.class   
  from prediction_results t1, dt_golf t2   
 where t1.id=t2.id order by id;

查询结果如下:

代码语言:javascript复制
 id | estimated_class |    class       
---- ----------------- --------------  
  1 | 'Don't Play'    | 'Don't Play'  
  2 | 'Don't Play'    | 'Don't Play'  
  3 | 'Play'          | 'Play'  
  4 | 'Play'          | 'Play'  
  5 | 'Play'          | 'Play'  
  6 | 'Don't Play'    | 'Don't Play'  
  7 | 'Play'          | 'Play'  
  8 | 'Don't Play'    | 'Don't Play'  
  9 | 'Play'          | 'Play'  
 10 | 'Play'          | 'Play'  
 11 | 'Play'          | 'Play'  
 12 | 'Play'          | 'Play'  
 13 | 'Play'          | 'Play'  
 14 | 'Don't Play'    | 'Don't Play'  
(14 rows)  

0 人点赞