Как использовать RandomState со Sklearn RandomizedSearchCV на нескольких ядрах

Я озадачен правильным способом использования np.random.RandomState с 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. Документация для 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_split, GradientBoostingClassifier, StratifiedKFold, а также 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.

2 ответа

Решение

Кандидаты в параметры генерируются перед передачей в многопоточные функции с помощью объекта ParameterSampler. Так что только один random_state достаточно для воспроизводимости RandomizedSearchCV.

Обратите внимание, что я сказал "reproducibility of RandomizedSearchCV", Для оценок, используемых внутри него (base_clf здесь), каждый оценщик должен иметь свой собственный random_state как вы сделали.

Сейчас говорим о a single instance of RandomState, это прекрасно для кода, который является последовательным. Единственный случай для беспокойства - это когда мультипроцессор включается. Итак, давайте проанализируем шаги, которые происходят во время выполнения вашей программы.

  1. Вы создали RandomState объект с семенем. У него сейчас состояние.
  2. внутри train_test_split, StratifiedShuffleSplit используется (потому что вы использовали stratify param) который будет использовать пройденный RandomState объект для разделения и генерации перестановок в обучающих и тестовых данных. Так что внутреннее состояние RandomState изменилось сейчас. Но это последовательно и не о чем беспокоиться.
  3. Теперь вы установите это random_state объект в skf, Но расщепления не происходит, пока fit() в RandomizedSearchCV называется. Так что состояние не изменилось.
  4. После этого, когда search_clf.fit называется, происходит следующее:

    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
      • Вышеупомянутые два шага происходят многократно (количество разбиений х раз комбинаций параметров) из родительского потока (без асинхронности). И каждый раз оригинал 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 

Что касается того, что он не использует все ядра вашего процессора:

У меня была такая же проблема, и я мог решить ее, выполнив две вещи.

  • Я написал свой собственный класс дистрибутива и понял, что из-за проблемы он был безумно медленным. Ускорение помогло.

  • я установил pre_dispatchк чему-то разумному, например pre_dispatch=10*os.cpu_count(). Я думаю, что проблема была в том, что он подготавливает все данные, прежде чем начать подгонять их к другим ядрам.

Другие вопросы по тегам