"""
.. module:: CClassifierMulticlassOVA
:synopsis: One-Vs-All multiclass classifier
.. moduleauthor:: Marco Melis <marco.melis@unica.it>
"""
from secml.ml.classifiers.multiclass import CClassifierMulticlass
from secml.ml.classifiers.gradients import CClassifierGradientMixin
from secml.array import CArray
from secml.data import CDataset
from secml.parallel import parfor2
def _fit_one_ova(
tr_class_idx, multi_ova, dataset, verbose):
"""Fit a OVA classifier.
Parameters
----------
tr_class_idx : int
Index of the label against which the classifier should be trained.
multi_ova : CClassifierMulticlassOVA
Instance of the multiclass OVA classifier.
dataset : CDataset
Training set. Must be a :class:`.CDataset` instance with
patterns data and corresponding labels.
verbose : int
Verbosity level of the logger.
"""
# Resetting verbosity level. This is needed as objects
# change id when passed to subprocesses and our logging
# level is stored per-object looking to id
multi_ova.verbose = verbose
multi_ova.logger.info(
"Training against class: {:}".format(tr_class_idx))
# Binarizing dataset
train_ds = multi_ova.binarize_dataset(tr_class_idx, dataset)
# Extracting the internal one-vs-all classifier
classifier_instance = multi_ova._binary_classifiers[tr_class_idx]
# Setting verbosity level
classifier_instance.verbose = multi_ova.verbose
# Training one-vs-all classifier
classifier_instance.fit(train_ds)
return classifier_instance
[docs]class CClassifierMulticlassOVA(CClassifierMulticlass,
CClassifierGradientMixin):
"""OVA (One-Vs-All) Multiclass Classifier.
Parameters
----------
classifier : unbound class
Unbound (not initialized) CClassifier subclass.
kwargs : any
Any other construction parameter for each OVA classifier.
Attributes
----------
class_type : 'ova'
"""
__class_type = 'ova'
def __init__(self, classifier, preprocess=None, **clf_params):
super(CClassifierMulticlassOVA, self).__init__(
classifier=classifier,
preprocess=preprocess,
**clf_params
)
def _fit(self, dataset, n_jobs=1):
"""Trains the classifier.
A One-Vs-All classifier is trained for each dataset class.
Parameters
----------
dataset : CDataset
Training set. Must be a :class:`.CDataset` instance with
patterns data and corresponding labels.
n_jobs : int
Number of parallel workers to use for training the classifier.
Default 1. Cannot be higher than processor's number of cores.
Returns
-------
trained_cls : CClassifierMulticlassOVA
Instance of the classifier trained using input dataset.
"""
# Preparing the binary classifiers
self.prepare(dataset.num_classes)
# Fit a one-vs-all classifier for each class
# Use the specified number of workers
self._binary_classifiers = parfor2(_fit_one_ova,
self.classes.size,
n_jobs, self, dataset,
self.verbose)
return self
[docs] @staticmethod
def binarize_dataset(class_idx, dataset):
"""Returns the dataset needed by the class_idx binary classifier.
Parameters
----------
class_idx : int
Index of the target class.
dataset : CDataset
Dataset to binarize.
Returns
-------
bin_dataset : CDataset
Binarized dataset.
"""
return CDataset(
dataset.X, dataset.get_labels_ovr(dataset.classes[class_idx]),
header=dataset.header)
def _forward(self, x):
"""Computes the decision function for each pattern in x.
For One-Vs-All (OVA) multiclass scheme,
this is the output of the `label`^th classifier.
Parameters
----------
x : CArray
Array with new patterns to classify, 2-Dimensional of shape
(n_patterns, n_features).
y : int or None, optional
The label of the class wrt the function should be calculated.
If None, return the output for all classes.
Returns
-------
score : CArray
Value of the decision function for each test pattern.
Dense flat array of shape (n_samples,) if y is not None,
otherwise a (n_samples, n_classes) array.
"""
# Getting predicted scores for classifier associated with y
scores = CArray.ones(shape=(x.shape[0], self.n_classes))
for i in range(self.n_classes): # TODO parfor
scores[:, i] = self._binary_classifiers[i].forward(x)[:, 1]
return scores
def _backward(self, w):
"""Implement gradient of decision function wrt x."""
if w is None:
w = CArray.ones(shape=(self.n_classes,))
# this is where we'll accumulate grads
grad = CArray.zeros(
shape=self._cached_x.shape, sparse=self._cached_x.issparse)
# loop only over non-zero elements in w, to save computations
for c in w.nnz_indices[1]:
grad_c = self._binary_classifiers[c].grad_f_x(self._cached_x, y=1)
grad += w[c] * grad_c
return grad