一、决策树的基本概念
决策树(Decision Tree)算法是一类常用的机器学习算法,在分类问题中,决策树算法通过样本中某一些属性的值,将样本划分到不同的类别中。
决策树跟人在做决策的思考方式很想像,先考虑重点选项,不符合则可最优先做出决策。
1. 划分标准
在决定是否去见相亲对象时,该女提出了4个特征来做决策,这些特征在做决策的过程中是存在一定顺序的,首先选择了相貌,可能是因为她是外貌协会,这个通不过就不打算再考虑后续的特征了,决策性较强。
在决策树的算法中,通常用标准来确定特征的先后顺序:信息增益(Information Gain)、增益率(Gain Ratio)和基尼指数(Gini Index)。
首先介绍一下 熵(Entropy) 的概念,熵是度量样本集合纯度最常用的指标,对于包含m个训练样本的数据集:D{(X(1),y(1)),…,(X(m),y(m))}, 在数据集D中,第k类的样本所占的比例为pk,则数据集D的信息熵为:
其中,k表示数据集D中类别的个数,pk为类别k的数量占数据集总数量的比例。
假定有10个相亲对象,各特征值如下:
相关对象 | 相貌 | 才华 | 羽毛球 | 游泳 | 是否去见 |
---|---|---|---|---|---|
1 | 帅 | 会识字 | 小白 | 热爱 | 不去 |
2 | 不帅 | 会识字 | 教练级 | 热爱 | 不去 |
3 | 不帅 | 会识字 | 小白 | 早鸭子 | 不去 |
4 | 不帅 | 才华横溢 | 小白 | 热爱 | 不去 |
5 | 不帅 | 会识字 | 小白 | 热爱 | 不去 |
6 | 不帅 | 会识字 | 小白 | 早鸭子 | 不去 |
7 | 帅 | 才华横溢 | 教练级 | 热爱 | 去 |
8 | 不帅 | 会识字 | 小白 | 早鸭子 | 不去 |
9 | 不帅 | 会识字 | 小白 | 热爱 | 不去 |
10 | 不帅 | 会识字 | 教练级 | 早鸭子 | 不去 |
以如上数据为例,其信息熵为:
from math import log
from collections import defaultdict
def calculate_entropy(data):
total_sample = len(data)
if len(data) == 0:
return 0
label_counts = label_unique_cnt(data) #统计数据集中不同标签的个数
# 计算数据集中的Entropy
entropy = 0
for label in label_counts.keys():
class_ratio = label_counts[label]/total_sample
entropy -= class_ratio * log(class_ratio,2)
return entropy
def label_unique_cnt(data):
label_unique_cnt = defaultdict(int)
for x in data:
label = x[len(x) -1] # 取得每一个样本的类标签
label_unique_cnt[label] += 1
return label_unique_label
当样本按照特征“相貌”划分成两个子数据集D1和D2时,此时整个数据集D的熵为两个独立数据集D1和D2的熵的加权和,即:
由上述的划分可知,在用“相貌”划分后数据集D的信息熵减小了,对于给定的数据集,划分前后数据集信息熵的减少量称为__信息增益(Information Gain)__,即:
在选择数据集划分的标准时,通过选择能够使得信息增益最大的划分。
ID3决策树算法就是利用信息增益作为划分数据集的一种方法。
增益率(Gain Ratio) 是可以作为最优划分属性的方法,
其中,IV(A)是特征A的"固有值(Intrinsic Value)",即
意思是指按特征A划分成两个子数据集的信息熵,不考虑类别的情况,举例说明:
在著名的C4.5决策树算法中就是利用增益率作为划分数据集的方法。
基尼指数(Gini Index) 也可以选择最优的划分属性,Gini值越小表示纯度越高。对于数据集D,假设有n个分类,则样本属于第k个分类的概率为pk,则此概率分布的基尼指数为:
其中,Ck表示数据集D中属于类别k的样本个数。
还是利用相亲对象的数据为例,数据集的基尼指数为:
利用特征“相貌”将数据集划分后,基尼指数为:
在CART决策树算法中利用Gini指数作为划分数据集的方法。
下面用Python来计算Gini指数:
from math import pow
def calculate_gini_index(data):
total_sample = len(data)
if len(data) == 0:
return 0
label_counts = label_unique_cnt(data) #统计数据集中不同标签的个数
# 计算数据集中的gini指数
gini = 0
for label in label_counts.keys():
gini += pow(label_counts[label],2)
gini = 1- float(gini)/pow(total_sample,2)
return gini
2.停止划分的标准
在按照特征对数据集进行进行划分时,需设置划分的终止条件。通常在算法过程中设置终止条件的方法有:
- 结点中的样本数小于给定阈值
- 样本集的Gini指数小于给定值
- 没有更多特征
在sklearn的决策树模型中,就有相关的参数用来设置终止划分的条件:
DecisionTreeClassifier
class sklearn.tree.DecisionTreeClassifier(criterion=’gini’, splitter=’best’, max_depth=None, min_samples_split=2, min_samples_leaf=1, min_weight_fraction_leaf=0.0, max_features=None, random_state=None, max_leaf_nodes=None, min_impurity_decrease=0.0, min_impurity_split=None, class_weight=None, presort=False)
max_depth - 最大深度,因为有些特征可能被重复选择用来划分,导致可能出深度相对特征会大得多的情况;
min_samples_split - 被划分时,该结点所需含的最少数量;
min_samples_leaf - 被划分后的叶结点中最少应包含的数量;
min_weight_fraction_leaf - 叶结点中,相对总数据集的最小加权分数
max_leaf_nodes - 叶结点数上限
min_impurity_decrease - 不纯度减少的阈值,若该结点被划分后减少的不纯度>=此值,则将被划分
min_impurity_split - 不纯度阈值,只有该结点的不纯度>=此值,该结点才会被划分,否则为叶结点
二、ID3算法
Iterative Dichotomizer, 迭代二分类器。
算法核心:在决策树各个子结点上应用信息增益准则来选择特征,递归地构建决策树,已经用来划分过的特征将不再重复使用。
只有树的生成,容易过拟合,分得太细,考虑条件太多。
缺点:1)用信息增益选择特征时,偏向于选择分枝比较多的属性值;
2)不能处理连续属性
三、C4.5算法
对ID3算法的改进,主要为:
- 用信息增益率来选择属性
- 能处理非离散数据
- 能处理不完整数据(抛弃有特征值缺失的样本)
- 决策树构造后会对树进行剪枝
连续型变量计算信息增益:由小->大递增排序,取相邻两个值的中点作为分裂点,然后按离散型来计算IG,取最大IG的点为分裂点,如连续数据列(10.2, 12, 14, 16, 18, …):
以分裂点11.1, 13, 15, 17, …分别计算IG,假如在数值13的IG是最大的,则以13来将数据划分成: <13 和 >13两个子数据集。
四、CART算法
Classification And Regression Tree, 分类与回归树,属于二叉树,可创建分类树和回归树,既能处理分类问题也可以处理回归问题。
- CART作为分类树
分类属性是离散类型,特征属性可以是离散型或连续型。
在节点分裂时,使用Gini指数作为划分的指标,遍历所有属性及其可能的分裂点,寻找最佳切分属性及其最佳分裂点,使得切分后的Gini指数最小,利用该属性及其最佳分裂点将训练数据划分成两个子数据集,重复该划分步骤,直到子数据集均为同一类别或者用尽特征。
对于取值数N>=3的特征属性,因只能有两个分支,需人为创建二取值序列并取使切分之后的Gini指数最小的点作为树分叉决策点。
例如:size取值为[“S”,“M”,“L”],二分序列有三种情况:
- [“S", (“M”,“L”)]
- [“M”,(“S”,“L”)]
- [“L”,(“S”,“M”)]
对于连续属性,需转换成离散属性:
- 先排序(升序)
- 取中点作为可能分裂点,计算那些使分类属性发生改变的特征值的Gini指数
- 选择Gini指数最小的点作为该特征的最佳分裂点
注意:离散特征分支划分数据集时,子数据集中不再包含该特征;而连续特征分支时,各子数据集依旧包含该特征,在接下来的树分支过程中可能被再次用来树分支。
class node:
'''树的节点的类
'''
def __init__(self, fea=-1, value=None, results=None, right=None, left=None):
self.fea = fea # 用于切分数据集的属性的列索引值
self.value = value # 设置划分的值
self.results = results # 存储叶节点所属的类别
self.right = right # 右子树
self.left = left # 左子树
def split_tree(data, fea, value):
'''根据特征fea中的值value将数据集data划分成左右子树
input: data(list):数据集
fea(int):待分割特征的索引
value(float):待分割的特征的具体值
output: (set1,set2)(tuple):分割后的左右子树
'''
set_1 = []
set_2 = []
for x in data:
if x[fea] >= value:
set_1.append(x)
else:
set_2.append(x)
return (set_1, set_2)
def build_tree(data):
'''构建树
input: data(list):训练样本
output: node:树的根结点
'''
# 构建决策树,函数返回该决策树的根节点
if len(data) == 0:
return node()
# 1、计算当前的Gini指数
currentGini = calculate_gini_index(data)
bestGain = 0.0
bestCriteria = None # 存储最佳切分属性以及最佳切分点
bestSets = None # 存储切分后的两个数据集
feature_num = len(data[0]) - 1 # 样本中特征的个数
# 2、找到最好的划分
for fea in range(0, feature_num):
# 2.1、取得fea特征处所有可能的取值
feature_values = {} # 在fea位置处可能的取值
for sample in data: # 对每一个样本
feature_values[sample[fea]] = 1 # 存储特征fea处所有可能的取值
# 2.2、针对每一个可能的取值,尝试将数据集划分,并计算Gini指数
for value in feature_values.keys(): # 遍历该属性的所有切分点
# 2.2.1、 根据fea特征中的值value将数据集划分成左右子树
(set_1, set_2) = split_tree(data, fea, value)
# 2.2.2、计算当前的Gini指数
nowGini = float(len(set_1) * calculate_gini_index(set_1) +\
len(set_2) * calculate_gini_index(set_2)) / len(data)
# 2.2.3、计算Gini指数的增加量
gain = currentGini - nowGini
# 2.2.4、判断此划分是否比当前的划分更好
if gain > bestGain and len(set_1) > 0 and len(set_2) > 0:
bestGain = gain
bestCriteria = (fea, value)
bestSets = (set_1, set_2)
# 3、判断划分是否结束
if bestGain > 0:
right = build_tree(bestSets[0])
left = build_tree(bestSets[1])
return node(fea=bestCriteria[0], value=bestCriteria[1],\
right=right, left=left)
else:
return node(results=label_unique_cnt(data))
# 返回当前的类别标签作为最终的类别标签
def predict(sample, tree):
'''对每一个样本sample进行预测
input: sample(list):需要预测的样本
tree(类):构建好的分类树
output: tree.results:所属的类别
'''
# 1、只是树根
if tree.results != None:
return tree.results
else:
# 2、有左右子树
val_sample = sample[tree.fea]
branch = None
if val_sample >= tree.value:
branch = tree.right
else:
branch = tree.left
return predict(sample, branch)
- CART作为回归树
<待补充>
参考的文献
——————————————————————————————————————
[1] 赵志勇. Python机器学习算法. 北京: 电子工业出版社. 2017.
[2] Wikipedia. ID3算法,https://en.wikipedia.org/wiki/ID3_algorithm
[3] Wikipedia. C4.5算法,https://en.wikipedia.org/wiki/C4.5_algorithm