Changeset 10954:444fb02c6ccb in orange


Ignore:
Timestamp:
07/19/12 12:00:55 (21 months ago)
Author:
Ales Erjavec <ales.erjavec@…>
Branch:
default
Message:

Handle a case where not all classes are present in the trained libsvm model.

This can happen if the training data does not have any instances for some
classes.

File:
1 edited

Legend:

Unmodified
Added
Removed
  • Orange/classification/svm/__init__.py

    r10774 r10954  
    4444 
    4545maxNu = max_nu 
     46 
     47 
     48def is_discrete(feature): 
     49    return isinstance(feature, Orange.feature.Discrete) 
     50 
     51 
     52def is_continuous(feature): 
     53    return isinstance(feature, Orange.feature.Continuous) 
     54 
    4655 
    4756class SVMLearner(_SVMLearner): 
     
    253262        return data.translate(newdomain) 
    254263 
     264 
    255265SVMLearner = Orange.utils.deprecated_members({ 
    256266    "learnClassifier": "learn_classifier", 
    257267    "tuneParameters": "tune_parameters", 
    258     "kernelFunc" : "kernel_func", 
     268    "kernelFunc": "kernel_func", 
    259269    }, 
    260270    wrap_methods=["__init__", "tune_parameters"])(SVMLearner) 
     271 
    261272 
    262273class SVMClassifier(_SVMClassifier): 
    263274    def __new__(cls, *args, **kwargs): 
    264275        if args and isinstance(args[0], _SVMClassifier): 
    265             # Will wrap a C++ object  
     276            # Will wrap a C++ object 
    266277            return _SVMClassifier.__new__(cls, name=args[0].name) 
    267278        elif args and isinstance(args[0], variable.Descriptor): 
    268279            # The constructor call for the C++ object. 
    269             # This is a hack to support loading of old pickled classifiers   
     280            # This is a hack to support loading of old pickled classifiers 
    270281            return _SVMClassifier.__new__(_SVMClassifier, *args, **kwargs) 
    271282        else: 
     
    281292        self.kernel_type = wrapped.kernel_type 
    282293        self.__wrapped = wrapped 
    283          
     294 
    284295        assert(type(wrapped) in [_SVMClassifier, _SVMClassifierSparse]) 
    285          
     296 
    286297        if self.svm_type in [SVMLearner.C_SVC, SVMLearner.Nu_SVC] \ 
    287298                and len(wrapped.support_vectors) > 0: 
     
    291302            support_vectors = [] 
    292303            for n in wrapped.n_SV: 
    293                 support_vectors.append(wrapped.support_vectors[start: start + n]) 
     304                support_vectors.append( 
     305                    wrapped.support_vectors[start: start + n] 
     306                ) 
    294307                start += n 
    295             support_vectors = [support_vectors[i] for i in label_map] 
    296             self.support_vectors = Orange.data.Table(reduce(add, support_vectors)) 
     308            support_vectors = [support_vectors[i] for i in label_map \ 
     309                               if i is not None] 
     310            support_vectors = reduce(add, support_vectors) 
     311            self.support_vectors = Orange.data.Table(support_vectors) 
    297312        else: 
    298313            self.support_vectors = wrapped.support_vectors 
    299      
     314 
    300315    @property 
    301316    def coef(self): 
    302317        """Coefficients of the underlying svm model. 
    303          
     318 
    304319        If this is a classification model then this is a list of 
    305320        coefficients for each binary 1vs1 classifiers, i.e. 
    306321        #Classes * (#Classses - 1) list of lists where 
    307322        each sublist contains tuples of (coef, support_vector_index) 
    308          
     323 
    309324        For regression models it is still a list of lists (for consistency) 
    310         but of length 1 e.g. [[(coef, support_vector_index), ... ]]  
    311             
    312         """ 
    313         if isinstance(self.class_var, variable.Discrete): 
     325        but of length 1 e.g. [[(coef, support_vector_index), ... ]] 
     326 
     327        """ 
     328        if is_discrete(self.class_var): 
    314329            # We need to reorder the coef values 
    315330            # see http://www.csie.ntu.edu.tw/~cjlin/libsvm/faq.html#f804 
     
    319334            c_map = self._get_libsvm_bin_classifier_map() 
    320335            label_map = self._get_libsvm_labels_map() 
    321             libsvm_coef = self.__wrapped.coef 
    322             coef = [] #[None] * len(c_map) 
     336            coef = [] 
    323337            n_class = len(label_map) 
    324338            n_SV = self.__wrapped.n_SV 
     
    331345                    ni = label_map[i] 
    332346                    nj = label_map[j] 
     347 
     348                    if ni is None or nj is None: 
     349                        # One of the classes is missing from the model. 
     350                        continue 
     351 
    333352                    bc_index, mult = c_map[p] 
    334                      
     353 
    335354                    if ni > nj: 
     355                        # The order in libsvm model is switched. 
    336356                        ni, nj = nj, ni 
    337                      
     357 
    338358                    # Original class indices 
    339359                    c1_range = range(libsvm_class_indices[ni], 
    340360                                     libsvm_class_indices[ni + 1]) 
    341                     c2_range = range(libsvm_class_indices[nj],  
     361                    c2_range = range(libsvm_class_indices[nj], 
    342362                                     libsvm_class_indices[nj + 1]) 
    343                      
     363 
    344364                    coef1 = mult * coef_array[nj - 1, c1_range] 
    345365                    coef2 = mult * coef_array[ni, c2_range] 
    346                      
     366 
    347367                    # Mapped class indices 
    348368                    c1_range = range(class_indices[i], 
    349369                                     class_indices[i + 1]) 
    350                     c2_range = range(class_indices[j],  
     370                    c2_range = range(class_indices[j], 
    351371                                     class_indices[j + 1]) 
    352372                    if mult == -1.0: 
    353373                        c1_range, c2_range = c2_range, c1_range 
    354                          
     374 
    355375                    nonzero1 = np.abs(coef1) > 0.0 
    356376                    nonzero2 = np.abs(coef2) > 0.0 
    357                      
     377 
    358378                    coef1 = coef1[nonzero1] 
    359379                    coef2 = coef2[nonzero2] 
    360                      
    361                     c1_range = [sv_i for sv_i, nz in zip(c1_range, nonzero1) if nz] 
    362                     c2_range = [sv_i for sv_i, nz in zip(c2_range, nonzero2) if nz] 
    363                      
    364                     coef.append(list(zip(coef1, c1_range)) + list(zip(coef2, c2_range))) 
    365                      
     380 
     381                    c1_range = [sv_i for sv_i, nz in zip(c1_range, nonzero1) 
     382                                if nz] 
     383                    c2_range = [sv_i for sv_i, nz in zip(c2_range, nonzero2) 
     384                                if nz] 
     385 
     386                    coef.append(list(zip(coef1, c1_range)) + \ 
     387                                list(zip(coef2, c2_range))) 
     388 
    366389                    p += 1 
    367390        else: 
    368             coef = [zip(self.__wrapped.coef[0], range(len(self.support_vectors)))] 
    369              
     391            coef = [zip(self.__wrapped.coef[0], 
     392                        range(len(self.support_vectors)))] 
     393 
    370394        return coef 
    371      
     395 
    372396    @property 
    373397    def rho(self): 
    374398        """Constant (bias) terms of the svm model. 
    375          
    376         For classification models this is a list of bias terms  
     399 
     400        For classification models this is a list of bias terms 
    377401        for each binary 1vs1 classifier. 
    378          
     402 
    379403        For regression models it is a list with a single value. 
    380           
     404 
    381405        """ 
    382406        rho = self.__wrapped.rho 
    383         if isinstance(self.class_var, variable.Discrete): 
     407        if is_discrete(self.class_var): 
    384408            c_map = self._get_libsvm_bin_classifier_map() 
    385409            return [rho[i] * m for i, m in c_map] 
    386410        else: 
    387411            return list(rho) 
    388      
     412 
    389413    @property 
    390414    def n_SV(self): 
    391415        """Number of support vectors for each class. 
    392416        For regression models this is `None`. 
    393          
    394         """ 
    395         if self.__wrapped.n_SV is not None: 
    396             c_map = self._get_libsvm_labels_map() 
    397             n_SV= self.__wrapped.n_SV 
    398             return [n_SV[i] for i in c_map] 
     417 
     418        """ 
     419        n_SV = self.__wrapped.n_SV 
     420        if n_SV is not None: 
     421            labels_map = self._get_libsvm_labels_map() 
     422            return [n_SV[i] if i is not None else 0 for i in labels_map] 
    399423        else: 
    400424            return None 
    401      
     425 
    402426    # Pairwise probability is expresed as: 
    403     #   1.0 / (1.0 + exp(dec_val[i] * prob_a[i] + prob_b[i]))  
    404     # Since dec_val already changes signs if we switch the  
     427    #   1.0 / (1.0 + exp(dec_val[i] * prob_a[i] + prob_b[i])) 
     428    # Since dec_val already changes signs if we switch the 
    405429    # classifier direction only prob_b must change signs 
    406430    @property 
     
    416440        else: 
    417441            return None 
    418      
     442 
    419443    @property 
    420444    def prob_b(self): 
     
    426450        else: 
    427451            return None 
    428      
     452 
    429453    def __call__(self, instance, what=Orange.core.GetValue): 
    430454        """Classify a new ``instance`` 
     
    442466        """Return the decision values of the binary 1vs1 
    443467        classifiers for the ``instance`` (:class:`~Orange.data.Instance`). 
    444          
     468 
    445469        """ 
    446470        instance = Orange.data.Instance(self.domain, instance) 
     
    453477        else: 
    454478            return list(dec_values) 
    455          
     479 
    456480    def get_model(self): 
    457481        """Return a string representing the model in the libsvm model format. 
    458482        """ 
    459483        return self.__wrapped.get_model() 
    460      
     484 
    461485    def _get_libsvm_labels_map(self): 
    462         """Get the internal libsvm label mapping.  
    463         """ 
    464         labels = [line for line in self.__wrapped.get_model().splitlines() \ 
     486        """Get the mapping from indices in `class_var.values` to 
     487        internal libsvm labels. If a class value is missing from the libsvm 
     488        model the returned corresponding entry is `None`) 
     489 
     490        """ 
     491        if is_discrete(self.class_var): 
     492            n_classes = len(self.class_var.values) 
     493        else: 
     494            # OneClass/Regression models 
     495            n_classes = 1 
     496        model_string = self.__wrapped.get_model() 
     497        # Get the labels definition line from the model string 
     498        # (the labels, if present, are always integer strings 
     499        # indexing self.class_var.values) 
     500        labels = [line for line in model_string.splitlines() \ 
    465501                  if line.startswith("label")] 
    466502        labels = labels[0].split(" ")[1:] if labels else ["0"] 
    467503        labels = [int(label) for label in labels] 
    468         return [labels.index(i) for i in range(len(labels))] 
     504        labels_map = dict((cls_index, i) for i, cls_index in enumerate(labels)) 
     505        return [labels_map.get(i) for i in range(n_classes)] 
    469506 
    470507    def _get_libsvm_bin_classifier_map(self): 
    471508        """Return the libsvm binary classifier mapping (due to label ordering). 
    472509        """ 
    473         if not isinstance(self.class_var, variable.Discrete): 
     510        if not is_discrete(self.class_var): 
    474511            raise TypeError("SVM classification model expected") 
     512 
    475513        label_map = self._get_libsvm_labels_map() 
    476514        bin_c_map = [] 
    477         n_class = len(self.class_var.values) 
    478         p = 0 
    479         for i in range(n_class - 1): 
    480             for j in range(i + 1, n_class): 
     515        n_class_values = len(self.class_var.values) 
     516        nr_class = len([i for i in label_map if i is not None]) 
     517        for i in range(n_class_values - 1): 
     518            for j in range(i + 1, n_class_values): 
    481519                ni = label_map[i] 
    482520                nj = label_map[j] 
    483521                mult = 1 
     522 
     523                if ni is None or nj is None: 
     524                    # One or both classes are missing from the libsvm model. 
     525                    continue 
     526 
    484527                if ni > nj: 
     528                    # The order in libsvm is switched 
    485529                    ni, nj = nj, ni 
    486530                    mult = -1 
     531 
    487532                # classifier index 
    488                 cls_index = n_class * (n_class - 1) / 2 - (n_class - ni - 1) * (n_class - ni - 2) / 2 - (n_class - nj) 
     533                cls_index = nr_class * (nr_class - 1) / 2 - \ 
     534                            (nr_class - ni - 1) * (nr_class - ni - 2) / 2 - \ 
     535                            (nr_class - nj) 
    489536                bin_c_map.append((cls_index, mult)) 
    490537        return bin_c_map 
    491                  
     538 
    492539    def __reduce__(self): 
    493540        return SVMClassifier, (self.__wrapped,), dict(self.__dict__) 
Note: See TracChangeset for help on using the changeset viewer.