99from sklearn .svm import OneClassSVM
1010
1111from Orange .base import SklLearner , SklModel
12- from Orange .data import Table , Domain , DiscreteVariable , ContinuousVariable , \
13- Variable
14- from Orange .data .util import get_unique_names
12+ from Orange .data import Table , Domain , DiscreteVariable , ContinuousVariable
13+ from Orange .data .util import get_unique_names , SharedComputeValue
1514from Orange .preprocess import AdaptiveNormalize
16- from Orange .util import wrap_callback , dummy_callback
15+ from Orange .util import dummy_callback
1716
1817__all__ = ["LocalOutlierFactorLearner" , "IsolationForestLearner" ,
1918 "EllipticEnvelopeLearner" , "OneClassSVMLearner" ]
2019
2120
21+ class _CachedTransform :
22+ # to be used with SharedComputeValue
23+ def __init__ (self , model ):
24+ self .model = model
25+
26+ def __call__ (self , data ):
27+ return self .model .data_to_model_domain (data )
28+
29+
2230class _OutlierModel (SklModel ):
2331 def __init__ (self , skl_model ):
2432 super ().__init__ (skl_model )
2533 self ._cached_data = None
2634 self .outlier_var = None
35+ self .cached_transform = _CachedTransform (self )
2736
2837 def predict (self , X : np .ndarray ) -> np .ndarray :
2938 pred = self .skl_model .predict (X )
3039 pred [pred == - 1 ] = 0
3140 return pred [:, None ]
3241
42+ def new_domain (self , data : Table ) -> Domain :
43+ assert self .outlier_var is not None
44+ return Domain (data .domain .attributes , data .domain .class_vars ,
45+ data .domain .metas + (self .outlier_var ,))
46+
3347 def __call__ (self , data : Table , progress_callback : Callable = None ) \
3448 -> Table :
3549 assert isinstance (data , Table )
36- assert self .outlier_var is not None
3750
38- domain = Domain (data .domain .attributes , data .domain .class_vars ,
39- data .domain .metas + (self .outlier_var ,))
51+ domain = self .new_domain (data )
4052 if progress_callback is None :
4153 progress_callback = dummy_callback
42- progress_callback (0 , "Preprocessing..." )
43- self ._cached_data = self .data_to_model_domain (
44- data , wrap_callback (progress_callback , end = 0.1 ))
45- progress_callback (0.1 , "Predicting..." )
46- metas = np .hstack ((data .metas , self .predict (self ._cached_data .X )))
54+ progress_callback (0 , "Predicting..." )
55+ new_table = data .transform (domain )
4756 progress_callback (1 )
48- return Table . from_numpy ( domain , data . X , data . Y , metas )
57+ return new_table
4958
5059
5160class _OutlierLearner (SklLearner ):
@@ -64,27 +73,17 @@ def _fit_model(self, data: Table) -> _OutlierModel:
6473 compute_value = transformer
6574 )
6675
67- transformer .variable = variable
6876 model .outlier_var = variable
6977 return model
7078
7179
72- class _Transformer :
80+ class _Transformer ( SharedComputeValue ) :
7381 def __init__ (self , model : _OutlierModel ):
82+ super ().__init__ (model .cached_transform )
7483 self ._model = model
75- self ._variable = None
76-
77- @property
78- def variable (self ) -> Variable :
79- return self ._variable
8084
81- @variable .setter
82- def variable (self , var : Variable ):
83- self ._variable = var
84-
85- def __call__ (self , data : Table ) -> np .ndarray :
86- assert isinstance (self ._variable , Variable )
87- return self ._model (data ).get_column_view (self ._variable )[0 ]
85+ def compute (self , data : Table , shared_data : Table ) -> np .ndarray :
86+ return self ._model .predict (shared_data .X )[:, 0 ]
8887
8988
9089class OneClassSVMLearner (_OutlierLearner ):
@@ -142,13 +141,16 @@ def mahalanobis(self, observations: np.ndarray) -> np.ndarray:
142141 """
143142 return self .skl_model .mahalanobis (observations )[:, None ]
144143
145- def __call__ (self , data : Table , progress_callback : Callable = None ) \
146- -> Table :
147- pred = super ().__call__ (data , progress_callback )
148- domain = Domain (pred .domain .attributes , pred .domain .class_vars ,
149- pred .domain .metas + (self .mahal_var ,))
150- metas = np .hstack ((pred .metas , self .mahalanobis (self ._cached_data .X )))
151- return Table .from_numpy (domain , pred .X , pred .Y , metas )
144+ def new_domain (self , data : Table ) -> Domain :
145+ assert self .mahal_var is not None
146+ domain = super ().new_domain (data )
147+ return Domain (domain .attributes , domain .class_vars ,
148+ domain .metas + (self .mahal_var ,))
149+
150+
151+ class _TransformerMahalanobis (_Transformer ):
152+ def compute (self , data : Table , shared_data : Table ) -> np .ndarray :
153+ return self ._model .mahalanobis (shared_data .X )[:, 0 ]
152154
153155
154156class EllipticEnvelopeLearner (_OutlierLearner ):
@@ -166,13 +168,12 @@ def _fit_model(self, data: Table) -> EllipticEnvelopeClassifier:
166168 domain = data .domain
167169 model = super ()._fit_model (data .transform (Domain (domain .attributes )))
168170
169- transformer = _Transformer (model )
171+ transformer = _TransformerMahalanobis (model )
170172 names = [v .name for v in domain .variables + domain .metas ]
171173 variable = ContinuousVariable (
172174 get_unique_names (names , "Mahalanobis" ),
173175 compute_value = transformer
174176 )
175177
176- transformer .variable = variable
177178 model .mahal_var = variable
178179 return model
0 commit comments