source: orange/Orange/classification/svm/__init__.py @ 10584:edc3a37bc3c3

Revision 10584:edc3a37bc3c3, 31.4 KB checked in by Ales Erjavec <ales.erjavec@…>, 2 years ago (diff)

Fixed get_linear_svm_weights function with respect to the internal libsvm label ordering.

Line 
1import math
2
3from collections import defaultdict
4
5import Orange.core
6import Orange.data
7import Orange.misc
8import Orange.feature
9
10import kernels
11import warnings
12
13from Orange.core import SVMLearner as _SVMLearner
14from Orange.core import SVMLearnerSparse as _SVMLearnerSparse
15from Orange.core import LinearClassifier, \
16                        LinearLearner, \
17                        SVMClassifier, \
18                        SVMClassifierSparse
19
20from Orange.data import preprocess
21
22from Orange import feature as variable
23
24from Orange.utils import _orange__new__
25
26def max_nu(data):
27    """
28    Return the maximum nu parameter for the given data table for
29    Nu_SVC learning.
30   
31    :param data: Data with discrete class variable
32    :type data: Orange.data.Table
33   
34    """
35    nu = 1.0
36    dist = list(Orange.core.Distribution(data.domain.classVar, data))
37    def pairs(seq):
38        for i, n1 in enumerate(seq):
39            for n2 in seq[i + 1:]:
40                yield n1, n2
41    return min([2.0 * min(n1, n2) / (n1 + n2) for n1, n2 in pairs(dist) \
42                if n1 != 0 and n2 != 0] + [nu])
43
44maxNu = max_nu
45
46class SVMLearner(_SVMLearner):
47    """
48    :param svm_type: the SVM type
49    :type svm_type: SVMLearner.SVMType
50    :param kernel_type: the kernel type
51    :type kernel_type: SVMLearner.Kernel
52    :param degree: kernel parameter (only for ``Polynomial``)
53    :type degree: int
54    :param gamma: kernel parameter; if 0, it is set to 1.0/#features (for ``Polynomial``, ``RBF`` and ``Sigmoid``)
55    :type gamma: float
56    :param coef0: kernel parameter (for ``Polynomial`` and ``Sigmoid``)
57    :type coef0: int
58    :param kernel_func: kernel function if ``kernel_type`` is
59        ``kernels.Custom``
60    :type kernel_func: callable object
61    :param C: C parameter (for ``C_SVC``, ``Epsilon_SVR`` and ``Nu_SVR``)
62    :type C: float
63    :param nu: Nu parameter (for ``Nu_SVC``, ``Nu_SVR`` and ``OneClass``)
64    :type nu: float
65    :param p: epsilon parameter (for ``Epsilon_SVR``)
66    :type p: float
67    :param cache_size: cache memory size in MB
68    :type cache_size: int
69    :param eps: tolerance of termination criterion
70    :type eps: float
71    :param probability: build a probability model
72    :type probability: bool
73    :param shrinking: use shrinking heuristics
74    :type shrinking: bool
75    :param weight: a list of class weights
76    :type weight: list
77
78    Example:
79   
80        >>> import Orange
81        >>> from Orange.classification import svm
82        >>> from Orange.evaluation import testing, scoring
83        >>> data = Orange.data.Table("vehicle.tab")
84        >>> learner = svm.SVMLearner()
85        >>> results = testing.cross_validation([learner], data, folds=5)
86        >>> print scoring.CA(results)[0]
87        0.789613644274
88   
89    """
90    __new__ = _orange__new__(_SVMLearner)
91
92    C_SVC = _SVMLearner.C_SVC
93    Nu_SVC = _SVMLearner.Nu_SVC
94    OneClass = _SVMLearner.OneClass
95    Nu_SVR = _SVMLearner.Nu_SVR
96    Epsilon_SVR = _SVMLearner.Epsilon_SVR
97
98    @Orange.utils.deprecated_keywords({"kernelFunc": "kernel_func"})
99    def __init__(self, svm_type=Nu_SVC, kernel_type=kernels.RBF,
100                 kernel_func=None, C=1.0, nu=0.5, p=0.1, gamma=0.0, degree=3,
101                 coef0=0, shrinking=True, probability=True, verbose=False,
102                 cache_size=200, eps=0.001, normalization=True,
103                 weight=[], **kwargs):
104        self.svm_type = svm_type
105        self.kernel_type = kernel_type
106        self.kernel_func = kernel_func
107        self.C = C
108        self.nu = nu
109        self.p = p
110        self.gamma = gamma
111        self.degree = degree
112        self.coef0 = coef0
113        self.shrinking = shrinking
114        self.probability = probability
115        self.verbose = verbose
116        self.cache_size = cache_size
117        self.eps = eps
118        self.normalization = normalization
119        for key, val in kwargs.items():
120            setattr(self, key, val)
121        self.learner = Orange.core.SVMLearner(**kwargs)
122        self.weight = weight
123
124    max_nu = staticmethod(max_nu)
125
126    def __call__(self, data, weight=0):
127        """Construct a SVM classifier
128       
129        :param table: data with continuous features
130        :type table: Orange.data.Table
131       
132        :param weight: ignored (required due to base class signature);
133        """
134
135        examples = Orange.core.Preprocessor_dropMissingClasses(data)
136        class_var = examples.domain.class_var
137        if len(examples) == 0:
138            raise ValueError("Example table is without any defined classes")
139
140        # Fix the svm_type parameter if we have a class_var/svm_type mismatch
141        if self.svm_type in [0, 1] and \
142            isinstance(class_var, Orange.feature.Continuous):
143            self.svm_type += 3
144            #raise AttributeError, "Cannot learn a discrete classifier from non descrete class data. Use EPSILON_SVR or NU_SVR for regression"
145        if self.svm_type in [3, 4] and \
146            isinstance(class_var, Orange.feature.Discrete):
147            self.svm_type -= 3
148            #raise AttributeError, "Cannot do regression on descrete class data. Use C_SVC or NU_SVC for classification"
149        if self.kernel_type == kernels.Custom and not self.kernel_func:
150            raise ValueError("Custom kernel function not supplied")
151
152        import warnings
153
154        nu = self.nu
155        if self.svm_type == SVMLearner.Nu_SVC: #is nu feasible
156            max_nu = self.max_nu(examples)
157            if self.nu > max_nu:
158                if getattr(self, "verbose", 0):
159                    warnings.warn("Specified nu %.3f is infeasible. \
160                    Setting nu to %.3f" % (self.nu, max_nu))
161                nu = max(max_nu - 1e-7, 0.0)
162
163        for name in ["svm_type", "kernel_type", "kernel_func", "C", "nu", "p",
164                     "gamma", "degree", "coef0", "shrinking", "probability",
165                     "verbose", "cache_size", "eps"]:
166            setattr(self.learner, name, getattr(self, name))
167        self.learner.nu = nu
168        self.learner.set_weights(self.weight)
169
170        if self.svm_type == SVMLearner.OneClass and self.probability:
171            self.learner.probability = False
172            warnings.warn("One-class SVM probability output not supported yet.")
173        return self.learn_classifier(examples)
174
175    def learn_classifier(self, data):
176        if self.normalization:
177            data = self._normalize(data)
178            svm = self.learner(data)
179            return SVMClassifierWrapper(svm)
180        return self.learner(data)
181
182    @Orange.utils.deprecated_keywords({"progressCallback": "progress_callback"})
183    def tune_parameters(self, data, parameters=None, folds=5, verbose=0,
184                       progress_callback=None):
185        """Tune the ``parameters`` on the given ``data`` using
186        internal cross validation.
187       
188        :param data: data for parameter tuning
189        :type data: Orange.data.Table
190        :param parameters: names of parameters to tune
191            (default: ["nu", "C", "gamma"])
192        :type parameters: list of strings
193        :param folds: number of folds for internal cross validation
194        :type folds: int
195        :param verbose: set verbose output
196        :type verbose: bool
197        :param progress_callback: callback function for reporting progress
198        :type progress_callback: callback function
199           
200        Here is example of tuning the `gamma` parameter using
201        3-fold cross validation. ::
202
203            svm = Orange.classification.svm.SVMLearner()
204            svm.tune_parameters(table, parameters=["gamma"], folds=3)
205                   
206        """
207
208        import orngWrap
209
210        if parameters is None:
211            parameters = ["nu", "C", "gamma"]
212
213        searchParams = []
214        normalization = self.normalization
215        if normalization:
216            data = self._normalize(data)
217            self.normalization = False
218        if self.svm_type in [SVMLearner.Nu_SVC, SVMLearner.Nu_SVR] \
219                    and "nu" in parameters:
220            numOfNuValues = 9
221            if isinstance(data.domain.class_var, variable.Discrete):
222                max_nu = max(self.max_nu(data) - 1e-7, 0.0)
223            else:
224                max_nu = 1.0
225            searchParams.append(("nu", [i / 10.0 for i in range(1, 9) if \
226                                        i / 10.0 < max_nu] + [max_nu]))
227        elif "C" in parameters:
228            searchParams.append(("C", [2 ** a for a in  range(-5, 15, 2)]))
229        if self.kernel_type == 2 and "gamma" in parameters:
230            searchParams.append(("gamma", [2 ** a for a in range(-5, 5, 2)] + [0]))
231        tunedLearner = orngWrap.TuneMParameters(object=self,
232                            parameters=searchParams,
233                            folds=folds,
234                            returnWhat=orngWrap.TuneMParameters.returnLearner,
235                            progressCallback=progress_callback
236                            if progress_callback else lambda i:None)
237        tunedLearner(data, verbose=verbose)
238        if normalization:
239            self.normalization = normalization
240
241    def _normalize(self, data):
242        dc = preprocess.DomainContinuizer()
243        dc.class_treatment = preprocess.DomainContinuizer.Ignore
244        dc.continuous_treatment = preprocess.DomainContinuizer.NormalizeBySpan
245        dc.multinomial_treatment = preprocess.DomainContinuizer.NValues
246        newdomain = dc(data)
247        return data.translate(newdomain)
248
249SVMLearner = Orange.utils.deprecated_members({
250    "learnClassifier": "learn_classifier",
251    "tuneParameters": "tune_parameters",
252    "kernelFunc" : "kernel_func",
253    },
254    wrap_methods=["__init__", "tune_parameters"])(SVMLearner)
255
256class SVMClassifierWrapper(Orange.core.SVMClassifier):
257    def __new__(cls, wrapped):
258        return Orange.core.SVMClassifier.__new__(cls, name=wrapped.name)
259
260    def __init__(self, wrapped):
261        self.wrapped = wrapped
262        for name, val in wrapped.__dict__.items():
263            self.__dict__[name] = val
264
265    def __call__(self, example, what=Orange.core.GetValue):
266        example = Orange.data.Instance(self.wrapped.domain, example)
267        return self.wrapped(example, what)
268
269    def class_distribution(self, example):
270        example = Orange.data.Instance(self.wrapped.domain, example)
271        return self.wrapped.class_distribution(example)
272
273    def get_decision_values(self, example):
274        example = Orange.data.Instance(self.wrapped.domain, example)
275        dec_values = self.wrapped.get_decision_values(example)
276        # decision values are ordred by libsvm internal class values
277        # i.e. the order of labels in the data
278        map = self._get_libsvm_labels_map()
279        n_class = len(self.class_var.values)
280        new_values = []
281        for i in range(n_class - 1):
282            for j in range(i + 1, n_class):
283                # Internal indices
284                ni, nj = map.index(i), map.index(j)
285                mult = 1.0
286                if ni > nj:
287                    ni, nj = nj, ni
288                    # Multiply by -1 if we switch the order of the 1vs1
289                    # classifier.
290                    mult = -1.0
291                val_index = n_class * (n_class - 1) / 2 - (n_class - ni - 1) * (n_class - ni - 2) / 2 - (n_class - nj)
292                new_values.append(mult * dec_values[val_index])
293        return Orange.core.FloatList(new_values)
294       
295    def get_model(self):
296        return self.wrapped.get_model()
297   
298    def _get_libsvm_labels_map(self):
299        """Get the libsvm label mapping from the model string
300        """
301        labels = [line for line in self.get_model().splitlines() \
302                  if line.startswith("label")]
303        labels = labels[0].split(" ")[1:] if labels else ["0"]
304        return [int(label) for label in labels]
305
306    def __reduce__(self):
307        return SVMClassifierWrapper, (self.wrapped,), dict([(name, val) \
308            for name, val in self.__dict__.items() \
309            if name not in self.wrapped.__dict__])
310       
311    def get_binary_classifier(self, c1, c2):
312        """Return a binary classifier for classes `c1` and `c2`.
313        """
314        import numpy as np
315        if self.svm_type not in [SVMLearner.C_SVC, SVMLearner.Nu_SVC]:
316            raise TypeError("SVM classification model expected.")
317       
318        c1 = int(self.class_var(c1))
319        c2 = int(self.class_var(c2))
320       
321        libsvm_label = [line for line in self.get_model().splitlines() \
322                        if line.startswith("label")]
323       
324        n_class = len(self.class_var.values)
325       
326        if c1 == c2:
327            raise ValueError("Different classes expected.")
328       
329        bin_class_var = Orange.feature.Discrete("%s vs %s" % \
330                        (self.class_var.values[c1], self.class_var.values[c2]),
331                        values=["0", "1"])
332       
333        # Map the libsvm labels
334        labels_map = self._get_libsvm_labels_map()
335        c1 = labels_map.index(c1)
336        c2 = labels_map.index(c2)
337       
338        mult = 1.0
339        if c1 > c2:
340            c1, c2 = c2, c1
341            mult = -1.0
342       
343        # Index of the 1vs1 binary classifier
344        classifier_i = n_class * (n_class - 1) / 2 - (n_class - c1 - 1) * (n_class - c1 - 2) / 2 - (n_class - c2)
345       
346        # Indices for classes in the coef structure.
347        class_indices = np.cumsum([0] + list(self.n_SV), dtype=int)
348        c1_range = range(class_indices[c1], class_indices[c1 + 1])
349        c2_range = range(class_indices[c2], class_indices[c2 + 1])
350       
351        coef_array = np.array(self.coef)
352        coef1 = mult * coef_array[c2 - 1, c1_range]
353        coef2 = mult * coef_array[c1, c2_range]
354       
355        # Support vectors for the binary classifier
356        sv1 = [self.support_vectors[i] for i in c1_range]
357        sv2 = [self.support_vectors[i] for i in c2_range]
358       
359        # Rho for the classifier
360        rho = mult * self.rho[classifier_i]
361       
362        # Filter non zero support vectors
363        nonzero1 = np.abs(coef1) > 0.0
364        nonzero2 = np.abs(coef2) > 0.0
365       
366        coef1 = coef1[nonzero1]
367        coef2 = coef2[nonzero2]
368       
369        sv1 = [sv for sv, nz in zip(sv1, nonzero1) if nz]
370        sv2 = [sv for sv, nz in zip(sv2, nonzero2) if nz]
371       
372        sv_indices1 = [i for i, nz in zip(c1_range, nonzero1) if nz]
373        sv_indices2 = [i for i, nz in zip(c2_range, nonzero2) if nz]
374       
375        model = self._binary_libsvm_model(bin_class_var, [coef1, coef2],
376                                          [rho], sv_indices1 + sv_indices2)
377       
378        all_sv = Orange.data.Table(sv1 + sv2)
379        if self.kernel_type == kernels.Custom:
380            classifier = SVMClassifier(bin_class_var, self.examples,
381                                       all_sv, model, self.kernel_func)
382        else:
383            classifier = SVMClassifier(bin_class_var, self.examples,
384                                       all_sv, model)
385           
386        return SVMClassifierWrapper(classifier)
387   
388    def _binary_libsvm_model(self, class_var, coefs, rho, sv_indices):
389        """Return a libsvm formated model string for binary subclassifier
390        """
391        import itertools
392       
393        model = []
394       
395        # Take the model up to nr_classes
396        for line in self.get_model().splitlines():
397            if line.startswith("nr_class"):
398                break
399            else:
400                model.append(line.rstrip())
401       
402        model.append("nr_class %i" % len(class_var.values))
403        model.append("total_sv %i" % len(sv_indices))
404        model.append("rho " + " ".join(str(r) for r in rho))
405        model.append("label " + " ".join(str(i) for i in range(len(class_var.values))))
406        # No probA and probB
407       
408        model.append("nr_sv " + " ".join(str(len(c)) for c in coefs))
409        model.append("SV")
410       
411        def instance_to_svm(inst):
412            values = [(i, float(inst[v])) \
413                      for i, v in enumerate(inst.domain.attributes) \
414                      if not inst[v].is_special() and float(inst[v]) != 0.0]
415            return " ".join("%i:%f" % (i + 1, v) for i, v in values)
416       
417        if self.kernel_type == kernels.Custom:
418            SV = self.get_model().split("SV\n", 1)[1]
419            # Get the sv indices (the last entry in the SV entrys)
420            indices = [int(s.split(":")[-1]) for s in SV.splitlines() if s.strip()]
421            for c, sv_i in zip(itertools.chain(*coefs), itertools.chain(sv_indices)):
422                model.append("%f 0:%i" % (c, indices[sv_i]))
423        else:
424            for c, sv_i in zip(itertools.chain(*coefs), itertools.chain(sv_indices)):
425                model.append("%f %s" % (c, instance_to_svm(self.support_vectors[sv_i])))
426               
427        model.append("")
428        return "\n".join(model)
429       
430
431SVMClassifierWrapper = Orange.utils.deprecated_members({
432    "classDistribution": "class_distribution",
433    "getDecisionValues": "get_decision_values",
434    "getModel" : "get_model",
435    })(SVMClassifierWrapper)
436
437class SVMLearnerSparse(SVMLearner):
438
439    """
440    A :class:`SVMLearner` that learns from data stored in meta
441    attributes. Meta attributes do not need to be registered with the
442    data set domain, or present in all data instances.
443    """
444
445    @Orange.utils.deprecated_keywords({"useNonMeta": "use_non_meta"})
446    def __init__(self, **kwds):
447        SVMLearner.__init__(self, **kwds)
448        self.use_non_meta = kwds.get("use_non_meta", False)
449        self.learner = Orange.core.SVMLearnerSparse(**kwds)
450
451    def _normalize(self, data):
452        if self.use_non_meta:
453            dc = preprocess.DomainContinuizer()
454            dc.class_treatment = preprocess.DomainContinuizer.Ignore
455            dc.continuous_treatment = preprocess.DomainContinuizer.NormalizeBySpan
456            dc.multinomial_treatment = preprocess.DomainContinuizer.NValues
457            newdomain = dc(data)
458            data = data.translate(newdomain)
459        return data
460
461class SVMLearnerEasy(SVMLearner):
462
463    """A class derived from :obj:`SVMLearner` that automatically
464    scales the data and performs parameter optimization using
465    :func:`SVMLearner.tune_parameters`. The procedure is similar to
466    that implemented in easy.py script from the LibSVM package.
467   
468    """
469
470    def __init__(self, **kwds):
471        self.folds = 4
472        self.verbose = 0
473        SVMLearner.__init__(self, **kwds)
474        self.learner = SVMLearner(**kwds)
475
476    def learn_classifier(self, data):
477        transformer = preprocess.DomainContinuizer()
478        transformer.multinomialTreatment = preprocess.DomainContinuizer.NValues
479        transformer.continuousTreatment = \
480            preprocess.DomainContinuizer.NormalizeBySpan
481        transformer.classTreatment = preprocess.DomainContinuizer.Ignore
482        newdomain = transformer(data)
483        newexamples = data.translate(newdomain)
484        #print newexamples[0]
485        params = {}
486        parameters = []
487        self.learner.normalization = False ## Normalization already done
488
489        if self.svm_type in [1, 4]:
490            numOfNuValues = 9
491            if self.svm_type == SVMLearner.Nu_SVC:
492                max_nu = max(self.max_nu(newexamples) - 1e-7, 0.0)
493            else:
494                max_nu = 1.0
495            parameters.append(("nu", [i / 10.0 for i in range(1, 9) \
496                                      if i / 10.0 < max_nu] + [max_nu]))
497        else:
498            parameters.append(("C", [2 ** a for a in  range(-5, 15, 2)]))
499        if self.kernel_type == 2:
500            parameters.append(("gamma", [2 ** a for a in range(-5, 5, 2)] + [0]))
501        import orngWrap
502        tunedLearner = orngWrap.TuneMParameters(object=self.learner,
503                                                parameters=parameters,
504                                                folds=self.folds)
505
506        return SVMClassifierWrapper(tunedLearner(newexamples,
507                                                 verbose=self.verbose))
508
509class SVMLearnerSparseClassEasy(SVMLearnerEasy, SVMLearnerSparse):
510    def __init__(self, **kwds):
511        SVMLearnerSparse.__init__(self, **kwds)
512
513def default_preprocessor():
514    # Construct and return a default preprocessor for use by
515    # Orange.core.LinearLearner learner.
516    impute = preprocess.Impute()
517    cont = preprocess.Continuize(multinomialTreatment=
518                                   preprocess.DomainContinuizer.AsOrdinal)
519    preproc = preprocess.PreprocessorList(preprocessors=
520                                            [impute, cont])
521    return preproc
522
523class LinearSVMLearner(Orange.core.LinearLearner):
524    """Train a linear SVM model."""
525
526    L2R_L2LOSS_DUAL = Orange.core.LinearLearner.L2R_L2Loss_SVC_Dual
527    L2R_L2LOSS = Orange.core.LinearLearner.L2R_L2Loss_SVC
528    L2R_L1LOSS_DUAL = Orange.core.LinearLearner.L2R_L1Loss_SVC_Dual
529    L2R_L1LOSS_DUAL = Orange.core.LinearLearner.L2R_L2Loss_SVC_Dual
530    L1R_L2LOSS = Orange.core.LinearLearner.L1R_L2Loss_SVC
531
532    __new__ = _orange__new__(base=Orange.core.LinearLearner)
533
534    def __init__(self, solver_type=L2R_L2LOSS_DUAL, C=1.0, eps=0.01, **kwargs):
535        """
536        :param solver_type: One of the following class constants: ``LR2_L2LOSS_DUAL``, ``L2R_L2LOSS``, ``LR2_L1LOSS_DUAL``, ``L2R_L1LOSS`` or ``L1R_L2LOSS``
537       
538        :param C: Regularization parameter (default 1.0)
539        :type C: float 
540       
541        :param eps: Stopping criteria (default 0.01)
542        :type eps: float
543         
544        """
545        self.solver_type = solver_type
546        self.eps = eps
547        self.C = C
548        for name, val in kwargs.items():
549            setattr(self, name, val)
550        if self.solver_type not in [self.L2R_L2LOSS_DUAL, self.L2R_L2LOSS,
551                self.L2R_L1LOSS_DUAL, self.L2R_L1LOSS_DUAL, self.L1R_L2LOSS]:
552            pass
553#            raise ValueError("Invalid solver_type parameter.")
554
555        self.preproc = default_preprocessor()
556
557    def __call__(self, instances, weight_id=None):
558        instances = self.preproc(instances)
559        classifier = super(LinearSVMLearner, self).__call__(instances, weight_id)
560        return classifier
561
562LinearLearner = LinearSVMLearner
563
564class MultiClassSVMLearner(Orange.core.LinearLearner):
565    """ Multi-class SVM (Crammer and Singer) from the `LIBLINEAR`_ library.
566    """
567    __new__ = _orange__new__(base=Orange.core.LinearLearner)
568
569    def __init__(self, C=1.0, eps=0.01, **kwargs):
570        """\
571        :param C: Regularization parameter (default 1.0)
572        :type C: float 
573       
574        :param eps: Stopping criteria (default 0.01)
575        :type eps: float
576       
577        """
578        self.C = C
579        self.eps = eps
580        for name, val in kwargs.items():
581            setattr(self, name, val)
582
583        self.solver_type = self.MCSVM_CS
584        self.preproc = default_preprocessor()
585
586    def __call__(self, instances, weight_id=None):
587        instances = self.preproc(instances)
588        classifier = super(MultiClassSVMLearner, self).__call__(instances, weight_id)
589        return classifier
590
591#TODO: Unified way to get attr weights for linear SVMs.
592
593def get_linear_svm_weights(classifier, sum=True):
594    """Extract attribute weights from the linear SVM classifier.
595   
596    For multi class classification, the result depends on the argument
597    :obj:`sum`. If ``True`` (default) the function computes the
598    squared sum of the weights over all binary one vs. one
599    classifiers. If :obj:`sum` is ``False`` it returns a list of
600    weights for each individual binary classifier (in the order of
601    [class1 vs class2, class1 vs class3 ... class2 vs class3 ...]).
602       
603    """
604
605    def update_weights(w, key, val, mul):
606        if key in w:
607            w[key] += mul * val
608        else:
609            w[key] = mul * val
610
611    def to_float(val):
612        return float(val) if not val.isSpecial() else 0.0
613
614    SVs = classifier.support_vectors
615    weights = []
616
617    class_var = SVs.domain.class_var
618    if classifier.svm_type not in [SVMLearner.C_SVC, SVMLearner.Nu_SVC]:
619        raise TypeError("SVM classification model expected.")
620   
621    classes = classifier.class_var.values
622   
623    for i in range(len(classes) - 1):
624        for j in range(i + 1, len(classes)):
625            # Get the coef and rho values from the binary sub-classifier
626            # Easier then using the full coef matrix (due to libsvm internal
627            # class  reordering)
628            bin_classifier = classifier.get_binary_classifier(i, j)
629            n_sv0 = bin_classifier.n_SV[0]
630            SVs = bin_classifier.support_vectors
631            w = {}
632           
633            for SV, alpha in zip(SVs, bin_classifier.coef[0]):
634                attributes = SVs.domain.attributes + \
635                SV.getmetas(False, Orange.feature.Descriptor).keys()
636                for attr in attributes:
637                    if attr.varType == Orange.feature.Type.Continuous:
638                        update_weights(w, attr, to_float(SV[attr]), alpha)
639               
640            weights.append(w)
641           
642    if sum:
643        scores = defaultdict(float)
644
645        for w in weights:
646            for attr, w_attr in w.items():
647                scores[attr] += w_attr ** 2
648        for key in scores:
649            scores[key] = math.sqrt(scores[key])
650        return dict(scores)
651    else:
652        return weights
653
654getLinearSVMWeights = get_linear_svm_weights
655
656def example_weighted_sum(example, weights):
657    sum = 0
658    for attr, w in weights.items():
659        sum += float(example[attr]) * w
660    return sum
661
662exampleWeightedSum = example_weighted_sum
663
664class ScoreSVMWeights(Orange.feature.scoring.Score):
665    """
666    Score a feature by the squared sum of weights using a linear SVM
667    classifier.
668       
669    Example:
670   
671        >>> score = Orange.classification.svm.ScoreSVMWeights()
672        >>> for feature in table.domain.features:
673        ...     print "%15s: %.3f" % (feature.name, score(feature, table))
674            compactness: 0.019
675            circularity: 0.026
676        distance circularity: 0.007
677           radius ratio: 0.010
678        pr.axis aspect ratio: 0.076
679        max.length aspect ratio: 0.010
680          scatter ratio: 0.046
681          elongatedness: 0.094
682        pr.axis rectangularity: 0.006
683        max.length rectangularity: 0.031
684        scaled variance along major axis: 0.001
685        scaled variance along minor axis: 0.000
686        scaled radius of gyration: 0.002
687        skewness about major axis: 0.004
688        skewness about minor axis: 0.003
689        kurtosis about minor axis: 0.001
690        kurtosis about major axis: 0.060
691          hollows ratio: 0.028
692             
693    """
694
695    def __new__(cls, attr=None, data=None, weight_id=None, **kwargs):
696        self = Orange.feature.scoring.Score.__new__(cls, **kwargs)
697        if data is not None and attr is not None:
698            self.__init__(**kwargs)
699            return self.__call__(attr, data, weight_id)
700        else:
701            return self
702
703    def __reduce__(self):
704        return ScoreSVMWeights, (), dict(self.__dict__)
705
706    def __init__(self, learner=None, **kwargs):
707        """
708        :param learner: Learner used for weight estimation
709            (default LinearSVMLearner(solver_type=L2Loss_SVM_Dual))
710        :type learner: Orange.core.LinearLearner
711       
712        """
713        if learner:
714            self.learner = learner
715        else:
716            self.learner = LinearSVMLearner(solver_type=
717                                    LinearSVMLearner.L2R_L2LOSS_DUAL)
718
719        self._cached_examples = None
720
721    def __call__(self, attr, data, weight_id=None):
722        if data is self._cached_examples:
723            weights = self._cached_weights
724        else:
725            classifier = self.learner(data, weight_id)
726            self._cached_examples = data
727            import numpy
728            weights = numpy.array(classifier.weights)
729            weights = numpy.sum(weights ** 2, axis=0)
730            weights = dict(zip(data.domain.attributes, weights))
731            self._cached_weights = weights
732        return weights.get(attr, 0.0)
733
734MeasureAttribute_SVMWeights = ScoreSVMWeights
735
736class RFE(object):
737
738    """Iterative feature elimination based on weights computed by
739    linear SVM.
740   
741    Example::
742   
743        import Orange
744        table = Orange.data.Table("vehicle.tab")
745        l = Orange.classification.svm.SVMLearner(
746            kernel_type=Orange.classification.svm.kernels.Linear,
747            normalization=False) # normalization=False will not change the domain
748        rfe = Orange.classification.svm.RFE(l)
749        data_subset_of_features = rfe(table, 5)
750       
751    """
752
753    def __init__(self, learner=None):
754        self.learner = learner or SVMLearner(kernel_type=
755                            kernels.Linear, normalization=False)
756
757    @Orange.utils.deprecated_keywords({"progressCallback": "progress_callback", "stopAt": "stop_at" })
758    def get_attr_scores(self, data, stop_at=0, progress_callback=None):
759        """Return a dictionary mapping attributes to scores.
760        A score is a step number at which the attribute
761        was removed from the recursive evaluation.
762       
763        """
764        iter = 1
765        attrs = data.domain.attributes
766        attrScores = {}
767
768        while len(attrs) > stop_at:
769            weights = get_linear_svm_weights(self.learner(data), sum=False)
770            if progress_callback:
771                progress_callback(100. * iter / (len(attrs) - stop_at))
772            score = dict.fromkeys(attrs, 0)
773            for w in weights:
774                for attr, wAttr in w.items():
775                    score[attr] += wAttr ** 2
776            score = score.items()
777            score.sort(lambda a, b:cmp(a[1], b[1]))
778            numToRemove = max(int(len(attrs) * 1.0 / (iter + 1)), 1)
779            for attr, s in  score[:numToRemove]:
780                attrScores[attr] = len(attrScores)
781            attrs = [attr for attr, s in score[numToRemove:]]
782            if attrs:
783                data = data.select(attrs + [data.domain.classVar])
784            iter += 1
785        return attrScores
786
787    @Orange.utils.deprecated_keywords({"numSelected": "num_selected", "progressCallback": "progress_callback"})
788    def __call__(self, data, num_selected=20, progress_callback=None):
789        """Return a new dataset with only `num_selected` best scoring attributes
790       
791        :param data: Data
792        :type data: Orange.data.Table
793        :param num_selected: number of features to preserve
794        :type num_selected: int
795       
796        """
797        scores = self.get_attr_scores(data, progress_callback=progress_callback)
798        scores = sorted(scores.items(), key=lambda item: item[1])
799
800        scores = dict(scores[-num_selected:])
801        attrs = [attr for attr in data.domain.attributes if attr in scores]
802        domain = Orange.data.Domain(attrs, data.domain.classVar)
803        domain.addmetas(data.domain.getmetas())
804        data = Orange.data.Table(domain, data)
805        return data
806
807RFE = Orange.utils.deprecated_members({
808    "getAttrScores": "get_attr_scores"},
809    wrap_methods=["get_attr_scores", "__call__"])(RFE)
810
811def example_table_to_svm_format(table, file):
812    warnings.warn("Deprecated. Use table_to_svm_format", DeprecationWarning)
813    table_to_svm_format(table, file)
814
815exampleTableToSVMFormat = example_table_to_svm_format
816
817def table_to_svm_format(data, file):
818    """Save :obj:`Orange.data.Table` to a format used by LibSVM.
819   
820    :param data: Data
821    :type data: Orange.data.Table
822    :param file: file pointer
823    :type file: file
824   
825    """
826
827    attrs = data.domain.attributes + data.domain.getmetas().values()
828    attrs = [attr for attr in attrs if attr.varType
829             in [Orange.feature.Type.Continuous,
830                 Orange.feature.Type.Discrete]]
831    cv = data.domain.classVar
832
833    for ex in data:
834        if cv.varType == Orange.feature.Type.Discrete:
835            file.write(str(int(ex[cv])))
836        else:
837            file.write(str(float(ex[cv])))
838
839        for i, attr in enumerate(attrs):
840            if not ex[attr].isSpecial():
841                file.write(" " + str(i + 1) + ":" + str(float(ex[attr])))
842        file.write("\n")
843
844tableToSVMFormat = table_to_svm_format
Note: See TracBrowser for help on using the repository browser.