机器学习 (四) 基于Python实现的随机森林详解

1.概述

随机森林 Random Forest 是一种强大的机器学习模型,得益于各种强大的库,现在我们可以很轻松地调用它,但并不是每一个会使用该模型的人都理解它真正的实现方式,本文将用 Python 实现并解释了决策树和随机森林的工作过程。

比如Scikit-Learn,让我们现在可以非常轻松地使用 Python 实现任何机器学习算法。

2.随机森林简介

随机森林,是用随机的方式建立一个森林,森林里面有很多的决策树组成,随机森林的每一棵决策树之间是没有关联的,随机森林背后的思想,是与群体智慧,甚至“看不见的手”相互映照。

决策树(decision tree)是一个树形结构。其中每个非叶节点表示一个特征属性上的判断,每个分支代表这个特征属性在某个判断上的输出,而每个叶节点对应的是一个类别。使用决策树进行决策的过程就是从根节点开始,对待分类相应的特征属性进行判断,并按照其值选择输出分支,直到到达叶子节点,将叶子节点存放的类别作为最后的决策结果。

3.单个决策树

这里先从一个简单的二元分类任务开始:

我们的数据仅有两个特征(x1, x2),这里共有 6 个数据点,2 种不同的 lable标签。

可以看出我们不能在这些数据之间用一条直线将各个点划分到对应的类别,也就是说不是线性可分的。但是,我们可以画出一系列直线来分开这两个类别,这实际上就是在构建决策树的做法。

在这里,我们使用Scikit-Learn快速构建和训练数据的单个决策树:

from sklearn.tree import DecisionTreeClassifier
# Set random seed to ensure reproducible runs
RSEED = 50
# Make a decision tree and train
tree = DecisionTreeClassifier(random_state=RSEED)
tree.fit(X, y) #训练模型
#输出决策树的结构
print(f'Decision tree has {tree.tree_.node_count} nodes with maximum depth {tree.tree_.max_depth}.')
# Decision tree has 9 nodes with maximum depth 3.
#输出准确度
print(f'Model Accuracy: {tree.score(X, y)}')
#Model Accuracy: 1.0

这里的决策树使用 Scikit-Learn 默认的超参数,但是它也可以根据需要增加决策树的个数,以便完全分离类。这样会导致过度拟合,因为该模型过度耦合训练数据,一般在实践中,我们通常希望限制树的深度,以便模型有更好的扩展性。

在训练过程中,我们会向模型提供特征 X 和标签 y,使其能够学习基于这些特征对数据点进行分类。从输出结果看出,决策树形成了9个节点nodes ,最大深度depth 为3,它将在训练数据上达到100%的准确性,因为我们没有限制深度,因此可以完美地对每个训练点进行分类。

4.随机森林

从上面的决策树来看,这种分类器堪称完美,因为根本不会犯任何错误,但要记住一个重点:单个决策树只是不会在训练数据上犯错。而机器学习模型的关键在于能很好地泛化用于测试数据。不幸的是,当我们不限制决策树的深度时,它往往都会与训练数据过拟合。

随机森林是多个决策树的集合,想象一下,你要分析明天小米的股票是否上涨,然后你决定去询问几位股票分析师朋友A和B。任何一位股票分析师都可能有很大的差异,并且非常依赖他们各自能获取的数据——A可能仅仅看到了小米公司新产品大卖的新闻,因此认为价格会上涨,而B看到了最近报道小米手机质量问题的新闻,所以认为应当下跌。这些分析师个体之间有很高的方差,因为他们的答案严重依赖于他们见过的数据。我们不只是询问单个分析师,而是集各种专家的意见,是相当于群体智慧,并基于最常见的答案给出最终决策。

随机森林的本质:不是使用单个决策树,而是使用数百或数千个决策树来组成一个强大的模型,则该模型的最终预测结果即为集体中所有决策树的预测的平均。

随机森林是由许多决策树构成的模型。这不仅仅是森林,而且是随机的,这涉及到两个概念:

  • 随机采样

随机森林的其中一个特点是:每个树都在训练集上随机的采样,这些样本是可重复地抽取出来的(叫 bootstrapping),也就是说某些样本会多次用于单个树的训练(当然也可以控制不重复)。这种在数据的不同子集上训练每个单个学习器然后再求预测结果的平均的流程被称为 bagging,这是 bootstrap aggregating 的缩写。

  • 基于特征属性的子集进行分类

随机森林的另一个特点是:在每个决策树中,分割每个节点时都只会考虑所有特征属性中的一个子集。通常设定为 sqrt(n_features),意思是在每个节点,决策树会基于一部分特征来考虑分割,这部分特征的数量为总特征数量的平方根。(在 Scikit-Learn 随机森林实现中,这些参数是可以调的。)

5.随机森林实践

我们将会使用一个真实数据集构建一个随机森林,非常类似于上面简单的二元分类模型,通过 Scikit-Learn 使用随机森林仅需要几行代码。

数据集

以下数据集来美国疾病预防控制中心,包括数十万人的社会经济和生活方式指标。目标是预测个体的整体健康状况:健康状况不佳0,健康状况良好1。

数据集这里可以获取:
https://www.kaggle.com/cdc/behavioral-risk-factor-surveillance-system

这是一个不平衡分类问题,因此准确度并不是合适的度量标准。我们将使用AUC(Area under Curve)来衡量,该度量的取值范围为 0(最差)到 1(最好),随机猜测的分数为 0.5。此外,我们还可以绘制 ROC 曲线来评估模型的表现。

AUC(Area under Curve):Roc曲线下的面积,介于0.1和1之间。Auc作为数值可以直观的评价分类器的好坏,值越大越好。

构建模型

这里我们省略了对数据的预处理,包括处理缺省值、去掉部分特征属性、分割训练测试集等,在读取了数据之后,我们可以用以下代码先实例化并训练一个决策树:

from sklearn.tree import DecisionTreeClassifier
from sklearn.metrics import precision_score, recall_score, roc_auc_score, roc_curve
# 单个决策树
tree.fit(train, train_labels)
print(f'Decision tree has {tree.tree_.node_count} nodes with maximum depth {tree.tree_.max_depth}.')
# Decision tree has 12327 nodes with maximum depth 55.
#评价性能
train_probs = tree.predict_proba(train)[:, 1]
probs = tree.predict_proba(test)[:, 1]

train_predictions = tree.predict(train)
predictions = tree.predict(test)
print(f'Train ROC AUC Score: {roc_auc_score(train_labels, train_probs)}')
print(f'Test ROC AUC  Score: {roc_auc_score(test_labels, probs)}')
#Train ROC AUC Score: 1.0
#Test ROC AUC  Score: 0.6639844451938421

以上是单个决策树的在训练集和测试集上的 ROC AUC 评价指标,可以看出单个决策树在训练集上AUC 值是1 ,但是在测试集上是0.66,AUC 大大降低了。

接下来我们可以实例化并训练一个随机森林:

from sklearn.ensemble import RandomForestClassifier
# Create the model with 100 trees
model = RandomForestClassifier(n_estimators=100, 
                               random_state=RSEED, 
                               max_features = 'sqrt',
                               n_jobs=-1, verbose = 1)
# Fit on training data
model.fit(train, train_labels)
#随机森林中决策树的平均深度以及节点数
n_nodes = []
max_depths = []
for ind_tree in model.estimators_:
    n_nodes.append(ind_tree.tree_.node_count)
    max_depths.append(ind_tree.tree_.max_depth)
print(f'Average number of nodes {int(np.mean(n_nodes))}')
print(f'Average maximum depth {int(np.mean(max_depths))}')
#Average number of nodes 13396
#Average maximum depth 46
....
....
#预测结果
rf_predictions = model.predict(test)
rf_probs = model.predict_proba(test)[:, 1]
# Calculate roc auc
print(f'Train ROC AUC Score: {roc_auc_score(train_labels, train_probs)}')
print(f'Test ROC AUC  Score: {roc_auc_score(test_labels, probs)}')
#Train: 1.0  Test: 0.87 

可以看出随机森林的在测试集上ROC AUC 结果是 0.87,而单个决策树的结果是 0.67。如果我们看看训练分数,可以看到这两个模型都得到了 1.0 的 ROC AUC,很明显这是过拟合了,因为我们已经为这些模型提供过训练数据的答案并且没有限制数的最大深度。尽管随机森林过拟合了,但是与单个决策树相比,它仍有更好的泛化能力。

构建调参

为了最大化随机森林的性能,我们可以使用随机搜索以获得更好的超参数。从网格中随机选择超参数的组合,使用对训练数据的交叉验证来评估它们,并返回执行最佳的值。

from sklearn.model_selection import RandomizedSearchCV
# Hyperparameter grid
param_grid = {
    'n_estimators': np.linspace(10, 200).astype(int),
    'max_depth': [None] + list(np.linspace(3, 20).astype(int)),
    'max_features': ['auto', 'sqrt', None] + list(np.arange(0.5, 1, 0.1)),
    'max_leaf_nodes': [None] + list(np.linspace(10, 50, 500).astype(int)),
    'min_samples_split': [2, 5, 10],
    'bootstrap': [True, False]
}
# Estimator for use in random search
estimator = RandomForestClassifier(random_state = RSEED)
# Create the random search model
rs = RandomizedSearchCV(estimator, param_grid, n_jobs = -1, 
                        scoring = 'roc_auc', cv = 3, 
                        n_iter = 10, verbose = 1, random_state=RSEED)
# Fit 
rs.fit(train, train_labels)
print(rs.best_params_ )
{'n_estimators': 29,
 'min_samples_split': 5,
 'max_leaf_nodes': 43,
 'max_features': 'auto',
 'max_depth': 16,
 'bootstrap': True}

我们可以看到最好的超参数值不是 Scikit-Learn 的默认值,这显示了为特定数据集调整模型的重要性。

...
best_model = rs.best_estimator_
train_rf_predictions = best_model.predict(train)
train_rf_probs = best_model.predict_proba(train)[:, 1]

rf_predictions = best_model.predict(test)
rf_probs = best_model.predict_proba(test)[:, 1]

n_nodes = []
max_depths = []

for ind_tree in best_model.estimators_:
    n_nodes.append(ind_tree.tree_.node_count)
    max_depths.append(ind_tree.tree_.max_depth)
    
print(f'Average number of nodes {int(np.mean(n_nodes))}')
print(f'Average maximum depth {int(np.mean(max_depths))}')
#Average number of nodes 85
#Average maximum depth 10

#预测结果
rf_predictions = model.predict(test)
rf_probs = model.predict_proba(test)[:, 1]
# Calculate roc auc
print(f'Train ROC AUC Score: {roc_auc_score(train_labels, train_probs)}')
print(f'Test ROC AUC  Score: {roc_auc_score(test_labels, probs)}')
#Train: 0.88  Test: 0.87 

如上所述,最佳模型的最大深度大大减少!这表明限制单个决策树的最大深度可以提高随机森林的交叉验证性能,此外,最优随机森林也减少了在训练集上的拟合度,优化模型实现与默认模型大致相同的性能。

6.总结

虽然我们可能不需要理解机器学习模型内部原理也能构建出强大的机器学习模型,但了解一些模型工作方式的还是很有必要的,对你理解模型的各个超参数有很大帮助。

7.参考资料

1.https://enlight.nyc/projects/random-forest
2.https://scikit-learn.org/

  • 5
    点赞
  • 49
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值