决策树是机器学习中最常用的算法之一,主要用于分类,也用于回归问题。每当我们在做出决定之前问自己一个问题时,我们的大脑就像决策树一样工作。例如:外面是阴天吗?如果是的话,我会带一把雨伞。
当训练数据集对变量进行分类时,决策树的思想是根据某个特征值将数据划分为更小的数据集,直到目标变量都属于一个类别。当人类的大脑决定根据经验(比如多云的天空)来选择“分裂特征”时,计算机根据最大的信息增益来分割数据集。让我们定义一个简单的问题,并进入一些计算,看看这到底意味着什么!
假设我们想建立一个决策树来根据体重和身高来决定宠物是猫还是狗。我们可以根据这两个特征之一的某些值来划分这些数据点,例如:对于超过15磅的体重,我确信宠物是一只狗,至少根据这个非常简单的数据集。但是如果权重小于这个值,我的子集将包含两只猫和一只狗,所以我需要再次分割子集,直到只剩下一个类。换句话说,直到所有的子集都是pure。实际上,我为这些数据值绘制了一个决策树,如下所示:
每棵树都以一个根节点开始,即第一个分割。在不考虑太多的情况下,我们可以轻松地提出不同类型的根节点,以便在相同数量的步骤中将数据分成一个解决方案。但是计算机如何决定如何定义节点?当然,它会寻找最有效的方式,为了理解这是什么,我们需要引入Gini:“最常用的不等式测量”。该不等式是指节点后的每个子集中的目标类。因此,可以在每次拆分后计算,并且根据节点之后不等式的变化,我们也可以定义“信息增益”。
为了计算Gini,我们考虑在节点之后找到每个类的概率,我们将这些值的平方加起来,然后从1中减去这个值。因此,当一个子集是pure(即它中只有一个类) ,基尼将为0,因为找到该类的概率确实是1!在那种情况下,我们说我们已经达到了叶子,因为在我们实现目标时不再需要分裂。但是如果我们看一下上面的图片,在False情况下的根节点之后,我们有一个包含3个观察的子集,其中2个是猫,1个是狗。如果我们想要计算该子集的基尼,我们有:
可用于计算类不等式的另一个度量标准是Entopy,而不是Gini。它们具有相同的目的,但熵的变化幅度略有不同; 但是,为此我们只会使用基尼。
根据我们选择的拆分策略,我们将为每个子集提供不同的Gini值,并且根据节点后的Gini值,我们可以定义信息增益:
这被定义为父母的基尼系数和孩子的基尼系数的加权平均值之间的差值。如果我们参考上面的例子,知道初始数据集的基尼系数仅应用定义就等于0.48,我们可以计算根节点后的信息增益(以15磅的权值分割):
然后,决策树将考虑所有可能的分裂并选择具有最高信息增益的分裂。事实上,让我们做一些编码,看看根据Python的决策树是什么!
通过运行下面的代码,我们从头开始构建数据框,并仅在几行中拟合模型。
注意:在训练模型之前训练/测试分裂是很好的做法,以防止过度拟合并且还要仔细检查这种模型对看不见的数据的执行情况。在这种情况下,我跳过了这一步,因为数据帧只包含少量观察。
Python代码如下:
from sklearn.tree import DecisionTreeClassifier import pandas as pd # create a matrix including the data data = [[8,8,'dog'],[50,40,'dog'],[8,9,'cat'],[15,12,'dog'],[9,9.8,'cat']] # generating a dataframe from the matrix df = pd.DataFrame(data, columns = ['weight','height','label']) # defining predictors X = df[['weight','height']] # definig the target variable and mapping it to 1 for dog and 0 for cat y = df['label'].replace({'dog':1, 'cat':0}) # instantiating the model tree = DecisionTreeClassifier() # fitting the model model = tree.fit(X,y)
重要的是要指出,在实例化时DecisionTreeClassifier,我没有在括号中指定任何参数。在处理非常大的数据集时,为了防止Tree失去控制和过度拟合,查看max_depth指定树的最大分割级别数非常有用。此外,设置max_features一个参数可以限制在搜索最佳拆分时要查看的预测变量的数量。此外,如果您希望树基于Entropy而不是Gini进行优化,则只需在实例化对象时进行编写criterion = 'entropy'。
我们建造了我们的模型 但那究竟是什么意思呢?决策树的美妙之处在于它易于解释,所以让我们绘制它!要运行下面的Python代码段,您可能需要先!pip install pydotplus pydot2在notebook中运行 。
from sklearn.externals.six import StringIO from sklearn.tree import export_graphviz import pydotplus from IPython.display import Image dot_data = StringIO() export_graphviz( model, out_file = dot_data, filled=True, rounded=True, proportion=False, special_characters=True, feature_names=X.columns, class_names=["cat", "dog"] ) graph = pydotplus.graph_from_dot_data(dot_data.getvalue()) Image(graph.create_png())
这段Python代码的输出将是下图
在上面的“手工”决策树中,我选择了15磅的权重作为根节点,而算法决定在相同的变量上进行拆分,但值为12。这就创建了一个只有狗的叶子(对于重量大于12的,我们实际上有,gini = 0),根节点后的真实条件生成的子集在高度变量上进一步分裂为8.5。最后一次拆分生成了两个带有空gini值的纯子集。
以下是优缺点的简要列表:
优点
- 这是可以解释的,特别是如果我们需要将我们的发现传达给非技术观众
- 它可以很好地处理嘈杂或不完整的数据
- 它可以用于回归和分类问题
缺点
- 它可能不稳定,这意味着数据的微小变化可能会导致模型发生重大变化
- 它倾向于过度拟合,这意味着低偏差但是方差很大:即使在训练数据得分很高的情况下,对看不见的数据也可能表现不佳
幸运的是,有许多技术可以处理过度拟合。通过使用bagging或boosting方法,我们可以从决策树的概念开始,通过使用随机森林分类器或AdaBoostClassifier等模型来开发更精确的分析。这两种都是ensamble方法,但是Random Forest通过boostrapping生成许多“新数据集”(即用替换对原始dataframe进行采样);它适用于每一个新的dataframe的树,并通过平均森林中的所有树来预测。Ada Boost是一种自适应树,它通过调整不正确的分类实例,同时始终使用相同的dataframe,从而从自身学习。