Машинное обучение невозможно: обучите 1 миллиард образцов за 5 минут на своем ноутбуке с помощью Vaex и Scikit-Learn

Сделайте свой ноутбук похожим на суперкомпьютер.

«Данные - это новая нефть». Независимо от того, согласны ли вы с этим утверждением, гонка за сбор и использование данных продолжается уже некоторое время. Фактически, сегодня технических гигантов объединяет их способность полностью использовать огромное количество данных, которые они собирают. У них есть знания, рабочая сила и ресурсы для анализа миллиардов точек данных, обучения и развертывания различных моделей машинного обучения в любом масштабе, которые затем влияют на бесчисленное количество людей по всей нашей планете.

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

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

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

Проблема: спрогнозировать продолжительность поездки на такси

В качестве примера предположим, что мы хотим помочь таксомоторной компании спрогнозировать, сколько времени может занять поездка в Нью-Йорке. Мы будем использовать набор данных New York City Taxi, который содержит информацию о более чем 1 миллионе поездок на такси, совершенных в период с 2009 по 2015 год узнаваемыми Yellow Taxis. В необработанном виде данные предоставляются Комиссией по такси и лимузинам Нью-Йорка (TLC) и могут быть загружены с их веб-сайтов. Если вас просто интересует блокнот Jupyter, использованный в качестве основы для этой статьи, вы можете щелкнуть здесь.

Подготовка данных с Vaex

Чтобы управлять большим объемом данных, мы будем использовать Vaex, Python библиотеку DataFrame с открытым исходным кодом. Используя такие концепции, как отображение памяти, ленивые оценки и эффективные алгоритмы вне ядра, Vaex может легко обрабатывать наборы данных, которые в противном случае были бы слишком большими, чтобы поместиться в ОЗУ.

Начнем с открытия данных. Для удобства я объединил данные о такси за 7 лет в один файл HDF5. Несмотря на то, что размер файла на диске превышает 100 ГБ, открытие его с помощью Vaex происходит мгновенно:

Проверка данных выполняется так же быстро. Поскольку данные отображаются в памяти, Vaex сразу знает, где искать, считывая только те части, которые необходимы. Для простого предварительного просмотра, как показано ниже, с диска считываются только первые и последние 5 строк.

Работать с набором данных New York Taxi с Vaex довольно просто, несмотря на то, что на диске он превышает 100 ГБ и содержит более 1,1 миллиарда записей. Эта статья рассматривает основы Vaex и представляет исследовательский анализ данных того же самого набора данных.

Прежде чем мы начнем, давайте разделим данные на поезд и тестовый набор. Мы сделаем разбивку по годам так, чтобы поездки, проведенные в 2015 году, составляли набор тестов, а все предыдущие - учебные. Набор данных упорядочен по годам, поэтому мы можем выполнить разделение, просто разрезав DataFrame.

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

Обработка данных

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

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

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

Основная идея фильтрации состоит в том, чтобы удалить выбросы и возможные ошибочные входные данные, чтобы модель не слишком «отвлекалась» на несколько аномальных выборок. После применения всех фильтров обучающая выборка включает «всего» 812 816 595 поездок на такси.

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

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

Обратите внимание на метод .jit_numba в конце вызова функции. Вычисление вычислительно затратных выражений, подобных тем, которые определены выше, можно ускорить за счет своевременной компиляции с помощью Numba. Если на вашем компьютере установлена ​​более новая видеокарта NVIDIA, вы можете использовать CUDA, вызвав метод .jit_cuda для дополнительного ускорения. Используя .jit_numba в одних выражениях и .jit_cuda в других, вы можете одновременно использовать как процессор, так и графический процессор для достижения максимальной производительности.

Преобразование данных (vaex.ml)

Прежде чем передавать данные в какую-либо модель, мы должны убедиться, что они соответствующим образом преобразованы, чтобы получить от них максимальную отдачу. Vaex содержит пакет vaex.ml, который реализует множество стандартных преобразований данных, таких как PCA, категориальные кодировщики и числовые масштабирующие устройства. Все преобразования вызываются с помощью знакомого API scikit-learn, распараллеливаются и выполняются вне ядра.

Давайте сосредоточимся на местах получения и возврата. Во многих городах США улицы образуют сетку. Это хорошо видно в районе Манхэттена. Одна из идей повышения производительности модели - применить набор преобразований PCA к точкам посадки и высадки. Это приведет к тому, что улицы, прорисованные множеством точек посадки и высадки, будут выровнены с естественными географическими осями (долгота и широта), а не под углом к ​​ним.

Обратите внимание, что при использовании vaex.ml весь DataFrame передается методам .fit, .transform или .fit_transform преобразователя, в то время как функции, к которым применяется преобразование, указываются во время создания экземпляра этого преобразователя. Наиболее важно то, что метод .transform возвращает неглубокую копию DataFrame, в которой результат преобразования сохраняется в форме виртуальных столбцов. Это позволяет нам легко визуализировать результаты преобразований PCA.

Давайте сместим наше внимание на функции pickup_time, pickup_day и pickup_month, которые мы определили ранее. Ключевым свойством этих характеристик является их цикличность по своей природе, то есть январь так же близок к февралю, как и к декабрю. Таким образом, мы будем использовать CycleTransformer, реализованный в vaex.ml, который по существу рассматривает каждую функцию как угол φ в единичном круге в полярных координатах (ρ = 1, φ). Преобразователь, чем вычисляет 𝑥 и 𝑦 проекцию радиуса ρ, таким образом получая два новых компонента для каждого объекта. Этот трюк отлично сохраняет относительное расстояние между разными значениями, поскольку это просто преобразование координат. Подробнее об этом подходе вы можете прочитать в этом посте.

Мы применяем тот же трюк к углу направления, так как это тоже циклическая характеристика. Давайте изобразим особенности, полученные из столбца «pickup_time» ,, чтобы убедиться в целесообразности преобразований.

Мы видим, что преобразованные элементы образуют идеальный единичный круг. Обратите внимание, что, в отличие от обычных настенных часов, все 24 часа представлены на круге. Например, «полночь» имеет координаты (x, y) = (1, 0), «6 часов» находится в (x, y) = (0, 1), , а полдень - в (x, y) = (-1, 0).

Наконец, у нас есть только одна непрерывная функция, arc_distance, на которую мы не обращали никакого внимания. Мы просто применим к нему стандартное масштабирование.

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

Обучение модели: Vaex + scikit-learn

На этом этапе мы готовы к этапу обучения модели. Хотя vaex.ml еще не реализует никаких прогнозных моделей, он предоставляет интерфейс для нескольких популярных библиотек машинного обучения в экосистеме Python, таких как scikit-learn и xgboost. Преимущество использования этих моделей через Vaex заключается в том, что при очистке данных, проектировании функций и предварительной обработке не тратится впустую память, что позволяет максимально увеличить доступную оперативную память для обучения модели.

Для текущей проблемы нам по-прежнему требуется значительно больше оперативной памяти, чем доступно на обычном ноутбуке или настольном компьютере, если бы мы материализовали все функции, которые мы собираемся использовать для обучения. Чтобы обойти эту проблему, мы будем использовать SGDRegressor, доступный через scikit-learn. SGDRegressor принадлежит к семейству прогнозных моделей в scikit-learn, которые, помимо обычного .fit, также реализуют метод .partial_fit. Это позволяет обучать модель на пакетах данных, по сути, делая ее неосновной.

С помощью оболочки, реализованной в vaex.ml, можно легко отправлять пакеты данных из Vaex DataFrame в модель scikit-learn. Использование простое. Сначала создайте экземпляр SGDRegressor из scikit-learn, задав его параметры стандартным способом. Затем создайте экземпляр IncrementalPredictor, реализованный в vaex.ml, одновременно предоставив основную модель, список имен функций, имя целевого столбца и размер пакета. В принципе, можно использовать любую модель, если она имеет .partial_fit метод и следует соглашению API scikit-learn. Посредством размера пакета можно контролировать использование оперативной памяти. При желании можно также указать количество эпох, то есть сколько раз данные в каждом пакете просматриваются моделью и следует ли их перетасовывать или нет. Наконец, мы вызываем метод .fit в экземпляре IncrementalPredictor, куда передаем обучающий набор DataFrame.

Примечательно, что эта комбинация Vaex + scikit-learn привела к общему времени обучения всего за 7 минут на моем ноутбуке (MacBook Pro 15, 2018, Intel Core i7 с тактовой частотой 2,6 ГГц, 32 ГБ ОЗУ). Это включает в себя оценку на лету 14 используемых функций, все из которых являются виртуальными столбцами, и обучение модели scikit-learn. Кроме того, с набором параметров, показанных выше, использование ОЗУ никогда не превышало 4 ГБ, что оставляет мне достаточно места для запуска Slack.

Точно такая же установка, как показано выше, также была выполнена Маартеном Бредделсом на своем ноутбуке Lenovo Thinkpad X1 Extreme (Intel Core i7–8750H, 32 ГБ ОЗУ), и этап обучения занял менее 5 минут. !

IncrementalPredictor - это не просто удобный класс для передачи фреймов Vaex DataFrames в модели scikit-learn. Это также vaex.ml преобразователь, означающий, что метод .transform возвращает неглубокую копию DataFrame, содержащего прогнозы модели, в виде виртуального столбца. Это относится к любой оболочке модели, реализованной в vaex.ml. Это не только не требует дополнительных затрат памяти, но и делает весьма удобным постобработку результата и даже создание ансамблей, а также вычисление различных показателей производительности и диагностических графиков, как мы вскоре увидим.

Наконец, давайте ограничим прогнозы пределами данных, на которых была обучена модель. Это сделано для предотвращения оценки нефизической или нереалистичной продолжительности для некоторых экзотических комбинаций значений признаков. Таким образом, давайте установим любые прогнозы менее 3 минут на 3, а любые прогнозы более 25 минут на 25.

А что насчет трубопровода?

Теперь, когда модель обучена, мы хотели бы узнать, насколько хорошо она работает, применив ее к набору тестов. Вы могли заметить, что, в отличие от других библиотек, мы явно не создавали конвейер для распространения всех этапов очистки и преобразования данных. Фактически, с Vaex конвейер автоматически создается при выполнении исследования и преобразования данных. Каждый фрейм данных Vaex содержит состояние, которое представляет собой сериализуемый объект, содержащий все примененные к нему преобразования: фильтрацию, создание новых виртуальных столбцов, преобразование столбцов).

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

Состояние также можно сериализовать и прочитать с диска, что значительно упрощает задачу развертывания модели.

Диагностика модели

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

Обратите внимание, что расчет 4 статистических данных, показанных на рисунке выше, занял ~ 4,5 минуты. Это довольно впечатляюще, учитывая, что вычисление требует оценки всех функций, которые затем были переданы модели для получения прогнозов, и это было сделано для более чем 1 миллиарда выборок между поездом и тестовыми наборами, дважды.

Средняя абсолютная ошибка, оцененная на тестовом наборе, составляет чуть более 3,5 минут. Является ли это допустимой ошибкой, вероятно, следует проконсультироваться с частым пассажиром такси или водителем такси в Нью-Йорке. Мы не должны принимать такую ​​статистику за чистую монету, поэтому давайте создадим пару диагностических графиков. Построим график распределения фактической продолжительности поездки по сравнению с предполагаемой, а также абсолютную ошибку прогнозируемой продолжительности.

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

На правой панели показано количество поездок на ячейку абсолютной ошибки. Фактически, 77% всей продолжительности поездки в тестовой выборке были оценены с абсолютной погрешностью менее 5 минут. Хотя текущая модель может служить хорошей отправной точкой, есть много возможностей для улучшения. Хорошая новость заключается в том, что с Vaex у нас есть инструмент, который позволяет нам проявлять творческий подход и невероятно эффективно опробовать многие функции и комбинации предварительной обработки.

Говоря о модели, обученный экземпляр SGDRegressor легко доступен как атрибут IncrementalPredictor. К нему легко получить доступ, и мы можем использовать его, например, для проверки относительной важности функций, используемых для обучения модели.

А как насчет производства?

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

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

Чтобы преодолеть это препятствие, мы можем использовать тот факт, что в Vaex отфильтрованный DataFrame фактически все еще содержит все данные, а также выражение, которое определяет, какие строки отфильтрованы, а какие сохранены. В то время как в других распространенных библиотеках, таких как Numpy и Pandas, можно только отфильтровать большее количество строк, в Vaex можно фактически вернуть строки, используя оператор «или» в фильтре.

Давайте реализуем эту идею для нашей задачи. Мы добавляем переменную под названием «production» в обучающий DataFrame и устанавливаем для нее значение False. Затем мы добавляем дополнительный фильтр, который в данном случае является просто значением переменной. Однако теперь мы используем «или» вместо оператора «и» по умолчанию, который используется в таких случаях, как df3 = df2[df2.x < 5]. Идея проста: если этот последний фильтр установлен на False, он не повлияет на другие фильтры, так как его режим работы - «или». Однако, если он установлен на True, что можно сделать, изменив значение переменной «production», это сделает недействительными все другие фильтры, и, таким образом, любая точка данных может пройти.

В приведенном выше блоке кода показано, как мы можем включить и изменить переменную «production», чтобы фильтры отключались при необходимости. Мы можем легко подтвердить, что в этом случае тестовый DataFrame содержит столько же выборок, сколько было, когда мы предварительно сформировали разделение на поезд / тест, с прогнозом для каждой из них. Вычисление средней абсолютной ошибки в этом случае дает более высокий результат - 13 минут, что неудивительно, учитывая, что модель плохо обучена, чтобы давать прогнозы для многих выбросов или ошибочных выборок, которые теперь являются частью тестового набора.

Наше видение будущего

Надеюсь, мне удалось показать вам, насколько легко и удобно создать конвейер машинного обучения с помощью Vaex, даже когда обучающие данные насчитывают более миллиарда выборок и занимают более 100 ГБ дискового пространства. Я уверен, что вы найдете гораздо больше творческих способов использования Vaex для улучшения модели, представленной в этой статье, а также для создания множества новых для различных других задач и вариантов использования.

Мы, команда Vaex, верим в будущее, в котором наука о данных и машинное обучение могут быть сокращены как удобно, так и эффективно, даже для больших данных, с помощью бесплатных инструментов с открытым исходным кодом. Мы стремимся улучшить интеграцию Vaex с основами стека науки о данных Python и прилагаем значительные усилия для сближения Vaex и scikit-learn. Следите за обновлениями, определенно впереди ждут и другие интересные вещи!

Удачной работы с данными!