Random Forest 实现

Random Forest 实现

随机森林是一种由决策树构成的集成算法,他在很多情况下都能有不错的表现。

本文将介绍随机森林的基本概念、4 个构造步骤、4 种方式的对比评测、10 个优缺点和 4 个应用方向。

什么是随机森林?

随机森林属于 集成学习 中的 Bagging(Bootstrap AGgregation 的简称) 方法。如果用图来表示他们之间的关系如下:

Random Forest 实现

决策树 – Decision Tree

Random Forest 实现

在解释随机森林前,需要先提一下决策树。决策树是一种很简单的算法,他的解释性强,也符合人类的直观思维。这是一种基于if-then-else规则的有监督学习算法,上面的图片可以直观的表达决策树的逻辑。

了解详情:《一文看懂决策树 – Decision tree(3个步骤+3种典型算法+10个优缺点)》

随机森林 – Random Forest | RF

Random Forest 实现

随机森林是由很多决策树构成的,不同决策树之间没有关联。

当我们进行分类任务时,新的输入样本进入,就让森林中的每一棵决策树分别进行判断和分类,每个决策树会得到一个自己的分类结果,决策树的分类结果中哪一个分类最多,那么随机森林就会把这个结果当做最终的结果。

构造随机森林的 4 个步骤

Random Forest 实现

  1. 一个样本容量为N的样本,有放回的抽取N次,每次抽取1个,最终形成了N个样本。这选择好了的N个样本用来训练一个决策树,作为决策树根节点处的样本。
  2. 当每个样本有M个属性时,在决策树的每个节点需要分裂时,随机从这M个属性中选取出m个属性,满足条件m << M。然后从这m个属性中采用某种策略(比如说信息增益)来选择1个属性作为该节点的分裂属性。
  3. 决策树形成过程中每个节点都要按照步骤2来分裂(很容易理解,如果下一次该节点选出来的那一个属性是刚刚其父节点分裂时用过的属性,则该节点已经达到了叶子节点,无须继续分裂了)。一直到不能够再分裂为止。注意整个决策树形成过程中没有进行剪枝。
  4. 按照步骤1~3建立大量的决策树,这样就构成了随机森林了。

随机森林的优缺点

优点

  1. 它可以出来很高维度(特征很多)的数据,并且不用降维,无需做特征选择
  2. 它可以判断特征的重要程度
  3. 可以判断出不同特征之间的相互影响
  4. 不容易过拟合
  5. 训练速度比较快,容易做成并行方法
  6. 实现起来比较简单
  7. 对于不平衡的数据集来说,它可以平衡误差。
  8. 如果有很大一部分的特征遗失,仍可以维持准确度。

缺点

  1. 随机森林已经被证明在某些噪音较大的分类或回归问题上会过拟合。
  2. 对于有不同取值的属性的数据,取值划分较多的属性会对随机森林产生更大的影响,所以随机森林在这种数据上产出的属性权值是不可信的

随机森林 4 种实现方法对比测试

随机森林是常用的机器学习算法,既可以用于分类问题,也可用于回归问题。本文对 scikit-learn、Spark MLlib、DolphinDB、XGBoost 四个平台的随机森林算法实现进行对比测试。评价指标包括内存占用、运行速度和分类准确性。

测试结果如下:

Random Forest 实现

测试过程及说明忽略,感兴趣的可以查看原文《随机森林算法 4 种实现方法对比测试:DolphinDB 速度最快,XGBoost 表现最差》

随机森林的 4 个应用方向

Random Forest 实现

随机森林可以在很多地方使用:

  1. 对离散值的分类
  2. 对连续值的回归
  3. 无监督学习聚类
  4. 异常点检测

百度百科+维基百科

百度百科版本

在机器学习中,随机森林是一个包含多个决策树的分类器, 并且其输出的类别是由个别树输出的类别的众数而定。 Leo Breiman和Adele Cutler发展出推论出随机森林的算法。

查看详情

维基百科版本

随机森林或随机决策森林是用于分类,回归和其他任务的集成学习方法,其通过在训练时构建多个决策树并输出作为类的模式(分类)或平均预测(回归)的类来操作。个别树木。随机决策森林纠正决策树过度拟合其训练集的习惯。

查看详情

扩展阅读

文科生也能看懂的机器学习教程2:决策树和随机森林

1. 基本介绍

随机森林(Random Forest,简称RF)是Bagging的一个扩展变体。RF在以决策树为基学习器构建Bagging集成的基础上,进一步在决策树的训练过程中引入了随机属性选择。
随机森林,是用随机的方式建立一个森林,森林里面有很多的决策树组成,随机森林的每一刻决策树之间是没有关联的。在得到森林之后,当有一个新的输入样本进入的时候,就让森林中的每一棵决策树分别进行一下判断,看看这个样本应该属于哪一类(对于分类算法),然后看看哪一类被选择最多,就预测这个样本为哪一类。
在建立每一棵决策树的过程中,有两点需要注意——采样与完全分裂。首先是两个随机采样的过程,random forest对输入的数据要进行行、列的采样。对于行采样,进行有放回的方式,也就是在采样得到的样本集合中,可能有重复的样本。假设输入样本为N个,那么采样的样本也为N个。这样使得在训练的时候,每一棵树的输入样本都不是全部的样本,使得相对不容易出现over-fitting。

2. 随机森林的分类

随机森林用于分类时,采用n个决策树分类,将分类结果用简单投票得到最终分类,提高准确率。
随机森林时对决策树的集成,其不同之处如下:

  1. 采样差异:从含m个样本的数据集中有放回采样,得到含m个样本的采样集用于训练,保证每个决策树的训练样本不完全一样;
  2. 特征选择差异:每个决策树的k个分类特征时所在特征中随机选择的(k需要调参)。

随机森林需要调整的参数:

  • 决策树的个数m;
  • 特征属性的个数k;
  • 递归次数(决策树的深度);

实现流程

  1. 导入数据并将特征转为float形式;
  2. 将数据集合分成几份,方便交叉验证;
  3. 构造数据子集(随机采样),并在指定特征个数(假设m个,调参)下选择最优特征;
  4. 构造决策树(决策树的深度);
  5. 创建随机森林(多个决策树的结合);
  6. 输入测试集并进行测试,输出测试结果。

3. 随机森林的优点

  • 在当前的很多数据集上,相对于其他算法有着很大的优势;
  • 能够处理高纬度(feature)的数据,并且不用做特征选择;
  • 在训练完后,能够给出哪些feature比较重要;
  • 创建随机森林时,对generalization error使用的是无偏估计;
  • 训练速度快;
  • 在训练过程中,能够监测到feature间的相互影响;
  • 容易做成并行化方法;
  • 理解、实现简单。

4. 随机森林的代码实现

在scikit-learn中,RF的分类类是RandomForestClassifier,回归类是RandomForestRegressor。当然RF的变种Extra Trees也有, 分类类ExtraTreesClassifier,回归类ExtraTreesRegressor。由于RF和Extra Trees的区别较小,调参方法基本相同,本文只关注于RF的调参。

RF框架参数:

  1. n_estimators: 也就是弱学习器的最大迭代次数,或者说最大的弱学习器的个数。一般来说n_estimators太小,容易欠拟合,n_estimators太大,计算量会太大,并且n_estimators到一定的数量后,再增大n_estimators获得的模型提升会很小,所以一般选择一个适中的数值。默认是100。在实际调参的过程中,我们常常将n_estimators和learning_rate一起考虑。
  2. oob_score :即是否采用袋外样本来评估模型的好坏。默认识False。个人推荐设置为True,因为袋外分数反应了一个模型拟合后的泛化能力。 oob_score :即是否采用袋外样本来评估模型的好坏。默认识False。个人推荐设置为True,因为袋外分数反应了一个模型拟合后的泛化能力。
  3. criterion: 即CART树做划分时对特征的评价标准。分类模型和回归模型的损失函数是不一样的。分类RF对应的CART分类树默认是基尼系数gini,另一个可选择的标准是信息增益。回归RF对应的CART回归树默认是均方差mse,另一个可以选择的标准是绝对值差mae。一般来说选择默认的标准就已经很好的。criterion: 即CART树做划分时对特征的评价标准。分类模型和回归模型的损失函数是不一样的。分类RF对应的CART分类树默认是基尼系数gini,另一个可选择的标准是信息增益。回归RF对应的CART回归树默认是均方差mse,另一个可以选择的标准是绝对值差mae。一般来说选择默认的标准就已经很好的。

从上面可以看出, RF重要的框架参数比较少,主要需要关注的是 n_estimators,即RF最大的决策树个数。

RF决策树参数:

  1. RF划分时考虑的最大特征数max_features: 可以使用很多种类型的值,默认是"None",意味着划分时考虑所有的特征数;如果是"log2"意味着划分时最多考虑个特征;如果是"sqrt"或者"auto"意味着划分时最多考虑个特征。如果是整数,代表考虑的特征绝对数。如果是浮点数,代表考虑特征百分比,即考虑(百分比xN)取整后的特征数。其中N为样本总特征数。一般来说,如果样本特征数不多,比如小于50,我们用默认的"None"就可以了,如果特征数非常多,我们可以灵活使用刚才描述的其他取值来控制划分时考虑的最大特征数,以控制决策树的生成时间。
  2. 决策树最大深度max_depth: 默认可以不输入,如果不输入的话,决策树在建立子树的时候不会限制子树的深度。一般来说,数据少或者特征少的时候可以不管这个值。如果模型样本量多,特征也多的情况下,推荐限制这个最大深度,具体的取值取决于数据的分布。常用的可以取值10-100之间。
  3. 内部节点再划分所需最小样本数min_samples_split: 这个值限制了子树继续划分的条件,如果某节点的样本数少于min_samples_split,则不会继续再尝试选择最优特征来进行划分。 默认是2.如果样本量不大,不需要管这个值。如果样本量数量级非常大,则推荐增大这个值。
  4. 叶子节点最少样本数min_samples_leaf: 这个值限制了叶子节点最少的样本数,如果某叶子节点数目小于样本数,则会和兄弟节点一起被剪枝。 默认是1,可以输入最少的样本数的整数,或者最少样本数占样本总数的百分比。如果样本量不大,不需要管这个值。如果样本量数量级非常大,则推荐增大这个值。
  5. 叶子节点最小的样本权重和min_weight_fraction_leaf:这个值限制了叶子节点所有样本权重和的最小值,如果小于这个值,则会和兄弟节点一起被剪枝。 默认是0,就是不考虑权重问题。一般来说,如果我们有较多样本有缺失值,或者分类树样本的分布类别偏差很大,就会引入样本权重,这时我们就要注意这个值了。叶子节点最小的样本权重
  6. 最大叶子节点数max_leaf_nodes: 通过限制最大叶子节点数,可以防止过拟合,默认是"None”,即不限制最大的叶子节点数。如果加了限制,算法会建立在最大叶子节点数内最优的决策树。如果特征不多,可以不考虑这个值,但是如果特征分成多的话,可以加以限制,具体的值可以通过交叉验证得到。
  7. 节点划分最小不纯度min_impurity_split: 这个值限制了决策树的增长,如果某节点的不纯度(基于基尼系数,均方差)小于这个阈值,则该节点不再生成子节点。即为叶子节点 。一般不推荐改动默认值1e-7。

上面决策树参数中最重要的包括最大特征数max_features, 最大深度max_depth, 内部节点再划分所需最小样本数min_samples_split和叶子节点最少样本数min_samples_leaf。 上面决策树参数中最重要的包括最大特征数max_features, 最大深度max_depth, 内部节点再划分所需最小样本数min_samples_split和叶子节点最少样本数min_samples_leaf。

随机森林分类器代码
测试数据下载

#导入需要的库
import pandas as pd
import numpy as np
from sklearn.ensemble import RandomForestClassifier
from sklearn.model_selection import GridSearchCV
from sklearn import metrics

import matplotlib.pylab as plt
%matplotlib inline

#解压数据,查看数据的类别分布
train = pd.read_csv('train_modified.csv')
target = 'Disbursed' #Disbursed的值就是二元分类的输出
IDcol = 'ID'
train['Disbursed'].value_counts()

#选择样本特征和类别输出
x_columns = [x for x in train.columns if x not in [target, IDcol]]
X = train[x_columns]
y = train['Disbursed']

#拟合数据
rf0 = RandomForestClassifier(oob_score=True, random_state=10)
rf0.fit(X, y)
print(rf0.oob_score_)
y_predprob = rf0.predict_proba(X)[:, 1]
print(y_predprob)
print("AUC Score (Train): %f" % metrics.roc_auc_score(y, y_predprob))

#对n_estimators进行网格搜索
param_test1 = {'n_estimators': range(10, 71, 10)}
gsearch2 = GridSearchCV(estimator=RandomForestClassifier(min_samples_split=100, 
                                                        min_samples_leaf=20, max_depth = 8,
                                                        max_features='sqrt', random_state=10),
                       param_grid=param_test1, scoring='roc_auc', cv=5)
gsearch2.fit(X, y)
gsearch2.grid_scores_, gsearch2.best_params_, gsearch2.best_score_

#对决策树最大深度max_depth和内部节点再划分所需最小样本min_samples_split进行网格搜索
param_test2 = {'max_depth':range(3,14,2), 'min_samples_split':range(50,201,20)}
gsearch2 = GridSearchCV(estimator=RandomForestClassifier(n_estimators=60, min_samples_leaf=20, max_features='sqrt', oob_score=True, random_state=10),
                       param_grid = param_test2, scoring='roc_auc', iid=False, cv=5)
gsearch2.fit(X, y)
gsearch2.grid_scores_, gsearch2.best_params_, gsearch2.best_score_

#查看模型的袋外分数
rf1 = RandomForestClassifier(n_estimators=60, max_depth=13, min_samples_split=110, min_samples_leaf=20, max_features='sqrt', oob_score=True, random_state=10)
rf1.fit(X, y)
print(rf1.oob_score_)

#0.984袋外分数提高,即模型的泛化能力增强
#对内部节点再划分所需最小样本书min_samples_split和叶子节点最少样本数min_samples_leaf一起调参
param_test3 = {'min_samples_split':range(80,150,20), 'min_samples_leaf':range(10,60,10)}
gsearch3 = GridSearchCV(estimator=RandomForestClassifier(n_estimators=60, max_depth=13, max_features='sqrt', oob_score=True, random_state=10),
                        param_grid=param_test3, scoring='roc_auc', iid=False, cv=5)
gsearch3.fit(X, y)
gsearch3.grid_scores_, gsearch3.best_params_, gsearch3.best_score_

#对最大特征数max_features做调参
param_test4 = {'max_features':range(3,11,2)}
gsearch4 = GridSearchCV(estimator=RandomForestClassifier(n_estimators=60, max_depth=13, min_samples_split=120, min_samples_leaf=20, oob_score=True, random_state=10),
                       param_grid = param_test4, scoring='roc_auc', iid=False, cv=5)
gsearch4.fit(X, y)
gsearch4.grid_scores_, gsearch4.best_params_, gsearch4.best_score_

#最终模型的拟合
rf2 = RandomForestClassifier(n_estimators=60, max_depth=13, min_samples_split=120, min_samples_leaf=20, max_features=7, oob_score=True, random_state=10)
rf2.fit(X, y)
print(rf2.oob_score_)

随机森林回归代码:

# 导入需要的库
import pandas as pd
import numpy as np
from sklearn.ensemble import RandomForestRegressor
from sklearn.model_selection import GridSearchCV
from sklearn import metrics

import matplotlib.pyplot as plt
%matplotlib inline

# 解压数据
train = pd.read_csv('data_processing.csv')
target = 'score'
IDcol = 'client_code'
x_columns = [i for i in train.columns if i not in [target, IDcol]]
x = train[x_columns]
y = train['score']

# 拟合数据
rf0 = RandomForestRegressor(oob_score=True, random_state=10)
rf0.fit(x, y)
print("oob_score: %f" % rf0.oob_score_)
y_score = rf0.score(x, y)
print("Score: %f" % y_score)

# 对n_estimators进行网格搜索
num = []
oob_score = []
score = []
for i in range(30, 45):
    rf1 = RandomForestRegressor(oob_score=True, random_state=10, n_estimators=i)
    rf1.fit(x, y)
    oob_score.append(rf1.oob_score_)
    num.append(i)
    score.append(rf1.score(x, y))
plt.figure()
plt.plot(num, score, label='score')
plt.plot(num, oob_score, color='r', label='oob_score')
plt.xlabel('n_estimators')
plt.ylabel('score')
plt.legend()
plt.show()

# 对最大深度max_depth进行网格搜索
num.clear()
score.clear()
oob_score.clear()
for i in range(6, 19, 2):
    rf2 = RandomForestRegressor(oob_score=True, random_state=10, n_estimators=36, max_depth=i)
    rf2.fit(x, y)
    num.append(i)
    score.append(rf2.score(x, y))
    oob_score.append(rf2.oob_score_)
    
plt.figure()
plt.plot(num, score, label='score')
plt.plot(num, oob_score, color='r', label='oob_score')
plt.xlabel('max_depth')
plt.ylabel('score')
plt.legend()
plt.show()

# 对内部节点再划分所需最小样本min_samples_split进行网格搜索
num.clear()
score.clear()
oob_score.clear()
for i in range(2, 11): 
    rf3 = RandomForestRegressor(oob_score=True, random_state=10, n_estimators=36, max_depth=14, min_samples_split=i)
    rf3.fit(x, y)
    num.append(i)
    score.append(rf3.score(x, y))
    oob_score.append(rf3.oob_score_)
    
plt.figure()
plt.plot(num, score, label='score')
plt.plot(num, oob_score, color='r', label='oob_score')
plt.xlabel('min_samples_split')
plt.ylabel('score')
plt.legend()
plt.show()

# 对最大特征数max_features进行调参
num.clear()
score.clear()
oob_score.clear()
for i in range(3, 21, 2):
    rf4 = RandomForestRegressor(oob_score=True, random_state=10, n_estimators=36, max_depth=14, min_samples_split=2, max_features=i)
    rf4.fit(x, y)
    num.append(i)
    score.append(rf4.score(x, y))
    oob_score.append(rf4.oob_score_)
    
plt.figure()
plt.plot(num, score, label='score')
plt.plot(num, oob_score, color='r', label='oob_score')
plt.xlabel('max_features')
plt.ylabel('score')
plt.legend()
plt.show()

#最终结果
rf5 = RandomForestRegressor(oob_score=True, random_state=10, n_estimators=36, max_depth=14, min_samples_split=2, max_features=13)
rf5.fit(x, y)
print(rf5.oob_score_)
print(rf5.score(x, y))

5. 参考

  1. 《机器学习》—— 周志华;
  2. [Machine Learning & Algorithm] 随机森林(Random Forest);
  3. 机器学习之随机森林(简单理解);
  4. scikit-learn随机森林调参小结。