如何在多核上将 RandomState 与 Sklearn RandomizedSearchCV 一起使用

How to use RandomState with Sklearn RandomizedSearchCV on multiple cores

我对在多核上使用 np.random.RandomStatesklearn.model_selection.RandomizedSearchCV 时 运行 的正确方法感到困惑。

我使用 RandomState 生成伪随机数,以便我的结果可重现。我给 RandomizedSearchCV 一个 RandomState 的实例并设置 n_jobs=-1 以便它使用所有六个核心。

运行 在多核上引入了异步元素。我希望这会导致在不同的运行中以不同的顺序从各个内核请求伪随机数。因此,不同的运行应该给出不同的结果,而不是显示可重复性。

但实际上结果是可以重现的。对于给定的 n_iter 值(即参数 space 的抽取次数),找到的最佳超参数值与下一个 运行 相同。如果 n_jobs 是小于核心数的正数,我也会得到相同的值。

具体来说,这里是代码:

import numpy as np
import scipy.stats as stats
from sklearn.datasets import load_iris
from sklearn.ensemble import GradientBoostingClassifier
from sklearn.model_selection import RandomizedSearchCV, StratifiedKFold, train_test_split

# Use RandomState for reproducibility.
random_state = np.random.RandomState(42)

# Get data. Split it into training and test sets.
iris = load_iris()
X, y = iris.data, iris.target
X_train, X_test, y_train, y_test = train_test_split(
    X, y, test_size=0.4, random_state=random_state, stratify=y)

# Prepare for hyper-parameter optimization.
n_iter = 1_000

base_clf = GradientBoostingClassifier(
    random_state=random_state, max_features='sqrt')

param_space = {'learning_rate': stats.uniform(0.05, 0.2),
               'n_estimators': [50, 100, 200],
               'subsample': stats.uniform(0.8, 0.2)}

# Generate data folds for cross validation.
skf = StratifiedKFold(n_splits=5, shuffle=True, random_state=random_state)

# Create the search classifier.
search_clf = RandomizedSearchCV(
    base_clf, param_space, n_iter=n_iter, scoring='f1_weighted', n_jobs=-1, 
    cv=skf, random_state=random_state, return_train_score=False)

# Optimize the hyper-parameters and print the best ones found.
search_clf.fit(X_train, y_train)
print('Best params={}'.format(search_clf.best_params_))

我有几个问题。

  1. 为什么我在异步方面得到了可重现的结果?

  2. RandomizedSearchCV的文档说了random_state参数:"Pseudo random number generator state used for random uniform sampling from lists of possible values instead of scipy.stats distributions."这是否意味着它不影响参数[=76=中的分布]?上面的代码是否足以确保可重现性,或者我是否需要设置 np.random.seed(),或者写这样的东西:

    distn_learning_rate = stats.uniform(0.05, 0.2)  
    distn_learning_rate.random_state = random_state  
    distn_subsample = stats.uniform(0.8, 0.2)  
    distn_subsample.random_state = random_state  
    param_space = {'learning_rate': distn_learning_rate,  
                   'n_estimators': [50, 100, 200],  
                   'subsample': distn_subsample}  
    
  3. 总的来说,这是为重现性设置 RandomizedSearchCV 的正确方法吗?

  4. 使用 RandomState 的单个实例没问题,还是我应该为 train_test_splitGradientBoostingClassifierStratifiedKFold 和 [ 使用单独的实例=15=]?另外,np.random.seed 的文档说种子是在 RandomState 初始化时设置的。这如何与 RandomizedSearchCV 设置种子交互?

  5. n_jobs 设置为使用少于所有内核时,我仍然在所有内核上看到 activity,尽管每个内核的使用级别增加并且经过的时间随着核心数量的增加而减少。这只是 sklearn and/or macOS 优化机器使用吗?

我正在使用 macOS 10.14.2、Python 3.6.7、Numpy 1.15.4、Scipy 1.1.0 和 Sklearn 0.20.1。

候选参数是在使用 ParameterSampler object 传递给多线程功能之前生成的。因此,只有一个 random_state 就足以实现 RandomizedSearchCV 的可重复性。

注意我说的是"reproducibility of RandomizedSearchCV"。对于其中使用的估算器(此处为base_clf),每个估算器都应像您所做的那样携带自己的random_state

现在说a single instance of RandomState,对于顺序的代码来说完全没问题。唯一需要担心的情​​况是多处理开始时。所以让我们分析一下程序执行过程中发生的步骤。

  1. 您设置了一个带有种子的 RandomState 对象。它现在有一个状态。
  2. train_test_split中,使用了一个StratifiedShuffleSplit(因为你使用了stratify参数),它将使用传递的RandomState对象在train中拆分和生成排列和测试数据。所以 RandomState 的内部状态现在改变了。但它是连续的,不用担心。
  3. 现在您在 skf 中设置此 random_state 对象。但是在调用 RandomizedSearchCV 中的 fit() 之前不会发生拆分。所以状态不变。
  4. 之后,调用search_clf.fit时,the following happens:

      执行
    1. _run_search(),会使用random_state一次生成所有的参数组合(根据给定的n_iters)。所以仍然没有多线程的部分发生,一切都很好。
    2. evaluate_candidates() 被调用。有趣的部分是:

      out = parallel(delayed(_fit_and_score)(clone(base_estimator),
                                                 X, y,
                                                 train=train, test=test,
                                                 parameters=parameters,
                                                 **fit_and_score_kwargs)
                         for parameters, (train, test)
                         in product(candidate_params,
                                    cv.split(X, y, groups)))
      
    3. parallel(delayed(_fit_and_score)之后的部分仍然是顺序的,由父线程处理。

      • cv.split() 将使用 random_state(更改其状态)生成训练测试拆分
      • clone(estimator) 将克隆估算器的所有参数(random_state 也是)。因此 RandomStatecv.split 对象的改变状态成为 estimator
      • 中的基本状态
      • 以上两个步骤从父线程(无异步)发生多次(拆分次数 x 参数组合次数)。并且每次克隆原始 RandomState 来为估算器服务。所以结果是可重现的。
      • 所以在真正开始多线程部分的时候,并没有使用原来的RandomState,而是每个estimator(线程)都会有自己的一份RandomState

希望这是有道理的,并回答你的问题。 Scikit-learn explicitly requests the user 设置如下:

import numpy as np
np.random.seed(42)

使整个执行过程可重现,但你正在做的也会做。

我不能完全确定你的最后一个问题,因为我无法在我的系统上重现它。我有 4 个核心,当我设置 n_jobs=23 时,我只看到这些核心处于 100% 并保持在 20-30% 左右。我的系统规格:

System:
    python: 3.6.6 |Anaconda, Inc.| (default, Jun 28 2018, 17:14:51)  [GCC 7.2.0]
   machine: Linux-4.15.0-20-generic-x86_64-with-debian-buster-sid

Python deps:
       pip: 18.1
setuptools: 40.2.0
   sklearn: 0.20.1
     numpy: 1.15.4
     scipy: 1.1.0
    Cython: 0.29
    pandas: 0.23.4 

关于它不使用您所有的 cpu 个核心的方面:

我遇到了同样的问题,可以通过做两件事来解决。

  • 我已经编写了自己的发行版 class 并且意识到由于一个问题它非常慢。加快速度有帮助。

  • 我将 pre_dispatch 设置为合理的值,例如 pre_dispatch=10*os.cpu_count()。我认为问题在于它在开始适应其他核心之前准备好所有数据。