source: orange/Orange/classification/svm/__init__.py @ 10574:f7272ba4865d

Revision 10574:f7272ba4865d, 30.2 KB checked in by Ales Erjavec <ales.erjavec@…>, 2 years ago (diff)

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