source: orange/Orange/classification/svm/__init__.py @ 10573:70f6ce446c91

Revision 10573:70f6ce446c91, 29.7 KB checked in by Ales Erjavec <ales.erjavec@…>, 2 years ago (diff)

Added get_binary_classifier method to SVMClassifierWrapper.

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