Sampling and KPCA resolve `random_state` in `__init__`, so `get_params()` leaks a RandomState and a refit of `Sampling` is not reproducible
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)
FalseThe 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.
Source: yzhao062/pyod