Skip to content

Model configuration

How to set hyperparameters and choose features for a forecasting experiment.


Config files

Each model family has a base YAML in conf/model/. The only one today is conf/model/xgboost.yaml. The file has two required top-level keys:

# Identifies the BaseForecaster subclass to instantiate.
_target_: xgboost_forecaster.forecaster.XGBoostForecaster

model_params:
  selected_features:
    - "power_lag_24h"
    - "temperature_2m"
    - ...

  # Model-specific hyperparameters:
  n_estimators: 500
  learning_rate: 0.05
  ...

_target_ is a fully-qualified Python class path, resolved by contracts.config_schemas.import_class at registration time. You should not change it unless you are wiring up a new model family. The class that validates model_params is not named here: it is the forecaster's CONFIG_CLASS, the same class its load rebuilds a saved config with, so the two cannot drift apart.


Features (selected_features)

selected_features is a set of strings, written in the YAML as a list. Registration only checks that it is a list of strings coercible to that set — a typo'd top-level key (e.g. selected_featuers) is rejected there, by pydantic's extra="forbid" on BaseForecasterConfig. The individual strings inside the list are not parsed until training runs: the feature engineering pipeline (ml_core.features._parsed_features.ParsedFeatures.from_strings) parses each one into a typed descriptor and raises ValueError on any unrecognised or forbidden name, so a typo'd feature name (e.g. tempurature_2m) surfaces only then, not at registration.

Power lags

Pattern Example Notes
power_lag_{N}h power_lag_24h Observed power at valid_time − N hours. N must be a positive integer ≤ 17,520 (2 years).

Power lags shorter than or equal to the forecast lead time are automatically nullified at engineering time to prevent lookahead bias — see Lookahead-bias guardrails below.

Raw weather variables

These are the NWP variables available directly from ECMWF ENS at valid_time.

Feature name Variable
temperature_2m 2 m temperature (°C)
dew_point_temperature_2m 2 m dew point (°C)
wind_speed_10m Wind speed at 10 m (m/s)
wind_direction_10m Wind direction at 10 m (°)
wind_speed_100m Wind speed at 100 m (m/s)
wind_direction_100m Wind direction at 100 m (°)
pressure_surface Surface pressure (Pa)
pressure_reduced_to_mean_sea_level MSLP (Pa)
geopotential_height_500hpa 500 hPa geopotential height (m)
downward_long_wave_radiation_flux_surface Downward LW radiation (W/m²)
downward_short_wave_radiation_flux_surface Downward SW radiation (W/m²)
precipitation_surface Total precipitation (kg/m²)
categorical_precipitation_type_surface Precipitation type (categorical)

Weather lags and rolling means

Any raw weather variable can be lagged or smoothed:

Pattern Example Notes
{weather_var}_lag_{N}h temperature_2m_lag_6h NWP value at valid_time − N hours. Never leaky (NWP forecasts are available for future times).
{weather_var}_rolling_mean_{N}h temperature_2m_rolling_mean_6h Mean of the weather variable over the N-hour window ending at valid_time. Never leaky.

N must be a positive integer ≤ 17,520 (2 years).

Time features

Cyclical and calendar encodings computed from valid_time in local wall-clock time (respecting DST). Electricity demand follows human behaviour, which tracks local time not UTC.

Feature name Encoding
local_time_of_day_sin Sine of fraction of day (24 h period)
local_time_of_day_cos Cosine of fraction of day (24 h period)
local_time_of_year_sin Sine of fraction of year (ordinal day of year divided by 366, so a given calendar date's fraction shifts by one day's worth after 29 February in a leap year)
local_time_of_year_cos Cosine of fraction of year (see local_time_of_year_sin)
local_day_of_week_sin Sine of day-of-week (7 day period)
local_day_of_week_cos Cosine of day-of-week (7 day period)
local_day_of_week Day-of-week name as a categorical (MondaySunday)
local_utc_offset_minutes UTC offset in minutes (0 or 60 for GB; 330 for India's +5:30)

Static derived features

Derived from weather variables via a fixed formula; no time-shifting.

Feature name Definition
windchill Wind-chill temperature from temperature_2m and wind_speed_10m (°C)

Pass-through base columns

These columns from the AllFeatures frame can be passed directly to the model as-is (e.g. to let the model learn horizon-dependent biases).

Feature name Description
nwp_lead_time_hours Hours between nwp_init_time and valid_time
ensemble_member ECMWF ensemble member index (0–50)
time_series_id Substation identifier (integer)
time_series_type Asset category, e.g. PV, Wind, Disaggregated Demand (categorical string)
power_fcst_init_time When the forecast was issued
nwp_init_time When the NWP model ran

Lookahead-bias guardrails

Two feature names are forbidden and raise ValueError at parse time:

  • power — requesting the raw target variable as an input would let the model learn a trivial identity function, useless at inference time.
  • valid_time — an index column; use time features (e.g. local_time_of_day_sin) instead.

Power lags are automatically nullified by the feature engineering pipeline when the lag is shorter than or equal to the forecast lead time. For example, power_lag_1h would leak observed power into a 1-hour-ahead forecast, so its value is set to null for those rows. The model sees a null and treats it as a missing value (XGBoost handles nulls natively). This nullification happens per-row in _nullify_leaky_lags(), not at config time — the feature name is still valid; the pipeline just makes it safe.

Weather lags and rolling means are never nullified: NWP forecasts cover future valid_times, so a weather feature is always available at inference time regardless of lead time.


XGBoost hyperparameters

These fields live in XGBoostConfig (which inherits the universal fields from BaseForecasterConfig).

Universal fields (all model families)

Field Default Description
selected_features (required) List of feature name strings — see above.
weather_source "" Leaderboard tag, e.g. "ecmwf_control" or "ecmwf_ens".
training_strategy "" Leaderboard tag, e.g. "horizon_as_feature".
random_seed 0 Passed to XGBoost's seed param; makes re-training a fold reproduce the same model.
experiment_name "" Set from the job's own experiment_name; cannot be overridden.
ml_flow_experiment_id None Stamped onto every PowerForecast row. Nothing sets it automatically.

XGBoost-specific fields

Field Default Description
n_estimators 1000 Number of boosting rounds (num_boost_round).
learning_rate 0.05 Step size shrinkage (eta).
max_depth 6 Maximum tree depth.
min_child_weight 1 Minimum sum of instance weights in a child.
subsample 0.8 Fraction of rows sampled per tree.
colsample_bytree 0.8 Fraction of features sampled per tree.
device "cpu" "cpu" or "cuda".
objective "reg:squarederror" Loss function.

Tweaking a config for an experiment

You never edit a YAML file per experiment. Instead, pass config_overrides to register_experiment_job. Each override is applied to model_params before the config object is constructed.

Every key must name a field the config class declares — the two tables above. Write n_estimtors instead of n_estimators and registration fails, before a single fold is scheduled, with a ValidationError naming the key. This matters more than a typo usually would, because the searches that drive most registrations are unattended: the LLM auto-research agent registers, materialises and reads the leaderboard with nobody in the loop, and the variant grid sweeps several dimensions at once. A key that was quietly dropped would give you a grid of identical runs, each scoring plausibly, each landing on the leaderboard, and nothing to distinguish that grid from a genuine null result. That is principle 8.

Two further keys are refused for their own reasons. _target_ names the forecaster class, and the config class follows from it: to use a different one, point base_model_config at a different YAML. experiment_name comes from the job's own experiment_name parameter, which would overwrite an override of it.

Example — reduce tree depth and add a feature:

{
  "max_depth": 4,
  "selected_features": [
    "power_lag_24h",
    "power_lag_48h",
    "temperature_2m",
    "local_time_of_day_sin",
    "local_time_of_day_cos"
  ]
}

Every override is a whole-value replacement, not a merge. selected_features is the case you will meet first: to add one feature to the baseline set you must list all the features you want, not just the new one. The same holds when a model_params value is itself a mapping — an override replaces the whole mapping, dropping the base's other keys, so restate every key you want to keep.

The resolved config (the YAML defaults with your overrides applied) is frozen as a JSON tag on the MLflow experiment at registration time. That frozen record is what trained_cv_model reads back at train time — so changing the YAML after registering an experiment has no effect on it.