LSCP - Locally Selective Combination in Parallel outlier ensembles.
Classes
LSCP picks the best detector for each point's own neighbourhood.
__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
- Fit a pool of base detectors on the training data.
- Standardise their training scores and take a pseudo ground truth —
- For a test point, find its local region: the training points that are
- Within that region, rank the detectors by Pearson correlation with the
Theory
Let s_c be detector c's standardised training scores and
the pseudo ground truth. For a test point with local region \mathcal{R}, detector competence is
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
KNNDetector instances with n_neighbors of 5, 10, 20 and 35, matching the varying-k pool of the original paper.
local_region_size
n_subspaces
method
'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
random_state
Attributes
detectors_
X_train_
train_scores_
pseudo_target_
threshold_
n_features_in_
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
See Also
>>> 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:
>>> 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
fit
(self, X: np.ndarray, _y: Optional[np.ndarray]=None) -> 'LSCPDetector'
fit
(self, X: np.ndarray, _y: Optional[np.ndarray]=None) -> 'LSCPDetector'
Fit the base detectors and build the pseudo ground truth.
Parameters
X
_y
Returns
self
local_competence
(self, X: np.ndarray) -> np.ndarray
local_competence
(self, X: np.ndarray) -> np.ndarray
Return each detector's local competence for each sample.
Parameters
X
Returns
competence