Формируем тренировочный сэмпл данных при distribution shift

Моя цель - предложение широкого ассортимента товаров и услуг на постоянно высоком качестве обслуживания по самым выгодным ценам.
Дисклеймер: статья является переведенным продуктом автора Max’a Halforda. Перевод не чистый, а адаптивный. Такой, чтобы было понимание на любом рубеже знаний.



«Мои друзья и я недавно прошли квалификацию в финал Data Science Game 2017. Первой частью соревнований был Kaggle с датасетом от компании Deezer. Проблема состояла в решении задачи бинарной классификации: нужно было предсказать, собирается ли пользователь перейти к прослушиванию предложенной ему композиции.
Как и другие команды мы извлекли релевантные признаки и обучили XGBoost классификатор. Однако, мы сделали особенную вещь — подвыборку обучающего набора данных, такую, что он (обучающий набор) стал более репрезентативен для тестового набора.»


Одним из базовых требований к процессу обучения для успешной работы машинной модели является одинаковая природа распределений в тренировочном и тестовом наборах данных. Как грубый пример: модель обучена на пользователях 20-тилетнего возраста, а в тестовой выборке пользователи 60+ лет.
Здесь интуитивно естественно, что с возрастом, на котором модель не обучалась, она и не справится. Конечно, такой пример сугубо синтетический, но в реальности для весомых различий достаточно обучить модель на 20+ и попробовать заставить работать на 30+. Результат будет аналогичен.

Так происходит, потому что модели изучают распределения данных. Если распределения признака в обучающем и тестовом наборе совпадают, модель скажет вам «спасибо».

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

Смещение в распределениях по одной фиче может происходить по разным причинам. Наиболее интуитивный пример можно позаимствовать у Facebook.

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

Когда произошло разделение общего приложения Facebook на основную социальную сеть (ленту etc) и систему обмена сообщениями, время в основном приложении уменьшилось, то есть входящие наборы данных поменялись и уже не соответствовали прошлой ретроспективе. Математически, учитывая фичу времени модель станет предсказывать более низкий уровень лояльности, хотя в реальности это не так — перенос времени просто разделился на два приложения. Печально выходит.

Таким образом, искажение (distribution shift) возникает, когда распределение ретроспективных данных становится неактуальными для предсказания новых.

В датасете компании Deezer несоответствие распределений было в признаке, измеряющем количество прослушанных песен до момента решения задачи предсказания. И в общучающем, и в тестовом наборах данных данная фича имела экспоненциальное распределение. Однако в тестовом наборе данных оно было более выражено, поэтому среднее в тренировочном сете было ниже, чем в тестовом. После ресемплинга тренировочного распределения удалось повысить метрику ROC-AUC и подняться по рейтинга примерно на 20 пунктов.

Ниже приведен пример разницы распределений:

import numpy as np
import plotly.figure_factory as ff

train = np.random.exponential(2, size=100000)
test = np.random.exponential(1, size=10000)

distplot = ff.create_distplot([train, test], ['Train', 'Test'], bin_size=0.5)
distplot.update_layout(title_text='Распределения Test, Train')


""

Идея нивелирования distribution shift состоит в том, чтобы переформировать обучающий сэмпл так, чтобы он отражал тестовое распределение.

Давайте представим, что мы хотим создать подвыборку размером 50 000 наблюдений из нашего обучающего набора, чтобы она соответствовала распределению тестового. Что хочется сделать интуитивно?
Сделать так, чтобы объекты, чаще встречающиеся в тестовом наборе данных также часто встречались и в обучающем! Но как определить, какие объекты нужны более, а какие менее часто?
Весами!

Шаги действий будут примерно такие:

поделить числовую прямую распределения на равные интервалы (или корзины (bins)
посчитать количество объектов в каждой корзине (bin size)
для каждого наблюдения в корзине рассчитать его вес равный 1/(bin size)
создать подвыборку k с распределением, учитывающим веса (объекты с более высоким весом станут появляться в подвыборке чаще)

Перекладывая на код, мы производим следующие действия:
SAMPLE_SIZE = 50000
N_BINS = 300

# Задаем частоту корзин, иначе говоря получаем перцентили распределения тестового массива.
# Каждое значение полученное значение будет границей интервала корзины
step = 100 / N_BINS

test_percentiles = [
    np.percentile(test, q, axis=0)
    for q in np.arange(start=step, stop=100, step=step)
]

# Присваиваем каждой коризне набор объектов. 
# Функция возвращает индекс корзины, в которое входит значение
train_bins = np.digitize(train, test_percentiles)

# Считаем количество объектов в каждой корзине или сколько индекс i из входного массива,
# где 0 будет соответствовать количеству вхождений индекса нуль, 1 количеству вхождений индекса 1 и так до i индекса
train_bin_counts = np.bincount(train_bins)

# Считаем веса каждого объекта, единица делится на количество вхождений объектов в корзине
weights = 1 / np.array([train_bin_counts[x] for x in train_bins])

# Нормируем веса так, чтобы их сумма равнялась единице
weights_norm = weights / np.sum(weights)

np.random.seed(0)

sample = np.random.choice(train, size=SAMPLE_SIZE, p=weights_norm, replace=False)

distplot_with_sample = ff.create_distplot([train, test, sample], ['Train', 'Test', 'New train'], bin_size=0.5)
distplot_with_sample.update_layout(title_text='Распределения Test, Train, New train')


""

Новое распределение (зеленое) теперь стало лучше соответствовать распределению тестовой выборки (оранжевое). Аналогичные действия были использованы нами на соревновании — исходный датасет содержал 3 миллиона строк, размер новой выборки мы сгенерировали из 1.3 миллиона объектов. Данных стало меньше, но репрезентативность распределения улучшила качество обучения.

Несколько примечаний из личного опыта автора:

  • Количество корзин не играет большой роли, но чем меньше корзин, тем быстрее учится алгоритм (попробуйте изменить в примере количество корзин (N_BINS) на 3, 30 и вы увидите, что разница действительно невелика)
  • Что касается размера, то чем меньше разница между обучающим и тестовым распределениями, тем меньших размеров обучающей новой выборки достаточно, чтобы “повторить” тестовое распределение, по крайней мере если брать объекты без возвращения, избегая формирования дубликатов.
    (В нашем примере мы перераспределили частотность, за счет чего корзины более “набитые” объектами стали содержать больше значений, относительно менее “набитых” корзин. Это подняло распределение тк концентрация значений сменилась. На мой взгляд достаточность обучающего набора должна исходить из размера тестового сета, но это только примечание переводчика)


Алгоритм переформирования есть на гитхабе автора статьи (папка xam). В будущем автор планирует разбирать новые темы и делиться ими в блоге.

Надеюсь перевод и примечания были полезны и понятны. Жду вашей обратной связи в конструктивном формате. Спасибо за уделенное статье время.
Источник: https://habr.com/ru/post/526938/


Интересные статьи

Интересные статьи

Как развеять предубеждения потенциальных пользователей относительно безопасности публичных облаков? На помощь приходит технология Intel Software Guard Extensions (Intel SGX). Рассказываем...
Некоторое время назад я опубликовал свою статью о разработке велосипедного велосипеда, в которой описал причины, побудившие меня этим заняться. Если вкратце, то мне была нужна миниатюр...
Кто бы что ни говорил, но я считаю, что изобретение велосипедов — штука полезная. Использование готовых библиотек и фреймворков, конечно, хорошо, но порой стоит их отложить и создать ...
Бизнес-смыслы появились в Битриксе в начале 2016 года, но мало кто понимает, как их правильно использовать для удобной настройки интернет-магазинов.
Здравствуйте. Я уже давно не пишу на php, но то и дело натыкаюсь на интернет-магазины на системе управления сайтами Битрикс. И я вспоминаю о своих исследованиях. Битрикс не любят примерно так,...