决策树算法
1 决策树
1.1 算法介绍
决策树是一种树形模型,也是一种十分常用的分类和回归方法。决策树算法是一种监督学习算法,英文是Decision tree。
决策树思想的来源非常朴素,程序设计中的条件分支结构就是 if-else 结构,最早的决策树就是利用这类结构分割数据的一种分类学习方法。
- 是一种树形结构,本质是一颗由多个判断节点组成的树
- 其中每个内部节点表示一个属性上的判断,
- 每个分支代表一个判断结果的输出,
- 最后每个叶节点代表一种分类结果。
1.2 入门案例
基于鸢尾花数据绘制图像
import numpy as np
import matplotlib.pyplot as plt
from sklearn import datasets
iris = datasets.load_iris()
X = iris.data[:,2:]
y = iris.target
plt.scatter(X[y==0,0],X[y==0,1])
plt.scatter(X[y==1,0],X[y==1,1])
plt.scatter(X[y==2,0],X[y==2,1])
plt.show()
训练决策树模型
from sklearn.tree import DecisionTreeClassifier
tree = DecisionTreeClassifier(max_depth=2,criterion="entropy")
tree.fit(X,y)
依据模型绘制决策树的决策边界
#找到模型的决策边界,并绘制图像(此方法所用到的api不需要掌握,能够调用就行)
def plot_decision_boundary(model,axis):
x0,x1 = np.meshgrid(
np.linspace(axis[0],axis[1],int((axis[1]-axis[0])*100)).reshape(-1,1),
np.linspace(axis[2],axis[3],int((axis[3]-axis[2])*100)).reshape(-1,1)
)
X_new = np.c_[x0.ravel(),x1.ravel()]
y_predict = model.predict(X_new)
zz = y_predict.reshape(x0.shape)
from matplotlib.colors import ListedColormap
custom_map = ListedColormap(["#EF9A9A","#FFF59D","#90CAF9"])
plt.contourf(x0,x1,zz,linewidth=5,cmap=custom_map)
plot_decision_boundary(tree,axis=[0.5,7.5,0,3])
plt.scatter(X[y==0,0],X[y==0,1])
plt.scatter(X[y==1,0],X[y==1,1])
plt.scatter(X[y==2,0],X[y==2,1])
plt.show()
树模型可视化
from sklearn.tree import plot_tree
import matplotlib.pyplot as plt
plot_tree(tree,filled=True)
plt.show()
从上面的可视化图形中看出
- X[1] <=0.8 作为第一次分割的依据,满足条件的所有样本均为统一类别
- X[1]>0.8的,依据 X[1]<=0.75 为划分依据
- 由于设置了树的最大深度为2,第二层的两个叶子节点没有完全区分开
决策树算法:
- 是非参数学习算法
- 可以解决分类(多分类)问题
- 可以解决回归问题:落在叶子节点的数据的平均值作为回归的结果
基于规则构建决策树需要以下三个步骤
- 特征选择:选取有较强分类能力的特征
- 决策树生成
- 决策树剪枝
2 ID3决策树
ID3 树是基于信息增益构建的决策树。
2.1 信息熵
熵在信息论中代表随机变量不确定度的度量。
- 熵越大,数据的不确定性度越高
- 熵越小,数据的不确定性越低
信息熵公式
例子1:假如有三个类别,分别占比为:{⅓,⅓,⅓},信息熵计算结果为:
例子2:假如有三个类别,分别占比为:{1/10,2/10,7/10},信息熵计算结果为:
例子3:假如有三个类别,分别占比为:{1,0,0},信息熵计算结果为:
当数据类别只有两类的情况下,公式可以做如下转换
代码角度理解信息熵的概念
import numpy as np
import matplotlib.pyplot as plt
def entropy(p):
return -p*np.log(p)-(1-p)*np.log(1-p)
x = np.linspace(0.01,0.99,200)
plt.plot(x,entropy(x))
plt.show()
观察上图可以得出,当我们的系统每一个类别是等概率的时候,系统的信息熵最高,当系统偏向于某一列,相当于系统有了一定程度的确定性,直到系统整体百分之百的都到某一类中,此时信息熵就达到了最低值,即为0。上述结论也可以拓展到多类别的情况。
2.2 信息增益
信息增益用于衡量那个特征更加适合优先分裂。
下面以常用的贷款申请样本数据表为样本集,通过数学计算来介绍信息增益计算过程。
Step1 计算经验熵
类别一共是两个拒绝/同意,数量分别是6和9,根据熵定义可得:
Step2 各特征的条件熵
Step3 计算增益
最终构建的决策树如下:
ID3算法步骤
- 计算每个特征的信息增益
- 使用信息增益最大的特征将数据集 S 拆分为子集
- 使用该特征(信息增益最大的特征)作为决策树的一个节点
- 使用剩余特征对子集重复上述(1,2,3)过程
3 C4.5决策树
C4.5算法的核心思想是ID3算法,对ID3算法进行了相应的改进。
3.1 信息增益率
信息增益率用于计算模型优先选择那个特征进行树分裂,使用信息增益率构建的决策树称为 C4.5 决策树。
Gain_Ratio 表示信息增益率,其中 IV 表示分裂信息、内在信息。
-
= 特征的信息增益 ➗ 内在信息
-
如果某个特征的特征值种类较多,则其内在信息值就越大。即:特征值种类越多,除以的系数就越大。
-
如果某个特征的特征值种类较小,则其内在信息值就越小。即:特征值种类越小,除以的系数就越小。
信息增益比本质: 是在信息增益的基础之上乘上一个惩罚参数。特征个数较多时,惩罚参数较小;特征个数较少时,惩罚参数较大。
3.2 对比 ID3 算法
ID3算法缺点
- ID3算法不能处理具有连续值的属性
- ID3算法不能处理属性具有缺失值的样本
- 算法会生成很深的树,容易产生过拟合现象
- 算法一般会优先选择有较多属性值的特征,因为属性值多的特征会有相对较大的信息增益,但这里的属性并不一定是最优的。
C4.5算法的核心思想是ID3算法,对ID3算法进行了相应的改进。
C4.5使用的是信息增益比来选择特征,克服了ID3的不足。可以处理离散型描述属性,也可以处理连续数值型属性,也能处理不完整数据。
C4.5算法
-
优点:
- 分类规则利于理解,准确率高
- 采用了一种后剪枝方法,避免树的高度无节制的增长,避免过度拟合数据
-
缺点:
- 在构造过程中,需要对数据集进行多次的顺序扫描和排序,导致算法的低效
- C4.5只适合于能够驻留内存的数据集,当数据集非常大时,程序无法运行
无论是ID3还是C4.5最好在小数据集上使用,当特征取值很多时最好使用C4.5算法。
4 CART决策树
4.1 算法介绍
Cart模型是一种决策树模型,它即可以用于分类,也可以用于回归,其学习算法分为下面两步:
- 决策树生成:用训练数据生成决策树,生成树尽可能大
- 决策树剪枝:基于损失函数最小化的剪枝,用验证数据对生成的数据进行剪枝。
分类和回归树模型采用不同的最优化策略。Cart回归树使用平方误差最小化策略,Cart分类生成树采用的基尼指数最小化策略。
Scikit-learn中有两类决策树,他们均采用优化的Cart决策树算法。一个是DecisionTreeClassifier,一个是DecisionTreeRegressor回归。
4.2 CART 分类决策树
4.2.1 基尼指数
使用基尼指数构建的决策树称为 CART 决策树。
信息增益(ID3)、信息增益率值越大(C4.5),则说明优先选择该特征。
基尼指数值越小(cart),则说明优先选择该特征。
基尼指数计算举例
(1)是否有房
计算有房子的基尼值: 有房子有 1、4、7 共计三个样本,对应:3个no、0个yes
计算无房子的基尼值:无房子有 2、3、5、6、8、9、10 共七个样本,对应:4个no、3个yes
计算基尼指数:第一部分样本数量占了总样本的 3/10、第二部分样本数量占了总样本的 7/10:
(2)婚姻状况
计算 {married} 和 {single,divorced} 情况下的基尼指数,结婚的基尼值,有 2、4、6、9 共 4 个样本,并且对应目标值全部为 no:
不结婚的基尼值,有 1、3、5、7、8、10 共 6 个样本,并且对应 3 个 no,3 个 yes:
以 married 作为分裂点的基尼指数:
同理,计算 {single} | {married,divorced} 情况下的基尼指数和计算 {divorced} | {single,married} 情况下基尼指数,分别为0.367和0.4。最终该特征的基尼值为 0.3,并且预选分裂点为:{married} 和 {single,divorced}
(3)年收入
先将数值型属性升序排列,以相邻中间值作为待确定分裂点:
以年收入 65 将样本分为两部分,计算基尼指数:
以此类推计算所有分割点的基尼指数,我们发现最小的基尼指数为 0.3。
此时,我们发现:
- 以是否有房作为分裂点的基尼指数为:0.343
- 以婚姻状况为分裂特征、以 married 作为分裂点的基尼指数为:0.3
- 以年收入作为分裂特征、以 97.5 作为分裂点的的基尼指数为:0.3
最小基尼指数有两个分裂点,我们随机选择一个即可,假设婚姻状况,则可确定决策树如下:
重复上面步骤,直到每个叶子结点纯度达到最高。
4.2.2 Cart分类树原理
如果目标变量是离散变量,则是classfication Tree分类树。
分类树是使用树结构算法将数据分成离散类的方法。
(1)分类树两个关键点:
将训练样本进行递归地划分自变量空间进行建树‚用验证数据进行剪枝。
(2)对于离散变量X(x1…xn)处理:
分别取X变量各值的不同组合,将其分到树的左枝或右枝,并对不同组合而产生的树,进行评判,找出最佳组合。如果只有两个取值,直接根据这两个值就可以划分树。取值多于两个的情况就复杂一些了,如变量年纪,其值有“少年”、“中年”、“老年”,则分别生产{少年,中年}和{老年},{少年、老年}和{中年},{中年,老年}和{少年},这三种组合,最后评判对目标区分最佳的组合。因为CART二分的特性,当训练数据具有两个以上的类别,CART需考虑将目标类别合并成两个超类别,这个过程称为双化。这里可以说一个公式,n个属性,可以分出(2^n-2)/2种情况。
CART树剪枝
我们知道,决策树算法对训练集很容易过拟合,导致泛化能力很差,为解决此问题,需要对CART树进行剪枝。CART剪枝算法从“完全生长”的决策树的底端剪去一些子树,使决策树变小,从而能够对未知数据有更准确的预测,也就是说CART使用的是后剪枝法。一般分为两步:先生成决策树,产生所有可能的剪枝后的CART树,然后使用交叉验证来检验各种剪枝的效果,最后选择泛化能力好的剪枝策略。
4.2.3 使用CART算法构建决策树
import numpy as np
import matplotlib.pyplot as plt
from sklearn import datasets
iris = datasets.load_iris()
X = iris.data[:,2:]
y = iris.target
from sklearn.tree import DecisionTreeClassifier
#注意:此处传入的是"gini"而不是"entropy",默认criterion='gini'
tree = DecisionTreeClassifier(max_depth=2,criterion="gini")
tree.fit(X,y)
def plot_decision_boundary(model,axis):
x0,x1 = np.meshgrid(
np.linspace(axis[0],axis[1],int((axis[1]-axis[0])*100)).reshape(-1,1),
np.linspace(axis[2],axis[3],int((axis[3]-axis[2])*100)).reshape(-1,1)
)
X_new = np.c_[x0.ravel(),x1.ravel()]
y_predict = model.predict(X_new)
zz = y_predict.reshape(x0.shape)
from matplotlib.colors import ListedColormap
custom_map = ListedColormap(["#EF9A9A","#FFF59D","#90CAF9"])
plt.contourf(x0,x1,zz,linewidth=5,cmap=custom_map)
plot_decision_boundary(tree,axis=[0.5,7.5,0,3])
plt.scatter(X[y==0,0],X[y==0,1])
plt.scatter(X[y==1,0],X[y==1,1])
plt.scatter(X[y==2,0],X[y==2,1])
plt.show()
分析上图可知:
- X[1] <=0.8 作为第一次分割的依据,满足条件的所有样本均为同一类别,gini系数为0.667
- X[1]>0.8的,依据 X[1]<=0.75 为划分依据
4.3 CART 回归决策树
CART 回归树和 CART 分类树的不同之处在于:
- CART 分类树预测输出的是一个离散值,CART 回归树预测输出的是一个连续值。
- CART 分类树使用基尼指数作为划分、构建树的依据,CART 回归树使用平方损失。
- 分类树使用叶子节点里出现更多次数的类别作为预测类别,回归树则采用叶子节点里均值作为预测输出
CART 回归树构建:
例子:假设数据集只有 1 个特征 x, 目标值值为 y,如下图所示:
x | 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 |
---|---|---|---|---|---|---|---|---|---|---|
y | 5.56 | 5.7 | 5.91 | 6.4 | 6.8 | 7.05 | 8.9 | 8.7 | 9 | 9.05 |
由于只有 1 个特征,所以只需要选择该特征的最优划分点,并不需要计算其他特征。
(1)先将特征 x 的值排序,并取相邻元素均值作为待划分点,如下图所示:
s | 1.5 | 2.5 | 3.5 | 4.5 | 5.5 | 6.5 | 7.5 | 8.5 | 9.5 |
---|
(2)计算每一个划分点的平方损失,例如:1.5 的平方损失计算过程为:
(3)以此方式计算 2.5、3.5… 等划分点的平方损失,结果如下所示:
s | 1.5 | 2.5 | 3.5 | 4.5 | 5.5 | 6.5 | 7.5 | 8.5 | 9.5 |
---|---|---|---|---|---|---|---|---|---|
m(s) | 15.72 | 12.07 | 8.36 | 5.78 | 3.91 | 1.93 | 8.01 | 11.73 | 15.74 |
(4)当划分点 s=6.5 时,m(s) 最小。因此,第一个划分变量:特征为 X, 切分点为 6.5,即:j=x, s=6.5
(5)对左子树的 6 个结点计算每个划分点的平方式损失,找出最优划分点:
x | 1 | 2 | 3 | 4 | 5 | 6 |
---|---|---|---|---|---|---|
y | 5.56 | 5.56 | 5.7 | 5.91 | 6.4 | 6.8 |
s | 1.5 | 2.5 | 3.5 | 4.5 | 5.5 |
---|---|---|---|---|---|
m(s) | 1.3087 | 0.754 | 0.2771 | 0.4368 | 1.0644 |
(6)s=3.5时,m(s) 最小,所以左子树继续以 3.5 进行分裂:
(7)假设在生成3个区域 之后停止划分,以上就是回归树。每一个叶子结点的输出为:挂在该结点上的所有样本均值。
x | 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 |
---|---|---|---|---|---|---|---|---|---|---|
y | 5.56 | 5.7 | 5.91 | 6.4 | 6.8 | 7.05 | 8.9 | 8.7 | 9 | 9.05 |
1号样本真实值 5.56 预测结果:5.72 =(5.56+5.7+5.91)/ 3
2号样本真实值是 5.7 预测结果:5.72
3 号样本真实值是 5.91 预测结果 5.72
CART 回归树构建过程如下:
- 选择第一个特征,将该特征的值进行排序,取相邻点计算均值作为待划分点
- 根据所有划分点,将数据集分成两部分:R1、R2
- R1 和 R2 两部分的平方损失相加作为该切分点平方损失
- 取最小的平方损失的划分点,作为当前特征的划分点
- 以此计算其他特征的最优划分点、以及该划分点对应的损失值
- 在所有的特征的划分点中,选择出最小平方损失的划分点,作为当前树的分裂点
回归决策树是二叉树
5 剪枝
剪枝 (pruning)是决策树学习算法对付 过拟合 的主要手段。
在决策树学习中,为了尽可能正确分类训练样本,结点划分过程将不断重复,有时会造成决策树分支过多,这时就可能因训练样本学得"太好"了,以致于把训练集自身的一些特点当作所有数据都具有的一般性质而导致过拟合。因此,可通过主动去掉一些分支来降低过拟合的风险。
剪枝是指将一颗子树的子节点全部删掉,利用叶子节点替换子树(实质上是后剪枝技术),也可以(假定当前对以root为根的子树进行剪枝)只保留根节点本身而删除所有的叶子,以下图为例:
决策树是充分考虑了所有的数据点而生成的复杂树,有可能出现过拟合的情况,决策树越复杂,过拟合的程度会越高。
考虑极端的情况:如果我们令所有的叶子节点都只含有一个数据点,那么我们能够保证所有的训练数据都能准确分类,但是很有可能得到高的预测误差,原因是将训练数据中所有的噪声数据都”准确划分”了,强化了噪声数据的作用。
而剪枝修剪分裂前后分类误差相差不大的子树,能够降低决策树的复杂度,降低过拟合出现的概率。
两种方案:先剪枝和后剪枝
- 先剪枝说白了就是提前结束决策树的增长,跟上述决策树停止生长的方法一样。预剪枝是指在决策树生成过程中,对每个结点在划分前先进行估计,若当前结点的划分不能带来决策树泛化性能提升,则停止划分并将当前结点标记为叶结点;
- 后剪枝是指在决策树生长完成之后再进行剪枝的过程。后剪枝则是先从训练集生成一棵完整的决策树,然后自底向上地对非叶结点进行考察,若将该结点对应的子树替换为叶结点能带来决策树泛化性能提升,则将该子树替换为叶结点。
剪枝技术对比
预剪枝
- 优点:
- 预剪枝使决策树的很多分支没有展开,不单降低了过拟合风险,还显著减少了决策树的训练、测试时间开销
- 缺点:
- 有些分支的当前划分虽不能提升泛化性能,甚至会导致泛化性能降低,但在其基础上进行的后续划分却有可能导致性能的显著提高
- 预剪枝决策树也带来了欠拟合的风险
后剪枝
- 优点:
- 比预剪枝保留了更多的分支。一般情况下,后剪枝决策树的欠拟合风险很小,泛化性能往往优于预剪枝
- 缺点:
- 但后剪枝过程是在生成完全决策树之后进行的,并且要自底向上地对树中所有非叶子节点进行逐一考察,因此在训练时间开销比未剪枝的决策树和预剪枝的决策树都要大得多。
6 决策树案例
泰坦尼克号生存预测
泰坦尼克号沉没是历史上最著名的沉船事件。1912年4月15日,在她的处女航中,泰坦尼克号在与冰山相撞后沉没,在2224名乘客和船员中造成1502人死亡。这场耸人听闻的悲剧震惊了国际社会,并为船舶制定了更好的安全规定。 造成海难失事的原因之一是乘客和船员没有足够的救生艇。尽管幸存下来有一些运气因素,但有些人比其他人更容易生存,例如妇女,儿童和社会地位较高的人群。 在这个案例中,我们要求您完成对哪些人可能存活的分析
import pandas as pd
import numpy as np
from sklearn.feature_extraction import DictVectorizer
from sklearn.model_selection import train_test_split
from sklearn.tree import DecisionTreeClassifier, export_graphviz
from sklearn.tree import plot_tree
import joblib
import matplotlib.pyplot as plt
# 模型训练
def test01():
data = pd.read_csv("data/泰坦尼克号数据集.csv")
print(data.head())
# 确定特征值和目标值
x = data[["Pclass", "Age", "Sex"]].copy()
y = data["Survived"]
# 缺失值需要处理,将特征当中有类别的这些特征进行字典特征抽取
x['Age'].fillna(x['Age'].mean(), inplace=True)
# 类别特征进行独热编码
x = pd.get_dummies(x, columns=['Pclass', 'Sex'])
# 数据集划分
x_train, x_test, y_train, y_test = train_test_split(x, y, random_state=22)
# 4.机器学习(决策树)
estimator = DecisionTreeClassifier(criterion="entropy", max_depth=5)
estimator.fit(x_train, y_train)
# 5.模型评估
print('准确率:', estimator.score(x_test, y_test))
print('预测结果:', estimator.predict(x_test))
# 模型保存
joblib.dump(estimator, 'model/dt.pth')
# 决策树可视化
def test02():
# 1. 加载模型
estimator = joblib.load('model/dt.pth')
# 2. 决策树可视化
fig, ax = plt.subplots(figsize=(50, 50))
plot_tree(estimator,
ax=ax,
max_depth=3,
filled=True,
feature_names=['Age', 'Pclass_1', 'Pclass_2', 'Pclass_3', 'Sex_female', 'Sex_male'],
class_names=['生存', '不生存'])
plt.savefig('a.png', dpi=100)
if __name__ == '__main__':
# test01()
test02()