Source code for pySPACE.missions.nodes.classification.svm_variants.ORMM

""" One class variants of BRMM based on separation from zero """

# the output is a prediction vector
import logging
import warnings
import numpy

from pySPACE.missions.nodes.decorators import NoOptimizationParameter, BooleanParameter, NormalParameter, \
    ChoiceParameter, LogUniformParameter
from pySPACE.resources.data_types.prediction_vector import PredictionVector

from pySPACE.missions.nodes.classification.one_class \
    import OneClassClassifierBase
from pySPACE.missions.nodes.classification.svm_variants.brmm import RMM2Node, RmmPerceptronNode

@NoOptimizationParameter("offset_factor")
@BooleanParameter("outer_boundary")
[docs]class OcRmmNode(RMM2Node, OneClassClassifierBase): """ Take zero as opposite class **References** ========= ================================================== main ========= ================================================== author Krell, M. M. and Woehrle, H. title `New one-class classifiers based on the origin separation approach <http://dx.doi.org/10.1016/j.patrec.2014.11.008>`_ journal Pattern Recognition Letters publisher Elsevier doi 10.1016/j.patrec.2014.11.008 year 2015 ========= ================================================== **Exemplary Call** .. code-block:: yaml - node : OcRmmNode parameters : complexity : 1.0 class_labels : ['Target'] range : 4 :input: FeatureVector :output: PredictionVector :Author: Mario Michael Krell :Created: 2013/08/16 """
[docs] def __init__(self, outer_boundary=False, **kwargs): if "offset_factor" in kwargs: kwargs.pop("offset_factor") RMM2Node.__init__(self, offset_factor=0, **kwargs) self.set_permanent_attributes( b=1, weight=[1, 1], one_class=True, outer_boundary=outer_boundary)
[docs] def append_weights_and_class_factors(self, label): """ Only label zero is expected and label factor one is used """ if not label == 0: self._log("Unexpected label (%s) occurred!" % str(label), level=logging.ERROR) self.bi.append(-1) if self.linear_weighting: self.ci.append(self.complexity*self.weight[0]*self.num_samples) else: self.ci.append(self.complexity*self.weight[0])
[docs] def train(self, data, label): """ Forward to one class method """ OneClassClassifierBase.train(self, data, label)
[docs] def _execute(self, data): result = RMM2Node._execute(self, data) label = result.label prediction = result.prediction + 1 if self.outer_boundary and \ prediction < (-1.0 * self.range + 1) * 0.5: prediction = (-1.0 * self.range + 1) - prediction if prediction > 0: label = self.classes[1] elif 0 >= prediction: label = self.classes[0] return PredictionVector(prediction=prediction, label=label, predictor=self)
[docs] def _inc_train(self, data, label): """ Special wrapper needed to avoid wrong or unknown label Mostly code copy from train method. """ #one vs. REST case if "REST" in self.classes and not label in self.classes: label = "REST" # one vs. one case if not self.multinomial and len(self.classes) == 2 \ and not label in self.classes: return if len(self.classes) == 0: self.classes.append(label) self._log("No positive class label given in: %s. Taking now: %s."\ %(self.__class__.__name__, label), level=logging.ERROR) if not label == self.classes[0]: if len(self.classes) == 1: self.classes.append(label) self._log("No negative class label given in: %s. Taking now: %s."\ %(self.__class__.__name__, label), level=logging.WARNING) return super(OcRmmNode, self)._inc_train(data, label)
@NoOptimizationParameter("offset_factor") @BooleanParameter("outer_boundary")
[docs]class OcRmmPerceptronNode(RmmPerceptronNode): """ Take zero as opposite class for online learning update formula **References** ========= ================================================== main ========= ================================================== author Krell, M. M. and Woehrle, H. title `New one-class classifiers based on the origin separation approach <http://dx.doi.org/10.1016/j.patrec.2014.11.008>`_ journal Pattern Recognition Letters publisher Elsevier doi 10.1016/j.patrec.2014.11.008 year 2015 ========= ================================================== **Exemplary Call** .. code-block:: yaml - node : OcRmmPerceptronNode parameters : complexity : 1.0 class_labels : ['Target'] range : 4 :input: FeatureVector :output: PredictionVector :Author: Mario Michael Krell :Created: 2014/01/02 """
[docs] def __init__(self, outer_boundary=False, **kwargs): if "offset_factor" in kwargs: kwargs.pop("offset_factor") RmmPerceptronNode.__init__(self, offset_factor=0, **kwargs) self.set_permanent_attributes( b=1, weight=[1, 1], one_class=True, outer_boundary=outer_boundary, samples="unused")
[docs] def train(self,data,label): """ Code copy from OneClassClassifierBase """ # print label, type(label), type(self.classes[0]), self.classes, label in self.classes #one vs. REST case if "REST" in self.classes and not label in self.classes: label = "REST" # one vs. one case if not self.multinomial and len(self.classes) == 2 \ and not label in self.classes: return if len(self.classes)==0: self.classes.append(label) self._log("No positive class label given in: %s. Taking now: %s."\ %(self.__class__.__name__,label), level=logging.ERROR) if not label==self.classes[0]: if len(self.classes)==1: self.classes.append(label) self._log("No negative class label given in: %s. Taking now: %s."\ %(self.__class__.__name__,label), level=logging.WARNING) return super(OcRmmPerceptronNode, self).train(data, label)
[docs] def _inc_train(self, data, label): """ Special wrapper needed to avoid wrong or unknown label Mostly code copy from train method. """ #one vs. REST case if "REST" in self.classes and not label in self.classes: label = "REST" # one vs. one case if not self.multinomial and len(self.classes) == 2 \ and not label in self.classes: return if len(self.classes) == 0: self.classes.append(label) self._log("No positive class label given in: %s. Taking now: %s."\ %(self.__class__.__name__,label), level=logging.ERROR) if not label == self.classes[0]: if len(self.classes) == 1: self.classes.append(label) self._log("No negative class label given in: %s. Taking now: %s."\ %(self.__class__.__name__,label), level=logging.WARNING) return self._train(data, label)
[docs] def _execute(self, data): result = RmmPerceptronNode._execute(self, data) label = result.label prediction = result.prediction + 1 if prediction > 0: label = self.classes[1] elif 0 >= prediction > -1.0 * self.range + 1: label = self.classes[0] elif -1.0 * self.range + 1 >= prediction and self.outer_boundary: label = self.classes[1] prediction += self.range - 1 elif -1.0 * self.range + 1 >= prediction: label = self.classes[0] return PredictionVector(prediction=prediction, label=label, predictor=self)
@NoOptimizationParameter("squared_loss")
[docs]class L2OcRmmPerceptronNode(OcRmmPerceptronNode): """ Squared loss variant of the one-class RMM Perceptron **References** ========= ================================================== main ========= ================================================== author Krell, M. M. and Woehrle, H. title `New one-class classifiers based on the origin separation approach <http://dx.doi.org/10.1016/j.patrec.2014.11.008>`_ journal Pattern Recognition Letters publisher Elsevier doi 10.1016/j.patrec.2014.11.008 year 2015 ========= ================================================== .. seealso:: :class:`OcRmmPerceptronNode` **Exemplary Call** .. code-block:: yaml - node : L2OcRmmPerceptronNode parameters : complexity : 1.0 class_labels : ['Target'] range : 4 :input: FeatureVector :output: PredictionVector :Author: Mario Michael Krell :Created: 2014/04/28 """
[docs] def __init__(self, **kwargs): if "squared_loss" in kwargs: kwargs.pop("squared_loss") OcRmmPerceptronNode.__init__(self, squared_loss=True, **kwargs)
@LogUniformParameter("radius", min_value=0, max_value=10000) @BooleanParameter("radius_opt") @ChoiceParameter("version", choices=[0, 1, 2])
[docs]class SvddPassiveAggressiveNode(OcRmmPerceptronNode): """ Support Vector Data Description like Perceptron's suggested by Crammer **References** ========= ================================================== main ========= ================================================== author Crammer, K. and Dekel, O. and Keshet, J. and Shalev-Shwartz, S. and Singer, Y. title `Online Passive-Aggressive Algorithms <http://dx.doi.org/10.1016/j.patrec.2013.09.018>`_ journal Journal of Machine Learning Research doi 10.1016/j.patrec.2013.09.018 volume 7 pages 551 - 585 year 2006 ========= ================================================== **Parameters** :radius: Maximum range parameter allowed for sphere (*optional, default: 0*) :radius_opt: Optimize the range parameter as described in *reference*. If no optimization is used, the radius parameter defines the used range. (*optional, default: False*) :version: Defines the handling of loss: * 0: hard margin with zero loss on new sample (PA0), * 1: soft margin with linear loss punishment (PA1), * 2: soft margin with squared loss punishment (PA2). For more details refer to the given *reference*. (*optional, default: 1*) **Exemplary Call** .. code-block:: yaml - node : SvddPassiveAggressive parameters : complexity : 1.0 class_labels : ['Target', 'REST'] radius : 2 radius_opt : True version : 1 :input: FeatureVector :output: PredictionVector :Author: Mario Michael Krell :Created: 2014/04/17 """
[docs] def __init__(self, radius=0, version=1, radius_opt=False, **kwargs): OcRmmPerceptronNode.__init__(self, **kwargs) self.set_permanent_attributes( radius=float(radius) if not radius_opt else 0.0, center=None, version=version, max_radius=float(radius),)
[docs] def _train(self, data, class_label): if self.feature_names is None: try: self.feature_names = data.feature_names except AttributeError as e: warnings.warn("Use a feature generator node before a " + "classification node.") raise e if self.dim is None: self.dim = data.shape[1] if class_label not in self.classes and not "REST" in self.classes: warnings.warn("Please give the expected classes to the classifier! " + "%s unknown. "%class_label + "Therefore define the variable 'class_labels' in " + "your spec file, where you use your classifier. " + "For further info look at the node documentation.") if not(len(self.classes) == 2): self.classes.append(class_label) self.set_permanent_attributes(classes=self.classes) data_array = data.view(numpy.ndarray) # individual initialization of classification vector if self.center is None: self.center = data_array vector_distance = numpy.linalg.norm(self.center - data_array) if self.max_radius <= self.radius: distance = vector_distance radius_distance = 0 else: # extend new vector with zero and center with radius distance # for distance calculation radius_distance = numpy.sqrt(self.max_radius**2 - self.radius**2) # norm(a,b)=norm(norm(a),norm(b)) distance = numpy.linalg.norm([vector_distance, radius_distance]) if distance > self.max_radius: loss = distance - self.max_radius else: loss = 0 # no change needed if loss == 0: # update_factor = 0 return # PA0 elif self.version == 0: update_factor = loss # PA1 elif self.version == 1: update_factor = numpy.min([self.complexity, loss]) # PA2 elif self.version == 2: update_factor = loss / (1.0 + 1.0 / (2.0 * self.complexity)) self.center += update_factor / distance * (data_array - self.center) # extend new vector with zero and center with radius distance # and apply same update step radius_distance += update_factor / distance * (0 - radius_distance) if radius_distance <= 0: self.radius = self.max_radius else: # recalculation of radius from distance self.radius = numpy.sqrt(self.max_radius**2 - radius_distance**2)
[docs] def _execute(self, data): data_array = data.view(numpy.ndarray) if self.center is None: self.center = data_array distance = float(numpy.linalg.norm(self.center - data_array)) prediction = distance - self.radius if prediction > 0 : label = self.classes[1] else: label = self.classes[0] return PredictionVector(prediction=prediction, label=label, predictor=self)
@NoOptimizationParameter("version")
[docs]class UnaryPA0Node(SvddPassiveAggressiveNode): """ PA0 Node for unary classification .. seealso:: :class:`SvddPassiveAggressiveNode` **Exemplary Call** .. code-block:: yaml - node : UnaryPA0 parameters : complexity : 1.0 class_labels : ['Target', 'REST'] radius : 2 radius_opt : True :input: FeatureVector :output: PredictionVector :Author: Mario Michael Krell :Created: 2014/04/17 """
[docs] def __init__(self, **kwargs): kwargs.pop("version", "") SvddPassiveAggressiveNode.__init__(self, version=0, **kwargs)
@NoOptimizationParameter("version")
[docs]class UnaryPA1Node(SvddPassiveAggressiveNode): """ PA1 Node for unary classification .. seealso:: :class:`SvddPassiveAggressiveNode` **Exemplary Call** .. code-block:: yaml - node : UnaryPA1 parameters : complexity : 1.0 class_labels : ['Target', 'REST'] radius : 2 radius_opt : True :input: FeatureVector :output: PredictionVector :Author: Mario Michael Krell :Created: 2014/04/17 """
[docs] def __init__(self, **kwargs): kwargs.pop("version", "") SvddPassiveAggressiveNode.__init__(self, version=1, **kwargs)
@NoOptimizationParameter("version")
[docs]class UnaryPA2Node(SvddPassiveAggressiveNode): """ PA2 Node for unary classification .. seealso:: :class:`SvddPassiveAggressiveNode` **Exemplary Call** .. code-block:: yaml - node : UnaryPA2 parameters : complexity : 1.0 class_labels : ['Target', 'REST'] radius : 2 radius_opt : True :input: FeatureVector :output: PredictionVector :Author: Mario Michael Krell :Created: 2014/04/17 """
[docs] def __init__(self, **kwargs): kwargs.pop("version", "") SvddPassiveAggressiveNode.__init__(self, version=2, **kwargs)