决策树
ID3 算法
最直接也最简单的 ID3 算法(Iterative Dichotomiser 3)。
该算法的核心是:以信息增益为度量,选择分裂后信息增益最大的特征进行分裂。
首先我们要了解一个概念——信息熵。
假设一个随机变量 x 有 n 种取值,分别为
{x1,x1,…,xn},每一种取值取到的概率分别是
{p1,p2,…,pn},那么 x 的信息熵定义为:
熵表示的是信息的混乱程度,信息越混乱,熵值越大。
设 S 为全部样本的集合,全部的样本一共分为 n 个类,则:
其中,pi 为属于第 i 个类别的样本,在总样本中出现的概率。
接下来要了解的概念是信息增益,信息增益的公式为(下式表达的是样本集合 S 基于特征 T 进行分裂后所获取的信息增益):
其中:
S 为全部样本集合,
|S|为S 的样本数;
T为样本的一个特征;
value(T) 是特征
T 所有取值的集合;
v 是 T 的一个特征值;
Sv 是 S 中特征 T 的值为 v 的样本的集合,
|Sv|为Sv 的样本数。
C4.5算法
ID3选用信息增量作为特征度量,其存在一大缺点:ID3一般会优先选择取值种类较多的特征作为分类特征。
因为取值种类多的特征会有相对较大的信息增益----信息增益反映的是给定一个条件以后不确定性被减少的程度,必然是分得越细的数据集确定性更高。
被取值多的特征分裂,分裂成的结果也就容易细,分裂结果越细,则信息增益越大。
为了避免此项不足,在ID3基础上提出了改进版C4.5算法
C4.5选用信息增益率,用比例而不是单纯的量--作为选择分支的标准。
信息增益率通过引入一个被称作分裂信息的项,来惩罚取值性较多的特征
ID3还有问题:就是不能处理在连续区间的特征。例如上面的例子里,假设训练的样本有一个特征是年龄吗,取值为(0,100)区间内的实数。ID3就GG了。
C4.5在这方面也进行了弥补:
1.把需要处理的样本(对应整课树)或样本子集(对应子树)按照连续变量的大小从小到大进行排序。
2.假设所有的m个样本数据在特征上的实际取值一共有k(k<m)个,那么总共有k-1个可能的候选分割阈值点,每个候选的分割阈值点的值为上述排序后的特征值中两两前后连续元素的中点。根据这k-1个分割点把原来连续的一个特征,转化为k-1个Bool特征。
3.用信息增益率选择这k-1个特征的最佳划分
但是C4.5存在一个问题:当某个|Sv|的大小跟|S|的大小接近的时候:
为了避免这种情况导致某个其实无关紧要的特征占据根节点,可以采用启发式的思路,对每一个特征先进行信息增益率的计算,在其增益率较高的情况下,才应用信息增益率作为分裂标准
C4.5 的优良性能和对数据和运算力要求都相对较小的特点,使得它成为了机器学习最常用的算法之一。它在实际应用中的地位,比 ID3 还要高。
CART
ID3和C4.5构造的都是分类树。还有一种算法,在决策树中应用非常广泛,他就是CART算法
CART算法的全称是:Classification and Regression Tree ,分类和回归树。CART算法的运行过程和ID3和C4.5大致相同,不同之处在于:
1.CART的特征选取依据不是增益率或者增益量,而是GINI系数。每次选择基尼系数最小的特征作为最优切分点;
2.CART是一颗严格二叉树。每次分裂只做二分
GINI系数原本是一个统计学概念,20世纪初由意大利学者科拉多.基尼提出,是用来判断年收入分配公平程度的指标。GINI系数本身是一个比例数,取值在0-1之间。
当GINI系数用于评判一个国家的民众收入时,取值越小,说明年收入分配越平均,反之越集中。当基尼系数=0时,说明国家收入在所有国民中平均分配,当基尼系数=1时,说明掌握在一人手中。
在GINI系数出现之前,美国经济学家马克斯.劳伦茨提出了“收入分配的曲线”的概念。如下图
图中横轴为人口累计百分比,纵轴为该部分人的收入占全部人口总收入的百分比,红色线段表示人口收入分配处于绝对平均状态,而橘色曲线就是劳伦茨曲线,表现的是实际的收入分配情况。
我们可以看出,横轴75%处,如果依据红色线段,对应的纵轴也是75%,但是按照橘色曲线,则对应纵轴只有不到40%。
A是红色线段和橘色曲线所夹部分面积,而B是橘色曲线下部分的面积。GINI系数实际上就是
这个概念在经济学领域中远比机器学习中有名。
基尼系数的计算方法是:
对于二分类问题,若样本属于第一类的概率是p,则:
这时,如果p=0.5,则基尼系数为0.5;如果p=0.9,则基尼系数为0.18。0.18<0.5,根据CART的原则,当p=0.9时,这个特征更容易被选中作为分裂特征。
由此可见,对二分类问题中,两种可能性的概率越不平均,则越可能是更佳优越的切分点。
上面的例子虽然用的二分类,但实际上,对于多分类,趋势也是一样的,哪些概率分布在不同可能性之间越不平均的特征,越容易成为分裂特征。
到了这里可能产生误会, 认为我们一直说的都是用CART做分类的做法,回归去哪里了?
回归树和分类树的区别在于最终的输出值到底是连续的还是离散的,每一个特征—也就是分裂点决策条件----无论特征值本身是连续的还是离散的,都要被当做离散的来处理,而且都是被转化为二分类特征,来进行处理:
1.如果对应的分裂特征是连续的,处理与C4.5算法类似
2.如果特征的离散的,而该特征总共有k个取值,则将这个特征转化为k个特征,对每一个新特征按照是不是取这个值来分yes和no
二 决策树实例
Hello Kitty,一只以无嘴造型40年来风靡全球的萌萌猫,在其40岁生日时,居然被其形象拥有者宣称:Hello Kitty 不是猫!
2014年8月,研究 Hello Kitty 多年的人类学家 Christine R. Yano 在写展品解说时,却被 Hello Kitty 持有商三丽鸥纠正:Hello Kitty 是一个卡通人物,她是一个小女孩,是一位朋友,但她“绝不”是一只猫。
粉了快半个世纪的世界萌猫,你说是人就是人啦?!就算是形象持有者,也没权利下这个定论啊!
谁有权认定 Hello Kitty 是人是猫呢?我们把裁决权交给世界上最公正无私的裁判—— 计算机。让机器来决定。
特征选取
我们提取七个特征,用来判断一个形象,是人是猫。这七个特征包括:有否蝴蝶结;是否穿衣服;是否高过5个苹果;是否有胡子;是否圆脸;是否有猫耳朵;是否两脚走路
用ID3算法构造分类树
1.根据信息熵的概念,我们先来计算Entropy(S)。因为总共只有两个类别:人和猫,因此n=2;
2.然后再分别计算各个特征:
因为无论哪个特征,都只有两个特征值:yes或者no,因此value(T)总共只有两个取值。
下面以“Has a bow”为例来示意其计算过程。
3.进一步计算,得出 InfoGain(Has cat ears) 最大,因此“Has cat ears”是第一个分裂节点。
而从这一特征对应的类别也可以看出,所有特征值为 No 的都一定是 Girl;特征值为 Yes,可能是 Girl 也可能是 Cat,那么第一次分裂,我们得出如下结果
现在“has cat ears”已经成为了分裂点,则下一步将其排除,用剩下的6个Feature继续分裂树:
上表作为第二次分裂所使用的训练数据,如此反复迭代,最后使得7个特征都成为分裂点。
需要注意的是,如果某个特征被选为当前轮的分裂点,但是它现存数据中只有一个值,另一个值对应的记录为空,则这个时候针对不存在的特征值,将他标记为该特征在所有训练数据中所占比例最大的类型。
对本例而言,当我们将“wear clothes”作为分裂点时,会发现该特征只剩下了一个选项yes,妈耶!!!
都是yes,怎么分叉啊?
这时就要看第一张数据表,“Wear Clothes”为 No 的记录中是 Girl 多还是 Cat 多。一目了然,在 Table-1 中这两种记录数量为 0:6,因此“Wear Clothes”为 No 的分支直接标志成 Cat。
根据上述方法,我们最终构建如下图的决策树:
DecisionTree induceTree(training_set, features) {
If(training_set中所有的输入项都被标记为同一个label){
return 一个标志位该label的叶子节点;
} else if(features为空) {
# 默认标记为在所有training_set中所占比例最大的label
return 一个标记为默认label的叶子节点;
} else {
选取一个feature,F;
以F为根节点创建一棵树currentTree;
从Features中删除F;
foreach(value V of F) {
将training_set中feature F的取值为V的元素全部提取出来,组成partition_v;
branch_v= induceTree(partition_V, features);
将branch_v添加为根节点的子树,根节点到branch_v的路径为F的V值;
}
returncurrentTree;
}
}
后剪枝优化决策树
决策树剪枝
1.先剪枝(局部剪枝):在构造过程中,当某个节点满足剪枝条件,则直接停止此分支的构造。
2.后剪枝(全局剪枝):先构造完成完整的决策树,再通过某些条件遍历树进行剪枝
后剪枝优化 Hello Kitty 树
现在,决策树已经构造完成,所以我们采用后剪枝法,对上面决策树进行修剪。
在决策树中最后两个分裂点“Has round face”和“Has a bow”存在并无意义想想也是啊,无论人猫,都有可能是圆脸,也都可以戴蝴蝶结啊。
所以我们遍历所有节点,将没有区分作用的节点删除。完成后,我们的决策树变了。
from sklearn import tree
from sklearn.model_selection im
port train_test_split
import numpy as np
#9个女孩和8只猫的数据,对应7个feature,yes取值为1,no为0
features = np.array([
[1, 1, 0, 0, 1, 0, 1],
[1, 1, 1, 0, 0, 0, 1],
[0, 1, 0, 0, 0, 0, 1],
[1, 1, 0, 0, 1, 0, 1],
[0, 1, 0, 0, 1, 0, 0],
[0, 1, 0, 0, 1, 0, 1],
[1, 1, 0, 0, 1, 0, 1],
[0, 1, 0, 0, 1, 0, 1],
[0, 1, 0, 1, 1, 1, 1],
[1, 0, 1, 1, 1, 1, 0],
[0, 0, 0, 1, 1, 1, 0],
[1, 0, 1, 1, 1, 1, 0],
[0, 0, 0, 1, 1, 1, 0],
[1, 0, 0, 1, 1, 1, 0],
[0, 0, 1, 0, 1, 1, 0],
[1, 1, 1, 1, 1, 1, 0],
[1, 0, 1, 1, 1, 1, 0]
])
#1 表示是女孩,0表示是猫
labels = np.array([
[1],
[1],
[1],
[1],
[1],
[1],
[1],
[1],
[1],
[0],
[0],
[0],
[0],
[0],
[0],
[0],
[0],
])
# 从数据集中取20%作为测试集,其他作为训练集
X_train, X_test, y_train, y_test = train_test_split(
features,
labels,
test_size=0.2,
random_state=0,
)
# 训练分类树模型
clf = tree.DecisionTreeClassifier()
clf.fit(X=X_train, y=y_train)
# 测试
print(clf.predict(X_test))
# 对比测试结果和预期结果
print(clf.score(X=X_test, y=y_test))
# 预测HelloKitty
HelloKitty = np.array([[1,1,1,1,1,1,1]])
print(clf.predict(HelloKitty))