天天看点

python-机器学习打卡(四)--监督学习(三)--决策树、决策树集成

目录

    • 决策树
      • 控制决策树复杂度
      • 分析决策树
      • 特征重要性
      • 决策树优缺点
    • 决策树集成
      • 随机森林
        • 构造随机森林
        • 分析数据
        • 优缺点
      • 梯度提升回归树(梯度提升机)
        • 优缺点

决策树

决策树就是一棵树,一颗决策树包含一个根节点、若干个内部结点和若干个叶结点;叶结点对应于决策结果,其他每个结点则对应于一个属性测试;每个结点包含的样本集合根据属性测试的结果被划分到子结点中;根结点包含样本全集,从根结点到每个叶子结点的路径对应了一个判定测试序列。

本质上他是从一层一层的if/else问题中进行学习并得出结论。

如下图所示,如果要区分四种动物鹰(hawk),企鹅(penguin),海豚(dolphin),熊(bear)。我们想通过尽可能少的判断得到正确的结果。首先可以通过是否有羽毛(feather),将动物分为两类(鹰和企鹅,海豚和熊),对于第一组我们可以在通过能否飞(fly)来区分企鹅和鹰;然后判断是否有鳍(fins)区分熊和海。

下图可用代码展示:

import mglearn
import matplotlib.pyplot as plt
cancer = load_breast_cancer()
mglearn.plots.plot_animal_tree()
plt.show()
           
python-机器学习打卡(四)--监督学习(三)--决策树、决策树集成

此处代码需要安装graphviz:

  1. graphviz,安装完成后将其配置到系统环境变量
此电脑-》属性-》高级系统设置-》环境变量-》path
  添加   (安装路径)\Graphviz\bin
           

2.然后再到terminal中安装graphviz第三方库

pip install graphviz

如果执行第1步会报错:

graphviz.backend.ExecutableNotFound: failed to execute [‘dot’, ‘-Tpng’, ‘-O’, ‘test.gv’], make sure the Graphviz executables are on your systems’ PATH

控制决策树复杂度

通常来说,决策树会构造树知道所有节点为纯叶子节点,这会导致树非常的深,模型十分复杂,也是的模型对训练集拟合度高。纯叶子节点说明树的拟合程度为100%。

防止过拟合常见的两种策略:预剪枝和后剪枝

预剪枝:及早的停止树的构建,包括限制叶子结点个数,树的最大深度

后剪枝:先构造树,然后删除或者折叠信息量少的枝

sklearn的tree模块中分别实现了DecisionTreeRegressor和DecisionTreeClassifier。而且这两个类中只实现了预剪枝。

首先在乳腺癌数据集(cancer)上实现一次决策树分类,不进行预剪枝,直到所有节点都为叶子结点。

from sklearn.tree import DecisionTreeClassifier
from sklearn.datasets import load_breast_cancer
from sklearn.model_selection import train_test_split

cancer = load_breast_cancer()
data = cancer.data
target = cancer.target
xtrain, xtest, ytrain, ytest = train_test_split(data, target, stratify=target, random_state=42)

fit = DecisionTreeClassifier(random_state=0).fit(xtrain, ytrain)
print("training set score is {:.4f}".format(fit.score(xtrain, ytrain)))
print("test set score is {:.4}".format(fit.score(xtest, ytest)))
           

输出

training set score is 1.0000

test set score is 0.9371

可以看到训练集精度达到了100%,因为叶子结点是纯的,树的深度很大,而且模型过拟合,导致泛化能力不佳。可以通过预剪枝来降低树的深度:

fit2 = DecisionTreeClassifier(random_state=0, max_depth=4).fit(xtrain, ytrain)
print("training set score is {:.4f}".format(fit2.score(xtrain, ytrain)))
print("test set score is {:.4}".format(fit2.score(xtest, ytest)))
           

输出

training set score is 0.9883

test set score is 0.951

通过设置树最大深度后,虽然训练集精度下降了,但是模型泛化能力得到提升。

分析决策树

将上树深度为4的决策树可视化

from sklearn.tree import DecisionTreeClassifier
from sklearn.datasets import load_breast_cancer
from sklearn.model_selection import train_test_split
from sklearn.tree import export_graphviz
import graphviz

cancer = load_breast_cancer()
xtrain, xtest, ytrain, ytest = train_test_split(cancer.data, cancer.target, stratify=cancer.target, random_state=42)

fit2 = DecisionTreeClassifier(random_state=0, max_depth=4).fit(xtrain, ytrain)

export_graphviz(fit2, out_file="tree.dot", class_names=["m", "b"], feature_names=cancer.feature_names, impurity=False,
                filled=True)
                
f = open("tree.dot")
dor = f.read()
graph = graphviz.Source(dor)
graph.render("tree") #会生成一个pdf文件
           

预览

python-机器学习打卡(四)--监督学习(三)--决策树、决策树集成

观察发现worst perimeter <= 106.1的左节点中259个样例只有11个良性样本,这个节点之后的划分也是用了更细的区别将这11个样本分离。

特征重要性

查看整个树可能十分费劲,我们使用特征重要性对树的决策重要性进行排序,每个重要性是介于0到1的一个小数。

输出

Feature importance:

[0. 0. 0. 0. 0. 0.

0. 0. 0. 0. 0.01019737 0.04839825

0. 0. 0.0024156 0. 0. 0.

0. 0. 0.72682851 0.0458159 0. 0.

0.0141577 0. 0.018188 0.1221132 0.01188548 0. ]

可以自定义一个方法对数据进行可视化:

from sklearn.tree import DecisionTreeClassifier
from sklearn.datasets import load_breast_cancer
from sklearn.model_selection import train_test_split
import numpy as np
import matplotlib.pyplot as plt

cancer = load_breast_cancer()
xtrain, xtest, ytrain, ytest = train_test_split(cancer.data, cancer.target, stratify=cancer.target, random_state=42)
fit2 = DecisionTreeClassifier(random_state=0, max_depth=4).fit(xtrain, ytrain)

def plot_feature_importances_cancer(model):
    n_feature = cancer.data.shape[1]
    plt.barh(range(n_feature),model.feature_importances_,align='center')
    plt.yticks(np.arange(n_feature),cancer.feature_names)
    plt.xlabel("Feature importances")
    plt.ylabel("Feature name")
    plt.show()

plot_feature_importances_cancer(fit2)
           

预览

python-机器学习打卡(四)--监督学习(三)--决策树、决策树集成

决策树优缺点

决策树有两个优点:

  1. 得到的模型很容易可视化
  2. 算法完全不受数据缩放影响

缺点:即使做了预剪枝操作,也容易过拟合,泛化性能很差。

决策树集成

集成:合并多个机器学习模型来构建更加强大的方法。

有两种以决策树为基础的集成模型对大量分类和回归数据集都是有效的:随机森林(random forest,RF)和梯度提升决策树(gradient boosted decision tree)。

随机森林

思路:如果有很多决策树,预测结果很好,都以不同的方式过拟合,那么可以对这些树取平均来降低过拟合。

这意味着我们需要构建很多树,而且每颗树的应该与其他树不同。RF对树的随机化有两种:一种是选择用于构造树数据点,二是通过选择每次划分测试的特征。

构造随机森林

构造随机森林要确定构造树的个数:在RandomForestRegressor或RandomForestClassifier中的n_estimators参数设置

  • 对于构造一棵树,首先要进行自助采样

    从n_samples个样本中进行自助采样,就是对样本进行有放回的抽取,取到样本大小为n_samples。这样意味着同一个数据可能出现多次。

  • 然后基于自主采样得到的新数据集构造决策树。这一步会使用算法选择数据点的特征的随机子集进行测试,而不是将所有的特征都进行测试。选择子集的特征个数由参数max_features来控制
  • 通过自助采样和随机特征子集,保证构造的每棵树都不同。

对于回归问题,最后的结果是随机森林中所有树的结果平均值。而分类问题采用软投票,即将每棵树输出的不同类别概率进行平均,取平均概率最大的类别作为结果。

分析数据

我们使用two_moons数据集,并构造五棵树,我们将每棵树的决策边界可视化,也将RF的结果可视化:

from sklearn.ensemble import RandomForestClassifier
from sklearn.datasets import make_moons
from sklearn.model_selection import train_test_split
import mglearn
import matplotlib.pyplot as plt

x, y = make_moons(n_samples=200, noise=0.25, random_state=3)

xtrain, xtest, ytrain, ytest = train_test_split(x, y, stratify=y, random_state=2)
forest = RandomForestClassifier(n_estimators=5, random_state=2)
forest.fit(xtrain, ytrain)
fig, axs = plt.subplots(2, 3, figsize=(20, 10))

for i, (tree, ax) in enumerate(zip(forest.estimators_, axs.ravel())):
    ax.set_title("Tree {}".format(i))
    mglearn.plots.plot_tree_partition(xtrain, ytrain, tree=tree, ax=ax)

mglearn.plots.plot_2d_separator(forest, xtrain, fill=True, ax=axs[-1, -1], alpha=0.4)
axs[-1, -1].set_title("random tree")
mglearn.discrete_scatter(xtrain[:, 0], xtrain[:, 1], ytrain)

plt.show()
           

预览

python-机器学习打卡(四)--监督学习(三)--决策树、决策树集成

可以看到每棵树都犯了一点错误,决策边界也不相同,原因在于训练集中因为自助采样缺失了一些数据点。

随机森林比单独每一棵树过拟合都要小,给出的边界也更符合很自觉,在任何实际应用中,我们会用到更多的树,从而得到更平滑的边界。

再举一个例子,使用包含100棵树的随机森林在乳腺癌数据集上:

from sklearn.ensemble import RandomForestClassifier
from sklearn.model_selection import train_test_split
from sklearn.datasets import load_breast_cancer
import numpy as  np

cancer = load_breast_cancer()
Xtrain, Xtest, ytrain, ytest = train_test_split(cancer.data, cancer.target, stratify=cancer.target, random_state=2)
rf = RandomForestClassifier(n_estimators=100,random_state=0).fit(Xtrain,ytrain)

def plot_feature_importances_cancer(model):
    n_feature = cancer.data.shape[1]
    plt.barh(range(n_feature), model.feature_importances_, align='center')
    plt.yticks(np.arange(n_feature), cancer.feature_names)
    plt.xlabel("Feature importances")
    plt.ylabel("Feature name")
    plt.show()

plot_feature_importances_cancer(rf)
           

输出

Training set score is 1.0

Test set score is 0.965

我们可以看到随机森林精度为96.5%,比线性模型或者单棵决策树结果都要好,我们也可以调节max_features参数或者对森林中的树进行预剪枝,但通常默认参数得到的结果已经很好的。我们也可以使用上面代码进行可视化:

python-机器学习打卡(四)--监督学习(三)--决策树、决策树集成

比起单颗树,随机森林中有跟多特征重要性不为0。对于单颗决策树特征重要性图中可以看到"worst radius" 被给与了最大的重要性,而在随机森林中,也同样很重要。

优缺点

  • 用于回归和分类的随机森林是目前应用最广的机器学习方法之一。
  • 随机森林有决策树所有的优点,同时弥补了一些缺陷,如果要用可视化的方式向专家展示决策过程,单棵决策树是最好的办法。
  • 如果特征维度高且稀疏,随机森林表现往往不是很好。
  • 随机森林需要占用内存较多,训练时间也长。
  • Max_features和n_estimators是RF的主要参数,当然也有树的深度(max_depth)。max_features总是越大越好,但是随着max_features增大,模型运行占用内存就越大,训练时间也越久。

梯度提升回归树(梯度提升机)

梯度提升机通过合并多个决策树来构建一个更为强大的模型,虽然名字中有回归,但是该模型既可以用于回归,也可以用于分类。

与RF不同,该模型采用连续的方式构造树,每棵树会纠正前一颗树的错误。默认梯度提升回归树中没有随机化,而是用到了强预剪枝。树的深度一般很小,这样占用内存小,预测速度也更快。

梯度提升回归树的参数除了预剪枝与集成树的数量外,还有一个很重要参数是learning_rate(学习率),用于控制每颗树纠正前一颗树的错误的强度。较高的学习率,意味着后一棵树要进行较强的修改,会增加模型的复杂的度。增大树的数量,树的深度同样也会导致一个模型复杂度的增加。

我们在乳腺癌数据集上应用GradienBoostingClassifier模型,设置默认值(100颗树,最大深度为3,学习率为0.1),最大深度为1,学习率为0.01三个模型对比:

from sklearn.ensemble import GradientBoostingClassifier
from sklearn.model_selection import train_test_split
from sklearn.datasets import load_breast_cancer

cancer = load_breast_cancer()

xtrain, xtest, ytrain, ytest = train_test_split(cancer.data, cancer.target, random_state=0)

classifier = GradientBoostingClassifier(random_state=0)
classifier.fit(xtrain, ytrain)
print("Training set with default setting score is {:.3}".format(classifier.score(xtrain, ytrain)))
print("Test set with default setting score is {:.3}".format(classifier.score(xtest, ytest)))

classifier2 = GradientBoostingClassifier(random_state=0 ,max_depth=1)
classifier2.fit(xtrain, ytrain)
print("Training set with max_depth 1  score is {:.3}".format(classifier2.score(xtrain, ytrain)))
print("Test set with max_depth 1  score is {:.3}".format(classifier2.score(xtest, ytest)))

classifier3 = GradientBoostingClassifier( random_state=0, learning_rate=0.01)
classifier3.fit(xtrain, ytrain)
print("Training set with learning rate 0.01 score is {:.3}".format(classifier3.score(xtrain, ytrain)))
print("Test set with learning rate 0.01 score is {:.3}".format(classifier3.score(xtest, ytest)))

           

输出

Training set with default setting score is 1.0

Test set with default setting score is 0.965

Training set with max_depth 1 score is 0.991

Test set with max_depth 1 score is 0.972

Training set with learning rate 0.01 score is 0.988

Test set with learning rate 0.01 score is 0.965

在模型使用默认参数时,训练集精度为100%,而测试集精度为96.5%,模型存在过拟合。为了降低过拟合,更改最大深度参数为1,模型泛化能力得到提高,降低学习率后,模型过拟合也降低了。

优缺点

  • 算法不需要对数据缩放就可以表现的很好,适用于二元特征和联系特征同时存在的数据集。
  • 梯度提升回归树主要缺点是需要仔细调参,而且训练时间长。
  • 参数包括n_estimators和learning_rate,前者控制树的个数,后者控制树对前一颗树的纠正强度,两个参数增大都会增加模型的复杂度。