Skip to content

Commit 54353fa

Browse files
committed
Better file location handeling for META and RANK
1 parent cb53ed3 commit 54353fa

3 files changed

Lines changed: 106 additions & 156 deletions

File tree

.pre-commit-config.yaml

Lines changed: 1 addition & 30 deletions
Original file line numberDiff line numberDiff line change
@@ -5,44 +5,15 @@ repos:
55
- id: trailing-whitespace
66
- id: end-of-file-fixer
77
- id: check-yaml
8-
- id: double-quote-string-fixer
98
- id: requirements-txt-fixer
109
- id: name-tests-test
1110
always_run: true
1211
args: [--pytest-test-first]
1312
- id: mixed-line-ending
1413
args: [--fix=lf]
1514

16-
- repo: https://github.com/DanielNoord/pydocstringformatter
17-
rev: v0.7.5
18-
hooks:
19-
- id: pydocstringformatter
20-
args: [--style=pep257, --style=numpydoc]
21-
name: Format docstrings
22-
23-
- repo: https://github.com/asottile/pyupgrade
24-
rev: v3.21.2
25-
hooks:
26-
- id: pyupgrade
27-
args: [--py38-plus]
28-
name: Upgrade code
29-
30-
- repo: https://github.com/hhatto/autopep8
31-
rev: v2.3.2
32-
hooks:
33-
- id: autopep8
34-
args: [--in-place]
35-
name: Format code style
36-
37-
- repo: https://github.com/PyCQA/isort
38-
rev: 7.0.0
39-
hooks:
40-
- id: isort
41-
args: [-m=3]
42-
name: Sort imports
43-
4415
- repo: https://github.com/charliermarsh/ruff-pre-commit
45-
rev: v0.15.1
16+
rev: v0.15.12
4617
hooks:
4718
- id: ruff
4819
args: [--exit-non-zero-on-fix, --fix, --line-length=180]

pythresh/thresholds/meta.py

Lines changed: 41 additions & 47 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,4 @@
1-
import os
2-
from os.path import dirname as up
1+
from importlib.resources import files
32

43
import joblib
54
import numpy as np
@@ -18,56 +17,53 @@
1817
class META(BaseThresholder):
1918
r"""META class for Meta-modelling thresholder.
2019
21-
Use a trained meta-model to evaluate a non-parametric means
22-
to threshold scores generated by the decision_scores where outliers
23-
are set based on the trained meta-model classifier.
24-
See :cite:`zhao2022meta` for details.
25-
26-
Parameters
27-
----------
20+
Use a trained meta-model to evaluate a non-parametric means
21+
to threshold scores generated by the decision_scores where outliers
22+
are set based on the trained meta-model classifier.
23+
See :cite:`zhao2022meta` for details.
2824
25+
Parameters
26+
----------
2927
method : {'LIN', 'GNB', 'GNBC', 'GNBM'}, optional (default='GNBM')
30-
select
31-
32-
- 'LIN': RidgeCV trained linear classifier meta-model on true labels
33-
- 'GNB': Gaussian Naive Bayes trained classifier meta-model on true labels
34-
- 'GNBC': Gaussian Naive Bayes trained classifier meta-model on best contamination
35-
- 'GNBM': Gaussian Naive Bayes multivariate trained classifier meta-model
28+
select
3629
37-
random_state : int, optional (default=1234)
38-
Random seed for the random number generators of the thresholders. Can also
39-
be set to None.
30+
- 'LIN': RidgeCV trained linear classifier meta-model on true labels
31+
- 'GNB': Gaussian Naive Bayes trained classifier meta-model on true labels
32+
- 'GNBC': Gaussian Naive Bayes trained classifier meta-model on best contamination
33+
- 'GNBM': Gaussian Naive Bayes multivariate trained classifier meta-model
4034
41-
Attributes
42-
----------
35+
random_state : int, optional (default=1234)
36+
Random seed for the random number generators of the thresholders. Can also
37+
be set to None.
4338
39+
Attributes
40+
----------
4441
thresh_ : threshold value that separates inliers from outliers
4542
46-
dscores_ : 1D array of decomposed decision scores
47-
48-
Notes
49-
-----
43+
dscores_ : 1D array of decomposed decision scores
5044
45+
Notes
46+
-----
5147
Meta-modelling is the creation of a model of models. If a dataset
52-
that contains only the explanatory variables (X), yet no response
53-
variable (y), it can still be predicted by using a meta-model. This
54-
is done by modelling datasets with known response variables that
55-
are similar to the dataset that is missing the response variable.
56-
57-
The META thresholder was trained using the ``PyOD`` outlier
58-
detection methods ``LODA, QMCD, CD, MCD, GMM, KNN, KDE, PCA, Sampling`` and ``IForest``
59-
on the AD benchmark datasets: ``ALOI, annthyroid, breastw, campaign, cardio,
60-
Cardiotocography, fault, glass, Hepatitis, Ionosphere, landsat, letter, Lymphography,
61-
magic.gamma, mammography, mnist, musk, optdigits, PageBlocks, pendigits, Pima,
62-
satellite, satimage-2, shuttle, smtp, SpamBase, speech, Stamps, thyroid, vertebral,
63-
vowels, Waveform, WBC, WDBC, Wilt, wine, WPBC, yeast`` available at
64-
`ADBench dataset <https://github.com/Minqi824/ADBench/tree/main/adbench/datasets/Classical>`_.
65-
META uses a majority vote of all the trained models to determine the
66-
inlier/outlier labels.
67-
68-
Update: the latest GNBC model was further trained on the ``backdoor, celeba, census,
69-
cover, donors, fraud, http, InternetAds,`` and ``skin`` datasets and additionally using
70-
the ``AutoEncoder, LUNAR, OCSVM, HBOS, KPCA,`` and ``DIF`` outlier detection methods.
48+
that contains only the explanatory variables (X), yet no response
49+
variable (y), it can still be predicted by using a meta-model. This
50+
is done by modelling datasets with known response variables that
51+
are similar to the dataset that is missing the response variable.
52+
53+
The META thresholder was trained using the ``PyOD`` outlier
54+
detection methods ``LODA, QMCD, CD, MCD, GMM, KNN, KDE, PCA, Sampling`` and ``IForest``
55+
on the AD benchmark datasets: ``ALOI, annthyroid, breastw, campaign, cardio,
56+
Cardiotocography, fault, glass, Hepatitis, Ionosphere, landsat, letter, Lymphography,
57+
magic.gamma, mammography, mnist, musk, optdigits, PageBlocks, pendigits, Pima,
58+
satellite, satimage-2, shuttle, smtp, SpamBase, speech, Stamps, thyroid, vertebral,
59+
vowels, Waveform, WBC, WDBC, Wilt, wine, WPBC, yeast`` available at
60+
`ADBench dataset <https://github.com/Minqi824/ADBench/tree/main/adbench/datasets/Classical>`_.
61+
META uses a majority vote of all the trained models to determine the
62+
inlier/outlier labels.
63+
64+
Update: the latest GNBC model was further trained on the ``backdoor, celeba, census,
65+
cover, donors, fraud, http, InternetAds,`` and ``skin`` datasets and additionally using
66+
the ``AutoEncoder, LUNAR, OCSVM, HBOS, KPCA,`` and ``DIF`` outlier detection methods.
7167
7268
"""
7369

@@ -98,7 +94,6 @@ def eval(self, decision):
9894
it should be considered as an outlier according to the
9995
fitted model. 0 stands for inliers and 1 for outliers.
10096
"""
101-
10297
if self._is_fitted is None:
10398
self._set_attributes(self._attrs, None)
10499

@@ -115,8 +110,8 @@ def eval(self, decision):
115110

116111
contam = []
117112
counts = len(decision)
118-
parent = up(up(__file__))
119-
model = joblib.load(os.path.join(parent, 'models', clf))
113+
model_path = files('pythresh.models').joinpath(clf)
114+
model = joblib.load(model_path)
120115

121116
# Sklearn 1.8.0 API patch
122117
for e in getattr(model, 'estimators_', {}).values():
@@ -194,7 +189,6 @@ def _patch_ridge(est):
194189
@njit(fastmath=True, parallel=True)
195190
def _wrap_around_discrepancy(data, check):
196191
"""Wrap-around Quasi-Monte Carlo discrepancy method."""
197-
198192
n = data.shape[0]
199193
d = data.shape[1]
200194
p = check.shape[0]

pythresh/utils/rank.py

Lines changed: 64 additions & 79 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,4 @@
1-
import os
2-
from os.path import dirname as up
1+
from importlib.resources import files
32

43
import numpy as np
54
import scipy.stats as stats
@@ -9,89 +8,81 @@
98
from sklearn.preprocessing import MinMaxScaler
109
from sklearn.utils import check_array
1110

12-
from pythresh.utils.rank_utility import (
13-
BREG_metric,
14-
Contam_score,
15-
GNB_score,
16-
mclain_rao_index
17-
)
11+
from pythresh.utils.rank_utility import BREG_metric, Contam_score, GNB_score, mclain_rao_index
1812

1913

20-
class RANK():
14+
class RANK:
2115
"""RANK class for ranking outlier detection and thresholding methods.
2216
23-
Use the RANK class to rank outlier detection and thresholding methods' capabilities
24-
to provide the best matthews correlation with respect to the
25-
selected threshold method
26-
27-
Parameters
28-
----------
17+
Use the RANK class to rank outlier detection and thresholding methods' capabilities
18+
to provide the best matthews correlation with respect to the
19+
selected threshold method
2920
21+
Parameters
22+
----------
3023
od_models : {list of pyod.model classes}
3124
32-
thresh : {pythresh.threshold class, float, int, list of pythresh.threshold classes, list of floats, list of ints}
33-
34-
method : {'model', 'native'}, optional (default='model')
25+
thresh : {pythresh.threshold class, float, int, list of pythresh.threshold classes, list of floats, list of ints}
3526
36-
weights : list of shape 3, optional (default=None)
37-
These weights are applied to the combined rank score. The first
38-
is for the cdf rankings, the second for the clust rankings, and
39-
the third for the mode rankings. Default applies equal weightings
40-
to all proxy-metrics. Only applies when method = 'native'.
27+
method : {'model', 'native'}, optional (default='model')
4128
42-
Attributes
43-
----------
29+
weights : list of shape 3, optional (default=None)
30+
These weights are applied to the combined rank score. The first
31+
is for the cdf rankings, the second for the clust rankings, and
32+
the third for the mode rankings. Default applies equal weightings
33+
to all proxy-metrics. Only applies when method = 'native'.
4434
35+
Attributes
36+
----------
4537
cdf_rank_ : list of tuples shape (2, n_od_models) of cdf based rankings
4638
47-
clust_rank_ : list of tuples shape (2, n_od_models) of cluster based rankings
48-
49-
consensus_rank_ : list of tuples shape (2, n_od_models) of consensus based rankings
39+
clust_rank_ : list of tuples shape (2, n_od_models) of cluster based rankings
5040
51-
Notes
52-
-----
41+
consensus_rank_ : list of tuples shape (2, n_od_models) of consensus based rankings
5342
43+
Notes
44+
-----
5445
The RANK class ranks the outlier detection methods by evaluating
55-
three distinct proxy-metrics. The first proxy-metric looks at the outlier
56-
likelihood scores by class and measures the cumulative distribution
57-
separation using the the Wasserstein distance, and the Exponential Euclidean
58-
Bregman distance. The second proxy-metric looks at the relationship between the
59-
fitted features (X) and the evaluated classes (y) using the Calinski-Harabasz scores
60-
and between the outlier likihood score and the evaluated classes using the
61-
Mclain Rao Index. The third proxy-metric evaluates the class difference for each outlier
62-
detection and thresholding method with respect to consensus based metrics of all the evaluated
63-
outlier detection class labels. This is done using the mean contamination deviation based on
64-
TruncatedSVD decomposed scores and Gaussian Naive-Bayes trained consensus score
65-
66-
Each proxy-metric is ranked separately and a final ranking is applied
67-
using all three proxy-metric to get a single ranked result of each
68-
outlier detection and thresholding method using the 'native' method. The model method uses
69-
a trained LambdaMART ranking model using all the proxy-metrics as input.
70-
71-
Please note that the data is standardized using
72-
``from pyod.utils.utility import standardizer`` during this ranking process
73-
74-
Examples
75-
--------
76-
77-
.. code:: python
78-
79-
# Import libraries
80-
from pyod.models.knn import KNN
81-
from pyod.models.iforest import IForest
82-
from pyod.models.pca import PCA
83-
from pyod.models.mcd import MCD
84-
from pyod.models.qmcd import QMCD
85-
from pythresh.thresholds.filter import FILTER
86-
from pythresh.utils.ranking import RANK
87-
88-
# Initialize models
89-
clfs = [KNN(), IForest(), PCA(), MCD(), QMCD()]
90-
thres = FILTER()
91-
92-
# Get rankings
93-
ranker = RANK(clfs, thres)
94-
rankings = ranker.eval(X)
46+
three distinct proxy-metrics. The first proxy-metric looks at the outlier
47+
likelihood scores by class and measures the cumulative distribution
48+
separation using the the Wasserstein distance, and the Exponential Euclidean
49+
Bregman distance. The second proxy-metric looks at the relationship between the
50+
fitted features (X) and the evaluated classes (y) using the Calinski-Harabasz scores
51+
and between the outlier likihood score and the evaluated classes using the
52+
Mclain Rao Index. The third proxy-metric evaluates the class difference for each outlier
53+
detection and thresholding method with respect to consensus based metrics of all the evaluated
54+
outlier detection class labels. This is done using the mean contamination deviation based on
55+
TruncatedSVD decomposed scores and Gaussian Naive-Bayes trained consensus score
56+
57+
Each proxy-metric is ranked separately and a final ranking is applied
58+
using all three proxy-metric to get a single ranked result of each
59+
outlier detection and thresholding method using the 'native' method. The model method uses
60+
a trained LambdaMART ranking model using all the proxy-metrics as input.
61+
62+
Please note that the data is standardized using
63+
``from pyod.utils.utility import standardizer`` during this ranking process
64+
65+
Examples
66+
--------
67+
68+
.. code:: python
69+
70+
# Import libraries
71+
from pyod.models.knn import KNN
72+
from pyod.models.iforest import IForest
73+
from pyod.models.pca import PCA
74+
from pyod.models.mcd import MCD
75+
from pyod.models.qmcd import QMCD
76+
from pythresh.thresholds.filter import FILTER
77+
from pythresh.utils.ranking import RANK
78+
79+
# Initialize models
80+
clfs = [KNN(), IForest(), PCA(), MCD(), QMCD()]
81+
thres = FILTER()
82+
83+
# Get rankings
84+
ranker = RANK(clfs, thres)
85+
rankings = ranker.eval(X)
9586
"""
9687

9788
def __init__(self, od_models, thresh, method='model', weights=None):
@@ -119,7 +110,6 @@ def eval(self, X):
119110
thresholder ranked from best to worst in terms of
120111
performance
121112
"""
122-
123113
X = check_array(X, ensure_2d=True)
124114
X = standardizer(X)
125115

@@ -193,9 +183,9 @@ def eval(self, X):
193183

194184
# Load trained ranking model
195185
clf = 'rank_model_XGB.json'
196-
parent = up(up(__file__))
186+
model_path = files('pythresh.models').joinpath(clf)
197187
ranker = xgb.XGBRanker()
198-
ranker.load_model(os.path.join(parent, 'models', clf))
188+
ranker.load_model(model_path)
199189

200190
# Transform data
201191
scaler = MinMaxScaler()
@@ -217,7 +207,6 @@ def eval(self, X):
217207

218208
def _cdf_metric(self, scores, labels):
219209
"""Calculate CDF based metrics."""
220-
221210
if len(np.unique(labels)) == 1:
222211
return [-1e6, -1e6]
223212

@@ -254,7 +243,6 @@ def _cdf_metric(self, scores, labels):
254243

255244
def _clust_metric(self, X, scores, labels):
256245
"""Calculate clustering based metrics."""
257-
258246
if len(np.unique(labels)) == 1:
259247
return [-1e6, -1e6]
260248

@@ -265,15 +253,13 @@ def _clust_metric(self, X, scores, labels):
265253

266254
def _consensus_metric(self, X, scores, labels, contam):
267255
"""Calculate consensus based metrics."""
268-
269256
gnb = GNB_score(X, labels)
270257
contam = Contam_score(scores, labels, contam)
271258

272259
return np.vstack([gnb, contam]).T.tolist()
273260

274261
def _equi_rank(self, data, order):
275262
"""Get equally weighted rankings from metrics."""
276-
277263
# Get indexes of best to worst for data
278264
sortings = []
279265

@@ -292,10 +278,9 @@ def _equi_rank(self, data, order):
292278

293279
def _rank_sort(self, sortings, weights):
294280
"""Sort weighted rankings."""
295-
296281
# Get unique index values for ranking
297282
unique_values = {value for ls in sortings for value in ls}
298-
scores = {value: 0 for value in unique_values}
283+
scores = dict.fromkeys(unique_values, 0)
299284

300285
# Get equally weighted rank
301286
for value in unique_values:

0 commit comments

Comments
 (0)