基础知识点

信息熵 - 条件熵 - 信息增益 - 信息增益率

基尼指数 - 条件基尼指数 - 基尼指数增益

决策树模型

函数说明:

DecisionTreeClassifier(criterion='gini', splitter='best', max_depth=None,min_samples_split=2, min_samples_leaf=1,max_leaf_nodes=None, class_weight=None)

scikit-learn使用的是CATR算法,该算法仅生成二叉树:非叶节点永远只有两个子节点(即问题答案仅有是或否)

  • criterion:用于指定选择节点字段的评价指标,对于分类决策树,默认为'gini',表示采用基尼指数选 择节点的最佳分割字段,也可以选择信息增益的熵’entropy’;对于回归决策树,默认为'mse',表示使用均方误差选择节点的最佳分割字段
  • splitter:用于指定节点中的分割点选择方法,默认为'best',表示从所有的分割点中选择最佳分割点; 如果指定为'random',则表示随机选择分割点
  • max_depth:用于指定决策树的最大深度,默认为None,表示树的生长过程中对深度不做任何限制
  • min_samples_split:用于指定根节点或中间节点能够继续分割的最小样本量, 默认为2。用来抑制树增长
  • min_samples_leaf:用于指定叶节点的最小样本量,默认为1。用来抑制树增长
  • max_leaf_nodes:用于指定最大的叶节点个数,默认为None,表示对叶节点个数不做任何限制
  • class_weight:用于指定因变量中类别之间的权重,默认为None,表示每个类别的权重都相等;如果 为balanced,则表示类别权重与原始样本中类别的比例成反比;还可以通过字典传递类别之间的权重 差异,其形式为{class_label:weight}

模型使用

CART分类及回归

1. 鸢尾花分类决策树

from sklearn.datasets import load_iris
from sklearn.tree import DecisionTreeClassifier

iris = load_iris()
X= iris.data[:,2:] #花瓣长度和宽度
y = iris.target

tree_clf = DecisionTreeClassifier(max_depth=3)
tree_clf.fit(X,y)


'''
输出结果:
DecisionTreeClassifier(ccp_alpha=0.0, class_weight=None, criterion='gini',
                       max_depth=2, max_features=None, max_leaf_nodes=None,
                       min_impurity_decrease=0.0, min_impurity_split=None,
                       min_samples_leaf=1, min_samples_split=2,
                       min_weight_fraction_leaf=0.0, presort='deprecated',
                       random_state=None, splitter='best')
采用基尼指数选择节点的最佳分割字段,树的最大深度为3,最小样本量为1,能够继续分割的最小样本量为2
'''
from sklearn import tree
import matplotlib.pyplot as plt
plt.figure(figsize=(15,10))
tree.plot_tree(tree_clf,feature_names=iris.feature_names[2:],class_names=iris.target_names,rounded=True,filled=True,fontsize=20)
plt.show()

 

节点的samples属性统计它应用的训练实例数量。例如,有100个训练实例的花瓣长度大于2.45cm(深度1,右),其中54个花瓣宽度小于1.75cm(深度2,左)。节点的value属性说明了该节点上每个类别的训练实例数量。例如,右下节点深度2应用在0个山鸢尾、1个变色鸢尾和45个维吉尼亚鸢尾实例上。最后,节点的gini属性衡量其不纯度(impurity):如果应用的所有训练实例都属于同一个类别,那么节点就是“纯”的(gini=0)。例如,深度1左侧节点仅应用于山鸢尾花训练实例,所以它就是纯的,并且gini值为0。例如,深度2左侧节点,基尼系数等于1-(\frac{0}{54})^{2}-(\frac{49}{54})^{2}-(\frac{5}{54})^{2}\approx 0.168

# 估计某个实例属于特定类的概率
print(tree_clf.predict_proba([[5,1.5]])) # [[0.         0.33333333 0.66666667]],2/6,4/6
print(tree_clf.predict([[5,1.5]])) #[2]

 2.正则化超参数

决策树极少对训练数据做出假设(比如线性模型就正好相反,它显然假设数据是线性的)。如果不加以限制,树的结构将跟随训练集变化,严密拟合,并且很可能过拟合。这种模型通常被称为非参数模型,这不是说它不包含任何参数(事实上它通常有很多参数),而是指在训练之前没有确定参数的数量,导致模型结构自由而紧密地贴近数据。相反,参数模型(比如线性模型)则有预先设定好的一部分参数,因此其自由度受限,从而降低了过拟合的风险(但是增加了欠拟合的风险)。

DecisionTreeClassifier类正则化一般通过设置以下参数 :

max_depth:限制决策树的最大深度

min_samples_split:增大分裂节点必须有的最小样本数

min_samples_leaf:增大叶节点必须有的最小样本量

min_weight_fraction_leaf:与min_samples_leaf一样,但表现为加权实例总数的占比

max_leaf_nodes:限制最大叶节点数量

max_features:限制分裂每个节点苹果的最大特征数量

还可以先不加约束地训练模型,然后再对不必要的节点进行剪枝(删除)。如果一个节点的子节点全部为叶节点,则该节点可被认为不必要,除非它所表示的纯度提升有重要的统计意义。标准统计测试(比如χ2测试)用来估算“提升纯粹是出于偶然”(被称为零假设)的概率。如果这个概率(称之为p值)高于一个给定阈值(通常是5%,由超参数控制),那么这个节点可被认为不必要,其子节点可被删除。直到所有不必要的节点都被删除,剪枝过程结束。

3. 鸢尾花回归决策树

from sklearn.datasets import load_iris
iris = load_iris()
X = iris.data[:,2:]
y = iris.target

from sklearn.tree import DecisionTreeRegressor
tree_reg = DecisionTreeRegressor(max_depth=2)
tree_reg.fit(X,y)

from sklearn import tree
import matplotlib.pyplot as plt
plt.figure(figsize=(10,8))
tree.plot_tree(tree_reg,feature_names=iris.feature_names[2:],class_names=iris.target_names,rounded=True,filled=True,fontsize=20)
plt.show()

CRAT回归与分类的差别在于每个节点上不再预测一个类别而是预算一个值。这个预测值是与这个叶节点关联的实例的平均目标值。比如,想要对花瓣长度为3,宽度为2的新实例进行预测,则预测值为1.978,预测产生的均方误差为0.021。同样可以通过调整参数来防止过拟合。

决策树优缺点

优点:易理解,数据准备工作简单,其他模型通常需要数据归一化

缺点:已拟合,对数据的变化敏感

改进:剪枝,主成分分析处理特征,随机森林

练习题

1.如果训练集有100万个实例,训练决策树(无约束)大致的深度是多少?

\log_{2}10^{6}= 20

2.通常来说,子节点的基尼不纯度是高于还是低于其父节点?是通常更高/更低?还是永远更高/更低?

子节点的基尼不纯度通常是低于其父节点,但如果一个子节点的不纯度远小于同层树的另一个子节点,那也可能使子节点的基尼不纯度高于父节点,只要那个不纯度更低的子节点能够抵偿这个增加即可。

3.如果决策树过拟合训练集,减少max_depth是否为一个好主意?

是,会限制模型,使其正则化

4.如果决策树对训练集欠拟合,尝试缩放输入特征是否为一个好主意?

不是,决策树不关心数据是否归一化,所以缩放特征没用

5.如果在包含100万个实例的训练集上训练决策树需要一个小时,那么在包含1000万个实例的训练集上训练决策树,大概需要多长时间?

决策树的训练复杂度为O(n×mlog(m))。所以,如果将训练集大小乘以10,训练时间将乘以K=(n×10m×log(10m))/(n×m×log(m))=10×log(10m)/log(m)。如果m=10^{6},那么K≈11.7,所以训练1000万个实例大约需要11.7小时。

6.如果训练集包含10万个实例,设置presort=True可以加快训练吗?

只有当数据集小于数千个实例时,预处理训练集才可以加速训练。如果包含100000个实例,设置presort=True会显著减慢训练

案例1:泰坦尼克号

1. 数据准备

import pandas as pd
titanic = pd.read_csv(r'C:\Users\zxzy\Downloads\query\课程数据挖掘\第14章 决策树与随机森林\Titanic.csv')
titanic.info()

 

# 删除无意义的变量,并检查剩余变量是否含有缺失值
titanic.drop(['PassengerId','Name','Ticket','Cabin'],axis=1,inplace=True)
titanic.isnull().sum(axis=0)

 

#对sex分组,用各组乘客的平均年龄填充各组中的缺失年龄
fillna_titanic = []
for i in titanic.Sex.unique():
    update = titanic.loc[titanic.Sex==i,].fillna(value={'Age':titanic.Age[titanic.Sex==i].mean()},inplace=False)
    fillna_titanic.append(update)
titanic = pd.concat(fillna_titanic)
# 使用Embarked变量的众数填充缺失值
titanic.fillna(value={'Embarked':titanic.Embarked.mode()[0]},inplace=True)

# 将数值型的Pclass转换为类别型,否则无法对其哑变量处理
titanic.Pclass = titanic.Pclass.astype('category')
# 哑变量处理
dummy = pd.get_dummies(titanic[['Sex','Embarked','Pclass']])
titanic = pd.concat([titanic,dummy],axis=1)
# 删除元素的'Sex','Embarked','Pclass'
titanic.drop(['Sex','Embarked','Pclass'],inplace=True,axis=1)
# 取出所有自变量名称
predictors = titanic.columns[1:]

from sklearn import model_selection
# 讲数据集拆分为训练集和测试集,且测试集的比例为25%
X_train,X_test,y_train,y_test = model_selection.train_test_split(titanic[predictors],titanic.Survived)

2.寻找超参数

使用交叉验证的网格搜索(GridSearchCV)为DecisionTreeClassifier找到适合的超参数

# 预设各参数的不同选项值
max_depth = [2,3,4,5,6]
min_samples_split = [2,4,6,8]
min_samples_leaf = [2,4,8,10,12]
# 将各参数值以字典形式组织起来
parameters = {'max_depth':max_depth, 'min_samples_split':min_samples_split, 'min_samples_leaf':min_samples_leaf}
from sklearn.model_selection import GridSearchCV
from sklearn import tree
# 网格搜索法,测试不同的参数值
grid_dtcateg = GridSearchCV(estimator=tree.DecisionTreeClassifier(),param_grid=parameters,cv=10)
# 模型拟合
grid_dtcateg.fit(X_train,y_train)
grid_dtcateg.best_params_ # {'max_depth': 6, 'min_samples_leaf': 8, 'min_samples_split': 2}

3.训练模型与评估

# 构建分类决策树
cart_class = tree.DecisionTreeClassifier(max_depth=5,min_samples_leaf=4,min_samples_split=4)
# 模拟拟合
decision_tree = cart_class.fit(X_train,y_train)
# 模型预测
pred = cart_class.predict(X_test)
# 模型准确率
from sklearn.metrics import accuracy_score
print('模型在测试集的预测准确率:\n',round(accuracy_score(y_test,pred),2)) # 0.81
print('模型在训练集的预测准确率:\n',round(accuracy_score(y_train,cart_class.predict(X_train)),2)) # 0.86
from sklearn.metrics import roc_curve,auc
import matplotlib.pyplot as plt
y_score = cart_class.predict_proba(X_test)[:,1]
fpr,tpr,threshold = roc_curve(y_test,y_score)
roc_auc = auc(fpr,tpr)

# 面积
plt.stackplot(fpr,tpr,color='steelblue',alpha=0.5,edgecolor='black')
# 边际线
plt.plot(fpr,tpr,color='black',lw=1)
# 对角线
plt.plot([0,1],[0,1],color='red',linestyle='--')

plt.text(0.5,0.3,'ROC curve(area = %0.2f)'% roc_auc)
plt.xlabel('1-Specificity')
plt.ylabel('Sensitivity')
plt.show()

# 安装Graphviz,下载对应版本的zip文件
# https://graphviz.gitlab.io/_pages/Download/Download_windows.html
# 然后将解压文件中的bin设置到环境变量中,重启电脑
from sklearn.tree import export_graphviz
from IPython.display import Image
import pydotplus
from six import StringIO
dot_data = StringIO()
export_graphviz(
    decision_tree,
    out_file=dot_data,
    feature_names=predictors,
    class_names=['Unsurvived','Survived'],
#     filled=True,
    rounded=True,
    special_characters=True
)
# 决策树展现
graph = pydotplus.graph_from_dot_data(dot_data.getvalue())
Image(graph.create_png())

 

参考文献:

[1](法)奥雷利安·杰龙.机器学习实战:基于Scikit-Learn、Keras和TensorFlow:原书第2版[M].华章分社:北京,2020:1.

Logo

魔乐社区(Modelers.cn) 是一个中立、公益的人工智能社区,提供人工智能工具、模型、数据的托管、展示与应用协同服务,为人工智能开发及爱好者搭建开放的学习交流平台。社区通过理事会方式运作,由全产业链共同建设、共同运营、共同享有,推动国产AI生态繁荣发展。

更多推荐