Baseline diagnostics¶
Cheap pre-training diagnostic that runs once per (target_type, target_name)
in train_mlframe_models_suite and surfaces three answers operators ask
after a model is trained:
- What is the headline metric of a quick fit?
- Which features are dominant — and by how many percentage points?
- Does native residual learning (
init_score/base_margin) already capture the dominant signal?
Output lands at metadata["baseline_diagnostics"][target_type][target_name]
and is logged via format_baseline_diagnostics_report at INFO level
before per-target training starts.
Why this exists¶
The original motivation: a data scientist predicting TVT on ~100 features
only learned that TVT_prev dominated feature importance after the full
suite finished training. Hours of compute to surface a one-line diagnostic.
This module surfaces the dominant feature and quantifies its contribution before per-target training begins, so you can act on it:
- accept the dominance (the model is doing the right thing);
- drop the feature (lift other features into FI);
- configure composite-target discovery (residual learning).
What it does¶
from mlframe.training.core import train_mlframe_models_suite
models, metadata = train_mlframe_models_suite(
df=df,
target_name="TVT",
model_name="experiment_1",
features_and_targets_extractor=fte,
mlframe_models=["lgb", "xgb"],
# default ON for regression / binary classification — opt out via
# baseline_diagnostics_config=BaselineDiagnosticsConfig(enabled=False)
# (BaselineDiagnosticsConfig lives in mlframe.training.configs); with
# enabled=False, metadata["baseline_diagnostics"] is not populated at all.
)
# Read it back
report = metadata["baseline_diagnostics"]["regression"]["TVT"]
print(report["composite_recommendation"]) # 'high_potential' / 'marginal' / 'unlikely_to_help' / 'skipped'
print(report["ablation"][0]) # top dominant feature, e.g. {'feature': 'TVT_prev', 'metric_after_drop': ..., 'delta_pct': 672.4, 'rank': 1}
print(report["init_score_baseline"]) # native residual baseline (or None)
Sample log output¶
[BaselineDiagnostics] target='TVT' (regression) RMSE_raw=11497.6592 sample_n=50000 elapsed=42.3s
[BaselineDiagnostics] Ablation (drop -> Δ%, positive = drop hurt):
rank=1 TVT_prev Δ%=+20.51 RMSE_after_drop=13854.2102
rank=2 X Δ%=+0.04 RMSE_after_drop=11502.1308
rank=3 dY Δ%=+0.02 RMSE_after_drop=11500.0011
...
[BaselineDiagnostics] init_score(TVT_prev) RMSE=11489.32 Δ%=-0.07 vs raw
[BaselineDiagnostics] composite_recommendation=high_potential
reason: top ablation Δ%=20.51 >= 5.00 (strong dominant feature); init_score baseline still off raw by -0.07% (residual has structure)
How it works¶
- Sample. Random 50 000-row sample (configurable). Larger frames are subsampled to keep the diagnostic under one minute.
- Quick fit. One LightGBM model on an 80/20 random holdout with
n_estimators=100. Records headline metric (RMSEfor regression,AUCfor binary) andfeature_importances_. (Was 200; a 6-scenario x 3-seed bench showed the dominant-feature verdict is identical at 100 and the ablation runs ~1.8x faster — seesrc/mlframe/training/baselines/_benchmarks/bench_ablation_n_estimators_provisioning.py.) - Ablation. Top-K features by FI are each dropped in turn (sequential, independent — not cumulative), the model is refit on the reduced feature set, and Δ% is measured against the raw fit. Sign convention: positive Δ% always means "dropping this feature hurt performance".
init_scorebaseline (regression and binary classification). Top-1 dominant feature is passed asinit_scoreto a fresh LightGBM. The model now learns only the residual. If the resulting metric is withininit_score_optimal_threshold_pctof the raw fit, native residual learning already captures the dominant signal.- Recommendation. Three-way classifier:
| Recommendation | Condition |
|---|---|
high_potential |
max ablation Δ% ≥ high_potential_min_dominance_pct AND init_score baseline did NOT close the gap |
marginal |
max ablation Δ% in [marginal_threshold_pct, high_potential_min_dominance_pct) |
unlikely_to_help |
max ablation Δ% < marginal_threshold_pct OR init_score baseline matches raw within threshold |
skipped |
config disabled / unsupported target_type / degenerate inputs |
Configuration¶
BaselineDiagnosticsConfig(
enabled=True, # default ON
ablation_top_k=5, # drop top-5 by FI
quick_model_n_estimators=100,
quick_model_num_leaves=31,
quick_model_learning_rate=0.05,
init_score_top_k=1, # 1 = single-feature, K>1 = OLS combiner
init_score_apply_to_target_types=("regression", "binary_classification"), # binary supported via a logit-space init_score
sample_n=50_000, # None = use full train
high_potential_min_dominance_pct=5.0, # Δ% threshold for "dominant"
init_score_optimal_threshold_pct=1.0, # init_score within 1pct of raw -> already optimal
marginal_threshold_pct=2.0,
apply_to_target_types=("regression", "binary_classification"),
random_state=42,
)
When the diagnostic is skipped¶
The component never raises into the training pipeline; on any internal
error it returns a skipped=True report with skip_reason set and emits
a single WARNING log. Skip reasons in the wild:
config.enabled=False— explicitly disabled.target_type='X' not in apply_to_target_types=...— multiclass / multilabel / LtR / quantile_regression are out of scope (init_score semantics don't carry).no feature_cols provided— degenerate input.length mismatch X=N vs y=M— caller passed misaligned arrays.raw quick-fit metric is non-finite— typically a constant target or a near-empty sample.internal_error: ...— anything else (LightGBM unavailable, fit exception, etc.).
Limitations¶
- Sequential ablation, not joint. Two features that are individually small but jointly large will not surface as dominant. Cost of joint ablation grows combinatorially; out of scope here.
- Sample bias. The diagnostic runs on a random 50 000-row sample by
default. For temporal datasets this is not a temporal split — set
sample_n=Noneto use the full train, or pre-sort and use a smaller sample if recency matters more than statistical noise. init_scorebaseline now covers regression AND binary classification. Binary'sinit_scoreis a logit offset: top-K dominant features are LR-combined into a probability-scale score, converted to logit, and passed as LightGBM'sinit_score=so the booster learns the residual logit.- Multiclass / multilabel skipped. The native residual story breaks
down (no scalar
y - base). Future composite-target discovery will decide whether to extend this.