独家 | 一文读懂随机森林的解释和实现(附python代码)

简介:

如今由于像Scikit-Learn这样的库的出现,我们可以很容易地在Python中实现数百种机器学习算法。它们是如此易用,以至于我们通常都不需要任何关于模型底层工作机制的知识就可以使用它们。虽然没必要了解所有细节,但了解某个机器学习模型大致是如何工作的仍然有帮助。这使得我们可以在模型表现不佳时进行诊断,或者解释模型是如何做决策的,这一点至关重要,尤其当我们想要说服别人相信我们的模型时。 

在本文中,我们将介绍如何在Python中构建和使用随机森林(Random Forest)。除了查看代码之外,我们还将尝试了解此模型的工作原理。因为随机森林由许多决策树(decision tree)组成,所以我们先来了解一下单个决策树如何在一个简单的问题上进行分类。随后,我们将使用随机森林来解决一个现实世界中的数据科学问题。本文的完整代码在GitHub上以Jupyter Notebook的形式提供。

注意:本文最初出现在enlight上,这是一个社区驱动的开源平台,为那些希望学习机器学习的人提供教程。

理解决策树

决策树是随机森林的基本构成要素,而且是一种直观的模型。我们可以将决策树视为一系列关于数据的是/否问题,从而最终得出一个预测类别或回归情况下的连续值)。 这是一个可解释的模型,因为它非常像我们人类进行分类的过程:在我们做出决定之前(在理想世界中),我们会对可用数据进行一系列的询问。

决策树的技术细节在于如何形成关于数据的问题。在CART算法中,通过确定问题(称为节点的分裂)来构建决策树,这些问题在得到应答时会导致基尼不纯度(Gini Impurity)的最大减少。这意味着决策树试图形成包含来自单个类的高比例样本(数据点)的节点,这个过程通过在能将数据干净地划分为不同类的特征中找到适当的值来实现。

我们稍后会谈谈基尼不纯度的更底层细节,但首先,让我们构建一个决策树,以便我们能够在高层次上理解它。

简单问题上的决策树

我们将从一个非常简单的二元分类问题开始,如下所示:

a7cfd81222f1ad4f2da40c41490c5fd4a9c806ed

目标是把数据点划分到各自所属的类

我们的数据只有两个特征(预测变量),x1和x2,共有6个数据点(样本),被分为2个不同的标签。虽然这个问题很简单,但它不是线性可分的(linearly separable),这意味着我们不能绘制一条通过数据的直线来对点进行分类。

然而,我们可以绘制一系列直线,将数据点划分入多个框,我们称这些框为节点。 事实上,这就是决策树在训练期间所做的事情。实际上决策树是通过构造许多线性边界而构建的一个非线性模型。

我们使用Scikit-Learn来创建决策树并在数据上训练(拟合)

25d073585d3c0d693df555005544ed807aa61971

在训练过程中,我们为模型提供特征和标签,以帮助它学习如何根据特征对点进行分类。(针对这个简单问题我们没有测试集,在测试时,我们只为模型提供特征值并让它对标签进行预测。)

我们可以在训练数据上测试模型的准确性:

90eec199071a8934df05389f51f065840c20c602

可以看到它获得了我们所期望的100%的准确性,这是因为我们给了它训练的答案(y),并且没有限制树的深度。事实证明,在训练数据中过强的学习能力可能是一个缺点,因为它可能导致过拟合(overfitting),我们将在稍后对此进行讨论。

可视化决策树

当我们训练决策树时到底发生了什么?可视化可以帮助我们更好地理解决策树,这可以通过Scikit-Learn的一个功能来实现(详细信息,请查看notebook或这篇文章)

81b13715cafd2fd62dcd95404615e146defd48f4

简单的决策树

除叶子节点(彩色终端节点)外,所有节点都有5个部分:

 ●  基于某个特征的一个值对数据进行的提问,每个提问都有一个真或假的答案可以分裂节点。根据答案,数据点相应地向下移动。
 ●  gini: 节点的Gini不纯度。当我们沿着树向下移动时,平均加权基尼不纯度会减少。
 ●  samples :节点中的观测数据数量。
 ●  value: 每个类中的样本数。例如,根节点中有2个样本属于类0,有4个样本属于类1。
 ●  class: 该节点中大多数点的分类。在叶节点中,即是对节点中所有样本的预测。

叶节点中不再提问,因为这里已经产生了最终的预测。要对某个新数据点进行分类,只需沿着树向下移动,使用新点的特征来回答问题,直到到达某个叶节点,该叶节点对应的分类就是最终的预测。

为了以不同的方式查看树,我们可以在原始数据上绘制由决策树构建的分割。

b80a56c884c2b2e27ea8d936a1ed4e819c790e52

决策树构建的分割

每个分割都是一条线,它根据特征值将数据点划分到不同节点。对于这个简单的问题并且对最大深度没有做出限制,划分最终把每个点放置在仅包含同类点的一个节点中。(再次提醒,稍后我们将看到训练数据的这种完美划分可能并非我们想要的,因为它可能导致过拟合)

基尼不纯度(Gini Impurity)

是时候深入了解基尼不纯度的概念了(数学并不吓人!)节点的基尼不纯度是指,根据节点中样本的分布对样本分类时,从节点中随机选择的样本被分错的概率。例如,在根节点中,根据节点中的样本标签有44.4%的可能性错误地对某个随机选择的数据点进行分类。可以使用以下等式得出这个值:

deae64001cc17f1fde89438457a2a0b7b4540b39

节点n的基尼不纯度

节点n的基尼不纯度是1减去每个类(二元分类任务中是2)的样本比例的平方和。有点拗口,所以我们来一起计算出根节点的基尼不纯度。

242719c513fe705849b540174e1184cf606a94f9

根节点的基尼不纯度

在每个节点,决策树要在所有特征中搜索用于拆分的值,从而可以最大限度地减少基尼不纯度。(拆分节点的另一个替代方法是使用信息增益)。

然后,它以贪婪递归的过程重复这种拆分,直到达到最大深度,或者每个节点仅包含同类的样本。树每层的加权总基尼不纯度一定是减少的。在树的第二层,总加权基尼不纯度值为0.333:

ff3683805bbb23f8b416224fbb13b08ea4eba523

(每个节点的基尼不纯度按照该节点中来自父节点的点的比例进行加权。)你可以继续为每个节点计算基尼不纯度(可视化图中有答案)。 就这样,从一些基本的数学中,诞生了一个强大的模型!

最终,最后一层的加权总基尼不纯度变为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表示身体健康。该数据集由疾病控制和预防中心收集,可在此处获取。

1cb868f1a772e0117db22a7bac17df6e698bb5c4

数据样本

通常,一个数据科学项目80%的工作是在清洗,探索和提取数据中的特征。然而这篇文章我们的重点在于建模(有关其他步骤的详细信息,请参阅本文)。

这是一个不平衡的分类问题,因此准确率(accuracy)并不是一个合适的衡量指标。作为替代,我们将利用ROC和AUC,AUC是一个从0(最差)到1(最佳)的度量值,随机猜测得分为0.5。我们还可以绘制ROC曲线来评估模型。

这个notebook包含了决策树和随机森林的实现,但在这里我们只关注随机森林。 在读取数据后,我们就可以实例化并且训练一个随机森林,具体如下:

cfe7c89dbadc096a07d3afbbc9ae42f4530c53cf

在几分钟的训练后,模型已准备好对测试数据进行预测了,如下:

f6645c41ad89fd0ce68522f5ceb191cd082d585f

我们预测分类(predict)以及预测概率(predict_proba)来计算ROC AUC。一旦我们有了对测试集的预测结果,我们就可以计算出ROC AUC。

5a267fcabbe6c85447363d7eb780639c16bac4df

结果

随机森林的最终测试集ROC AUC为0.87,而具有无限最大深度的单一决策树的最终测试集ROC AUC为0.67。如果查看训练分数,则两个模型都达到了1.0的 ROC AUC,这也是可以预料到的,因为我们给这些模型提供了训练答案,并且没有限制每棵树的最大深度。

虽然随机森林过拟合了(在训练数据上比在测试数据上做得更好),但在测试数据上它比单一决策树泛化地更好。随机森林具有较低的方差(好处),同时能保持与一棵决策树相同的低偏差(也是好处)

我们还可以绘制单个决策树(顶部)和随机森林(底部)的ROC曲线。靠近左上角的曲线代表着更好的模型:

961e9c4fb667b2e9afaa2d56c31ba7c8899c9e8d

随机森林明显优于单一决策树。

另一个我们可以采用的模型诊断措施是绘制测试集预测结果的混淆矩阵(详细信息,请参阅notebook):

4cdfe1698a174a61c8fcb1de31bc1c72f175a72e

在左上角和右下角它显示了模型的正确预测,在左下角和右上角显示了模型误判的预测。我们可以使用这类图来诊断我们的模型,来决定它是否表现的足够良好并可以投入生产。

特征重要性(Feature Importances)

随机森林中的特征重要性表示在该特征上拆分的所有节点的基尼不纯度减少的总和。我们可以使用它来尝试找出随机森林认为最重要的预测变量。可以从一个训练好的随机森林中提取特征重要性,并将其放入Pandas的DataFrame中,如下所示:

7ca18b454bc0964a5409f75389240ae84a1918ba

通过告诉我们哪些变量在类之间最具辨别力,特征重要性可以让我们更好地洞察问题。例如,DIFFWALK是表明患者是否行走困难的的重要的特征,这在问题的上下文中也说得通。

通过从最重要的特征中构建额外的特征,特征重要性可以被用于特征工程(feature engineering)。我们还可以通过删除不重要的特征,来把特征重要性用于特征选择。

可视化森林中的树

最后,我们可以可视化在森林中的单个决策树。这次我们必须限制树的深度,否则它将太大而无法被转换为一幅图像。为了制作下图,我将最大深度限制为6。但这仍然产生了一棵我们无法完全解析的大树!不过由于我们深入地研究过决策树,我们还是可以通过这幅图掌握这个模型的工作原理。

830380fedd22cb894805fd97f80bc71a538eb323

随机森林中的单棵决策树

下一步

下一步是使用Scikit-Learn中的RandomizedSearchCV通过随机搜索来优化随机森林。优化是指在给定数据集上找到模型的最佳超参(hyperparameters)。最佳超参将随着数据集的不同而变化,因此我们必须在每个数据集上单独执行优化这也称为模型调整(model tuning)

我喜欢将模型调整视为给一个机器学习算法寻找最佳设置。我们可以在随机森林中优化的东西包括决策树的数量,每个决策树的最大深度,拆分每个节点的最大特征数量,以及叶子节点中所能包含的最大数据点数。

有关随机森林模型优化的随机搜索的具体实现,请参阅Jupyter Notebook。

完整的运行示例

下面的代码是使用repl.it创建的,它展示了Python中随机森林的一个完整的交互式运行示例。你可以随意运行和更改代码(加载包可能需要一些时间)

941bb327dce1d334aa0d91a0bea707f216bac1eb

建议查看原文中的交互环境

结论

虽然我们不需要理解底层原理就可以在Python中构建功能强大的机器学习模型,但我发现了解幕后发生的事情会更有效。在本文中,我们不仅在Python中构建和使用了随机森林,而且我们还从基础出发了解了该模型。

我们首先查看了单独的决策树,这也是一个随机森林的基本构成要素,然后我们学习了如何通过在一个称为随机森林的集成模型中组合数百个决策树来解决单个决策树的高方差问题。随机森林可以总结为使用观测数据的随机抽样,特征的随机抽样并且平均各个树的预测。

从这篇文章中理解到的关键概念是:

 ●  决策树: 一种直观的模型,可根据询问有关特征值的一系列问题做出决策。具有低偏差和高方差的特征,这会导致过拟合训练数据。
 ●  基尼不纯度: 决策树在拆分每个节点时尝试最小化的度量。表示根据节点中的样本分布对随机选择的样本分类错误的概率。
 ●  自助抽样法: 有放回地对观察值进行随机采样。
 ●  随机特征子集: 考虑对决策树中每个节点的分割时,选择一组随机特征。
 ●  随机森林: 使用自助抽样法,随机特征子集和平均投票来进行预测的由许多决策树组成的集合模型。这是Bagging的一个例子。
 ●  偏差方差权衡: 机器学习中的核心问题,描述了具有高灵活性(高方差),即可以很好地学习训练数据,但以牺牲泛化新数据的能力的模型,与无法学习训练数据的不灵活(高偏差)的模型之间的平衡。随机森林减少了单个决策树的方差,从而可以更好地预测新数据。

希望本文为你提供了在项目中使用随机森林所需的信心和对原理的理解。随机森林是一种强大的机器学习模型,但这不应该阻止我们理解它的工作机制。我们对模型的了解越多,我们就越有能力有效地使用它并解释它如何进行预测。

一如既往,欢迎进行评论反馈和建设性的批评。可以通过Twitter @koehrsen_will与我联系。本文最初发布于enlight,一个用于研究机器学习的开源社区。感谢enlight和用来托管文中代码的repl.it。


原文发布时间为:2018-11-29

本文作者:William Koehrsen

本文来自云栖社区合作伙伴“数据派THU”,了解相关信息可以关注“数据派THU”。

相关文章
|
8天前
|
缓存 监控 测试技术
Python中的装饰器:功能扩展与代码复用的利器###
本文深入探讨了Python中装饰器的概念、实现机制及其在实际开发中的应用价值。通过生动的实例和详尽的解释,文章展示了装饰器如何增强函数功能、提升代码可读性和维护性,并鼓励读者在项目中灵活运用这一强大的语言特性。 ###
|
11天前
|
缓存 开发者 Python
探索Python中的装饰器:简化代码,增强功能
【10月更文挑战第35天】装饰器在Python中是一种强大的工具,它允许开发者在不修改原有函数代码的情况下增加额外的功能。本文旨在通过简明的语言和实际的编码示例,带领读者理解装饰器的概念、用法及其在实际编程场景中的应用,从而提升代码的可读性和复用性。
|
7天前
|
Python
探索Python中的装饰器:简化代码,提升效率
【10月更文挑战第39天】在编程的世界中,我们总是在寻找使代码更简洁、更高效的方法。Python的装饰器提供了一种强大的工具,能够让我们做到这一点。本文将深入探讨装饰器的基本概念,展示如何通过它们来增强函数的功能,同时保持代码的整洁性。我们将从基础开始,逐步深入到装饰器的高级用法,让你了解如何利用这一特性来优化你的Python代码。准备好让你的代码变得更加优雅和强大了吗?让我们开始吧!
15 1
|
12天前
|
设计模式 缓存 监控
Python中的装饰器:代码的魔法增强剂
在Python编程中,装饰器是一种强大而灵活的工具,它允许程序员在不修改函数或方法源代码的情况下增加额外的功能。本文将探讨装饰器的定义、工作原理以及如何通过自定义和标准库中的装饰器来优化代码结构和提高开发效率。通过实例演示,我们将深入了解装饰器的应用,包括日志记录、性能测量、事务处理等常见场景。此外,我们还将讨论装饰器的高级用法,如带参数的装饰器和类装饰器,为读者提供全面的装饰器使用指南。
|
8天前
|
存储 缓存 监控
掌握Python装饰器:提升代码复用性与可读性的利器
在本文中,我们将深入探讨Python装饰器的概念、工作原理以及如何有效地应用它们来增强代码的可读性和复用性。不同于传统的函数调用,装饰器提供了一种优雅的方式来修改或扩展函数的行为,而无需直接修改原始函数代码。通过实际示例和应用场景分析,本文旨在帮助读者理解装饰器的实用性,并鼓励在日常编程实践中灵活运用这一强大特性。
|
12天前
|
存储 算法 搜索推荐
Python高手必备!揭秘图(Graph)的N种风骚表示法,让你的代码瞬间高大上
在Python中,图作为重要的数据结构,广泛应用于社交网络分析、路径查找等领域。本文介绍四种图的表示方法:邻接矩阵、邻接表、边列表和邻接集。每种方法都有其特点和适用场景,掌握它们能提升代码效率和可读性,让你在项目中脱颖而出。
26 5
|
10天前
|
机器学习/深度学习 数据采集 人工智能
探索机器学习:从理论到Python代码实践
【10月更文挑战第36天】本文将深入浅出地介绍机器学习的基本概念、主要算法及其在Python中的实现。我们将通过实际案例,展示如何使用scikit-learn库进行数据预处理、模型选择和参数调优。无论你是初学者还是有一定基础的开发者,都能从中获得启发和实践指导。
24 2
|
12天前
|
数据库 Python
异步编程不再难!Python asyncio库实战,让你的代码流畅如丝!
在编程中,随着应用复杂度的提升,对并发和异步处理的需求日益增长。Python的asyncio库通过async和await关键字,简化了异步编程,使其变得流畅高效。本文将通过实战示例,介绍异步编程的基本概念、如何使用asyncio编写异步代码以及处理多个异步任务的方法,帮助你掌握异步编程技巧,提高代码性能。
48 4
|
14天前
|
缓存 开发者 Python
探索Python中的装饰器:简化和增强你的代码
【10月更文挑战第32天】 在编程的世界中,简洁和效率是永恒的追求。Python提供了一种强大工具——装饰器,它允许我们以声明式的方式修改函数的行为。本文将深入探讨装饰器的概念、用法及其在实际应用中的优势。通过实际代码示例,我们不仅理解装饰器的工作方式,还能学会如何自定义装饰器来满足特定需求。无论你是初学者还是有经验的开发者,这篇文章都将为你揭示装饰器的神秘面纱,并展示如何利用它们简化和增强你的代码库。
|
12天前
|
API 数据处理 Python
探秘Python并发新世界:asyncio库,让你的代码并发更优雅!
在Python编程中,随着网络应用和数据处理需求的增长,并发编程变得愈发重要。asyncio库作为Python 3.4及以上版本的标准库,以其简洁的API和强大的异步编程能力,成为提升性能和优化资源利用的关键工具。本文介绍了asyncio的基本概念、异步函数的定义与使用、并发控制和资源管理等核心功能,通过具体示例展示了如何高效地编写并发代码。
24 2