source: orange/Orange/classification/svm/__init__.py @ 10634:1ebc6f592252

Revision 10634:1ebc6f592252, 36.7 KB checked in by mstajdohar, 2 years ago (diff)

Fixed some more obsolete stuff and added ignore DeprecationWarning on tutorial tests.

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