Skip to content

FittedPipeline

The deployable artifact returned by AutoSupervised.fit. It holds only what prediction needs (the AutoGluon predictor, the frozen recipe, and the fitted calibration / thresholds / intervals / target-transform / baseline layers) and applies them at predict time. See the inference guide.

In short

  • FittedPipeline is what AutoSupervised.fit returns and parleml_ml4.load_pipeline reloads.
  • It holds the AutoGluon predictor, the frozen recipe and the post-fit layers, and applies them at predict time.
  • decision_basis and decision_provenance make the decision layer re-decidable without retraining.
  • feature_insight carries the fit-time "why" payload for raw text and categorical columns.

Two passengers are not prediction layers. decision_basis is what calibration, the decision threshold and the baseline gate were fitted from, and decision_provenance says which revision of those three is serving — together they are what makes the layer re-decidable without retraining. See the decision layer.

The other is feature_insight, which carries the fit-time "why" payload for the raw text / categorical columns. It rides here because nothing downstream can rebuild it — the bundle keeps no training rows — so an explanation surface that tried to derive it at serve time would have nothing to read. See the interpretability contract.

FittedPipeline dataclass

FittedPipeline(
    predictor: TabularPredictor,
    task: Tasks,
    positive_class: Any = None,
    calibration: Optional[CalibrationMap] = None,
    calibration_report: Optional[CalibrationReport] = None,
    decision_basis: Optional["DecisionBasis"] = None,
    decision_provenance: Optional[
        "DecisionLayerProvenance"
    ] = None,
    threshold: Optional[BinaryThreshold] = None,
    multiclass_thresholds: Optional[
        MulticlassThresholds
    ] = None,
    multiclass_thresholds_discarded: bool = False,
    interval: Optional[IntervalLayer] = None,
    interval_coverage: float = 0.9,
    apply_calibration: bool = True,
    baseline: Optional[BaselineReport] = None,
    holdout_info: Optional[Dict[str, Any]] = None,
    target_transform: Optional[TargetTransform] = None,
    eval_metric: Optional[EvalMetric] = None,
    recipe: Optional["FeatureRecipe"] = None,
    monotone_evidence: Optional["MonotoneEvidence"] = None,
    infer_cost_s_per_row: Optional[float] = None,
    treelite: Optional["TreeliteAccelerator"] = None,
    feature_insight: Optional["FeatureInsight"] = None,
    requested_models: Tuple[str, ...] = (),
    search: Optional["SearchReport"] = None,
    fit_rows: Optional["FitRows"] = None,
    out_of_sample: Optional["OutOfSampleProba"] = None,
)

A deployed model + its post-fit statistical layers.

Task-aware: classification serves predict / predict_proba; regression serves predict / predict_interval. Each task's post-fit layers are optional (fit only when applicable), so the same facade covers all three.

is_calibration_active property

is_calibration_active: bool

Whether calibrated probabilities are served by default.

predict_is_expensive property

predict_is_expensive: bool

Whether serving is costly — MLI reads this to budget SHAP / permutation sampling (§7.3). True when either (a) a foundation / in-context leaf is in the deployed strongest model (it re-encodes its context on every predict), or (b) a measured end-to-end cost exceeds :data:EXPENSIVE_S_PER_ROW (see :meth:measure_infer_cost).

ml_task property

ml_task: Tasks

Alias of :attr:task under the published InterpretablePipeline name (§7.3).

classes_ property

classes_: Optional[list]

The class labels MLI aligns explanations to; None for regression.

Deliberately the full column set, including any :meth:untrained_classes: this is a column-alignment contract for :meth:predict_proba, and AutoGluon's frame carries a (constantly zero) column for a deleted class. Dropping it here would misalign every consumer that zips classes_ against a probability row. The place the distinction matters is scoring, and that is where it is made (:mod:parleml_ml4.evaluation).

predict_proba

predict_proba(
    X: DataFrame, *, calibrated: Optional[bool] = None
) -> DataFrame

Class probabilities; calibrated by default when a calibration is present.

predict

predict(X: DataFrame) -> Series

Predictions: thresholded class labels (classification) or point values in original units (regression). Classification with neither a decision layer nor an active calibration delegates to AutoGluon's own predict to preserve its native output; otherwise the label is decided on the served probabilities, as the baseline gate, :func:~parleml_ml4.evaluation.evaluate and the export decide it.

There is deliberately no calibrated toggle here, unlike :meth:predict_proba (CAL-5). ml4 fits ONE decision layer, on whichever probabilities actually serve — decision_layer.fit_decision_layer tunes it on the calibrated frame when calibration won its bake-off, raw otherwise — and stores no raw-scale twin. A toggle on this method therefore offered a cutoff selected on one scale to numbers on another: for binary, a threshold tuned at (say) 0.31 on calibrated probabilities applied to a raw column whose operating point is nowhere near it; for multiclass, per-class divisors tuned one-vs-rest on calibrated columns dividing a raw matrix. Both produce plausible-looking labels at an operating point the model was never validated at, silently. predict_proba keeps the toggle because a probability is meaningful on either scale; a decision is not. The air-gapped :class:~parleml_ml4.delivery.export.ExportedPipeline has never had the toggle and was always correct — this brings the in-process facade in line with the artifact it ships.

main_thresholds

main_thresholds() -> Dict[Any, float]

The per-class operating points the SERVED decision rule uses; empty when no layer was fit.

Binary → {positive_class: cut}, the value predict compares P(positive) against. Multiclass → {class: divisor}, the vector argmax_c P_c / d_c divides by (:meth:~parleml_ml4.delivery.thresholds.MulticlassThresholds.divisors), not the raw per-class sweep outcome in :attr:~parleml_ml4.delivery.thresholds.MulticlassThresholds.thresholds.

The distinction is not cosmetic and this method used to get it wrong. A class whose one-vs-rest sweep refused to run — or returned a bar at ~0 — keeps a degenerate marker in thresholds that the served rule deliberately does not divide by (MET-2), so publishing the raw map here meant every consumer of ml4's "published operating point" (measures.json, the model card, MLI's case page, the backend's batch scorer) stated a decision rule the deployed model does not implement, for precisely the classes whose handling is least obvious. :meth:tuned_thresholds is where the measurement lives.

tuned_thresholds

tuned_thresholds() -> Dict[Any, float]

What each per-class sweep actually RETURNED, before the served rule reinterprets it.

Identical to :meth:main_thresholds for binary (a cut is served as measured) and for a multiclass layer whose every class tuned to a usable bar. They diverge exactly on :meth:substituted_threshold_classes, where the recorded value is a degenerate marker rather than a measurement — which is the honest thing to publish under this name and the dishonest thing to publish under the other.

substituted_threshold_classes

substituted_threshold_classes() -> Tuple[Any, ...]

Classes where :meth:main_thresholds and :meth:tuned_thresholds disagree — the multiclass classes served a neutral divisor because their own bar is unusable. Always empty for binary (there is nothing to substitute) and when no layer was fit.

main_decision_threshold

main_decision_threshold(cls: Any = None) -> Optional[float]

The operating threshold MLI displays: for cls if given, else the binary positive-class threshold; None when no decision layer was fit or (multiclass) no single main threshold exists.

leaf_models

leaf_models() -> 'list[LeafModelHandle]'

The deployed strongest model's raw leaf estimators for MLI (§7.3) — exactly one on the regulated path. Each carries the library estimator MLI introspects directly (ml4 extracts no coefficients/rules/shapes itself).

scorecard_points

scorecard_points() -> 'Optional[ScorecardTable]'

The deployed scorecard's points table + its scale; None for any other model.

The regulated scorecard's deliverable is not its coefficients — it is base + Σ points·attribute, in the attributes' own units. That table is computed by :class:~parleml_ml4.engine.models.glassbox.GlassBoxModel and lives on the AutoGluon wrapper, one level above the LogisticRegression a :class:~parleml_ml4.contracts.pipeline_protocol.LeafModelHandle carries (whose coef_ is in standard deviations of an engineered column, not in an applicant's units). So this accessor exists rather than a handle field: without it the audit artifact the regulated path is for never left the library (REG-2).

None — not a guess — when the strongest model is bagged or an ensemble of scorecards. Each fold child fits its own coefficients and AutoGluon averages probabilities, so no single table reproduces the served score, and a table that does not reproduce the score is not an audit artifact. A regulated run cannot hit that case (its fit shape never bags and builds no weighted ensemble); it is guarded because publishing one fold's points as "the scorecard" would be a quiet lie if that ever changed.

best_introspectable_leaf

best_introspectable_leaf() -> 'Optional[LeafIntrospection]'

The best introspectable basic-tree base leaf on the board — for MLI native rules (§7.3).

Unlike :meth:leaf_models (which unwraps the deployed strongest model), this scans the whole AutoGluon board for the best-scoring readable tree — GBDT preferred, glass-box as a fallback — so native rules are produced even when the strongest model is a weighted ensemble or a foundation model. None when nothing introspectable survives on disk.

warmup

warmup(X: DataFrame) -> None

Prime lazy model / feature-generator state so the first real predict isn't cold (§7.9). Runs one throwaway prediction on a tiny slice of X; idempotent and safe to call repeatedly. A no-op on an empty frame.

measure_infer_cost

measure_infer_cost(
    X: DataFrame, *, repeats: int = 3
) -> float

Measure END-TO-END serving cost in seconds/row (feature generation + model) over a sample of X — the real latency an SLA sees, not AutoGluon's model-only infer_limit (§7.9 / P16). Warms up first, takes the best of repeats timed passes (least contended), records it on :attr:infer_cost_s_per_row, and returns it.

untrained_classes

untrained_classes() -> Tuple[Any, ...]

Classes AutoGluon deleted before training — see :func:untrained_classes_of (TYPE-3).

A class the model has no column to predict is not a class it scored 0 on, so the evaluation harness excludes these from per_class and the headline (accounting for their rows under unscored_rows) and names them under test.untrained_classes; measures.json carries the same list for a run with no outer test.

served_model

served_model() -> Optional[Dict[str, Any]]

Which model predict serves, and whether it is a refit_full retrain.

Under ml4's default engine.refit_full and set_best_to_refit_full the candidate that won selection (selected_as) is retrained on every training row — AutoGluon's internal tuning rows included, never the reserved holdout or the outer test — and that *_FULL twin is what ships. The run's reported scores (the reserved validation rows and the outer test) are the served model's own; the selection-time validation score belonged to the candidate before its refit. None for a predictor that cannot say.

text_embedding_records

text_embedding_records() -> 'Tuple[EmbeddingRecord, ...]'

What this model's text embeddings came from: one record per embedded column.

A local model's pinned revision, or a provider's connection, model and fingerprint — the model's external dependencies, read off the fitted GLOBAL chain. Empty when the recipe embeds nothing (or embedded nothing because its backend was missing at fit).

transform

transform(x: DataFrame) -> DataFrame

Raw input → the model-input feature frame (the GLOBAL FE / feature generator). MLI transforms once here, then reuses the frame across the methods below (the per-leaf fold-local FE is applied by :meth:prepare_member_introspection_frame).

predict_proba_from_encoded

predict_proba_from_encoded(
    encoded: DataFrame,
    *,
    calibrated: bool = True,
    member: Optional[str] = None
) -> DataFrame

Class probabilities from an already-:meth:transformed frame (skips re-running the feature generator); member scores one leaf model by name, else the strongest model.

predict_point_from_encoded

predict_point_from_encoded(encoded: DataFrame) -> ndarray

Regression point predictions in ORIGINAL units from an already-:meth:transformed frame — the regression analogue of :meth:predict_proba_from_encoded. Per-row explanation (justify) transforms once then reuses the frame, so it needs a point predictor that skips the feature generator; the target transform is inverted so callers see the same units as :meth:predict.

prepare_member_introspection_frame

prepare_member_introspection_frame(
    encoded: DataFrame,
) -> DataFrame

The introspected base tree's full post-FE matrix (global + its fold-local FE) from a :meth:transformed frame — the member space MLI aligns SHAP / archetypes / rules on. Uses the best introspectable base tree (see :meth:_introspection_leaf), which matches what compute fits its artifacts on even when the strongest model is an ensemble; raises only when nothing on the board is introspectable.

evaluate

evaluate(
    X_test: DataFrame,
    y_test: Any,
    *,
    metric: Optional[EvalMetric] = None,
    bootstrap: int = 1000,
    cfg: Optional["Ml4Config"] = None
) -> Dict[str, Any]

Fit-time measures + reported scores on a labelled test set.

Lives here, not only on :class:~parleml_ml4.facade.AutoSupervised, because this is the object you actually hold after :func:parleml_ml4.load_pipeline — a reloaded bundle had predict and save but no way to score itself, so evaluating one meant knowing to reach past the published surface into :mod:parleml_ml4.evaluation.

metric overrides the pipeline's own evaluation metric for this report; bootstrap is the resample count behind the headline score's 95% CI (0 skips it).

cfg is only needed for a run whose metric was beta / user_defined_metric: the custom scorer cannot be rebuilt from the bundle alone, so without it the report falls back to the task default and says so rather than silently mislabelling the number (DL-6). :meth:AutoSupervised.evaluate <parleml_ml4.facade.AutoSupervised.evaluate> passes its own config for you; pass it here when you reloaded from disk and used a custom metric.

save

save(path: str) -> str

Persist this pipeline as a bundle directory (predictor + post-fit layers).

load classmethod

load(path: str) -> 'FittedPipeline'

Restore a pipeline from a bundle written by :meth:save.

A pipeline that pseudonymises identifier columns predicts only where $ML4_PSEUDONYMISATION_KEY holds the key it was fitted with — the bundle stores the key's fingerprint, never the key (see :func:~parleml_ml4.delivery.bundle.load_pipeline).

threshold_report

threshold_report(
    X: DataFrame, y_true: Any
) -> ThresholdScores

Honest vs optimistic F-β on a labelled set — the deployed operating point (tuned on validation) vs a threshold re-tuned on this set (leaks → an upper bound, never the real score). Binary only; scores calibrated proba, matching how the operating threshold was tuned.

predict_intervals

predict_intervals(X: DataFrame) -> DataFrame

Regression prediction + every configured band: prediction plus lo_<L> / hi_<L> per level (adaptive width under CQR). Bands obey lo <= prediction <= hi and nest by level.

predict_interval

predict_interval(
    X: DataFrame, *, coverage: Optional[float] = None
) -> DataFrame

Regression prediction + a single band (prediction / lower / upper) at coverage (default: the widest configured level). For all configured levels at once use :meth:predict_intervals.

Next: Interpretability contract · Inference & fast scoring