LSCP - Locally Selective Combination in Parallel outlier ensembles.

Classes

LSCPDetector

class algorithms.anomaly.lscp.LSCPDetector(Classifier)

LSCP picks the best detector for each point's own neighbourhood.

Combining detectors by averaging assumes one of them is right everywhere. LSCP does not: it defines a local region around each test point and chooses, within that region alone, whichever base detector best agrees with the ensemble's consensus. A detector that shines in a dense region and fails in a sparse one is used only where it works.
Constructor
__init__(
    self,
    detectors: Optional[List[Any]] = None,
    local_region_size: int = 30,
    n_subspaces: int = 10,
    method: str = 'average',
    contamination: float = 0.1,
    random_state: Optional[int] = None,
)

Overview

  1. Fit a pool of base detectors on the training data.
  2. Standardise their training scores and take a pseudo ground truth
the per-point maximum across the pool, which is the ensemble's best unsupervised guess at which points are anomalies.
  1. For a test point, find its local region: the training points that are
repeatedly its nearest neighbours across an ensemble of random feature subspaces.
  1. Within that region, rank the detectors by Pearson correlation with the
pseudo ground truth, and combine the winners.

Theory

Let s_c be detector c's standardised training scores and

t_i = \max_c s_{c,i}

the pseudo ground truth. For a test point with local region \mathcal{R}, detector competence is

\rho_c = \mathrm{corr}\left( s_c[\mathcal{R}],\ t[\mathcal{R}] \right)

and the final score is either the single most competent detector (method='maximum') or the mean of the top half of the pool (method='average').

The local region is deliberately built from random subspaces rather than one nearest-neighbour list in the full space. In high dimension a single full-space neighbourhood is unstable and nearly meaningless; requiring a training point to appear in many independently drawn subspaces before it joins the region makes the region far more robust.

LSCP is unsupervised throughout — the pseudo ground truth is derived from the detectors themselves, never from labels. That is also its main weakness: if the whole pool agrees on something wrong, the consensus inherits the error and local selection cannot rescue it. Diversity in the pool is what makes the method work.

Parameters

detectors
list of Classifier
Pool of unfitted base detectors, each deep-copied before fitting. Defaults to four KNNDetector instances with n_neighbors of 5, 10, 20 and 35, matching the varying-k pool of the original paper.
local_region_size
int = 30
Number of nearest neighbours drawn per subspace. Larger regions give steadier correlations and less locality.
n_subspaces
int = 10
Number of random feature subspaces used to build each local region.
method
{'average', 'maximum'} = 'average'
'average' (LSCP_A) averages the top half of the pool by local competence; 'maximum' (LSCP_M) uses the single best detector. Averaging is steadier and the better default; maximum is sharper when the pool genuinely contains one specialist per region.
contamination
float = 0.1
Expected proportion of outliers. Sets the decision threshold.
random_state
int
Seed for the subspace sampling.

Attributes

detectors_
list of Classifier
The fitted base detectors.
X_train_
np.ndarray of shape (n_samples, n_features)
Training data retained for local-region search.
train_scores_
np.ndarray of shape (n_samples, n_detectors)
Standardised training scores, higher meaning more anomalous.
pseudo_target_
np.ndarray of shape (n_samples,)
The per-point maximum across detectors.
threshold_
float
Decision-function value separating inliers from outliers.
n_features_in_
int
Number of features seen during fit.

Notes

Complexity. Fitting costs the sum of the pool's fits. Scoring is the expensive part: O(m \cdot p \cdot n \cdot d') for p subspaces of width d', plus every base detector's own scoring cost. Expect LSCP to be roughly an order of magnitude slower than its slowest member — it buys accuracy with compute, and there is no way around that.

When to use. LSCP pays off when the data has regions of genuinely different character — mixed density, several clusters with different shapes — and no single detector wins everywhere. On homogeneous data a plain average of the same pool performs just as well for a fraction of the cost, so benchmark against that baseline before adopting it. Give it a diverse pool; a pool of near-identical detectors leaves nothing to select between.

References

Zhao2019
Zhao, Y., Nasrullah, Z., Hryniewicki, M. K., & Li, Z. (2019). LSCP: Locally Selective Combination in Parallel Outlier Ensembles. SIAM International Conference on Data Mining (SDM), 585-593. :doi:`10.1137/1.9781611975673.66`
python
>>> import numpy as np
>>> from tuiml.algorithms.anomaly import LSCPDetector
>>> rng = np.random.default_rng(0)
>>> X = np.vstack([rng.normal(0, 1, (200, 4)), rng.normal(7, 1, (10, 4))])
>>> detector = LSCPDetector(contamination=0.05, random_state=0).fit(X)
>>> int((detector.predict(X)[-10:] == -1).sum())
10

A custom, deliberately diverse pool:

python
>>> from tuiml.algorithms.anomaly import ECODDetector, KNNDetector
>>> pool = [ECODDetector(), KNNDetector(n_neighbors=10),
...         KNNDetector(n_neighbors=30)]
>>> detector = LSCPDetector(detectors=pool, random_state=0).fit(X)
>>> len(detector.detectors_)
3

Methods

get_parameter_schema (cls) -> Dict[str, Dict[str, Any]]

Return JSON Schema for algorithm parameters.

get_capabilities (cls) -> List[str]

Return supported capabilities.

get_complexity (cls) -> str

Return complexity analysis.

get_references (cls) -> List[str]

Return academic citations.

fit (self, X: np.ndarray, _y: Optional[np.ndarray]=None) -> 'LSCPDetector'

Fit the base detectors and build the pseudo ground truth.

Parameters
X
np.ndarray of shape (n_samples, n_features)
Training data. Labels are ignored; the method is unsupervised.
_y
np.ndarray
Ignored, present for API consistency.
Returns
self
LSCPDetector
The fitted detector.
decision_function (self, X: np.ndarray) -> np.ndarray

Compute anomaly scores for samples.

Parameters
X
np.ndarray of shape (n_samples, n_features)
Input samples.
Returns
scores
np.ndarray of shape (n_samples,)
Anomaly scores. Lower scores indicate anomalies.
predict (self, X: np.ndarray) -> np.ndarray

Predict if samples are anomalies or not.

Parameters
X
np.ndarray of shape (n_samples, n_features)
Input samples.
Returns
predictions
np.ndarray of shape (n_samples,)
-1 for anomalies, 1 for normal instances.
score_samples (self, X: np.ndarray) -> np.ndarray

Alias for decision_function for compatibility.

Parameters
X
np.ndarray of shape (n_samples, n_features)
Input samples.
Returns
scores
np.ndarray of shape (n_samples,)
Anomaly scores. Lower scores indicate anomalies.
local_competence (self, X: np.ndarray) -> np.ndarray

Return each detector's local competence for each sample.

Parameters
X
np.ndarray of shape (n_samples, n_features)
Input samples.
Returns
competence
np.ndarray of shape (n_samples, n_detectors)
Pearson correlation with the pseudo ground truth inside each sample's local region. Detectors whose local scores are constant score 0.
__repr__ (self) -> str

Return a readable representation of the detector.