如今由于像Scikit-Learn这样的库的出现,我们可以很容易地在Python中实现数百种机器学习算法。它们是如此易用,以至于我们通常都不需要任何关于模型底层工作机制的知识就可以使用它们。虽然没必要了解所有细节,但了解某个机器学习模型大致是如何工作的仍然有帮助。这使得我们可以在模型表现不佳时进行诊断,或者解释模型是如何做决策的,这一点至关重要,尤其当我们想要说服别人相信我们的模型时。
在本文中,我们将介绍如何在Python中构建和使用随机森林(Random Forest)。除了查看代码之外,我们还将尝试了解此模型的工作原理。因为随机森林由许多决策树(decision tree)组成,所以我们先来了解一下单个决策树如何在一个简单的问题上进行分类。随后,我们将使用随机森林来解决一个现实世界中的数据科学问题。本文的完整代码在GitHub上以Jupyter Notebook的形式提供。
注意:本文最初出现在enlight上,这是一个社区驱动的开源平台,为那些希望学习机器学习的人提供教程。
理解决策树
决策树是随机森林的基本构成要素,而且是一种直观的模型。我们可以将决策树视为一系列关于数据的是/否问题,从而最终得出一个预测类别(或回归情况下的连续值)。 这是一个可解释的模型,因为它非常像我们人类进行分类的过程:在我们做出决定之前(在理想世界中),我们会对可用数据进行一系列的询问。
决策树的技术细节在于如何形成关于数据的问题。在CART算法中,通过确定问题(称为节点的分裂)来构建决策树,这些问题在得到应答时会导致基尼不纯度(Gini Impurity)的最大减少。这意味着决策树试图形成包含来自单个类的高比例样本(数据点)的节点,这个过程通过在能将数据干净地划分为不同类的特征中找到适当的值来实现。
我们稍后会谈谈基尼不纯度的更底层细节,但首先,让我们构建一个决策树,以便我们能够在高层次上理解它。
简单问题上的决策树
我们将从一个非常简单的二元分类问题开始,如下所示:
目标是把数据点划分到各自所属的类
我们的数据只有两个特征(预测变量),x1和x2,共有6个数据点(样本),被分为2个不同的标签。虽然这个问题很简单,但它不是线性可分的(linearly separable),这意味着我们不能绘制一条通过数据的直线来对点进行分类。
然而,我们可以绘制一系列直线,将数据点划分入多个框,我们称这些框为节点。 事实上,这就是决策树在训练期间所做的事情。实际上决策树是通过构造许多线性边界而构建的一个非线性模型。
我们使用Scikit-Learn来创建决策树并在数据上训练(拟合)。
在训练过程中,我们为模型提供特征和标签,以帮助它学习如何根据特征对点进行分类。(针对这个简单问题我们没有测试集,在测试时,我们只为模型提供特征值并让它对标签进行预测。)
我们可以在训练数据上测试模型的准确性:
可以看到它获得了我们所期望的100%的准确性,这是因为我们给了它训练的答案(y),并且没有限制树的深度。事实证明,在训练数据中过强的学习能力可能是一个缺点,因为它可能导致过拟合(overfitting),我们将在稍后对此进行讨论。
可视化决策树
当我们训练决策树时到底发生了什么?可视化可以帮助我们更好地理解决策树,这可以通过Scikit-Learn的一个功能来实现(详细信息,请查看notebook或这篇文章)。
简单的决策树
除叶子节点(彩色终端节点)外,所有节点都有5个部分:
● 基于某个特征的一个值对数据进行的提问,每个提问都有一个真或假的答案可以分裂节点。根据答案,数据点相应地向下移动。● gini: 节点的Gini不纯度。当我们沿着树向下移动时,平均加权基尼不纯度会减少。
● samples :节点中的观测数据数量。
● value: 每个类中的样本数。例如,根节点中有2个样本属于类0,有4个样本属于类1。
● class: 该节点中大多数点的分类。在叶节点中,即是对节点中所有样本的预测。
叶节点中不再提问,因为这里已经产生了最终的预测。要对某个新数据点进行分类,只需沿着树向下移动,使用新点的特征来回答问题,直到到达某个叶节点,该叶节点对应的分类就是最终的预测。
为了以不同的方式查看树,我们可以在原始数据上绘制由决策树构建的分割。
决策树构建的分割
每个分割都是一条线,它根据特征值将数据点划分到不同节点。对于这个简单的问题并且对最大深度没有做出限制,划分最终把每个点放置在仅包含同类点的一个节点中。(再次提醒,稍后我们将看到训练数据的这种完美划分可能并非我们想要的,因为它可能导致过拟合)
基尼不纯度(Gini Impurity)
是时候深入了解基尼不纯度的概念了(数学并不吓人!)节点的基尼不纯度是指,根据节点中样本的分布对样本分类时,从节点中随机选择的样本被分错的概率。例如,在根节点中,根据节点中的样本标签有44.4%的可能性错误地对某个随机选择的数据点进行分类。可以使用以下等式得出这个值:
节点n的基尼不纯度
节点n的基尼不纯度是1减去每个类(二元分类任务中是2)的样本比例的平方和。有点拗口,所以我们来一起计算出根节点的基尼不纯度。
根节点的基尼不纯度
在每个节点,决策树要在所有特征中搜索用于拆分的值,从而可以最大限度地减少基尼不纯度。(拆分节点的另一个替代方法是使用信息增益)。
然后,它以贪婪递归的过程重复这种拆分,直到达到最大深度,或者每个节点仅包含同类的样本。树每层的加权总基尼不纯度一定是减少的。在树的第二层,总加权基尼不纯度值为0.333:
(每个节点的基尼不纯度按照该节点中来自父节点的点的比例进行加权。)你可以继续为每个节点计算基尼不纯度(可视化图中有答案)。 就这样,从一些基本的数学中,诞生了一个强大的模型!
最终,最后一层的加权总基尼不纯度变为0,也意味着每个节点都是完全纯粹的,从节点中随机选择的点不会被错误分类。虽然这一切看起来挺好的,但这意味着模型可能过拟合,因为所有节点都是仅仅使用训练数据构建的。
过拟合:为什么森林比一棵树更好
你可能会想问为什么不能只用一个决策树呢?它似乎很完美,因为它没有犯任何错误!但别忘了这个关键点,即这棵树是在训练数据上没有犯错。我们早已预计会出现这种情况,因为我们给树提供了答案,而且没有限制最大深度(树的层数)。然而,机器学习模型的目标是可以对从未见过的新数据很好地泛化。
过拟合发生在当我们有一个非常灵活的模型(模型具有高能力)时,其本质上是通过紧密拟合来记住训练数据。这样的问题是模型不仅学到了训练数据中的实际关系,还学习了存在的噪声。灵活的模型具有高方差(variance),因为学到的参数(例如决策树的结构)将随着训练数据的不同而变化很大。
另一方面,因为对训练数据做出了假设,所以一个不灵活的模型具有较高的偏差(bias),(它偏向于对数据预先构思的想法)例如,线性分类器假设数据是线性的,不具备拟合非线性关系的灵活性。一个不灵活的模型甚至可能无法拟合训练数据,在高方差和高偏差这两种情况下,模型都无法很好地泛化到新数据之上。
一个能记住训练数据的非常灵活的模型与不能学习训练数据的不灵活模型之间的平衡称为偏差-方差权衡(bias-variance-tradeoff),它是机器学习中的一个基本概念。
当我们不限制最大深度时决策树容易过拟合的原因是它具有无限的灵活性,这意味着它可以持续生长,直到它为每个单独的观察点都生成一个叶节点,达到完美地分类。如果返回到之前决策树的图像并将最大深度限制为2(仅进行一次拆分),则分类不再100%正确。我们减少了决策树的方差,但代价是增加了偏差。
限制树的深度可以减少方差(好)并且增加偏差(坏),一种替代方案是,我们可以将许多决策树组合成一个称为随机森林的集成模型(ensemble model)。
随机森林
随机森林是由许多决策树组成的模型。这个模型不是简单地平均所有树(我们可以称之为“森林”)的预测,而是使用了两个关键概念,名字中的随机二字也是由此而来:
● 在构建树时对训练数据点进行随机抽样● 分割节点时考虑特征的随机子集
随机抽样训练观测数据
在训练时,随机森林中的每棵树都会从数据点的随机样本中学习。样本被有放回的抽样,称为自助抽样法(bootstrapping),这意味着一些样本将在一棵树中被多次使用。背后的想法是在不同样本上训练每棵树,尽管每棵树相对于特定训练数据集可能具有高方差,但总体而言,整个森林将具有较低的方差,同时不以增加偏差为代价。
在测试时,通过平均每个决策树的预测来进行预测。这种在不同的自助抽样数据子集上训练单个学习器,然后对预测进行平均的过程称为bagging,是bootstrap aggregating的缩写。
用于拆分节点的随机特征子集
随机森林中的另一个主要概念是,只考虑所有特征的一个子集来拆分每个决策树中的每个节点。通常将其设置为sqrt(n_features)以进行分类,这意味着如果有16个特征,则在每个树中的每个节点处,只考虑4个随机特征来拆分节点。(随机森林也可以在每个节点处考虑所有的特征,如回归中常见的那样。这些选项可以在Scikit-Learn Random Forest的实现中控制)。
如果你能理解一棵单独的决策树,bagging的理念,以及随机的特征子集,那么你对随机森林的工作方式也就有了很好的理解:
随机森林将成百上千棵决策树组合在一起,在略微不同的观察集上训练每个决策树,在每棵树中仅考虑有限数量的特征来拆分节点。随机森林的最终预测是通过平均每棵树的预测来得到的。
想理解为什么随机森林优于单一的决策树,请想象以下场景:你要判断特斯拉的股票是否上涨,现在你身边有十几位对该公司都没有先验知识的分析师。每个分析师都有较低的偏见,因为他们没有任何假设,并且可以从新闻报道的数据集中学习。
这似乎是一个理想的情况,但问题是报道中除了真实的信号外也可能包含噪音。 因为分析师们完全根据数据做出预测,即他们具有很高的灵活性,也就意味着他们可能会被无关的信息所左右。分析师们可能会从同一数据集中得出不同的预测。此外,如果提供不同的报道训练集,每个分析师都有高方差,并得出截然不同的预测。
解决方案是不依赖于任何一个人,而是汇集每个分析师的投票。此外,与随机森林一样,允许每个分析师仅使用一部分报道,并希望通过采样来消除噪声信息的影响。在现实生活中,我们也依赖于多种信息来源(从不信任亚马逊的单独评论),因此,不仅决策树的思想很直观,而且将它们组合在一起成为随机森林的想法同样如此。
实践中的随机森林
接下来,我们将在Python中用Scikit-Learn构建一个随机森林。我们不是学习一个简单的问题,而是会使用一个被分为训练集和测试集的真实数据,我们使用测试集来估计模型对新数据的性能,这也可以帮我们确定模型过拟合的程度。
数据集
我们要解决的问题是一个二元分类任务,目的是预测个人的健康状况。数据集的特征代表个人的社会经济和生活方式,标签为0表示健康状况不佳,1表示身体健康。该数据集由疾病控制和预防中心收集,可在此处获取。
数据样本
通常,一个数据科学项目80%的工作是在清洗,探索和提取数据中的特征。然而这篇文章我们的重点在于建模(有关其他步骤的详细信息,请参阅本文)。
这是一个不平衡的分类问题,因此准确率(accuracy)并不是一个合适的衡量指标。作为替代,我们将利用ROC和AUC,AUC是一个从0(最差)到1(最佳)的度量值,随机猜测得分为0.5。我们还可以绘制ROC曲线来评估模型。
这个notebook包含了决策树和随机森林的实现,但在这里我们只关注随机森林。 在读取数据后,我们就可以实例化并且训练一个随机森林,具体如下:
在几分钟的训练后,模型已准备好对测试数据进行预测了,如下:
我们预测分类(predict)以及预测概率(predict_proba)来计算ROC AUC。一旦我们有了对测试集的预测结果,我们就可以计算出ROC AUC。
结果
随机森林的最终测试集ROC AUC为0.87,而具有无限最大深度的单一决策树的最终测试集ROC AUC为0.67。如果查看训练分数,则两个模型都达到了1.0的 ROC AUC,这也是可以预料到的,因为我们给这些模型提供了训练答案,并且没有限制每棵树的最大深度。
虽然随机森林过拟合了(在训练数据上比在测试数据上做得更好),但在测试数据上它比单一决策树泛化地更好。随机森林具有较低的方差(好处),同时能保持与一棵决策树相同的低偏差(也是好处)。
我们还可以绘制单个决策树(顶部)和随机森林(底部)的ROC曲线。靠近左上角的曲线代表着更好的模型:
随机森林明显优于单一决策树。
另一个我们可以采用的模型诊断措施是绘制测试集预测结果的混淆矩阵(详细信息,请参阅notebook):
在左上角和右下角它显示了模型的正确预测,在左下角和右上角显示了模型误判的预测。我们可以使用这类图来诊断我们的模型,来决定它是否表现的足够良好并可以投入生产。
特征重要性(Feature Importances)
随机森林中的特征重要性表示在该特征上拆分的所有节点的基尼不纯度减少的总和。我们可以使用它来尝试找出随机森林认为最重要的预测变量。可以从一个训练好的随机森林中提取特征重要性,并将其放入Pandas的DataFrame中,如下所示:
通过告诉我们哪些变量在类之间最具辨别力,特征重要性可以让我们更好地洞察问题。例如,DIFFWALK是表明患者是否行走困难的的重要的特征,这在问题的上下文中也说得通。
通过从最重要的特征中构建额外的特征,特征重要性可以被用于特征工程(feature engineering)。我们还可以通过删除不重要的特征,来把特征重要性用于特征选择。
可视化森林中的树
最后,我们可以可视化在森林中的单个决策树。这次我们必须限制树的深度,否则它将太大而无法被转换为一幅图像。为了制作下图,我将最大深度限制为6。但这仍然产生了一棵我们无法完全解析的大树!不过由于我们深入地研究过决策树,我们还是可以通过这幅图掌握这个模型的工作原理。
随机森林中的单棵决策树
下一步
下一步是使用Scikit-Learn中的RandomizedSearchCV通过随机搜索来优化随机森林。优化是指在给定数据集上找到模型的最佳超参(hyperparameters)。最佳超参将随着数据集的不同而变化,因此我们必须在每个数据集上单独执行优化这也称为模型调整(model tuning)。
我喜欢将模型调整视为给一个机器学习算法寻找最佳设置。我们可以在随机森林中优化的东西包括决策树的数量,每个决策树的最大深度,拆分每个节点的最大特征数量,以及叶子节点中所能包含的最大数据点数。
有关随机森林模型优化的随机搜索的具体实现,请参阅Jupyter Notebook。
完整的运行示例
下面的代码是使用repl.it创建的,它展示了Python中随机森林的一个完整的交互式运行示例。你可以随意运行和更改代码(加载包可能需要一些时间)。
建议查看原文中的交互环境
结论
虽然我们不需要理解底层原理就可以在Python中构建功能强大的机器学习模型,但我发现了解幕后发生的事情会更有效。在本文中,我们不仅在Python中构建和使用了随机森林,而且我们还从基础出发了解了该模型。
我们首先查看了单独的决策树,这也是一个随机森林的基本构成要素,然后我们学习了如何通过在一个称为随机森林的集成模型中组合数百个决策树来解决单个决策树的高方差问题。随机森林可以总结为使用观测数据的随机抽样,特征的随机抽样并且平均各个树的预测。
从这篇文章中理解到的关键概念是:
● 决策树: 一种直观的模型,可根据询问有关特征值的一系列问题做出决策。具有低偏差和高方差的特征,这会导致过拟合训练数据。● 基尼不纯度: 决策树在拆分每个节点时尝试最小化的度量。表示根据节点中的样本分布对随机选择的样本分类错误的概率。
● 自助抽样法: 有放回地对观察值进行随机采样。
● 随机特征子集: 考虑对决策树中每个节点的分割时,选择一组随机特征。
● 随机森林: 使用自助抽样法,随机特征子集和平均投票来进行预测的由许多决策树组成的集合模型。这是Bagging的一个例子。
● 偏差方差权衡: 机器学习中的核心问题,描述了具有高灵活性(高方差),即可以很好地学习训练数据,但以牺牲泛化新数据的能力的模型,与无法学习训练数据的不灵活(高偏差)的模型之间的平衡。随机森林减少了单个决策树的方差,从而可以更好地预测新数据。
希望本文为你提供了在项目中使用随机森林所需的信心和对原理的理解。随机森林是一种强大的机器学习模型,但这不应该阻止我们理解它的工作机制。我们对模型的了解越多,我们就越有能力有效地使用它并解释它如何进行预测。
一如既往,欢迎进行评论反馈和建设性的批评。可以通过Twitter @koehrsen_will与我联系。本文最初发布于enlight,一个用于研究机器学习的开源社区。感谢enlight和用来托管文中代码的repl.it。
原文发布时间为:2018-11-29
本文作者:William Koehrsen