如何在多个内核上将RandomState与Sklearn RandomizedSearchCV结合使用

时间:2018-12-11 15:05:04

标签: python numpy scikit-learn scipy numpy-random

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

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

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

但是实际上结果是可重复的。对于给定的n_iter值(即,来自参数空间的绘制数),从一次运行到下一次运行,找到的最佳超参数值是相同的。如果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参数:“伪随机数生成器状态,用于从可能值列表而不是scipy.stats分布进行随机统一采样。”这是否意味着它不影响参数空间中的分布?上面的代码是否足以确保可重复性,或者我是否需要设置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_splitGradientBoostingClassifierStratifiedKFoldRandomizedSearchCV使用单独的实例?另外,np.random.seed的文档说,种子{{1}初始化时已设置。这与RandomState设置种子如何相互作用?

  5. RandomizedSearchCV设置为使用少于所有内核时,尽管每个内核的使用级别增加,并且随着内核数量的增加,经过的时间减少,但我仍然看到所有内核上的活动。这仅仅是sklearn和/或macOS优化机器使用率吗?

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

2 个答案:

答案 0 :(得分: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对象在训练和测试中拆分并生成排列数据。因此RandomState的内部状态现在已更改。但是它的顺序性并没有什么可担心的。
  3. 现在您在random_state中设置了此skf对象。但是在fit()中的RandomizedSearchCV被调用之前,不会发生分裂。因此状态不变。
  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)。因此,RandomState对象中cv.split的已更改状态成为estimator中的基本状态
      • 以上两个步骤从父线程发生了多次(拆分次数x参数组合次数)(无异步性)。并且每次克隆原始RandomState来为估算器服务。因此结果是可重复的。
      • 因此,当实际的多线程部分开始时,不使用原始的RandomState,但是每个估计器(线程)将拥有自己的RandomState副本

希望这是有道理的,并回答您的问题。 Scikit学习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 

答案 1 :(得分:0)

关于它没有使用您所有的 cpu 内核:

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

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

  • 我将 findOne 设置为诸如 pre_dispatch 之类的合理值。我认为问题在于它在开始将内容安装到其他内核之前准备了所有数据。