Spec-Zone.ru › scikit-learn

3.1. Кросс-валидация: оценка качества работы оценщика

Обучение параметров функции предсказания и её проверка на тех же данных — методологическая ошибка: модель, которая просто повторяет метки уже просмотренных образцов, получит идеальную оценку, но не сможет делать полезные предсказания для ранее не встречавшихся данных. Такая ситуация называется переобучением. Чтобы избежать этого, при проведении эксперимента в области машинного обучения (с учителем) обычно часть доступных данных оставляют в качестве тестовой выборки X_test, y_test. Обратите внимание, что слово «эксперимент» здесь не подразумевает только академическое применение, поскольку даже в коммерческих условиях машинное обучение обычно начинается с экспериментов. Ниже представлена блок-схема типичного рабочего процесса кросс-валидации при обучении модели. Наилучшие параметры можно определить с помощью методов поиска по сетке.

Grid Search Workflow

В scikit-learn случайное разделение на обучающую и тестовую выборки можно быстро выполнить с помощью вспомогательной функции train_test_split. Загрузим набор данных ирисов, чтобы обучить на нём линейную машину опорных векторов:

>>> import numpy as np
>>> from sklearn.model_selection import train_test_split
>>> from sklearn import datasets
>>> from sklearn import svm

>>> X, y = datasets.load_iris(return_X_y=True)
>>> X.shape, y.shape
((150, 4), (150,))

Теперь мы можем быстро сформировать обучающую выборку, оставив 40% данных для тестирования (оценки) классификатора:

>>> X_train, X_test, y_train, y_test = train_test_split(
...     X, y, test_size=0.4, random_state=0)

>>> X_train.shape, y_train.shape
((90, 4), (90,))
>>> X_test.shape, y_test.shape
((60, 4), (60,))

>>> clf = svm.SVC(kernel='linear', C=1).fit(X_train, y_train)
>>> clf.score(X_test, y_test)
0.96

При оценке различных настроек («гиперпараметров») оценщиков, таких как настройка C, которую необходимо задать вручную для SVM, сохраняется риск переобучения на тестовой выборке, поскольку параметры можно подбирать до тех пор, пока оценщик не покажет оптимальный результат. В результате информация о тестовой выборке может «просочиться» в модель, и метрики оценки перестанут отражать способность к обобщению. Чтобы решить эту проблему, можно выделить ещё одну часть набора данных — так называемую «валидационную выборку»: обучение выполняется на обучающей выборке, затем проводится оценка на валидационной выборке, а если эксперимент кажется успешным, окончательную оценку можно выполнить на тестовой выборке.

Однако при разделении доступных данных на три выборки мы значительно уменьшаем количество образцов, которые можно использовать для обучения модели, а результаты могут зависеть от конкретного случайного выбора пары выборок (обучающей и валидационной).

Решение этой проблемы — процедура, называемая кросс-валидацией (сокращённо CV). Для окончательной оценки по-прежнему следует отложить тестовую выборку, но при CV валидационная выборка больше не нужна. В базовом подходе, называемом k-блочной кросс-валидацией, обучающая выборка разбивается на k частей меньшего размера (другие подходы описаны ниже, но в целом основаны на тех же принципах). Для каждой из k «блоков» выполняется следующая процедура:

  • Модель обучается на \(k-1\) блоках;
  • полученная модель проверяется на оставшейся части данных (то есть она используется как тестовая выборка для вычисления такой метрики качества, как точность).

В этом случае показателем качества, полученным при k-блочной кросс-валидации, является среднее значение результатов, вычисленных в цикле. Этот подход может быть вычислительно затратным, но не расходует слишком много данных (в отличие от выбора произвольной валидационной выборки), что является важным преимуществом для таких задач, как обратный вывод, где число образцов очень мало.

A depiction of a 5 fold cross validation on a training set, while holding out a test set.

3.1.1. Вычисление метрик с помощью кросс-валидации

Самый простой способ использовать кросс-валидацию — вызвать вспомогательную функцию cross_val_score для оценщика и набора данных.

В следующем примере показано, как оценить точность машины опорных векторов с линейным ядром на наборе данных ирисов: данные разбиваются на части, обучается модель и вычисляется оценка 5 раз подряд (каждый раз с другим разбиением):

>>> from sklearn.model_selection import cross_val_score
>>> clf = svm.SVC(kernel='linear', C=1, random_state=42)
>>> scores = cross_val_score(clf, X, y, cv=5)
>>> scores
array([0.96, 1. , 0.96, 0.96, 1. ])

Таким образом, среднее значение оценки и стандартное отклонение равны:

>>> print("%0.2f accuracy with a standard deviation of %0.2f" % (scores.mean(), scores.std()))
0.98 accuracy with a standard deviation of 0.02

По умолчанию на каждой итерации CV вычисляется оценка, возвращаемая методом score оценщика. Её можно изменить с помощью параметра scoring:

>>> from sklearn import metrics
>>> scores = cross_val_score(
...     clf, X, y, cv=5, scoring='f1_macro')
>>> scores
array([0.96, 1., 0.96, 0.96, 1.])

Подробности см. в разделе Параметр scoring: определение правил оценки модели. В наборе данных Iris образцы равномерно распределены по целевым классам, поэтому точность и F1-мера почти одинаковы.

Если аргумент cv является целым числом, cross_val_score по умолчанию использует стратегии KFold или StratifiedKFold; последняя используется, если оценщик наследуется от ClassifierMixin.

Можно также использовать другие стратегии кросс-валидации, передав вместо этого итератор кросс-валидации, например:

>>> from sklearn.model_selection import ShuffleSplit
>>> n_samples = X.shape[0]
>>> cv = ShuffleSplit(n_splits=5, test_size=0.3, random_state=0)
>>> cross_val_score(clf, X, y, cv=cv)
array([0.977, 0.977, 1., 0.955, 1.])

Ещё один вариант — использовать итерируемый объект, возвращающий разбиения (train, test) в виде массивов индексов, например:

>>> def custom_cv_2folds(X):
...     n = X.shape[0]
...     i = 1
...     while i <= 2:
...         idx = np.arange(n * (i - 1) / 2, n * i / 2, dtype=int)
...         yield idx, idx
...         i += 1
...
>>> custom_cv = custom_cv_2folds(X)
>>> cross_val_score(clf, X, y, cv=custom_cv)
array([1.        , 0.973])
Преобразование данных с использованием отложенных данных

Так же как важно проверять предсказатель на данных, не использованных при обучении, предварительная обработка (например, стандартизация, отбор признаков и т. д.) и аналогичные преобразования данных должны обучаться на обучающей выборке и применяться к отложенным данным для предсказания:

>>> from sklearn import preprocessing
>>> X_train, X_test, y_train, y_test = train_test_split(
...     X, y, test_size=0.4, random_state=0)
>>> scaler = preprocessing.StandardScaler().fit(X_train)
>>> X_train_transformed = scaler.transform(X_train)
>>> clf = svm.SVC(C=1).fit(X_train_transformed, y_train)
>>> X_test_transformed = scaler.transform(X_test)
>>> clf.score(X_test_transformed, y_test)
0.9333

Pipeline упрощает составление цепочек оценщиков и обеспечивает такое поведение при кросс-валидации:

>>> from sklearn.pipeline import make_pipeline
>>> clf = make_pipeline(preprocessing.StandardScaler(), svm.SVC(C=1))
>>> cross_val_score(clf, X, y, cv=cv)
array([0.977, 0.933, 0.955, 0.933, 0.977])

См. раздел Конвейеры и составные оценщики.

3.1.1.1. Функция cross_validate и оценка по нескольким метрикам

Функция cross_validate отличается от cross_val_score двумя особенностями:

  • Она позволяет задавать несколько метрик для оценки.
  • Она возвращает словарь, содержащий время обучения, время вычисления оценки (а также, при необходимости, оценки на обучающих выборках, обученные оценщики и индексы разбиений на обучающую и тестовую выборки) в дополнение к оценке на тестовой выборке.

При оценке по одной метрике, когда параметр scoring является строкой, вызываемым объектом или None, ключами будут - ['test_score', 'fit_time', 'score_time']

При оценке по нескольким метрикам возвращаемым значением является словарь со следующими ключами - ['test_<scorer1_name>', 'test_<scorer2_name>', 'test_<scorer...>', 'fit_time', 'score_time']

По умолчанию return_train_score имеет значение False, чтобы сократить время вычислений. Чтобы оценивать результаты также на обучающей выборке, необходимо задать ему значение True. Можно также сохранить оценщик, обученный на каждой обучающей выборке, задав return_estimator=True. Аналогичным образом можно задать return_indices=True, чтобы сохранить индексы обучающих и тестовых данных, использованные для разделения набора данных на обучающую и тестовую выборки в каждом разбиении CV.

Несколько метрик можно указать в виде списка, кортежа или множества имён предопределённых функций оценки:

>>> from sklearn.model_selection import cross_validate
>>> from sklearn.metrics import recall_score
>>> scoring = ['precision_macro', 'recall_macro']
>>> clf = svm.SVC(kernel='linear', C=1, random_state=0)
>>> scores = cross_validate(clf, X, y, scoring=scoring)
>>> sorted(scores.keys())
['fit_time', 'score_time', 'test_precision_macro', 'test_recall_macro']
>>> scores['test_recall_macro']
array([0.96, 1., 0.96, 0.96, 1.])

Или в виде словаря, сопоставляющего имя функции оценки с предопределённой или пользовательской функцией оценки:

>>> from sklearn.metrics import make_scorer
>>> scoring = {'prec_macro': 'precision_macro',
...            'rec_macro': make_scorer(recall_score, average='macro')}
>>> scores = cross_validate(clf, X, y, scoring=scoring,
...                         cv=5, return_train_score=True)
>>> sorted(scores.keys())
['fit_time', 'score_time', 'test_prec_macro', 'test_rec_macro',
 'train_prec_macro', 'train_rec_macro']
>>> scores['train_rec_macro']
array([0.97, 0.97, 0.99, 0.98, 0.98])

Вот пример использования cross_validate с одной метрикой:

>>> scores = cross_validate(clf, X, y,
...                         scoring='precision_macro', cv=5,
...                         return_estimator=True)
>>> sorted(scores.keys())
['estimator', 'fit_time', 'score_time', 'test_score']

3.1.1.2. Получение предсказаний с помощью кросс-валидации

Функция cross_val_predict имеет интерфейс, похожий на интерфейс cross_val_score, но для каждого элемента входных данных возвращает предсказание, полученное для этого элемента, когда он входил в тестовую выборку. Можно использовать только стратегии кросс-валидации, в которых каждый элемент ровно один раз попадает в тестовую выборку (в противном случае возникает исключение).

Предупреждение

Примечание о некорректном использовании cross_val_predict

Результат cross_val_predict может отличаться от результата cross_val_score, поскольку элементы группируются по-разному. Функция cross_val_score вычисляет среднее по блокам кросс-валидации, тогда как cross_val_predict просто возвращает метки (или вероятности), полученные от нескольких отдельных моделей без различения между ними. Поэтому cross_val_predict не является подходящей мерой ошибки обобщения.

Функция cross_val_predict подходит для:
  • Визуализации предсказаний, полученных от разных моделей.
  • Смешивания моделей: предсказания одного оценщика с учителем используются для обучения другого оценщика в ансамблевых методах.

Доступные итераторы кросс-валидации представлены в следующем разделе.

Примеры

  • ROC-кривая с кросс-валидацией,
  • Рекурсивное исключение признаков с кросс-валидацией,
  • Пользовательская стратегия переобучения модели при поиске по сетке с кросс-валидацией,
  • Пример конвейера для извлечения признаков из текста и оценки,
  • Визуализация предсказаний, полученных с помощью кросс-валидации,
  • Вложенная и невложенная кросс-валидация.

3.1.2. Итераторы перекрёстной проверки

В следующих разделах перечислены утилиты для генерации индексов, которые можно использовать для создания разбиений набора данных в соответствии с различными стратегиями перекрёстной проверки.

3.1.2.1. Итераторы перекрёстной проверки для i.i.d.-данных

Предположение о том, что данные являются независимыми и одинаково распределёнными (i.i.d.), означает, что все выборки получены в результате одного и того же порождающего процесса и что этот процесс не зависит от ранее созданных выборок.

В таких случаях можно использовать следующие средства перекрёстной проверки.

Примечание

Хотя предположение о независимых и одинаково распределённых данных часто встречается в теории машинного обучения, на практике оно выполняется редко. Если известно, что выборки были получены с использованием зависящего от времени процесса, безопаснее использовать схему перекрёстной проверки с учётом временных рядов. Аналогично, если известно, что порождающий процесс имеет групповую структуру (выборки собраны у разных испытуемых, в ходе экспериментов или с помощью измерительных приборов), безопаснее использовать перекрёстную проверку с учётом групп.

3.1.2.1.1. K-блочная перекрёстная проверка

KFold делит все выборки на \(k\) групп, называемых фолдами (если \(k = n\), это эквивалентно стратегии Leave One Out), по возможности одинакового размера. Функция предсказания обучается на \(k - 1\) фолдах, а оставшийся фолд используется для тестирования.

Пример двухблочной перекрёстной проверки на наборе данных из 4 выборок:

>>> import numpy as np
>>> from sklearn.model_selection import KFold

>>> X = ["a", "b", "c", "d"]
>>> kf = KFold(n_splits=2)
>>> for train, test in kf.split(X):
...     print("%s %s" % (train, test))
[2 3] [0 1]
[0 1] [2 3]

Ниже показана визуализация работы перекрёстной проверки. Обратите внимание, что на KFold не влияют классы или группы.

../_images/sphx_glr_plot_cv_indices_006.png

Каждый фолд состоит из двух массивов: первый относится к обучающей выборке, а второй — к тестовой выборке. Поэтому обучающую и тестовую выборки можно создать с помощью индексирования в NumPy:

>>> X = np.array([[0., 0.], [1., 1.], [-1., -1.], [2., 2.]])
>>> y = np.array([0, 1, 0, 1])
>>> X_train, X_test, y_train, y_test = X[train], X[test], y[train], y[test]

3.1.2.1.2. Повторная K-блочная перекрёстная проверка

RepeatedKFold выполняет KFold \(n\) раз, создавая разные разбиения при каждом повторе.

Пример двукратной K-блочной перекрёстной проверки с двумя повторами:

>>> import numpy as np
>>> from sklearn.model_selection import RepeatedKFold
>>> X = np.array([[1, 2], [3, 4], [1, 2], [3, 4]])
>>> random_state = 12883823
>>> rkf = RepeatedKFold(n_splits=2, n_repeats=2, random_state=random_state)
>>> for train, test in rkf.split(X):
...     print("%s %s" % (train, test))
...
[2 3] [0 1]
[0 1] [2 3]
[0 2] [1 3]
[1 3] [0 2]

Аналогично, RepeatedStratifiedKFold выполняет StratifiedKFold \(n\) раз, используя при каждом повторе разные случайные перестановки.

3.1.2.1.3. Исключение одного объекта (LOO)

LeaveOneOut (или LOO) — это простая схема перекрёстной проверки. Каждая обучающая выборка создаётся из всех объектов, кроме одного; оставшийся объект образует тестовую выборку. Таким образом, для \(n\) объектов получаем \(n\) различных обучающих и \(n\) различных тестовых выборок. Эта процедура перекрёстной проверки не приводит к значительной потере данных, поскольку из обучающей выборки исключается только один объект:

>>> from sklearn.model_selection import LeaveOneOut

>>> X = [1, 2, 3, 4]
>>> loo = LeaveOneOut()
>>> for train, test in loo.split(X):
...     print("%s %s" % (train, test))
[1 2 3] [0]
[0 2 3] [1]
[0 1 3] [2]
[0 1 2] [3]

Тем, кто собирается использовать LOO для выбора модели, следует учитывать несколько известных недостатков. По сравнению с \(k\)-блочной перекрёстной проверкой в этом случае обучается \(n\) моделей на основе \(n\) объектов вместо \(k\) моделей, где \(n > k\). Кроме того, каждая модель обучается на \(n - 1\) объектах, а не на \((k-1) n / k\). В обоих отношениях, если \(k\) не слишком велико и \(k < n\), LOO требует больше вычислительных ресурсов, чем \(k\)-блочная перекрёстная проверка.

С точки зрения точности LOO часто приводит к высокой дисперсии оценки ошибки на тестовых данных. Интуитивно это объясняется тем, что для построения каждой модели используется \(n - 1\) из \(n\) объектов: модели, построенные на фолдах, практически идентичны друг другу и модели, построенной на всей обучающей выборке.

Однако если для рассматриваемого размера обучающей выборки кривая обучения имеет крутой наклон, перекрёстная проверка с 5 или 10 фолдами может завышать ошибку обобщения.

В целом большинство авторов и эмпирические данные указывают на то, что предпочтительнее использовать перекрёстную проверку с 5 или 10 фолдами, а не LOO.

Ссылки
  • http://www.faqs.org/faqs/ai-faq/neural-nets/part3/section-12.html;
  • T. Hastie, R. Tibshirani, J. Friedman, Элементы статистического обучения, Springer, 2009
  • L. Breiman, P. Spector, Выбор и оценка подмоделей в регрессии: случай X-случайности, International Statistical Review, 1992;
  • R. Kohavi, Исследование перекрёстной проверки и бутстрэпа для оценки точности и выбора модели, Intl. Jnt. Conf. AI
  • R. Bharat Rao, G. Fung, R. Rosales, Об опасностях перекрёстной проверки. Экспериментальная оценка, SIAM, 2008;
  • G. James, D. Witten, T. Hastie, R. Tibshirani, Введение в статистическое обучение, Springer, 2013.

3.1.2.1.4. Исключение P объектов (LPO)

LeavePOut очень похож на LeaveOneOut: он создаёт все возможные обучающие и тестовые выборки, исключая \(p\) объектов из полного набора. Для \(n\) объектов это даёт \({n \choose p}\) пар обучающей и тестовой выборок. В отличие от LeaveOneOut и KFold, тестовые выборки будут пересекаться при \(p > 1\).

Пример исключения двух объектов из набора данных из 4 объектов:

>>> from sklearn.model_selection import LeavePOut

>>> X = np.ones(4)
>>> lpo = LeavePOut(p=2)
>>> for train, test in lpo.split(X):
...     print("%s %s" % (train, test))
[2 3] [0 1]
[1 3] [0 2]
[1 2] [0 3]
[0 3] [1 2]
[0 2] [1 3]
[0 1] [2 3]

3.1.2.1.5. Перекрёстная проверка со случайными перестановками, или Shuffle & Split

Итератор ShuffleSplit создаёт заданное пользователем количество независимых разбиений набора данных на обучающую и тестовую выборки. Сначала объекты перемешиваются, а затем разделяются на обучающую и тестовую выборки.

Для воспроизводимости результатов случайность можно контролировать, явно задав начальное значение генератора псевдослучайных чисел random_state.

Пример использования:

>>> from sklearn.model_selection import ShuffleSplit
>>> X = np.arange(10)
>>> ss = ShuffleSplit(n_splits=5, test_size=0.25, random_state=0)
>>> for train_index, test_index in ss.split(X):
...     print("%s %s" % (train_index, test_index))
[9 1 6 7 3 0 5] [2 8 4]
[2 9 8 0 6 7 4] [3 5 1]
[4 5 1 0 6 9 7] [2 3 8]
[2 7 5 8 0 3 4] [6 1 9]
[4 1 0 6 8 9 3] [5 2 7]

Ниже показана визуализация работы перекрёстной проверки. Обратите внимание, что на ShuffleSplit не влияют классы или группы.

../_images/sphx_glr_plot_cv_indices_008.png

Таким образом, ShuffleSplit — хорошая альтернатива KFold: она позволяет точнее управлять количеством итераций и долей объектов в каждой части разбиения на обучающую и тестовую выборки.

3.1.2.2. Итераторы перекрёстной проверки со стратификацией по меткам классов

В некоторых задачах классификации могут встречаться редкие классы: например, отрицательных наблюдений может быть на порядки больше, чем положительных (например, при медицинском скрининге, обнаружении мошенничества и т. д.). В результате разбиение для перекрёстной проверки может создавать обучающие или проверочные фолды, в которых отсутствует какой-либо класс. Это обычно приводит к неопределённым метрикам классификации (например, ROC AUC), исключениям при вызове fit или отсутствующим столбцам в выходных данных методов predict_proba или decision_function многоклассовых классификаторов, обученных на разных фолдах.

Чтобы уменьшить вероятность таких проблем, средства разбиения, например StratifiedKFold и StratifiedShuffleSplit, используют стратифицированную выборку, чтобы относительные частоты классов в каждом фолде оставались примерно такими же, как во всём наборе.

Примечание

Стратифицированную выборку добавили в scikit-learn для обхода упомянутых выше инженерных проблем, а не для решения статистической задачи.

Стратификация делает фолды перекрёстной проверки более однородными и тем самым скрывает часть вариативности, свойственной обучению моделей на ограниченном количестве наблюдений.

В результате стратификация может искусственно уменьшить разброс значений метрики, измеренной в ходе итераций перекрёстной проверки: вариативность между фолдами больше не отражает неопределённость качества классификаторов при наличии редких классов.

3.1.2.2.1. Стратифицированная K-блочная перекрёстная проверка

StratifiedKFold — это вариант K-блочной перекрёстной проверки, который возвращает стратифицированные фолды: каждый набор содержит примерно такой же процент объектов каждого целевого класса, как и полный набор.

Ниже приведён пример стратифицированной перекрёстной проверки с 3 фолдами на наборе данных из 50 объектов, принадлежащих к двум несбалансированным классам. Мы показываем количество объектов каждого класса и сравниваем результат с KFold.

>>> from sklearn.model_selection import StratifiedKFold, KFold
>>> import numpy as np
>>> X, y = np.ones((50, 1)), np.hstack(([0] * 45, [1] * 5))
>>> skf = StratifiedKFold(n_splits=3)
>>> for train, test in skf.split(X, y):
...     print('train -  {}   |   test -  {}'.format(
...         np.bincount(y[train]), np.bincount(y[test])))
train -  [30  3]   |   test -  [15  2]
train -  [30  3]   |   test -  [15  2]
train -  [30  4]   |   test -  [15  1]
>>> kf = KFold(n_splits=3)
>>> for train, test in kf.split(X, y):
...     print('train -  {}   |   test -  {}'.format(
...         np.bincount(y[train]), np.bincount(y[test])))
train -  [28  5]   |   test -  [17]
train -  [28  5]   |   test -  [17]
train -  [34]   |   test -  [11  5]

Как видно, StratifiedKFold сохраняет соотношение классов (примерно 1 к 10) и в обучающей, и в тестовой выборках.

Ниже показана визуализация работы перекрёстной проверки.

../_images/sphx_glr_plot_cv_indices_009.png

RepeatedStratifiedKFold можно использовать для выполнения стратифицированной K-блочной перекрёстной проверки n раз, применяя при каждом повторе разные случайные перестановки.

3.1.2.2.2. Стратифицированное разбиение с перемешиванием

StratifiedShuffleSplit — это вариант ShuffleSplit, который возвращает стратифицированные разбиения, то есть создаёт разбиения, сохраняя для каждого целевого класса ту же долю объектов, что и в полном наборе.

Ниже показана визуализация работы перекрёстной проверки.

../_images/sphx_glr_plot_cv_indices_012.png

3.1.2.3. Предопределённые разбиения на фолды и проверочные выборки

Для некоторых наборов данных уже существует заранее определённое разбиение на обучающий и проверочный фолды или на несколько фолдов для перекрёстной проверки. С помощью PredefinedSplit эти фолды можно использовать, например, при подборе гиперпараметров.

Например, при использовании проверочной выборки задайте значение test_fold равным 0 для всех объектов, входящих в проверочную выборку, и -1 для всех остальных.

3.1.2.4. Итераторы перекрёстной проверки для сгруппированных данных

Предположение о независимости и одинаковом распределении нарушается, если лежащий в основе данных порождающий процесс создаёт группы зависимых объектов.

Способ группировки данных зависит от предметной области. Например, медицинские данные могут быть собраны у нескольких пациентов, при этом у каждого пациента может быть взято несколько образцов. Такие данные, вероятно, зависят от принадлежности к конкретной группе. В нашем примере идентификатор пациента для каждой выборки будет идентификатором её группы.

В этом случае мы хотим выяснить, насколько хорошо модель, обученная на определённом наборе групп, обобщает результат на ранее не встречавшиеся группы. Для этого нужно гарантировать, что все объекты в проверочном фолде принадлежат группам, которые полностью отсутствуют в соответствующем обучающем фолде.

Для этого можно использовать следующие средства разбиения для перекрёстной проверки. Идентификатор группы каждого объекта указывается с помощью параметра groups.

3.1.2.4.1. Групповая K-блочная перекрёстная проверка

GroupKFold — это вариант K-блочной перекрёстной проверки, который гарантирует, что одна и та же группа не попадёт одновременно в тестовую и обучающую выборки. Например, если данные получены от разных испытуемых, у каждого из которых имеется несколько выборок, гибкая модель может обучиться на особенностях, характерных только для конкретных людей, и не обобщать результат на новых испытуемых. GroupKFold позволяет выявить подобное переобучение.

Предположим, у вас есть три испытуемых, которым соответствуют числа от 1 до 3:

>>> from sklearn.model_selection import GroupKFold

>>> X = [0.1, 0.2, 2.2, 2.4, 2.3, 4.55, 5.8, 8.8, 9, 10]
>>> y = ["a", "b", "b", "b", "c", "c", "c", "d", "d", "d"]
>>> groups = [1, 1, 1, 2, 2, 2, 3, 3, 3, 3]

>>> gkf = GroupKFold(n_splits=3)
>>> for train, test in gkf.split(X, y, groups=groups):
...     print("%s %s" % (train, test))
[0 1 2 3 4 5] [6 7 8 9]
[0 1 2 6 7 8 9] [3 4 5]
[3 4 5 6 7 8 9] [0 1 2]

Каждый испытуемый находится в отдельном тестовом фолде, и один и тот же испытуемый никогда не попадает одновременно в тестовую и обучающую выборки. Обратите внимание, что из-за несбалансированности данных фолды имеют разный размер. Если необходимо сбалансировать доли классов в фолдах, лучше использовать StratifiedGroupKFold.

Ниже показана визуализация работы перекрёстной проверки.

../_images/sphx_glr_plot_cv_indices_007.png

Как и в случае с KFold, тестовые выборки, создаваемые GroupKFold, образуют полное разбиение всех данных.

Если задан параметр shuffle=False, GroupKFold пытается поместить в каждый фолд одинаковое количество объектов; если задан параметр shuffle=True, он пытается поместить в каждый фолд одинаковое количество различных групп (но не учитывает размеры групп).

3.1.2.4.2. Стратифицированная групповая K-блочная перекрёстная проверка

StratifiedGroupKFold — это схема перекрёстной проверки, объединяющая StratifiedKFold и GroupKFold. Её цель — по возможности сохранять распределение классов в каждом разбиении, при этом оставляя каждую группу внутри одного разбиения. Это может быть полезно при работе с несбалансированным набором данных, поскольку использование только GroupKFold может привести к перекошенным разбиениям.

Пример:

>>> from sklearn.model_selection import StratifiedGroupKFold
>>> X = list(range(18))
>>> y = [1] * 6 + [0] * 12
>>> groups = [1, 2, 3, 3, 4, 4, 1, 1, 2, 2, 3, 4, 5, 5, 5, 6, 6, 6]
>>> sgkf = StratifiedGroupKFold(n_splits=3)
>>> for train, test in sgkf.split(X, y, groups=groups):
...     print("%s %s" % (train, test))
[ 0  2  3  4  5  6  7 10 11 15 16 17] [ 1  8  9 12 13 14]
[ 0  1  4  5  6  7  8  9 11 12 13 14] [ 2  3 10 15 16 17]
[ 1  2  3  8  9 10 12 13 14 15 16 17] [ 0  4  5  6  7 11]
Примечания к реализации
  • В текущей реализации полное перемешивание в большинстве сценариев невозможно. При shuffle=True выполняются следующие действия:

    1. Все группы перемешиваются.
    2. Группы сортируются по стандартному отклонению классов с использованием стабильной сортировки.
    3. Отсортированные группы перебираются и распределяются по фолдам.

    Это означает, что перемешиваются только группы с одинаковым стандартным отклонением распределения классов; такой подход может быть полезен, если в каждой группе представлен только один класс.

  • Алгоритм жадно назначает каждую группу одному из n_splits тестовых наборов, выбирая набор, который минимизирует дисперсию распределения классов между тестовыми наборами. Сначала назначаются группы с наибольшей дисперсией частоты классов, затем — с наименьшей; то есть сначала назначаются большие группы, в которых преобладают один или несколько классов.
  • Это разбиение неоптимально в том смысле, что оно может приводить к несбалансированным разбиениям, даже если возможна идеальная стратификация. Если распределения классов в каждой группе достаточно близки, лучше использовать GroupKFold.

Ниже показана визуализация работы перекрёстной проверки для групп разного размера:

../_images/sphx_glr_plot_cv_indices_005.png

3.1.2.4.3. Исключение одной группы

LeaveOneGroupOut — это схема перекрёстной проверки, при которой в каждом разбиении исключаются объекты, принадлежащие одной определённой группе. Информация о группах задаётся массивом, в котором указана группа каждого объекта.

Таким образом, каждая обучающая выборка состоит из всех объектов, кроме тех, которые относятся к определённой группе. Это эквивалентно LeavePGroupsOut при n_groups=1 и эквивалентно GroupKFold, если n_splits равно числу уникальных меток, переданных параметру groups.

Например, если проводятся несколько экспериментов, LeaveOneGroupOut можно использовать для создания схемы перекрёстной проверки на основе этих экспериментов: обучающая выборка создаётся из объектов всех экспериментов, кроме одного:

>>> from sklearn.model_selection import LeaveOneGroupOut

>>> X = [1, 5, 10, 50, 60, 70, 80]
>>> y = [0, 1, 1, 2, 2, 2, 2]
>>> groups = [1, 1, 2, 2, 3, 3, 3]
>>> logo = LeaveOneGroupOut()
>>> for train, test in logo.split(X, y, groups=groups):
...     print("%s %s" % (train, test))
[2 3 4 5 6] [0 1]
[0 1 4 5 6] [2 3]
[0 1 2 3] [4 5 6]

Ещё один распространённый вариант применения — использование временных данных: например, в качестве групп можно задать год сбора объектов и таким образом выполнять перекрёстную проверку с разбиениями по времени.

3.1.2.4.4. Исключение P групп

LeavePGroupsOut похож на LeaveOneGroupOut, но для каждого набора обучающих и тестовых данных исключает объекты, относящиеся к \(P\) группам. Исключаются все возможные комбинации из \(P\) групп, поэтому тестовые выборки будут пересекаться при \(P>1\).

Пример исключения двух групп:

>>> from sklearn.model_selection import LeavePGroupsOut

>>> X = np.arange(6)
>>> y = [1, 1, 1, 2, 2, 2]
>>> groups = [1, 1, 2, 2, 3, 3]
>>> lpgo = LeavePGroupsOut(n_groups=2)
>>> for train, test in lpgo.split(X, y, groups=groups):
...     print("%s %s" % (train, test))
[4 5] [0 1 2 3]
[2 3] [0 1 4 5]
[0 1] [2 3 4 5]

3.1.2.4.5. Групповое разбиение с перемешиванием

Итератор GroupShuffleSplit сочетает в себе принципы ShuffleSplit и LeavePGroupsOut и создаёт последовательность случайных разбиений, в каждом из которых исключается подмножество групп. Каждое разбиение на обучающую и тестовую выборки выполняется независимо, поэтому связь между последовательными тестовыми выборками не гарантируется.

Пример использования:

>>> from sklearn.model_selection import GroupShuffleSplit

>>> X = [0.1, 0.2, 2.2, 2.4, 2.3, 4.55, 5.8, 0.001]
>>> y = ["a", "b", "b", "b", "c", "c", "c", "a"]
>>> groups = [1, 1, 2, 2, 3, 3, 4, 4]
>>> gss = GroupShuffleSplit(n_splits=4, test_size=0.5, random_state=0)
>>> for train, test in gss.split(X, y, groups=groups):
...     print("%s %s" % (train, test))
...
[0 1 2 3] [4 5 6 7]
[2 3 6 7] [0 1 4 5]
[2 3 4 5] [0 1 6 7]
[4 5 6 7] [0 1 2 3]

Ниже показана визуализация работы перекрёстной проверки.

../_images/sphx_glr_plot_cv_indices_011.png

Этот класс полезен, если требуется поведение LeavePGroupsOut, но групп настолько много, что генерация всех возможных разбиений с исключением \(P\) групп потребовала бы чрезмерных вычислительных ресурсов. В такой ситуации GroupShuffleSplit предоставляет случайную выборку (с возвращением) разбиений на обучающие и тестовые данные, создаваемых LeavePGroupsOut.

3.1.2.5. Использование итераторов перекрёстной проверки для разделения данных на обучающую и тестовую выборки

Описанные выше функции групповой перекрёстной проверки также могут быть полезны для разделения набора данных на обучающую и тестовую подвыборки. Обратите внимание, что вспомогательная функция train_test_split является обёрткой над ShuffleSplit, поэтому она поддерживает только стратифицированное разбиение (с использованием меток классов) и не учитывает группы.

Чтобы разделить данные на обучающую и тестовую выборки, используйте индексы обучающей и тестовой подвыборок, возвращаемые генератором метода split() средства разбиения для перекрёстной проверки. Например:

>>> import numpy as np
>>> from sklearn.model_selection import GroupShuffleSplit

>>> X = np.array([0.1, 0.2, 2.2, 2.4, 2.3, 4.55, 5.8, 0.001])
>>> y = np.array(["a", "b", "b", "b", "c", "c", "c", "a"])
>>> groups = np.array([1, 1, 2, 2, 3, 3, 4, 4])
>>> train_indx, test_indx = next(
...     GroupShuffleSplit(random_state=7).split(X, y, groups)
... )
>>> X_train, X_test, y_train, y_test = \
...     X[train_indx], X[test_indx], y[train_indx], y[test_indx]
>>> X_train.shape, X_test.shape
((6,), (2,))
>>> np.unique(groups[train_indx]), np.unique(groups[test_indx])
(array([1, 2, 4]), array([3]))

3.1.2.6. Перекрёстная проверка данных временных рядов

Данные временных рядов характеризуются корреляцией между близкими по времени наблюдениями (автокорреляцией). Однако классические методы перекрёстной проверки, такие как KFold и ShuffleSplit, предполагают, что выборки независимы и одинаково распределены, и при работе с данными временных рядов приводят к необоснованной корреляции между обучающими и тестовыми экземплярами (что даёт плохие оценки ошибки обобщения). Поэтому очень важно оценивать нашу модель на данных временных рядов по «будущим» наблюдениям, наименее похожим на те, которые использовались для обучения модели. Одно из решений этой задачи предлагает TimeSeriesSplit.

3.1.2.6.1. Разделение временных рядов

TimeSeriesSplit — это вариант k-блочной перекрёстной проверки, который возвращает первые \(k\) блоков в качестве обучающей выборки, а \((k+1)\)-й блок — в качестве тестовой выборки. Обратите внимание, что, в отличие от стандартных методов перекрёстной проверки, каждая следующая обучающая выборка является надмножеством предыдущей. Кроме того, все избыточные данные добавляются в первый обучающий блок, который всегда используется для обучения модели.

Этот класс можно использовать для перекрёстной проверки выборок данных временных рядов, наблюдаемых через фиксированные интервалы времени. Действительно, блоки должны представлять одинаковые промежутки времени, чтобы метрики для разных блоков можно было сравнивать.

Пример перекрёстной проверки временного ряда с 3 блоками на наборе данных из 6 выборок:

>>> from sklearn.model_selection import TimeSeriesSplit

>>> X = np.array([[1, 2], [3, 4], [1, 2], [3, 4], [1, 2], [3, 4]])
>>> y = np.array([1, 2, 3, 4, 5, 6])
>>> tscv = TimeSeriesSplit(n_splits=3)
>>> print(tscv)
TimeSeriesSplit(gap=0, max_train_size=None, n_splits=3, test_size=None)
>>> for train, test in tscv.split(X):
...     print("%s %s" % (train, test))
[0 1 2] [3]
[0 1 2 3] [4]
[0 1 2 3 4] [5]

Ниже показано, как выполняется перекрёстная проверка.

../_images/sphx_glr_plot_cv_indices_013.png

3.1.3. Примечание о перемешивании

Если порядок данных не является произвольным (например, выборки с одинаковой меткой класса расположены подряд), предварительное перемешивание может быть необходимо для получения осмысленного результата перекрёстной проверки. Однако для выборок, которые не являются независимыми и одинаково распределёнными, может быть верно обратное. Например, если выборки соответствуют новостным статьям, упорядоченным по времени публикации, перемешивание данных, скорее всего, приведёт к переобученной модели и завышенной оценке валидации: модель будет проверяться на выборках, искусственно похожих (близких по времени) на обучающие.

Некоторые итераторы перекрёстной проверки, например KFold, имеют встроенную возможность перемешивать индексы данных перед их разделением. Обратите внимание:

  • Это требует меньше памяти, чем непосредственное перемешивание данных.
  • По умолчанию перемешивание не выполняется, в том числе при (стратифицированной) k-блочной перекрёстной проверке, задаваемой с помощью cv=some_integer для cross_val_score, поиска по сетке и т. д. Помните, что train_test_split по-прежнему возвращает случайное разбиение.
  • Параметр random_state по умолчанию имеет значение None, то есть перемешивание будет отличаться при каждом проходе по KFold(..., shuffle=True). Однако GridSearchCV использует одинаковое перемешивание для каждого набора параметров, проверяемого одним вызовом метода fit.
  • Чтобы получать одинаковые результаты при каждом разбиении, задайте для random_state целое число.

Дополнительные сведения о том, как управлять случайностью разбиений для перекрёстной проверки и избегать распространённых ошибок, см. в разделе Управление случайностью.

3.1.4. Перекрёстная проверка и выбор модели

Итераторы перекрёстной проверки также можно использовать для непосредственного выбора модели с помощью поиска по сетке, чтобы подобрать оптимальные гиперпараметры модели. Эта тема рассматривается в следующем разделе: Настройка гиперпараметров оценивателя.

3.1.5. Оценка перестановочного теста

permutation_test_score предлагает ещё один способ оценить производительность предиктора. Функция возвращает p-значение, полученное с помощью перестановочного теста, которое показывает, насколько вероятно, что наблюдаемая производительность оценивателя могла быть получена случайно. Нулевая гипотеза этого теста состоит в том, что оцениватель не использует статистическую зависимость между признаками и целевыми значениями для получения правильных предсказаний на отложенных данных. permutation_test_score строит нулевое распределение, вычисляя n_permutations различных перестановок данных. При каждой перестановке целевые значения случайным образом перемешиваются, тем самым устраняя любую зависимость между признаками и целевыми значениями. Выходное p-значение — это доля перестановок, оценка перекрёстной проверки в которых не ниже истинной оценки, вычисленной без перестановки целевых значений. Для получения надёжных результатов значение n_permutations обычно должно быть больше 100, а значение cv — соответствовать 3–10 блокам.

Низкое p-значение свидетельствует о том, что набор данных содержит реальную зависимость между признаками и целевыми значениями и что оцениватель смог использовать эту зависимость для получения хороших результатов. Высокое p-значение, напротив, может быть обусловлено одной из следующих причин:

  • отсутствием зависимости между признаками и целевыми значениями (то есть отсутствием систематической связи, а все наблюдаемые закономерности, вероятно, возникли случайно);
  • или неспособностью оценивателя использовать зависимость в данных (например, из-за недообучения).

Во втором случае использование более подходящего оценивателя, способного использовать структуру данных, приведёт к более низкому p-значению.

Перекрёстная проверка даёт представление о том, насколько хорошо оцениватель обобщает данные, оценивая диапазон ожидаемых оценок. Однако оцениватель, обученный на наборе данных высокой размерности без структуры, всё же может случайно показать на перекрёстной проверке результат лучше ожидаемого. Обычно это может происходить на небольших наборах данных, содержащих менее нескольких сотен выборок. permutation_test_score позволяет определить, обнаружил ли оцениватель реальную зависимость между признаками и целевыми значениями, и помогает оценить его производительность.

Важно отметить, что этот тест может давать низкие p-значения, даже если структура данных выражена слабо, поскольку в соответствующих переставленных наборах данных структура полностью отсутствует. Поэтому этот тест позволяет лишь определить, превосходит ли модель случайное угадывание с достаточной надёжностью.

Наконец, permutation_test_score вычисляется полным перебором и внутри обучает (n_permutations + 1) * n_cv моделей. Поэтому его применение оправдано только для небольших наборов данных, на которых обучение отдельной модели выполняется очень быстро. Параметр n_jobs позволяет распараллелить вычисления и тем самым ускорить их.

Примеры

  • Проверка значимости оценки классификации с помощью перестановок
Литература
  • Ojala и Garriga. Перестановочные тесты для изучения производительности классификатора. J. Mach. Learn. Res. 2010.

© 2007–2026 The scikit-learn developers
Licensed under the 3-clause BSD License.
https://scikit-learn.org/1.9/modules/cross_validation.html

Spec-Zone.ru

Настройки Оффлайн Что нового Помощь О нас
Spec-Zone .ru
спецификации, руководства, описания, API