Changeset 8195:87e4c1e47a98 in orange


Ignore:
Timestamp:
08/17/11 14:48:20 (3 years ago)
Author:
ales_erjavec <ales.erjavec@…>
Branch:
default
Convert:
a43387f752aedcea1037e45fd19e2a1aa6f8ff48
Message:

Added support for classification (expand discrete class with indicator columns and learn a multi response model).
Using ctypes.c_* for dtype instead of numpy.* dtypes in the ctypes interface to ForwardPass and EvalSubsetsUsingXtx.

File:
1 edited

Legend:

Unmodified
Added
Removed
  • orange/Orange/regression/earth.py

    r8157 r8195  
    5050 
    5151import Orange 
    52 from Orange.core import EarthLearner as BaseEarthLearner, \ 
    53                         EarthClassifier as BaseEarthClassifier 
     52from Orange.data.variable import Discrete, Continuous 
     53from Orange.data import Table, Domain 
    5454from Orange.preprocess import Preprocessor_continuize, \ 
    5555                              Preprocessor_impute, \ 
    5656                              Preprocessor_preprocessorList, \ 
    5757                              DomainContinuizer 
    58              
     58 
    5959import numpy 
    6060 
     61def is_discrete(var): 
     62    return isinstance(var, Discrete) 
     63 
     64def is_continuous(var): 
     65    return isinstance(var, Continuous) 
     66 
     67def expand_discrete(var): 
     68    if len(var.values) > 2: 
     69        values = var.values 
     70    elif len(var.values) == 2: 
     71        values = var.values[-1:] 
     72    else: 
     73        values = var.values[:1] 
     74    new_vars = [] 
     75    for value in values: 
     76        new = Continuous("{0}={1}".format(var.name, value)) 
     77        new.get_value_from = cls = Orange.core.ClassifierFromVar(whichVar=var) 
     78        cls.transformer = Orange.core.Discrete2Continuous() 
     79        cls.transformer.value = int(Orange.core.Value(var, value)) 
     80        new.source_variable = var 
     81        new_vars.append(new) 
     82    return new_vars 
     83     
    6184class EarthLearner(Orange.core.LearnerFD): 
    6285    """ Earth learner class. 
     
    124147         
    125148        impute = Preprocessor_impute() 
    126         cont = Preprocessor_continuize(multinomialTreatment= 
    127                                        DomainContinuizer.AsOrdinal) 
    128          
     149        cont = Preprocessor_continuize(multinomialTreatment=DomainContinuizer.AsOrdinal) 
    129150        self.preproc = Preprocessor_preprocessorList(preprocessors=\ 
    130151                                                     [impute, cont]) 
    131152         
    132153    def __call__(self, examples, weight_id=None): 
     154        original_domain = examples.domain # Needed by classifier and evimp 
    133155        examples = self.preproc(examples) 
    134          
     156        expanded_class = None 
    135157        if self.multi_label: 
    136158            label_mask = data_label_mask(examples.domain) 
     159            data = examples.to_numpy_MA("Ac")[0] 
     160            y = data[:, label_mask] 
     161            x = data[:, ~ label_mask] 
    137162        else: 
    138             label_mask = numpy.zeros(len(examples.domain.variables), 
    139                                      dtype=bool) 
    140             label_mask[-1] = bool(examples.domain.class_var) 
    141              
    142         if not any(label_mask): 
    143             raise ValueError("The domain has no response variable.") 
    144           
    145         data = examples.to_numpy_MA("Ac")[0] 
    146         y = data[:, label_mask] 
    147         x = data[:, ~ label_mask] 
    148          
    149         if self.scale_resp: 
     163            # Expand a discrete class with indicator columns 
     164            if is_discrete(examples.domain.class_var): 
     165                expanded_class = expand_discrete(examples.domain.class_var) 
     166                y = Table(Domain(expanded_class, None), examples) 
     167                y = y.to_numpy_MA("A")[0] 
     168                x = examples.to_numpy_MA("A")[0] 
     169                label_mask = [False] * x.shape[1] + [True] * y.shape[1] 
     170                label_mask = numpy.array(label_mask) 
     171            elif is_continuous(examples.domain.class_var): 
     172                label_mask = numpy.zeros(len(examples.domain.variables), 
     173                                         dtype=bool) 
     174                label_mask[-1] = True 
     175                x, y, _ = examples.to_numpy_MA() 
     176                y = y.reshape((-1, 1)) 
     177            else: 
     178                raise ValueError("Cannot handle the response.") 
     179         
     180        if self.scale_resp and y.shape[1] == 1: 
    150181            sy = y - numpy.mean(y, axis=0) 
    151182            sy = sy / numpy.std(sy, axis=1) 
     
    175206                               subsets, rss_per_subset, gcv_per_subset, 
    176207                               examples=examples if self.store_examples else None, 
    177                                label_mask=label_mask, multi_flag=self.multi_label) 
     208                               label_mask=label_mask, multi_flag=self.multi_label, 
     209                               expanded_class=expanded_class) 
    178210     
    179211 
     
    183215    def __init__(self, domain, best_set, dirs, cuts, betas, subsets=None, 
    184216                 rss_per_subset=None, gcv_per_subset=None, examples=None, 
    185                  label_mask=None, multi_flag=False, **kwargs): 
     217                 label_mask=None, multi_flag=False, expanded_class=None, 
     218                 original_domain=None, **kwargs): 
    186219        self.multi_flag = multi_flag 
    187220        self.domain = domain 
     
    196229        self.examples = examples 
    197230        self.label_mask = label_mask 
     231        self.expanded_class = expanded_class 
     232        self.original_domain = original_domain 
    198233        self.__dict__.update(kwargs) 
    199234         
    200235    def __call__(self, example, result_type=Orange.core.GetValue): 
    201         resp_vars = [v for v, m in zip(self.domain.variables, self.label_mask)\ 
    202                      if m] 
     236        if self.multi_flag: 
     237            resp_vars = [v for v, m in zip(self.domain.variables, 
     238                                           self.label_mask) if m] 
     239        elif is_discrete(self.class_var): 
     240            resp_vars = self.expanded_class 
     241        else: 
     242            resp_vars = [self.class_var] 
     243             
    203244        vals = self.predict(example) 
    204245        vals = [var(val) for var, val in zip(resp_vars, vals)] 
    205246         
    206         probs = [] 
    207         for var, val in zip(resp_vars, vals): 
    208             dist = Orange.statistics.distribution.Distribution(var) 
    209             dist[val] = 1.0 
    210             probs.append(dist) 
     247        from Orange.statistics.distribution import Distribution 
     248         
     249        if is_discrete(self.class_var): 
     250            winner = max(vals) #TODO: Handle ties. 
     251            value = winner.variable.get_value_from.transformer.value 
     252            value = self.class_var(value) 
     253            dist = Distribution(self.class_var) 
     254            dist[value] = 1.0 
     255            vals, probs = [value], [dist] 
     256        else: 
     257            probs = [] 
     258            for var, val in zip(resp_vars, vals): 
     259                dist = Distribution(var) 
     260                dist[val] = 1.0 
     261                probs.append(dist) 
    211262             
    212263        if not self.multi_flag: 
     
    422473_c_forward_pass_.argtypes = \ 
    423474    [ctypes.POINTER(ctypes.c_int),  #pnTerms: 
    424      ctypeslib.ndpointer(dtype=numpy.bool, ndim=1),  #FullSet 
    425      ctypeslib.ndpointer(dtype=numpy.double, ndim=2, flags="F_CONTIGUOUS"), #bx 
    426      ctypeslib.ndpointer(dtype=numpy.int, ndim=2, flags="F_CONTIGUOUS"),    #Dirs 
    427      ctypeslib.ndpointer(dtype=numpy.double, ndim=2, flags="F_CONTIGUOUS"), #Cuts 
    428      ctypeslib.ndpointer(dtype=numpy.int, ndim=1),  #nFactorsInTerms 
    429      ctypeslib.ndpointer(dtype=numpy.int, ndim=1),  #nUses 
    430      ctypeslib.ndpointer(dtype=numpy.double, ndim=2, flags="F_CONTIGUOUS"), #x 
    431      ctypeslib.ndpointer(dtype=numpy.double, ndim=2, flags="F_CONTIGUOUS"), #y 
    432      ctypeslib.ndpointer(dtype=numpy.double, ndim=1), # Weights 
     475     ctypeslib.ndpointer(dtype=ctypes.c_bool, ndim=1),  #FullSet 
     476     ctypeslib.ndpointer(dtype=ctypes.c_double, ndim=2, flags="F_CONTIGUOUS"), #bx 
     477     ctypeslib.ndpointer(dtype=ctypes.c_int, ndim=2, flags="F_CONTIGUOUS"),    #Dirs 
     478     ctypeslib.ndpointer(dtype=ctypes.c_double, ndim=2, flags="F_CONTIGUOUS"), #Cuts 
     479     ctypeslib.ndpointer(dtype=ctypes.c_int, ndim=1),  #nFactorsInTerms 
     480     ctypeslib.ndpointer(dtype=ctypes.c_int, ndim=1),  #nUses 
     481     ctypeslib.ndpointer(dtype=ctypes.c_double, ndim=2, flags="F_CONTIGUOUS"), #x 
     482     ctypeslib.ndpointer(dtype=ctypes.c_double, ndim=2, flags="F_CONTIGUOUS"), #y 
     483     ctypeslib.ndpointer(dtype=ctypes.c_double, ndim=1), # Weights 
    433484     ctypes.c_int,  #nCases 
    434485     ctypes.c_int,  #nResp 
     
    441492     ctypes.c_double,   #FastBeta 
    442493     ctypes.c_double,   #NewVarPenalty 
    443      ctypeslib.ndpointer(dtype=numpy.int, ndim=1),  #LinPreds 
     494     ctypeslib.ndpointer(dtype=ctypes.c_int, ndim=1),  #LinPreds 
    444495     ctypes.c_bool, #UseBetaCache 
    445496     ctypes.c_char_p    #sPredNames 
     
    451502    """ 
    452503    import ctypes, orange 
    453     x = numpy.asfortranarray(x, dtype="d") 
    454     y = numpy.asfortranarray(y, dtype="d") 
     504    x = numpy.asfortranarray(x, dtype=ctypes.c_double) 
     505    y = numpy.asfortranarray(y, dtype=ctypes.c_double) 
    455506    if x.shape[0] != y.shape[0]: 
    456507        raise ValueError("First dimensions of x and y must be the same.") 
     
    466517    # Output variables 
    467518    n_term = ctypes.c_int() 
    468     full_set = numpy.zeros((terms,), dtype=numpy.bool, order="F") 
    469     bx = numpy.zeros((n_cases, terms), dtype=numpy.double, order="F") 
    470     dirs = numpy.zeros((terms, n_preds), dtype=numpy.int, order="F") 
    471     cuts = numpy.zeros((terms, n_preds), dtype=numpy.double, order="F") 
    472     n_factors_in_terms = numpy.zeros((terms,), dtype=numpy.int, order="F") 
    473     n_uses = numpy.zeros((n_preds,), dtype=numpy.int, order="F") 
    474     weights = numpy.ones((n_cases,), dtype=numpy.double, order="F") 
    475     lin_preds = numpy.zeros((n_preds,), dtype=numpy.int, order="F") 
     519    full_set = numpy.zeros((terms,), dtype=ctypes.c_bool, order="F") 
     520    bx = numpy.zeros((n_cases, terms), dtype=ctypes.c_double, order="F") 
     521    dirs = numpy.zeros((terms, n_preds), dtype=ctypes.c_int, order="F") 
     522    cuts = numpy.zeros((terms, n_preds), dtype=ctypes.c_double, order="F") 
     523    n_factors_in_terms = numpy.zeros((terms,), dtype=ctypes.c_int, order="F") 
     524    n_uses = numpy.zeros((n_preds,), dtype=ctypes.c_int, order="F") 
     525    weights = numpy.ones((n_cases,), dtype=ctypes.c_double, order="F") 
     526    lin_preds = numpy.zeros((n_preds,), dtype=ctypes.c_int, order="F") 
    476527    use_beta_cache = True 
    477528     
     
    519570 
    520571_c_eval_subsets_xtx.argtypes = \ 
    521     [ctypeslib.ndpointer(dtype=numpy.bool, ndim=2, flags="F_CONTIGUOUS"),   #PruneTerms 
    522      ctypeslib.ndpointer(dtype=numpy.double, ndim=1),   #RssVec 
     572    [ctypeslib.ndpointer(dtype=ctypes.c_bool, ndim=2, flags="F_CONTIGUOUS"),   #PruneTerms 
     573     ctypeslib.ndpointer(dtype=ctypes.c_double, ndim=1),   #RssVec 
    523574     ctypes.c_int,  #nCases 
    524575     ctypes.c_int,  #nResp 
    525576     ctypes.c_int,  #nMaxTerms 
    526      ctypeslib.ndpointer(dtype=numpy.double, ndim=2, flags="F_CONTIGUOUS"),   #bx 
    527      ctypeslib.ndpointer(dtype=numpy.double, ndim=2, flags="F_CONTIGUOUS"),   #y 
    528      ctypeslib.ndpointer(dtype=numpy.double, ndim=1)  #WeightsArg 
     577     ctypeslib.ndpointer(dtype=ctypes.c_double, ndim=2, flags="F_CONTIGUOUS"),   #bx 
     578     ctypeslib.ndpointer(dtype=ctypes.c_double, ndim=2, flags="F_CONTIGUOUS"),   #y 
     579     ctypeslib.ndpointer(dtype=ctypes.c_double, ndim=1)  #WeightsArg 
    529580     ] 
    530581 
     
    532583    """ Subsets selection using EvalSubsetsUsingXtx in the Earth package. 
    533584    """ 
    534     X = numpy.asfortranarray(X, dtype=numpy.double) 
    535     Y = numpy.asfortranarray(Y, dtype=numpy.double) 
     585    X = numpy.asfortranarray(X, dtype=ctypes.c_double) 
     586    Y = numpy.asfortranarray(Y, dtype=ctypes.c_double) 
    536587    if Y.ndim == 1: 
    537588        Y = Y.reshape((-1, 1), order="F") 
     
    543594    resp_count = Y.shape[1] 
    544595    cases = X.shape[0] 
    545     subsets = numpy.zeros((var_count, var_count), dtype=numpy.bool, 
     596    subsets = numpy.zeros((var_count, var_count), dtype=ctypes.c_bool, 
    546597                              order="F") 
    547     rss_vec = numpy.zeros((var_count,), dtype=numpy.double, order="F") 
    548     weights = numpy.ones((cases,), dtype=numpy.double, order="F") 
     598    rss_vec = numpy.zeros((var_count,), dtype=ctypes.c_double, order="F") 
     599    weights = numpy.ones((cases,), dtype=ctypes.c_double, order="F") 
    549600     
    550601    _c_eval_subsets_xtx(subsets, rss_vec, cases, resp_count, var_count, 
     
    579630    return used, subsets, rss_vec, gcv_vec 
    580631     
     632"""\ 
     633Variable importance estimation 
     634------------------------------ 
     635""" 
     636 
     637from collections import defaultdict 
     638 
     639def collect_source(vars): 
     640    """ Given a list of variables ``var``, return a mapping from source 
     641    variables (``source_variable`` or ``get_value_from.variable`` members) 
     642    back to the variables in ``vars`` (assumes the default preprocessor in 
     643    EarthLearner). 
     644     
     645    """ 
     646    source = defaultdict(list) 
     647    for var in vars: 
     648        svar = None 
     649        if var.source_variable: 
     650            source[var.source_variable].append(var) 
     651        elif isinstance(var.get_value_from, Orange.core.ClassifierFromVar): 
     652            source[var.get_value_from.variable].append(var) 
     653        elif isinstance(var.get_value_from, Orange.core.ImputeClassifier): 
     654            source[var.get_value_from.classifier_from_var.variable].append(var) 
     655        else: 
     656            source[var].append(var) 
     657    return dict(source) 
     658 
     659def map_to_source_var(var, sources): 
     660    """  
     661    """ 
     662    if var in sources: 
     663        return var 
     664    elif var.source_variable in sources: 
     665        return var.source_variable 
     666    elif isinstance(var.get_value_from, Orange.core.ClassifierFromVar): 
     667        return map_to_source_var(var.get_value_from.variable, sources) 
     668    elif isinstance(var.get_value_from, Orange.core.ImputeClassifier): 
     669        var = var.get_value_from.classifier_from_var.variable 
     670        return map_to_source_var(var, sources) 
     671    else: 
     672        return None 
    581673     
    582674def evimp(model, used_only=True): 
     
    647739    x_axis = axes1.xaxis 
    648740    x_axis.set_ticks(X) 
    649     x_axis.set_ticklabels([a.name for a in attrs], rotation=45) 
     741    x_axis.set_ticklabels([a.name for a in attrs], rotation=90) 
    650742     
    651743    axes1.yaxis.set_label_text("nsubsets") 
     
    655747    axes1.set_title("Variable importance") 
    656748    fig.show() 
     749 
    657750     
    658751def bagged_evimp(classifier, used_only=True): 
     
    673766    assert_type(classifier, BaggedClassifier) 
    674767    bagged_imp = defaultdict(list) 
    675      
     768    attrs_by_name = defaultdict(list) 
    676769    for c in classifier.classifiers: 
    677770        assert_type(c, EarthClassifier) 
    678         imp = evimp(c, used_only=False) 
     771        imp = evimp(c, used_only=used_only) 
    679772        for attr, score in imp: 
    680             bagged_imp[attr].append(score) 
     773            bagged_imp[attr.name].append(score) # map by name 
     774            attrs_by_name[attr.name].append(attr) 
    681775             
    682776    for attr, scores in bagged_imp.items(): 
     
    684778        bagged_imp[attr] = tuple(scores) 
    685779     
     780     
    686781    bagged_imp = sorted(bagged_imp.items(), key=lambda t: (t[1][0],t[1][1]), 
    687                         reverse=True)     
     782                        reverse=True) 
     783     
     784    bagged_imp = [(attrs_by_name[name][0], scores) for name, scores in bagged_imp] 
     785     
    688786    if used_only: 
    689787        bagged_imp = [(a, r) for a, r in bagged_imp if r[0] > 0] 
     
    703801    """ 
    704802    mask = model.label_mask 
    705     r_vars = [v for v, m in zip(model.domain.variables, 
    706                                 model.label_mask) 
    707               if m] 
     803    if model.multi_flag: 
     804        r_vars = [v for v, m in zip(model.domain.variables, 
     805                                    model.label_mask) 
     806                  if m] 
     807    elif is_discrete(model.class_var): 
     808        r_vars = model.expanded_class 
     809    else: 
     810        r_vars = [model.class_var] 
     811         
    708812    r_names = [v.name for v in r_vars] 
    709813    betas = model.betas 
     
    764868from Orange.feature import scoring 
    765869from Orange.misc import _orange__new__ 
    766  
     870             
    767871class ScoreEarthImportance(scoring.Score): 
    768     """ Score features based on there importance in the Earth model using 
     872    """ Score features based on their importance in the Earth model using 
    769873    ``bagged_evimp``'s function return value. 
    770874    """ 
     
    774878    GCV = 2 
    775879     
    776 #    _cache = weakref.WeakKeyDictionary() 
    777880    __new__ = _orange__new__(scoring.Score) 
    778881         
    779     def __init__(self, t=10, score_what="nsubsets", cached=True): 
     882    def __init__(self, t=10, degree=2, terms=10, score_what="nsubsets", cached=True): 
    780883        """ 
    781884        :param t: Number of earth models to train on the data 
     
    788891        """ 
    789892        self.t = t 
     893        self.degree = degree 
     894        self.terms = terms 
    790895        if isinstance(score_what, basestring): 
    791896            score_what = {"nsubsets":self.NSUBSETS, "rss":self.RSS, 
     
    805910        else: 
    806911            from Orange.ensemble.bagging import BaggedLearner 
    807             bc = BaggedLearner(EarthLearner(degree=2, terms=10))(data, weight_id) 
     912            bc = BaggedLearner(EarthLearner(degree=self.degree, 
     913                            terms=self.terms))(data, weight_id) 
    808914            evimp = bagged_evimp(bc, used_only=False) 
    809             self._cache_ref = data #weakref.ref(data) 
     915            self._cache_ref = data 
    810916            self._cached_evimp = evimp 
    811              
     917         
    812918        evimp = dict(evimp) 
    813919        score = evimp.get(attr, None) 
    814920         
    815921        if score is None: 
    816             raise ValueError("Attribute %r not in the domain." % attr) 
     922            source = collect_source(evimp.keys()) 
     923            if attr in source: 
     924                # Return average of source var scores 
     925                return numpy.average([evimp[v][self.score_what] \ 
     926                                      for v in source[attr]]) 
     927            else: 
     928                raise ValueError("Attribute %r not in the domain." % attr) 
    817929        else: 
    818930            return score[self.score_what] 
    819931     
    820932     
    821 class ScoreRSS(scoring.Score): 
    822     __new__ = _orange__new__(scoring.Score) 
    823     def __init__(self): 
    824         self._cache_data = None 
    825         self._cache_rss = None 
    826          
    827     def __call__(self, attr, data, weight_id=None): 
    828         ref = self._cache_data 
    829         if ref is not None and ref is data: 
    830             rss = self._cache_rss 
    831         else: 
    832             x, y = data.to_numpy_MA("1A/c") 
    833             subsets, rss = subsets_selection_xtx_numpy(x, y) 
    834             rss_diff = -numpy.diff(rss) 
    835             rss = numpy.zeros_like(rss) 
    836             for s_size in range(1, subsets.shape[0]): 
    837                 subset = subsets[s_size, :s_size + 1] 
    838                 rss[subset] += rss_diff[s_size - 1] 
    839             rss = rss[1:] #Drop the intercept 
    840             self._cache_data = data 
    841             self._cache_rss = rss 
    842 #        print sorted(zip(rss, data.domain.attributes)) 
    843         index = list(data.domain.attributes).index(attr) 
    844         return rss[index] 
    845          
    846      
     933#class ScoreRSS(scoring.Score): 
     934#    __new__ = _orange__new__(scoring.Score) 
     935#    def __init__(self): 
     936#        self._cache_data = None 
     937#        self._cache_rss = None 
     938#         
     939#    def __call__(self, attr, data, weight_id=None): 
     940#        ref = self._cache_data 
     941#        if ref is not None and ref is data: 
     942#            rss = self._cache_rss 
     943#        else: 
     944#            x, y = data.to_numpy_MA("1A/c") 
     945#            subsets, rss = subsets_selection_xtx_numpy(x, y) 
     946#            rss_diff = -numpy.diff(rss) 
     947#            rss = numpy.zeros_like(rss) 
     948#            for s_size in range(1, subsets.shape[0]): 
     949#                subset = subsets[s_size, :s_size + 1] 
     950#                rss[subset] += rss_diff[s_size - 1] 
     951#            rss = rss[1:] #Drop the intercept 
     952#            self._cache_data = data 
     953#            self._cache_rss = rss 
     954##        print sorted(zip(rss, data.domain.attributes)) 
     955#        index = list(data.domain.attributes).index(attr) 
     956#        return rss[index] 
     957         
     958 
     959#from Orange.core import EarthLearner as BaseEarthLearner, \ 
     960#                        EarthClassifier as BaseEarthClassifier 
    847961#from Orange.misc import member_set 
    848962#  
Note: See TracChangeset for help on using the changeset viewer.