Configuration & presets¶
One object drives every ml4 run: Ml4Config. It
describes the problem (what you are predicting and how success is scored) and
the strategy (which models to train, how to split, how to engineer features, and
what to deliver). You hand it to AutoSupervised, and the engine
does the rest.
In short
Ml4Configholds the problem fields (ml_task,eval_metric, …) at the top level and everything else in composed sections such asengine,hpoanddata.data(DataConfig) maps your columns to roles: the target, numeric and categorical columns, and the domain lists that route columns to specialised encoders.- Leave
eval_metricatNoneto get the task default:loglossfor classification,rmsefor regression. - Start from one of the sixteen presets (
presets.default(),presets.quick_explore(), …) and override fields; ml4 re-validates the config when you callfit.
import parleml_ml4
from parleml_ml4 import AutoSupervised, Ml4Config, Tasks
from parleml_ml4.contracts.metrics import EvalMetric
cfg = Ml4Config()
cfg.ml_task = Tasks.BINARY_CLASSIFICATION
cfg.eval_metric = EvalMetric.f1
cfg.data.target_column = "churned"
model = AutoSupervised(cfg).fit(train_df)
Ml4Config is a plain Python dataclass, so it is fully introspectable, copyable,
and serialisable. There is no hidden global state — a run is exactly what its
config says it is.
The composite object model¶
Ml4Config is deliberately thin. Only the problem-definition fields live on
the top-level object; everything else is a composed section dataclass with its
own defaults. This keeps the surface honest: a change in one area (say, imputation)
never disturbs another.
Top-level fields¶
These describe the problem itself and are read at every layer:
| Field | Type | Purpose |
|---|---|---|
ml_task |
Tasks |
BINARY_CLASSIFICATION (default), MULTICLASS_CLASSIFICATION, or REGRESSION. |
eval_metric |
EvalMetric | None |
Optimisation + reporting metric. None resolves to the task default (see below). |
beta_threshold |
float |
Threshold for the beta metric family (default 0.5). |
positive_class |
str | None |
Binary only — the label to encode as class 1; asserted after fit. |
user_defined_metric_ref |
str | None |
Importable "package.module:qualname" for a custom metric. |
user_defined_metric_direction |
"minimize" | "maximize" | None |
Optimisation direction for the custom metric. |
Composed sections¶
Each section is reached as an attribute of the config and holds a focused group of knobs. Set a nested field by assigning to it directly.
cfg.engine.num_bag_folds = 8 # cross-validation folds
cfg.data.target_column = "y" # column roles
cfg.constraints.monotone_auto = True # monotonic constraints
| Section | Type | What it governs | Guide |
|---|---|---|---|
execution |
ExecutionConfig |
Seed, device, parallelism. | Reproducibility, GPU |
data |
DataConfig |
Target + column-role mapping. | this page |
engine |
Ml4EngineConfig |
Training policy (cross-validated ensemble or single model + holdout), folds, stacking, time budget. | Training workflow |
hpo |
HPOConfig |
Hyperparameter search — ml4's Optuna study or AutoGluon's own (provider, off by default). |
Hyperparameter search |
roster |
Ml4RosterConfig |
Which model specs / regulated model to train. | Algorithms & metrics |
constraints |
ConstraintsConfig |
Monotonic constraints. | Constraints |
baseline |
BaselineConfig |
Constant-baseline honesty gate. | Training workflow |
imputation |
ImputationConfig |
Missing-value strategy. | Feature engineering |
balancing |
BalancingConfig |
Resampling for imbalance. (classification only) | Imbalanced data |
outliers |
OutlierRemovalConfig |
Training-row outlier removal. | Feature engineering |
stateless |
StatelessFEConfig |
Domain-aware stateless encoders. | Stateless encoders |
target_encoding |
TargetEncodingConfig |
Categorical target / WOE encoding. | Feature engineering |
target_transform |
TargetTransformConfig |
Regression y-transform tuning, plus the glass-box feature-side power / cyclical switches. (the y-transform half is regression only) | Supervised learning (y-transform) · Feature engineering (feature-side switches) |
stateful_fe |
StatefulFEConfig |
Interactions, clustering, GP text. | Feature engineering |
temporal |
TemporalEncodersConfig |
Datetime / recency encoders. | Temporal encoders |
feature_selection |
FeatureSelectionConfig |
Ranker-based feature pruning. | Feature engineering |
calibration |
CalibrationConfig |
Post-hoc probability calibration. (classification only) | Training workflow |
intervals |
IntervalsConfig |
Regression prediction intervals. (regression only) | Prediction intervals |
deployment |
DeploymentConfig |
Serving artifact delivery. | Deployment |
Details: two sections a shared template deliberately cannot carry
Most sections describe the recipe — what the run learns — and are portable, which is what makes a template worth sharing. Two are not, for opposite reasons, and an embedding app that offers a template editor should keep both off it:
datais dataset-bound. It names specific columns, so it means nothing away from the dataset it was written against. It is stripped wherever it appears, including from a stored run snapshot — some other surface owns the binding, so a copy could only go stale.executionis machine-bound. The same recipe is legitimately run on a laptop and on a GPU box, under one seed and then another. It must, however, survive on an individual run's snapshot: that is whereexecution.seedis recorded, and it is what makes the run reproducible.
The schema publishes the union as the sections missing from x-group-order, and the two
halves are queryable separately — column_referencing_config_paths() and
machine_scoped_config_sections(). Treating them as one set is a tempting simplification
that quietly deletes every run's seed.
Details: task-specific sections and fields
The fit path consults the sections marked above for some tasks and ignores them
entirely for the rest — a classification run never fits prediction
intervals, and a regression run never calibrates probabilities or rebalances
classes. The schema publishes this as an x-tasks hint per section
(schema._SECTION_TASKS), and the template editor hides a section that would be
wholly inert for the template's ml_task rather than offering knobs no run will
read. Setting one anyway is harmless: it is a no-op.
Gating is per section only when the whole section is inert. target_transform
is the counter-example: its y-transform tuner is regression-only, but the two
feature-side switches that live alongside it —
enable_x_power_transform and enable_x_cyclical_transform — are handed to every
GlassBox variant (glm / scorecard / rulefit) on any task, so a binary
scorecard template needs them. Those fields carry their own per-field x-tasks
(schema._FIELD_TASKS), and the section itself is not task-gated.
Validation happens twice
Cross-section consistency (for example, that a feature step whose prerequisite
is switched off is not left mining zero columns) is checked in
Ml4Config.__post_init__ and again at fit entry via
validate_cross_section. That second pass matters because the common "build a
config, then mutate it" idiom bypasses the constructor — see the tip below.
Mutating a built config re-validates on fit
Assigning to nested fields after construction (or applying a preset and then
tweaking it) is fully supported. ml4 re-runs validate_cross_section when you
call fit, so a post-construction change can never silently ship a conflicting
combination.
Column roles: DataConfig¶
DataConfig describes the data itself — which column is the target, which
columns are which kind, and which specialised encoders should look at which
columns. The engine reads it to build AutoGluon's feature metadata and the
split policy keys.
Core roles¶
| Field | Purpose |
|---|---|
target_column |
The label column. Also the default for fit(label=...). |
cat_features |
Categorical columns. |
num_features |
Numeric columns. |
key1, key2 |
Identification keys — never used as features. |
base_time_columns |
Anchor datetimes for "days-since" recency features. |
token_delimiters |
Per-column delimiter for the tokenised-text encoders. |
Free text has no bucket of its own
There is no text_features field: a column becomes text by being assigned a
text-family domain below (text_domain, embedding_domain, mt_domain,
wt_domain, dirty_domain), which is what routes it to an encoder and keeps it
out of the num/cat pool.
Group / time columns live on the split policy
The fold geometry is engine.split_policy (time_column / group_column) —
see Data splitting. DataConfig has no fold, time or group
column field of its own.
Domain-column lists (stateless FE routing)¶
Beyond the three schema buckets, DataConfig carries per-encoder domain lists.
Assigning a column to a domain routes it to the matching stateless encoder, which
parses structure a generic encoder would miss. See Stateless encoders
for the full catalogue; the routing hints are:
| Domain field | Encoder / feature output |
|---|---|
names_domains |
First-name / surname analysis. |
time_domain |
Calendar parts + recency (paired with base_time_columns). |
email_domain, url_domain, ip_domain |
Structured web / network parsing. |
useragent_domain |
Browser / OS / device family from raw UA strings. |
device_info_domain |
Structured device JSON blob. |
address_domain, zip_domain, city_domain, state_domain, street_domain |
Address components. |
phone_domain, creditcard_domain, iban_domain, vat_domain |
Checksum / validity / classification features. |
text_domain |
Free-form text → TF-IDF / SVD. |
embedding_domain |
Free-form text → neural sentence embeddings. |
mt_domain, wt_domain |
Multi-token and weighted multi-token (packed-count) fields. |
dirty_domain |
Noisy high-cardinality strings → Gamma-Poisson. |
cfg.data.target_column = "fraud"
cfg.data.num_features = ["amount", "account_age_days"]
cfg.data.cat_features = ["merchant_category"]
cfg.data.email_domain = ["billing_email"]
cfg.data.useragent_domain = ["user_agent"]
Roles are declarative, not inferred blindly
ml4 only treats a column as a special domain when you place it in that domain list — the specialised encoders never sniff for datetime or email columns on their own. That keeps a run reproducible and leak-safe.
The eval_metric catalogue¶
eval_metric is an EvalMetric enum member. If you leave it None, ml4 resolves
the task-appropriate default at fit time: binary → logloss, multiclass →
logloss, regression → rmse. An explicit metric is validated against the
task and rejected on mismatch.
The classification default is threshold-free on purpose: AutoGluon scores a label metric
(f1, accuracy, …) at a fixed 0.5 / argmax cut that ml4 replaces with a tuned operating
point, so selecting on one picks a model at a cut the product never serves. See
the evaluation-metric catalogue.
auc, logloss, f1, beta, average_precision, accuracy
logloss, accuracy, and the averaged families
f1_macro / f1_weighted,
beta_macro / beta_weighted,
precision_macro / precision_weighted,
recall_macro / recall_weighted
There is no *_micro member: micro-averaging a single-label multiclass
prediction reduces to accuracy identically, for every β — see
Algorithms & metrics for the derivation. Pick
accuracy when that is the quantity you want.
rmse, mse, mae, mape, r2, spearman, pearson
The beta* metrics and any user_defined_metric build a custom AutoGluon
Scorer; the rest map to native AutoGluon metric strings. To supply your own
metric:
cfg.eval_metric = EvalMetric.user_defined_metric
cfg.user_defined_metric_ref = "myproject.metrics:profit_score"
cfg.user_defined_metric_direction = "maximize"
The callable must accept (y_true, y_predicted, sample_weight=None) and return a
scalar. See Algorithms & metrics for the full metric
reference and the custom-scorer mechanics.
Presets¶
Rather than hand-tuning every knob, start from a preset — a one-line factory
that returns a fully-formed Ml4Config with the strategy set (the AutoGluon
budget, the roster, and the feature-engineering toggles). You still supply the
dataset-specific data section.
from parleml_ml4.config import presets
cfg = presets.quick_explore() # a named factory
cfg.data.target_column = "y"
# or resolve by name from the registry
cfg = presets.PRESETS["high_accuracy"]()
cfg.data.target_column = "y"
Presets flip only the levers they mean to change, so the difference between a preset and the default is exactly its intent.
The sixteen built-in presets¶
Roster entries are model specs, not families: a gradient booster names the variant it
trains. The Fit column is the fit shape the preset resolves to: CV k is cross-validated
bagging over k folds (+ stack when a stacking layer trains on top), holdout is the
single deployed model validated on a reserved slice. See
Algorithms & metrics for the full spec table.
| Preset | Fit | Models trained | What it is for |
|---|---|---|---|
default |
CV 5 | (unset) → GBM:gbdt |
Balanced defaults for most datasets — the ROI-optimal starting point. A one-model roster trains no stacking layer. |
quick_explore |
holdout | GBM:gbdt |
Fast first pass (heavy FE off) to validate data wiring in minutes. |
high_accuracy |
CV 8 + stack | GBM:gbdt GBM:dart GBM:rf CAT:symmetric CAT:lossguide XGB:gbtree |
The widest feature space and full variant roster; serves the bagged ensemble (engine.refit_full=False). Four-hour model-selection budget; potentially large artifacts. |
diverse_ensemble |
CV 8 + stack | GBM:gbdt CAT:symmetric XGB:gbtree RF XT LR |
Breadth of families over depth, at default feature cost. |
tuned_single |
holdout | GBM:gbdt XGB:gbtree CAT:symmetric |
Tuned single model — a seeded Optuna TPE search across the boosters, deploys the single best (DAI / H2O / mljar style). |
tuned_ensemble |
CV 8 + stack | GBM:gbdt XGB:gbtree CAT:symmetric |
Tuned ensemble — one search per roster model, then bagged and greedy-ensembled. |
reliable_tuned_ensemble |
CV 8 + stack | GBM:gbdt XGB:gbtree CAT:symmetric |
tuned_ensemble with parameter selection over five policy-aware folds at 20 trials per booster — at most 300 search fits before the final training. |
tuned_variants |
CV 8 + stack | GBM:gbdt GBM:dart GBM:rf CAT:symmetric CAT:lossguide XGB:gbtree |
The widest roster ml4 ships: every booster variant, each searched separately, then bagged and ensembled. The most expensive template here — six Optuna studies before AutoGluon starts. |
real_time |
holdout | GBM:gbdt XGB:gbtree |
Latency-optimised: a tuned Treelite-eligible booster, narrow feature pool, no bagging. |
large_dataset |
holdout | GBM:gbdt |
Big tables (~2M+ rows): one time-boxed LightGBM with lean FE, tuned on a short leash (15 trials, not the usual 60). |
small_data |
CV 10 | GBM:gbdt CAT:symmetric XGB:gbtree LR |
Under ~20k rows: variance control — ten folds and no stacking layer (stacking overfits at this size), feature selection off. |
text_heavy |
CV 5 + stack | GBM:gbdt LR |
Free-text-heavy data: neural sentence embeddings on, arithmetic crosses off. |
foundation_ensemble |
CV 8 | GBM:gbdt CAT:symmetric XGB:gbtree RF XT LR + foundation |
The diverse roster plus the zero-shot foundation challenger, all ensembled over 8 folds with no stacking layer. |
regulated_glassbox |
holdout | one glass-box model | A single transparent model (GLM / EBM / RuleFit) with audit-friendly FE. |
regulated_scorecard |
holdout | one scorecard | A weight-of-evidence logistic whose coefficients become points. |
regulated_monotonic |
holdout | GBM:gbdt XGB:gbtree CAT:symmetric → one winner |
A tuned monotone-constrained booster with auto-derived directions. |
The default roster is one explicit LightGBM, not a portfolio
ml4 never delegates model selection to an AutoGluon portfolio — every fitted model must
carry the feature recipe — so roster.models defaults visibly to one GBM:gbdt.
A preset that wants breadth names its models itself, and every stock preset exposes its
effective roster in the template editor. Regulated presets force a glass-box model, so
their stock roster is ignored outright.
The three regulated_* presets deploy a single, non-bagged model.
regulated_glassbox and regulated_scorecard use transparent estimators and
disable AutoGluon's fit time limit. regulated_monotonic searches constrained
boosters under a one-hour model-selection budget; it is explained through its
monotone contract and SHAP. See Constraints & regulated models.
Preset time budgets¶
engine.time_limit is a nominal search/model-selection budget, not a hard
deadline for the whole experiment. Preparation, native model completion,
refit_full, post-fit evaluation and explanations can add time.
| Preset | Model-selection budget |
|---|---|
default |
30 minutes |
quick_explore |
10 minutes |
high_accuracy |
4 hours |
diverse_ensemble, tuned_single, regulated_monotonic, real_time, large_dataset, text_heavy, foundation_ensemble |
1 hour |
tuned_ensemble, reliable_tuned_ensemble |
2 hours |
tuned_variants |
3 hours |
small_data |
15 minutes |
regulated_glassbox, regulated_scorecard |
No AutoGluon fit time limit |
A bare Ml4Config() has engine.time_limit=None; it differs from
presets.default(), which sets 30 minutes. Override a preset's limit when the
dataset or host needs more time. With native ml4 Optuna search, the search gets
half the budget and its actual elapsed time is deducted from AutoGluon's fit
allowance, subject to a minimum fit allowance. See
Hyperparameter search.
Details: no AutoGluon preset is named in the fit
AutoGluon's presets= ladder varies its model portfolio — which ml4 always replaces with
recipe-carrying models — and a handful of fit levers that ml4 sets explicitly from the
fit shape: folds, stacking, the stacking probe, refit, the time limit. So ml4 names no
AutoGluon preset at all. engine has no preset field; a stored config that still
carries one is refused at load with a message naming the field — delete the key. When you
want a quality level, set the knob that carries it: roster.models for breadth,
engine.num_bag_folds / engine.num_stack_levels for the ensemble, and
engine.refit_full=False to serve the bagged ensemble instead of a refit single model.
Details: model type is part of the config, not a separate launch choice
Whether a run trains a bagged CV ensemble (best accuracy) or a single
deployed model + reserved holdout (simplest to explain / serve / regulate) is
a property of the config itself — engine.training_policy plus
roster.regulated. A single-model preset sets the policy (see
presets._single_model), so it stays single wherever it is used, while a bagged
preset pins its fold count and stack depth explicitly. Everything else about the fit
— whether it bags, stacks, builds the weighted ensemble, keeps only the best model —
is derived from those knobs by parleml_ml4.config.resolve_fit_shape, never stored. The
validation strategy (how rows are assigned to folds / holdout — SplitPolicy) is
the data-driven axis covered in Data splitting & validation, and
three pairings with the policy are refused, each with a message naming the fields:
a temporal split needs chronological_train_validation, chronological_train_validation
needs a temporal split, and a regulated roster cannot use cross_validation.
Composers¶
Two helpers transform an existing config rather than build one from scratch:
| Composer | Effect |
|---|---|
with_foundation_challenger(cfg, model=None) |
Requires the catalogued foundation challenger (TabICL by default). A missing package or a challenger that fails to train stops the run. On a regulated glass-box roster the flag is inert and warns. |
audit_friendly_fe(cfg) |
Disables opaque feature engineering (interactions, clustering, GP text) so a regulated model traces cleanly back to its inputs. |
from parleml_ml4.config import presets
cfg = presets.with_foundation_challenger(presets.diverse_ensemble())
cfg = presets.with_foundation_challenger(Ml4Config(), model="tabicl")
Presets are just configs
A preset returns an ordinary Ml4Config, so you can apply one and then
override any field. The fit-time re-validation keeps your tweaks coherent.
Where to go next¶
- The complete, field-by-field parameter reference: configuration parameters.
- The end-to-end fit story: Supervised learning.
- How splitting and validation work: Data splitting & validation.
- The preset registry as data: presets reference.