10.3. Контроль случайности ¶
Некоторые объекты scikit-learn по своей природе случайны. Обычно это оценщики (например RandomForestClassifier
) и разделители перекрестной проверки (например KFold
). Случайность этих объектов контролируется с помощью их random_state
параметра, как описано в Глоссарии . В этом разделе подробно рассматривается запись в глоссарии, и описываются передовые методы и распространенные ошибки, связанные с этим тонким параметром.
Примечание
Для оптимальной надежности кросс-валидации (CV) результатов, проходят RandomState
экземпляры при создании оценок, либо оставить random_state
в None
. Передача целых чисел в разделители CV обычно является самым безопасным и предпочтительным вариантом; передача RandomState
экземпляров разделителям иногда может быть полезна для достижения очень конкретных вариантов использования. И для оценщиков, и для разделителей передача целого числа по сравнению с передачей экземпляра (или None
) приводит к тонким, но значительным различиям, особенно для процедур CV. Эти различия важно понимать при сообщении результатов.
Чтобы получить воспроизводимые результаты при выполнении, удалите любое использование random_state=None
.
10.3.1. Использование None или RandomState экземпляры, а также повторные вызовы fit и split
Параметр random_state
определяет , будет ли несколько вызовов fit (для оценок) или split (для CV разветвителей) будет производить те же результаты, в соответствии с этими правилами:
- Если передано целое число, вызов
fit
илиsplit
несколько раз всегда дает одни и те же результаты. - Если
None
илиRandomState
экземпляр передается:fit
иsplit
будет давать разные результаты каждый раз, когда они вызываются, а последовательность вызовов исследует все источники энтропии.None
— значение по умолчанию для всехrandom_state
параметров.
Здесь мы проиллюстрируем эти правила как для оценщиков, так и для CV-сплиттеров.
Примечание
Поскольку передача random_state=None
эквивалентна передаче глобального RandomState
экземпляра из numpy
( random_state=np.random.mtrand._rand
), мы не будем None
здесь явно упоминать . Все, что относится к экземплярам, также применимо к использованию None
.
10.3.1.1. Оценщики
Передача экземпляров означает, что fit
многократный вызов не даст одинаковых результатов, даже если оценщик приспособлен к одним и тем же данным и с одними и теми же гиперпараметрами:
>>> from sklearn.linear_model import SGDClassifier >>> from sklearn.datasets import make_classification >>> import numpy as np >>> rng = np.random.RandomState(0) >>> X, y = make_classification(n_features=5, random_state=rng) >>> sgd = SGDClassifier(random_state=rng) >>> sgd.fit(X, y).coef_ array([[ 8.85418642, 4.79084103, -3.13077794, 8.11915045, -0.56479934]]) >>> sgd.fit(X, y).coef_ array([[ 6.70814003, 5.25291366, -7.55212743, 5.18197458, 1.37845099]])
Из приведенного выше фрагмента видно, что многократные вызовы sgd.fit
привели к созданию разных моделей, даже если данные были одинаковыми. Это связано с тем, что генератор случайных чисел (RNG) оценщика потребляется (т. Е. Изменяется) при fit
вызове, и этот измененный RNG будет использоваться в последующих вызовах fit
. Кроме того, rng
объект является общим для всех объектов, которые его используют, и, как следствие, эти объекты становятся в некоторой степени взаимозависимыми. Например, два оценщика, которые используют один и тот же RandomState
экземпляр, будут влиять друг на друга, как мы увидим позже, когда будем обсуждать клонирование. Об этом важно помнить при отладке.
Если бы мы передали целое число в random_state
параметр объекта RandomForestClassifier
, мы бы получили одни и те же модели и, следовательно, каждый раз одни и те же оценки. Когда мы передаем целое число, во всех вызовах используется один и тот же RNG fit
. Что внутри происходит, так это то, что даже если RNG потребляется при fit
вызове, он всегда сбрасывается в исходное состояние в начале fit
.
10.3.1.2. CV разветвители
Рандомизированные разделители CV имеют аналогичное поведение при RandomState
передаче экземпляра; вызов split
несколько раз дает разные разбиения данных:
>>> from sklearn.model_selection import KFold >>> import numpy as np >>> X = y = np.arange(10) >>> rng = np.random.RandomState(0) >>> cv = KFold(n_splits=2, shuffle=True, random_state=rng) >>> for train, test in cv.split(X, y): ... print(train, test) [0 3 5 6 7] [1 2 4 8 9] [1 2 4 8 9] [0 3 5 6 7] >>> for train, test in cv.split(X, y): ... print(train, test) [0 4 6 7 8] [1 2 3 5 9] [1 2 3 5 9] [0 4 6 7 8]
Мы видим, что расколы отличаются от второго раза split
. Это может привести к неожиданным результатам, если вы сравните производительность нескольких оценщиков путем многократного вызова split
, как мы увидим в следующем разделе.
10.3.2. Распространенные подводные камни и тонкости
Хотя правила, управляющие random_state
параметром, кажутся простыми, они, тем не менее, имеют некоторые тонкие последствия. В некоторых случаях это может даже привести к неверным выводам.
10.3.2.1. Оценщики
Различные типы random_state приводят к разным процедурам перекрестной проверки
В зависимости от типа random_state
параметра оценщики будут вести себя по-разному, особенно в процедурах перекрестной проверки. Рассмотрим следующий фрагмент:
>>> from sklearn.ensemble import RandomForestClassifier >>> from sklearn.datasets import make_classification >>> from sklearn.model_selection import cross_val_score >>> import numpy as np >>> X, y = make_classification(random_state=0) >>> rf_123 = RandomForestClassifier(random_state=123) >>> cross_val_score(rf_123, X, y) array([0.85, 0.95, 0.95, 0.9 , 0.9 ]) >>> rf_inst = RandomForestClassifier(random_state=np.random.RandomState(0)) >>> cross_val_score(rf_inst, X, y) array([0.9 , 0.95, 0.95, 0.9 , 0.9 ])
Мы видим, что результаты перекрестной проверки rf_123
и rf_inst
отличаются, как и следовало ожидать, поскольку мы не передали один и тот же random_state
параметр. Однако разница между этими оценками более тонкая, чем кажется, и процедуры перекрестной проверки, которые выполняли cross_val_score
, значительно различаются в каждом случае :
- Поскольку
rf_123
было передано целое число, каждый вызовfit
использует один и тот же RNG: это означает, что все случайные характеристики случайного оценщика леса будут одинаковыми для каждого из 5 кратностей процедуры CV. В частности, (случайно выбранное) подмножество функций оценщика будет одинаковым для всех складок. - Поскольку
rf_inst
был переданRandomState
экземпляр, каждый вызовfit
начинается с другого RNG. В результате случайный набор функций будет отличаться для каждой складки.
Хотя наличие постоянного оценщика RNG по сгибам не является неправильным по своей сути, мы обычно хотим, чтобы результаты CV были устойчивыми по отношению к случайности оценщика. В результате передача экземпляра вместо целого числа может быть предпочтительнее, поскольку это позволит оценивающему RNG изменяться для каждой кратности.
Примечание
Здесь cross_val_score
будет использоваться нерандомизированный разделитель CV (по умолчанию), поэтому обе оценки будут оцениваться на одних и тех же разделениях. Этот раздел не о вариативности шпагатов. Кроме того, make_classification
для нашей цели иллюстрации не имеет значения , передаем ли мы целое число или экземпляр : важно то, что мы передаем RandomForestClassifier
оценщику.
Клонирование
Еще один тонкий побочный эффект передачи RandomState
экземпляров — это то, как clone
будет работать:
>>> from sklearn import clone >>> from sklearn.ensemble import RandomForestClassifier >>> import numpy as np >>> rng = np.random.RandomState(0) >>> a = RandomForestClassifier(random_state=rng) >>> b = clone(a)
Поскольку RandomState
экземпляр был передан a
, a
и b
они не являются клонами в строгом смысле, а скорее клонами в статистическом смысле: a
и b
все равно будут разными моделями, даже при обращении к fit(X, y) одним и тем же данным. Кроме того, a и b будут влиять друг на друга, так как они одни и те же внутренние RNG: вызов будет потреблять b RNG, и вызов b.fit будет потреблять a RNG, так как они одинаковы. Этот бит истинен для любых оценщиков, которые совместно используют параметр random_state; это не относится к клонам.
Если бы было передано целое число, a
и b были бы точными клонами, и они не влияли бы друг на друга.
Предупреждение
Несмотря на то, clone
редко используется в коде пользователя, она называется pervasively по всей scikit учиться CodeBase: в частности, большинство мета-оценок , которые принимают не подогнанные оценщики называют clone
внутренне (
и т.д.).GridSearchCV
, StackingClassifier
, CalibratedClassifierCV
10.3.2.2. CV разветвители
При передаче RandomState
экземпляра разделители CV дают разные разделения каждый раз при split
вызове. При сравнении различных оценщиков это может привести к переоценке дисперсии разницы в производительности между оценщиками:
>>> from sklearn.naive_bayes import GaussianNB >>> from sklearn.discriminant_analysis import LinearDiscriminantAnalysis >>> from sklearn.datasets import make_classification >>> from sklearn.model_selection import KFold >>> from sklearn.model_selection import cross_val_score >>> import numpy as np >>> rng = np.random.RandomState(0) >>> X, y = make_classification(random_state=rng) >>> cv = KFold(shuffle=True, random_state=rng) >>> lda = LinearDiscriminantAnalysis() >>> nb = GaussianNB() >>> for est in (lda, nb): ... print(cross_val_score(est, X, y, cv=cv)) [0.8 0.75 0.75 0.7 0.85] [0.85 0.95 0.95 0.85 0.95]
Непосредственное сравнение производительности LinearDiscriminantAnalysis
оценщика и GaussianNB
оценщика в каждом сгибе было бы ошибкой: разбиения, на которых оцениваются оценщики, разные . Действительно, cross_val_score
будет внутренне вызывать один и cv.split
тот же KFold
экземпляр, но разбиение будет каждый раз другим. Это также верно для любого инструмента, который выполняет выбор модели с помощью перекрестной проверки, например, GridSearchCV
and RandomizedSearchCV
: scores не сопоставимы по размеру при разных вызовах search.fit
, поскольку cv.split
они вызывались бы несколько раз. Однако в рамках одного вызова search.fit
сравнение сгиба-к-сгибу возможно, так как средство оценки поиска вызывает только cv.split
один раз.
Для получения сравнимых сгиба до раза результатов во всех сценариях, следует передать целое число в CV разветвителя: cv = KFold(shuffle=True, random_state=0)
Примечание
Несмотря на то, что сравнение кратных значений с RandomState
экземплярами не рекомендуется , однако можно ожидать, что средние баллы позволят сделать вывод о том, лучше ли один оценщик, чем другой, при условии, что используется достаточное количество складок и данных.
Примечание
В этом примере важно то, что было передано KFold
. Передаем ли мы RandomState
экземпляр или целое число, make_classification
не имеет отношения к нашей цели иллюстрации. Кроме того, нет LinearDiscriminantAnalysis
и GaussianNB
рандомизированных оценок.
10.3.3. Общие рекомендации
10.3.3.1. Получение воспроизводимых результатов при многократном выполнении
Чтобы получить воспроизводимые (т. Е. Постоянные) результаты при выполнении нескольких программ , нам нужно удалить все варианты использования random_state=None
, что является значением по умолчанию. Рекомендуемый способ — объявить rng
переменную в верхней части программы и передать ее любому объекту, принимающему random_state
параметр:
>>> from sklearn.ensemble import RandomForestClassifier >>> from sklearn.datasets import make_classification >>> from sklearn.model_selection import train_test_split >>> import numpy as np >>> rng = np.random.RandomState(0) >>> X, y = make_classification(random_state=rng) >>> rf = RandomForestClassifier(random_state=rng) >>> X_train, X_test, y_train, y_test = train_test_split(X, y, ... random_state=rng) >>> rf.fit(X_train, y_train).score(X_test, y_test) 0.84
Теперь нам гарантируется, что результат этого скрипта всегда будет 0,84, независимо от того, сколько раз мы его запускали. Изменение глобальной rng
переменной на другое значение должно повлиять на результаты, как и ожидалось.
Также можно объявить rng
переменную как целое число. Однако это может привести к менее надежным результатам перекрестной проверки, как мы увидим в следующем разделе.
Примечание
Мы не рекомендуем устанавливать глобальное numpy
начальное число путем вызова np.random.seed(0)
. Смотрите здесь для обсуждения.
10.3.3.2. Надежность результатов перекрестной проверки
Когда мы оцениваем производительность рандомизированного оценщика с помощью перекрестной проверки, мы хотим убедиться, что оценщик может давать точные прогнозы для новых данных, но мы также хотим убедиться, что оценщик устойчив по отношению к своей случайной инициализации. Например, мы хотели бы, чтобы инициализация случайных весов SGDCLassifier
была стабильно хорошей для всех сверток: в противном случае, когда мы обучаем эту оценку новым данным, нам может не повезти, и случайная инициализация может привести к плохой производительности. Точно так же мы хотим, чтобы случайный лес был устойчивым по отношению к набору случайно выбранных функций, которые будет использовать каждое дерево.
По этим причинам предпочтительно оценивать преформность перекрестной проверки, позволяя оценщику использовать различный RNG в каждом сгибе. Это делается путем передачи RandomState
экземпляра (или None
) в инициализацию оценщика.
Когда мы передаем целое число, оценщик будет использовать один и тот же RNG для каждого сгиба: если оценщик работает хорошо (или плохо), по оценке CV, это может быть просто потому, что нам повезло (или не повезло) с этим конкретным начальным числом. Передача экземпляров приводит к более надежным результатам CV и делает более справедливым сравнение между различными алгоритмами. Это также помогает ограничить соблазн рассматривать RNG оценщика как гиперпараметр, который можно настраивать.
Независимо от того, передаем ли мы RandomState
экземпляры или целые числа в разделители CV, это не влияет на надежность, если split
вызывается только один раз. Когда split
вызывается несколько раз, сравнение сгиба-к-сгибу больше невозможно. В результате передача целого числа в разделители CV обычно более безопасна и охватывает большинство случаев использования.