#750·pyod

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

Author: owgreen-devCreated Sep 18, 2026Updated Sep 18, 2026

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

python
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):

python
>>> 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.