source: orange/Orange/classification/svm/__init__.py @ 10585:e0b498c68aaa

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

Always wrap the native SVMLearner.

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