Retraining on New Data
Keep a scenario current as new events arrive, new entities appear, and behaviour drifts — without retraining the foundation model. The foundation model is trained once and reused; the scenario is the cheap part you refresh against a newer date range.
To bring a scenario up to date you:
- reuse your existing foundation model checkpoint,
- point scenario training at a newer date range via the
splitargument, and - call
fit()to write a refreshed scenario checkpoint.
Two-stage design
The foundation model learns general-purpose entity representations and stays fixed. A scenario fine-tunes it for one prediction task. To pick up new data you re-run scenario training against a newer date range while pointing at the same foundation model checkpoint.
How often should I retrain?
Typically every ~4 weeks, to account for new entities, seasonal patterns, and behavioural shifts. The right cadence depends on your data refresh rate and domain dynamics — see the FAQ.
Which Approach to Use
There are two ways to bring a scenario up to date. Both reuse the existing foundation model.
| Approach | Function | When to use |
|---|---|---|
| Refresh from the foundation model | load_from_foundation_model() |
Recommended for a genuinely new time window. Re-initialises the scenario head and trains from scratch on recent data. |
| Continue an existing scenario | load_from_checkpoint() with scoring=False |
Resume from an already-trained scenario's weights — e.g. train a few more epochs, or nudge the window forward while keeping learned weights. |
Prerequisites
- A trained foundation model checkpoint (its
fm/subdirectory). - Your data source already contains the new rows for the date range you ask for. BaseModel reads events from the live data source at the date ranges you pass — it does not store a frozen copy. If the new events are not in the table yet, they will not be used.
Passing New Dates
Dates are controlled through the split, which you override at load time with the split argument — for either function. The split is a mapping from data mode (TRAIN / VALIDATION / TEST / PREDICT) to a TimeRange:
from datetime import datetime, timezone
from monad.ui.config import DataMode, TimeRange
new_split = {
DataMode.TRAIN: TimeRange(start_date=datetime(2026, 1, 1, tzinfo=timezone.utc)),
DataMode.VALIDATION: TimeRange(
start_date=datetime(2026, 5, 1, tzinfo=timezone.utc),
end_date=datetime(2026, 5, 31, tzinfo=timezone.utc),
),
DataMode.TEST: TimeRange(
start_date=datetime(2026, 6, 1, tzinfo=timezone.utc),
end_date=datetime(2026, 6, 30, tzinfo=timezone.utc),
),
}
TimeRange takes a required start_date and an optional end_date (open-ended when omitted). Timezone-aware UTC datetimes are recommended to avoid ambiguity. This dict is the concrete form of the TimeSplitOverride type in the Scenario Model API.
Move the data stream start with the window
The split says which events become inputs, validation, and targets. data_start_date says how far back the event stream begins. When you move the window forward you will usually want to move data_start_date with it — pass it as a keyword argument:
Which keyword overrides are honoured
Extra keyword arguments are applied only if they match a field of DataParams (for example data_start_date, minimum_splitpoint_date, maximum_splitpoints_per_entity). Any keyword that does not match a DataParams field is silently ignored — double-check spelling. See Loading Overrides for the full list.
Example: Refresh on a Newer Window
The recommended path for periodic retraining. It reuses the foundation model and trains a fresh scenario head on recent data. Adapt the paths, dates, task type, and target function to your scenario.
from datetime import datetime, timezone
from pathlib import Path
from monad.ui.module import load_from_foundation_model, BinaryClassificationTask
from monad.ui.config import DataMode, TimeRange, TrainingParams
foundation_model_path = Path("./output/fm") # unchanged, reused
scenario_model_path = Path("./churn_2026-07") # new output dir per refresh
def target_fn(history, future, entity, ctx):
... # same labelling logic as before
return label
new_split = {
DataMode.TRAIN: TimeRange(start_date=datetime(2026, 1, 1, tzinfo=timezone.utc)),
DataMode.VALIDATION: TimeRange(
start_date=datetime(2026, 5, 1, tzinfo=timezone.utc),
end_date=datetime(2026, 5, 31, tzinfo=timezone.utc),
),
DataMode.TEST: TimeRange(
start_date=datetime(2026, 6, 1, tzinfo=timezone.utc),
end_date=datetime(2026, 6, 30, tzinfo=timezone.utc),
),
}
trainer = load_from_foundation_model(
checkpoint_path=foundation_model_path,
downstream_task=BinaryClassificationTask(),
target_fn=target_fn,
split=new_split,
data_start_date=datetime(2025, 12, 1, tzinfo=timezone.utc),
)
trainer.fit(
training_params=TrainingParams(checkpoint_dir=scenario_model_path, epochs=5),
seed=42,
)
Keep old checkpoints
Write each refresh to its own checkpoint_dir (e.g. dated) rather than overwriting the previous scenario. This lets you compare metrics across refreshes and roll back if a new window regresses.
Example: Continue an Existing Scenario
Use this when you want to keep the weights an existing scenario already learned and carry on from them. Point load_from_checkpoint() at the scenario checkpoint (not the foundation model) and keep scoring=False (the default) so it loads for training rather than inference.
from datetime import datetime, timezone
from pathlib import Path
from monad.ui.module import load_from_checkpoint
from monad.ui.config import DataMode, TimeRange, TrainingParams
trainer = load_from_checkpoint(
checkpoint_path=Path("./churn_2026-06"), # a trained scenario checkpoint
scoring=False, # load for (resumed) training
split={
DataMode.TRAIN: TimeRange(start_date=datetime(2026, 2, 1, tzinfo=timezone.utc)),
DataMode.VALIDATION: TimeRange(
start_date=datetime(2026, 6, 1, tzinfo=timezone.utc),
end_date=datetime(2026, 6, 30, tzinfo=timezone.utc),
),
},
)
trainer.fit(
training_params=TrainingParams(checkpoint_dir=Path("./churn_2026-07"), epochs=2),
seed=42,
)
The target function is loaded for you
load_from_checkpoint() restores the target function saved with the checkpoint (target_fn.bin), so you do not re-pass target_fn. This differs from load_from_foundation_model(), where target_fn is required.
Expect date-coherence warnings
When the new ranges differ from the ones stored in the checkpoint, or if any period overlaps another, BaseModel emits warnings (it does not stop). This is expected when you deliberately shift the window — read them to confirm the dates are what you intended.
Do I Also Need to Retrain the Foundation Model?
Usually no. The foundation model generalises to unseen behaviour, which is the whole point of the two-stage design. Retrain the foundation model as well when the change is structural rather than incremental — for example a large influx of brand-new entities, a new data source or event type, or a pronounced distribution shift that a scenario refresh alone does not recover from. See Foundation Model training.
Related Resources
| Resource | Description |
|---|---|
| Loading Overrides | Every override you can apply at load time, including entity-based splits (EntitySplitOverride) and prediction filtering |
| Reference: Scenario Model | Full signatures for load_from_foundation_model() and load_from_checkpoint() |
| Inference | Score new data with an existing model without retraining, using prediction_date |
| Testing Against Ground Truth | Measure a refreshed scenario against held-out data |