决策树是一种基本的分类与回归算法,本文介绍该算法的分类应用。在分类过程中,可以看作if-then规则的集合,也可以看作是特征空间与类空间上的条件概论分布。决策树学习,包括三个步骤:特征选择、决策树的生成、决策树的剪枝,利用训练数据,根据损失函数最小化原则构建树的模型。具体算法有:ID3、C4.5、CART。
决策树学习算法通常递归的选择最优特征,依据此特征对训练数据集进行划分,使划分的子数据集有最好的分类过程。开始,构建根节点,将所有数据放在根节点上,选择最优特征,按此特征将数据集划分为不同的子集;如果子集能够基本正确分类,则生成叶子节点,若不能正确分类继续选择新的最优特征划分为更小的子集直到所有子集能够基本正确分类,决策树模型构建完毕。构建的决策树可能发生过拟合,对测试集表现差,可对决策树进行剪枝,去掉过于细分的叶子节点,回退到父节点或更高的节点作为新的叶子节点。决策树的生成只考虑局部最优,对应模型的局部选择;决策树的剪枝考虑全局最优,对应模型的全局选择。
特征选择在于选取对训练数据能够进行分类的特征,通常选择的准则为信息增益(information gain)、信息增益比(information gain ratio)最大或者基尼指数(Gini index)最小化准则
信息增益表示得知特征X的信息而使类Y的信息不确定性减少的程度,特征A对训练数据集D的信息增益g(D,A)定义为集合D的经验熵与特征A给定条件下集合D的经验条件熵之差。
g ( D , A ) = H ( D ) − H ( D ∣ A ) g(D,A)=H(D)-H(D|A) g(D,A)=H(D)−H(D∣A)
下面介绍熵(entropy)与条件熵(conditional entropy)的概念。
熵是表示随机变量不确定的度量,设X为取有限值的离散随机变量,其概率分布为:
P ( X = x i ) = p i , i = 1 , 2... n : P(X=x_{i})=p_{i} , i=1,2...n: P(X=xi)=pi,i=1,2...n:
则随机变量X的熵为:
H ( X ) = − ∑ i = 1 n p i l o g p i H(X)=-\sum_{i=1}^{n}p_{i}logp_{i} H(X)=−i=1∑npilogpi
熵越大不确定性程度越大。式中的对数以2或e为底时,熵的单位分别称为比特和纳特。
随机变量X给定条件下随机变量Y的条件熵 H ( Y ∣ X ) H(Y|X) H(Y∣X),定义为X条件下Y的条件概率分布的熵对X 的数学期望。
H ( Y ∣ X ) = ∑ i = 1 n p i H ( Y ∣ X = x i ) H(Y|X)=\sum_{i=1}^{n}p_{i}H(Y|X=x_{i}) H(Y∣X)=i=1∑npiH(Y∣X=xi)
熵与条件熵之差称为互信息,决策树中的信息增益等价于训练数据集中特征与类的互信息。信息增益大的特征具有较强的分类能力。
假设训练数据集为 D D D,数量为 ∣ D ∣ |D| ∣D∣,有K个类别 C k C_{k} Ck,每个类别数量为 ∣ C k ∣ |C_{k}| ∣Ck∣,根据特征A的取值可将数据集划分为n个子集 D i D_{i} Di,每个子集其数量为 ∣ D i ∣ |D_{i}| ∣Di∣,按照类别 C k C_{k} Ck对 D i D_{i} Di划分成 D i k D_{ik} Dik,数量为 ∣ D i k ∣ |D_{ik}| ∣Dik∣。计算信息增益的过程为:
#计算经验熵
def cal_entropy(data):
data_length = len(data)
data_count = {}
for i in range(data_length):
label = data[i][-1]
if label not in data_count:
data_count[label] = 0
data_count[label] += 1
entropy = -sum([(count/data_length)*np.log2(count/data_length) for count in data_count.values()])
return entropy
#计算经验条件熵
def cond_entropy(data,axis=0):
data_length = len(data)
feature_sets = {}
for i in range(data_length):
feature = data[i][axis]
if feature not in feature_sets:
feature_sets[feature] = []
feature_sets[feature].append(data[i])
cond_entropy = sum([(len(p)/data_length)*cal_entropy(p) for p in feature_sets.values()])
return cond_entropy
#计算信息增益
def inf_gain(entropy,cond_entropy):
return entropy - cond_entropy
信息增益值的大小是相对于训练数据集而言的,无意义。可选用信息增益比进行衡量,特征A对于数据集D的信息增益比 g R ( D , A ) g_{R}(D,A) gR(D,A)定义为:
g R ( D , A ) = g ( D , A ) H ( D ) g_{R}(D,A)=\frac{g(D,A)}{H(D)} gR(D,A)=H(D)g(D,A)
分类问题中,假设有K个类别,第k个概率为 p k p_{k} pk,则基尼指数定义为:
G i n i ( p ) = ∑ k = 1 K p k ( 1 − p k ) = 1 − ∑ k = 1 K p k 2 Gini(p)=\sum_{k=1}^{K}p_{k}(1-p_{k})=1-\sum_{k=1}^{K}p_{k}^2 Gini(p)=k=1∑Kpk(1−pk)=1−k=1∑Kpk2
对于给定数据集D,定义为:
G i n i ( D ) = ∑ k = 1 K ∣ C k ∣ ∣ D ∣ ( 1 − ∣ C k ∣ ∣ D ∣ ) = 1 − ∑ k = 1 K ∣ C k ∣ ∣ D ∣ 2 Gini(D)=\sum_{k=1}^{K}\frac{|C_{k}|}{|D|}(1-\frac{|C_{k}|}{|D|})=1-\sum_{k=1}^{K}\frac{|C_{k}|}{|D|}^2 Gini(D)=k=1∑K∣D∣∣Ck∣(1−∣D∣∣Ck∣)=1−k=1∑K∣D∣∣Ck∣2
给定特征条件A下数据集D的基尼指数定义为:
G i n i ( D , A ) = ∑ i = 1 n ∣ D i ∣ D G i n i ( D i ) = ∑ i = 1 n ∣ D i ∣ ∣ D ∣ ( 1 − ∑ k = 1 K ∣ D i k ∣ ∣ D i ∣ ) Gini(D,A)=\sum_{i=1}^{n}\frac{|D_{i}|}{D}Gini(D_{i})=\sum_{i=1}^{n}\frac{|D_{i}|}{|D|}(1-\sum_{k=1}^{K}\frac{|D_{ik}|}{|D_{i}|}) Gini(D,A)=i=1∑nD∣Di∣Gini(Di)=i=1∑n∣D∣∣Di∣(1−k=1∑K∣Di∣∣Dik∣)
基尼指数表示集合的不确定性,指数越大,不确定性越大。
该算法核心是在决策树的每个节点利用信息增益准则选取特征,递归的构建决策树,直到所有特征的信息增益值都很小或者没有特征为止,相当于利用极大似然法进行模型的选择。算法流程如下:
输入:训练数据集D,特征集A,阈值 ϵ \epsilon ϵ
输出:决策树T
步骤:
(1)如果D中所有实例属于同一类 C k C_{k} Ck,则T为单节点树,并用 C k C_{k} Ck作为该节点的类标记,返回T;
(2)如果A为空集,则T为单节点树,并用实例数最多的 C k C_{k} Ck作为该节点的类标记,返回T;
(3)计算A中所有特征对D的信息增益,选取信息增益最大的特征 A g A_{g} Ag进行分类;
(4)如果最大的信息增益小于阈值,则置T为单节点树,并用D中实例数最多的类作为该节点的类标记,返回T;
(5)按照特征 A g A_{g} Ag中的取值将D分为若干子集 D i D_{i} Di,用 D i D_{i} Di中实例数最多的类作为类标记构建子节点,由节点和字节点构建成树T,返回T;
(6)对 D i D_{i} Di个子节点,以 D i D_{i} Di为数据集, A − A g A-A_{g} A−Ag为特征集,递归调用1-5步,生成子树Ti,返回Ti。
该算法产生的树容易过拟合。
完整代码:
import pandas as pd
import numpy as np
from collections import namedtuple
#定义决策树中的节点类,多叉树
class Node:
def __init__(self,root=True,label=None,feature_name=None,feature=None):
self.root = root #判断是否为叶子节点
self.label = label #分类标签
self.feature_name = feature_name #选择特征名
self.feature = feature #记录特征位置
self.tree = {} #保存该节点的子节点
self.result = {'特征':self.feature_name,'分类':self.label,'子树':self.tree} #记录节点信息
def __repr__(self): #重写内置方法,用于打印输出节点,显示构建树的结构
return '{}'.format(self.result)
def addNode(self,feature_value,node): #添加子节点
self.tree[feature_value] = node
def predict(self,features):
if self.root is True:
return self.label
return self.tree[features[self.feature]].predict(features)
#定义决策树模型
class DecisionTree:
#计算经验熵
def calEntropy(self,data): #data为array-like数据类型,包括类别
data_length = len(data)
labels_list = {} #保存每一类别实例数目
for i in range(data_length):
label = data[i][-1]
if label not in labels_list:
labels_list[label] = 0
labels_list[label] += 1
entropy = -sum([(label_count/data_length)*np.log2(label_count/data_length) for label_count in labels_list.values()])
return entropy
#计算经验条件熵
def calConditionalEntropy(self,data,axis): #data为array-like数据类型,包括类别;axis为某特征
data_length = len(data)
feature_dic = {} #保存按该特征分类的子集
for i in range(data_length):
feature_value = data[i][axis]
if feature_value not in feature_dic:
feature_dic[feature_value] = []
feature_dic[feature_value].append(data[i])
conditional_entropy = sum([len(feature_data)/data_length*self.calEntropy(feature_data) for feature_data in feature_dic.values()])
return conditional_entropy
#计算最大信息增益
def getMaxEntropyGain(self,data):
features_length = len(data[0])-1
value = 0
feature = 0
result = namedtuple('max_entropy_gain','feature value') #记录最大信息增益特征的信息
entropy = self.calEntropy(data)
for i in range(features_length):
entropy_gain = entropy - self.calConditionalEntropy(data,axis=i)
if value < entropy_gain:
value = entropy_gain
feature = i
return result(feature,value)
def fit(self,pd_data,feature_labels,epsilon): #data为数据类型为DataFrame,包括类别;feature_labels为特征标签,array_like类型;epsilon为阈值
labels = pd_data.iloc[:,-1]
#1.若数据集所有实例类别均相同,返回单节点树
if len(labels.value_counts()) == 1:
return Node(root=True,label=labels.value_counts().index[0])
#2.若特征集为0,返回单节点树
if len(feature_labels) == 0:
return Node(root=True,label=labels.value_counts().sort_values(ascending=False).index[0])
#3.进行特征选取,取信息增益最大的特征Ag分类
select_feature = self.getMaxEntropyGain(np.array(pd_data))
#4.若选取特征Ag的信息增益小于阈值,返回单节点树
if select_feature.value < epsilon:
return Node(root=True,label=labels.value_counts().sort_values(ascending=False).index[0])
#5.按照选取特征Ag值划分数据集为多个子集,对子集进行分类
max_feature_name = feature_labels[select_feature.feature]
tree_node = Node(root=False, feature_name=max_feature_name,feature=select_feature.feature)
feature_value = pd_data[max_feature_name].value_counts().index
feature_labels.remove(max_feature_name)
for f in feature_value:
sub_data = pd_data.loc[pd_data[max_feature_name]==f].drop([max_feature_name],axis=1)
#6.递归生成树
sub_node = self.fit(sub_data,feature_labels,epsilon)
tree_node.addNode(f,sub_node)
self.tree = tree_node #用根节点记录整棵树的信息
return tree_node
def predict(self,features):
return self.tree.predict(features)
def create_data():
datasets = [['青年', '否', '否', '一般', '否'],
['青年', '否', '否', '好', '否'],
['青年', '是', '否', '好', '是'],
['青年', '是', '是', '一般', '是'],
['青年', '否', '否', '一般', '否'],
['中年', '否', '否', '一般', '否'],
['中年', '否', '否', '好', '否'],
['中年', '是', '是', '好', '是'],
['中年', '否', '是', '非常好', '是'],
['中年', '否', '是', '非常好', '是'],
['老年', '否', '是', '非常好', '是'],
['老年', '否', '是', '好', '是'],
['老年', '是', '否', '好', '是'],
['老年', '是', '否', '非常好', '是'],
['老年', '否', '否', '一般', '否'],
]
labels = ['年龄','有工作','有房子','信贷情况','录取']
return datasets,labels
datasets,labels = create_data()
pd_data = pd.DataFrame(datasets,columns=labels)
labels.remove('录取')
tree = DecisionTree()
print(tree.getMaxEntropyGain(datasets))
tree_node = tree.fit(pd_data,labels,0.1)
print(tree_node)
print(tree.predict(['老年', '否', '否', '一般']))
运行结果:
max_entropy_gain(feature=2, value=0.41997309402197491)
{'特征': '有房子', '分类': None, '子树': {'否': {'特征': '有工作', '分类': None, '子树': {'否': {'特征': None, '分类': '否', '子树': {}}, '是': {'特征': None, '分类': '是', '子树': {}}}}, '是': {'特征': None, '分类': '是', '子树': {}}}}
否
特征选择的准则为信息增益比,方法同ID3算法。
分类与回归树(classification and regression tree,CART),是在给定随机变量X条件下输出随机变量Y的条件概率分布。CART假设决策树为二叉树,节点特征取值为’是’(左分支)和’否’(右分支),由两步组成:决策树的生成和决策树的剪枝。
决策树生成,其回归树利用平方误差最小化准则构建树;其分类树利用基尼指数最小化准则构建树。对于回归树不做介绍,介绍分类树的生成算法:
输入:训练数据集D,算法停止条件(节点中的样本个数小于阈值或基尼指数小于阈值)
输出:CART决策树
步骤:
(1)设根节点的数据集为D,计算数据集D的每一个特征每一个取值的基尼指数。按照特征A(可以取连续值或离散值)的不同取值,如A=a,将数据集按‘是’和‘否’划分为两个子集D1、D2计算。
(2)在所有可能的特征和切分点中,选取基尼指数最大的作为最优特征和切分点划分数据集,生成两个子节点。
(3)递归调用步骤1-2,直到满足算法停止条件。
(4)生成决策树T。
CART决策树的剪枝,在下面介绍。
将生成的树进行简化的过程称为剪枝,即去掉一些叶子节点或子节点,让其父节点作为新的叶子节点,通过极小化决策树整体的损失函数或代价函数实现。设树的叶子节点个数为 ∣ T ∣ |T| ∣T∣个,t为叶子节点,该叶节点有 N t N_{t} Nt个样本,其中k类的样本有 N t k N_{tk} Ntk个,则决策树学习的损失函数可以定义为:
C α ( T ) = ∑ t = 1 ∣ T ∣ N t H t ( T ) + α ∣ T ∣ = C ( T ) + α ∣ T ∣ C_{\alpha}(T)=\sum_{t=1}^{|T|}N_{t}H_{t}(T)+\alpha|T| =C(T)+\alpha|T| Cα(T)=t=1∑∣T∣NtHt(T)+α∣T∣=C(T)+α∣T∣
其中, H t ( T ) H_{t}(T) Ht(T)为第t个叶子节点的经验熵:
H t ( T ) = − ∑ k = 1 K N t k N t l o g 2 N t k N t H_{t}(T)=-\sum_{k=1}^{K}\frac{N_{tk}}{N_{t}}log2\frac{N_{tk}}{N_{t}} Ht(T)=−k=1∑KNtNtklog2NtNtk
C ( T ) C(T) C(T)表示模型对训练数据的预测误差,|T|表示模型复杂度。 C α ( T ) C_{\alpha}(T) Cα(T)的极小化等价于极小化正则化的极大似然函数。
算法流程(动态规划):
输入:整棵树T,参数 α \alpha α
输出:修剪后的子树 T α T_{\alpha} Tα
步骤:
(1)计算每个节点的经验熵;
(2)递归的从叶子节点向上回缩,设叶子节点向上回缩前与后的树为 T A 、 T B T_{A}、T_{B} TA、TB,计算代价函数 C α ( T A ) 、 C α ( T B ) C_{\alpha}(T_{A})、C_{\alpha}(T_{B}) Cα(TA)、Cα(TB)。如果 C α ( T A ) > C α ( T B ) C_{\alpha}(T_{A})>C_{\alpha}(T_{B}) Cα(TA)>Cα(TB),则对该叶子节点进行剪枝;
(3)返回步骤2,直到不能继续剪枝为止,返回子树 T α T_{\alpha} Tα
算法 | 支持模型 | 树结构 | 特征选择 | 连续值处理 | 缺失值处理 | 剪枝 | 特征属性多次使用 |
---|---|---|---|---|---|---|---|
ID3 | 分类 | 多叉树,速度慢 | 信息增益,偏向特征值多的特征 | 不支持 | 不支持 | 不支持 | 不支持 |
C4.5 | 分类 | 多叉树,速度慢 | 信息增益比,偏向特征值小的特征 | 支持 | 支持 | 支持,悲观剪枝策略 | 不支持 |
CART | 分类回归 | 二叉树,速度较快 | 基尼指数,克服对数运算,偏向特征值较多的特征 | 支持 | 支持 | 支持,代价复杂度策略 | 支持 |
参考文章:
李航,《统计学习方法》.