Vereinfachung der Zeitreihenprognose: Replikation der Lösung von Monsaraida auf Kaggle für Vorhersagen des Einzelhandelsvolumens | von ODSC – Open Data Science | Juni 2023

0
28


Der auf Kaggle veranstaltete M5-Wettbewerb hat kürzlich die Aufmerksamkeit auf die Wirksamkeit von Gradienten-Boosting-Methoden für Volumenprognosen von Einzelhandelsprodukten gelenkt. In diesem Artikel konzentrieren wir uns auf die Genauigkeitsspur des Wettbewerbs und gehen einen an Zeitfolgen Drawback. Durch die Nachahmung einer der hochrangigsten, aber dennoch einfachsten und klarsten Lösungen, die Monsaraida vorgeschlagen hat (Masanori Miyahara), einem japanischen Informatiker, möchten wir unsere Leser mit Code und Ideen ausstatten, um zukünftige Prognosewettbewerbe auf Kaggle erfolgreich zu meistern. Während viele Lösungen auf den Diskussionsseiten der Konkurrenz verfügbar sind, zeichnet sich die Lösung von Monsaraida durch ihre Einfachheit und Effektivität aus und belegt mit einem Wert von 0,53583 den vierten Platz auf der privaten Bestenliste. Insbesondere nutzt diese Lösung allgemeine Funktionen ohne vorherige Auswahl, wie Verkaufsstatistiken, Kalender, Preise und Kennungen.

Dieser Artikel ist ein Auszug aus dem Buch Das Kaggle-Arbeitsbuch von Konrad Banachewicz und Luca Massaron, ein praktisches Arbeitsbuch, das auf praktischen Übungen basiert und Ihnen dabei helfen kann, wie ein erfahrener Datenwissenschaftler zu denken.

Der Plan zur Replikation der Monsaraida-Lösung besteht darin, ein durch Eingabeparameter anpassbares Pocket book zu erstellen, um die erforderlichen verarbeiteten Daten für Trainings- und Testdatensätze sowie die LightGBM-Modelle für Vorhersagen zu erstellen. Die Modelle werden anhand von Daten aus der Vergangenheit trainiert, um zu lernen, Werte für eine bestimmte Anzahl von Tagen in der Zukunft vorherzusagen. Die besten Ergebnisse können erzielt werden, wenn jedes Modell lernt, die Werte in einem bestimmten Wochenbereich für die Zukunft vorherzusagen. Da wir bis zu 28 Tage im Voraus vorhersagen müssen, benötigen wir ein Modell, das von Tag +1 bis Tag +7 in der Zukunft vorhersagt, dann ein anderes, das von Tag +8 bis Tag +14 vorhersagen kann, und ein anderes von Tag +15 bis + 21 und schließlich ein weiteres, das Vorhersagen vom Tag +22 bis zum Tag +28 verarbeiten kann. Für jeden dieser Zeitbereiche benötigen wir ein Kaggle-Notizbuch, additionally vier Notizbücher. Jedes dieser Notebooks wird trainiert, um die zukünftige Zeitspanne für jedes der zehn am Wettbewerb teilnehmenden Geschäfte vorherzusagen. Insgesamt wird jedes Pocket book zehn Modelle produzieren. Insgesamt wird es dann 40 Pocket book-Modelle geben, die alle künftigen Sortimente und alle Filialen abdecken.

Da wir sowohl für die öffentliche als auch für die personal Rangliste Vorhersagen treffen müssen, ist es notwendig, diesen Vorgang zweimal zu wiederholen und das Coaching am Tag 1.913 (Vorhersage von Tagen von 1.914 bis 1.941) für die Einreichung des öffentlichen Testsatzes und am Tag 1.941 ( prognostizierte Tage von 1.942 bis 1.969) für den privaten.

Angesichts der aktuellen Einschränkungen beim Betrieb von Kaggle-Notebooks auf CPU-Foundation können alle acht Notebooks parallel betrieben werden (der gesamte Vorgang dauert quick sechseinhalb Stunden). Jedes Notizbuch kann durch seinen Namen von anderen unterschieden werden und enthält die Parameterwerte relativ zum letzten Trainingstag und den Vorausschauhorizont in Tagen. Ein Beispiel für eines dieser Notizbücher finden Sie unter https://www.kaggle.com/code/lucamassaron/m5-train-day-1941-horizon-7.

Lassen Sie uns nun gemeinsam untersuchen, wie der Code aufgebaut ist und was wir aus Monsaraidas Lösung lernen können.

Wir beginnen einfach mit dem Import der notwendigen Pakete. Sie können einfach feststellen, dass neben NumPy und Pandas LightGBM das einzige auf Datenwissenschaft spezialisierte Paket ist. Möglicherweise fällt Ihnen auch auf, dass wir gc (Rubbish Assortment) verwenden werden: Das liegt daran, dass wir die vom Skript verwendete Speichermenge begrenzen müssen und häufig nur den ungenutzten Speicher sammeln und recyceln. Im Rahmen dieser Strategie speichern wir Modelle und Datenstrukturen häufig auch auf der Festplatte, anstatt sie im Speicher zu behalten:

import numpy as np
import pandas as pd
import os
import random
import math
from decimal import Decimal as dec
import datetime
import time
import gc
import lightgbm as lgb
import pickle
import warnings
warnings.filterwarnings("ignore", class=UserWarning)

Als Teil der Strategie zur Begrenzung der Speichernutzung greifen wir auf die Funktion zur Reduzierung des Pandas DataFrame-Speicherbedarfs zurück, die im Kaggle-Buch beschrieben und ursprünglich von Arjan Groen während des Zillow-Wettbewerbs entwickelt wurde (lesen Sie die Diskussion unter https://www.kaggle.com/competitions/tabular-playground-series-dec-2021/discussion/291844):

def reduce_mem_usage(df, verbose=True):
numerics = ['int16', 'int32', 'int64', 'float16', 'float32',
'float64']
start_mem = df.memory_usage().sum() / 1024**2
for col in df.columns:
col_type = df[col].dtypes
if col_type in numerics:
c_min = df[col].min()
c_max = df[col].max()
if str(col_type)[:3] == 'int':
if c_min > np.iinfo(np.int8).min and c_max < np.iinfo(np.
int8).max:
df[col] = df[col].astype(np.int8)
elif c_min > np.iinfo(np.int16).min and c_max <
np.iinfo(np.int16).max:
df[col] = df[col].astype(np.int16)
elif c_min > np.iinfo(np.int32).min and c_max <
np.iinfo(np.int32).max:
df[col] = df[col].astype(np.int32)
elif c_min > np.iinfo(np.int64).min and c_max <
np.iinfo(np.int64).max:
df[col] = df[col].astype(np.int64)
else:
if c_min > np.finfo(np.float32).min and c_max <
np.finfo(np.float32).max:
df[col] = df[col].astype(np.float32)
else:
df[col] = df[col].astype(np.float64)
end_mem = df.memory_usage().sum() / 1024**2
if verbose: print('Mem. utilization decreased to {:5.2f} Mb ({:.1f}%
discount)'.format(end_mem, 100 * (start_mem - end_mem) / start_mem))
return df

Wir definieren weiterhin Funktionen für diese Lösung, weil die Aufteilung der Lösung in kleinere Teile hilfreich ist und weil es einfacher ist, alle verwendeten Variablen zu bereinigen, wenn Sie einfach von einer Funktion zurückkehren (Sie behalten nur das, was Sie auf der Festplatte gespeichert haben). Unsere nächste Funktion hilft uns, alle verfügbaren Daten zu laden und zu komprimieren:

def load_data():
train_df = reduce_mem_usage(pd.read_csv("../enter/m5-forecasting-
accuracy/sales_train_evaluation.csv"))
prices_df = reduce_mem_usage(pd.read_csv("../enter/m5-forecasting-
accuracy/sell_prices.csv"))
calendar_df = reduce_mem_usage(pd.read_csv("../enter/m5-forecasting-
accuracy/calendar.csv"))
submission_df = reduce_mem_usage(pd.read_csv("../enter/m5-forecastingaccuracy/
sample_submission.csv"))
return train_df, prices_df, calendar_df, submission_df

Sobald die Funktion definiert ist, führen wir sie aus:

train_df, prices_df, calendar_df, submission_df = load_data()

Nachdem wir den Code zum Abrufen der Daten zu Preisen, Mengen und Kalenderinformationen vorbereitet haben, bereiten wir die erste Verarbeitungsfunktion vor, die die Aufgabe hat, eine grundlegende Informationstabelle mit item_id, dept_id, cat_id, state_id und store_id zu erstellen Zeilenschlüssel, eine Tagesspalte und eine Wertespalte mit den Volumina. Dies wird ausgehend von Zeilen erreicht, die alle Datenspalten für Tage enthalten, indem der Pandas-Befehl Soften (https://pandas.pydata.org/pandas-docs/secure/reference/api/pandas.soften.html).

Der Befehl verwendet als Referenz den Index des DataFrame und wählt dann alle verbleibenden Options aus, indem er ihren Namen in eine Spalte und ihren Wert in eine andere einfügt (die Parameter var_name und value_name helfen Ihnen, den Namen dieser neuen Spalten zu definieren). Auf diese Weise können Sie eine Zeile, die die Verkaufsreihe eines bestimmten Artikels in einem bestimmten Geschäft darstellt, in mehrere Zeilen aufteilen, die jeweils einen einzelnen Tag darstellen. Die Tatsache, dass die Positionsreihenfolge der entfalteten Spalten erhalten bleibt, garantiert, dass sich Ihre Zeitreihe jetzt auf der vertikalen Achse erstreckt (Sie können daher weitere Transformationen darauf anwenden, z. B. Mittelwerte verschieben).

Um Ihnen eine Vorstellung davon zu geben, was passiert, ist hier der train_df vor der Transformation mit pd.soften. Beachten Sie, dass die Volumina der einzelnen Tage Spaltenmerkmale sind:

Abbildung 1: Der Trainings-DataFrame

Nach der Transformation erhalten Sie ein Grid_df, in dem die Spalten in Zeilen umgewandelt wurden und die Tage nun unter einer neuen Spalte zu finden sind:

Abbildung 2: Anwenden von pd.soften auf den Trainings-DataFrame

Das Merkmal d enthält den Verweis auf die Spalten, die nicht Teil des Index sind, additionally im Wesentlichen alle Merkmale von d_1 bis d_1935. Dies impliziert eine Erhöhung der Anzahl der Zeilen im Datensatz um das 1.935-fache. Indem Sie einfach das Präfix d_ aus seinen Werten entfernen und diese in Ganzzahlen umwandeln, verfügen Sie jetzt über eine Tagesfunktion.

Darüber hinaus trennt das Code-Snippet auch einen Holdout der Zeilen. Ein solcher Holdout ist Ihr Validierungssatz. Die Validierungsstrategie basiert auf der zeitlichen Reservierung eines Teils der Trainingsdaten. Im Trainingsteil werden auch die Zeilen hinzugefügt, die für Ihre Vorhersagen erforderlich sind, basierend auf dem von Ihnen angegebenen Vorhersagehorizont (die Anzahl der Tage, die Sie in der Zukunft vorhersagen möchten).

Hier ist die Funktion, die unsere grundlegende Function-Vorlage erstellt. Als Eingabe werden der train_df-Datenrahmen, die Zahl des Tages, an dem das Coaching endet, und der Vorhersagehorizont verwendet:

def generate_base_grid(train_df, end_train_day_x, predict_horizon):
index_columns = ['id', 'item_id', 'dept_id', 'cat_id', 'store_id',
'state_id']
    grid_df = pd.soften(train_df, id_vars=index_columns, var_name='d',
value_name='gross sales')
grid_df = reduce_mem_usage(grid_df, verbose=False)
grid_df['d_org'] = grid_df['d']
grid_df['d'] = grid_df['d'].apply(lambda x: x[2:]).astype(np.int16)
time_mask = (grid_df['d'] > end_train_day_x) & (grid_df['d'] <= end_
train_day_x + predict_horizon)
holdout_df = grid_df.loc[time_mask, ["id", "d", "sales"]].reset_
index(drop=True)
holdout_df.to_feather(f"holdout_df_{end_train_day_x}_to_{end_train_
day_x + predict_horizon}.feather")
del(holdout_df)
gc.acquire()
grid_df = grid_df[grid_df['d'] <= end_train_day_x]
grid_df['d'] = grid_df['d_org']
grid_df = grid_df.drop('d_org', axis=1)
add_grid = pd.DataFrame()
for i in vary(predict_horizon):
temp_df = train_df[index_columns]
temp_df = temp_df.drop_duplicates()
temp_df['d'] = 'd_' + str(end_train_day_x + i + 1)
temp_df['sales'] = np.nan
add_grid = pd.concat([add_grid, temp_df])
grid_df = pd.concat([grid_df, add_grid])
grid_df = grid_df.reset_index(drop=True)
for col in index_columns:
grid_df[col] = grid_df[col].astype('class')
grid_df = reduce_mem_usage(grid_df, verbose=False)
grid_df.to_feather(f"grid_df_{end_train_day_x}_to_{end_train_day_x +
predict_horizon}.feather")
del(grid_df)
gc.acquire()

Nachdem wir die Funktion zum Erstellen der grundlegenden Function-Vorlage bearbeitet haben, bereiten wir eine Zusammenführungsfunktion für Pandas DataFrames vor, die dabei hilft, Speicherplatz zu sparen und Speicherfehler bei der Verarbeitung großer Datenmengen zu vermeiden. Bei zwei DataFrames, df1 und df2, und dem Satz an Fremdschlüsseln, die wir zusammenführen müssen, wendet die Funktion einen linken äußeren Be part of zwischen df1 und df2 an, ohne ein neues zusammengeführtes Objekt zu erstellen, sondern erweitert einfach den vorhandenen df1-DataFrame.

Die Funktion extrahiert zunächst die Fremdschlüssel aus df1 und führt dann die extrahierten Schlüssel mit df2 zusammen. Auf diese Weise erstellt die Funktion einen neuen DataFrame namens merged_gf, der als df1 geordnet ist. An dieser Stelle weisen wir einfach die merged_gf-Spalten df1 zu. Intern wählt df1 den Verweis auf die internen Datenstrukturen aus merged_gf aus. Ein solcher Ansatz trägt dazu bei, die Speichernutzung zu minimieren, da immer nur die benötigten Daten erstellt werden (es gibt keine Duplikate, die den Speicher füllen könnten). Wenn die Funktion df1 zurückgibt, wird merged_gf abgebrochen, jedoch für die Daten, die jetzt von df1 verwendet werden.

Hier ist der Code für diese Dienstprogrammfunktion:

def merge_by_concat(df1, df2, merge_on):
merged_gf = df1[merge_on]
merged_gf = merged_gf.merge(df2, on=merge_on, how='left')
new_columns = [col for col in list(merged_gf)
if col not in merge_on]
df1[new_columns] = merged_gf[new_columns]
return df1

Nach diesem notwendigen Schritt programmieren wir eine neue Funktion zur Verarbeitung der Daten. Dieses Mal verarbeiten wir die Preisdaten, einen Datensatz, der die Preise für jeden Artikel in jedem Geschäft für alle Wochen enthält. Da es wichtig ist, herauszufinden, ob es sich um ein neues Produkt handelt, das in einem Geschäft erscheint oder nicht, wählt die Funktion das erste Datum der Preisverfügbarkeit aus (unter Verwendung der Funktion wm_yr_wk in der Preistabelle, die die ID der Woche darstellt) und es kopiert es in unsere Function-Vorlage.

Hier ist der Code zur Verarbeitung der Veröffentlichungstermine:

def calc_release_week(prices_df, end_train_day_x, predict_horizon):
index_columns = ['id', 'item_id', 'dept_id', 'cat_id', 'store_id',
'state_id']
    grid_df = pd.read_feather(f"grid_df_{end_train_day_x}_to_{end_train_
day_x + predict_horizon}.feather")
release_df = prices_df.groupby(['store_id', 'item_id'])['wm_yr_wk'].
agg(['min']).reset_index()
release_df.columns = ['store_id', 'item_id', 'release']
grid_df = merge_by_concat(grid_df, release_df, ['store_id', 'item_
id'])
del release_df
grid_df = reduce_mem_usage(grid_df, verbose=False)
gc.acquire()
grid_df = merge_by_concat(grid_df, calendar_df[['wm_yr_wk', 'd']],
['d'])
grid_df = grid_df.reset_index(drop=True)
grid_df['release'] = grid_df['release'] - grid_df['release'].min()
grid_df['release'] = grid_df['release'].astype(np.int16)

grid_df = reduce_mem_usage(grid_df, verbose=False)
grid_df.to_feather(f"grid_df_{end_train_day_x}_to_{end_train_day_x +
predict_horizon}.feather")
del(grid_df)
gc.acquire()

Durch die Replikation der erstklassigen Lösung von Monsaraida haben wir die Wirksamkeit der Verwendung einfacher und unkomplizierter Methoden zur Verarbeitung von Veröffentlichungsdaten demonstriert. Um jedoch besser zu verstehen, wie diese Lösung durch die Nutzung allgemeiner Funktionen wie Verkaufsstatistiken, Kalender, Preise und Kennungen einen beeindruckenden privaten Leaderboard-Rating von 0,53583 erreichte, empfehlen wir Ihnen dringend, sich darauf zu beziehen Das Kaggle-Arbeitsbuch von Konrad Banachewicz und Luca Massaron. Beide sind Kaggle-Großmeister und hochgelobte Datenwissenschaftler. Wir hoffen, dass dieser Artikel unseren Lesern wertvolle Einblicke und Code für die Bewältigung ähnlicher Prognosewettbewerbe auf Kaggle geliefert und uns zu weiteren Erkundungen und Experimenten im Bereich der Zeitreihenvorhersage inspiriert hat.



Source link

HINTERLASSEN SIE EINE ANTWORT

Please enter your comment!
Please enter your name here