Learning to rank tuning and uncertainty - #45
Conversation
There was a problem hiding this comment.
Pull request overview
This PR enhances the CatboostRankerMother wrapper to support rank-based predictions and staged-prediction uncertainty estimates, and adds more flexible hyperparameter tuning controls in the shared CatBoost tuning base class.
Changes:
- Added
tune_loss_functionto_CatboostHyperParamsto optionally keep the constructor loss fixed during Optuna tuning. - Introduced rank utilities and rank-normalization options (
scores_to_ranks,predict(..., ranks=..., normalize_by_group_size=...)) plus a rewrittenpredict_uncertaintyfor rank uncertainty viastaged_predict. - Expanded
CatboostRankerMothertuning options (pairwise loss inclusion,top,max_pairs) and implemented estimator parameter/pickling helpers.
Reviewed changes
Copilot reviewed 1 out of 2 changed files in this pull request and generated 4 comments.
| File | Description |
|---|---|
| uv.lock | Bumps the mother-ml package version to 1.0.1. |
| src/mother/ml/models/m_catboost.py | Adds rank conversion helper, new tuning flags, rank prediction/normalization, and staged rank-uncertainty estimation for CatboostRankerMother. |
💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.
There was a problem hiding this comment.
Pull request overview
Copilot reviewed 8 out of 9 changed files in this pull request and generated no new comments.
Suppressed comments (2)
src/mother/ml/models/m_catboost.py:104
ensure_metadata_routing(and its sklearn config imports) is defined but not used anywhere in this module, and its docstring suggests auto-enabling metadata routing whileCatboostRankerMother’s class docs state routing is not enabled automatically. Keeping an unused decorator with contradictory guidance makes the public contract harder to understand.
def ensure_metadata_routing(func: Callable) -> Callable:
"""
Decorator to ensure metadata routing is enabled before executing a function.
This decorator checks if sklearn's metadata routing is enabled and activates it
if necessary. It's particularly useful for initializing ranking models that require
metadata routing for passing additional parameters like group_id.
Parameters
----------
func : Callable
The function to be decorated (typically __init__ of a ranking model)
Returns
-------
Callable
The wrapped function with metadata routing ensured
"""
@wraps(func)
def wrapper(*args, **kwargs):
use_metadata_routing: bool = bool(skl_get_config().get("enable_metadata_routing", False))
if not use_metadata_routing:
module_logger.warning(
"Metadata routing is not enabled, enabling it now. This may cause issues in passing "
"training arguments to other sklearn objects."
)
skl_set_config(enable_metadata_routing=True) # NOSONAR
return func(*args, **kwargs)
return wrapper
src/mother/ml/models/m_catboost.py:1848
CatboostRankerMother.__setstate__dropsposterior_samplingfrom the serialized state (state.pop("posterior_sampling", None)), which prevents the CatBoost base class from restoring that init param. This can changeget_params()after unpickling and may break uncertainty behavior if CatBoost usesposterior_samplingfor virtual ensembles.
self.target_type = state.pop("target_type", "single_target")
self.model_type = state.pop("model_type", "ranking")
state.pop("posterior_sampling", None) # legacy field, no longer stored
self.tune_pairwise_type = state.pop("tune_pairwise_type", False)
self.tune_boosting_type = state.pop("tune_boosting_type", False)
There was a problem hiding this comment.
Pull request overview
Copilot reviewed 7 out of 8 changed files in this pull request and generated no new comments.
Suppressed comments (3)
src/mother/ml/models/m_catboost.py:1699
max_pairsis appended to the PairLogit loss string whenever it is not None, but there’s no guard that it’s a positive integer. Passingmax_pairs=0(or a negative value) will currently produce...max_pairs=0inloss_function, which is likely invalid for CatBoost and is inconsistent with the later> 0checks inset_params()/suggested_params_loss().
elif (
"PairLogit" in kwargs["loss_function"]
and "max_pairs" not in kwargs["loss_function"]
and self.max_pairs is not None
):
src/mother/ml/utils.py:486
get_virtual_prediction()doesn’t validatevirtual_ensembles_count. Several callers (e.g., the regressor/classifier uncertainty paths) forwardn_ensemblesdirectly, sovirtual_ensembles_count=0will currently fall through to CatBoost and fail with a less clear error. Adding a small guard here keeps the behavior consistent with the ranker API (which already enforcesn_ensembles >= 1).
module_logger.info("Using catboost's builtin uncertainty prediction")
if isinstance(model, CatBoostRanker):
src/mother/ml/utils.py:667
topk_rank_disagreement()returns the fraction of ensemble members that place each item in the top-k (i.e., an agreement / membership probability). The name reads like it should increase as ensembles disagree. Consider renaming to something liketopk_membership_probability/topk_membership_frequency, or (if you want to keep this name) returning1 - in_topk.mean(axis=1)and adjusting the doc/tests accordingly.
def topk_rank_disagreement(
rank_ensembles: np.ndarray,
k: int,
) -> np.ndarray:
"""Compute per-item probability of appearing in the top-k across virtual ensembles.
There was a problem hiding this comment.
Pull request overview
Copilot reviewed 7 out of 8 changed files in this pull request and generated no new comments.
Suppressed comments (2)
src/mother/ml/models/m_catboost.py:1788
- CatboostRankerMother.set_params() updates the loss string with max_pairs only when max_pairs is not None, but it never removes an existing max_pairs suffix when the caller sets max_pairs=None (and does not explicitly provide loss_function). This leaves the CatBoost loss_function still enforcing the previous max_pairs value, which contradicts the updated max_pairs attribute and can lead to surprising tuning/training behavior.
if "PairLogit" in updated_loss and max_pairs_changed and self.max_pairs is not None and self.max_pairs > 0:
updated_loss = re.sub(r";max_pairs=[^;]+|:max_pairs=[^;]+", "", updated_loss)
if ":" not in updated_loss and ";" in updated_loss:
updated_loss = updated_loss.replace(";", ":", 1)
separator = ";" if ":" in updated_loss else ":"
src/mother/ml/models/m_catboost.py:1800
- CatboostRankerMother.init disables tune_tree_structure_type / tune_boosting_type when an explicit Pairwise loss is set (to prevent incompatible Optuna trials), but set_params() only validates the current grow_policy/boosting_type and leaves those tuning flags enabled. This can produce invalid hyperparameter trials later (Pairwise loss with non-SymmetricTree/non-Plain settings) and is inconsistent with the constructor’s behavior.
current_params = self.get_params(deep=False)
effective_loss = str(params.get("loss_function", current_params.get("loss_function", "")))
if "Pairwise" in effective_loss:
effective_grow = params.get("grow_policy", current_params.get("grow_policy", "SymmetricTree"))
effective_boost = params.get("boosting_type", current_params.get("boosting_type", "Plain"))
There was a problem hiding this comment.
Pull request overview
Copilot reviewed 7 out of 8 changed files in this pull request and generated no new comments.
Suppressed comments (1)
src/mother/ml/models/m_catboost.py:73
ensure_metadata_routingis defined but not referenced anywhere in the repository, so it is currently dead code (and also forces extra sklearn imports). This increases maintenance surface and can confuse readers about whether metadata routing is automatically enabled. Consider either applying it where intended or removing the decorator and its now-unused imports.
def ensure_metadata_routing(func: Callable) -> Callable:
There was a problem hiding this comment.
Pull request overview
Copilot reviewed 7 out of 8 changed files in this pull request and generated no new comments.
Suppressed comments (2)
src/mother/ml/models/m_catboost.py:77
- ensure_metadata_routing() is defined but not referenced anywhere in the codebase. Keeping an unused decorator (especially one that mutates global sklearn config) increases maintenance cost and can confuse readers about whether metadata routing is auto-enabled for rankers.
def ensure_metadata_routing(func: Callable) -> Callable:
"""
Decorator to ensure metadata routing is enabled before executing a function.
This decorator checks if sklearn's metadata routing is enabled and activates it
src/mother/ml/models/m_catboost.py:2207
- In suggested_params_loss(), max_pairs is appended to PairLogit losses whenever max_pairs is non-None and > 0, but this allows non-integer values (e.g., True, 2.5) to produce an invalid CatBoost loss string like
PairLogit:max_pairs=True. This contradicts the stricter validation used elsewhere in the ranker and can cause Optuna trials to fail unexpectedly.
loss_function += f":max_pairs={self.max_pairs}"
suggested_params[prefix + "loss_function"] = loss_function
There was a problem hiding this comment.
Pull request overview
Copilot reviewed 7 out of 8 changed files in this pull request and generated no new comments.
Suppressed comments (2)
src/mother/ml/utils.py:750
- topk_score_variance() documents reference_ranks as 1-based ranks but doesn’t validate the range. If a caller passes 0-based ranks or values outside [1, n_samples], items can be incorrectly treated as top-k without any error. Adding a simple range check would make the API safer and prevent silent misanalysis.
reference_ranks = np.asarray(reference_ranks).reshape(-1)
if len(reference_ranks) != arr.shape[0]:
raise ValueError("reference_ranks must have one entry per score_ensembles row.")
if not np.isfinite(reference_ranks).all():
raise ValueError("reference_ranks must contain only finite values.")
src/mother/ml/utils.py:702
- topk_rank_disagreement() assumes rank_ensembles contains 1-based ranks, but it only validates finiteness. If callers accidentally pass 0-based ranks (or any values outside [1, n_items]), the function will silently over-count top-k membership (e.g., rank=0 always counts as top-k). Consider validating the rank range explicitly to fail fast on invalid inputs.
This issue also appears on line 746 of the same file.
if not np.isfinite(arr).all():
raise ValueError("rank_ensembles must contain only finite values.")
in_topk = arr <= k
return in_topk.mean(axis=1)
There was a problem hiding this comment.
Pull request overview
Copilot reviewed 7 out of 8 changed files in this pull request and generated no new comments.
Suppressed comments (2)
src/mother/ml/utils.py:491
virtual_ensembles_countvalidation currently requiresisinstance(..., int), which rejects NumPy integer types (e.g.np.int64(10)) even though they are valid positive integers per the error message. This can cause unexpected ValueErrors for callers using NumPy-derived counts; consider acceptingnp.integerand normalizing to a built-inintafter validation.
if (
not isinstance(virtual_ensembles_count, int)
or isinstance(virtual_ensembles_count, bool)
or virtual_ensembles_count < 1
):
raise ValueError(f"virtual_ensembles_count must be a positive integer, got {virtual_ensembles_count}.")
module_logger.info("Using catboost's builtin uncertainty prediction")
src/mother/ml/utils.py:702
topk_rank_disagreementdocuments thatrank_ensemblescontains 1-based ranks, but it never validates that the values are within[1, n_items]. Out-of-range (or 0-based) ranks will silently skew the computed top-k membership probabilities; adding an explicit range check makes the contract enforceable and failures easier to diagnose.
arr = np.asarray(rank_ensembles)
if arr.ndim != 2:
raise ValueError(f"Expected 2D rank_ensembles, got {arr.ndim}D.")
if k < 1:
raise ValueError(f"k must be >= 1, got {k}.")
if k > arr.shape[0]:
raise ValueError(f"k must be <= the number of items ({arr.shape[0]}), got {k}.")
if not np.isfinite(arr).all():
raise ValueError("rank_ensembles must contain only finite values.")
in_topk = arr <= k
return in_topk.mean(axis=1)
There was a problem hiding this comment.
Pull request overview
Copilot reviewed 7 out of 8 changed files in this pull request and generated no new comments.
Suppressed comments (2)
src/mother/ml/models/m_catboost.py:103
ensure_metadata_routingis defined in this module but does not appear to be used anywhere insrc/(only the definition exists). This also pulls inwraps,skl_get_config, andskl_set_configsolely for that unused decorator, which increases maintenance surface and can trigger unused-import lint failures. Consider either removing this decorator (and its related imports) or actually applying it where intended (and reconciling with the class docs that say metadata routing is not enabled automatically).
def ensure_metadata_routing(func: Callable) -> Callable:
"""
Decorator to ensure metadata routing is enabled before executing a function.
This decorator checks if sklearn's metadata routing is enabled and activates it
if necessary. It's particularly useful for initializing ranking models that require
metadata routing for passing additional parameters like group_id.
Parameters
----------
func : Callable
The function to be decorated (typically __init__ of a ranking model)
Returns
-------
Callable
The wrapped function with metadata routing ensured
"""
@wraps(func)
def wrapper(*args, **kwargs):
use_metadata_routing: bool = bool(skl_get_config().get("enable_metadata_routing", False))
if not use_metadata_routing:
module_logger.warning(
"Metadata routing is not enabled, enabling it now. This may cause issues in passing "
"training arguments to other sklearn objects."
)
skl_set_config(enable_metadata_routing=True) # NOSONAR
return func(*args, **kwargs)
return wrapper
src/mother/ml/utils.py:783
groupwise_topk_analysis's docstring saysuncertainty_dfmust containmean_predictionsandknowledge_uncertainty, but the implementation never reads those columns (it only copies the frame and appends new columns). This is misleading for callers and makes the contract stricter than the code actually requires.
uncertainty_df : pd.DataFrame
Output from ``predict_uncertainty`` containing ``mean_predictions`` and
``knowledge_uncertainty`` columns.
score_ensembles : np.ndarray, shape (n_samples, n_ensembles)
There was a problem hiding this comment.
Pull request overview
Copilot reviewed 7 out of 8 changed files in this pull request and generated 2 comments.
Suppressed comments (1)
src/mother/ml/models/m_catboost.py:77
ensure_metadata_routingis defined but not used anywhere in the repo, and it pulls in extra imports (wraps,skl_get_config,skl_set_config) plus a global side effect (mutating sklearn config). If metadata routing should not be auto-enabled (per the ranker docs), consider removing this unused decorator and its imports to avoid confusion.
def ensure_metadata_routing(func: Callable) -> Callable:
"""
Decorator to ensure metadata routing is enabled before executing a function.
This decorator checks if sklearn's metadata routing is enabled and activates it
There was a problem hiding this comment.
Pull request overview
Copilot reviewed 7 out of 8 changed files in this pull request and generated 1 comment.
Suppressed comments (1)
Previously missed (1) — in code that hasn't changed since the last review.
src/mother/ml/models/m_catboost.py:2139
CatboostRankerMother.suggested_params_loss()decides whether Pairwise losses are allowed usingboosting_type = suggested_params.get(..., "Plain"). However, whengrow_policy == "SymmetricTree"andtune_boosting_type=False,_CatboostHyperParams.get_hyperparameter_space()does not populateboosting_typeinsuggested_params, so this defaults to "Plain" even if the model was constructed with (fixed)boosting_type="Ordered". That can make Optuna suggest*Pairwiselosses that are incompatible with the actual fixed boosting type.
To avoid invalid trials, derive the fallback boosting_type from the model’s current parameters when it isn’t present in suggested_params.
grow_policy: Optional[str] = suggested_params.get(prefix + "grow_policy")
boosting_type: str = suggested_params.get(prefix + "boosting_type", "Plain")
can_use_pairwise: bool = grow_policy == "SymmetricTree" and boosting_type == "Plain"
| intermediate_performance_data: pd.DataFrame = val_estimator.predict_uncertainty(X.iloc[test_idx, :], **kwargs) | ||
|
|
||
| if isinstance(intermediate_performance_data, tuple): | ||
| raise TypeError( | ||
| "mother_cv requires predict_uncertainty to return a pandas DataFrame; " | ||
| "tuple-valued predict_uncertainty outputs are not supported." | ||
| ) |
There was a problem hiding this comment.
Pull request overview
Copilot reviewed 7 out of 8 changed files in this pull request and generated 1 comment.
Suppressed comments (1)
src/mother/ml/models/m_catboost.py:77
ensure_metadata_routingis defined here but is not used anywhere in the repo. Keeping an unused decorator that mutates global sklearn configuration increases maintenance burden and may confuse readers about whether metadata routing is automatically enabled for rankers.
def ensure_metadata_routing(func: Callable) -> Callable:
"""
Decorator to ensure metadata routing is enabled before executing a function.
This decorator checks if sklearn's metadata routing is enabled and activates it
There was a problem hiding this comment.
Pull request overview
Copilot reviewed 7 out of 9 changed files in this pull request and generated no new comments.
Suppressed comments (2)
Previously missed (2) — in code that hasn't changed since the last review.
src/mother/ml/utils.py:512
get_virtual_prediction()callsmodel.virtual_ensembles_predict(...)in the non-ranker branch without first validating thatmodelis actually a CatBoost model instance. If a caller passesNoneor an unexpected object, this will raise anAttributeErrorinstead of the intendedValueError(and the finalelse: raise ValueError(...)becomes effectively unreachable for such inputs). Add an earlyisinstanceguard so invalid inputs fail with a clear, consistent exception.
module_logger.info("Using catboost's builtin uncertainty prediction")
if isinstance(model, CatBoostRanker):
src/mother/ml/models/m_catboost.py:77
ensure_metadata_routing()is defined in this module but does not appear to be used anywhere (no decorators/call sites). Keeping an unused helper that mutates global sklearn config (set_config(enable_metadata_routing=True)) is confusing and risks being applied later without noticing the side effects; either apply it intentionally (with clear rationale) or remove it to avoid dead code.
def ensure_metadata_routing(func: Callable) -> Callable:
"""
Decorator to ensure metadata routing is enabled before executing a function.
This decorator checks if sklearn's metadata routing is enabled and activates it
There was a problem hiding this comment.
Pull request overview
Copilot reviewed 7 out of 9 changed files in this pull request and generated no new comments.
Suppressed comments (2)
Previously missed (1) — in code that hasn't changed since the last review.
src/mother/ml/models/m_catboost.py:80
ensure_metadata_routing()is defined here but is not referenced anywhere insrc/mother/ml/models/m_catboost.py(and there is also a separateensure_metadata_routingdecorator intest/unit/conftest.py). Leaving an unused public-looking helper in the production module increases maintenance burden and can confuse readers about whether metadata routing is auto-enabled. Consider removing it from this module (or applying it where intended) and keeping a single authoritative implementation.
def ensure_metadata_routing(func: Callable) -> Callable:
"""
Decorator to ensure metadata routing is enabled before executing a function.
This decorator checks if sklearn's metadata routing is enabled and activates it
if necessary. It's particularly useful for initializing ranking models that require
metadata routing for passing additional parameters like group_id.
src/mother/ml/utils.py:703
topk_rank_disagreement()currently returns1 - P(in_topk)(i.e., the probability an item is not in the top-k). That contradicts the docstring (“0.0 means every ensemble agrees about the item's top-k membership”) and the PR description’s “top-k membership probability”: if an item is never in the top-k across ensembles, all ensembles agree it is not in the top-k, but this function returns 1.0 (max disagreement). Please clarify the intended metric and either (a) change the implementation to a true disagreement measure (0 when always-in or always-out), or (b) rename/update the docstring/column names/tests to reflect that this is actually an exclusion probability.
For each sample, returns one minus the fraction of ensemble members that
place it in the top-k positions. A value of 0.0 means every ensemble agrees
about the item's top-k membership; values near 1 indicate disagreement.
Parameters
----------
There was a problem hiding this comment.
Pull request overview
Copilot reviewed 7 out of 9 changed files in this pull request and generated no new comments.
Suppressed comments (1)
Previously missed (1) — in code that hasn't changed since the last review.
src/mother/pipeline_utils.py:785
predict_uncertainty()is now called with**kwargsand explicitly allowed to return a tuple (which is handled immediately after), but the variable is still annotated aspd.DataFrame. With strict typing (mypy), this is inconsistent and can fail type-checking. Annotate asAny(or remove the annotation) since the code intentionally supports non-DataFrame intermediate values before normalization/validation.
intermediate_performance_data: pd.DataFrame = val_estimator.predict_uncertainty(X.iloc[test_idx, :], **kwargs)
There was a problem hiding this comment.
Pull request overview
Copilot reviewed 8 out of 10 changed files in this pull request and generated no new comments.
Suppressed comments (4)
Previously missed (1) — in code that hasn't changed since the last review.
src/mother/pipeline_utils.py:791
- The TypeError message is inaccurate:
mother_cvcan accept non-DataFrame outputs (it converts them to a DataFrame below), it just cannot accept tuple outputs. This wording may mislead estimator authors debugging theirpredict_uncertaintyimplementations.
intermediate_performance_data: pd.DataFrame = val_estimator.predict_uncertainty(X.iloc[test_idx, :], **kwargs)
if isinstance(intermediate_performance_data, tuple):
raise TypeError(
"mother_cv requires predict_uncertainty to return a pandas DataFrame; "
"tuple-valued predict_uncertainty outputs are not supported."
)
src/mother/ml/models/m_catboost.py:429
__setstate__usesstate.pop("tune_loss_function")without a default. Unpickling olderCatboostRegressorMotherobjects (saved beforetune_loss_functionexisted) will raiseKeyError, which contradicts the PR description’s “serialization compatibility/legacy fields” safeguards.
def __setstate__(self, state):
self.target_type = state.pop("target_type", "single_target")
self.tune_boosting_type = state.pop("tune_boosting_type", False)
self.tune_loss_function = state.pop("tune_loss_function")
self.model_type = state.pop("model_type", "regression")
src/mother/ml/models/m_catboost.py:1422
__setstate__usesstate.pop("tune_loss_function")without a default. Unpickling olderCatboostClassifierMotherobjects (saved beforetune_loss_functionexisted) will raiseKeyError, which contradicts the PR description’s “serialization compatibility/legacy fields” safeguards.
def __setstate__(self, state):
self.target_type = state.pop("target_type", "single_target")
self.tune_boosting_type = state.pop("tune_boosting_type", False)
self.tune_loss_function = state.pop("tune_loss_function")
self.model_type = state.pop("model_type", "classification_binary")
self.tune_tree_structure_type = state.pop("tune_tree_structure_type", True)
src/mother/ml/models/m_catboost.py:77
- The
ensure_metadata_routingdecorator is defined but not referenced anywhere in this module. Keeping unused code here makes the ranking implementation harder to follow and suggests a behavior (auto-enabling global sklearn metadata routing) that does not actually occur.
def ensure_metadata_routing(func: Callable) -> Callable:
"""
Decorator to ensure metadata routing is enabled before executing a function.
This decorator checks if sklearn's metadata routing is enabled and activates it
There was a problem hiding this comment.
Pull request overview
Copilot reviewed 8 out of 10 changed files in this pull request and generated no new comments.
Suppressed comments (1)
src/mother/ml/models/m_catboost.py:77
ensure_metadata_routingis introduced as a helper that mutates global sklearn config, but it is not referenced anywhere in this module. Keeping an unused decorator (especially one with global side effects) is confusing and increases maintenance burden; either apply it where needed or remove it and the associated imports to avoid implying metadata routing is auto-enabled.
def ensure_metadata_routing(func: Callable) -> Callable:
"""
Decorator to ensure metadata routing is enabled before executing a function.
This decorator checks if sklearn's metadata routing is enabled and activates it
There was a problem hiding this comment.
Pull request overview
Copilot reviewed 8 out of 10 changed files in this pull request and generated no new comments.
Suppressed comments (2)
Previously missed (1) — in code that hasn't changed since the last review.
src/mother/pipeline_utils.py:785
- This assignment is type-annotated as a
pd.DataFrame, but the code immediately handles tuple-valued outputs (and non-DataFrame outputs) at runtime. Keeping the explicitpd.DataFrameannotation here is misleading and can confuse static analysis (e.g., making the tuple-guard look unreachable).
intermediate_performance_data: pd.DataFrame = val_estimator.predict_uncertainty(X.iloc[test_idx, :], **kwargs)
src/mother/ml/utils.py:799
- The docstring says
topk_disagreement_probis a “fraction of ensembles disagreeing”, buttopk_rank_disagreement()returns2 * p * (1 - p)(pairwise disagreement probability between two randomly chosen ensemble members), which is a different quantity. This is misleading for users interpreting the output.
- ``topk_disagreement_prob``: fraction of ensembles disagreeing about each item's top-k membership
) Add rank-normalized predictions, virtual-ensemble uncertainty, posterior sampling, and configurable ranking loss tuning for CatBoost rankers. Add top-k rank disagreement utilities, metadata-routed uncertainty controls, input validation, TabPFN compatibility, focused regression coverage, and an expanded ranking tutorial.
Reject missing group IDs before groupwise analysis so NaN values cannot silently skip rows. Clarify that topk_disagreement_prob is a pairwise disagreement probability.
Move pre-fitted TabPFN models to the embedding transformer's configured device before extracting embeddings, rather than reusing the model's prior device.
Release PreflightAutomated check that complements semantic-release changelog generation. # Release Preflight Report
Base ref: origin/main
Commit count: 5
## Conventional commit summary
- feat: 1
- fix: 3
- revert: 1
## Commits missing issue or PR reference
- 94d27f3b fix(ml): stop GP unpickling from re-enabling disabled tuning flags
## Suggested manual follow-up
- Run the update-docs skill before release for issue and milestone reconciliation.
- Confirm docs updates for release-specific changes.
|
Restore the pre-fitted TabPFN embedding path so it matches main exactly by removing the branch-only inference_precision assignment and device transfer, along with the accompanying device test. Reverts 2ea2b4d ("fix(tabpfn): honor configured device for pre-fitted models"), whose post-fit mutations were not present in main and were not required for the pre-fitted embedding flow.
CatboostGaussianProcessRegressorMother always disables tune_boosting_type, tune_tree_structure_type, and tune_loss_function in __init__, since GP posterior sampling never supports hyperparameter tuning. However, __setstate__ restored these three flags from the pickled state dict, so unpickling an older/foreign pickle containing True values could silently re-enable tuning behavior the class is meant to permanently disable. These flags are also no longer part of the public get_params()/set_params() API, so there was no legitimate way for a caller to have set them intentionally. - __setstate__ now discards any tune_* values found in the pickled state and always hard-codes tune_boosting_type/tune_tree_structure_type/ tune_loss_function to False, matching __init__ - __getstate__ no longer serializes tune_boosting_type/ tune_tree_structure_type, since they're constant and not part of the public API - Verified against existing pickling/state-persistence/cloning tests in test_catboost_reg_uncertainty.py (13 passed) docs(tabpfn): clarify device/precision are not enforced for pre-fitted models TabPFNEmbeddingTransformer accepts a `device` constructor parameter, but the pre-fitted `model` code path never applies it — it calls get_embeddings() directly on whatever device/precision the supplied model already has. A fix attempting to honor `device` for pre-fitted models (2ea2b4d) was intentionally reverted (c7f3807) to match main, since this area has a history of fragile device/precision/autocast changes needing reverts. Document the actual behavior instead of re-touching the runtime path: - `device`: note it only applies when Mother fits a new model, not for a pre-fitted one - `model`: note its device/precision are used as-is and will not be moved, so the caller must place it on the desired device beforehand
Add CatBoost Ranking, Uncertainty, and Stability Analysis
Overview
This pull request adds a complete CatBoost ranking workflow to MotherML, together with uncertainty estimation, rank-aware analysis utilities, hyperparameter tuning support, and compatibility safeguards for cross-validation and model serialization.
The main functionality being added to
mainis the ability to train CatBoost rankers through the Mother framework, obtain either ranking scores or within-group ranks, quantify uncertainty across virtual ensembles, and analyse ranking stability at group and top-k level.What This PR Adds
CatBoost Ranking Model
CatboostRankerMotherprovides a Mother-compatible CatBoost ranker with:model_type="ranking".topfor YetiRank andmax_pairsfor PairLogit losses.group_idduring fitting and scoring.get_params(),set_params(), cloning, and serialization behavior.Score and Rank Predictions
The ranker can return either raw CatBoost scores or 1-based ranks:
use_ranks=Trueconverts scores into 1-based ranks within the supplied ranking group.This makes the same model useful both for downstream score-based ranking metrics and for users who need an explicit rank position for every item.
Ranking Uncertainty from Virtual Ensembles
CatboostRankerMother.predict_uncertainty()uses CatBoost virtual ensembles throughvirtual_ensembles_predict, via the sharedmother.ml.utils.get_virtual_prediction()helper.The implementation does not use
staged_predictsnapshots and does not calculate IQR-based uncertainty by default. Its uncertainty semantics are:predcontains raw scores by default.use_ranks=True,predcontains 1-based ranks.mean_predictionscontains the mean virtual-ensemble score or rank.knowledge_uncertaintyis the standard deviation across virtual-ensemble scores or ranks.data_uncertaintyisNonefor ranking.total_uncertaintyisNonefor ranking because this implementation reports epistemic uncertainty only.The method supports
uncertainty_for_opt=Trueto return only the uncertainty column needed by optimization workflows.Raw Ensemble Scores and Quantile Analysis
For standalone ranking analysis, the ranker supports:
return_raw=True, returning(uncertainty_df, raw_scores), whereraw_scorescontains one raw score per sample and virtual ensemble.return_quantiles=True, adding empirical score or rank quantiles such asscore_q25,score_q50, andscore_q75.The raw score matrix enables analysis beyond the aggregate uncertainty DataFrame, including how consistently items appear near the top of a ranking across virtual ensembles.
Groupwise and Top-k Stability Analysis
New ranking utilities support stability analysis at the level users normally inspect rankings:
ranker_predict_for_groups()predicts scores or ranks independently within each group.ranker_predict_uncertainty_for_groups()runs uncertainty estimation independently for each group and restores the original input order.topk_score_variance()identifies top-k items and computes their score variance across virtual ensembles.groupwise_topk_analysis()calculates top-k membership probability, top-k score variance, and consensus top-k membership for each group.These utilities make it possible to distinguish stable rankings from ambiguous rankings and to identify items whose top-k membership changes across virtual ensemble members.
The groupwise uncertainty helper intentionally rejects
return_raw=True, because raw results are tuples and require explicit per-group aggregation. Users who need raw scores should call the ranker directly for each group.Ranking Hyperparameter Tuning
The ranker integrates with MotherTuner and exposes ranking-specific search behavior:
max_pairsis applied when the effective selected loss supports it, especially PairLogit.max_pairsis configured.topthroughset_params()updates the effective YetiRank mode and top suffix when no explicit loss is supplied in the same call.:for a bare loss and;for additional parameters.This allows the ranker to be tuned without losing ranking-specific constraints or silently applying parameters to losses that do not support them.
Gaussian-Process Compatibility
The CatBoost Gaussian-process regressor remains supported and its behavior is made explicit:
False.Cross-validation and Pipeline Integration
The ranking model works with the Mother pipeline and cross-validation utilities, including group-aware workflows and rank-aware scoring.
mother_cv()forwards uncertainty keyword arguments but requires one uncertainty DataFrame per fold. It now rejects tuple-valued uncertainty results before generic DataFrame conversion. For the CatBoost ranker, one example isreturn_raw=True, which returns(uncertainty_df, raw_scores)and is intended for standalone analysis rather than cross-validation aggregation.Dependency and Code-Quality Improvements
Shared ranking helpers now live in
mother.ml.utilsinstead of importing them frommother.ml.models.m_catboost. This removes the circular dependency between the utility module and the CatBoost model module while preserving compatibility aliases for existingm_catboostusers.CatBoost ranking integration tests that train real models and run virtual-ensemble uncertainty are marked as slow so they do not unnecessarily delay the default unit-test suite.
Validation
The implementation is covered by focused tests for:
max_pairsbehavior.mother_cvforwarding and tuple-output rejection.The slow-test suite also identified stale GP assertions expecting the old public tuning flags; those assertions were updated to verify that the flags are absent from the public parameter API while remaining disabled internally.
User-Facing Contract
The ranking uncertainty contract added by this PR is therefore:
It is not a staged-predict/IQR implementation. Reviewers and downstream users should interpret
knowledge_uncertaintyas virtual-ensemble standard deviation, withtotal_uncertainty=Nonefor the ranker.