#!/usr/bin/python """ ***************************************************************************** Copyright (c) 2005, 2008, Uwe Schmitt (http://www.procoders.net) All rights reserved. Redistribution and use in source and binary forms, with or without modification, are permitted provided that the following conditions are met: * Redistributions of source code must retain the above copyright notice, this list of conditions and the following disclaimer. * Redistributions in binary form must reproduce the above copyright notice, this list of conditions and the following disclaimer in the documentation and/or other materials provided with the distribution. * Neither the name of procoders.net nor the names of its contributors may be used to endorse or promote products derived from this software without specific prior written permission. THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. """ WHITE = (240,240,240) LIGHTGREY = (210,210,210) DARKGREY = (120,120,120) BLUE = (150, 150, 255) BLACK = (0,0,0) NEGCOLOR = (200,0,0) POSCOLOR = (0,200,0) from PythonCard import model from PythonCard.components import button, radiogroup, choice, textfield, \ combobox, textarea, staticbox, statictext, bitmapcanvas import wx import Image, ImageDraw, math, random import BmpImagePlugin, FontFile, PngImagePlugin import ImageFont from CSVM import * from numpy import * class MyBackground(model.Background): xmin, xmax = -3.0, 3.0 ymin, ymax = -3.0, 3.0 decisionFunctions = { 'circle' : '2.5-x*x-y*y', 'xor' : 'x*y', 'line' : 'x+y-1', 'stripe' : 'sin(2*x)', 'sine' : 'y-sin(2.5*x)', 'checker1' : 'max(abs(x),abs(y))-1.5', 'checker2' : 'abs(x)+abs(y)-1.5', 'userdefined' : None } kernelData = { #name definition, paramnames default values 'linear' : ('k(x,y) = ', None, None, "", "" ), 'polynomial': ('k(x,y) = (c + )^d, c>0', 'c','d', "1", "2.0"), 'radial' : ('k(x,y) = exp( -c * ||x-y||_2 )', 'c',None, ".005","") } def on_initialize(self, event): self.rect = (self.xmin, self.xmax, self.ymin, self.ymax) w,h = self.components.bitmap.size # functions for scaling real coordinates to bitmap coordinates # xmin, xmax, ymin, ymax = map(float, self.rect) self.xscal = lambda x: self.xmin + (self.xmax-self.xmin)/float(w-1) * x self.yscal = lambda y: self.ymin + (self.ymax-self.ymin)/float(h-1) \ * (h-1-y) self.xrescal = lambda x: (x-self.xmin)/(self.xmax-self.xmin)* float(w-1) self.yrescal = lambda y: h-1-(y-self.ymin)/(self.ymax-self.ymin)\ * float(h-1) self.initProblem() # generate fresh bitmap and clear class problem self.freshUp() # draw bitmapt to gui self.drawMode = 0 # draw positive examples stdProb = "circle" self.components.examplefun.stringSelection=stdProb self.actualFun = self.decisionFunctions[stdProb] self.components.lambdafield.text = self.actualFun or "" stdKernel = "radial" self.components.kernelfun.stringSelection = stdKernel self.setKernelParams(stdKernel) self.components.cp.text="99999.0" self.components.cn.text="" def setKernelParams(self, kernel): data = self.kernelData.get(kernel) self.components.kernelinfo.text = data[0] self.components.par1label.text = data[1] or "" self.components.par2label.text = data[2] or "" self.components.p1.enabled = data[1] is not None self.components.p2.enabled = data[2] is not None self.components.p1.text = data[3] self.components.p2.text = data[4] def freshUp(self): """ draw bitmap to gui """ self.components.bitmap.drawBitmap(self.img) def initProblem(self): """ generate fresh bitmap and clear classification problem """ self.drawBackground() # initialize examples list self.posList = [] self.negList = [] self.components.draw.enabled = 0 def drawBackground(self, klass=None): # background of canvas w,h = self.components.bitmap.size img = Image.new("RGB", (w,h), LIGHTGREY) if klass: xr = linspace(self.xmin, self.xmax, w) yr = linspace(self.ymax, self.ymin, h) # build mat as (set-)crossproduct of xr and yr xxx, yyy = meshgrid(xr, yr) mat = array(zip(xxx.flatten(), yyy.flatten())) # apply svm labels = klass.classify_matrix(mat) # map values -1,1 to 0, 255 labels[labels==-1]=0 labels *= 255 labels.shape = (h,w) # create mask mask = Image.fromarray(labels) grey = img black = Image.new("RGB", (w,h), BLUE) img = Image.composite(black, grey, mask) self.img = img dr = ImageDraw.Draw(self.img) # draw axes x0, xmin, xmax = map(self.xrescal, (0, self.xmin, self.xmax)) y0, ymin, ymax = map(self.yrescal, (0, self.ymin, self.ymax)) dr.line((xmin, y0, xmax, y0), fill=BLACK) dr.line((x0, ymin, x0, ymax), fill=BLACK) # draw labels txt1 = "%+.1f" % self.ymax; txt2 = "%+.1f" % self.xmax txt3 = "%+.1f" % self.ymin; txt4 = "%+.1f" % self.xmin (w1, h1), (w2,h2) = dr.textsize(txt1), dr.textsize(txt2) (w3, h3), (w4,h4) = dr.textsize(txt3), dr.textsize(txt4) dr.text((x0-w1, ymax), txt1, fill=BLACK) dr.text((xmax-w2, y0), txt2, fill=BLACK) dr.text((xmin, y0), txt3, fill=BLACK) dr.text((x0, ymin-h4), txt4, fill=BLACK) del dr def on_drawmode_select(self, event): """ toggle drawing of positive and negative examples """ self.drawMode = event.GetSelection() def on_bitmap_mouseDown(self, event): """ draw to bitmap and record example """ # draw point with appropriate color x,y = event.x, event.y self.drawPoint(x,y, self.drawMode == 0 and 1 or -1) self.freshUp() # record example xs, ys = self.xscal(x), self.yscal(y) [self.posList, self.negList][self.drawMode].append((xs,ys)) def drawPoint(self, x,y, label): dr=ImageDraw.Draw(self.img) ps = int(math.ceil(min(*self.img.size)/150.0)) # pointsize color = label>0 and POSCOLOR or NEGCOLOR dr.ellipse((x-ps, y-ps, x+ps,y+ps), fill=color) del dr def drawRetangle(self, x,y, label): dr=ImageDraw.Draw(self.img) ps = int(2*math.ceil(min(*self.img.size)/150.0)) # pointsize #color = label>0 and POSCOLOR or NEGCOLOR dr.rectangle((x-ps, y-ps, x+ps,y+ps), outline=0) del dr def on_clear_mouseDown(self, event): """ clear problem an generate new and fresh bitmap """ self.initProblem() self.freshUp() def on_examplefun_select(self, event): sel=event.GetSelection() fname = self.components.examplefun.items[sel] self.actualFun=self.decisionFunctions[fname] lambdafield = self.components.lambdafield lambdafield.enabled = (self.actualFun is None) lambdafield.text = self.actualFun or "" def on_generate_mouseDown(self, event): """ generate examples """ try: num = int(self.components.exnumber.text) except: return try: margin = float(self.components.margin.text) except: return ran=random.random fun = self.actualFun or self.components.lambdafield.text for i in range(num): # counting avoids locking due to unreasonable large # margin count = 0 while count<1000: count += 1 x = ran()*(self.xmax-self.xmin)+self.xmin y = ran()*(self.ymax-self.ymin)+self.ymin try: klass = eval(fun, math.__dict__, locals()) except: return if abs(klass)>margin: break else: return if klass>0: self.posList.append((x,y)) else: self.negList.append((x,y)) xs, ys = self.xrescal(x), self.yrescal(y) self.drawPoint(xs, ys, klass) self.freshUp() def svminfo(self, txt): self.components.svminfo.text = txt # dirty trick to force refresh: self.components.svminfo.appendText("\n"*10) self.components.svminfo.setInsertionPoint(0) self.components.svminfo.Refresh() def getFloat(self, attribute, allowEmpty= False ): attribute=str(attribute).strip() if allowEmpty: if attribute == "": return attribute try: val = float(attribute) return val except: return None def on_classify_mouseDown(self, event): if not len(self.posList): self.svminfo("no postive examples given") return if not len(self.negList): self.svminfo("no negative examples given") return kernelName = self.components.kernelfun.stringSelection data=self.kernelData[kernelName] p1, p2 = None, None if data[1] is not None: p1 = self.getFloat(self.components.p1.text) if p1 is None: self.svminfo("parameter %s invalid" % data[1]) return if data[2] is not None: p2 = self.getFloat(self.components.p2.text) if p2 is None: self.svminfo("parameter %s invalid" % data[2]) return cp = self.getFloat(self.components.cp.text, allowEmpty=False) if cp is None or cp<0.0: self.svminfo("parameter cp is invalid") return cn = self.getFloat(self.components.cn.text, allowEmpty=True) if cn=="": cn = cp elif cn is None or cn<0.0: self.svminfo("parameter cn is invalid") return self.svminfo("start training...") wx.SetCursor(wx.StockCursor(wx.CURSOR_WAIT)) # create and configure kernel # (this statement is the analogue to C/C++ switch statement) kernel={ 'linear': LinearKernel(), 'polynomial':PolynomialKernel(coef0=p1, degree=p2, gamma=1.0), 'radial': RadialKernel(gamma=p1), }[kernelName] machine = CSVM(kernel, Cp=cp, Cm=cn, verbose=1) mat_good = asarray(self.posList) mat_bad = asarray(self.negList) machine.learn(mat_good, mat_bad) num_sv = machine.get_num_sv() txt= "iterations : %d\n" % machine.get_num_iter() txt+="#support vectors : %d\n" % num_sv ii = range(num_sv) self.alphas = [ machine.get_alpha(i) for i in ii ] self.svs = [ machine.get_sv(i) for i in ii ] self.machine = machine self.svminfo(txt) wx.SetCursor(wx.NullCursor) self.components.draw.enabled = 1 def on_draw_mouseDown(self, event): wx.SetCursor(wx.StockCursor(wx.CURSOR_WAIT)) machine = self.machine self.drawBackground(klass=machine) for x,y in self.posList: self.drawPoint(self.xrescal(x),self.yrescal(y), 1) for x,y in self.negList: self.drawPoint(self.xrescal(x),self.yrescal(y), -1) for vec, w in zip(self.svs, self.alphas): x,y = self.xrescal(vec[0]), self.yrescal(vec[1]) self.drawRetangle(x,y, w) self.freshUp() wx.SetCursor(wx.NullCursor) def on_kernelfun_select(self, event): kernel = event.target.GetStringSelection() self.setKernelParams(kernel) if __name__ == '__main__': app = model.Application(MyBackground) app.MainLoop()