详解决策树-分类树【菜菜的sklearn课堂笔记】

时间:2022-10-26 12:20:39

文章换行有问题,望谅解

视频作者:菜菜TsaiTsai 链接:【技术干货】菜菜的机器学习sklearn【全85集】Python进阶_哔哩哔哩_bilibili

关键概念:节点

  • 根节点:没有进边,有出边。包含最初的,针对特征的提问。
  • 中间节点:既有进边也有出边,进边只有一条,出边可以有很多条。都是针对特征的提问。
  • 叶子节点:有进边,没有出边,每个叶子节点都是一个类别标签。
  • 子节点和父节点:在两个相连的节点中,更接近根节点的是父节点,另一个是子节点。

DecisionTreeClassifier与红酒数据集

tree.DecisionTreeClassifier(
    ["criterion='gini'", "splitter='best'", 'max_depth=None', 'min_samples_split=2', 'min_samples_leaf=1', 'min_weight_fraction_leaf=0.0', 'max_features=None', 'random_state=None', 'max_leaf_nodes=None', 'min_impurity_decrease=0.0', 'min_impurity_split=None', 'class_weight=None', 'presort=False'],
)

建立一棵树

导入需要的算法库和模块

from sklearn import tree
from sklearn.datasets import load_wine # 里面有很多著名的数据集
from sklearn.model_selection import train_test_split

了解数据

wine = load_wine() # 内置的红酒数据集
print(type(wine)) # 类似于字典
---
<class 'sklearn.utils.Bunch'>

wine.keys()
---
dict_keys(['data', 'target', 'target_names', 'DESCR', 'feature_names'])

print(wine.DESCR) # 数据集简介
# 太多了,这里不写了

wine.feature_names # 特征名,也就是X每个维度代表什么,一会有翻译
---
['alcohol', 'malic_acid', 'ash', 'alcalinity_of_ash', 'magnesium', 'total_phenols', 'flavanoids', 'nonflavanoid_phenols', 'proanthocyanins', 'color_intensity', 'hue', 'od280/od315_of_diluted_wines', 'proline']

wine.target_names # 目标名,也就是每一类Y的名字。这里有3类Y,y_i in ('class_0', 'class_1', 'class_2')
---
array(['class_0', 'class_1', 'class_2'], dtype='<U7')

wine.data.shape # 这里说明了数据集X一共有178个数据,13个特征
---
(178, 13)

wine.target # 即整个Y集
---
# 就是178个0,1,2这里不写了

import pandas as pd
pd.concat([pd.DataFrame(wine.data),pd.DataFrame(wine.target)],axis=1) # 将X和Y连成一个DataFrame
---

详解决策树-分类树【菜菜的sklearn课堂笔记】

详解决策树-分类树【菜菜的sklearn课堂笔记】

分割训练集和数据集

Xtrain, Xtest, Ytrain, Ytest = train_test_split(wine.data,wine.target,test_size=0.3)
# test_size=0.3意味着训练集占70%,测试集占30%
# 注意,这里是随机的

print(type(Xtrain))
---
<class 'numpy.ndarray'>

Xtrain.shape,Xtest.shape,Ytrain.shape,Ytest.shape
---
((124, 13), (54, 13),(124,), (54,))

建立模型

clf = tree.DecisionTreeClassifier(criterion="entropy")
clf = clf.fit(Xtrain, Ytrain)
score = clf.score(Xtest, Ytest) #返回预测准确度accuracy

score # 每次可能不一样,在后面会解释为什么不一样
---
0.8888888888888888

tree.score可以对测试集,同样的,也可以对训练集

score_train = clf.score(Xtrain, Ytrain) # 注意这里是在训练集上计算score
score_train
---
1.0

画出一棵树

import graphviz # 画树所需的包
feature_name=['酒精','苹果酸','灰','灰的碱性','镁','总酚','类黄酮','非黄烷类酚类','花青素','颜色强度','色调','od280/od315稀释葡萄酒','脯氨酸'] # 和上面的英文特征相对应
dot_data = tree.export_graphviz(clf
								,feature_names=feature_name
                                ,class_names=['琴酒','雪莉','贝尔摩德']
                                ,filled=True
                                ,rounded=True
)
graph = graphviz.Source(dot_data)
# feature_names:X特征名,即X不同维度的名称
# class_names:Y类名,即不同类别Y的名称
# filled:是否填充颜色,颜色深浅表示纯度,不同颜色表示不同类别
# rounded:使用圆框/方框显示

逗号写在参数前面,方便注释,尤其是最后一行注释掉,不需要增删逗号

graph
# entropy:纯度
# samples:样本个数
# value:样本比例

详解决策树-分类树【菜菜的sklearn课堂笔记】

很遗憾图片并不能显示中文

保存png格式的graphviz输出的dot文件

dot_data = tree.export_graphviz(clf
                                ,feature_names=feature_name
                                ,class_names=['琴酒','雪莉','贝尔摩德']
                                ,filled=True
                                ,rounded=True
                                ,out_file="./tree.dot"
)

注意这里要加,out_file="./tree.dot",然后在与笔记同一目录下会生成一个tree.dot文件 找目录的方法

import os
print(os.path.abspath('.'))

看返回结果 cmd到该目录下,执行

dot -Tpng tree.dot -o tree.png

继续探索决策树

clf.feature_importances_ # 特征重要性
---
array([0.02512577, 0.        , 0.        , 0.        , 0.035285  , 0.        , 0.        , 0.        , 0.        , 0.09686628, 0.13368571, 0.24884153, 0.46019571])

list(zip(feature_name,clf.feature_importances_))
---
[('酒精', 0.02512577188063646),
 ('苹果酸', 0.0),
 ('灰', 0.0),
 ('灰的碱性', 0.0),
 ('镁', 0.0),
 ('总酚', 0.0),
 ('类黄酮', 0.0),
 ('非黄烷类酚类', 0.0),
 ('花青素', 0.03528500112337327),
 ('颜色强度', 0.09686627661594578),
 ('色调', 0.13368570769252183),
 ('od280/od315稀释葡萄酒', 0.24884153360350494),
 ('脯氨酸', 0.46019570908401763)]

重要参数

criterion分枝指标

为了通过表格生成一棵决策树,我们需要找到最佳的节点和最佳的分枝方法,对分类树来说,衡量这个最佳的指标可以使用基尼系数,输入"gini",这也是criterion默认的值;使用信息熵,输入"entropy" 二者的数学表示 $$ \begin{aligned} Entropy(t)&=-\sum\limits_{i=0}^{c-1}p(i|t)\log_{2}p(i|t)\ Gini(t)&=1-\sum\limits_{i=0}^{c-1}p(i|t)^{2} \end{aligned} $$ $t$代表给定的节点,$i$代表标签的任一分类

这里我们尽量少提数学相关的内容,可以到统计学习方法中看决策树相关知识

基尼系数缺点:对不纯度相对不敏感。 信息熵缺点:涉及对数计算,速度相对慢一点;对不纯度更敏感,尤其对于高维数据或者噪声很多的数据,容易过拟合

通常使用基尼系数 数据维度很大,噪声很大的时候使用基尼系数 维度低,数据比较清晰的时候,信息熵和基尼系数没区别 当决策树拟合程度不够好的时候,使用信息熵

random_state随机生成参数

实际上,无论决策树模型如何进化,在分枝上的本质都是追求某个不纯度相关的指标的优化,也就是说,决策树在建树时,是考优化节点来追求一棵优化的树,但即使我们在每个节点都选择最优分类结点,我们并不能保证整体数最优。 集成算法被用来解决这个问题。sklearn表示,既然一棵树不能保证最优,那就建更多的不同的树,然后从中取最好的。怎样从一组数据集中建不同的树?在每次分枝时,不从使用全部特征,而是随机选取一部分特征,从中选取不纯度相关指标最优的作为分枝用的节点。这样,每次生成的树也就不同了。

clf = tree.DecisionTreeClassifier(criterion="entropy"
                                  ,random_state=0
                                 )
# random_state:用来设置分枝中的随机模式的参数,输入任意非负正整数,会一直长处一棵树,让模型稳定下来。默认None,在高维度时随机性会表现更明显,低维度的数据随机性几乎不会显现。
clf = clf.fit(Xtrain, Ytrain)
score = clf.score(Xtest, Ytest)

score # 依旧有可能不一样
---
0.9074074074074074

即使我们固定了random_state=0或者其他数字,由于训练集和数据集的划分是随机的。当我们重启jupyter的时候,数据集将不同。因此,即使固定random_state,树仍有可能是不同的

splitter随机生成参数

clf = tree.DecisionTreeClassifier(criterion="entropy"
                                  ,random_state=0
                                  ,splitter='random'
                                 )
# splitter:控制决策树分枝的方式
# 默认"best",即选择更重要的特征进行分枝(上面有提到"在每次分枝时,不从使用全部特征,而是随机选取一部分特征",因此这里只是对这一部分随机选取的特征选择feature_importances_最大的进行分枝)
# 输入"random",在分支的时候会更加随机,注意这里的随机可以认为是最佳随机,即给与一定的随机性,但仍然受到feature_importances_影响,不是随机选一个。
clf = clf.fit(Xtrain, Ytrain)
score = clf.score(Xtest, Ytest)

score
---
0.9259259259259259

当我们预测到模型有可能过拟合,可以考虑调节splitter来降低过拟合的可能性。 实际上,当书建成以后,我们也可以通过剪枝来防止过拟合

max_depth剪枝参数

限制树的最大深度,超过设定深度的树枝会被全部剪掉 对于一棵决策树,每多生长一层,对样本的需求量就会增加一倍,所以限制树的深度能够有效的限制过拟合 默认五None。如果为None,决策树会持续生长直到虽有叶节点不纯度为0,或者直到每个叶节点所含的样本量都小于参数min_samples_split中输入的数字

实际使用时,建议从=3开始尝试,看看拟合的效果决定是否增加设定深度

生成决策树后我们需要用score评判决策树的好坏,如果score没有变化或者变化很小,说明剪枝后效果也很好,且计算更少(奥卡姆剃刀原理),所以选择剪枝

min_sample_leaf & min_samples_split剪枝参数

min_samples_leaf限定,一个节点在分枝后的每个子节点都必须包含至少min_samples_leaf个训练样本,否则分枝就不会发生,或者,分枝会朝着满足每个子节点都包含min_samples_leaf个样本的方向去发生 如果输入整数,则认为输入的数字是叶节点存在所需的最小样本量;如果输入浮点数,则认为输入的浮点数是比例,即叶节点存在所需的最小样本量为输入模型的样本量$\times$浮点数

min_samples_split就是对于中间结点而言的,同min_sample_leaf

这里我们可以看到,如果我们限定的min_sample_leaf不合适,决策树可能*生成与数据集少部分数据相违背的决策树分枝,即

score_train = clf.score(Xtrain, Ytrain) 
score_train

结果不为1.0

这个参数设置的太小会引起过拟合,设置的太大会阻止模型学习数据,一般来说,建议从=5开始使用

max_features剪枝参数

分枝时考虑的最大特征个数,超过限制个数的特征会被舍弃 不建议使用该参数,因为其舍弃特征并不考虑特征的重要性。如果数据维度过多可以考虑PCA等方式降维来防止过拟合 默认None

允许参数详情查询api

min_impurity_decrease剪枝参数

限制不纯度降低的最小大小,不纯度下降小于设定数值,分枝不会发生

在前面说到,不纯度可以是基尼指数,也可以是信息增益

带权重的不纯度下降可以表示为 $$ \frac{N_{t}}{N}\times 所在中间结点不纯度- \frac{N_{tR}}{N}\times 右侧子树不纯度- \frac{N_{tl}}{N}\times 左侧子树不纯度 $$

默认=0,接收浮点数

信息增益小也就意味着,该分枝对于区分该中间节点所对应的数据集作用并不大

class_weight & min_weight_fraction_leaf目标权重参数

常用于样本不平衡的数据集中。 样本不平衡是指在一组数据集中,标签的一类天生占有很大的比例。比如说,在银行要判断“一个办了信用卡的人是否会违约”,就是是vs否(1%:99%)的比例。这种分类状况下,即便模型什么也不做,全把结果预测成“否”,正确率也能有99%。

建议查询官方API,暂时用不到,用到回来补(其实现在我还不会用,也说不明白)

确认最优剪枝参数

一般我们可以结合matplotlib来绘制超参数学习曲线来进行判断 超参数学习曲线,是一条以超参数的取值为横坐标,模型的度量指标为纵坐标的曲线,它是用来衡量不同超参数取值下模型的表现的线 在我们建好的决策树里,我们的模型度量指标就是score

import matplotlib.pyplot as plt

test = []
for i in range(10):
    clf = tree.DecisionTreeClassifier(max_depth=i+1
                                     ,criterion="entropy"
                                     ,random_state=0
                                     ,splitter="best"
                                     )
    clf = clf.fit(Xtrain,Ytrain)
    score = clf.score(Xtest,Ytest)
    test.append(score)

plt.plot(range(1,11),test,color="red",label="max_depth")
plt.legend()
plt.show()

详解决策树-分类树【菜菜的sklearn课堂笔记】

重要接口和属性

之前提到过

  • feature_importances_,查看各个特征推模型的重要性
  • fit用于生成决策树
  • score用于评估决策树准确性
clf.apply(Xtest)# 返回每个测试样本所在叶结点的索引
---
array([10,  7,  7, 10,  4, 10, 10,  7,  4, 10,  5,  5, 10,  7,  7,  4, 10, 5,  7,  5,  7,  7,  9,  5,  7,  9, 10, 10,  4, 10, 10,  4,  4,  7, 7,  4, 10,  4,  5,  4, 10,  7, 10,  7, 10, 10,  7,  7,  4, 10,  5, 10,  7,  5], dtype=int64)

clf.predict(Xtest)# 返回每个测试样本的分类/回归结果
---
array([0, 1, 1, 0, 2, 0, 0, 1, 2, 0, 1, 1, 0, 1, 1, 2, 0, 1, 1, 1, 1, 1, 1, 1, 1, 1, 0, 0, 2, 0, 0, 2, 2, 1, 1, 2, 0, 2, 1, 2, 0, 1, 0, 1, 0, 0, 1, 1, 2, 0, 1, 0, 1, 1])