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

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

Давайте посмотрим на один из таких наборов данных, Uber TLC FOIL Response. По сути, это свалка поездок Uber в Нью-Йорке за большую часть 2014 и 2015 годов. Дамп также содержит аналогичные данные для других поставщиков услуг такси и поездок, хотя и в гораздо меньшем масштабе - менее 200 тыс. Для крупнейшего набора данных поставщика по сравнению с миллионами. записей для Uber. Наконец, включены некоторые вспомогательные данные, такие как совокупная статистика и различные поисковые запросы по коду.

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

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

По сути, у нас есть выбор между наборами данных за 2014 и 2015 годы. Как поясняет README, разница заключается в следующем:

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

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

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

Базовый учебник по пандам

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

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

будет выглядеть так:

Обратите внимание, что значения столбца представлены как объект pandas.Series. В целом pandas предоставляет довольно стандартный ООП API - и DataFrame, и Series имеют множество средств доступа и методов для работы с данными. В частности, есть три DataFrame (или DF) метода, которые вы, вероятно, будете использовать очень часто.

Первый - .head(), который просто возвращает начальные строки DataFrame:

Здесь у нас всего 3 строки, поэтому печатается весь DF, но на практике вы будете использовать гораздо большие наборы данных. И это то, для чего .head() хорош - чтобы получить первую, немедленную «разновидность» данных.

Второй метод .info():

Это дает относительно подробный обзор DF, включая его размер в памяти. Мы уделяем особое внимание столбцу Dtype - каждый тип столбца имеет разные операторы, которые можно использовать с ним. Мы также видим, что конструктор DataFrame был достаточно умен, чтобы идентифицировать первый столбец как целочисленный. В остальном он выбрал object, который также является значением по умолчанию для столбцов «смешанного типа».

Наконец, есть .describe():

который отображает некоторую сводную статистику для каждого столбца (по умолчанию только числовые, поэтому мы используем include=’all’). Обратите внимание, что для числовых и номинальных значений доступны разные наборы информации. В целом, это еще один инструмент для дегустации данных - например, вы можете быстро определить, есть ли какие-либо значительные выбросы, или посмотреть, сколько уникальных значений содержится в номинальных столбцах.

Однако важно помнить, что этот API не является обычным API коллекций Python. Например, индексация работает так:

или по столбцам и строк:

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

На самом деле мы можем использовать средство доступа к индексу на DataFrame:

Это позволяет нам получить все точки данных для данного столбца по индексу. Более того, он возвращает объект вышеупомянутого типа Series:

которые мы будем широко использовать в нашем анализе.

И это потому, что понимание списков, еще один основной продукт программиста Python, также не одобряется в мире pandas. Вместо этого в игру вступают вышеупомянутые операторы, специфичные для Dtype:

В последнем примере мы явно используем str, метод доступа StringMethods, чтобы использовать pandas различные строковые методы, доступные на Series.

Причина, по которой вся песня и танец с индивидуальным API проста: в фоновом режиме pandas полагается на очень эффективный собственный код для быстрого выполнения операций с большими наборами данных. Поскольку мы будем запускать pandas вызовов на основе данных Uber FOIL за 2015 г., мы увидим, что большинство шагов занимает не более доли секунды - для более десятка миллионов записей. Замена этого кода на «стандартный Python» значительно замедлит его работу. Обратной стороной, конечно же, является то, что вам придется изучить совершенно новый API, включая необычные шаблоны доступа, такие как .str StringMethods выше.

На код

Давайте загрузим набор данных, распакуем его в папку с именем data, запустим Jupyter Notebook (с установленными pandas и scikit-learn) и загрузим набор данных. .

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

Результат примерно такой же, как и ожидалось:

Ух ты, 14 миллионов записей! Интересно, что для многих проблем, решаемых специалистами по анализу данных или инженерами по машинному обучению, это не только управляемо, но, в зависимости от их специализации, также может быть небольшим. Тем не менее, весь DataFrame занимает менее половины гигабайта ОЗУ, что означает около 32 байтов на строку - довольно эффективно.

Рассмотрим подробнее столбцы.

Dispatching_base_num и Affiliated_base_num являются кодами баз компаний, лицензированных Комиссией по такси и лимузинам Нью-Йорка. Например, B02617 - это код для базы Weiter, которая, как вы, вероятно, ожидаете, является одной из баз Uber (по какой-то причине названной в основном в честь немецких словосочетаний).

Pickup_date содержит красиво отформатированные (мы надеемся, для всех строк) дату и время ISO-8601. Это хорошо - будет легко выполнить синтаксический разбор во что-то еще, кроме object Dtype,, как мы увидим.

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

На этом фронте мы теперь хотим преобразовать абстрактные locationID в более значимую информацию. Для этого мы используем предоставленную справочную таблицу:

Таблица, как и ожидалось, содержит сопоставления из locationID в «главном» наборе данных как в районы, так и в их подразделения и / или PoI.

Мы объединяем два DataFrames с одной уловкой - сначала нам нужно переименовать столбец подстановки, так как он написан с заглавной буквы в DataFrame зоны такси (в отличие от «основного»):

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

Примечание: ни автор этого сообщения, ни SoftwareMill никоим образом не связаны с Uber.