随机森林(Random Forest) -- 原理及Python实现

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)}
gsearch1 = 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)
gsearch1.fit(X, y)
gsearch1.grid_scores_, gsearch1.best_params_, gsearch1.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随机森林调参小结。

你可能感兴趣的:(机器学习)