2

在多核上运行时,我对使用np.random.RandomStatewith的正确方法感到困惑。sklearn.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. 关于参数的文档RandomizedSearchCVrandom_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是使用ok的单个实例,还是应该为train_test_splitGradientBoostingClassifierStratifiedKFold和使用单独的实例RandomizedSearchCV?此外,文档np.random.seed说种子是在RandomState初始化时设置的。这与RandomizedSearchCV设置种子有何相互作用?

  5. n_jobs设置为使用少于所有核心时,我仍然可以看到所有核心上的活动,尽管每个核心的使用水平会随着核心数量的增加而增加并且经过的时间会减少。这只是 sklearn 和/或 macOS 优化机器使用吗?

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

4

2 回答 2

3

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

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

现在谈论a single instance of RandomState,对于顺序代码来说非常好。唯一需要担心的是多处理何时启动。因此,让我们分析程序执行期间发生的步骤。

  1. 你设置一个RandomState带有种子的对象。它现在有一个状态。
  2. 在内部train_test_splitStratifiedShuffleSplit使用了 a(因为您使用stratify了参数),它将使用传递的RandomState对象在训练和测试数据中拆分并生成排列。所以现在的内部状态发生了RandomState变化。但它是连续的,没有什么可担心的。
  3. 现在你把这个random_state对象设置在skf. 但是fit()RandomizedSearchCV调用 in 之前不会发生拆分。所以状态不变。
  4. 之后,当search_clf.fit被调用时,会发生以下情况

    1. _run_search()被执行,它将使用random_state一次生成所有参数组合(根据 given 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并没有使用原来的,而是每个估计器(线程)都有自己的副本RandomState

希望这是有道理的,并回答你的问题。Scikit-learn明确要求用户进行如下设置:

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

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

我不完全确定您的最后一个问题,因为我无法在我的系统上重现该问题。我有 4 个核心,当我设置时,n_jobs=2或者3我只看到那些 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 
于 2018-12-11T17:11:38.797 回答
0

在它不使用所有 cpu 内核的方面:

我有同样的问题,可以做两件事来解决它。

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

  • 我设置pre_dispatch了一些合理的东西,比如pre_dispatch=10*os.cpu_count(). 我认为问题在于它在开始将内容安装到其他内核之前准备好所有数据。

于 2021-01-29T22:51:57.740 回答