source: orange/Orange/classification/svm/__init__.py @ 10578:29902b555575

Revision 10578:29902b555575, 31.7 KB checked in by Ales Erjavec <ales.erjavec@…>, 2 years ago (diff)

Map internal class values order of libsvm to orange variable's native ordering in get_decision_values and get_binary_classifier.

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.misc 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.misc.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.misc.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.misc.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 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("Wrong svm type.")
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.misc.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.misc.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 in [SVMLearner.C_SVC, SVMLearner.Nu_SVC]:
619        classes = class_var.values
620    else:
621        classes = [""]
622    if len(classes) > 1:
623        sv_ranges = [(0, classifier.nSV[0])]
624        for n in classifier.nSV[1:]:
625            sv_ranges.append((sv_ranges[-1][1], sv_ranges[-1][1] + n))
626    else:
627        sv_ranges = [(0, len(SVs))]
628
629    for i in range(len(classes) - 1):
630        for j in range(i + 1, len(classes)):
631            w = {}
632            coef_ind = j - 1
633            for sv_ind in range(*sv_ranges[i]):
634                attributes = SVs.domain.attributes + \
635                SVs[sv_ind].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(SVs[sv_ind][attr]), \
639                                       classifier.coef[coef_ind][sv_ind])
640            coef_ind = i
641            for sv_ind in range(*sv_ranges[j]):
642                attributes = SVs.domain.attributes + \
643                SVs[sv_ind].getmetas(False, Orange.feature.Descriptor).keys()
644                for attr in attributes:
645                    if attr.varType == Orange.feature.Type.Continuous:
646                        update_weights(w, attr, to_float(SVs[sv_ind][attr]), \
647                                       classifier.coef[coef_ind][sv_ind])
648            weights.append(w)
649
650    if sum:
651        scores = defaultdict(float)
652
653        for w in weights:
654            for attr, w_attr in w.items():
655                scores[attr] += w_attr ** 2
656        for key in scores:
657            scores[key] = math.sqrt(scores[key])
658        return scores
659    else:
660        return weights
661
662getLinearSVMWeights = get_linear_svm_weights
663
664def example_weighted_sum(example, weights):
665    sum = 0
666    for attr, w in weights.items():
667        sum += float(example[attr]) * w
668    return sum
669
670exampleWeightedSum = example_weighted_sum
671
672class ScoreSVMWeights(Orange.feature.scoring.Score):
673    """
674    Score a feature by the squared sum of weights using a linear SVM
675    classifier.
676       
677    Example:
678   
679        >>> score = Orange.classification.svm.ScoreSVMWeights()
680        >>> for feature in table.domain.features:
681        ...     print "%15s: %.3f" % (feature.name, score(feature, table))
682            compactness: 0.019
683            circularity: 0.026
684        distance circularity: 0.007
685           radius ratio: 0.010
686        pr.axis aspect ratio: 0.076
687        max.length aspect ratio: 0.010
688          scatter ratio: 0.046
689          elongatedness: 0.094
690        pr.axis rectangularity: 0.006
691        max.length rectangularity: 0.031
692        scaled variance along major axis: 0.001
693        scaled variance along minor axis: 0.000
694        scaled radius of gyration: 0.002
695        skewness about major axis: 0.004
696        skewness about minor axis: 0.003
697        kurtosis about minor axis: 0.001
698        kurtosis about major axis: 0.060
699          hollows ratio: 0.028
700             
701    """
702
703    def __new__(cls, attr=None, data=None, weight_id=None, **kwargs):
704        self = Orange.feature.scoring.Score.__new__(cls, **kwargs)
705        if data is not None and attr is not None:
706            self.__init__(**kwargs)
707            return self.__call__(attr, data, weight_id)
708        else:
709            return self
710
711    def __reduce__(self):
712        return ScoreSVMWeights, (), dict(self.__dict__)
713
714    def __init__(self, learner=None, **kwargs):
715        """
716        :param learner: Learner used for weight estimation
717            (default LinearSVMLearner(solver_type=L2Loss_SVM_Dual))
718        :type learner: Orange.core.LinearLearner
719       
720        """
721        if learner:
722            self.learner = learner
723        else:
724            self.learner = LinearSVMLearner(solver_type=
725                                    LinearSVMLearner.L2R_L2LOSS_DUAL)
726
727        self._cached_examples = None
728
729    def __call__(self, attr, data, weight_id=None):
730        if data is self._cached_examples:
731            weights = self._cached_weights
732        else:
733            classifier = self.learner(data, weight_id)
734            self._cached_examples = data
735            import numpy
736            weights = numpy.array(classifier.weights)
737            weights = numpy.sum(weights ** 2, axis=0)
738            weights = dict(zip(data.domain.attributes, weights))
739            self._cached_weights = weights
740        return weights.get(attr, 0.0)
741
742MeasureAttribute_SVMWeights = ScoreSVMWeights
743
744class RFE(object):
745
746    """Iterative feature elimination based on weights computed by
747    linear SVM.
748   
749    Example::
750   
751        import Orange
752        table = Orange.data.Table("vehicle.tab")
753        l = Orange.classification.svm.SVMLearner(
754            kernel_type=Orange.classification.svm.kernels.Linear,
755            normalization=False) # normalization=False will not change the domain
756        rfe = Orange.classification.svm.RFE(l)
757        data_subset_of_features = rfe(table, 5)
758       
759    """
760
761    def __init__(self, learner=None):
762        self.learner = learner or SVMLearner(kernel_type=
763                            kernels.Linear, normalization=False)
764
765    @Orange.misc.deprecated_keywords({"progressCallback": "progress_callback", "stopAt": "stop_at" })
766    def get_attr_scores(self, data, stop_at=0, progress_callback=None):
767        """Return a dictionary mapping attributes to scores.
768        A score is a step number at which the attribute
769        was removed from the recursive evaluation.
770       
771        """
772        iter = 1
773        attrs = data.domain.attributes
774        attrScores = {}
775
776        while len(attrs) > stop_at:
777            weights = get_linear_svm_weights(self.learner(data), sum=False)
778            if progress_callback:
779                progress_callback(100. * iter / (len(attrs) - stop_at))
780            score = dict.fromkeys(attrs, 0)
781            for w in weights:
782                for attr, wAttr in w.items():
783                    score[attr] += wAttr ** 2
784            score = score.items()
785            score.sort(lambda a, b:cmp(a[1], b[1]))
786            numToRemove = max(int(len(attrs) * 1.0 / (iter + 1)), 1)
787            for attr, s in  score[:numToRemove]:
788                attrScores[attr] = len(attrScores)
789            attrs = [attr for attr, s in score[numToRemove:]]
790            if attrs:
791                data = data.select(attrs + [data.domain.classVar])
792            iter += 1
793        return attrScores
794
795    @Orange.misc.deprecated_keywords({"numSelected": "num_selected", "progressCallback": "progress_callback"})
796    def __call__(self, data, num_selected=20, progress_callback=None):
797        """Return a new dataset with only `num_selected` best scoring attributes
798       
799        :param data: Data
800        :type data: Orange.data.Table
801        :param num_selected: number of features to preserve
802        :type num_selected: int
803       
804        """
805        scores = self.get_attr_scores(data, progress_callback=progress_callback)
806        scores = sorted(scores.items(), key=lambda item: item[1])
807
808        scores = dict(scores[-num_selected:])
809        attrs = [attr for attr in data.domain.attributes if attr in scores]
810        domain = Orange.data.Domain(attrs, data.domain.classVar)
811        domain.addmetas(data.domain.getmetas())
812        data = Orange.data.Table(domain, data)
813        return data
814
815RFE = Orange.misc.deprecated_members({
816    "getAttrScores": "get_attr_scores"},
817    wrap_methods=["get_attr_scores", "__call__"])(RFE)
818
819def example_table_to_svm_format(table, file):
820    warnings.warn("Deprecated. Use table_to_svm_format", DeprecationWarning)
821    table_to_svm_format(table, file)
822
823exampleTableToSVMFormat = example_table_to_svm_format
824
825def table_to_svm_format(data, file):
826    """Save :obj:`Orange.data.Table` to a format used by LibSVM.
827   
828    :param data: Data
829    :type data: Orange.data.Table
830    :param file: file pointer
831    :type file: file
832   
833    """
834
835    attrs = data.domain.attributes + data.domain.getmetas().values()
836    attrs = [attr for attr in attrs if attr.varType
837             in [Orange.feature.Type.Continuous,
838                 Orange.feature.Type.Discrete]]
839    cv = data.domain.classVar
840
841    for ex in data:
842        if cv.varType == Orange.feature.Type.Discrete:
843            file.write(str(int(ex[cv])))
844        else:
845            file.write(str(float(ex[cv])))
846
847        for i, attr in enumerate(attrs):
848            if not ex[attr].isSpecial():
849                file.write(" " + str(i + 1) + ":" + str(float(ex[attr])))
850        file.write("\n")
851
852tableToSVMFormat = table_to_svm_format
Note: See TracBrowser for help on using the repository browser.