pyod 3.6.2__tar.gz → 3.6.3__tar.gz

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (133) hide show
  1. {pyod-3.6.2/pyod.egg-info → pyod-3.6.3}/PKG-INFO +11 -2
  2. {pyod-3.6.2 → pyod-3.6.3}/README.rst +10 -1
  3. {pyod-3.6.2 → pyod-3.6.3}/pyod/models/deep_svdd.py +105 -38
  4. {pyod-3.6.2 → pyod-3.6.3}/pyod/utils/encoders/__init__.py +21 -2
  5. {pyod-3.6.2 → pyod-3.6.3}/pyod/version.py +1 -1
  6. {pyod-3.6.2 → pyod-3.6.3/pyod.egg-info}/PKG-INFO +11 -2
  7. {pyod-3.6.2 → pyod-3.6.3}/LICENSE +0 -0
  8. {pyod-3.6.2 → pyod-3.6.3}/MANIFEST.in +0 -0
  9. {pyod-3.6.2 → pyod-3.6.3}/pyod/__init__.py +0 -0
  10. {pyod-3.6.2 → pyod-3.6.3}/pyod/cli.py +0 -0
  11. {pyod-3.6.2 → pyod-3.6.3}/pyod/mcp_server.py +0 -0
  12. {pyod-3.6.2 → pyod-3.6.3}/pyod/models/__init__.py +0 -0
  13. {pyod-3.6.2 → pyod-3.6.3}/pyod/models/_pyg_utils.py +0 -0
  14. {pyod-3.6.2 → pyod-3.6.3}/pyod/models/_ts_utils.py +0 -0
  15. {pyod-3.6.2 → pyod-3.6.3}/pyod/models/abod.py +0 -0
  16. {pyod-3.6.2 → pyod-3.6.3}/pyod/models/ae1svm.py +0 -0
  17. {pyod-3.6.2 → pyod-3.6.3}/pyod/models/alad.py +0 -0
  18. {pyod-3.6.2 → pyod-3.6.3}/pyod/models/anogan.py +0 -0
  19. {pyod-3.6.2 → pyod-3.6.3}/pyod/models/audio_ae.py +0 -0
  20. {pyod-3.6.2 → pyod-3.6.3}/pyod/models/auto_encoder.py +0 -0
  21. {pyod-3.6.2 → pyod-3.6.3}/pyod/models/base.py +0 -0
  22. {pyod-3.6.2 → pyod-3.6.3}/pyod/models/base_dl.py +0 -0
  23. {pyod-3.6.2 → pyod-3.6.3}/pyod/models/cblof.py +0 -0
  24. {pyod-3.6.2 → pyod-3.6.3}/pyod/models/cd.py +0 -0
  25. {pyod-3.6.2 → pyod-3.6.3}/pyod/models/cof.py +0 -0
  26. {pyod-3.6.2 → pyod-3.6.3}/pyod/models/combination.py +0 -0
  27. {pyod-3.6.2 → pyod-3.6.3}/pyod/models/copod.py +0 -0
  28. {pyod-3.6.2 → pyod-3.6.3}/pyod/models/devnet.py +0 -0
  29. {pyod-3.6.2 → pyod-3.6.3}/pyod/models/dif.py +0 -0
  30. {pyod-3.6.2 → pyod-3.6.3}/pyod/models/ecod.py +0 -0
  31. {pyod-3.6.2 → pyod-3.6.3}/pyod/models/embedding.py +0 -0
  32. {pyod-3.6.2 → pyod-3.6.3}/pyod/models/feature_bagging.py +0 -0
  33. {pyod-3.6.2 → pyod-3.6.3}/pyod/models/gaal_base.py +0 -0
  34. {pyod-3.6.2 → pyod-3.6.3}/pyod/models/gmm.py +0 -0
  35. {pyod-3.6.2 → pyod-3.6.3}/pyod/models/hbos.py +0 -0
  36. {pyod-3.6.2 → pyod-3.6.3}/pyod/models/hdbscan.py +0 -0
  37. {pyod-3.6.2 → pyod-3.6.3}/pyod/models/iforest.py +0 -0
  38. {pyod-3.6.2 → pyod-3.6.3}/pyod/models/inne.py +0 -0
  39. {pyod-3.6.2 → pyod-3.6.3}/pyod/models/kde.py +0 -0
  40. {pyod-3.6.2 → pyod-3.6.3}/pyod/models/knn.py +0 -0
  41. {pyod-3.6.2 → pyod-3.6.3}/pyod/models/kpca.py +0 -0
  42. {pyod-3.6.2 → pyod-3.6.3}/pyod/models/lmdd.py +0 -0
  43. {pyod-3.6.2 → pyod-3.6.3}/pyod/models/loci.py +0 -0
  44. {pyod-3.6.2 → pyod-3.6.3}/pyod/models/loda.py +0 -0
  45. {pyod-3.6.2 → pyod-3.6.3}/pyod/models/lof.py +0 -0
  46. {pyod-3.6.2 → pyod-3.6.3}/pyod/models/lscp.py +0 -0
  47. {pyod-3.6.2 → pyod-3.6.3}/pyod/models/lunar.py +0 -0
  48. {pyod-3.6.2 → pyod-3.6.3}/pyod/models/mad.py +0 -0
  49. {pyod-3.6.2 → pyod-3.6.3}/pyod/models/mcd.py +0 -0
  50. {pyod-3.6.2 → pyod-3.6.3}/pyod/models/mo_gaal.py +0 -0
  51. {pyod-3.6.2 → pyod-3.6.3}/pyod/models/ocsvm.py +0 -0
  52. {pyod-3.6.2 → pyod-3.6.3}/pyod/models/pca.py +0 -0
  53. {pyod-3.6.2 → pyod-3.6.3}/pyod/models/pyg_anomalous.py +0 -0
  54. {pyod-3.6.2 → pyod-3.6.3}/pyod/models/pyg_anomalydae.py +0 -0
  55. {pyod-3.6.2 → pyod-3.6.3}/pyod/models/pyg_cola.py +0 -0
  56. {pyod-3.6.2 → pyod-3.6.3}/pyod/models/pyg_conad.py +0 -0
  57. {pyod-3.6.2 → pyod-3.6.3}/pyod/models/pyg_dominant.py +0 -0
  58. {pyod-3.6.2 → pyod-3.6.3}/pyod/models/pyg_guide.py +0 -0
  59. {pyod-3.6.2 → pyod-3.6.3}/pyod/models/pyg_radar.py +0 -0
  60. {pyod-3.6.2 → pyod-3.6.3}/pyod/models/pyg_scan.py +0 -0
  61. {pyod-3.6.2 → pyod-3.6.3}/pyod/models/qmcd.py +0 -0
  62. {pyod-3.6.2 → pyod-3.6.3}/pyod/models/rgraph.py +0 -0
  63. {pyod-3.6.2 → pyod-3.6.3}/pyod/models/rod.py +0 -0
  64. {pyod-3.6.2 → pyod-3.6.3}/pyod/models/sampling.py +0 -0
  65. {pyod-3.6.2 → pyod-3.6.3}/pyod/models/sklearn_base.py +0 -0
  66. {pyod-3.6.2 → pyod-3.6.3}/pyod/models/so_gaal.py +0 -0
  67. {pyod-3.6.2 → pyod-3.6.3}/pyod/models/so_gaal_new.py +0 -0
  68. {pyod-3.6.2 → pyod-3.6.3}/pyod/models/sod.py +0 -0
  69. {pyod-3.6.2 → pyod-3.6.3}/pyod/models/sos.py +0 -0
  70. {pyod-3.6.2 → pyod-3.6.3}/pyod/models/suod.py +0 -0
  71. {pyod-3.6.2 → pyod-3.6.3}/pyod/models/thresholds.py +0 -0
  72. {pyod-3.6.2 → pyod-3.6.3}/pyod/models/ts_anomaly_transformer.py +0 -0
  73. {pyod-3.6.2 → pyod-3.6.3}/pyod/models/ts_kshape.py +0 -0
  74. {pyod-3.6.2 → pyod-3.6.3}/pyod/models/ts_lstm.py +0 -0
  75. {pyod-3.6.2 → pyod-3.6.3}/pyod/models/ts_matrix_profile.py +0 -0
  76. {pyod-3.6.2 → pyod-3.6.3}/pyod/models/ts_od.py +0 -0
  77. {pyod-3.6.2 → pyod-3.6.3}/pyod/models/ts_sand.py +0 -0
  78. {pyod-3.6.2 → pyod-3.6.3}/pyod/models/ts_spectral_residual.py +0 -0
  79. {pyod-3.6.2 → pyod-3.6.3}/pyod/models/vae.py +0 -0
  80. {pyod-3.6.2 → pyod-3.6.3}/pyod/models/xgbod.py +0 -0
  81. {pyod-3.6.2 → pyod-3.6.3}/pyod/skills/__init__.py +0 -0
  82. {pyod-3.6.2 → pyod-3.6.3}/pyod/skills/od_expert/SKILL.md +0 -0
  83. {pyod-3.6.2 → pyod-3.6.3}/pyod/skills/od_expert/__init__.py +0 -0
  84. {pyod-3.6.2 → pyod-3.6.3}/pyod/skills/od_expert/references/graph.md +0 -0
  85. {pyod-3.6.2 → pyod-3.6.3}/pyod/skills/od_expert/references/pitfalls.md +0 -0
  86. {pyod-3.6.2 → pyod-3.6.3}/pyod/skills/od_expert/references/tabular.md +0 -0
  87. {pyod-3.6.2 → pyod-3.6.3}/pyod/skills/od_expert/references/text_image.md +0 -0
  88. {pyod-3.6.2 → pyod-3.6.3}/pyod/skills/od_expert/references/time_series.md +0 -0
  89. {pyod-3.6.2 → pyod-3.6.3}/pyod/skills/od_expert/references/workflow.md +0 -0
  90. {pyod-3.6.2 → pyod-3.6.3}/pyod/utils/__init__.py +0 -0
  91. {pyod-3.6.2 → pyod-3.6.3}/pyod/utils/_detector_factory.py +0 -0
  92. {pyod-3.6.2 → pyod-3.6.3}/pyod/utils/_kb_router.py +0 -0
  93. {pyod-3.6.2 → pyod-3.6.3}/pyod/utils/_llm.py +0 -0
  94. {pyod-3.6.2 → pyod-3.6.3}/pyod/utils/_nl_feedback.py +0 -0
  95. {pyod-3.6.2 → pyod-3.6.3}/pyod/utils/_quality_metrics.py +0 -0
  96. {pyod-3.6.2 → pyod-3.6.3}/pyod/utils/ad_engine.py +0 -0
  97. {pyod-3.6.2 → pyod-3.6.3}/pyod/utils/auto_model_selector.py +0 -0
  98. {pyod-3.6.2 → pyod-3.6.3}/pyod/utils/data.py +0 -0
  99. {pyod-3.6.2 → pyod-3.6.3}/pyod/utils/encoders/audio.py +0 -0
  100. {pyod-3.6.2 → pyod-3.6.3}/pyod/utils/encoders/huggingface.py +0 -0
  101. {pyod-3.6.2 → pyod-3.6.3}/pyod/utils/encoders/openai_encoder.py +0 -0
  102. {pyod-3.6.2 → pyod-3.6.3}/pyod/utils/encoders/sentence_transformer.py +0 -0
  103. {pyod-3.6.2 → pyod-3.6.3}/pyod/utils/example.py +0 -0
  104. {pyod-3.6.2 → pyod-3.6.3}/pyod/utils/investigation.py +0 -0
  105. {pyod-3.6.2 → pyod-3.6.3}/pyod/utils/knowledge/__init__.py +0 -0
  106. {pyod-3.6.2 → pyod-3.6.3}/pyod/utils/knowledge/algorithms.json +0 -0
  107. {pyod-3.6.2 → pyod-3.6.3}/pyod/utils/knowledge/benchmarks.json +0 -0
  108. {pyod-3.6.2 → pyod-3.6.3}/pyod/utils/knowledge/papers.json +0 -0
  109. {pyod-3.6.2 → pyod-3.6.3}/pyod/utils/knowledge/routing_rules.json +0 -0
  110. {pyod-3.6.2 → pyod-3.6.3}/pyod/utils/model_analysis_jsons/AE1SVM.json +0 -0
  111. {pyod-3.6.2 → pyod-3.6.3}/pyod/utils/model_analysis_jsons/ALAD.json +0 -0
  112. {pyod-3.6.2 → pyod-3.6.3}/pyod/utils/model_analysis_jsons/AnoGAN.json +0 -0
  113. {pyod-3.6.2 → pyod-3.6.3}/pyod/utils/model_analysis_jsons/AutoEncoder.json +0 -0
  114. {pyod-3.6.2 → pyod-3.6.3}/pyod/utils/model_analysis_jsons/Deep SVDD.json +0 -0
  115. {pyod-3.6.2 → pyod-3.6.3}/pyod/utils/model_analysis_jsons/DevNet.json +0 -0
  116. {pyod-3.6.2 → pyod-3.6.3}/pyod/utils/model_analysis_jsons/LUNAR.json +0 -0
  117. {pyod-3.6.2 → pyod-3.6.3}/pyod/utils/model_analysis_jsons/MO-GAAL.json +0 -0
  118. {pyod-3.6.2 → pyod-3.6.3}/pyod/utils/model_analysis_jsons/SO-GAAL.json +0 -0
  119. {pyod-3.6.2 → pyod-3.6.3}/pyod/utils/model_analysis_jsons/VAE.json +0 -0
  120. {pyod-3.6.2 → pyod-3.6.3}/pyod/utils/model_analysis_jsons/__init__.py +0 -0
  121. {pyod-3.6.2 → pyod-3.6.3}/pyod/utils/persistence.py +0 -0
  122. {pyod-3.6.2 → pyod-3.6.3}/pyod/utils/stat_models.py +0 -0
  123. {pyod-3.6.2 → pyod-3.6.3}/pyod/utils/torch_utility.py +0 -0
  124. {pyod-3.6.2 → pyod-3.6.3}/pyod/utils/utility.py +0 -0
  125. {pyod-3.6.2 → pyod-3.6.3}/pyod.egg-info/SOURCES.txt +0 -0
  126. {pyod-3.6.2 → pyod-3.6.3}/pyod.egg-info/dependency_links.txt +0 -0
  127. {pyod-3.6.2 → pyod-3.6.3}/pyod.egg-info/entry_points.txt +0 -0
  128. {pyod-3.6.2 → pyod-3.6.3}/pyod.egg-info/requires.txt +0 -0
  129. {pyod-3.6.2 → pyod-3.6.3}/pyod.egg-info/top_level.txt +0 -0
  130. {pyod-3.6.2 → pyod-3.6.3}/pyproject.toml +0 -0
  131. {pyod-3.6.2 → pyod-3.6.3}/requirements.txt +0 -0
  132. {pyod-3.6.2 → pyod-3.6.3}/setup.cfg +0 -0
  133. {pyod-3.6.2 → pyod-3.6.3}/setup.py +0 -0
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: pyod
3
- Version: 3.6.2
3
+ Version: 3.6.3
4
4
  Summary: A Python library for anomaly detection across tabular, time series, graph, text, image, and audio data. 61 detectors, benchmark-backed ADEngine orchestration, and an agentic workflow for AI agents.
5
5
  Author-email: Yue Zhao <yzhao062@gmail.com>
6
6
  License-Expression: BSD-2-Clause
@@ -72,12 +72,21 @@ Requires-Dist: librosa>=0.10; extra == "all"
72
72
  Requires-Dist: soundfile; extra == "all"
73
73
  Dynamic: license-file
74
74
 
75
+ .. image:: https://raw.githubusercontent.com/yzhao062/pyod/master/brand/pyod-icon.svg
76
+ :target: https://pyod.dev
77
+ :alt: PyOD Ecosystem
78
+ :width: 84px
79
+
75
80
  Python Outlier Detection (PyOD) 3
76
81
  ==================================
77
82
 
78
83
  **PyOD 3: Agentic Anomaly Detection At Scale**
79
84
 
80
- |badge_pypi| |badge_anaconda| |badge_docs| |badge_stars| |badge_forks| |badge_downloads| |badge_testing| |badge_coverage| |badge_maintainability| |badge_license| |badge_benchmark|
85
+ |badge_website| |badge_pypi| |badge_anaconda| |badge_docs| |badge_stars| |badge_forks| |badge_downloads| |badge_testing| |badge_coverage| |badge_maintainability| |badge_license| |badge_benchmark|
86
+
87
+ .. |badge_website| image:: https://img.shields.io/badge/website-pyod.dev-990000
88
+ :target: https://pyod.dev
89
+ :alt: Website
81
90
 
82
91
  .. |badge_pypi| image:: https://img.shields.io/pypi/v/pyod.svg?color=brightgreen
83
92
  :target: https://pypi.org/project/pyod/
@@ -1,9 +1,18 @@
1
+ .. image:: https://raw.githubusercontent.com/yzhao062/pyod/master/brand/pyod-icon.svg
2
+ :target: https://pyod.dev
3
+ :alt: PyOD Ecosystem
4
+ :width: 84px
5
+
1
6
  Python Outlier Detection (PyOD) 3
2
7
  ==================================
3
8
 
4
9
  **PyOD 3: Agentic Anomaly Detection At Scale**
5
10
 
6
- |badge_pypi| |badge_anaconda| |badge_docs| |badge_stars| |badge_forks| |badge_downloads| |badge_testing| |badge_coverage| |badge_maintainability| |badge_license| |badge_benchmark|
11
+ |badge_website| |badge_pypi| |badge_anaconda| |badge_docs| |badge_stars| |badge_forks| |badge_downloads| |badge_testing| |badge_coverage| |badge_maintainability| |badge_license| |badge_benchmark|
12
+
13
+ .. |badge_website| image:: https://img.shields.io/badge/website-pyod.dev-990000
14
+ :target: https://pyod.dev
15
+ :alt: Website
7
16
 
8
17
  .. |badge_pypi| image:: https://img.shields.io/pypi/v/pyod.svg?color=brightgreen
9
18
  :target: https://pypi.org/project/pyod/
@@ -6,6 +6,8 @@
6
6
  # License: BSD 2 clause
7
7
 
8
8
 
9
+ import copy
10
+
9
11
  import numpy as np
10
12
 
11
13
  try:
@@ -65,9 +67,11 @@ class InnerDeepSVDD(nn.Module):
65
67
  dropout_rate : float in (0., 1), optional (default=0.2)
66
68
  The dropout to be used across all layers.
67
69
 
68
- l2_regularizer : float in (0., 1), optional (default=0.1)
69
- The regularization strength of activity_regularizer
70
- applied on each layer. By default, l2 regularizer is used. See
70
+ l2_regularizer : float in (0., 1), optional (default=0.5e-6)
71
+ Weight decay passed to the optimizer (L2 regularization on the
72
+ network weights). The default matches the reference Deep SVDD
73
+ implementation; larger values push the weights toward zero and can
74
+ collapse the hypersphere.
71
75
  """
72
76
 
73
77
  def __init__(self, n_features, use_ae,
@@ -85,20 +89,14 @@ class InnerDeepSVDD(nn.Module):
85
89
  self.model = self._build_model()
86
90
 
87
91
  def _init_c(self, X_norm, eps=0.1):
88
- intermediate_output = {}
89
- hook_handle = self.model._modules.get(
90
- 'net_output').register_forward_hook(
91
- lambda module, input, output: intermediate_output.update(
92
- {'net_output': output})
93
- )
94
- output = self.model(X_norm)
95
-
96
- out = intermediate_output['net_output']
97
- hook_handle.remove()
98
-
99
- self.c = torch.mean(out, dim=0)
92
+ # The center must live in the same representation space used by the
93
+ # loss. In particular, include the activation after ``net_output``.
94
+ self.eval()
95
+ with torch.no_grad():
96
+ self.c = torch.mean(self.model(X_norm), dim=0)
100
97
  self.c[(torch.abs(self.c) < eps) & (self.c < 0)] = -eps
101
98
  self.c[(torch.abs(self.c) < eps) & (self.c > 0)] = eps
99
+ return self.c
102
100
 
103
101
  def _build_model(self):
104
102
  layers = nn.Sequential()
@@ -153,15 +151,29 @@ class DeepSVDD(BaseDetector):
153
151
  data by calculating the distance from center
154
152
  See :cite:`ruff2018deepsvdd` for details.
155
153
 
154
+ This one-class objective works best when the fitting samples are
155
+ predominantly normal; PyOD fits it unsupervised on contaminated data,
156
+ so accuracy can trail a clean one-class setup. With ``use_ae=True``
157
+ the hypersphere distance and the anomaly scores are computed from the
158
+ decoder reconstructions; the encoder's latent layer remains an
159
+ intermediate activation.
160
+
156
161
  Parameters
157
162
  ----------
158
163
  n_features: int,
159
164
  Number of features in the input data.
160
165
 
161
- c: float, optional (default='forwad_nn_pass')
162
- Deep SVDD center, the default will be calculated based on network
163
- initialization first forward pass. To get repeated results set
164
- random_state if c is set to None.
166
+ c: float, numpy array, or torch tensor, optional (default=None)
167
+ Hypersphere center, kept as configuration and never overwritten by
168
+ ``fit``. When None (the default) the center is initialized from the
169
+ first forward pass of the freshly built network; set
170
+ ``random_state`` for repeatable results. A scalar is expanded to
171
+ the network output width; a vector must match that width, which is
172
+ ``hidden_neurons[-1]`` normally and ``n_features`` when
173
+ ``use_ae=True``. The value must be finite and not all zeros: an
174
+ all-zero center is the trivial solution of the Deep SVDD objective
175
+ for a bias-free network and collapses the hypersphere. The fitted
176
+ center is exposed as ``c_``.
165
177
 
166
178
  use_ae: bool, optional (default=False)
167
179
  The AutoEncoder type of DeepSVDD it reverse neurons from hidden_neurons
@@ -184,6 +196,9 @@ class DeepSVDD(BaseDetector):
184
196
  String (name of optimizer) or optimizer instance.
185
197
  See https://keras.io/optimizers/
186
198
 
199
+ learning_rate : float, optional (default=1e-4)
200
+ Learning rate used by the optimizer.
201
+
187
202
  epochs : int, optional (default=100)
188
203
  Number of epochs to train the model.
189
204
 
@@ -193,10 +208,11 @@ class DeepSVDD(BaseDetector):
193
208
  dropout_rate : float in (0., 1), optional (default=0.2)
194
209
  The dropout to be used across all layers.
195
210
 
196
- l2_regularizer : float in (0., 1), optional (default=0.1)
197
- The regularization strength of activity_regularizer
198
- applied on each layer. By default, l2 regularizer is used. See
199
- https://keras.io/regularizers/
211
+ l2_regularizer : float in (0., 1), optional (default=0.5e-6)
212
+ Weight decay passed to the optimizer (L2 regularization on the
213
+ network weights). The default matches the reference Deep SVDD
214
+ implementation; larger values push the weights toward zero and
215
+ can collapse the hypersphere.
200
216
 
201
217
  validation_size : float in (0., 1), optional (default=0.1)
202
218
  The percentage of data to be used for validation.
@@ -234,15 +250,23 @@ class DeepSVDD(BaseDetector):
234
250
  The binary labels of the training data. 0 stands for inliers
235
251
  and 1 for outliers/anomalies. It is generated by applying
236
252
  ``threshold_`` on ``decision_scores_``.
253
+
254
+ c_ : torch tensor of shape (output_width,)
255
+ The fitted hypersphere center actually used by the training
256
+ objective and by ``decision_function``. Either the center
257
+ initialized from the data (when ``c`` is None) or a validated
258
+ copy of the user-supplied ``c``.
237
259
  """
238
260
 
239
261
  def __init__(self, n_features, c=None, use_ae=False, hidden_neurons=None,
240
262
  hidden_activation='relu',
241
263
  output_activation='sigmoid', optimizer='adam', epochs=100,
242
264
  batch_size=32,
243
- dropout_rate=0.2, l2_regularizer=0.1, validation_size=0.1,
265
+ dropout_rate=0.2, l2_regularizer=0.5e-6,
266
+ validation_size=0.1,
244
267
  preprocessing=True,
245
- verbose=1, random_state=None, contamination=0.1):
268
+ verbose=1, random_state=None, contamination=0.1,
269
+ learning_rate=1e-4):
246
270
  super(DeepSVDD, self).__init__(contamination=contamination)
247
271
 
248
272
  self.n_features = n_features
@@ -252,6 +276,7 @@ class DeepSVDD(BaseDetector):
252
276
  self.hidden_activation = hidden_activation
253
277
  self.output_activation = output_activation
254
278
  self.optimizer = optimizer
279
+ self.learning_rate = learning_rate
255
280
  self.epochs = epochs
256
281
  self.batch_size = batch_size
257
282
  self.dropout_rate = dropout_rate
@@ -315,9 +340,40 @@ class DeepSVDD(BaseDetector):
315
340
  dropout_rate=self.dropout_rate,
316
341
  l2_regularizer=self.l2_regularizer)
317
342
  X_norm = torch.tensor(X_norm, dtype=torch.float32)
343
+ # ``c`` is a constructor parameter and must stay untouched so that
344
+ # get_params/clone round-trip and a refit re-initializes the center
345
+ # for the freshly built network. The fitted center lives in ``c_``.
318
346
  if self.c is None:
319
- self.c = 0.0
320
- self.model_._init_c(X_norm)
347
+ center = self.model_._init_c(X_norm)
348
+ else:
349
+ center = torch.as_tensor(self.c, dtype=X_norm.dtype,
350
+ device=X_norm.device)
351
+
352
+ # The center must match the width of whatever ``forward()`` returns:
353
+ # the decoder reconstruction under use_ae, else the final embedding.
354
+ output_width = (self.n_features if self.use_ae
355
+ else self.hidden_neurons[-1])
356
+ if center.ndim == 0:
357
+ center = center.repeat(output_width)
358
+ elif center.ndim != 1 or center.numel() != output_width:
359
+ raise ValueError(
360
+ f'DeepSVDD: c must be a scalar or a vector of length '
361
+ f'{output_width}, got shape {tuple(center.shape)}.')
362
+ if not torch.isfinite(center).all():
363
+ raise ValueError('DeepSVDD: c must contain only finite values.')
364
+ if not torch.any(center != 0):
365
+ # An all-zero center is the trivial solution of the Deep SVDD
366
+ # objective for a bias-free network (Ruff et al., ICML 2018,
367
+ # Proposition 1): all-zero weights map every input to it, so
368
+ # training would converge to a collapsed hypersphere.
369
+ raise ValueError(
370
+ 'DeepSVDD: the hypersphere center is all zeros, which makes '
371
+ 'the trivial all-zero-weights solution optimal and collapses '
372
+ 'the hypersphere. Pass c=None to initialize the center from '
373
+ 'the data, or supply a non-zero center.')
374
+ # clone() so a mutable user-supplied array/tensor cannot alias (and
375
+ # later mutate) the fitted center.
376
+ self.c_ = center.detach().clone()
321
377
 
322
378
  # Predict on X itself and calculate the reconstruction error as
323
379
  # the outlier scores. Noted X_norm was shuffled has to recreate
@@ -334,32 +390,43 @@ class DeepSVDD(BaseDetector):
334
390
  best_loss = float('inf')
335
391
  best_model_dict = None
336
392
 
337
- optimizer = optimizer_dict[self.optimizer](self.model_.parameters(),
338
- weight_decay=self.l2_regularizer)
339
- w_d = 1e-6 * sum(
340
- [torch.linalg.norm(w) for w in self.model_.parameters()])
341
-
393
+ optimizer = optimizer_dict[self.optimizer](
394
+ self.model_.parameters(), lr=self.learning_rate,
395
+ weight_decay=self.l2_regularizer)
342
396
  for epoch in range(self.epochs):
343
397
  self.model_.train()
344
398
  epoch_loss = 0
345
399
  for batch_x, _ in dataloader:
346
400
  optimizer.zero_grad()
347
401
  outputs = self.model_(batch_x)
348
- dist = torch.sum((outputs - self.c) ** 2, dim=-1)
402
+ dist = torch.sum((outputs - self.c_) ** 2, dim=-1)
403
+ # L2 regularization term, recomputed every step so that it
404
+ # is part of the current batch's computation graph
405
+ w_d = 1e-6 * sum(
406
+ [torch.linalg.norm(w) for w in self.model_.parameters()])
349
407
  if self.use_ae:
350
408
  loss = torch.mean(dist) + w_d + torch.mean(
351
409
  torch.square(outputs - batch_x))
352
410
  else:
353
411
  loss = torch.mean(dist) + w_d
354
412
 
355
- # loss.backward()
413
+ loss.backward()
356
414
  optimizer.step()
357
415
  epoch_loss += loss.item()
358
- if epoch_loss < best_loss:
359
- best_loss = epoch_loss
360
- best_model_dict = self.model_.state_dict()
416
+ # keep the best performing model across epochs (epoch_loss is the
417
+ # accumulated loss over the whole epoch, so this must run after
418
+ # the batch loop, not inside it)
419
+ if epoch_loss < best_loss:
420
+ best_loss = epoch_loss
421
+ # state_dict() returns references to the live parameters, so
422
+ # a later optimizer.step() would mutate this snapshot in
423
+ # place and leave the final weights here instead of the best
424
+ # ones. Copy it.
425
+ best_model_dict = copy.deepcopy(self.model_.state_dict())
361
426
  print(f"Epoch {epoch + 1}/{self.epochs}, Loss: {epoch_loss}")
362
427
  self.best_model_dict = best_model_dict
428
+ if self.best_model_dict is not None:
429
+ self.model_.load_state_dict(self.best_model_dict)
363
430
 
364
431
  self.decision_scores_ = self.decision_function(X)
365
432
  self._process_decision_scores()
@@ -394,6 +461,6 @@ class DeepSVDD(BaseDetector):
394
461
  self.model_.eval()
395
462
  with torch.no_grad():
396
463
  outputs = self.model_(X_norm)
397
- dist = torch.sum((outputs - self.c) ** 2, dim=-1)
464
+ dist = torch.sum((outputs - self.c_) ** 2, dim=-1)
398
465
  anomaly_scores = dist.numpy()
399
466
  return anomaly_scores
@@ -382,8 +382,27 @@ def resolve_encoder(encoder):
382
382
  # with local_files_only=True rather than resolved as a Hub ID.
383
383
  import os
384
384
  if os.path.exists(encoder):
385
- return _create_encoder('sentence_transformer',
386
- model_name=encoder)
385
+ # Local path: prefer the SentenceTransformer backend, but fall
386
+ # back to HuggingFace when sentence-transformers isn't installed
387
+ # (e.g. a pyod[huggingface]-only environment). Both backends load
388
+ # from a local directory without any network/Hub call.
389
+ try:
390
+ return _create_encoder('sentence_transformer',
391
+ model_name=encoder)
392
+ except ImportError:
393
+ pass
394
+
395
+ try:
396
+ return _create_encoder('huggingface',
397
+ model_name=encoder,
398
+ modality='text')
399
+ except ImportError:
400
+ pass
401
+
402
+ raise ImportError(
403
+ "Loading a local encoder path requires sentence-transformers "
404
+ "or transformers. Install with: pip install pyod[embedding] "
405
+ "or pip install pyod[huggingface].")
387
406
 
388
407
  # Check registry
389
408
  if encoder in _ENCODER_REGISTRY:
@@ -20,4 +20,4 @@
20
20
  # Dev branch marker is: 'X.Y.dev' or 'X.Y.devN' where N is an integer.
21
21
  # 'X.Y.dev0' is the canonical version of 'X.Y.dev'
22
22
  #
23
- __version__ = '3.6.2' # pragma: no cover
23
+ __version__ = '3.6.3' # pragma: no cover
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: pyod
3
- Version: 3.6.2
3
+ Version: 3.6.3
4
4
  Summary: A Python library for anomaly detection across tabular, time series, graph, text, image, and audio data. 61 detectors, benchmark-backed ADEngine orchestration, and an agentic workflow for AI agents.
5
5
  Author-email: Yue Zhao <yzhao062@gmail.com>
6
6
  License-Expression: BSD-2-Clause
@@ -72,12 +72,21 @@ Requires-Dist: librosa>=0.10; extra == "all"
72
72
  Requires-Dist: soundfile; extra == "all"
73
73
  Dynamic: license-file
74
74
 
75
+ .. image:: https://raw.githubusercontent.com/yzhao062/pyod/master/brand/pyod-icon.svg
76
+ :target: https://pyod.dev
77
+ :alt: PyOD Ecosystem
78
+ :width: 84px
79
+
75
80
  Python Outlier Detection (PyOD) 3
76
81
  ==================================
77
82
 
78
83
  **PyOD 3: Agentic Anomaly Detection At Scale**
79
84
 
80
- |badge_pypi| |badge_anaconda| |badge_docs| |badge_stars| |badge_forks| |badge_downloads| |badge_testing| |badge_coverage| |badge_maintainability| |badge_license| |badge_benchmark|
85
+ |badge_website| |badge_pypi| |badge_anaconda| |badge_docs| |badge_stars| |badge_forks| |badge_downloads| |badge_testing| |badge_coverage| |badge_maintainability| |badge_license| |badge_benchmark|
86
+
87
+ .. |badge_website| image:: https://img.shields.io/badge/website-pyod.dev-990000
88
+ :target: https://pyod.dev
89
+ :alt: Website
81
90
 
82
91
  .. |badge_pypi| image:: https://img.shields.io/pypi/v/pyod.svg?color=brightgreen
83
92
  :target: https://pypi.org/project/pyod/
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes