source: orange/orange/orngLinVis.py @ 6538:a5f65d7f0b2c

Revision 6538:a5f65d7f0b2c, 22.4 KB checked in by Mitar <Mitar@…>, 4 years ago (diff)

Made XPM version of the icon 32x32.

Line 
1#
2# Module Orange Linear Model Visualization
3# ----------------------------------------
4#
5# CVS Status: $Id$
6#
7# Author: Aleks Jakulin (jakulin@acm.org)
8# (Copyright (C)2004 Aleks Jakulin)
9#
10# Purpose: Visualize all linear models (NB, LogReg, linear SVM, perceptron, etc.).
11#
12# ChangeLog:
13#   - 2004/05/29: no more normalization, no more flipping for SVM, corrected w/r continuous attributes
14#   - 2003/11/17: project initiated
15
16import orange, orngDimRed
17import math, numpy
18import numpy.linalg as LinearAlgebra
19
20
21def _treshold(x):
22    if x > 0.0:
23        return 1.0
24    elif x < 0.0:
25        return 0.0
26    return 0.5
27
28def _avg(set):
29    assert(len(set)>0)
30    return float(sum(set))/len(set)
31
32class _parse:
33    def bucketize(self, examples, attribute, buckets, getbuckets = 0):
34        assert(attribute.varType == 2) # continuous
35        l = [float(x[attribute]) for x in examples]
36        l.sort()
37        m = len(l)/buckets
38        if m == 0:
39            buckets = 1
40        sets = [l[:m]]
41        for x in range(1,buckets):
42            sets.append(l[m*x:m*(x+1)])
43        values = [_avg(x) for x in sets]
44        if getbuckets:
45            return (values,sets)
46        else:
47            return values
48
49    def quantize(self,values,x):
50        pv = values[0]
51        pi = 0
52        for k in xrange(1,len(values)):
53            if x <= values[k]:
54                if values[k]-x < x-pv:
55                    return k
56                return pi
57            pv = values[k]
58            pi = k
59        return len(values)-1
60
61    def __init__(self):
62        pass
63
64class _parseNB(_parse):
65    def _safeRatio(self,a,b):
66        if a*10000.0 < b:
67            return -10
68        elif b*10000.0 < a:
69            return 10
70        else:
71            return math.log(a)-math.log(b)
72
73    def __call__(self,classifier,examples, buckets):
74        # todo todo - support for loess
75        for i in xrange(len(examples.domain.attributes)):
76            for j in xrange(len(examples)):
77                if examples[j][i].isSpecial():
78                    raise "A missing value found in instance %d, attribute %s. Missing values are not allowed."%(j,examples.domain.attributes[i].name)
79
80        beta = -self._safeRatio(classifier.distribution[1],classifier.distribution[0])
81        coeffs = []
82        coeff_names = []
83        offsets = []
84        setss = []
85        runi = 0
86        transfvalues = [] # true conditional probability for each continuous value that appears in the data
87        for i in range(len(classifier.domain.attributes)):
88            tc = ["%s"%classifier.domain.attributes[i].name]
89            offsets.append(runi)
90            LUT = {}
91            if classifier.domain.attributes[i].varType == 1:
92                # discrete attribute
93                for j in range(len(classifier.domain.attributes[i].values)):
94                    tc.append('%s'%(classifier.domain.attributes[i].values[j]))
95                    p1 = classifier.conditionalDistributions[i][j][1]
96                    p0 = classifier.conditionalDistributions[i][j][0]
97                    coeffs.append(self._safeRatio(p1,p0)+beta)
98                    runi += 1
99                setss.append(0)
100            elif classifier.domain.attributes[i].varType == 2:
101                # continuous attribute
102                (val,sets) = self.bucketize(examples,classifier.domain.attributes[i],buckets,1)
103                p1 = 0.0
104                p0 = 0.0
105                # fill in the sum of probabilities, finding the nearest probability
106                l = 0
107                pp = -1e200
108                pv = -1e200
109                mm = classifier.conditionalDistributions[i].items()
110                for j in xrange(len(val)):
111#                    print sets[j][0],sets[j][-1],l
112                    tc.append(val[j])
113                    k = 0
114                    while k < len(sets[j]) and l < len(mm):
115                        # find the two nearest points to the given example value
116                        if l < len(mm):
117                            while mm[l][0] <= sets[j][k]:
118                                pv = mm[l][0]
119                                pp = mm[l][1]
120                                l += 1
121                                if l >= len(mm):
122                                    break
123                        # mark example values that are closer to the previous point
124                        if k < len(sets[j]) and l < len(mm):
125                            while sets[j][k]-pv <= mm[l][0]-sets[j][k]:
126                                LUT[sets[j][k]] = self._safeRatio(pp[1],pp[0])+beta
127                                p1 += pp[1]
128                                p0 += pp[0]
129                                k += 1
130                                if k >= len(sets[j]):
131                                    break
132                    while k < len(sets[j]):
133                        LUT[sets[j][k]] = self._safeRatio(pp[1],pp[0])+beta
134                        p1 += pp[1]
135                        p0 += pp[0]
136                        k += 1
137                    #print l, k, pv, mm[l][0], sets[j][k], len(mm), len(sets[j])
138                    coeffs.append(self._safeRatio(p1,p0)+beta)
139                    runi += 1
140                setss.append(sets)
141            else:
142                raise "unknown attribute type"
143            coeff_names.append(tc)
144            transfvalues.append(LUT)
145
146
147        # create the basis vectors for each attribute
148        basis = numpy.identity((len(coeffs)), numpy.float)
149        for j in range(len(classifier.domain.attributes)):
150            if classifier.domain.attributes[j].varType == 2:
151                for k in xrange(1,len(coeff_names[j])):
152                    i = offsets[j]+k-1
153                    basis[i][i] = coeff_names[j][k]
154
155        # create the example matrix (only attributes)
156        m = numpy.zeros((len(examples),len(coeffs)), numpy.float)
157        for i in range(len(examples)):
158            for j in range(len(classifier.domain.attributes)):
159                if classifier.domain.attributes[j].varType == 1:
160                    for k in coeff_names[j][1:]:
161                        if not examples[i][classifier.domain.attributes[j]].isSpecial():
162                            try:
163                                m[i][offsets[j]+int(examples[i][classifier.domain.attributes[j]])] = 1.0
164                            except:
165                                print "**",examples[i][classifier.domain.attributes[j]]
166                else:
167                    # quantize
168                    if not examples[i][classifier.domain.attributes[j]].isSpecial():
169                        cv = float(examples[i][classifier.domain.attributes[j]]) # obtain the attribute value
170                    else:
171                        cv = 0.0
172                    k = self.quantize(coeff_names[j][1:],cv) # obtain the right bucket
173                    tm = transfvalues[j][cv]  # true margin
174                    mu = coeffs[offsets[j]+k] # multiplier for the bucket
175                    if abs(mu)>1e-6:
176                        ac = tm/mu
177                    else:
178                        ac = tm
179                    m[i][offsets[j]+k] = ac
180
181        return (beta, coeffs, coeff_names, basis, m, lambda x:math.exp(x)/(1.0+math.exp(x)))
182
183class _parseLR(_parse):
184    def getDescriptors(self, translator, examples, buckets):
185        tcoeff_names = []
186        descriptors = [] # used for managing continuous atts
187        proto_example = orange.Example(examples[0]) # used for converting bucket averages into realistic values
188        true_values = []
189        for i in range(len(translator.trans)):
190            t = translator.trans[i]
191            tc = ["%s"%t.attr.name]
192            tv = []
193            d = t.description()
194            if d[0]==0:
195                # continuous
196                values = self.bucketize(examples, t.attr, buckets)
197                tc += values
198                descriptors.append((i,-1))
199                for v in values:
200                    proto_example[t.attr] = v
201                    tp = translator.extransform(proto_example)
202                    tv.append(tp[t.idx])
203            else:
204                # nominal
205                x = 0
206                for n in d[2]:
207                    if n!='':
208                        tc.append(n)
209                        descriptors.append((i,x))
210                        x += 1
211            true_values.append(tv)
212            tcoeff_names.append(tc)
213        return descriptors, tcoeff_names, true_values
214
215    def getNames(self, descriptors, tcoeff_names, true_values):
216        # filter the coeff_names using these masked descriptors
217        coeff_names = []
218        cur_i = -1
219        contins = []
220        tr_values = []
221        total = 0
222        for (a,b) in descriptors:
223            if cur_i == a:
224                coeff_names[-1].append(tcoeff_names[a][b+1])
225                total += 1
226            else:
227                tr_values.append(true_values[a])
228                if b == -1:
229                    # continuous
230                    contins.append(len(coeff_names))
231                    coeff_names.append(tcoeff_names[a])
232                    total += len(coeff_names[-1]) - 1
233                else:
234                    coeff_names.append([tcoeff_names[a][0],tcoeff_names[a][1+b]])
235                    total += 1
236                    cur_i = a
237        return coeff_names,total,contins,tr_values
238
239    def getBasis(self, total,xcoeffs,contins, coeff_names, tr_values):
240        # create the basis vectors for each attribute
241        basis = numpy.identity((total), numpy.float)
242
243        # fix up the continuous attributes in coeffs (duplicate) and in basis (1.0 -> value[i])
244        x = numpy.ones((total,), numpy.float)
245        coeffs = []
246        lookup = []
247        conti = 0
248        j = 0
249        jj = 0
250        contins.append(-1) # sentry
251        nlookup = []
252        for i in xrange(len(coeff_names)):
253            nlookup.append(len(coeffs))
254            if i==contins[conti]:
255                # continuous
256                conti += 1
257                lookup.append(len(coeffs))
258                for k in xrange(len(coeff_names[i])-1):
259                    coeffs.append(xcoeffs[jj])
260                    v = tr_values[i][k]
261                    x[j] = v
262                    j += 1
263                jj += 1
264            else:
265                # discrete
266                for k in xrange(len(coeff_names[i])-1):
267                    lookup.append(len(coeffs))
268                    coeffs.append(xcoeffs[jj])
269                    j += 1
270                    jj += 1
271        basis *= x
272        return (basis,lookup,nlookup,coeffs)
273
274    def getExamples(self,exx,tex,dim,classifier,lookup,nlookup,contins, coeff_names):
275        # create the example matrix (only attributes)
276        m = numpy.zeros((len(tex),dim), numpy.float)
277        for j in xrange(len(tex)):
278            tv = tex[j]
279            # do the insertion and quantization
280            # copy all coefficients
281            for i in xrange(len(lookup)):
282                try:
283                    m[j][lookup[i]] = tv[i]
284                except:
285                    m[j][lookup[i]] = 0 # missing value
286
287            # quantize the continuous attributes
288            for i in contins[:-1]:
289                # obtain the marker
290                vals = coeff_names[i][1:]
291                x = float(exx[j][coeff_names[i][0]]) # get the untransformed value of the attribute
292                newi = self.quantize(vals,x)
293                # move the value to the right bucket
294                v = m[j][nlookup[i]]
295                m[j][nlookup[i]] = 0.0
296                m[j][nlookup[i]+newi] = v
297        return m
298
299    def __call__(self,classifier,examples, buckets):
300        # skip domain translation and masking
301        robustc = classifier.classifier
302        primitivec = robustc.classifier
303        beta = -primitivec.beta[0]
304
305        (descriptors,prevnames,trans_values) = self.getDescriptors(classifier.translator,examples,buckets)
306
307        # include robust LR's masking
308        descriptors = robustc.translate(descriptors)
309
310        (coeff_names, total, contins, tr_values) = self.getNames(descriptors, prevnames, trans_values)
311
312        xcoeffs = primitivec.beta[1:]
313
314        (basis,lookup,nlookup,coeffs) = self.getBasis(total, xcoeffs, contins, coeff_names, tr_values)
315
316        tex = []
317        for ex in examples:
318            tex.append(robustc.translate(classifier.translator.extransform(ex)))
319
320        m = self.getExamples(examples,tex,len(basis),classifier,lookup,nlookup,contins, coeff_names)
321
322        return (beta, coeffs, coeff_names, basis, m, lambda x:math.exp(x)/(1.0+math.exp(x)))
323
324
325class _parseSVM(_parseLR):
326    def __call__(self,classifier,examples, buckets):
327        if classifier.model['kernel_type'] != 0:
328            raise "Use SVM with a linear kernel."
329        if classifier.model["svm_type"] != 0:
330            raise "Use ordinary SVM classification."
331        if classifier.model["nr_class"] != 2:
332            raise "This is not SVM with a binary class."
333
334        (descriptors,prevnames,trans_values) = self.getDescriptors(classifier.translate,examples,buckets)
335
336        (coeff_names, total, contins, tr_values) = self.getNames(descriptors, prevnames, trans_values)
337
338        beta = classifier.beta
339        xcoeffs = classifier.xcoeffs
340
341        (basis,lookup,nlookup,coeffs) = self.getBasis(total, xcoeffs, contins, coeff_names, tr_values)
342
343        tex = []
344        for i in range(len(examples)):
345            tex.append(classifier.translate.extransform(examples[i]))
346
347        m = self.getExamples(examples,tex,len(basis),classifier,lookup,nlookup,contins, coeff_names)
348
349        return (beta, coeffs, coeff_names, basis, m, _treshold)
350
351
352class _marginConverter:
353    def __init__(self,coeff,estdomain,estimator):
354        self.coeff = coeff
355        self.estdomain = estdomain
356        self.cv = self.estdomain.classVar(0)
357        self.estimator = estimator
358
359    def __call__(self, r):
360        ex = orange.Example(self.estdomain,[r*self.coeff,self.cv]) # need a dummy class value
361        p = self.estimator(ex,orange.GetProbabilities)
362        return p[1]
363
364class _parseMargin(_parse):
365    def __init__(self,marginc,parser):
366        self.parser = parser
367        self.marginc = marginc
368
369    def __call__(self,classifier,examples, buckets):
370        (beta, coeffs, coeff_names, basis, m, _probfunc) = self.parser(classifier.classifier,examples, buckets)
371        return (beta, coeffs, coeff_names, basis, m, _marginConverter(self.marginc.coeff, self.marginc.estdomain, self.marginc.estimator))
372
373
374class Visualizer:
375    def findParser(self, classifier):
376        if type(classifier)==orange.BayesClassifier:
377             return _parseNB()
378        else:
379            try:
380                name = classifier._name
381                if name == 'MarginMetaClassifier':
382                    return _parseMargin(classifier,self.findParser(classifier.classifier))
383                if name == 'SVM Classifier Wrap':
384                    return _parseSVM()
385                elif name == 'Basic Logistic Classifier':
386                    return _parseLR()
387                else:
388                    raise ""
389            except:
390                raise "Unrecognized classifier: %s"%classifier
391
392    def __init__(self, examples, classifier, dimensions = 2, buckets = 3, getpies = 0, getexamples = 1):
393        # error detection
394        if len(examples.domain.classVar.values) != 2:
395            raise "The domain does not have a binary class. Binary class is required."
396
397        all_attributes = [i for i in examples.domain.attributes]+[examples.domain.classVar]
398
399        # acquire the linear model
400        parser = self.findParser(classifier)
401
402        (beta, coeffs, coeff_names, basis, m, probfunc) = parser(classifier,examples, buckets)
403        #print "examples:"
404        #print m
405        #print "basis:"
406        #print basis
407        self.basis = basis
408        self.m = m
409
410        # get the parameters of the hyperplane, and normalize it
411        n = numpy.array(coeffs, numpy.float)
412        #length = numpy.sqrt(numpy.dot(n,n))
413        #ilength = 1.0/length
414        #n *= ilength
415        #beta = ilength*xbeta
416        #self.probfunc = lambda x:probfunc(x*length)
417        self.probfunc = lambda x:probfunc(x)
418
419        if getexamples or getpies or dimensions > 1:
420            # project the example matrix on the separating hyperplane, removing the displacement
421            h_dist = numpy.dot(m,n)
422            h_proj = m - numpy.dot(numpy.reshape(h_dist,(len(examples),1)),numpy.reshape(n,(1,len(coeffs))))/numpy.dot(n,n)
423            h_dist -= beta # correct distance
424
425        # perform classification for all examples
426        if getpies:
427            self.pies = []
428            for j in range(len(examples)):
429                p1 = self.probfunc(h_dist[j])
430                p0 = 1-p1
431                t = []
432                # calculate for all coefficients, prior (beta) is last
433                projj = m[j]*n # projection in the space defined by the basis and the hyperplane
434                tn = numpy.concatenate((projj,[beta-h_dist[j]]))
435                evidence0 = -numpy.clip(tn,-1e200,0.0)
436                evidence1 = numpy.clip(tn,0.0,1e200)
437                # evidence for label 0
438                evidence0 *= p0/max(numpy.sum(evidence0),1e-6)
439                # evidence for label 1
440                evidence1 *= p1/max(numpy.sum(evidence1),1e-6)
441                self.pies.append((evidence0,evidence1,projj))
442
443        basis_dist = numpy.dot(basis,n)
444        basis_proj = basis - numpy.dot(numpy.reshape(basis_dist,(len(coeffs),1)),numpy.reshape(n,(1,len(coeffs))))/numpy.dot(n,n)
445        basis_dist -= beta
446
447        # coordinates of the basis vectors in the visualization
448        self.basis_c = [[basis_dist[i]] for i in range(len(coeffs))]
449
450        self.coeff_names = coeff_names
451        self.coeffs = coeffs
452        self.beta = beta
453
454        if getexamples:
455            # coordinates of examples in the visualization
456            self.example_c = [[h_dist[i]] for i in range(len(examples))]
457
458        if dimensions > 1:
459            # perform standardization of attributes; the pa
460            for i in xrange(len(coeffs)):
461                # standardize the examples and return parameters
462                (h_proj[:,i],transf) = orngDimRed.VarianceScaling(h_proj[:,i])
463                # now transform the basis and the examples with the same parameters, so that nothing changes
464                (basis_proj[:,i],transf) = orngDimRed.VarianceScaling(basis_proj[:,i],transf)
465                #(m[:,i],transf) = orngDimRed.VarianceScaling(m[:,i],transf)
466
467            # obtain the second dimension using PCA
468            pca = orngDimRed.PCA(h_proj,dimensions-1)
469            # now transform the basis using the same matrix (need N)
470            # U' = U*D*V
471            # N' * V^-1 * D^-1 = N
472            # N = N' * (D*V)^-1
473            DV = numpy.dot(numpy.identity(len(coeffs),numpy.float)*numpy.clip(pca.variance,1e-6,1e6),pca.factors)
474            nbasis = numpy.dot(basis_proj,LinearAlgebra.inv(DV))
475
476            for j in range(dimensions-1):
477                for i in range(len(coeffs)):
478                    self.basis_c[i].append(nbasis[i][j])
479            if getexamples:
480                for j in range(dimensions-1):
481                    for i in range(len(examples)):
482                        self.example_c[i].append(pca.loading[i][j])
483
484
485if __name__== "__main__":
486    import orngLR_Jakulin, orngSVM, orngMultiClass
487
488    def printmodel(t,c,printexamples=1):
489        m = Visualizer(t,c,buckets=3,getpies=1)
490        print "Linear model:"
491        print t.domain.classVar.name,':',m.beta
492        j = 0
493        for i in range(len(m.coeff_names)):
494            print m.coeff_names[i][0],':'
495            for x in m.coeff_names[i][1:]:
496                print '\t',x,':',m.coeffs[j]
497                j += 1
498
499        print "\nbasis vectors:"
500        j = 0
501        for i in range(len(m.coeff_names)):
502            print m.coeff_names[i][0],':'
503            for x in m.coeff_names[i][1:]:
504                print '\t',x,':',m.basis_c[j]
505                j += 1
506
507        if printexamples:
508            print "\nexamples:"
509            for i in range(len(t)):
510                print t[i],'->',m.example_c[i], c(t[i],orange.GetBoth), m.probfunc(m.example_c[i][0])
511
512        print "\nprobability:"
513        print "-0.5:",m.probfunc(-0.5)
514        print " 0.0:",m.probfunc(0.0)
515        print "+0.5:",m.probfunc(+0.5)
516
517        idx = 0
518        print "\npie for example",idx,':',t[idx]
519        (e0,e1,vector) = m.pies[idx]
520        def printpie(e,p):
521            x = 0
522            for i in range(len(m.coeff_names)):
523                for j in m.coeff_names[i][1:]:
524                    if e[x] > 0.001:
525                        print '\t%2.1f%% : '%(100*e[x]),m.coeff_names[i][0],'=',
526                        if type(j)==type(1.0):
527                            print t[idx][m.coeff_names[i][0]] # continuous
528                        else:
529                            print j # discrete
530                    x += 1
531            if e[x] > 0.001:
532                print '\t%2.1f%% : '%(100*e[x]),"BASELINE"
533        print "Attributes in favor of %s = %s [%f]"%(t.domain.classVar.name,t.domain.classVar.values[0],1-m.probfunc(m.example_c[idx][0]))
534        printpie(e0,1-m.probfunc(m.example_c[idx][0]))
535        print "Attributes in favor of %s = %s [%f]"%(t.domain.classVar.name,t.domain.classVar.values[1],m.probfunc(m.example_c[idx][0]))
536        printpie(e1,m.probfunc(m.example_c[idx][0]))
537
538        print "\nProjection of the example in the basis space:"
539        j = 0
540        for i in range(len(m.coeff_names)):
541            print m.coeff_names[i][0],':'
542            for x in m.coeff_names[i][1:]:
543                print '\t',x,'=',vector[j]
544                j += 1
545        print "beta:",-m.beta
546
547    #t = orange.ExampleTable('c:/proj/domains/voting.tab') # discrete
548    t = orange.ExampleTable(r"E:\Development\Orange Datasets\UCI\shuttle.tab" ) # discrete
549
550    #t = orange.ExampleTable('c_cmc.tab') # continuous
551
552    print "NAIVE BAYES"
553    print "==========="
554    bl = orange.BayesLearner()
555    bl.estimatorConstructor = orange.ProbabilityEstimatorConstructor_Laplace()
556    # prevent too many estimation points
557    # increase the smoothing level
558    bl.conditionalEstimatorConstructorContinuous = orange.ConditionalProbabilityEstimatorConstructor_loess(windowProportion=0.5,nPoints = 10)
559    c = bl(t)
560    printmodel(t,c,printexamples=0)
561
562    print "\n\nLOGISTIC REGRESSION"
563    print     "==================="
564    c = orngLR_Jakulin.BasicLogisticLearner()(t)
565    printmodel(t,c,printexamples=0)
566
567    print "\n\nLINEAR SVM"
568    print     "=========="
569    l = orngSVM.BasicSVMLearner()
570    l.kernel = 0 # linear SVM
571    l.for_nomogram = 1
572    c = l(t)
573    printmodel(t,c,printexamples=0)
574
575    print "\n\nMARGIN SVM"
576    print     "=========="
577    l = orngSVM.BasicSVMLearner()
578    l.kernel = 0 # linear SVM
579    l.for_nomogram = 1
580    c = orngLR_Jakulin.MarginMetaLearner(l,folds = 1)(t)
581    printmodel(t,c,printexamples=0)
Note: See TracBrowser for help on using the repository browser.