决策树(Decision Tree)算法是一类常用的机器学习算法,在分类问题中,决策树算法通过样本中某一些属性的值,将样本划分到不同的类别中。
决策树跟人在做决策的思考方式很想像,先考虑重点选项,不符合则可最优先做出决策。
在决定是否去见相亲对象时,该女提出了4个特征来做决策,这些特征在做决策的过程中是存在一定顺序的,首先选择了相貌,可能是因为她是外貌协会,这个通不过就不打算再考虑后续的特征了,决策性较强。
在决策树的算法中,通常用标准来确定特征的先后顺序:信息增益(Information Gain)、增益率(Gain Ratio)和基尼指数(Gini Index)。
首先介绍一下 熵(Entropy) 的概念,熵是度量样本集合纯度最常用的指标,对于包含m个训练样本的数据集:D{(X(1),y(1)),…,(X(m),y(m))}, 在数据集D中,第k类的样本所占的比例为pk,则数据集D的信息熵为:
E n t r o p y ( D ) = − ∑ k = 1 k p k l o g 2 p k Entropy(D) =- \sum_{k=1}^k p_k\space log_2\space p_k Entropy(D)=−k=1∑kpk log2 pk
其中,k表示数据集D中类别的个数,pk为类别k的数量占数据集总数量的比例。
假定有10个相亲对象,各特征值如下:
相关对象 | 相貌 | 才华 | 羽毛球 | 游泳 | 是否去见 |
---|---|---|---|---|---|
1 | 帅 | 会识字 | 小白 | 热爱 | 不去 |
2 | 不帅 | 会识字 | 教练级 | 热爱 | 不去 |
3 | 不帅 | 会识字 | 小白 | 早鸭子 | 不去 |
4 | 不帅 | 才华横溢 | 小白 | 热爱 | 不去 |
5 | 不帅 | 会识字 | 小白 | 热爱 | 不去 |
6 | 不帅 | 会识字 | 小白 | 早鸭子 | 不去 |
7 | 帅 | 才华横溢 | 教练级 | 热爱 | 去 |
8 | 不帅 | 会识字 | 小白 | 早鸭子 | 不去 |
9 | 不帅 | 会识字 | 小白 | 热爱 | 不去 |
10 | 不帅 | 会识字 | 教练级 | 早鸭子 | 不去 |
以如上数据为例,其信息熵为:
E n t r o p y ( D ) = − ∑ k = 1 2 p k l o g 2 p k = − ( 1 10 l o g 2 1 10 + 9 10 l o g 2 9 10 ) = 0.469 Entropy(D) =- \sum_{k=1}^2 p_k\space log_2\space p_k=-({1 \over 10} log_2{1 \over 10} + {9 \over 10} log_2{9 \over 10}) =0.469 Entropy(D)=−k=1∑2pk log2 pk=−(101log2101+109log2109)=0.469
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的熵的加权和,即:
E n t r o p y ( D ) = − [ 2 10 ( 1 2 l o g 2 1 2 + 1 2 l o g 2 1 2 ) + 8 10 ( l o g 2 1 ) ] = 0.2 Entropy(D) =-[{2 \over 10}({1 \over 2} log_2{1 \over 2} + {1 \over 2} log_2{1 \over 2})+{8 \over 10}( log_2{1}) ] =0.2 Entropy(D)=−[102(21log221+21log221)+108(log21)]=0.2
由上述的划分可知,在用“相貌”划分后数据集D的信息熵减小了,对于给定的数据集,划分前后数据集信息熵的减少量称为__信息增益(Information Gain)__,即:
I G ( D , 相 貌 ) = 0.469 − 0.2 = 0.269 IG(D, 相貌) = 0.469-0.2 = 0.269 IG(D,相貌)=0.469−0.2=0.269
在选择数据集划分的标准时,通过选择能够使得信息增益最大的划分。
ID3决策树算法就是利用信息增益作为划分数据集的一种方法。
增益率(Gain Ratio) 是可以作为最优划分属性的方法,
G a i n R a t i o ( D , A ) = I G ( D , A ) I V ( A ) GainRatio(D, A) = { IG(D, A) \over IV(A)} GainRatio(D,A)=IV(A)IG(D,A)
其中,IV(A)是特征A的"固有值(Intrinsic Value)",即
I V ( A ) = − ∑ p = 1 p ∣ D p ∣ ∣ D ∣ l o g 2 ∣ D p ∣ ∣ D ∣ IV(A) = - \sum_{p=1}^p {\vert D~p~\vert \over \vert D\vert}\space log_2\space {\vert D~p~\vert \over \vert D\vert} IV(A)=−p=1∑p∣D∣∣D p ∣ log2 ∣D∣∣D p ∣
意思是指按特征A划分成两个子数据集的信息熵,不考虑类别的情况,举例说明:
I V ( 相 貌 ) = − 2 10 l o g 2 2 10 − 8 10 l o g 2 8 10 = 0.72 IV(相貌) = -{2 \over 10}log_2{2 \over 10} - {8 \over 10} log_2{8 \over 10} =0.72 IV(相貌)=−102log2102−108log2108=0.72
G a i n R a t i o ( D , 相 貌 ) = I G ( D , A ) I V ( A ) = 0.269 0.72 = 0.374 GainRatio(D, 相貌) = { IG(D, A) \over IV(A)} = {0.269 \over 0.72 }= 0.374 GainRatio(D,相貌)=IV(A)IG(D,A)=0.720.269=0.374
在著名的C4.5决策树算法中就是利用增益率作为划分数据集的方法。
基尼指数(Gini Index) 也可以选择最优的划分属性,Gini值越小表示纯度越高。对于数据集D,假设有n个分类,则样本属于第k个分类的概率为pk,则此概率分布的基尼指数为:
G i n i ( D ) = 1 − ∑ k = 1 k ( ∣ C k ∣ ∣ D ∣ ) 2 Gini(D) = 1 - \sum_{k=1}^k ({ {\vert C_k \vert} \over {\vert D \vert} })^2 Gini(D)=1−k=1∑k(∣D∣∣Ck∣)2
其中,Ck表示数据集D中属于类别k的样本个数。
还是利用相亲对象的数据为例,数据集的基尼指数为:
G i n i ( D ) = 1 − ∑ k = 1 2 ( p k ) 2 = 1 − [ ( 1 10 ) 2 + ( 9 10 ) 2 ] = 0.18 Gini(D) = 1 - \sum_{k=1}^2 (p_k)^2 = 1 - [({1 \over 10})^2 + ({9 \over 10} )^2] = 0.18 Gini(D)=1−k=1∑2(pk)2=1−[(101)2+(109)2]=0.18
利用特征“相貌”将数据集划分后,基尼指数为:
G i n i ( D , 相 貌 ) = 2 10 [ 1 − ( ( 1 2 ) 2 + ( 1 2 ) 2 ) ] + 8 10 [ 1 − 1 ] = 0.1 Gini(D,相貌) = {2 \over 10}[1 - \Big( ({1 \over 2})^2 + ({1 \over 2})^2 \Big)] + {8 \over 10}[1-1] = 0.1 Gini(D,相貌)=102[1−((21)2+(21)2)]+108[1−1]=0.1
在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
在按照特征对数据集进行进行划分时,需设置划分的终止条件。通常在算法过程中设置终止条件的方法有:
在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 - 不纯度阈值,只有该结点的不纯度>=此值,该结点才会被划分,否则为叶结点
Iterative Dichotomizer, 迭代二分类器。
算法核心:在决策树各个子结点上应用信息增益准则来选择特征,递归地构建决策树,已经用来划分过的特征将不再重复使用。
只有树的生成,容易过拟合,分得太细,考虑条件太多。
缺点:1)用信息增益选择特征时,偏向于选择分枝比较多的属性值;
2)不能处理连续属性
对ID3算法的改进,主要为:
连续型变量计算信息增益:由小->大递增排序,取相邻两个值的中点作为分裂点,然后按离散型来计算IG,取最大IG的点为分裂点,如连续数据列(10.2, 12, 14, 16, 18, …):
10.2 ∣ 12 ⎵ 1 1.1 12 ∣ 14 ⎵ 1 3 14 ∣ 16 ⎵ 1 5 16 ∣ 18 ⎵ 1 7 . . . \underbrace{10.2 | 12}_{\text 11.1} \space \underbrace{12 | 14}_{\text 13}\space \underbrace{14 | 16}_{\text 15}\space \underbrace{16 | 18}_{\text 17} \space ... 11.1 10.2∣12 13 12∣14 15 14∣16 17 16∣18 ...
以分裂点11.1, 13, 15, 17, …分别计算IG,假如在数值13的IG是最大的,则以13来将数据划分成: <13 和 >13两个子数据集。
Classification And Regression Tree, 分类与回归树,属于二叉树,可创建分类树和回归树,既能处理分类问题也可以处理回归问题。
对于取值数N>=3的特征属性,因只能有两个分支,需人为创建二取值序列并取使切分之后的Gini指数最小的点作为树分叉决策点。
例如:size取值为[“S”,“M”,“L”],二分序列有三种情况:
对于连续属性,需转换成离散属性:
注意:离散特征分支划分数据集时,子数据集中不再包含该特征;而连续特征分支时,各子数据集依旧包含该特征,在接下来的树分支过程中可能被再次用来树分支。
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、 逐个特征开始,按属性值升序
data.sort(key=lambda x:x[fea])
# 2.2、 查找不同类标号的属性值,找出最佳划分点
for s in range(1, len(data)):
# 2.2.1 如果相邻类标号相同,则往下,不同则计算Gini指数
if data[s][-1] == data[s-1][-1]:
continue
else:
value = (data[s-1][fea] + data[s][fea]) / 2
(set_1, set_2) = split_tree(data, fea, value)
# 2.2.2、计算当前的Gini指数
nowGini = float(len(set_1) * cal_gini_index(set_1) + \
len(set_2) * cal_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)
参考的文献
——————————————————————————————————————
[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