机器学习(四):决策树

1,180 阅读6分钟

一、决策树方法介绍

决策树是一种基本的分类和回归方法,由结点和有向边组成,包含根节点(root node)、内部节点(internal node)和叶节点(leaf node)。

  • 规则集合性质:互斥且完备(每个实例都被一条路径或一条规则覆盖,且只被一条路径或一条规则覆盖)。

二、相关概念:以贷款数据为例

  • 概念:熵、经验熵、条件熵、经验条件熵、信息增益、分裂信息、信息增益率、基尼系数
  • 信息增益的计算:父节点的熵减去子节点熵的加权平均,计算如下:
  1. 贷款申请数据按“是否放贷”分为2组,第一组“放贷”占比9/15,第二组“不放贷”占比6/15,可以计算根节点的熵:
  2. 考虑“年龄”特征有青年、中年和老年,在所有样本量中,青年占比5/15,中年5/15,老年5/15。

(1)青年:所有青年样本中,第一组青年占比2/5,第二组青年占比3/5 (2)中年:所有中年样本中,第一组中年占比3/5,第二组中年占比2/5 (3)老年:所有老年样本中,第一组老年占比4/5,第二组老年占比1/5 “年龄”分组的信息增益=0.971-0.888=0.083。依次可以计算其他特征(房产、工作、信贷)的信息增益,选择信息增益最大的特征构建决策树。

  • 决策树的停止条件

1、当前节点全部属于同一类别;

2、当前节点样本集为空(当前节点为叶节点,类别为父节点样本最多的类别);

3、当前节点属性集为空,或所有样本属性取值相同(当前节点为叶节点,类别为该节点样本最多的类别)。

三、构建决策树的方法——ID3、C4.5和CART

(一)ID3算法

  • 算法介绍:核心是在决策树的节点根据信息增益最大准则选择特征。从根节点开始,对结点计算所有可能特征的信息增益,选择信息增益最大的特征作为结点特征并建立子节点,然后对子节点递归调用以上方法构建决策树。
  • 递归停止条件:1、所有的类标签完全相同,直接返回该类标签;2、使用完了所有特征仍然不能将数据划分为仅包含唯一类别的分组,即决策树构建失败(特征不够用,数据维度不够),此时挑选数量最多的类别作为返回值。
  • 决策树剪枝:为防止过拟合问题,可以对决策树剪枝——预剪枝和后剪枝。预剪枝是在构建决策树时建立某些规则限制决策树的生长,降低过拟合的风险并缩短建树时间,但有可能带来欠拟合问题;后剪枝是一种全局优化方法,在建好树之后删除一些子树(如果剪枝后和没剪枝前的误差相差不大则可以减掉子树),这种剪枝效果一般比较好。
  • 优缺点总结 |优缺点|总结| |:---|:---| |缺点|1、无剪枝策略,容易过拟合;2、趋向于类别多的属性;3、只能处理离散分布的特征,只能分类;4、没有考虑缺失值。|

(二)C4.5算法

  • 算法介绍:核心是通过信息增益率最大选择特征,不同于ID3算法倾向于选择拥有多个属性值的属性作为分裂属性;可以处理离散型和连续型(离散化处理)的属性特征,也能处理有缺失属性值的训练数据。
  • 连续型数据特征离散化处理:当属性类型为连续型时,需要对数据离散化处理。将属性A的多个属性值按升序排列,通过二分类法将该属性的所有属性值分为2部分(共有N-1种分法,阈值=相邻属性值的中间值),选择信息增益最大的阈值进行划分。
  • 优缺点总结 |优缺点|总结| |:---|:---| |优点|1、克服了ID的缺点——3倾向于选择有多个属性值的属性;2、能够处理离散和连续属性变量;3、能进行剪枝预防过拟合;4、能处理有缺失属性值的数据。 |缺点|1、计算效率低,尤其是连续属性值的样本;2、

(三)CART算法

  • 介绍:典型的二叉决策树,可以做分类(离散型数据,基尼系数最小化原则)或回归(连续型数据,平方误差最小化原则),连续型变量不需要离散化,条件成立向左,反之向右。做分类时,待预测样本落在某叶子结点,输出该叶子结点中所有样本类别最多的那一类;做回归时,待预测样本落在某叶子结点,输出该叶子结点中所有样本的均值。
  • 最优二分方案的评价原则

(一)分类树:对属性A分别计算任意属性值将数据集划分为2部分之后的基尼系数选取其中的最小值作为A的最优方案,然后对样本集S计算所有属性的最优二分方案,选取其中的最小值作为整个样本集的最优方案。 (二)回归树:跟分类树类似,根据平方误差最小化选择属性A的最优二分方案,进一步选取样本集S的最优二分方案。

三、决策树的优缺点

优缺点总结
优点1、白盒模型,易于理解和解释;2、模型建立所需数据量较少;3、可同时用于分类和回归。
缺点1、容易过拟合,需要多参数调节;2、对数据敏感,可通过集成算法优化;3、优化过程是局部优化,未必能达到全局最优。

四、Python代码实现:鸢尾花分类和波士顿房价预测

  • 案例1:鸢尾花分类
# 导包
from sklearn.datasets import load_iris
from sklearn import tree
# 导图模块
import graphviz
import os

iris=load_iris()
x=iris.data
y=iris.target

# 建分类树,树的最大深度=3
clf=tree.DecisionTreeClassifier(max_depth=3,random_state=10)
clf=clf.fit(x,y)

# 导图
dot_data=tree.export_graphviz(clf,out_file=None,feature_names=iris.feature_names,class_names=iris.target_names,filled=True,rounded=True,special_characters=True)
os.chdir('data\\graphviz-2.38\\release\\bin')
graph=graphviz.Source(dot_data)
graph.view()

# 数据的特征和类标签
iris.feature_names
iris.target_names

设置中文显示:导图代码修改

with open('tree.dot','w',encoding='utf-8') as f:
	f=tree.export_graphviz(clf,out_file=f,feature_names=['花萼长度','花萼宽度','花瓣长度','花瓣宽度'],class_names=iris.target_names,filled=True,rounded=True,special_characters=True)

然后进入bin文件夹对tree.dot文件的字体进行替换:fontname="Microsoft YaHei",并通过Anaconda Prompt进入bin文件夹下,输入命令:dot -Tpng tree.dot -o iris.png,将结果保存为图片(图片名iris.png)。

  • 案例2:波士顿房价预测
from sklearn.datasets import load_boston
from sklearn import tree
import graphviz
import numpy as np

boston=load_boston()
x=boston.data
y=boston.target
# 数据集划分
from sklearn.model_selection import train_test_split
x_train,x_test,y_train,y_test=train_test_split(x,y,test_size=0.2,random_state=42)
dtr=tree.DecisionTreeRegressor(random_state=1)
dtr.fit(x_train,y_train)
dtr.score(x_test,y_test)

  • 参数调优:网格搜索交叉验证
from sklearn.model_selection import GridSearchCV
x_train,x_test,y_train,y_test=train_test_split(x,y,test_size=0.2,random_state=42)
# 设置参数范围
gini_impure=np.linspace(0,0.01,10)
param_grid={'min_impurity_decrease':gini_impure,'max_depth':range(2,10),'min_samples_split':range(2,50,2)}
reg=GridSearchCV(tree.DecisionTreeRegressor(),param_grid=param_grid,cv=10)
# 拟合数据建模
reg.fit(x_train,y_train)
# 最优参数和模型评分
print(reg.best_params_)
print(reg.score(x_test,y_test))

  • 保存和读取模型:2种方法
#保存读取模型——方法1
from sklearn.externals import joblib
joblib.dump(reg, 'regtree.pkl')  
#读取
clf = joblib.load('regtree.pkl') 

#保存读取模型——方法2
import pickle
with open('regtree.pk2','wb') as f:
    pickle.dump(clf,f)
with open('regtree.pk2','rb') as r:
    new_clf=pickle.load(r)