决策树集成-随机森林

基础概念

集成

集成是合并多个机器学习模型来构建更强大模型的方法。在机器学习算法中有许多模型属于这一类,但已证明有两种集成模型对大量分类和回归的数据集都是有效的,二者都以决策树为基础,分别是随机森林(random forest)梯度提升决策树决策(gradiet boosted decision tree)

本片文章先讲解一下随机森林。在了解随机森林之前建议先去看一下我的另外两篇讲解决策树的文章决策树算法之讲解实操(上)决策树算法之讲解实操(下),重复的东西,我这里就不在赘述了。
ps:接下来会花费很长的篇幅来讲解随机森林的思想和构造原理,已经有所了解的小伙伴可以直接跳过。

思想简介

在之前的一篇文章决策树的文章决策树算法之讲解实操(上)中我们提到过,决策树的一个主要缺点在于经常对训练数据过拟合。那么随机森林就是解决这个问题的一种方法。随机森林本质上是许多决策树的集合,其中每棵树都和其它的树略有不同。

它的思想是:每棵树的预测可能都相对较好,但可能对部分数据过拟合。如果构造很多树,并且每棵树的预测都很好,但都以不同的方式过拟合,那么我们可以对树的结果取平均值来降低过拟合。这样做,既能减少过拟合又能保持树的预测能力。

为了实现上面的思想,我们需要构造很多决策树,并且每棵树都与其它的树保持不同,即树的随机化。而树的随机化方法有两种:一种是通过选择用于构造树的数据点,另一种是通过选择每次划分测试的特征。接下来,我们来更深入地讲解这一块。

构造原理

想要构造一个随机森林模型,需要确定用于构造的树的个数,即RandomForestClassifier(分类算法)或RandomForestRegressor(回归算法)的n_estimators参数。比如我们想要构造10棵树,这些树在构造时彼此完全独立,算法对每棵树进行不同的随机选择。而想要构造一棵树,首先要对数据进行自助采样。也就是说,从n个数据点中有放回地重复抽取一个样本(同一样本可以被多次抽取),共抽取n次。

举例说明,比如我们想要创建列表[a,b,c,d]的自助采样。一种可能的结果是[b,d,d,c],另一种可能的结果是[d,a,d,a]。

接下来,基于这个新创建的数据集来构造决策树。在每个结点处,算法随机选择特征的一个子集,并对其中一个特征寻找最佳测试(注意并不是对每个结点都寻找最佳测试)。选择的特征个数由max_features参数来控制。每个结点中特征子集的选择都是相互独立的,这样树的每个结点可以使用特征的不同子集来作出决策。

总之,由于使用了自助采样,随机森林中构造每棵决策树的数据集都是略有不同的。由于每个结点的特征选择,每棵树中的每次划分都是基于特征的不同子集这两种方法共同保证随机森林中所有树都不同

想要利用随机森林进行预测,算法首先对森林中的每棵树进行预测。对于回归问题,我们可以对这些结果取平均值作为最终预测。对于分类问题,则用到了“软投票”策略。也就是说,每个算法作出“软预测”,给出每个可能的输出标签的概率。对所有树的预测概率取平均值,然后将概率最大的类别作为预测结果。

实操建模

数据是一份红酒质量分类的数据集,通过各个维度来判断红酒质量,之前在决策树算法之讲解实操(上)中已经讲解使用过了,这里就不多在赘述了,我们直接建模,代码如下所示:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
import pandas as pd
import numpy as np
import winreg
from sklearn.model_selection import train_test_split
from sklearn.ensemble import RandomForestClassifier#随机森林分类器
from sklearn.metrics import accuracy_score
###################
real_address = winreg.OpenKey(winreg.HKEY_CURRENT_USER,r'Software\Microsoft\Windows\CurrentVersion\Explorer\Shell Folders',)
file_address=winreg.QueryValueEx(real_address, "Desktop")[0]
file_address+='\\'
file_origin=file_address+"\\源数据-分析\\winequality-red.csv"###https://www.kaggle.com/uciml/red-wine-quality-cortez-et-al-2009
red_wine=pd.read_csv(file_origin)
#设立桌面绝对路径,读取源数据文件,这样将数据直接下载到桌面上就可以了,省得还要去找
###################
train=red_wine.drop(["quality"],axis=1)
X_train,X_test,y_train,y_test=train_test_split(train,red_wine["quality"],random_state=1)
###考虑到接下来可能需要进行其他的操作,所以定了一个随机种子,保证接下来的train和test是同一组数
forest=RandomForestClassifier(n_estimators=50,random_state=1)###n_estimators树的个数
train_prediction=forest.fit(X_train,y_train).predict(X_train)
test_prediction=forest.fit(X_train,y_train).predict(X_test)
print("随机森林分类器训练模型评分:"+str(accuracy_score(y_train,train_prediction)))
print("随机森林分类器待测模型评分:"+str(accuracy_score(y_test,test_prediction)))

结果如下所示:

在这里插入图片描述

下面是之前的文章中单棵决策树建立的模型结果:

在这里插入图片描述

二者相比可以看出,随机森林的模型精度要比单棵树的要好上不少,过拟合现象也比之前要减轻很多。
接下来我们了解一下随机森林的主要模型参数。

模型参数

在RandomForestClassifier中,我们主要会用到三个模型参数n_estimators(树的个数),max_depths(树的深度),max_features(随机特征数),它们对模型的影响程度依次递减。至于其它的参数,一般情况下直接默认就好。

n_estimators:这个参数总是越大越好,对更多的树取平均值可以降低过拟合,从而得到鲁棒性更好的集成。不过收益是递减的,而且树越多需要的内存也越多,训练的时间也越长。常用的经验法就是“在时间/内存允许的情况下尽量多”。

接下来我们来调节这个参数,提高模型精度,代码及结果如下所示:

1
2
3
4
5
6
result_1=pd.DataFrame(columns=["集成树的个数(n_estimators)","随机森林分类器待测模型评分"])
for i in range(1,500,10):
forest=RandomForestClassifier(n_estimators=i,random_state=1)
forest.fit(X_train,y_train)
result_1=result_1.append([{"集成树的个数(n_estimators)":i,"随机森林分类器待测模型评分":accuracy_score(y_test,forest.predict(X_test))}])
result_1[result_1["随机森林分类器待测模型评分"]==result_1["随机森林分类器待测模型评分"].max()]

在这里插入图片描述

可以看到当我们设定参数n_estimators为141或者151的时候,模型精度可以达到73%左右,较之前的结果提高了一些。

max_depths:通过调节这个参数可以像单棵决策树那样进行预剪枝,当然,这个参数默认情况下就是最大深度,一般不需要调节。

max_features:这个参数决定每棵树的随机性大小,较小的话可以降低过拟合,一般来说,好的经验就是使用默认值。

不过为了演示,我们依然可以调节这个参数,代码及结果如下所示:

1
2
3
4
5
6
result_1=pd.DataFrame(columns=["最大特征数量(max_features)","随机森林分类器待测模型评分"])
for i in range(1,11):
forest=RandomForestClassifier(n_estimators=151,max_features=i,random_state=1)
forest.fit(X_train,y_train)
result_1=result_1.append([{"最大特征数量(max_features)":i,"随机森林分类器待测模型评分":accuracy_score(y_test,forest.predict(X_test))}])
result_1[result_1["随机森林分类器待测模型评分"]==result_1["随机森林分类器待测模型评分"].max()]

在这里插入图片描述

正如我之前提到的,这个参数主要是用来限制树的最大随机特征数量的。那么,如果我们设置max_features等于特征值的数量(在上面的红酒质量数据集中是11),接下来每次划分都要考虑数据集的所有特征,也就意味着在特征选择过程中没有添加随机性(不过自助采样依然存在随机性)。如果设置max_features等于1,那么在划分时将无法选择对哪个特征进行测试,只能对随机选择的某个特征搜索不同的阈值。因此,如果max_features较大,那么随机森林中的树将会十分相似,利用最独特的特征可以轻松地拟合数据。如果max_features较小,那么随机森林中的树将会差异很大,为了更好地拟合数据,每棵树的深度都要很大。当然,实际过程中,它的默认参数通常就已经可以给出很好的结果了。

对于分类,默认值是max_features=sqrt(维度个数);
对于回归,默认值是max_features=维度个数。

分析特征重要性

与决策树类似,随机森林也可以给出特征重要性,计算方法是将森林中所有树的特征重要性求和并取平均。一般来说,随机森林给出的特征重要性要比单棵树给出的更为可靠。代码及结果如下:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
from sklearn.tree import DecisionTreeClassifier
import seaborn as sns
import matplotlib.pyplot as plt
plt.style.use("fivethirtyeight")
sns.set_style({'font.sans-serif':['SimHei','Arial']})
%matplotlib inline
tree=DecisionTreeClassifier(max_depth=3,random_state=1)###单棵树分类器
forest=RandomForestClassifier(max_depth=3,random_state=1)###随机森林分类器
tree_prediction=tree.fit(X_train,y_train)
forest_prediction=forest.fit(X_train,y_train)
fig= plt.subplots(figsize=(20,15))
fig1 = plt.subplot(211)
plt.title('决策树分类器特征重要性',fontsize=20)
plt.bar(train.columns,tree_prediction.feature_importances_,0.4,color="blue")
plt.legend()
fig2=plt.subplot(212)
plt.title('随机森林分类器特征重要性',fontsize=20)
plt.bar(train.columns,forest_prediction.feature_importances_,0.4,color="green")
plt.legend()
plt.xticks(fontsize=13)

在这里插入图片描述

如上图所示,在保证树的深度参数(max_depth)相同的情况下,与单棵树相比,随机森林中有更多特征的重要性不为零。由于构造随机森林过程中的随机性,算法需要考虑多种可能的解释,结果就是随机森林比单棵树更能从总体把握数据的特征性。

优缺点

用于分类和回归的随机森林是目前应用最广泛的机器学习方法之一。这种方法十分强大,通常不需要反复调节参数就可以给出很好的结果,也不需要对数据进行缩放。

从本质上看,随机森林拥有决策树的所有优点,同时弥补了决策树的一些缺陷。而实际中,我们仍然需要使用决策树的一个根本原因就是需要决策过程的紧凑表示。实际过程中,基本上不可能对几十棵甚至上百棵树作出详细解释,同时随机森林中树的深度往往比决策树还要大(因为用到了特征子集)。因此,如果你需要以可视化的方式想非专家总结预测过程,那么选择单棵树可能更好。虽然在大型数据集上构建随机森林可能比较费时间,但在一台计算器上用多个内核并行计算也很容易。

随机森林本质上是随机的,设置不同的状态(或者不设置random_state)可以彻底改变构建的模型。森林中的树越多,它对随机状态选择的鲁棒性(朴素贝叶斯分类器之分类实操这篇文章介绍了鲁棒性的概念)也就越好。如果你希望结果可以重现,固定random_state是很重要的。

如果是分析维度非常高的稀疏数据,比如文本数据,随机森林的表现往往不是很好。对于这种数据,使用线性模型(朴素贝叶斯分类器之分类实操或者支持向量机(SVM)算法之分类实操)可能更合适。即使是非常大的数据集,随机森林的表现通常也很好。不过随机森林需要更大的内存,训练和预测的速度也比线性模型要慢。对于一个应用来说,如果时间和内存很重要的话,那么换用线性模型可能更为明智。

有很多地方做的不是很好,欢迎网友来提出建议,也希望可以遇到些朋友来一起交流讨论。