ML01决策树


title: 决策树—机器学习经典算法
date: 2021-04-10
tags:

  • ML
  • 基础
    categories:
  • dataAnalysis
    typora-copy-images-to: ./img
    typora-root-url: ./img

决策树算法

决策树的工作方式

决策树是一种非参数的有监督学习方法。它能够从一系列有特征和标签的数据中总结出决策规则,并用树状图的结构来呈现这些规则,以解决分类和回归问题。决策树算法的核心是要解决两个问题。1)如何从数据表中找出最佳节点和最佳分枝。2)如何让决策树停止生长,防止过拟合。

决策树的原理

​ 简单地说,决策树算法相当于一个多级嵌套的选择结构,通过回答一系列问题来不停的选择树上的路径,最终达到一个表示某个结论或者类别的叶子结点,决策树属于有监督学习算法,需要根据已知样本数据以及目标来训练得到一个可以工作的模型,然后再使用该模型对未知样本进行分类。

​ 我们的目标是能够在合理的时间内构造出具有一定准确率的次最优决策树。这些算法基本都执行”贪心策略“,即通过局部的最优来达到我们相信是最接近全局最优的结果。所有的树模型都是这样的算法

机器学习的关键概念

过拟合欠拟合
模型在训练集上表现很好,在测试集上表现糟糕,学习能力很强,但是学的太过精细模型在训练集和测试集上都表现的比较糟糕,学习能力不足

机器学习的建模流程

  1. 实例化,建立评估模型对象。
  2. 通过模型接口训练模型
  3. 通过模型接口提取需要的信息
from sklearn import tree    #导入需要的模块

clf=tree.DesicionTreeClassifier()   #实例化
clf=clf.fit(X_train,y_train)   #用训练集数据训练模型
result=clf.score(X_test,y_test) #导入测试集,从接口中调用需要的信息

分类树

#分类树的参数如下
class sklearn.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

1. 重要参数

1. criterion

为了把表格转换为一棵树,决策树需要找出最佳节点和最佳分支方法。对分类树来说,衡量这个“最佳”的指标叫做“不纯度”。通常来说,不纯度越低,决策树对训练集的拟合越好。现在使用的决策树算法在分枝方法上的核心大多是围绕在对某个不纯度相关指标的最优化上。不纯度基于节点来计算,树中的每个节点都会有一个不纯度,并且子节点的不纯度一定是低于父节点的,也就是说,在同一棵决策树上,叶子节点的不纯度一定是最低的。

Criterion这个参数正是用来决定不纯度的计算方法的。sklearn提供了两种选择:

  1. entropy,使用不纯度。表示的是数据中包含数据量的大小或者数据的混乱程度,entropy越小,表示数据的纯度越高。
  2. Gini 基尼系数越大,表示数据纯度越低。gini表示的是,在样本空间中随机选取两个样本时,这两个样本属于不同类别的概率越大。

比起基尼系数,信息熵对不纯度更加敏感,对不纯度的惩罚最强。但是**在实际使用中,信息熵和基尼系数的效果基本相同。**信息熵的计算比基尼系数缓慢一些,因为基尼系数的计算不涉及对数。另外,因为信息熵对不纯度更加敏感,所以信息熵作为指标时,决策树的生长会更加“精细”,因此对于高维数据或者噪音很多的数据,信息熵很容易过拟合,基尼系数在这种情况下效果往往比较好。当模型拟合程度不足的时候,即当模型在训练集和测试集上都表现不太好的时候,使用信息熵。

所以决策树的基本流程可以简单概括如下:

  1. 计算全部特征的不纯度指标
  2. 选取不纯度指标最优的特征来分支(纯度最高)
  3. 在第一个特征的分支下,计算全部特征的不纯度指标
  4. 选取不纯度指标最优的特征来继续分支(纯度最高)
  5. 直到没有更多特征可用,或整体的不纯度指标已经最优,决策树就会停止生长。
  • 建立一棵树

    1. 导入需要的算法库和模块

      from sklearn import tree
      from sklearn.datasets import load_wine
      from sklearn.model_selection import train_test_split
      
    2. 以机器学习的红酒数据集为例

      wine = load_wine()
      
    3. 分训练集和测试集

      # 30%表示测试集。70表示训练集
      Xtrain, Xtest, Ytrain, Ytest = train_test_split(wine.data, wine.target, test_size=0.3)
      
    4. 建立模型

      clf = tree.DecisionTreeClassifier(criterion='entropy')
      clf = clf.fit(Xtrain, Ytrain)
      score = clf.score(Xtest, Ytest)  # 返回预测的准确度accuracy
      print(score)
      
      
    5. 画树

      feature_name = ['酒精', '苹果酸', '灰', '灰的碱性', '镁', '总酚', '类黄酮', '非黄烷类酚类',
                      '花青素', '颜色强度', '色调', 'od280/od315稀释葡萄酒', '脯氨酸']
      import graphviz
      dot_data = tree.export_graphviz(
          clf
          , feature_names=feature_name
          , class_names=['琴酒','雪梨', '贝尔摩德']
          , filled=True
          , rounded=True
      )
      graph = graphviz.Source(dot_data)
      

    总结

      我们之前提到过,无论决策树模型如何进化,在分枝上的本质都还是追求某个不纯度相关的指标的优化,而正如我们提到的,不纯度是基于节点来计算的,也就是说,决策树在建树时,是靠优化节点来追求一棵优化的树,但最优的节点能够保证最优的树吗?集成算法被用来解决这个问题:sklearn表示,既然一棵树不能保证最优,那就建更多的不同的树,然后从中取最好的。怎样从一组数据集中建不同的树?在每次分枝时,不从使用全部特征,而是随机选取一部分特征,从中选取不纯度相关指标最优的作为分枝用的节点。
    
    clf = tree.DecisionTreeClassifier(criterion="entropy",random_state=30)
    clf = clf.fit(Xtrain, Ytrain)
    score = clf.score(Xtest, Ytest) #返回预测的准确度
    
    2. Random_state&slpitter

    ​ random_state用来设置分枝中的随机模式的参数,默认None,在高维度时随机性会表现更明显,低维度的数据(比如鸢尾花数据集),随机性几乎不会显现。输入任意整数,会一直长出同一棵树,让模型稳定下来,random_state的值没有任何意义。 splitter也是用来控制决策树中的随机选项的,有两种输入值,输入”best",决策树在分枝时虽然随机,但是还是会优先选择更重要的特征进行分枝(重要性可以通过属性feature_importances_查看),输入“random",决策树在分枝时会更加随机,树会因为含有更多的不必要信息而更深更大,并因这些不必要信息而降低对训练集的拟合。这也是防止过拟合的一种方式。当你预测到你的模型会过拟合,用这两个参数来帮助你降低树建成之后过拟合的可能性。如果树已经建成,我们依然使用剪枝参数来防止过拟合。

    clf = tree.DecisionTreeClassifier(criterion='entropy',random_state=30,splitter="random")
    
    3. 剪枝参数

    为了让决策树有更好的泛化性,我们要对决策树进行剪枝。剪枝策略对决策树的影响巨大,正确的剪枝策略是优化决策树算法的核心sklearn为我们提供了不同的剪枝策略

    3.1 max_depth

    限制树的最大深度,超过设定深度的树枝全部剪掉这是用得最广泛的剪枝参数,在高维度低样本量时非常有效。决策树多生长一层,对样本量的需求会增加一倍,所以限制树深度能够有效地限制过拟合。在集成算法中也非常实用。实际使用时,建议从=3开始尝试,看看拟合的效果再决定是否增加设定深度。

    3.2 min_samples_leaf & min_samples_split

    ​ min_samples_leaf限定,一个节点在分枝后的每个子节点都必须包含至min_samples_leaf个训练样本,否则分枝就不会发生,或者,分枝会朝着满足每个子节点都包含min_samples_leaf个样本的方向去发生一般搭配max_depth使用,在回归树中有神奇的效果,可以让模型变得更加平滑。这个参数的数量设置得太小会引起过拟合,设置得太大就会阻止模型学习数据。一般来说,建议从=5开始使用。如果叶节点中含有的样本量变化很大,建议输入浮点数作为样本量的百分比来使用。同时,这个参数可以保证每个叶子的最小尺寸,可以在回归问题中避免低方差。

    ​ min_samples_split限定,一个节点要包含至少min_samples_slpit个训练样本,这个节点才允许被分支,否则分支不会发生。

    也就是说min_samples_leaf表示叶节点包含的训练样本,min_samples_split表示父节点包含的训练样本

    3.3 max_feature & min_impority_decrease

    ​ 一般max_depth使用,用作树的”精修“ max_features限制分枝时考虑的特征个数,超过限制个数的特征都会被舍弃。和max_depth异曲同工,max_features是用来限制高维度数据的过拟合的剪枝参数,但其方法比较暴力,是直接限制可以使用的特征数量而强行使决策树停下的参数,在不知道决策树中的各个特征的重要性的情况下,强行设定这个参数可能会导致模型学习不足。如果希望通过降维的方式防止过拟合,建议使用PCA,ICA或者特征选择模块中的降维算法。

    ​ min_impurity_decrease限制信息增益的大小,信息增益小于设定数值的分枝不会发生。这是在0.19版本中更新的功能,在0.19版本之前时使用min_impurity_split。

    4 目标权重参数class_weight & min_weight_fraction_leaf

    ​ 完成样本标签平衡的参数。样本不平衡是指在一组数据集中,标签的一类天生占有很大的比例。比如说,在银行要判断“一个办了信用卡的人是否会违约”,就是是vs否(1%:99%)的比例。这种分类状况下,即便模型什么也不做,全把结果预测成“否”,正确率也能有99%。因此我们要使用class_weight参数对样本标签进行一定的均衡,给少量的标签更多的权重,让模型更偏向少数类,向捕获少数类的方向建模。该参数默认None,此模式表示自动给与数据集中的所有标签相同的权重。有了权重之后,样本量就不再是单纯地记录数目,而是受输入的权重影响了,因此这时候剪枝,就需要搭配min_

      weight_fraction_leaf这个基于权重的剪枝参数来使用。另请注意,基于权重的剪枝参数(例如min_weight_fraction_leaf)将比不知道样本权重的标准(比如min_samples_leaf)更少偏向主导类。如果样本是加权的,则使用基于权重的预修剪标准来更容易优化树结构,这确保叶节点至少包含.
    

    2. 重要属性和接口

    1. 属性

    对决策树来说,最重要的是feature_importances。能够查看各个特征对模型的重要性。

    2. 接口

    机器学习中很多算法的接口都是相似的。常用的4个接口是

    1)fit

    2)score

    3)apply

    4)predict

    ​ 在这里不得不提的是,**所有接口中要求输入X_trainX_test的部分,输入的特征矩阵必须至少是一个二维矩阵。sklearn不接受任何一维矩阵作为特征矩阵被输入。**如果你的数据的确只有一个特征,那必须用reshape(-1,1)来给矩阵增维.

    ​ 如果数据只有一个特征和一个样本,使用reshape(1,-1)来给你的数据进行增维。

    # apply返回每个测试样本所在的叶子结点的索引
    clf.apply(Xtest)
    #predict返回每个测试样本的分类/回归结果
    clf.predict(Xtest)
    

    总结

    我们已经学了8个参数

    1. Criterion

    2. 两个随机性相关的参数(random_state和splitter)

    3. 五个剪枝参数(max_depth. Min_samples_split, min_samples_leaf, max_feature,min_impority_decrease)

回归树

class sklearn.tree.DecisionTreeRegressor (criterion=’mse’, 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, presort=False)
#几乎所有参数,属性及接口都和分类树一模一样。需要注意的是,在回归树种,没有标签分布是否均衡的问题,因此没有class_weight这样的参数。

1. 重要参数,属性和接口

回归树衡量分枝质量的指标,支持的标准有三种:

1)输入"mse"使用均方误差mean squared error(MSE),父节点和叶子节点之间的均方误差的差额将被用来作为

特征选择的标准,这种方法通过使用叶子节点的均值来最小化L2损失

2)输入“friedman_mse”使用费尔德曼均方误差,这种指标使用弗里德曼针对潜在分枝中的问题改进后的均方误差

3)输入"mae"使用绝对平均误差MAE(mean absolute error),这种指标使用叶节点的中值来最小化L1损失

属性中最重要的依然是feature_importances_,接口仍然是apply, fit,predict,score最核心。

在回归树中,MSE不只是我们的分枝质量衡量指标,也是我们最常用的衡量回归树回归质量的指标,当我们在使用交叉验证,或者其他方式获取回归树的结果时,我们往往选择均方误差作为我们的评估(在分类树中这个指标是score代表的预测准确率)。在回归中,我们追求的是,MSE越小越好。然而,回归树的接口score返回的是R平方,并不是MSE

**虽然均方误差永远为正,但是sklearn当中使用均方误差作为评判标准时,却是计算负均方误差(neg_mean_squared_error)。这是因为sklearn在计算模型评估指标的时候,会考虑指标本身的性质,均方误差本身是一种误差,所以被sklearn划分为模型的一种损失(loss),因此在sklearn当中,都以负数表示。真正的均方误差MSE的数值,其实就是neg_mean_squared_error去掉负号的数字。

2. 交叉验证

from sklearn.datasets import load_boston
from sklearn.model_selection import cross_val_score
from sklearn.tree import DecisionTreeRegressor

boston = load_boston() #连续型 波士顿房价

regressor = DecisionTreeRegressor(random_state=0)
cross_val_score(regressor, boston.data, boston.target, cv=10, 
                scoring = "neg_mean_squared_error") #交叉验证cross_val_score的用法

​ 交叉验证是用来观察模型的稳定性的一种方法,我们将数据划分为n份,依次使用其中一份作为测试集,其他n-1份作为训练集,多次计算模型的精确性来评估模型的平均准确程度。训练集和测试集的划分会干扰模型的结果,因此用交叉验证n次的结果求出的平均值,是对模型效果的一个更好的度量。

3. 一维回归的图像绘制

  • 导入需要的库

    import numpy as np
    from sklearn.tree import DesicionTreeRegressor
    import matplotlib.pyplot as plt
    
  • 创建一条的正弦曲线。

    # 创建一条含有噪声的正弦曲线
    rng = np.random.RandomState(1)
    # rng.rand(80, 1)表示生成80行一列。必须至少2维,所以我们直接生成2维
    # 5 * rng.rand(80, 1)把数据范围变为0-5之间
    # np.sort排序从小到大
    X = np.sort(5 * rng.rand(80, 1), axis=0)
    # .ravel()用于给y降为1维的
    y=np.sin(X).ravel()
    
  • 增加噪声

    #添加噪声,每五个数据就取一个数,然后随机加上-0.5到0.5之间的数,由于
    # -0.5到0.5太小,所以我们就再乘以3
    y[::5]+=3*(0.5-rng.rand(16))
    
  • 实例化训练模型

    #实例化训练模型
    regr_1 = DecisionTreeRegressor(max_depth=2)
    regr_2 = DecisionTreeRegressor(max_depth=5)
    regr_1.fit(X, y)
    regr_2.fit(X, y)
    
  • 测试集导入模型

    # [:, np.newaxis]增维 也可以reshape(1,-1)
    X_test = np.arange(0.0, 5.0, 0.01)[:, np.newaxis]
    y_1 = regr_1.predict(X_test)
    y_2 = regr_2.predict(X_test)
    
  • 绘图

    plt.figure()
    plt.scatter(X, y, s=20, edgecolor="black", c="darkorange")  # 绘制点
    plt.plot(X_test, y_1, color="cornflowerblue", label="max_depth=2", linewidth=2)  # 绘制线
    plt.plot(X_test, y_2, color="yellowgreen", label="max_depth=5", linewidth=2)  # 绘制线
    plt.xlabel('data')
    plt.ylabel('target')
    plt.title("Desicion Tree Regression")
    plt.legend()
    plt.show()
    

决策树的优缺点

优点

  1. 易于理解和解释,因为树木可以画出来被看见

  2. 需要很少的数据准备。其他很多算法通常都需要数据规范化,需要创建虚拟变量并删除空值等。但请注意,

sklearn中的决策树模块不支持对缺失值的处理。

  1. 使用树的成本(比如说,在预测数据的时候)是用于训练树的数据点的数量的对数,相比于其他算法,这是

一个很低的成本。

  1. 能够同时处理数字和分类数据,既可以做回归又可以做分类。其他技术通常专门用于分析仅具有一种变量类

型的数据集。

  1. 能够处理多输出问题,即含有多个标签的问题,注意与一个标签中含有多种标签分类的问题区别开

  2. 是一个白盒模型,结果很容易能够被解释。如果在模型中可以观察到给定的情况,则可以通过布尔逻辑轻松

解释条件。相反,在黑盒模型中(例如,在人工神经网络中),结果可能更难以解释。

  1. 可以使用统计测试验证模型,这让我们可以考虑模型的可靠性。

缺点

决策树的缺点

  1. 决策树学习者可能创建过于复杂的树,这些树不能很好地推广数据。这称为过度拟合。修剪,设置叶节点所需的最小样本数或设置树的最大深度等机制是避免此问题所必需的,而这些参数的整合和调整对初学者来说会比较晦涩

  2. 决策树可能不稳定,数据中微小的变化可能导致生成完全不同的树,这个问题需要通过集成算法来解决。

  3. 决策树的学习是基于贪婪算法,它靠优化局部最优(每个节点的最优)来试图达到整体的最优,但这种做法不能保证返回全局最优决策树。这个问题也可以由集成算法来解决,在随机森林中,特征和样本会在分枝过程中被随机采样。

  4. 有些概念很难学习,因为决策树不容易表达它们,例如XOR,奇偶校验或多路复用器问题。

  5. 如果标签中的某些类占主导地位,决策树学习者会创建偏向主导类的树。因此,建议在拟合决策树之前平衡

案例。泰坦预测幸存者

import pandas as pd
from sklearn.tree import DecisionTreeClassifier
import matplotlib.pyplot as plt
from sklearn.model_selection import GridSearchCV
from sklearn.model_selection import cross_val_score
from sklearn.model_selection import train_test_split
import numpy as np

data = pd.read_csv('data.csv')
# print(data)
# 采用切片的方式把标签切出来
# print(data.info())
# 由于分类器只能处理数字,所以性别姓名之类的需要转换为数字。
# print(data.head())

# 首先筛选特征
data.drop(['Cabin', 'Name', 'Ticket'], inplace=True, axis=1)
# print(data.head())
# 处理缺失值
# 年龄是相对重要的特征
# 将年龄的平均值填充缺失值
data["Age"] = data['Age'].fillna(data["Age"].mean())

data = data.dropna()

# print(data['Embarked'].unique().tolist())
labels = data['Embarked'].unique().tolist()
print(labels.index('S'))
# 将index作为属性,需要Embarked几个属性之间没有联系
data['Embarked'] = data['Embarked'].apply(lambda x: labels.index(x))
# print(data['Embarked'])
# 同样需要将性别改变为数字,把布尔值转换为int值
data['Sex'] = (data['Sex'] == 'male').astype('int')
# print(data['Sex'])
# print(data.head())

# 提取标签和特征矩阵,分为测试集和训练集
# 取出所有的行,然后列名不等于Survived
X = data.iloc[:, data.columns != 'Survived']
# 取出所有的行,然后列名等于Survived
y = data.iloc[:, data.columns == 'Survived']
# print(X.head())
# print(y.head())

# 分测试集,训练集等
Xtrain, Xtest, Ytrain, Ytest = train_test_split(X, y, test_size=0.3)
# print(Xtrain.info())
# print(Xtest.info())
# print(Xtrain.head())
# 目的在于把索引值重新改为从0开始
for i in [Xtrain, Xtest, Ytrain, Ytest]:
    # i.shape[0]表示数据有多少行
    i.index = range(i.shape[0])
# print(Xtrain.head())

clf = DecisionTreeClassifier(random_state=25)
clf = clf.fit(Xtrain, Ytrain)
score = clf.score(Xtest, Ytest)
# print(score)

# 观察不同深度决策树的表现
tr = []
te = []
for i in range(10):
    clf = DecisionTreeClassifier(random_state=25
                                 , max_depth=i + 1
                                 , criterion='entropy')
    clf = clf.fit(Xtrain, Ytrain)
    score_tr = clf.score(Xtrain, Ytrain)
    # 测试集使用交叉验证
    score_te = cross_val_score(clf, X, y, cv=10).mean()
    tr.append(score_tr)
    te.append(score_te)
# print(max(te))
# x轴,y轴。线的颜色,名字
plt.plot(range(1, 11), tr, color="red", label="train")
plt.plot(range(1, 11), te, color="blue", label="test")
plt.xticks(range(1, 11))
# plt.legend()
# plt.show()

# 网格搜索判断用哪个参数
gini_thresholds = np.linspace(0, 0.5, 20)
parameters = {'splitter': ('best', 'random')
    , 'criterion': ("gini", "entropy")
    , "max_depth": [*range(1, 10)]
    , 'min_samples_leaf': [*range(1, 50, 5)]  # 1到50之间,以5为步长
    , 'min_impurity_decrease': [*np.linspace(0, 0.5, 20)]  # 最小增益,0到0.5之间取20个有顺序的随机数
              }
clf = DecisionTreeClassifier(random_state=25)
GS = GridSearchCV(clf, parameters, cv=10)
GS.fit(Xtrain, Ytrain)
# GS.best_params_   #返回我们输入的参数和参数取值的列表中,返回最佳组合
# GS.best_score_    #返回网格搜索后的模型的评判标准
# print(GS)
print(GS.best_params_)
print(GS.best_score_)

参考

菜菜的sklearn课堂直播间: https://live.bilibili.com/12582510

python数据分析挖掘与可视化

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值