Skip to content

Fix misaligned sample_weight in group holdout split - #1586

Open
Jeremy Schoemaker (shoemoney) wants to merge 2 commits into
microsoft:mainfrom
shoemoney:fix/group-holdout-sample-weight
Open

Fix misaligned sample_weight in group holdout split#1586
Jeremy Schoemaker (shoemoney) wants to merge 2 commits into
microsoft:mainfrom
shoemoney:fix/group-holdout-sample-weight

Conversation

@shoemoney

Copy link
Copy Markdown

Why are these changes needed?

With split_type="group", eval_method="holdout", and sample_weight set, the group branch of _prepare_data (flaml/automl/task/generic_task.py:956-966 on main) splits X, y, and groups by train_idx/val_idx but leaves state.fit_kwargs["sample_weight"] at full length and never sets state.weight_val. AutoMLState.prepare_sample_train_data (flaml/automl/state.py:241) then slices the weights positionally, weight[:sample_size], so every trial trains with weights that belong to other rows: rows after the first held-out group get a neighbor's weight, and the held-out rows' weights land on training rows. Because weight_val stays None, the validation loss that drives model selection ignores sample weights entirely. All of this is silent, no error is raised, and the wrong model can be selected.

#1554 fixed the crash in this configuration (#887, #1553) by chaining the "group" branch as an elif and setting state.sample_weight_all. It did not change how the weights are split. The pattern this PR follows is the pre-existing one: the "time" branch (generic_task.py:913-955) and _train_test_split (generic_task.py:309) both split weights into state.fit_kwargs["sample_weight"] and state.weight_val. The "group" branch is the only holdout branch that does not. This PR mirrors that pattern inside the GroupShuffleSplit loop, handling both pd.Series and array weights.

The regression test encodes each row's expected weight in the row itself (column 0 is a row id, weight is 1000 + id) and spies on RandomForestClassifier.fit, so any misalignment between the rows an estimator receives and the weights it receives fails the assert; it also asserts weight_val is populated. Without the source change the test fails on the alignment assert; with it, test/automl/test_split.py passes 9/9.

Not covered here: the group holdout path still does not handle Spark dataframes (the time branch has an explicit _split_pyspark arm, the group branch never did, and GroupShuffleSplit cannot split a psDataFrame). That is a pre-existing limitation, and this PR keeps the same pandas/numpy scope as the bug.

Related issue number

Follow-up to #1554 (issues #887, #1553), which stopped the crash in this configuration but left the group branch's weights unsplit.

Checks

… vector

With split_type="group", eval_method="holdout", and sample_weight set, the
group branch of _prepare_data splits X, y, and groups by train_idx/val_idx
but leaves state.fit_kwargs["sample_weight"] at full length and never sets
state.weight_val. AutoMLState.prepare_sample_train_data then slices the
weights positionally (weight[:sample_size]), so every trial trains with
weights belonging to other rows, and the validation loss that drives model
selection ignores sample weights entirely. No error is raised.

The "time" branch (generic_task.py:913-955) and _train_test_split
(generic_task.py:309) already split the weights this way; the group branch
is the only holdout branch that does not. This mirrors that pattern inside
the gss loop: slice the weights by train_idx/val_idx into
state.fit_kwargs["sample_weight"] and state.weight_val, handling both
pd.Series and array inputs.

The regression test spies on RandomForestClassifier.fit and encodes each
row's expected weight in the row itself, so any misalignment between the
rows an estimator receives and the weights it receives fails the assert.

Copilot AI left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull request overview

Fixes sample-weight alignment for group-based holdout evaluation.

Changes:

  • Splits training and validation weights using group split indices.
  • Adds a regression test verifying estimator weight alignment.

Reviewed changes

Copilot reviewed 2 out of 2 changed files in this pull request and generated 2 comments.

File Description
flaml/automl/task/generic_task.py Splits group holdout sample weights.
test/automl/test_split.py Tests weight alignment and validation weights.

💡 Add a code-review agent skill or configure MCP servers for context-aware, tailored reviews. Learn more in the docs.

Comment on lines +967 to +975
if "sample_weight" in state.fit_kwargs:
# NOTE: _prepare_data is before kwargs is updated to fit_kwargs_by_estimator
weight = state.fit_kwargs["sample_weight"]
if isinstance(weight, pd.Series):
state.fit_kwargs["sample_weight"] = weight.iloc[train_idx]
state.weight_val = weight.iloc[val_idx]
else:
state.fit_kwargs["sample_weight"] = weight[train_idx]
state.weight_val = weight[val_idx]
Comment thread test/automl/test_split.py
X = np.column_stack([np.arange(n, dtype=float), rng.normal(size=n)])
groups = np.repeat(np.arange(20), 10)
y = (groups % 2).astype(int)
sample_weight = 1000.0 + np.arange(n, dtype=float)
Convert non-Series sample_weight to ndarray before indexing by train_idx/
val_idx in the group holdout branch, so a plain list or tuple weight does
not raise TypeError on fancy indexing, and skip splitting when weight is
None. Add a regression test that uses a pd.Series with a non-default index
to confirm the split stays positional (.iloc), mirroring the existing
ndarray-only test.
@shoemoney

Copy link
Copy Markdown
Author

Done in ddd01f1: the else-branch now converts weight to ndarray before indexing (so a list/tuple no longer raises TypeError) and skips splitting when weight is None; added a second regression test using a pd.Series with a non-default index to confirm the split stays positional.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants