在阅读了原始论文和教科书中关于随机森林的内容后,我的印象是,使模型随机的原因是自举——在通过替换绘制的不同随机观察子集上训练每棵树——以及特征的随机子采样(有时称为“特征bootstrapping) - 使每个拆分只考虑有限数量的随机选择的特征。
然而,在 Scikit-Learn 中玩弄随机森林让我质疑这个假设。在 Scikit-Learn 中使用随机森林时,您可以禁用自举并且不使用特征的随机子采样。按照上述逻辑,这应该使森林中的所有树都相同,而没有这些特征的两个随机森林应该产生相同的预测。
然而,在不引导观察或对特征进行二次采样的情况下创建多个模型会导致森林中的树木不同,并且会产生不相等的预测。除了观察采样和特征子采样之外,还有什么使随机森林变得随机?
这是我用来测试两个具有bootstrap=False和max_features=1.0(使用所有特征)的模型是否在 Scikit-Learn 中做出相同预测的代码。
# Use Boston housing regression dataset
from sklearn.datasets import load_boston
boston = load_boston()
import pandas as pd
X = pd.DataFrame(data=boston.data, columns=boston.feature_names)
y= pd.Series(data=boston.target)
# Split into training and testing
from sklearn.model_selection import train_test_split
X_train, X_test, y_train, y_test = train_test_split(X, y,
random_state=100, test_size=100)
from sklearn.ensemble import RandomForestRegressor
# Make two random forests with no bootstrapping and using all features
model1 = RandomForestRegressor(bootstrap=False, max_features=1.0, max_depth=None)
model2 = RandomForestRegressor(bootstrap=False, max_features=1.0, max_depth=None)
# Make predictions with both models
pred_1 = model1.fit(X_train, y_train).predict(X_test)
pred_2 = model2.fit(X_train, y_train).predict(X_test)
# Test predictions for equality
import numpy as np
np.allclose(pred_1, pred_2)
# Output
False
# Look at predictions which disagree
not_close = np.where(~np.isclose(pred_1, pred_2))
pred_1[not_close]
pred_2[not_close]
#Output
array([29.43, 24.34, 18.39, 19.37, 23.64, 28.22, 21.71, 20.08, 12.54,
24.71, 26.05, 22.19, 28.29, 22.39, 20.12, 35.41, 47.78, 31.07,
15. , 12.11, 13.52, 5.81, 13.96, 25.82, 16.27, 11.42, 16.4 ,
16.2 , 20.08, 43.53, 24.74, 34.4 , 43.37, 7.84, 13.43, 20.17,
18.82, 22.97, 16.32, 23.03, 24.26, 28.91, 17.64, 12.64, 11.56,
16.4 , 20.34, 21.61, 25.3 , 14.37, 34.12, 33.76, 7.94, 20.35,
14.63, 35.05, 24.39, 16.16, 31.44, 20.28, 10.9 , 7.34, 32.72,
10.91, 11.21, 21.96, 41.65, 14.77, 12.84, 16.27, 14.72, 22.34,
14.44, 17.53, 31.16, 22.66, 23.84, 24.7 , 16.16, 13.91, 30.33,
48.12, 12.61, 45.58])
array([29.66, 24.5 , 18.34, 19.39, 23.56, 28.34, 21.78, 20.03, 12.91,
24.73, 25.62, 21.49, 28.36, 22.32, 20.14, 35.14, 48.12, 31.11,
15.56, 11.84, 13.44, 5.77, 13.9 , 25.81, 16.12, 10.81, 17.15,
16.18, 20.1 , 41.78, 25.8 , 34.5 , 45.58, 7.65, 12.64, 20.04,
18.78, 22.43, 15.92, 22.87, 24.28, 29.2 , 17.58, 12.03, 11.49,
17.15, 20.25, 21.58, 26.05, 12.97, 33.98, 33.94, 8.26, 20.09,
14.41, 35.19, 24.42, 16.18, 31.2 , 20.5 , 13.61, 7.36, 32.18,
10.39, 11.07, 21.9 , 41.98, 15.12, 13.12, 16.12, 15.32, 20.84,
14.49, 17.51, 31.39, 23.46, 23.75, 24.71, 16.42, 13.19, 29.4 ,
48.46, 12.91, 38.95])
(感谢@Sycorax 建议使用np.allclose()来比较预测。)
如果random_state两个模型的 都是固定的,那么预测结果完全相同。这意味着模型的一个方面仍然是随机的。
我还认为所有的树都是相同的,因为它们训练的示例或他们在进行拆分时考虑的特征之间没有区别。但是,将树的深度限制为 3(与max_depth = 3之前的模型没有最大深度相比)并将它们可视化显示了同一森林中回归树之间的差异:
这两棵树(来自同一个森林)在节点 #9 中不一致,这导致对同一测试点的不同预测。(如果有帮助,我可以提供可视化代码)。
我的问题是:除了观察的随机抽样(自举)和用于在每个节点进行分割的特征的随机子抽样之外,还有什么使随机森林变得随机?如果这两个功能被禁用,那么为什么所有的树都不完全相同?这只是 Scikit-Learn 实现的一个特性吗?

