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

Он также предполагает базовое понимание Python и библиотеки машинного обучения scikit-learn, и он был написан на записной книжке Jupyter под управлением Python 3.6 и sklearn 0.21. Набор данных, а также записную книжку можно получить в моей учетной записи Github или через поиск по набору данных Google.

1. Исследование и очистка данных

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

Index(['room_id', 'survey_id', 'host_id', 'room_type', 'country', 'city', 'borough', 'neighborhood', 'reviews', 'overall_satisfaction', 'accommodates', 'bedrooms', 'bathrooms', 'price', 'minstay', 'name', 'last_modified', 'latitude', 'longitude', 'location'], dtype='object')

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

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

  • средняя цена за ночь составляет около 88 евро.
  • цены варьируются от минимум 10 евро до 4203 евро.
  • стандартное отклонение цен составляет около 123 евро (!)

Распределение цен можно представить следующим образом:

Как мы видим, наше распределение цен концентрируется в интервале 300 евро с некоторыми записями для значений 4000 евро. Нанесение его на то место, где находится большая часть цен:

Из нашего представления выше ясно видно, что большинство цен на ночь в Лиссабоне будет стоить от 0 до 150 евро.

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

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

  • есть пустые столбцы: country, borough, bathrooms, minstay
  • такие записи, как host_id, survey_id, room_id, name, city, last_modified и survey_id, могут быть не столь актуальны для нашего предсказателя цен.
  • есть некоторые категориальные данные, которые мы не сможем изначально добавить к регрессии цены, такие как room_type и neighborhood (но мы вернемся к этим двум позже)
  • location может быть избыточным на данный момент, когда у нас есть и latitude, и longitude, и нам может потребоваться дальнейший вывод о природе формата этого поля.

Затем давайте продолжим разделение набора данных на:

  • один вектор Y, который будет содержать все реальные цены набора данных
  • на матрице X, которая содержит все функции, которые мы считаем релевантными для нашей модели

Этого можно добиться с помощью следующего фрагмента:

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

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

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

Еще одно любопытное наблюдение: расположение сильно влияет на цену и рейтинг. При нанесении на график долготы и широты мы можем получить квазигеографическое / пространственное распределение рейтингов вдоль Лиссабона:

Затем мы можем добавить эти данные на реальную карту Лиссабона, чтобы проверить распределение:

Как и ожидалось, большинство отзывов относятся к центру города, а группа отзывов уже актуальна наряду с недавним Parque das Nações. Северный более пригородный район, хоть и имеет несколько разрозненных мест, отзывы не такие высокие и распространенные, как в центре.

2. Разделение набора данных

Теперь, когда наш набор данных очищен должным образом, мы сначала разделим его на две части:

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

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

Для получения более подробной информации о переоборудовании, пожалуйста, обратитесь к https://en.wikipedia.org/wiki/Overfitting.

Чтобы избежать этого переобучения нашей модели тестовым данным, мы воспользуемся инструментом из sklearn под названием https://scikit-learn.org/stable/modules/generated/sklearn.model_selection. train_test_split.html , который по сути разделит наши данные на случайную последовательность обучающих и тестовых подмножеств:

Training set: Xt:(10183, 6) Yt:(10183,) 
Validation set: Xv:(3395, 6) Yv:(3395,) 
- 
Full dataset: X:(13578, 6) Y:(13578,)

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

3. Посадка деревьев решений

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

Пример визуализации, взятый из Википедии, может быть деревом решений вокруг прогноза выживания пассажиров Титаника:

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

В приведенном выше примере из Википедии легко проследить, как следует процесс принятия решения, и, поскольку вероятность выживания является здесь оценочным параметром, мы можем легко получить вероятность того, что «мужчина возрастом более 9,5 лет» выживет, когда «он не имеет братьев и сестер ».

(Для более глубокого понимания того, как строятся деревья решений для регрессии, я бы порекомендовал видео от StatQuest, названное Деревьями решений).

Давайте затем создадим нашу регрессию Дерева решений, используя реализацию sklearn:

DecisionTreeRegressor(criterion='mse', max_depth=None, max_features=None, max_leaf_nodes=None, min_impurity_decrease=0.0, min_impurity_split=None, min_samples_leaf=1, min_samples_split=2, min_weight_fraction_leaf=0.0, presort=False, random_state=42, splitter='best')

Мы можем проверить, как было построено дерево, в целях иллюстрации, на картинке ниже:

Пожалуйста, найдите здесь графическое представление сгенерированного дерева на Github.

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

Получаем следующие цены:

array([ 30., 81., 60., 30., 121.])

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

Для этого мы будем использовать показатель Средняя абсолютная ошибка (MAE). Мы можем рассматривать этот показатель как среднюю величину ошибки в наборе прогнозов. Его можно представить так:

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

Затем применим эту метрику к нашей модели, используя реализацию Scikit Learn:

42.91664212076583

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

Либо из-за небольшого набора данных, либо из-за наивности нашей модели этот результат неудовлетворителен.

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

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

Поскольку DecisionTreeRegressor sklearn позволяет нам указать максимальное количество конечных узлов в качестве гиперпараметра, давайте быстро попробуем оценить, есть ли значение, которое снижает нашу MAE:

(Size: 5, MAE: 42.6016036138866) 
(Size: 10, MAE: 40.951013502542885) 
(Size: 20, MAE: 40.00407688450048) 
(Size: 30, MAE: 39.6249335490541) 
(Size: 50, MAE: 39.038730827750555) 
(Size: 100, MAE: 37.72578309289501) 
(Size: 250, MAE: 36.82474862034445) 
(Size: 500, MAE: 37.58889602439078) 250

Затем давайте попробуем сгенерировать нашу модель, но с учетом вычисленного максимального размера дерева, и затем проверим его прогноз с новым пределом:

36.82474862034445

Таким образом, просто настроив максимальное количество гиперпараметров листовых узлов, мы могли бы получить значительное увеличение прогнозов нашей модели. Теперь мы улучшили в среднем (42.935 - 36.825) ** ~ 6,11 евро ** по ошибкам нашей модели.

4. Категориальные данные

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

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

1) Отбросить

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

Это был сценарий, который мы анализировали до сих пор, с MAE: 36.82474862034445.

2) Кодировка метки

Поэтому для кодирования меток мы предполагаем, что каждому значению присваивается уникальное целое число. Мы также можем выполнить это преобразование с учетом любого порядка / величины, которые могут иметь отношение к данным (например, рейтинги, просмотры и т. Д.). Давайте проверим простой пример с помощью препроцессора sklearn:

array([3, 3, 1, 0, 2])

Затем легко оценить преобразование, которое выполняет LabelEncoder, присвоив индекс массива подобранных данных:

array(['double room', 'shared room', 'single room', 'suite'], dtype='<U11')

Затем давайте применим к нашим категориальным данным эту технику предварительной обработки и проверим, как это влияет на наши прогнозы модели. Итак, наш новый набор данных будет:

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

['room_type', 'neighborhood']

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

Давайте теперь обучим и подгоним модель с преобразованными данными:

35.690195084932355

Затем мы улучшили наш предсказатель, закодировав наши категориальные данные, снизив MAE до ~ 35,69 евро.

3) Быстрое кодирование

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

array([[0., 0., 0., 1., 0., 0., 1.], [0., 1., 0., 0., 0., 1., 0.]])

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

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

  • добавить обратно исходные индексы строк, которые были потеряны во время преобразования
  • удалить исходные категориальные столбцы из исходных наборов train_X и validation_X
  • замените отброшенные столбцы нашим новым фреймом данных со всеми 26 возможными категориями

Теперь мы можем продолжить использование наших новых кодированных наборов в нашей модели:

36.97010930367817

Используя One Hot Encoding для наших категориальных данных, мы получаем MAE ~ 36.97EUR.

Этот результат может доказать, что One-Hot-Encoding не наилучшим образом подходит для обоих наших категориальных параметров по сравнению с Label Encoding и для обоих параметров одновременно. Тем не менее, этот результат все же позволил включить категориальные параметры с уменьшением начальной MAE.

5. Случайные леса

Из предыдущего раздела мы могли видеть, что с нашим деревом решений мы всегда балансируем между:

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

Мы можем представить себе «Случайный лес» как набор деревьев решений, которые, чтобы попытаться уменьшить дисперсию, упомянутую выше, генерируют деревья таким образом, чтобы алгоритм мог выбирать оставшиеся деревья таким образом, чтобы уменьшить ошибку. . Вот несколько примеров того, как создается случайный лес:

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

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

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

Затем давайте реализуем наш предсказатель, используя случайный лес:

33.9996500736377

Мы видим, что у нас есть значительное снижение нашей MAE при использовании случайного леса.

6. Резюме

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

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

7. Дальнейшее чтение

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

Свяжитесь со мной @ https://jose.tapadas.eu