随机森林random forest是一种有监督学习算法,既可用于分类,也能用于回归。本质上使用bagging的思路创建许多决策树,以达到预测目的。
背景知识
1、决策树 decision tree
- 核心思想:divide-and-conquer 分而治之
简单理解
假设一个训练集(m个样本,n个特征变量,1个响应变量)。而决策树算法是将训练集按照某个特征的某个阈值(yes/no),切割得到两个各自(响应变量)内部相似性很高的子样本集;再对每个子样本集重复上述的步骤(binary recursive partitioning),直至达到预设的条件为止(suitable stopping criterion)
如上有两个注意点:
(1)如何选择针对当前样本集的最佳特征变量的切割阈值
最佳特征变量的切割阈值会将当前样本集切割为两个各自内部响应变量相似度很高的子样本集。针对分类与回归问题,分别有不同的指标。
-
对于分类问题:具体指标为基尼系数。若子样本集中存在很大部分相同标签(responsible value),则不纯度很低。
对于预测结果来说:Leaf node样本标签的众数即为预测标签值;其所占的比例就是预测标签的概率值。
注:Pj表示:子样本集中,标签j所占的比例
- 对于回归问题:具体指标为SSE(误差平方和)。若子样本中的响应变量值基本都在一个确定值范围内波动,则不纯度低。
对于预测结果来说:Leaf node样本响应变量值的平均数即为预测的值。
注:C1、C2分别表示分得的子样本集响应变量的均值
(2)所谓达到预设的条件的意思
如上所说,并且结合下图,可以总结:训练一棵树就是将Root node(训练数据集)根据选择的切割指标最终得到许多个Leaf node(最终子数据集)的过程。
但如果进行无限的binary recursive partitioning,最终m个样本的数据集会得到m个节点(terminal node)。这样决策树很有可能会过拟合。可通过以下3种方法进行限制
- ① 直接限定决策树的深度,而深度的定义为:terminal node到root node的最长距离(所经历的决策次数)
- ② 限制terminal node的size,即terminal node至少要含有指定数目的样本
- ③ Pruning 修枝剪叶:先训练得到一个超级复杂的树(指定一个很大的深度),然后进行“修剪”得到一个最佳小树的过程(类比正则化回归)。
2、Bagging (Bootstrap aggregating)
- (1)核心思想:wisdom of the crowd 群体的智慧
- (2)简单理解:对于一个训练数据集(m个样本),采用Bootstrap方法进行有放回的抽样,训练得到n个模型(base learner)。综合这n个模型的预测结果进行最终的判断(对于分类问题,取众数; 对于回归问题,取均数),从而避免单个模型的过拟合问题,降低方差,提高预测准确率。
- (3)关键:由于是有放回的抽样,所以训练n个模型所各自基于的样本是有差别的,因此各个模型是有偏好的,否则多个完全一样的模型的综合结果是没有意义的。
bagging是集成学习的一种,常与决策树搭配,即下面将要学习的随机森林。
随机森林
1、简单理解
在上面了解了决策树与bagging之后,随机森林就十分好理解了。
-
随机森林简单就是使用Bagging思路,建立许多个决策树(弱相关),根据这些决策树的综合预测结果进行回归或者分类。
- 随机森林在每次建树之前会经历两次抽样过程。首先是基于样本的抽样(一般有放回);其次是基于特征变量的抽样(随机取部分特征变量)。这样就保证了建立每一棵决策树的数据都有一定程度的差异性,然后综合预测结果会更可靠。
- 由于随机森林相对来说是一种较为复杂的算法,所以在实际训练过程中有多种超参数可供调整,具体将在代码实操中演示。
2、代码实操
- 示例数据:房价预测(回归)
ames <- AmesHousing::make_ames()
dim(ames)
## [1] 2930 81
set.seed(123)
library(rsample)
split <- initial_split(ames, prop = 0.7,
strata = "Sale_Price")
ames_train <- training(split)
# [1] 2049 81
ames_test <- testing(split)
# [1] 881 81
- 主要R包
# Modeling packages
library(ranger) # a c++ implementation of random forest
2.1 默认参数训练随机森林
# number of features
n_features <- length(setdiff(names(ames_train), "Sale_Price"))
# train a default random forest model
ames_rf1 <- ranger(
Sale_Price ~ .,
data = ames_train,
mtry = floor(n_features / 3),
respect.unordered.factors = "order",
seed = 123)
# get OOB RMSE
default_rmse <- sqrt(ames_rf1$prediction.error)
default_rmse
# [1] 25488.39
- 关于OOB(Out of bag) RMSE
随机森林算法的优势之一在于其不需要另外的交叉验证。因为是有放回的抽样,对于一个样本来说,总有一部分决策树(1/e的概率)是没有用到该样本的。所以可用那些决策树构成的森林的预测结果与该样本的真实结果比较,从而计算RMSE
2.2 随机森林的超参数
- (1)决策树的数量
建议选择特征变量数目的10倍作为训练决策树的数量;
当然决策树越多,结果越稳定,但消耗的时间也会更长。 - (2)特征变量的选择
对于回归问题:建议取特征变量总数的三分之一;对于分类问题,建议取特征变量总数的平方根。 - (3)决策树复杂度
常用的方式是限定terminal node size的样本数量(one for classification and five for regression) - (4)样本抽样策略
首先是否采用有放回的抽样方法,其次抽样的数目占总样本数目的多少
在这四种超参数中,后两种对模型性能的影响其实很小,但也不妨一试。
step1:使用full Cartesian grid searches完全笛卡尔积的方式,寻找最佳超参数组合
# create hyperparameter grid
hyper_grid <- expand.grid(
mtry = floor(n_features * c(.05, .15, .25, .333, .4)),
min.node.size = c(1, 3, 5, 10),
replace = c(TRUE, FALSE),
sample.fraction = c(.5, .63, .8),
rmse = NA)
# 共有5*4*2*3=120种组合
dim(hyper_grid)
# [1] 120 5
# execute full cartesian grid search
for(i in seq_len(nrow(hyper_grid))) {
# fit model for ith hyperparameter combination
fit <- ranger(
formula = Sale_Price ~ .,
data = ames_train,
num.trees = n_features * 10,
mtry = hyper_grid$mtry[i],
min.node.size = hyper_grid$min.node.size[i],
replace = hyper_grid$replace[i],
sample.fraction = hyper_grid$sample.fraction[i],
verbose = FALSE,
seed = 123,
respect.unordered.factors = 'order',
)
# export OOB error
hyper_grid$rmse[i] <- sqrt(fit$prediction.error)
}
如下为Top10 超参数组合;其中第6列表示相对于默认超参数模型,该超参数组合模型的RMSE下降的百分比。
hyper_grid %>%
arrange(rmse) %>%
mutate(perc_gain = (default_rmse - rmse) / default_rmse * 100) %>%
head(10)
# mtry min.node.size replace sample.fraction rmse perc_gain
# 1 26 1 FALSE 0.8 24713.06 3.041873
# 2 26 3 FALSE 0.8 24847.98 2.512570
# 3 20 3 FALSE 0.8 24917.05 2.241554
# 4 20 1 FALSE 0.8 24929.10 2.194284
# 5 32 5 FALSE 0.8 24940.14 2.150967
# 6 32 1 FALSE 0.8 24978.78 1.999392
# 7 32 3 FALSE 0.8 24990.83 1.952085
# 8 26 5 FALSE 0.8 25004.10 1.900044
# 9 20 5 FALSE 0.8 25028.46 1.804464
# 10 12 1 FALSE 0.8 25029.93 1.798693
step2:采用最佳超参数组合建立随机森林模型
best_grid = hyper_grid %>% arrange(rmse) %>% head(1)
fit <- ranger(
formula = Sale_Price ~ .,
data = ames_train,
num.trees = n_features * 10,
mtry = best_grid$mtry,
min.node.size = best_grid$min.node.size,
replace = best_grid$replace,
sample.fraction = best_grid$sample.fraction,
verbose = FALSE,
seed = 123,
respect.unordered.factors = 'order')
sqrt(fit$prediction.error)
# [1] 24713.06
step3 测试集的数据预测
pred = predict(fit, ames_test)
ModelMetrics::rmse(ames_test$Sale_Price, pred$predictions)
# [1] 22258.24
2.3 衡量特征变量重要性
- 方式1:在建立决策树提到了不纯度的概念。当基于该特征的阈值分割,使得样本在切割前后的总体不纯度下降越高,则表明该特征越重要。
rf_impurity <- ranger(
formula = Sale_Price ~ .,
data = ames_train,
num.trees = 2000,
mtry = 32,
min.node.size = 1,
sample.fraction = .80,
replace = FALSE,
importance = "impurity", #基于不纯度计算特征重要性
respect.unordered.factors = "order",
verbose = FALSE,
seed = 123)
vip::vip(rf_impurity, num_features = 15, bar = FALSE)
- 方式2:在计算随机森林模型的基于OOB的RMSE时,如果某一特征值被随意修改,造成模型的准确率下降很多,则该特征越重要。
rf_permutation <- ranger(
formula = Sale_Price ~ .,
data = ames_train,
num.trees = 2000,
mtry = 32,
min.node.size = 1,
sample.fraction = .80,
replace = FALSE,
importance = "permutation",
respect.unordered.factors = "order",
verbose = FALSE,
seed = 123)
vip::vip(rf_permutation, num_features = 15, bar = FALSE)
虽然上述两种衡量方式的结果存在一定差异,但可以看出Top3 important feature还是一致的,说明这三种变量对于房价预测是很重要的。
网友评论