Skip to content

Sampling and KPCA resolve random_state in __init__, so get_params() leaks a RandomState and a refit of Sampling is not reproducible #750

Description

@owgreen-dev

Sampling.__init__ (pyod/models/sampling.py:108) and KPCA.__init__ (pyod/models/kpca.py:243) both do

self.random_state = check_random_state(random_state)

so the seed the user passed is replaced by a live generator at construction time.

Evidence (v3.6.6):

>>> Sampling(random_state=0).get_params()["random_state"]
RandomState(MT19937) at 0x...
>>> s = Sampling(random_state=0)
>>> a = s.fit(X).decision_scores_; b = s.fit(X).decision_scores_
>>> np.allclose(a, b)
False

The second fit draws its subset from a generator the first fit already advanced, so the same estimator on the same data gives different scores. KPCA has the same construction (kpca.py:291, used when sampling=True) and also forwards the generator object into the inner KernelPCA (kpca.py:337).

Why it matters: the scikit-learn contract is that __init__ stores arguments untouched and fit calls check_random_state. get_params() should return 0, not a generator; GridSearchCV/cross_val_score clone from get_params(), and a RandomState object there is neither reproducible nor picklable across runs.

Proposed fix: store random_state as given, call check_random_state(self.random_state) locally in fit. Same shape as #737/#738.

Found with a script that checks every detector for the sklearn parameter contract (get_params/clone/refit); drafted with Claude Code assistance and verified by hand.

Activity

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

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Projects

    No projects

      Milestone

      No milestone

      Relationships

      None yet

      Development

      No branches or pull requests

      Issue actions