3.1. Кросс-валидация (Cross-validation или перекрестная проверка): оценка эффективности оценок¶
Изучение параметров функции предсказания и ее тестирование на одних и тех же данных - методологическая ошибка: модель, которая просто повторяет метки образцов, которые она только что видела, будет иметь отличную оценку, но не сможет предсказать ничего полезного на еще не увиденных данных.
Такая ситуация называется переобучение (overfitting). Чтобы избежать ее, обычно при проведении эксперимента по машинному обучению (с учителем) часть имеющихся данных выделяется в качестве тестового набора X_test, y_test
.
Обратите внимание, что слово “эксперимент” не предназначено для обозначения только академического использования, поскольку даже в коммерческих условиях машинное обучение обычно начинается экспериментально.
Вот блок-схема типичного рабочего процесса кросс-валидации при обучении модели. Лучшие параметры могут быть определены с помощью методов grid search.
В scikit-learn случайное разбиение на обучающий и тестовый наборы может быть быстро вычислено с помощью вспомогательной функции train_test_split
.
Давайте загрузим набор данных Ирис, чтобы обучить с помощью SVM:
>>> 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, все еще существует риск переобучения на тестовом наборе, поскольку параметры могут быть подстроены, пока модель не будет работать оптимально.
Таким образом, знания о тестовом наборе могут “просочиться” (Утечки (“лики”) в данных) в модель, и метрики оценки перестанут отражать эффективность обобщения.
Чтобы решить эту проблему, можно выделить еще одну часть набора данных в качестве так называемого “валидационного набора”: обучение проходит на обучающем наборе, после чего оценка выполняется на валидационном наборе, и когда эксперимент кажется успешным, окончательная оценка может быть выполнена на тестовом наборе.
Однако, разбивая имеющиеся данные на три набора, мы резко сокращаем количество образцов, которые могут быть использованы для обучения модели, и результаты могут зависеть от конкретного случайного выбора пары наборов (обучающий, проверочный).
Решением этой проблемы является процедура, называемая Кросс-валидация (Перекрёстная проверка) (Cross-validation - CV для краткости).
Тестовый набор по-прежнему должен использоваться для окончательной оценки, но при выполнении CV валидационный набор больше не нужен.
В базовом подходе, называемом k-фолдовым CV, обучающее множество разбивается на k меньших множеств (другие подходы описаны ниже, но в целом следуют тем же принципам). Для каждой из k “фолдов” выполняется следующая процедура:
Обучается модель, используя \(k-1\) фолдов в качестве обучающих данных;
полученная модель проверяется на оставшейся части данных (т.е. используется в качестве тестового набора для вычисления такой меры эффективности, как точность (accuracy) ).
Мера эффективности, полученная в результате k-фолдовой кросс-валидации, представляет собой среднее значение величин, вычисленных в цикле.
Этот подход может быть вычислительно дорогим, но при этом не тратится слишком много данных (как в случае с фиксированием произвольного проверочного набора), что является важным преимуществом в таких задачах, как обратный вывод, где количество выборок очень мало.
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-score практически равны.
Когда аргумент 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...])
3.1.1.1. Функция cross_validate и оценка по нескольким метрикам¶
Функция cross_validate
отличается от cross_val_score
двумя способами:
Позволяет указать несколько метрик для оценки.
Она возвращает dict, содержащий fit-times, score-times (и, опционально, тренировочные баллы, обученные оценки, индексы разбиения тренировки и теста) в дополнение к тестовому баллу.
Для оценки по одной метрике, когда параметр скоринга является строкой, вызываемым или None, ключами будут - ['test_score', 'fit_time', 'score_time']
.
А для оценки по нескольким метрикам возвращаемое значение будет представлять собой dict со следующими ключами
['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. ])
Или как dict, сопоставляющий имя метрики оценки с предопределенной или пользовательской функцией скоринга:
>>> 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
подходит для:
Визуализации предсказаний, полученных с помощью разных моделей.
Смешивания моделей: Когда предсказания одного обученной модели используются для обучения другой модели в ансамблевых методах.
Доступные итераторы кросс-валидации представлены в следующем разделе.
3.1.2. Итераторы кросс-валидации¶
В следующих разделах перечислены утилиты для генерации индексов, которые можно использовать для создания разбиений набора данных в соответствии с различными стратегиями кросс-валидации.
3.1.2.1. Итераторы кросс-валидации для i.i.d. данных¶
Предположить, что некоторые данные являются независимыми и одинаково распределенными (Independent and Identically Distributed - i.i.d.), значит сделать предположение, что все образцы получены в результате одного и того же генеративного процесса и что генеративный процесс, как предполагается, не имеет памяти о прошлых сгенерированных образцах.
В таких случаях можно использовать следующие кросс-валидаторы.
Примечание
Хотя в теории машинного обучения принято считать, что данные являются i.i.d., на практике это предположение редко выполняется. Если известно, что выборки были сгенерированы с помощью процесса, зависящего от времени, безопаснее использовать схему кросс-валидации с учетом временных рядов.
Аналогично, если мы знаем, что генеративный процесс имеет групповую структуру (образцы, собранные от разных субъектов, экспериментов, измерительных устройств), безопаснее использовать групповую кросс-валидацию.
3.1.2.1.1. K-фолд (K-fold)¶
KFold
делит все образцы на \(k\) групп образцов, называемых фолдами (если \(k = n\), это эквивалентно стратегии Lave One Out), одинакового размера (если это возможно). Функция предсказания обучается на \(k - 1\) фолдах, а оставшийся фолд используется для проверки.
Пример 2-фолдовой кросс-валидации на наборе данных с 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
не зависит от классов или групп.
Каждый фолд состоит из двух массивов: первый связан с тренировочным набором, а второй - с тестовым набором. Таким образом, можно создать обучающий/тестовый наборы, используя индексацию 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
повторяет K-фолд n раз. Его можно использовать, когда требуется запустить KFold
n раз, производя разные разбиения в каждом повторе.
Пример 2-кратного повторения K-фолд 2 раза:
>>> 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
повторяет стратифицированный K-фолд n раз с разной рандомизацией в каждом повторе.
3.1.2.1.3. Оставить одного без внимания (Leave One Out - 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.
3.1.2.1.4. Оставить P без внимания (Leave P Out - LPO)¶
LeavePOut
очень похож на LeaveOneOut
, так как он создает все возможные обучающие/тестовые наборы путем удаления \(p\) образцов из полного набора. Для \(n\) образцов создаются пары \({n \choose p}\) тренирочных-тестовых данных. В отличие от LeaveOneOut
и KFold
, тестовые наборы будут перекрываться для \(p > 1\).
Пример Leave-2-Out на наборе данных с 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
не влияют классы или группы.
Таким образом, ShuffleSplit
является хорошей альтернативой KFold
кросс-валидации, которая позволяет более тонко контролировать количество итераций и долю образцов по обе стороны от обучающего/тестового разбиения.
3.1.2.2. Итераторы кросс-валидации со стратификацией на основе меток классов¶
В некоторых задачах классификации может наблюдаться большой дисбаланс в распределении целевых классов: например, отрицательных образцов может быть в несколько раз больше, чем положительных. В таких случаях рекомендуется использовать стратифицированную выборку, как это реализовано в StratifiedKFold
и StratifiedShuffleSplit
, чтобы гарантировать, что относительные частоты классов примерно сохраняются в каждой тренировочном и проверочном фолде.
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) как в обучающем, так и в тестовом наборе данных.
Вот визуализация поведения кросс-валидации.
RepeatedStratifiedKFold
можно использовать для повторения стратификации K-фолд n раз с разной рандомизацией в каждом повторении.
3.1.2.2.2. Стратифицированное случайное разделение¶
StratifiedShuffleSplit
- это вариация ShuffleSplit, которая возвращает стратифицированные сплиты, т.е. которая создает сплиты с сохранением того же процента для каждого целевого класса, что и в полном наборе.
Вот визуализация поведения кросс-валидации.
3.1.2.3. Итераторы кросс-валидации для сгруппированных данных¶
Предположение i.i.d. нарушается, если лежащий в основе генеративный процесс дает группы зависимых выборок.
Такая группировка данных специфична для конкретной области. Примером могут служить медицинские данные, собранные от нескольких пациентов, с несколькими образцами, взятыми от каждого пациента. И такие данные, скорее всего, будут зависеть от отдельной группы. В нашем примере идентификатор пациента для каждого образца будет его групповым идентификатором.
В данном случае мы хотели бы узнать, хорошо ли модель, обученная на определенном наборе групп, обобщает данные по невидимым группам. Чтобы измерить это, нам нужно убедиться, что все образцы в проверочном фолде принадлежат группам, которые совсем не представлены в парном обучающем фолде.
Для этого можно использовать следующие сплиттеры кросс-валидации. Идентификатор группировки для образцов задается через параметр groups
.
3.1.2.3.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
.
Здесь представлена визуализация поведения кросс-валидации.
Как и в случае с KFold
, тестовые наборы из GroupKFold
образуют полное разбиение всех данных.
В отличие от KFold
, GroupKFold
вообще не рандомизируется, в то время как KFold
рандомизируется при shuffle=True
.
3.1.2.3.2. StratifiedGroupKFold¶
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
, происходит следующее:Все группы перетасовываются.
Группы сортируются по стандартному отклонению классов с помощью стабильной сортировки.
Отсортированные группы итерируются и назначаются фолдам.
Это означает, что будут перетасованы только группы с одинаковым стандартным отклонением распределения классов, что может быть полезно, когда в каждой группе есть только один класс.
Алгоритм жадно назначает каждую группу на один из n_splits тестовых наборов, выбирая тот тестовый набор, который минимизирует дисперсию распределения классов по тестовым наборам. Назначение групп происходит от групп с наибольшей дисперсией частот классов к наименьшей, т.е. сначала назначаются большие группы с пиком по одному или нескольким классам.
Это разбиение является неоптимальным в том смысле, что оно может привести к несбалансированным разбиениям даже при идеальной стратификации. Если у вас относительно близкое распределение классов в каждой группе, лучше использовать
GroupKFold
.
Вот визуализация поведения кросс-валидации для неравномерных групп:
3.1.2.3.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.3.4. Оставить P групп в стороне¶
LeavePGroupsOut
аналогичен LeaveOneGroupOut
, но удаляет выборки, связанные с \(P\) группами для каждого набора обучения/тестирования. Все возможные комбинации групп \(P\) удаляются, что означает, что тестовые наборы будут перекрываться для \(P>1\).
Пример Leave-2-Group Out:
>>> 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.3.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]
Вот визуализация поведения кросс-валидации.
Этот класс полезен, когда требуется поведение LeavePGroupsOut
, но количество групп достаточно велико, чтобы генерировать все возможные разбиения с удержанием групп \(P\) было бы непомерно дорого.
В таком случае GroupShuffleSplit
предоставляет случайную выборку (с заменой) из тренировочных/тестовых разбиений, сгенерированных LeavePGroupsOut
.
3.1.2.4. Предопределенные фолд-сплит / валидационные наборы¶
Для некоторых наборов данных уже существует предопределенное разбиение данных на обучающую и проверочную фолды или на несколько кросс-валидаций.
Используя PredefinedSplit
, можно использовать эти фолды, например, при поиске гиперпараметров.
Например, при использовании валидационного набора установите значение test_fold
в 0 для всех образцов, входящих в валидационный набор, и в -1 для всех остальных образцов.
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]
Вот визуализация поведения кросс-валидации.
3.1.3. Замечание о перемешивание¶
Если упорядочивание данных не является произвольным (например, выборки с одной и той же меткой класса расположены рядом), их перемешивание может оказаться необходимым для получения значимого результата кросс-валидации. Однако обратное может быть верно, если выборки не являются независимо и одинаково распределенными. Например, если выборки соответствуют новостным статьям и упорядочены по времени их публикации, то перетасовка данных, скорее всего, приведет к тому, что модель будет переобучена и получит завышенный результат валидации: она будет протестирована на выборках, искусственно похожих (близких по времени) на обучающие выборки.
Некоторые итераторы кросс-валидации, такие как KFold
, имеют встроенную опцию перемешивания индексов данных перед их разбиением. Обратите внимание на это:
При этом расходуется меньше памяти, чем при непосредственном перемешивании данных.
По умолчанию тасование не происходит, в том числе для (стратифицированной) K-фолд кросс-валидации, выполняемой при указании
cv=some_integer
вcross_val_score`
, поиска по сетке и т.д. Следует помнить, чтоtrain_test_split
по-прежнему возвращает случайное разбиение.Параметр
random_state
по умолчанию имеет значениеNone
, что означает, что перемешивание будет отличаться каждый раз, когда выполняется итерацияKFold(..., shuffle=True)
. ОднакоGridSearchCV
будет использовать одно и то же перемешивание для каждого набора параметров, подтвержденных одним вызовом его методаfit
.Чтобы получить одинаковые результаты для каждого сплита, задайте
random_state
целое число.
Подробнее о том, как управлять случайностью сплиттеров cv и избегать распространенных ошибок, см. в Управление случайностью.
3.1.4. Кросс-валидация и выбор модели¶
Итераторы кросс-валидации можно также использовать для прямого выбора модели с помощью поиска оптимальных гиперпараметров модели по сетке. Этому посвящен следующий раздел: Настройка гиперпараметров модели.
3.1.5. Результат теста перестановки¶
permutation_test_score
предлагает другой способ оценки эффективности классификаторов. Он предоставляет p-value, основанное на перестановке, которое показывает, насколько вероятно, что наблюдаемая производительность классификатора была бы получена случайно. Нулевая гипотеза в этом тесте состоит в том, что классификатор не может использовать статистическую зависимость между признаками и метками, чтобы делать правильные предсказания на оставленных данных.
permutation_test_score
генерирует нулевое распределение, вычисляя n_permutations
различных перестановок данных. В каждой перестановке метки случайным образом перемешиваются, что устраняет любую зависимость между признаками и метками. Выходное p-value - это доля перестановок, для которых средняя оценка кросс-валидации, полученная моделью, лучше, чем оценка кросс-валидации, полученная моделью на исходных данных. Для получения надежных результатов n_permutations
обычно должно быть больше 100, а cv
- в пределах 3-10 фолдах.
Низкое значение p-value свидетельствует о том, что набор данных содержит реальную зависимость между признаками и метками и классификатор смог использовать ее для получения хороших результатов. Высокое значение p-value может быть связано с отсутствием зависимости между признаками и метками (нет разницы в значениях признаков между классами) или с тем, что классификатор не смог использовать зависимость в данных. В последнем случае использование более подходящего классификатора, способного использовать структуру данных, приведет к снижению p-value.
Кросс-валидация дает информацию о том, насколько хорошо обобщает классификатор, а именно диапазон ожидаемых ошибок классификатора. Однако классификатор, обученный на наборе данных высокой размерности без какой-либо структуры, может оказаться лучше ожидаемого результата при кросс-валидации, просто по случайности. Как правило, это происходит с небольшими наборами данных, содержащими менее нескольких сотен образцов.
permutation_test_score
предоставляет информацию о том, нашел ли классификатор реальную структуру класса, и может помочь в оценке производительности классификатора.
Важно отметить, что этот тест дает низкие p-value даже при наличии слабой структуры в данных, поскольку в соответствующих перемешанных наборах данных нет абсолютно никакой структуры. Поэтому этот тест способен показать только, когда модель достоверно превосходит случайное угадывание.
Наконец, permutation_test_score
вычисляется с помощью грубой силы и внутренне соответствует моделям (n_permutations + 1) * n_cv
. Поэтому он применим только для небольших наборов данных, для которых обучение отдельной модели происходит очень быстро.