Skip to content

Models: Level and Overrides

COREMat selects models either via a level preset or an explicit selected list. Individual model behaviour can be fine-tuned with overrides.


Model level presets

models.level selects a built-in group of models. When models.selected is also set, selected takes precedence.

Level Models included
explain Four models you can reason about: OLS, Ridge, a random-forest baseline, TabPFN v2
perform Ten strong learners: Ridge, Huber, random forest, ExtraTrees, kernel ridge (RBF/linear), SVR, LightGBM, CatBoost, TabPFN v2
compete Ten tuned models — the searched siblings of perform plus XGBoost. Not every registered model

Each level is a curated list in LEVEL_PRESETS (core/config/models.py), not a filter over the registry: XGBoost appears only in compete, and none of the three levels covers all 36 registered models. With neither level nor selected (level: null, the default) the run trains the single fastest model, lin__ols, and says so in the log.

models:
  level: perform   # LightGBM, CatBoost, random forest, … (XGBoost only in compete)

Explicit model selection

models:
  selected:
    - ens__cb__absolute
    - ens__xgb__squared
    - lin__ridge

Model IDs follow the schema <category>__<family>__<variant>. See the Model Zoo for all available IDs.


Per-model overrides

models.overrides is keyed by model ID. Each entry is a ModelRunOverride.

Available override keys

Key Type Effect
suffix str Append a suffix to the model's results directory name
param_space_override dict Search Space Override: replace individual entries of the HPO search space (Optuna distributions, keys prefixed regressor__). Parameters you do not name stay in the search
preprocess_override PreprocessPolicy Override preprocessing for this model only

To run only some models, list them explicitly in models.selected — there is no per-model "disable" flag.


Full example

from optuna.distributions import FloatDistribution, IntDistribution

from coremat import (
    FrameworkConfig,
    PathsConfig,
    DataPreprocessingConfig,
    ModelsConfig,
    ModelRunOverride,
    PreprocessPolicy,
    run_training,
)

fw = FrameworkConfig(
    paths=PathsConfig(data_file_path="data/alloys.csv", run_name="overrides_demo"),
    data=DataPreprocessingConfig(
        predict_column="yield_strength",
    ),
    models=ModelsConfig(
        selected=["ens__xgb__squared", "lin__ridge"],
        overrides={
            "ens__xgb__squared": ModelRunOverride(
                suffix="narrow_search",         # results dir: ens__xgb__squared_narrow_search
                param_space_override={          # replace these entries — the other
                                                # parameters stay in the search
                    "regressor__learning_rate": FloatDistribution(0.01, 0.05, log=True),
                    "regressor__max_depth": IntDistribution(4, 6),
                    "regressor__n_estimators": IntDistribution(200, 500),
                },
            ),
            "lin__ridge": ModelRunOverride(
                preprocess_override=PreprocessPolicy(
                    scale_numeric="robust",     # switch to a robust scaler for Ridge
                    encode_low_card="onehot",
                ),
            ),
        },
    ),
)

result = run_training(fw)

Notes

  • param_space_override merges into the registered search space: it replaces the entries it names and leaves every other parameter in place. Keys are pipeline-prefixed (regressor__<param>) and values are Optuna distributions (FloatDistribution, IntDistribution, CategoricalDistribution). A key the registered space does not contain raises UnknownOverrideParameterError — a registry rename fails loudly instead of quietly shrinking the search.
  • To take a parameter out of the search, pin it — give it a distribution without freedom, e.g. CategoricalDistribution([3]). Leaving it out of the override does not remove it: the registered range still applies. And removing a parameter from the space would not give you the curated value either — an Optuna model's estimator is constructed without hyperparameters, so it would fall back to the library default (XGBRegressor(n_estimators=100)), not a curated one. Pinning keeps the value visible in the config and in the Effective Search Space.
  • An overridden entry is never narrowed — but only that entry. Registered entries are clamped to the row count of each search (the Effective Search Space, see Nested CV), and they keep being clamped even when another parameter of the same model is overridden. Overriding one upper bound does not switch the mechanism off for the whole model. This is what makes an override a working answer to a "model skipped, search space degenerate" message.
  • A degenerate override is not corrected, but it is not computed either. If the range you set lies entirely in the region where the model becomes a constant predictor, the model is skipped like any other empty Effective Search Space. If it only reaches into that region, the range stays exactly as written and the overlap is reported. Your range is never silently changed.
  • Small datasets: iteration and tree counts (n_estimators, iterations, max_iter) are deliberately left out of the automatic narrowing, so on a few dozen rows an override is the way to keep them sane — IntDistribution(50, 600) instead of the declared 200..5000. Doing so does not cost you the clamping of the other nine parameters.
  • To run most of a preset but skip one model, use an explicit models.selected list instead of a level.

Overrides from a YAML config

The same overrides work from a YAML file. Optuna distributions have no YAML spelling of their own, so they are written in optuna's canonical form — the one optuna.distributions.distribution_to_json produces — and optuna reconstructs them:

models:
  selected:
    - ens__xgb__squared
  overrides:
    ens__xgb__squared:
      suffix: narrow_search
      param_space_override:
        regressor__n_estimators:
          name: IntDistribution
          attributes: {low: 50, high: 600, step: 1, log: false}
        regressor__learning_rate:
          name: FloatDistribution
          attributes: {low: 1.0e-3, high: 0.2, log: true, step: null}
        regressor__max_depth:
          name: CategoricalDistribution
          attributes: {choices: [null, 4, 6, 8]}
      preprocess_override:
        scale_numeric: robust
        encode_low_card: onehot

Anything that is not a distribution in this form — a bare [50, 600], say — is rejected when the config is loaded, not hours into the run.

You rarely have to write this by hand. Build the configuration in Python and export it:

fw = FrameworkConfig(...)
fw.save_yaml("paper-runs/snow_mps.yaml")     # or: print(fw.to_yaml())
coremat train paper-runs/snow_mps.yaml

Loading that file back yields an equal FrameworkConfig, so the batch run is the same run. Every run also saves its own configuration to _config/{timestamp}_config.yaml, which can be passed back the same way.

  • Models with supports_optuna=False run no Optuna search. A param_space_override on one still applies, but as fixed values rather than a space: every entry must be pinned, and the value is set on the estimator directly. A range raises UnpinnedBaseOverrideError — there is no search to draw from, so pin it (IntDistribution(3, 3)).