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.
Sampling.__init__(pyod/models/sampling.py:108) andKPCA.__init__(pyod/models/kpca.py:243) both doso the seed the user passed is replaced by a live generator at construction time.
Evidence (v3.6.6):
The second
fitdraws its subset from a generator the firstfitalready advanced, so the same estimator on the same data gives different scores.KPCAhas the same construction (kpca.py:291, used whensampling=True) and also forwards the generator object into the innerKernelPCA(kpca.py:337).Why it matters: the scikit-learn contract is that
__init__stores arguments untouched andfitcallscheck_random_state.get_params()should return0, not a generator;GridSearchCV/cross_val_scoreclone fromget_params(), and aRandomStateobject there is neither reproducible nor picklable across runs.Proposed fix: store
random_stateas given, callcheck_random_state(self.random_state)locally infit. 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.