除了引导和随机抽样特征之外,是什么让随机森林变得随机?

机器算法验证 机器学习 Python 随机森林 scikit-学习 监督学习
2022-03-18 15:44:31

在阅读了原始论文教科书中关于随机森林的内容后,我的印象是,使模型随机的原因是自举——在通过替换绘制的不同随机观察子集上训练每棵树——以及特征的随机子采样(有时称为“特征bootstrapping) - 使每个拆分只考虑有限数量的随机选择的特征。

然而,在 Scikit-Learn 中玩弄随机森林让我质疑这个假设。在 Scikit-Learn 中使用随机森林时,您可以禁用自举并且不使用特征的随机子采样。按照上述逻辑,这应该使森林中的所有树都相同,而没有这些特征的两个随机森林应该产生相同的预测。

然而,在不引导观察或对特征进行二次采样的情况下创建多个模型会导致森林中的树木不同,并且会产生不相等的预测。除了观察采样和特征子采样之外,还有什么使随机森林变得随机?

这是我用来测试两个具有bootstrap=Falsemax_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之前的模型没有最大深度相比)并将它们可视化显示了同一森林中回归树之间的差异:

树1

树2

这两棵树(来自同一个森林)在节点 #9 中不一致,这导致对同一测试点的不同预测。(如果有帮助,我可以提供可视化代码)。

我的问题是:除了观察的随机抽样(自举)和用于在每个节点进行分割的特征的随机子抽样之外,还有什么使随机森林变得随机?如果这两个功能被禁用,那么为什么所有的树都不完全相同?这只是 Scikit-Learn 实现的一个特性吗?

1个回答

如果我们撇开舍入误差引起的差异,剩下的差异源于对关系的处理。sklearn.ensemble.RandomForestClassifier由许多实例组成sklearn.tree.DecisionTreeClassifier(您可以通过阅读源代码来验证这一点)。如果我们阅读 的文档sklearn.tree.DecisionTreeClassifier,我们会发现树的构建方式存在一些不确定性,即使在使用所有功能时也是如此。这是因为该fit方法如何处理关系。

每次拆分时,特征总是随机排列。因此,即使在相同的训练数据和 max_features=n_features 的情况下,如果在搜索最佳拆分期间列举的几个拆分的标准改进相同,则找到的最佳拆分可能会有所不同。为了在拟合期间获得确定性行为,必须固定 random_state。


在大多数情况下,这是舍入误差。每当比较浮点数的相等性时,您都想使用类似的东西np.isclose,而不是==使用==是疯狂的方式。

import numpy as np
np.isclose(pred_1, pred_2)
array([ True,  True,  True,  True,  True,  True,  True,  True,  True,
        True,  True,  True,  True,  True,  True,  True,  True,  True,
        True,  True,  True,  True,  True,  True,  True,  True,  True,
        True,  True,  True,  True,  True,  True,  True, False,  True,
        True,  True,  True,  True,  True,  True,  True,  True,  True,
        True,  True,  True,  True,  True,  True,  True,  True,  True,
        True,  True,  True,  True,  True,  True,  True,  True,  True,
        True,  True,  True,  True,  True,  True,  True,  True,  True,
        True,  True,  True,  True,  True,  True,  True,  True,  True,
        True,  True,  True,  True,  True,  True,  True,  True,  True,
        True,  True,  True,  True,  True,  True,  True,  True,  True,
        True])

出于某种原因,只有第 34 个条目以某种方式不匹配,而这不是由数字错误引起的。

mistake = np.where(np.logical_not(np.isclose(pred_1, pred_2)))
mistake
# array([34])
pred_1[mistake]
# array([33.54285714])
pred_2[mistake]
# array([31.82857143])

如果我修复了用于模型的种子,这种差异就会消失。如果您选择另一对种子,它可能会重新出现。我不知道。

model3 = RandomForestRegressor(bootstrap=False, max_features=1.0, max_depth=3, random_state=13)
model4 = RandomForestRegressor(bootstrap=False, max_features=1.0, max_depth=3, random_state=14)

pred_3 = model3.fit(X_train, y_train).predict(X_test)
pred_4 = model4.fit(X_train, y_train).predict(X_test)
np.isclose(pred_3, pred_4).all()
# True

另请参阅:决策树模型如何在 scikit-learn 中选择阈值?