v<3.6.3>, <08/01/2026> -- DeepSVDD correctness fixes, contributor bug fixes, and project infrastructure. DeepSVDD hypersphere-collapse fix (builds on #704 by Devashish Moghe; closes #606 and #641): the training loop had `loss.backward()` commented out, so no gradient ever reached the weights and scores came from the randomly initialized network. #704 restored the backward pass, recomputing the L2 term `w_d` inside the batch loop so each step gets a fresh graph, moving the best-model bookkeeping out of the batch loop, and deep-copying `state_dict()` (which otherwise aliases the live parameters). Enabling training then exposed three pre-existing defects that the commented-out line had been masking. First, `fit()` assigned the estimator's center `self.c = 0.0` and let `_init_c()` write the computed center to the inner module, which the loss and scoring never read; for a bias-free ReLU network the all-zero weights map every input to 0, so `c = 0` is exactly the trivial solution of Ruff et al., ICML 2018, Proposition 1, and training converged to a collapsed hypersphere. On a 17-dataset ODDS benchmark (3 seeds, 30 epochs) the collapsed configuration returned a single distinct score on 10 of 17 datasets -- a detector that is silently useless while still meeting a ROC floor, because ordering can be induced by floating-point noise as small as 1e-24. The center is now taken from `_init_c()`, detached, and stored where the objective reads it. Second, the optimizer was constructed without a learning rate (Adam's default 1e-3) and passed `l2_regularizer=0.1` as `weight_decay`, five orders of magnitude above the 5e-7 used by the reference implementation; that decay drives the weights toward zero and compounds the collapse. `l2_regularizer` now defaults to 5e-7 and a new `learning_rate` parameter defaults to 1e-4 (both match the reference implementation; the `l2_regularizer` default change is API-visible). Third, `best_model_dict` was stored but never loaded, so scoring used final-epoch weights; the snapshot is now applied before scoring. Fourth, `fit()` wrote the computed center into the constructor parameter `c`, which leaked fitted state through `get_params()`, made `clone()` start pre-seeded, and made a refit train a newly built network against the previous fit's center; the fitted center now lives in `c_`, `c` stays configuration, and a user-supplied center is validated before training: a scalar is expanded to the network output width, a vector must match that width (`hidden_neurons[-1]`, or `n_features` when `use_ae=True`), non-finite values are rejected, an all-zero center is rejected with a `ValueError` because it is exactly the Proposition 1 trivial-solution condition, and the tensor is copied so a later mutation of the caller's array cannot change the fitted center. Measured effect on the 17-dataset benchmark: mean ROC AUC rises from 0.601 (collapsed) to 0.748, with score collapse eliminated on all 17 datasets. Calibration note: 0.748 is level with the 0.746 obtained by the untrained network, so these changes restore DeepSVDD to its baseline rather than improving on it. Deep SVDD assumes a clean one-class training set, while PyOD fits it on contaminated data unsupervised; in a controlled comparison, training on genuinely normal samples only reaches 0.879 and trimming the most anomalous 10% before training reaches 0.800, so a substantial part of the gap tracks the mismatch between the method's one-class assumption and unsupervised use on contaminated data. These two diagnostics do not rule out further defects or tuning gains. Tests: `test_scores_are_not_constant` asserts score diversity on both the standard and autoencoder paths and `test_center_is_valid` asserts the center is a detached, finite, non-scalar tensor; both fail on the collapsed code. `test_fit_changes_parameters` pins the original backward-pass defect only, and does not detect collapse. The LOCI and generate_data contributor PRs remain under review and are not included here. Contributor PRs: local encoder paths now fall back to the HuggingFace backend when sentence-transformers is absent (#701, Sunny Guntuka; follow-up to #696), so a `pyod[huggingface]`-only install no longer raises `ImportError` before the existing fallback can run; save/load round-trip coverage now spans 23 detectors (#708, Jayesh Suryavanshi; closes #269), up from 2, while 21 of them also gain clone assertions for unfitted equivalence and refit score reproduction, strengthening per-detector `test_model_clone` methods that previously only checked that `clone()` did not raise. Test fix (#710): `test_resolve_st_instance_no_download` built its no-download model with `SentenceTransformer(modules=[])`, which sentence-transformers 5.6 rejects; it now passes a single trivial `torch.nn.Identity` module instead, which keeps the test network-free without depending on `sentence_transformers.models` keyword arguments that have been renamed across 5.x. Project infrastructure (#705): pyod.dev website badge in the README (using an absolute image URL so it renders on the PyPI project page), brand assets under `brand/` plus Sphinx `docs/_static/` copies, and a `SECURITY.md` vulnerability-reporting policy.
0 commit comments