Оглавление
Вступление
Привет читатель,эта стать является переводом решений соревнования Forecasting Mini‑Course Sales на Kaggle и дополнениями от автора данной статьи.
Сразу хочу извинится за неточности которые могут содержаться в данной статье,автор не очень крутой переводчик и программист,но даже в этом случае статья будет полезна новичкам и мне.
Кому это надо? Эта статья предусмотрена для новичков или людей ищущих вдохновения для своего проекта.
Kaggle playground series
Как и в детстве ,мы начинаем познавать мир с песочницы.
Kaggle Playground Series - это серия соревнований по машинному обучению, которые проводятся Kaggle для новичков и людей которые хотят попрактиковать свои навыки ml .
Эти соревнования могут помочь новичкам в изучение ml и ds, благодарю тому ,что на испытания есть открытые дискуссии и примеры прошлых решений, которые публикуют верхние места таблиц
Соревнования в Kaggle Playground Series обычно основаны на небольших наборах данных, которые легко понять и обработать. Это делает их идеальным выбором для новичков, которые только начинают изучать машинное обучение.
Если вы только начинаете свой путь в ml или ds,то это идеальное место для практики , как и все соревнования на Kaggle.
Данные
Как и везде, нам нужно понять что нам дано, а точнее дано ли.
Ну раз вы читаете эту статью то вам скорее всего дано,если конечно автор её вообще допишет и опубликует.
Итак,что по данным?
Данные содержат продажи различных вымышленных учебных модулей за весь год из разных вымышленных магазинов под брендом Kaggle в разных (реальных!) странах. А точнее данные включают такие колонки: дата, страна продажи ,магазин продажи ,название курса, количество продаж.
Этот набор данных полностью синтетический, но содержит множество эффектов, которые вы видите в реальных данных, например, влияние выходных и праздничных дней, сезонность и т.д. Набор данных для обучения начинается с 1.01.2017 и до 31.12.2021 (~137к строк) , тестовые же данные захватывают весь 2022 год (~27,4к строк).
Примерно так выглядят данные за первые 1000 строк:
Но так как данные у нас из разных продуктов, давайте посмотрим на каждый продукт отдельно:
Так, и что же мы тут видим?Пожалуйста немного подумайте, но не долго.
Периодичность , сезонность . То есть график повторяется , а это очень полезно для прогнозирования.
Но как же нам это использовать ?
Дата
Откуда код
В этом отделе я буду использовать код из решения 9 места по соревнованию https://www.kaggle.com/code/yeoyunsianggeremie/s3e19-catboost-smoothing-post-processing/notebook
Для начала работы с датой нам нужно преобразовать дату в особый формат datetime:
df.date = pd.to_datetime(df.date)
немного подробно можно прочитать здесь.
Начнем с простого, а именно с обработки даты,в наших данных они представлены в формате гггг.мм.дд , эту дату мы можем преобразовать в такие полезные штуки как:
Год
df['year'] = df.date.dt.year
Месяц
df['month'] = df.date.dt.month
День в месяце (1-31)
df['dayofmonth'] = df.date.dt.day
День недели (0-6)
df['dayofweek'] = df.date.dt.dayofweek
Название дня
df['dayname'] = df.date.dt.strftime('%A')
День года (1 - 365)
df['dayofyear'] = df.date.dt.dayofyear
Но зачем мы это делаем? Ответ очень прост - прогнозирование за счёт исторических данных дня .Ведь каждая дата, кроме наверное года повторяется, это дает нашей будущей модели возможность предсказывать за счет прошлых значений в этот день.
Код коротко
def extractDate(df):
df.date = pd.to_datetime(df.date)
df['year'] = df.date.dt.year
df['month'] = df.date.dt.month
df['dayofmonth'] = df.date.dt.day
df['dayofweek'] = df.date.dt.dayofweek
df['dayname'] = df.date.dt.strftime('%A')
df['dayofyear'] = df.date.dt.dayofyear
extractDate(train) #ваш dataframe в скобках
Примерно так выглядит таблица после наших манипуляций :
Праздники
Нет ,нет , не нужно ничего праздновать.Нам всего лишь нужно добавить в нашу таблицу пару столбцов с праздниками . Зачем? Как мы знаем в праздники подарки либо дарят, либо получают , но в любом случае их покупают , а это нам и нужно.И для облегчения работы модели мы можем добавить маркеры (столбцы) праздников.
Но как это сделать?
Для работы с праздниками есть специальный модуль holidays, установим его
pip install holidays
Далее для обработки праздников можно использовать примерно такой код:
from datetime import datetime
import holidays
#данные должны быть в формате datetime
# Функция для получения информации о празднике
def get_holiday_info(row):
country = row['country']
date = row['date'].date()
holiday_obj = holidays.CountryHoliday(country)
is_holiday = int(date in holiday_obj)
holiday_name = holiday_obj.get(date)
if is_holiday==0:
holiday_name = 'Not Holiday'
return is_holiday, holiday_name
# Добавляем столбцы с информацией о праздниках
df[['is_holiday', 'holiday_name']] = df.apply(get_holiday_info, axis=1, result_type='expand')
Этот код делает из такой таблицы :
Такую:
Ну,скорее всего ваш день рождение этот модуль не найдет , но большинство известных праздников там будут представлены.
Модель
Большинство решений имеют схожие черты, поэтому я буду решение с первого места ,т.к. этот человек любезно предоставил полное решение и получившуюся модель.
Моделью является линейная регрессия с метрикой SMAPE.
Модель получается примерно такая :
ln(sold)=ln(GDP)+b1const(store)+b2(product)+b3waves(product)+b4holiday effect+b5weekend+b6covid
Или же:
ln(продано)=ln(ВВП) + b1const(магазин) + b2(продукт) + b3волны(продукт) + b4праздничный эффект + b5выходные + b6covid
такая модель учитывает многие факторы ,которые могут быть в реальной жизни и влиять на продажи курсов.
Для оценки результатов модели используется smape,поскольку эту метрику легче интерпретировать . Формула для метрики получается такая:
И получившийся код:
from sklearn.model_selection import GroupKFold
pd.options.mode.chained_assignment = None # default='warn'
kf = GroupKFold(n_splits=5)
train_a = []
val_a = []
val_Q1_a = []
val_Q234_a = []
for fold, (train_idx, val_idx) in enumerate(kf.split(train_data, groups=train_data.date.dt.year)):
X_tr = train_data.iloc[train_idx]
X_va = train_data.iloc[val_idx]
if X_va.iloc[1].year == 2020:
continue
print(X_va.iloc[1].year)
coef, model, train_mean, val_mean, val_Q1, val_Q234 = fit_model(
X_tr, X_va, use_columns)
train_a.append(train_mean)
val_a.append(val_mean)
val_Q1_a.append(val_Q1)
val_Q234_a.append(val_Q234)
# Compute mean
train_mean = np.mean(train_a)
val_mean = np.mean(val_a)
val_Q1_mean = np.mean(val_Q1_a)
val_Q234_mean = np.mean(val_Q234_a)
# Compute standard error of the mean
train_sem = stats.sem(train_a)
val_sem = stats.sem(val_a)
val_Q1_sem = stats.sem(val_Q1_a)
val_Q234_sem = stats.sem(val_Q234_a)
print(f"Train mean: {train_mean:.4f} ± {train_sem:.4f}")
print(f"Validation mean: {val_mean:.4f} ± {val_sem:.4f}")
print(f"Validation Q1 mean: {val_Q1_mean:.4f} ± {val_Q1_sem:.4f}")
print(f"Validation Q234 mean: {val_Q234_mean:.4f} ± {val_Q234_sem:.4f}")
Это лишь маленький кусок кода,остальная часть есть в решение в разделе модели.
Заключение
Данная статья не даст каких либо сверх знаний и умений.Я лишь немного приоткрыл завесу тайн решения задач на гегле,с помощью перевода и пересказа небольшого количества статей.Если вам было интересно читать эту статью , то вот лидерборд этого соревнования,в котором есть огромное количество статей по решению этой задачи.Также здесь можно найти решения задач прошлых лет.
Всем спасибо,всем пока.