How to improve model performance

View as Markdown

Overview

This document provides a step-by-step guide to improving the performance of KumoRFM. The tutorial demonstrates how to evaluate and optimize model settings on the H&M dataset, available through the RelBench repository.

1. Introduction

The H&M database contains extensive customer and product data from the company’s e-commerce operations. It includes detailed purchase histories and metadata, ranging from demographic information to product attributes.

In this example, we predict the total price per article_id over a 7-day window (start_date, start_date + 7 days], but only for items that had non-zero sales in the previous 7 days.

  • Prediction Window: (2020-09-07, 2020-09-14]
  • Transactions on 2020-09-07 are excluded.

2. Environment Setup

Install required packages:

$!pip install kumoai --pre
$!pip install relbench

Initialize the KumoRFM environment:

1import kumoai.rfm as rfm
2rfm.authenticate()
3rfm.init()

3. Data Loading

Download the dataset and prepare data frames for each table:

1from relbench.datasets import get_dataset
2from relbench.tasks import get_task
3
4db = get_dataset('rel-hm', download=True).get_db()
5df_dict = {
6 table_name: table.df
7 for table_name, table in db.table_dict.items()
8}

4. Ground Truth Calculation

Define a reference function to compute the target variable — total sales for each item within the next 7 days, restricted to items with prior sales activity.

1import pandas as pd
2
3def total_price_by_item_next_7_days(transactions_df: pd.DataFrame, anchor_time: str) -> pd.DataFrame:
4 """
5 Compute total price per article_id for the next 7-day window (left-exclusion, right-inclusive],
6 restricted to items with non-zero sales in the previous 7 days.
7 """
8 data = transactions_df.copy()
9 data["t_dat"] = pd.to_datetime(data["t_dat"])
10
11 anchor_time = pd.to_datetime(anchor_time)
12 anchor_time_7d_after = anchor_time + pd.Timedelta(days=7)
13 anchor_time_7d_before = anchor_time - pd.Timedelta(days=7)
14
15 prev_mask = (data["t_dat"] > anchor_time_7d_before) & (data["t_dat"] <= anchor_time)
16 prev_totals = data.loc[prev_mask].groupby("article_id")["price"].sum()
17 eligible_ids = prev_totals[prev_totals > 0].index
18
19 next_mask = (data["t_dat"] > anchor_time) & (data["t_dat"] <= anchor_time_7d_after)
20 out = (
21 data.loc[next_mask]
22 .groupby("article_id")["price"]
23 .sum()
24 .reindex(eligible_ids, fill_value=0.0)
25 .rename("total_price_7d")
26 .reset_index()
27 .assign(anchor_time=anchor_time)
28 )
29 return out
30
31ground_truth_df = total_price_by_item_next_7_days(df_dict['transactions'], '2020-09-07')

5. Model Initialization

Initialize the local relational graph and create a KumoRFM model instance.

1graph = rfm.Graph.from_data(df_dict, verbose=False)
2model = rfm.KumoRFM(graph)

Sample the evaluation dataset:

1import numpy as np
2
3test_df = ground_truth_df.sample(n=2000, random_state=42, replace=False)
4test_indices = test_df['article_id']

6. Evaluation Helper Function

Define an evaluation function for consistent benchmarking.

1def evaluate(run_mode, num_neighbors, use_prediction_time, query):
2 with model.batch_mode():
3 df = model.predict(
4 query=query,
5 anchor_time=pd.Timestamp('2020-09-07'),
6 indices=test_indices,
7 run_mode=run_mode,
8 num_neighbors=num_neighbors,
9 use_prediction_time=use_prediction_time,
10 verbose=False,
11 )
12
13 y_pred = df['TARGET_PRED'].to_numpy()
14 y_test = test_df['total_price_7d'].to_numpy()
15 print(f'MAE (lower is better): {np.abs(y_test - y_pred).mean():.4f}')

7. Performance Optimization

7.1 Align Predictive Query with Criteria

Ensure that the predictive query aligns with the business logic of the task—only predict for articles that had prior sales activity.

1query1 = 'PREDICT SUM(transactions.price, 0, 7, days) FOR EACH article.article_id'
2query2 = 'PREDICT SUM(transactions.price, 0, 7, days) FOR EACH article.article_id WHERE SUM(transactions.price, -7, 0, days) > 0'

Note that Query 2 is more closely aligned with the prediction criteria—it only generates predictions for articles that have had transactions in the previous 7 days. This alignment is important because in-context sampling is driven by the predictive query criteria.

When the condition WHERE SUM(transactions.price, -7, 0, days) > 0 is not included, all article_ids can be sampled during in-context sampling. However, when the condition is applied, only the eligible article items (those meeting the criteria) are sampled.

Evaluate both queries:

1evaluate(run_mode='fast', num_neighbors=[], use_prediction_time=False, query=query1) # MAE: 0.3913
2evaluate(run_mode='fast', num_neighbors=[], use_prediction_time=False, query=query2) # MAE: 0.3745 (better performance)
Run ModeNum NeighborsUse Prediction TimeQueryMAENotes
fast[]Falsequery10.3913Baseline
fast[]Falsequery20.3745Better performance

Ensuring that the (1) predictive query, (2) in-context sampling, and (3) prediction criteria are consistent helps improve overall model performance.

7.2 Tune Neighborhood Sampling

Adjusting the number of neighbors (num_neighbors) affects the model’s receptive field. Increasing neighbors generally improves performance, while too many hops may introduce noise.

1evaluate(run_mode='fast', num_neighbors=[], use_prediction_time=False, query=query2) # MAE: 0.3745
2evaluate(run_mode='fast', num_neighbors=[8], use_prediction_time=False, query=query2) # MAE: 0.3297
3evaluate(run_mode='fast', num_neighbors=[8, 8], use_prediction_time=False, query=query2) # MAE: 0.3276
4evaluate(run_mode='fast', num_neighbors=[32], use_prediction_time=False, query=query2) # MAE: 0.2506
5evaluate(run_mode='fast', num_neighbors=[32, 32], use_prediction_time=False, query=query2) # MAE: 0.3352
6evaluate(run_mode='fast', num_neighbors=[64], use_prediction_time=False, query=query2) # MAE: 0.2493 (best performance)
7evaluate(run_mode='fast', num_neighbors=[64, 64], use_prediction_time=False, query=query2) # MAE: 0.3350
Run ModeNum NeighborsUse Prediction TimeQueryMAENotes
fast[]Falsequery20.3745Baseline
fast[8]Falsequery20.3297Improved
fast[8, 8]Falsequery20.3276Slightly better
fast[32]Falsequery20.2506Significant gain
fast[32, 32]Falsequery20.3352Performance drop
fast[64]Falsequery20.2493Best performance
fast[64, 64]Falsequery20.3350Performance drop

7.3 Adjust Run Mode

run_mode controls the number of in-context examples used during prediction:

Run ModeIn-Context ExamplesDescription
fast1,000Quick but less accurate
normal5,000Balanced
best10,000Highest accuracy
1evaluate(run_mode='fast', num_neighbors=[64], use_prediction_time=False, query=query2) # MAE: 0.2493
2evaluate(run_mode='normal', num_neighbors=[64], use_prediction_time=False, query=query2) # MAE: 0.2156
3evaluate(run_mode='best', num_neighbors=[64], use_prediction_time=False, query=query2) # MAE: 0.2106 (best performance)
Run ModeNum NeighborsUse Prediction TimeQueryMAENotes
fast[64]Falsequery20.2493Baseline
normal[64]Falsequery20.2156Improved
best[64]Falsequery20.2106Best performance

7.4 Enable Prediction Time Feature (Optional)

Including prediction_time as a feature can capture temporal seasonality. In this example, enabling it did not improve results.

1evaluate(run_mode='best', num_neighbors=[64], use_prediction_time=False, query=query2) # MAE: 0.2106
2evaluate(run_mode='best', num_neighbors=[64], use_prediction_time=True, query=query2) # MAE: 0.2355

7.5 Add or Remove Features from the Graph

By default, all the data in the graph are used for prediction. However, too fine-grained features may introduce noise as part of in-context learning. Removing those features can improve performance.

Similarly, you may improve model performance by adding additional signals by providing new tables or new column features in existing table.

8. Results Summary

By iteratively refining predictive query alignment, neighborhood sampling, and run mode, MAE improved from 0.3913 → 0.2106.

Best Configuration:

1graph = rfm.Graph.from_data(df_dict, verbose=False)
2model = rfm.KumoRFM(graph)
3
4evaluate(run_mode='best', num_neighbors=[64], use_prediction_time=False, query=query2)

9. Key Takeaways

  • Align predictive queries with business logic and in-context sampling.
  • Optimize neighborhood sampling (num_neighbors).
  • Use higher run_mode values for accuracy-sensitive applications.

To learn more about explainability see this example notebook