CART算法
CART(分类与回归树)是在给定输入随机变量X条件下输出随机变量Y的条件概率分布的学习方法。
CART假设决策树是二叉树,内部结点特征的取值是“是”和“否”,左分支是取值为“是”的分支,右分支是取值为“否”的分支。
这样的决策树等价于递归地二分每个特征,将输入空间即特征空间划分为有限个单元,并在这些单元上确定预测的概率分布,也就是在输入给定的条件下输出条件概率分布。
CART算法由以下两步组成:
(1)决策树生成:基于训练数据集生成决策树,生成的决策树要尽量大;
(2)决策树剪枝:用验证数据集对一生成的树进行剪枝并选择最优子树,这时用损失函数最小作为剪枝的标准。
CART生成
决策树的生成就是递归地构建二叉决策树的过程。
- 对回归树用平方误差最小化准则,进行特征选择,生成二叉树
- 对分类树用基尼指数最小化准则,进行特征选择,生成二叉树
回归树的生成
最小二乘回归树生成算法
输入:训练数据集D;
输出:回归树
f
(
x
)
f(x)
f(x)。
在训练数据集所在的输入空间中,递归地将每个区域划分为两个子区域并决定每个子区域上的输出值,构建二叉决策树:
(1)选择最优切分变量j与切分点s,求解
min
j
,
s
[
min
c
1
∑
x
i
∈
R
1
(
j
,
s
)
(
y
i
−
c
1
)
2
+
min
c
2
∑
x
i
∈
R
2
(
j
,
s
)
(
y
i
−
c
2
)
2
]
\min\limits_{j,s}[\min\limits_{c_1}\sum\limits_{x_i \in R_1(j,s)}{(y_i-c_1)^2} + \min\limits_{c_2}\sum\limits_{x_i \in R_2(j,s)}{(y_i-c_2)^2}]
j,smin[c1minxi∈R1(j,s)∑(yi−c1)2+c2minxi∈R2(j,s)∑(yi−c2)2]
遍历变量j,对固定的切分变量j扫描切分点s,选择使上式达到最小值的对
(
j
,
s
)
(j,s)
(j,s)。
(2)用选定的对
(
j
,
s
)
(j,s)
(j,s)划分区域并决定相应的输出值:
R
1
(
j
,
s
)
=
{
x
∣
x
j
≤
s
}
,
R
2
(
j
,
s
)
=
{
x
∣
x
j
>
s
}
R_1(j,s) = \{x|x^{j} \leq s\}, R_2(j,s) = \{x|x^{j} > s\}
R1(j,s)={x∣xj≤s},R2(j,s)={x∣xj>s}
c
^
m
=
1
N
m
∑
x
i
∈
R
m
(
j
,
s
)
y
i
,
x
∈
R
m
,
m
=
1
,
2
\hat{c}_m = \frac{1}{N_m}\sum\limits_{x_i \in R_m(j,s)}{y_i}, \quad x \in R_m, \quad m=1,2
c^m=Nm1xi∈Rm(j,s)∑yi,x∈Rm,m=1,2
(3)继续对两个子区域调用步骤(1),(2),直至满足停止条件。
(4)将输入空间划分为M个区域
R
1
,
R
2
,
.
.
.
,
R
M
R_1,R_2,...,R_M
R1,R2,...,RM,生成决策树:
f
(
x
)
=
∑
m
=
1
M
c
^
m
I
(
x
∈
R
m
)
f(x) = \sum\limits_{m=1}^M{\hat{c}_m{I(x \in R_m)}}
f(x)=m=1∑Mc^mI(x∈Rm)
分类树的生成
基尼指数
分类问题中,假设有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\limits_{k=1}^K{p_k(1-p_k)} = 1 - \sum\limits_{k=1}^K{p_k^2}
Gini(p)=k=1∑Kpk(1−pk)=1−k=1∑Kpk2
对于二分类问题,若样本点属于第1个类的概率是
p
p
p,这概率分布的基尼指数为
G
i
n
i
(
p
)
=
2
p
(
1
−
p
)
Gini(p) = 2p(1-p)
Gini(p)=2p(1−p)
对于给定的样本集合D,其基尼指数为
G
i
n
i
(
D
)
=
1
−
∑
k
=
1
K
(
∣
C
k
∣
∣
D
∣
)
2
Gini(D) = 1- \sum\limits_{k=1}^K{(\frac{|C_k|}{|D|})^2}
Gini(D)=1−k=1∑K(∣D∣∣Ck∣)2
这里,
C
k
C_k
Ck是D中属于第k类的样本子集,K是类的个数。
如果样本集合D根据特征A是否取某一可能值a被分割成
D
1
D_1
D1和
D
2
D_2
D2两部分,即
D
1
=
{
(
x
,
y
)
∈
D
∣
A
(
x
)
=
a
}
,
D
2
=
D
−
D
1
D_1 = \{(x,y) \in D|A(x)=a\}, \quad D_2 = D - D_1
D1={(x,y)∈D∣A(x)=a},D2=D−D1
则在特征A的条件下,集合D的基尼指数定义为
G
i
n
i
(
D
,
A
)
=
∣
D
1
∣
∣
D
∣
G
i
n
i
(
D
1
)
+
∣
D
2
∣
∣
D
∣
G
i
n
i
(
D
2
)
Gini(D,A) = \frac{|D_1|}{|D|}Gini(D_1) + \frac{|D_2|}{|D|}Gini(D_2)
Gini(D,A)=∣D∣∣D1∣Gini(D1)+∣D∣∣D2∣Gini(D2)
- 基尼指数 G i n i ( D ) Gini(D) Gini(D)表示集合D的不确定性
- 基尼指数 G i n i ( D , A ) Gini(D,A) Gini(D,A)表示经 A = a A=a A=a分割后集合D的不确定性。
- 基尼指数值越大,样本集合的不确定性也就越大,这一点与熵相似。
CART生成算法
输入:训练数据集D,停止计算的条件;
输出:CART决策树。
根据训练数据集,从根结点开始,递归地对每个结点进行以下操作,构建二叉决策树:
(1)设结点的训练数据集为D,计算现有特征对该数据集的基尼指数。
对每一个特征A,对其可能取的每个值a。根据样本点对
A
=
a
A=a
A=a的测试为“是”或“否”将D分割成
D
1
D_1
D1和
D
2
D_2
D2两部分,计算
A
=
a
A=a
A=a时的基尼指数。
(2)在所有可能的特征A以及它们所有可能的切分点a中,选择基尼指数最小的特征及其对应的切分点,将训练数据集依特征分配到两个子结点中去。
(3)对两个子结点递归地调用(1),(2),直至满足停止条件。
(4)生成CART决策树。
算法停止计算的条件是结点中的样本个数小于预定阈值,或样本集的基尼指数小于预定阈值(样本基本属于同一类),或者没有更多特征。
CART剪枝
CART剪枝算法由两步组成:
(1)从生成算法产生的决策树
T
0
T_0
T0底端开始不断剪枝,直到
T
0
T_0
T0的根结点,形成一个子树序列
{
T
0
,
T
1
,
.
.
.
,
T
n
}
\{T_0,T_1,...,T_n\}
{T0,T1,...,Tn};
(2)通过交叉验证法在独立的验证集上对子树序列进行测试,从中选择最优子树。
CART剪枝算法
输入:CART算法生成的决策树
T
0
T_0
T0;
输出:最优决策树
T
α
T_{\alpha}
Tα。
(1)设
k
=
0
,
T
=
T
0
k = 0, \quad T = T_0
k=0,T=T0。
(2)设
α
=
+
∞
\alpha = +\infty
α=+∞。
(3)自下而上地对各内部结点t计算
C
(
T
t
)
C(T_t)
C(Tt),
∣
T
t
∣
|T_t|
∣Tt∣以及
g
(
t
)
=
C
(
t
)
−
C
(
T
t
)
∣
T
t
∣
−
1
g(t) = \frac{C(t) - C(T_t)}{|T_t| - 1}
g(t)=∣Tt∣−1C(t)−C(Tt)
α
=
min
(
α
,
g
(
t
)
)
\alpha = \min(\alpha, g(t))
α=min(α,g(t))
这里,
T
t
T_t
Tt表示以t为根结点的子树,
C
(
T
t
)
C(T_t)
C(Tt)是对训练数据的预测误差,
∣
T
t
∣
|T_t|
∣Tt∣是
T
t
T_t
Tt的叶结点个数。
(4)对
g
(
t
)
=
α
g(t) = \alpha
g(t)=α的内部结点t进行剪枝,并对叶结点t以多数表决法决定其类,得到树T。
(5)设
k
=
k
+
1
,
α
k
=
α
,
T
k
=
T
k = k+1, \quad \alpha_k = \alpha, \quad T_k = T
k=k+1,αk=α,Tk=T。
(6)如果
T
k
T_k
Tk不是由根结点及两个叶结点构成的树,则回到步骤(2);否则另
T
k
=
T
n
T_k = T_n
Tk=Tn。
(7)采用交叉验证法在子树序列
T
0
,
T
1
,
.
.
.
,
T
n
T_0,T_1,...,T_n
T0,T1,...,Tn中选取最优子树
T
α
T_{\alpha}
Tα。
Python自编程实现
def gini(dataset):
"""计算样本集合的基尼指数"""
prob = dataset.iloc[:,-1].value_counts()/dataset.shape[0]
return 1-prob.apply(lambda p: p*p).sum()
def condition_gini(dataset,feature, feature_value):
"""计算特征条件下,样本集合的基尼指数"""
sub_dataset1 = dataset[dataset[feature]==feature_value]
sub_dataset2 = dataset[dataset[feature]!=feature_value]
sub_len1 = sub_dataset1.shape[0]
sub_len2 = sub_dataset2.shape[0]
data_len = dataset.shape[0]
return sub_len1/data_len*gini(sub_dataset1) + sub_len2/data_len*gini(sub_dataset2)
def choose_feature_pair(dataset,features):
res = None
min_gini = 1
for feature in features:
for value in dataset[feature].unique():
gini_value = condition_gini(dataset, feature, value)
print("feature:{0}, value:{1}, gini:{2}".format(feature, value, gini_value))
if min_gini >= gini_value:
min_gini = gini_value
res = (feature, value, gini_value)
print("基尼指数最小的特征:{0},及最优切分点为:{1}".format(res[0], res[1]))
return res
import numpy as np
import pandas as pd
features=["年龄","有工作","有自己的房子","信贷情况"]
X_train=np.array([
["青年", "否", "否", "一般"],
["青年", "否", "否", "好"],
["青年", "是", "否", "好"],
["青年", "是", "是", "一般"],
["青年", "否", "否", "一般"],
["中年", "否", "否", "一般"],
["中年", "否", "否", "好"],
["中年", "是", "是", "好"],
["中年", "否", "是", "非常好"],
["中年", "否", "是", "非常好"],
["老年", "否", "是", "非常好"],
["老年", "否", "是", "好"],
["老年", "是", "否", "好"],
["老年", "是", "否", "非常好"],
["老年", "否", "否", "一般"]
])
y_train=np.array(["否","否","是", "是", "否", "否", "否", "是", "是", "是", "是", "是", "是", "是", "否"])
X = pd.DataFrame(X_train)
y = pd.DataFrame(y_train)
dataset = pd.concat((X,y),axis=1)
dataset.columns = ["年龄","有工作","有自己的房子","信贷情况","类别"]
下面是构建CART树
class CartTree(object):
def __init__(self, labels):
self.node_id = 0
self.lables = labels
def get_value(self,dataset):
value = []
for item in self.lables:
value.append(dataset[dataset.iloc[:,-1]==item].shape[0])
return value
def build_tree(self, dataset, features, attribute=None, ep=0.05):
"""这里停止条件是,样本集gini指数小于某个阈值ep"""
value=self.get_value(dataset)
dataset_gini = gini(dataset)
# 1.如果数据集中所有实例属于同一类
if dataset.iloc[:,-1].value_counts().shape[0] == 1:
label = dataset.iloc[0,-1]
return Node(self.node_id,None,None,attribute,label,value,dataset_gini)
# 2如果特征为空,或数据集在属性上都相同,
label = dataset.iloc[:,-1].value_counts().idxmax()
if len(features)==0 or dataset.iloc[:,0:-1].value_counts().shape[0]==1:
return Node(self.node_id, None,None,attribute,label,value,dataset_gini)
# 4.如果小于阈值
if dataset_gini < ep:
return Node(self.node_id,None,None,attribute,label,value,dataset_gini)
# 3.选择特征
choosed_feature, feature_type, feature_gini = choose_feature_pair(dataset, features)
# 6.分割子集,递归调用
children = []
new_features = features.copy()
new_features.remove(choosed_feature)
# 复制当前结点名称
this_node = self.node_id
# 数据集满足A=a被分配到左结点
left_dataset = dataset[dataset[choosed_feature]==feature_type]
self.node_id += 1
left_node = self.build_tree(left_dataset, new_features,"是", ep)
# 数据集满足A!=a被分配到左结点
right_dataset = dataset[dataset[choosed_feature]!=feature_type]
self.node_id += 1
right_node = self.build_tree(right_dataset, new_features, "否", ep)
children.append(left_node)
children.append(right_node)
# 根结点或内部结点
return Node(this_node,children, "{0}={1}".format(choosed_feature,feature_type),attribute,label, value,dataset_gini)
代码中的Node及画决策树的代码见上篇(https://blog.csdn.net/jasmine0244/article/details/113704872)。
关于CART树剪枝部分的代码,后面再补充。