Другой подход к конкурсу бульдозеров Kaggle Blue Book

Мотивация

Древовидные модели, такие как Random Forest и XGBoost, стали очень популярными при решении задач с табличными (структурированными) данными и в последнее время получили большую популярность в соревнованиях Kaggle. У этого есть свои очень достойные причины. Однако в этой статье я хочу представить подход, отличный от подхода Табличного модуля fast.ai:

Глубокое обучение и встраивание слоев.

Это немного противоречит общепринятому в отрасли мнению о том, что глубокое обучение больше подходит для неструктурированных данных, таких как изображения, аудио или NLP, и обычно не подходит для обработки табличных данных. Тем не менее, введение встраиваемых слоев для категориальных данных изменило эту перспективу, и мы попробуем использовать табличный модуль fast.ai в Соревновании бульдозеров Blue Book на Kaggle и посмотрим, насколько далеко может зайти этот подход. .

Вы можете найти Kaggle Notebook 📔: здесь.

Загрузка данных

Во-первых, давайте импортируем необходимые модули. Основной здесь fastai.tabular:

from fastai import *
from fastai.tabular import *

Затем мы прочитаем данные в DataFrame Pandas. Вы можете найти конкретный код по ссылке Kaggle Notebook в верхней части этой статьи, но здесь я покажу только необходимые фрагменты кода, чтобы все было как можно лаконичнее. Мы прочитаем CSV-файл в train_df, и это будет DataFrame, над которым мы будем в основном работать. Мы также прочитаем test_df, который является набором тестов.

Давайте кратко рассмотрим данные, с которыми мы имеем дело:

len(train_df), len(test_df)
(401125, 12457)

Сортировка обучающего набора

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

train_df = train_df.sort_values(by='saledate', ascending=False)
train_df = train_df.reset_index(drop=True)

Предварительная обработка данных

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

train_df.SalePrice = np.log(train_df.SalePrice)

Что касается проектирования функций, поскольку мы будем использовать глубокое обучение для решения проблемы, и оно очень хорошо умеет извлекать функции, мы будем делать это только на saledate. Это преимущество использования подхода глубокого обучения, он требует гораздо меньше функций и меньше зависит от знаний предметной области. Мы будем использовать add_datepart функцию fast.ai для добавления дополнительных функций, связанных с датой продажи.

# The only feature engineering we do is add some meta-data from the sale date column, using 'add_datepart' function in fast.ai
add_datepart(train_df, "saledate", drop=False)
add_datepart(test_df, "saledate", drop=False)

Что делает add_datepart, так это берет столбец saledate и добавляет кучу других столбцов, таких как day of week, day of month, независимо от того, является ли это начало или конец месяца, квартала и года и т. Д. Эти добавленные функции дадут больше информации о дате и имеет отношение к покупательскому поведению пользователей. Например, в конце года компания обычно проводит рекламные акции, и цены обычно снижаются.

Давайте проверим, все ли эти функции, связанные с датой, были добавлены в наш DataFrame:

# check and see whether all date related meta data is added.
def display_all(df):
    with pd.option_context("display.max_rows", 1000, "display.max_columns", 1000): 
        display(df)
        
display_all(train_df.tail(10).T)

Они действительно были добавлены. Хороший. Теперь нам нужно выполнить некоторую предварительную обработку данных, поскольку в этом DataFrame довольно много недостающих данных, и мы также хотим категоризировать и нормализовать столбцы. С библиотекой fast.ai это довольно просто. Мы просто указываем нужные нам методы предварительной обработки в списке Python, например:

# Defining pre-processing we want for our fast.ai DataBunch
procs=[FillMissing, Categorify, Normalize]

Эта переменная procs позже будет использоваться для создания группы данных fast.ai для обучения.

Построение модели

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

train_df.dtypes
g = train_df.columns.to_series().groupby(train_df.dtypes).groups
g

Вот результаты:

Затем мы поместим все категориальные столбцы в список cat_vars и все непрерывные столбцы в список cont_vars. Эти две переменные также будут использоваться для создания группы данных fast.ai.

# prepare categorical and continous data columns for building Tabular DataBunch.
cat_vars = ['SalesID', 'YearMade', 'MachineID', 'ModelID', 'datasource', 'auctioneerID', 'UsageBand', 'fiModelDesc', 'fiBaseModel', 'fiSecondaryDesc', 'fiModelSeries', 'fiModelDescriptor', 'ProductSize', 
            'fiProductClassDesc', 'state', 'ProductGroup', 'ProductGroupDesc', 'Drive_System', 'Enclosure', 'Forks', 'Pad_Type', 'Ride_Control', 'Stick', 'Transmission', 'Turbocharged', 'Blade_Extension', 
            'Blade_Width', 'Enclosure_Type', 'Engine_Horsepower', 'Hydraulics', 'Pushblock', 'Ripper', 'Scarifier', 'Tip_Control', 'Tire_Size', 'Coupler', 'Coupler_System', 'Grouser_Tracks', 'Hydraulics_Flow', 
            'Track_Type', 'Undercarriage_Pad_Width', 'Stick_Length', 'Thumb', 'Pattern_Changer', 'Grouser_Type', 'Backhoe_Mounting', 'Blade_Type', 'Travel_Controls', 'Differential_Type', 'Steering_Controls', 
            'saleYear', 'saleMonth', 'saleWeek', 'saleDay', 'saleDayofweek', 'saleDayofyear', 'saleIs_month_end', 'saleIs_month_start', 'saleIs_quarter_end', 'saleIs_quarter_start', 'saleIs_year_end', 
            'saleIs_year_start'
           ]
cont_vars = ['MachineHoursCurrentMeter', 'saleElapsed']

Мы создадим еще один DataFrame df для передачи в DataBunch. Мы также указываем зависимую переменную как dep_var.

# rearrange training set before feed into the databunch
dep_var = 'SalePrice'
df = train_df[cat_vars + cont_vars + [dep_var,'saledate']].copy()

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

# Look at the time period of test set, make sure it's more recent
test_df['saledate'].min(), test_df['saledate'].max()
# Calculate where we should cut the validation set. We pick the most recent 'n' records in training set where n is the number of entries in test set. 
cut = train_df['saledate'][(train_df['saledate'] == train_df['saledate'][len(test_df)])].index.max()
cut
12621
# specify the valid_idx variable as the cut out range.
valid_idx = range(cut)

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

Наконец, давайте создадим наш DataBunch для обучения с использованием API блока данных fast.ai:

# Use fast.ai datablock api to put our training data into the DataBunch, getting ready for training
data = (TabularList.from_df(df, path=path, cat_names=cat_vars, cont_names=cont_vars, procs=procs)
                   .split_by_idx(valid_idx)
                   .label_from_df(cols=dep_var, label_cls=FloatList)
                   .databunch())

Построение модели

Мы запустим fast.ai tabular.learner из только что созданного DataBunch. Мы хотим ограничить диапазон цен для нашего прогноза, чтобы он находился в пределах диапазона цен исторической продажи, поэтому нам нужно вычислить y_range. Обратите внимание, что мы умножили максимум SalePrice на 1,2, поэтому, когда мы применяем сигмоид, верхний предел также будет покрыт. Это небольшой трюк, позволяющий выжать из модели немного больше производительности.

max_y = np.max(train_df['SalePrice'])*1.2
y_range = torch.tensor([0, max_y], device=defaults.device)
y_range
tensor([ 0.0000, 14.2363], device='cuda:0')

Теперь мы можем создать нашего ученика:

# Create our tabular learner. The dense layer is 1000 and 500 two layer NN. We used dropout, hai 
learn = tabular_learner(data, layers=[1000,500], ps=[0.001,0.01], emb_drop=0.04, y_range=y_range, metrics=rmse)

Самая важная вещь в fast.ai tabular_learner - это использование встраиваемых слоев для категориальных данных. Это «секретный соус», который позволяет Deep Learning быть конкурентоспособным в обработке табличных данных. Имея один слой встраивания для каждой категориальной переменной, мы внедрили хорошее взаимодействие для категориальных переменных и использовали самую сильную сторону глубокого обучения: автоматическое извлечение признаков. Мы также использовали Drop Out как для плотных слоев, так и для встраиваемых слоев для лучшей регуляризации. Показателем учащегося является RMSE, поскольку мы уже вели журнал SalePrice. Посмотрим на модель.

TabularModel(
  (embeds): ModuleList(
    (0): Embedding(388505, 600)
    (1): Embedding(72, 18)
    (2): Embedding(331868, 600)
    (3): Embedding(5155, 192)
   ...
    (60): Embedding(3, 3)
    (61): Embedding(2, 2)
    (62): Embedding(3, 3)
  )
  (emb_drop): Dropout(p=0.04, inplace=False)
  (bn_cont): BatchNorm1d(2, eps=1e-05, momentum=0.1, affine=True, track_running_stats=True)
  (layers): Sequential(
    (0): Linear(in_features=2102, out_features=1000, bias=True)
    (1): ReLU(inplace=True)
    (2): BatchNorm1d(1000, eps=1e-05, momentum=0.1, affine=True, track_running_stats=True)
    (3): Dropout(p=0.001, inplace=False)
    (4): Linear(in_features=1000, out_features=500, bias=True)
    (5): ReLU(inplace=True)
    (6): BatchNorm1d(500, eps=1e-05, momentum=0.1, affine=True, track_running_stats=True)
    (7): Dropout(p=0.01, inplace=False)
    (8): Linear(in_features=500, out_features=1, bias=True)
  )
)

Как видно из вышеизложенного, у нас есть встраиваемые слои для категориальных столбцов, за которыми следует выпадающий слой. У нас есть слой пакетной нормы для непрерывных столбцов, затем мы объединяем их все (категориальные вложения + непрерывные переменные) вместе и помещаем их в два полностью связанных слоя с 1000 и 500 узлами с Relu, BatchNorm и Dropout между ними. Вполне стандартная штука.

Теперь, когда у нас есть модель, давайте воспользуемся поисковой системой fast.ai для определения хорошей скорости обучения:

learn.lr_find()
learn.recorder.plot()

Мы выберем скорость обучения в конце самого большого наклона кривой скорости обучения: le-02

Давайте потренируемся, используя подход одноциклового обучения fast.ai. Обратите внимание, что мы добавили некоторое уменьшение веса (0.2) для регуляризации.

learn.fit_one_cycle(2, 1e-2, wd=0.2)

Мы можем обучить еще несколько циклов с меньшей скоростью обучения:

learn.fit_one_cycle(5, 3e-4, wd=0.2)

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

Первое место - 0,229. Сравните с 0,223 этой модели. Мы не знаем, насколько хорошо это работает на тестовом наборе, но в целом я думаю, что результат, который мы получили, совсем неплох.

Еще пара слов о встраивании слоев

Что заставляет все щелкать здесь, так это слои встраивания. Встраивание - это просто причудливое слово, обозначающее сопоставление чего-либо с вектором. Подобно встраиванию слов, которое становится все более популярным в НЛП, это означает использование вектора (размер может быть произвольным, в зависимости от задач) для представления слов, и эти векторы являются весами и могут быть обучены с помощью back-prop.

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

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

Что еще интереснее, при встраивании слоев вы можете визуализировать проекцию переменной в пространстве матрицы встраивания. Возьмем, к примеру, проект Россмана. Они сделали двумерную проекцию матрицы вложения для немецких государств.

И если вы обведете несколько состояний в области встраивания и те же состояния на реальной карте. Вы обнаружите, что они ужасно похожи. Слой встраивания фактически открыл географию.

Эта статья оказалась полезной? Следуйте за мной (Майкл Ли) на Medium, или вы можете найти меня в Twitter @lymenlee или в моем блоге wayofnumbers.com. Вы также можете ознакомиться с моими самыми популярными статьями ниже!



« Это CS50 : приятный способ начать ваше образование в области науки о данных»
Почему CS50 особенно хорош для укрепления вашей основы разработки программного обеспечения в сторонуdatascience.com »





Две стороны одной монеты: fast.ai Джереми Ховарда и deeplearning.ai Эндрю Нга
Как не« переоснастить
ваше обучение искусственному интеллекту, взяв fast.ai и углубленное изучение .ai курсы todatascience.com »





Что нужно знать о Netflix« Убийца Jupyter : Polynote 📖
Пора Jupyter Notebook найти достойного конкурента по направлению кdatascience.com»