决策树
1.概述
决策树由节点和有向边组成,节点有两种类型,内部节点和叶节点,内部节点表示一个特征或属性,叶节点表示一个类。
在用决策树分类时,先从根节点出发,对实例中的某一特征进行测试,根据测试结果将实例分配到其子节点。这样以后,每一个子节点对应该特征的一个取值。这样不断递归下去对实例进行测试和分类,直至达到叶节点,最后将实例分到叶节点中。
其主要优点有:1.分类速度快, 2.具有可读性。
学习时,利用训练数据,根据损失函数最小化的原则建立决策树模型。预测时,对新数据集,利用决策树模型进行分类。
主要步骤:1.特征选择, 2.决策树的生成, 3.决策树的剪枝。
决策树的生成只考虑局部最优,容易过拟合。决策树的修剪则考虑全局最优。
2 ID3算法
2.1 数学原理
数学原理部分看博客:
http://www.cnblogs.com/pinard/p/6050306.html
2.2 算法思路
输入的是m个样本,样本输出集合为D,每个样本有n个离散特征,特征集合即为A,输出为决策树T。
算法的过程为:
1)初始化信息增益的阈值ϵ
2)判断样本是否为同一类输出Di,如果是则返回单节点树T。标记类别为Di
- 判断特征是否为空,如果是则返回单节点树T,标记类别为样本中输出类别D实例数最多的类别。
4)计算A中的各个特征(一共n个)对输出D的信息增益,选择信息增益最大的特征Ag - 如果Ag的信息增益小于阈值ϵ,则返回单节点树T,标记类别为样本中输出类别D实例数最多的类别。
6)否则,按特征Ag的不同取值Agi将对应的样本输出D分成不同的类别Di。每个类别产生一个子节点。对应特征值为Agi。返回增加了节点的数T。
7)对于所有的子节点,令D=Di,A=A−{Ag}递归调用2-6步,得到子树Ti并返回。
2.3 ID3算法的不足
- 没有考虑连续特征,比如长度、密度等连续值无法再ID3中运用
- ID3采用信息增益大的特征优先建立决策树的节点。很快就被人发现,在相同条件下,取值比较多的特征比取值少的特征信息增益大。比如一个变量有2个值,各为1/2,另一个变量为3个值,各为1/3,其实他们都是完全不确定的变量,但是取3个值的比取2个值的信息增益大。
- ID3未考虑缺失值
- 未考虑过拟合问题
3 C4.5算法
3.1 数学原理
和上面那个算法一样,可以参考这个教程
http://www.cnblogs.com/pinard/p/6050306.html
3.2 C4.5的特点
该算法和ID3的算法思路基本一致,但它有效的解决了上述四个问题。
首先,对于第一个问题:不能处理连续特征。C4.5的思路是将连续的特征离散化。比如m个样本的连续特征A有m个,从小到大排列为a1,a2,...,am,则C4.5取相邻两样本值的平均数,一共取得m-1个划分点。对于这m-1个点,分别计算以该点作为二元分类点时的信息增益。选择信息增益最大的点作为该连续特征的二元离散分类点。比如取到的增益最大的点为at,则小于at的值为类别1,大于at的值为类别2,这样我们就做到了连续特征的离散化。要注意的是,与离散属性不同的是,如果当前节点为连续属性,则该属性后面还可以参与子节点的产生选择过程。
对于第二个问题,信息增益作为标准容易偏向于取值较多的特征。这里引入了信息增益比。
其中n为特征A的类别数, Di为特征A的第i个取值对应的样本个数。D为样本个数。
特征数越多的特征对应的特征熵越大,它作为分母,可以校正信息增益容易偏向于取值较多的特征的问题。
对于第三个问题,缺失值处理的问题,主要需要解决的是两个问题,一是在样本某些特征缺失的情况下选择划分的属性,二是选定了划分属性,对于在该属性上缺失特征的样本的处理。
对于第一个子问题,对于某一个有缺失特征值的特征A。C4.5的思路是将数据分成两部分,对每个样本设置一个权重(初始可以都为1),然后划分数据,一部分是有特征值A的数据D1,另一部分是没有特征A的数据D2. 然后对于没有缺失特征A的数据集D1来和对应的A特征的各个特征值一起计算加权重后的信息增益比,最后乘上一个系数,这个系数是无特征A缺失的样本加权后所占加权总样本的比例。
对于第二个子问题,可以将缺失特征的样本同时划分入所有的子节点,不过将该样本的权重按各个子节点样本的数量比例来分配。比如缺失特征A的样本a之前权重为1,特征A有3个特征值A1,A2,A3。 3个特征值对应的无缺失A特征的样本个数为2,3,4.则a同时划分入A1,A2,A3。对应权重调节为2/9,3/9, 4/9。
对于第4个问题,C4.5引入了正则化系数进行初步的剪枝。
3.3 C4.5算法的不足
1.因为决策树算法特别容易过拟合,所以对于生成的决策数一定要进行剪枝
2.C4.5生成的是多叉树,一个父节点可以对于多个子节点,这样的结构会比二叉树的效率低。
3.C4.5只能用于分类
4.C4.5由于使用了熵模型,里面有大量的耗时的对数运算,如果是连续值还有大量的排序运算。
4 CART算法
4.1 算法原理
http://www.cnblogs.com/pinard/p/6053344.html
在这里CART算法选择了基尼系数作为特征选择的标准,基尼系数代表了模型的不纯度,基尼系数越小,则不纯度越低,特征越好。
相对于熵模型的表达式,基尼系数的计算为二次运算,比对数运算要简单一些,并且为了简化运算,CART算法每次仅对某个特征值的值进行二分类,这样建立起来的就是一棵二叉树,并且可以简化运算。
4.2 CART对于连续值和离散值处理的改进
对于CART分类树连续值的处理问题,其思想和C4.5是相同的,都是将连续的特征离散化。唯一的区别在于在选择划分点时的度量方式不同,C4.5使用的是信息增益比,则CART分类树使用的是基尼系数。
对于离散值的处理采用的思路是不停的二分离散特征。回忆下ID3或者C4.5,如果某个特征A被选取建立决策树节点,如果它有A1,A2,A3三种类别,我们会在决策树上一下建立一个三叉的节点。这样导致决策树是多叉树。但是CART分类树使用的方法不同,他采用的是不停的二分,还是这个例子,CART分类树会考虑把A分成{A1}和{A2,A3}, {A2}和{A1,A3}, {A3}和{A1,A2}三种情况,找到基尼系数最小的组合,比如{A2}和{A1,A3},然后建立二叉树节点,一个节点是A2对应的样本,另一个节点是{A1,A3}对应的节点。同时,由于这次没有把特征A的取值完全分开,后面我们还有机会在子节点继续选择到特征A来划分A1和A3。这和ID3或者C4.5不同,在ID3或者C4.5的一棵子树中,离散特征只会参与一次节点的建立。
4.3 分类树的生成
算法输入是训练集D,基尼系数的阈值,样本个数阈值。
输出是决策树T。
我们的算法从根节点开始,用训练集递归的建立CART树。
- 对于当前节点的数据集为D,如果样本个数小于阈值或者没有特征,则返回决策子树,当前节点停止递归。
- 计算样本集D的基尼系数,如果基尼系数小于阈值,则返回决策树子树,当前节点停止递归。
- 计算当前节点现有的各个特征的各个特征值对数据集D的基尼系数,对于离散值和连续值的处理方法和基尼系数的计算见第二节。缺失值的处理方法和上篇的C4.5算法里描述的相同。
- 在计算出来的各个特征的各个特征值对数据集D的基尼系数中,选择基尼系数最小的特征A和对应的特征值a。根据这个最优特征和最优特征值,把数据集划分成两部分D1和D2,同时建立当前节点的左右节点,做节点的数据集D为D1,右节点的数据集D为D2.
- 对左右的子节点递归的调用1-4步,生成决策树。
对于生成的决策树做预测的时候,假如测试集里的样本A落到了某个叶子节点,而节点里有多个训练样本。则对于A的类别预测采用的是这个叶子节点里概率最大的类别。
4.4 回归树的生成
ART回归树和CART分类树的建立算法大部分是类似的,所以这里我们只讨论CART回归树和CART分类树的建立算法不同的地方。
首先,我们要明白,什么是回归树,什么是分类树。两者的区别在于样本输出,如果样本输出是离散值,那么这是一颗分类树。如果果样本输出是连续值,那么那么这是一颗回归树。
除了概念的不同,CART回归树和CART分类树的建立和预测的区别主要有下面两点:
1)连续值的处理方法不同
2)决策树建立后做预测的方式不同。
对于连续值的处理,我们知道CART分类树采用的是用基尼系数的大小来度量特征的各个划分点的优劣情况。这比较适合分类模型,但是对于回归模型,我们使用了常见的和方差的度量方式,CART回归树的度量目标是,对于任意划分特征A,对应的任意划分点s两边划分成的数据集D1和D2,求出使D1和D2各自集合的均方差最小,同时D1和D2的均方差之和最小所对应的特征和特征值划分点。表达式为:
其中,c1为D1数据集的样本输出均值,c2为D2数据集的样本输出均值。
对于决策树建立后做预测的方式,上面讲到了CART分类树采用叶子节点里概率最大的类别作为当前节点的预测类别。而回归树输出不是类别,它采用的是用最终叶子的均值或者中位数来预测输出结果。
4.5 CART算法的剪枝
参考博客http://www.cnblogs.com/pinard/p/6053344.html
4.6 CART算法小结
5 决策树中的剪枝
5.1 预剪枝
预剪枝主要是再节点进行扩展之前,先计算当前的划分是否能带来模型泛化能力的提升,若不能,则停止生长。
停止树生长的方法:
1)当树到达一定深度的时候,停止书的生长
2)当到达当前节点的样本数量小于某个阈值时,停止树的生长
3)计算每次分裂对测试集准确度提升。当小于某个阈值时,不再生长
这种方法思想简单、算法简单、效率高,适于处理大规模数据。但如何准确地估计何时停止树的生长(即上述方法中的深度或阈值),针对不同问题会有很大差别,需要一定经验判断。且预剪枝存在一定局限性,有欠拟合的风险,虽然当前的划分会导致测试集准确率降低,但在之后的划分中,准确率可能会有显著上升。
5.2后剪枝
后剪枝的核心思想是让算法生成一棵完全生长的决策树,然后从最
底层向上计算是否剪枝。剪枝过程将子树删除,用一个叶子结点替代,该结点的类别同样按照多数投票的原则进行判断。同样地,后剪枝也可以通过在测试集上的准确率进行判断,如果剪枝过后准确率有所提升,则进行剪枝。相比于预剪枝,后剪枝方法通常可以得到泛化能力更强的决策树,但时间开销会更大。
常见的后剪枝方法包括错误率降低剪枝(Reduced Error Pruning,
REP)、悲观剪枝(Pessimistic Error Pruning,PEP)、代价复杂度剪枝(Cost ComplexityPruning,CCP)、最小误差剪枝(Minimum Error Pruning,MEP)、CVP(Critical Value Pruning)、OPP(Optimal
Pruning)等方法,这些剪枝方法各有利弊,关注不同的优化角度,下面选取著名的CART剪枝方法CCP进行介绍。
代价复杂剪枝主要包含以下两个步骤。
(1)从完整决策树T0开始,生成一个子树序列{T0,T1,T2,...,Tn},
其中Ti +1由Ti生成,Tn为树的根结点。
(2)在子树序列中,根据真实误差选择最佳的决策树。
对于步骤(1)主要以以下方式进行计算:
其中α代表误差增加率,R(t)代表剪枝后的误差,R(T)代表剪枝前的树的误差,|L(T)|代表叶子节点个数。
下面举个例子来说明:
女孩需要对80个人进行见或不见的分类。假设根据某种规则,已经得到了一棵CART决策树T 0 ,如图1所示。
此时共5个内部结点可供考虑,其中
可见α(t3)最小,因此对t3进行剪枝,得到新的子树T1,如图2所示。
而后继续计算所有结点对应的误差增加率,分别为α(t1)=3,α(t2)=3,α(t4)=4。因此对t1进行剪枝,得到T2,如图3所示。此时α(t0)=6.5,α(t2)=3,选择t2进行剪枝,得到T3。于是只剩下一个内部结点,即根结点,得到T4 。
在步骤(2)中:
我们需要从子树序列中选出真实误差最小的决策
树。CCP给出了两种常用的方法:
一种是基于独立剪枝数据集,该方法与REP类似,但由于其只能从子树序列{T0,T1,T2,...,Tn}中选择最佳决策树,而非像REP能在所有可能的子树中寻找最优解,因此性能上会有一定不足。
另一种是基于k折交叉验证,将数据集分成k份,前k−1份用于生成决策树,最后一份用于选择最优的剪枝树。重复进行N次,再从这N个子树中选择最优的子树。
6 决策树算法小结
决策树算法作为一个大类别的分类回归算法的优缺点。这部分总结于scikit-learn的英文文档。
决策树算法的优点:
1)简单直观,生成决策树很直观。
2)基本不需要预处理,不需要提前归一化,处理缺失值。
3)使用决策树预测的代价是O(log2m)。 m为样本数。
4)既可以处理离散值也可以处理连续值。很多算法只是专注于离散值或者连续值。
5)可以处理多维度输出的分类问题。
6)相比于神经网络之类的黑盒分类模型,决策树在逻辑上可以得到很好的解释
7)可以交叉验证的剪枝来选择模型,从而提高泛化能力。
8) 对于异常点的容错能力好,健壮性高。
决策树算法的缺点
1)决策树算法非常容易过拟合,导致泛化能力不强。可以通过设置节点最少样本数量和限制决策树深度来改进。
2)决策树会因为样本发生一点点的改动,就会导致树结构的剧烈改变。这个可以通过集成学习之类的方法解决。
3)寻找最优的决策树是一个NP难的问题,我们一般是通过启发式方法,容易陷入局部最优。可以通过集成学习之类的方法来改善。
4)有些比较复杂的关系,决策树很难学习,比如异或。这个就没有办法了,一般这种关系可以换神经网络分类方法来解决。
5)如果某些特征的样本比例过大,生成决策树容易偏向于这些特征。这个可以通过调节样本权重来改善。
7 统计学习方法相关习题答案
可以参考这个博客https://blog.csdn.net/familyshizhouna/article/details/72551841
这里摆下5.2题的答案和代码
分类图
代码实现
# 生成最小二乘回归树
import numpy as np
y = np.array([4.5, 4.75, 4.91, 5.34, 5.80, 7.05, 7.90, 8.23, 8.70, 9.00])
def CART(start, end, y):
# 如果数组长度不大于1则继续进行
if (end - start) > 1:
result = [] # 创建数组用来存储每次的结果
for i in range(start, end+1,1):
c1 = [np.mean(y[start:i+1])] # 算第一部分的平均值
c2 = [np.mean(y[i+1:end+1])] # 算第二部分的平均值
y1 = y[start:i+1]
y2 = y[i+1:end+1]
result.append((sum((y1 - c1)**2) + sum((y2 - c2)**2)))
index = np.argmin(result) + start
print(index, '******', np.mean(y[start:index+1]), '******', np.mean(y[index+1:end+1]))
# 递归生成左右子树
CART(start, index, y)
CART(index+1, end, y)
else:
return None
结果
8 sklearn中运用决策树
# 导入包
import numpy as np
import pandas as pd
import matplotlib.pyplot as plt
# 导入数据集
data = pd.read_csv('Social_Network_Ads.csv')
pd.head()
# 划分数据集,并将数据集划分为训练集和测试集
X = data.iloc[:,[2,3]].values()
y = data.iloc[:,-1].values
from skearn.model_selection import train_test_split
X_train,X_test,y_train,y_test = train_test_split(X,y,test_size = 0.25, random_state = 0)
# 进行特征缩放
from skleran.processing import StandardScaler
sc = StandardScaler()
X_train = sc.fit_transform(X_train)
X_test = sc.transform(X_test)
# 对测试数据集进行拟合分类,并且输出对测试集的结果
from skleran.tree import DecisionTreeClassfier
clf = DecisionTreeClassifer
clf.fit(X_train, y_train)
y_pred = clf.predict(X_test)
# 建立混淆矩阵
# 混淆矩阵被用于在分类问题上对准确率的一种评估形式,我们经常通过
# 观察混淆矩阵的对角线来评估出模型的分类效果,最理想的结果就是所有的数据都在对角线上,那么说明分类精度最高
from sklearn.metrics import confusion_matrix
cm = confusion_matrix(y_test, y_pred)
# 将训练数据集可视化
from matplotlib.colors import ListedColormap
X_set, y_set = X_train, y_train
X1, X2 = np.meshgrid(np.arange(start = X_set[:, 0].min() - 1 ,stop = X_set[:,0].max() + 1,step = 0.01),np.arange(strat = X_set[:,1].min() - 1,X_set[:,1].max() + 1,step = 0.01))
plt.contourf(X1, X2, clf.predict(np.array([X1.ravel(),X2.ravel()]).T).reshape(X1.shape),alpha = 0.75, cmap = ListedColormap(('red','gree')))
### 画出散点图
for i, j in range(np.unique(y_set)):
plt.scatter(X_set[y_set == j, 0], X_set[y_set == j, 1],
c = ListColormap(('red', 'green'))(i), label = j)
plt.title('Decision Tree Classification (Training set)')
plt.xlabel('Age')
plt.ylabel('Estimated Salary')
plt.legend()
plt.show()
# 将测试数据集可视化
X_set2, y_set2 = X_test, Y_test
T1, T2 = np.meshgrid(np.arange(start = X_set2[:,0].min()-1, stop=X_set2[:,0].max()+1,step = 0.01),
np.arange(start = X_set2[:,1].min()-1, stop=X_set2[:,1].max()+1,step = 0.01))
plt.contourf(T1,T2,clf.predict(np.array([T1.ravel(),T2.ravel()]).T).reshape(T1.shape),
alpha = 0.75, cmap = ListedColormap(('red', 'green')))
plt.xlim(T1.min(), T1.max())
plt.ylim(T2.min(), T2.max())
for i, j in enumerate(np.unique(y_set2)):
plt.scatter(X_set2[y_set2 == j, 0], X_set2[y_set2 == j, 1],
c = ListedColormap(('red', 'green'))(i), label = j)
plt.title('Decision Tree Classification (Test set)')
plt.xlabel('Age')
plt.ylabel('Estimated Salary')
plt.legend()
plt.show()