为什么 `max_features=n_features` 不会使随机森林与树的数量无关?

数据挖掘 Python 随机森林 决策树
2021-10-10 08:41:26

考虑以下简单的分类问题(Python,scikit-learn)

import pandas as pd
import numpy as np
import matplotlib.pyplot as plt

from sklearn.model_selection import train_test_split
from sklearn.ensemble import RandomForestClassifier
from sklearn.metrics import accuracy_score

def get_product_data(size):
    '''
    Given a size(int), sets `log10(size)` features to be uniform 
    random variables `Xi` in [-1,1] and an target `y` given by 1 if 
    their product `P` is larger than 0.0 and zero otherwise. 
    Returns a pandas DataFrame.
    '''
    n_features = int(max(2, np.log10(size)))
    features = dict(('x%d' % i, 2*np.random.rand(size) - 1) for i in range(n_features))
    y = np.prod(list(features.values()), axis=0)
    y = y > 0.0
    features.update({'y': y.astype(int)})
    return pd.DataFrame(features)

# create random data
df = get_product_data(1000)

X = np.array(df.drop(df.columns[-1], axis=1))
y = df['y']

X_train, X_test, y_train, y_test = train_test_split(X, y, test_size=0.33, 
                                                        random_state=1)    

def predict(clf):
    '''
    Splits train/test with a fixed seed, fits, and returns the accuracy
    '''
    clf.fit(X_train, y_train)
    return accuracy_score(y_test, clf.predict(X_test))

和以下分类器:

foo10 = RandomForestClassifier(10, max_features=None, bootstrap=False)
foo100 = RandomForestClassifier(100, max_features=None, bootstrap=False)
foo200 = RandomForestClassifier(200, max_features=None, bootstrap=False)

为什么

predict(foo10)  # 0.906060606061
predict(foo100)  # 0.933333333333
predict(foo200)  # 0.915151515152

给不同的分数?

具体来说,与

  1. max_features=None,为每棵树选择所有特征
  2. bootstrap=False,没有样本的引导
  3. max_depth=None(默认),所有树都达到最大深度

我希望每棵树都完全相同。因此,无论森林有多少棵树,预测应该是相等的。在这个例子中,树的可变性来自哪里?

我必须以具有相同分数RandomForestClassifier.__init__的方式引入哪些进一步的参数?foo*

2个回答

确实很有趣的谜题。

第一件事。DecisionTreeClassifier一些随机行为。例如,拆分器代码随机迭代特征:

        f_j = rand_int(n_drawn_constants, f_i - n_found_constants,
                       random_state)

您的数据很小并且来自相同的分布。这意味着您将获得许多相同的纯度分数,具体取决于迭代的完成方式。如果您 (a) 增加数据,或 (b) 使其更加可分离,您会发现问题应该得到改善。

澄清一下:如果算法计算特征 A 的分数,然后计算特征 B 的分数并得到分数 N。或者,如果它首先计算特征 B 的分数,然后计算特征 A 的分数并且得到相同的分数 N,你可以看到每个决策树将如何不同,并且在测试期间具有不同的分数,即使训练测试相同(当然,如果 max_depth=None 则为 100%)。(您可以确认这一点。)

在探索您的问题期间,我使用自己的随机森林实现生成了以下代码。由于花了我一些时间,我想我不妨把它贴在这里。:) 说真的,它可能很有用。您可以尝试random_state从我的实现中禁用以了解我的意思。

from sklearn.model_selection import train_test_split
from sklearn.ensemble import RandomForestClassifier
from sklearn.tree import DecisionTreeClassifier
from sklearn.metrics import accuracy_score
import numpy as np


class MyRandomForestClassifier:
    def __init__(self, n_estimators):
        self.n_estimators = n_estimators

    def fit(self, X, y):
        self.trees = [DecisionTreeClassifier(random_state=1).fit(X, y)
                      for _ in range(self.n_estimators)]
        return self

    def predict(self, X):
        yp = [tree.predict(X) for tree in self.trees]
        return ((np.sum(yp, 0) / len(self.trees)) > 0.5).astype(int)

    def score(self, X, y):
        return accuracy_score(y, self.predict(X))


for alpha in (1, 0.1, 0.01):
    np.random.seed(1)
    print('# alpha: %s' % str(alpha))
    N = 1000
    X = np.random.random((N, 10))
    y = np.r_[np.zeros(N//2, int), np.ones(N//2, int)]
    X[y == 1] = X[y == 1]*alpha
    Xtr, Xts, ytr, yts = train_test_split(X, y)

    print('## sklearn forest')
    for n_estimators in (1, 10, 100, 200, 500):
        m = RandomForestClassifier(
            n_estimators, max_features=None, bootstrap=False)
        m.fit(Xtr, ytr)
        print('%3d: %.4f' % (n_estimators, m.score(Xts, yts)))

    print('## my forest')
    for n_estimators in (1, 10, 100, 200, 500):
        m = MyRandomForestClassifier(n_estimators)
        m.fit(Xtr, ytr)
        print('%3d: %.4f' % (n_estimators, m.score(Xts, yts)))
    print()

总结:每一个DecisionTreeClassifier都是随机的,像你这样的数据很小并且来自相同的分布,即使随机森林本身是确定性的,也必然会产生略有不同的树。您可以通过将相同的种子传递给每个DecisionTreeClassifier您可以使用random_state=something. RandomForestClassifier也有一个random_state参数,它沿着 each 传递DecisionTreeClassifier(这有点不正确,请参阅编辑。)

EDIT2:虽然这消除了训练的随机性部分,但决策树仍然会有所不同。问题是 sklearn 集成根据给定的随机状态为每个孩子生成一个新的随机种子。他们不通过相同的random_state

_set_random_states通过检查ensemble 基本模块中的方法,您可以看到这种情况,特别是这一行,它在 ensemble 的子模块中传播random_state

从里卡多克鲁兹那里得到答案

原因是这DecisionTreeClassifier不是确定性分类器。具体来说,当在两个导致度量相同下降的特征之间进行拆分时,DecisionTreeClassifier随机选择一个。问题中观察到的波动就是由此引起的。

这一观察结果的推论是 scikit-learn 的随机森林将引导程序用于 3 种不同的事情:

  • 项目(样品不同的项目)
  • 特征(样本不同的特征)
  • 分裂(采样不同的分裂)

冻结items(使用bootstrap=False)和features(使用max_depth=None)不足以冻结splitting冻结的唯一方法splitting是使用固定的random_state.

请注意,random_state单独使用它并不足以冻结所有内容,即

foo10 = RandomForestClassifier(10, random_state=1)
foo100 = RandomForestClassifier(100, random_state=1)
foo200 = RandomForestClassifier(200, random_state=1)

不要给相同的分数。

因此,保证分类器对任何分类器给出相同结果的唯一方法n_estimators是将其初始化为

RandomForestClassifier(n_estimators, max_features=None, bootstrap=False, random_state=1)

这对分类本身并不是特别有帮助,但可以更好地理解幕后所做的事情。