Package mvpa :: Package clfs :: Package libsvmc :: Module _svm
[hide private]
[frames] | no frames]

Source Code for Module mvpa.clfs.libsvmc._svm

  1  # emacs: -*- mode: python; py-indent-offset: 4; indent-tabs-mode: nil -*- 
  2  # vi: set ft=python sts=4 ts=4 sw=4 et: 
  3  ### ### ### ### ### ### ### ### ### ### ### ### ### ### ### ### ### ### ### ## 
  4  # 
  5  #   See COPYING file distributed along with the PyMVPA package for the 
  6  #   copyright and license terms. 
  7  # 
  8  ### ### ### ### ### ### ### ### ### ### ### ### ### ### ### ### ### ### ### ## 
  9  """Python interface to the SWIG-wrapped libsvm""" 
 10   
 11  __docformat__ = 'restructuredtext' 
 12   
 13   
 14  from math import exp, fabs 
 15  import re, copy 
 16   
 17  import numpy as N 
 18   
 19  from mvpa.clfs.libsvmc import _svmc as svmc 
 20  from mvpa.clfs.libsvmc._svmc import C_SVC, NU_SVC, ONE_CLASS, EPSILON_SVR, \ 
 21                                    NU_SVR, LINEAR, POLY, RBF, SIGMOID, \ 
 22                                    PRECOMPUTED 
 23   
 24  if __debug__: 
 25      from mvpa.base import debug 
26 27 -def intArray(seq):
28 size = len(seq) 29 array = svmc.new_int(size) 30 i = 0 31 for item in seq: 32 svmc.int_setitem(array, i, item) 33 i = i + 1 34 return array
35
36 37 -def doubleArray(seq):
38 size = len(seq) 39 array = svmc.new_double(size) 40 i = 0 41 for item in seq: 42 svmc.double_setitem(array, i, item) 43 i = i + 1 44 return array
45
46 47 -def freeIntArray(x):
48 if x != 'NULL' and x != None: 49 svmc.delete_int(x)
50
51 52 -def freeDoubleArray(x):
53 if x != 'NULL' and x != None: 54 svmc.delete_double(x)
55
56 57 -def intArray2List(x, n):
58 return map(svmc.int_getitem, [x]*n, range(n))
59
60 61 -def doubleArray2List(x, n):
62 return map(svmc.double_getitem, [x]*n, range(n))
63
64 65 -class SVMParameter(object):
66 """ 67 SVMParameter class safe to be deepcopied. 68 """ 69 # default values 70 default_parameters = { 71 'svm_type' : C_SVC, 72 'kernel_type' : RBF, 73 'degree' : 3, 74 'gamma' : 0, # 1/k 75 'coef0' : 0, 76 'nu' : 0.5, 77 'cache_size' : 100, 78 'C' : 1, 79 'eps' : 1e-3, 80 'p' : 0.1, 81 'shrinking' : 1, 82 'nr_weight' : 0, 83 'weight_label' : [], 84 'weight' : [], 85 'probability' : 0 86 } 87
88 - class _SVMCParameter(object):
89 """Internal class to to avoid memory leaks returning away svmc's params""" 90
91 - def __init__(self, params):
92 self.param = svmc.new_svm_parameter() 93 for attr, val in params.items(): 94 # adjust val if necessary 95 if attr == 'weight_label': 96 #self.__weight_label_len = len(val) 97 val = intArray(val) 98 # no need? 99 #freeIntArray(self.weight_label) 100 elif attr == 'weight': 101 #self.__weight_len = len(val) 102 val = doubleArray(val) 103 # no need? 104 # freeDoubleArray(self.weight) 105 # set the parameter through corresponding call 106 set_func = getattr(svmc, 'svm_parameter_%s_set' % (attr)) 107 set_func(self.param, val)
108
109 - def __del__(self):
110 if __debug__: 111 debug('CLF_', 'Destroying libsvm._SVMCParameter %s' % str(self)) 112 freeIntArray(svmc.svm_parameter_weight_label_get(self.param)) 113 freeDoubleArray(svmc.svm_parameter_weight_get(self.param)) 114 svmc.delete_svm_parameter(self.param)
115 116
117 - def __init__(self, **kw):
118 self._orig_params = kw 119 self.untrain()
120
121 - def untrain(self):
122 self._params = {} 123 self._params.update(self.default_parameters) # kinda copy.copy ;-) 124 self._params.update(**self._orig_params) # update with new values 125 self.__svmc_params = None # none is computed 126 self.__svmc_recompute = False # thus none to recompute
127
128 - def __repr__(self):
129 return self._params
130
131 - def __str__(self):
132 return "SVMParameter: %s" % `self._params`
133
134 - def __copy__(self):
135 out = SVMParameter() 136 out._params = copy.copy(self._params) 137 return out
138
139 - def __deepcopy__(self, memo):
140 out = SVMParameter() 141 out._params = copy.deepcopy(self._params) 142 return out
143
144 - def _clear_svmc_params(self):
145 if not self.__svmc_params is None: 146 del self.__svmc_params 147 self.__svmc_params = None
148 149 @property
150 - def param(self):
151 if self.__svmc_recompute: 152 self._clear_svmc_params() 153 if self.__svmc_params is None: 154 self.__svmc_params = SVMParameter._SVMCParameter(self._params) 155 self.__svmc_recompute = False 156 return self.__svmc_params.param
157
158 - def __del__(self):
159 if __debug__: 160 debug('CLF_', 'Destroying libsvm.SVMParameter %s' % str(self)) 161 self._clear_svmc_params()
162
163 - def _setParameter(self, key, value):
164 """Not exactly proper one -- if lists are svmc_recompute, would fail anyways""" 165 self.__svmc_recompute = True 166 self._params[key] = value
167 168 @classmethod
169 - def _register_properties(cls):
170 for key in cls.default_parameters.keys(): 171 exec "%s.%s = property(fget=%s, fset=%s)" % \ 172 (cls.__name__, key, 173 "lambda self:self._params['%s']" % key, 174 "lambda self,val:self._setParameter('%s', val)" % key)
175 176 177 SVMParameter._register_properties()
178 179 -def convert2SVMNode(x):
180 """convert a sequence or mapping to an SVMNode array""" 181 import operator 182 183 if type(x) == dict: 184 iter_range = list(x).sort() 185 elif operator.isSequenceType(x): 186 iter_range = range(len(x)) 187 else: 188 raise TypeError, "data must be a mapping or a sequence" 189 190 data = svmc.svm_node_array(len(iter_range)+1) 191 svmc.svm_node_array_set(data, len(iter_range), -1, 0) 192 193 [svmc.svm_node_array_set(data, j, k, x[k]) for j, k in enumerate(iter_range)] 194 return data
195
196 197 198 -class SVMProblem:
199 - def __init__(self, y, x):
200 assert len(y) == len(x) 201 self.prob = prob = svmc.new_svm_problem() 202 self.size = size = len(y) 203 204 self.y_array = y_array = svmc.new_double(size) 205 for i in range(size): 206 svmc.double_setitem(y_array, i, y[i]) 207 208 self.x_matrix = x_matrix = svmc.svm_node_matrix(size) 209 self.data = [] 210 self.maxlen = 0 211 for i in range(size): 212 data = convert2SVMNode(x[i]) 213 self.data.append(data) 214 svmc.svm_node_matrix_set(x_matrix, i, data) 215 if type(x[i]) == dict: 216 if (len(x[i]) > 0): 217 self.maxlen = max(self.maxlen, max(x[i].keys())) 218 else: 219 self.maxlen = max(self.maxlen, len(x[i])) 220 221 svmc.svm_problem_l_set(prob, size) 222 svmc.svm_problem_y_set(prob, y_array) 223 svmc.svm_problem_x_set(prob, x_matrix)
224 225
226 - def __repr__(self):
227 return "<SVMProblem: size = %s>" % (self.size)
228 229
230 - def __del__(self):
231 if __debug__: 232 debug('CLF_', 'Destroying libsvm.SVMProblem %s' % `self`) 233 234 svmc.delete_svm_problem(self.prob) 235 svmc.delete_double(self.y_array) 236 for i in range(self.size): 237 svmc.svm_node_array_destroy(self.data[i]) 238 svmc.svm_node_matrix_destroy(self.x_matrix)
239
240 241 242 -class SVMModel:
243 - def __init__(self, arg1, arg2=None):
244 if arg2 == None: 245 # create model from file 246 filename = arg1 247 self.model = svmc.svm_load_model(filename) 248 else: 249 # create model from problem and parameter 250 prob, param = arg1, arg2 251 self.prob = prob 252 if param.gamma == 0: 253 param.gamma = 1.0/prob.maxlen 254 msg = svmc.svm_check_parameter(prob.prob, param.param) 255 if msg: 256 raise ValueError, msg 257 self.model = svmc.svm_train(prob.prob, param.param) 258 259 #setup some classwide variables 260 self.nr_class = svmc.svm_get_nr_class(self.model) 261 self.svm_type = svmc.svm_get_svm_type(self.model) 262 #create labels(classes) 263 intarr = svmc.new_int(self.nr_class) 264 svmc.svm_get_labels(self.model, intarr) 265 self.labels = intArray2List(intarr, self.nr_class) 266 svmc.delete_int(intarr) 267 #check if valid probability model 268 self.probability = svmc.svm_check_probability_model(self.model)
269 270
271 - def __repr__(self):
272 """ 273 Print string representation of the model or easier comprehension 274 and some statistics 275 """ 276 ret = '<SVMModel:' 277 try: 278 ret += ' type = %s, ' % `self.svm_type` 279 ret += ' number of classes = %d (%s), ' \ 280 % ( self.nr_class, `self.labels` ) 281 except: 282 pass 283 return ret+'>'
284 285
286 - def predict(self, x):
287 data = convert2SVMNode(x) 288 ret = svmc.svm_predict(self.model, data) 289 svmc.svm_node_array_destroy(data) 290 return ret
291 292
293 - def getNRClass(self):
294 return self.nr_class
295 296
297 - def getLabels(self):
298 if self.svm_type == NU_SVR \ 299 or self.svm_type == EPSILON_SVR \ 300 or self.svm_type == ONE_CLASS: 301 raise TypeError, "Unable to get label from a SVR/ONE_CLASS model" 302 return self.labels
303 304 305 #def getParam(self): 306 # return SVMParameter( 307 # svmc_parameter=svmc.svm_model_param_get(self.model)) 308 309
310 - def predictValuesRaw(self, x):
311 #convert x into SVMNode, allocate a double array for return 312 n = self.nr_class*(self.nr_class-1)//2 313 data = convert2SVMNode(x) 314 dblarr = svmc.new_double(n) 315 svmc.svm_predict_values(self.model, data, dblarr) 316 ret = doubleArray2List(dblarr, n) 317 svmc.delete_double(dblarr) 318 svmc.svm_node_array_destroy(data) 319 return ret
320 321
322 - def predictValues(self, x):
323 v = self.predictValuesRaw(x) 324 if self.svm_type == NU_SVR \ 325 or self.svm_type == EPSILON_SVR \ 326 or self.svm_type == ONE_CLASS: 327 return v[0] 328 else: #self.svm_type == C_SVC or self.svm_type == NU_SVC 329 count = 0 330 d = {} 331 for i in range(len(self.labels)): 332 for j in range(i+1, len(self.labels)): 333 d[self.labels[i], self.labels[j]] = v[count] 334 d[self.labels[j], self.labels[i]] = -v[count] 335 count += 1 336 return d
337 338
339 - def predictProbability(self, x):
340 #c code will do nothing on wrong type, so we have to check ourself 341 if self.svm_type == NU_SVR or self.svm_type == EPSILON_SVR: 342 raise TypeError, "call get_svr_probability or get_svr_pdf " \ 343 "for probability output of regression" 344 elif self.svm_type == ONE_CLASS: 345 raise TypeError, "probability not supported yet for one-class " \ 346 "problem" 347 #only C_SVC, NU_SVC goes in 348 if not self.probability: 349 raise TypeError, "model does not support probabiliy estimates" 350 351 #convert x into SVMNode, alloc a double array to receive probabilities 352 data = convert2SVMNode(x) 353 dblarr = svmc.new_double(self.nr_class) 354 pred = svmc.svm_predict_probability(self.model, data, dblarr) 355 pv = doubleArray2List(dblarr, self.nr_class) 356 svmc.delete_double(dblarr) 357 svmc.svm_node_array_destroy(data) 358 p = {} 359 for i in range(len(self.labels)): 360 p[self.labels[i]] = pv[i] 361 return pred, p
362 363
364 - def getSVRProbability(self):
365 #leave the Error checking to svm.cpp code 366 ret = svmc.svm_get_svr_probability(self.model) 367 if ret == 0: 368 raise TypeError, "not a regression model or probability " \ 369 "information not available" 370 return ret
371 372
373 - def getSVRPdf(self):
374 #get_svr_probability will handle error checking 375 sigma = self.getSVRProbability() 376 return lambda z: exp(-fabs(z)/sigma)/(2*sigma)
377 378
379 - def save(self, filename):
380 svmc.svm_save_model(filename, self.model)
381 382
383 - def __del__(self):
384 if __debug__: 385 debug('CLF_', 'Destroying libsvm.SVMModel %s' % (`self`)) 386 387 try: 388 svmc.svm_destroy_model(self.model) 389 except: 390 # blind way to overcome problem of already deleted model and 391 # "SVMModel instance has no attribute 'model'" in ignored 392 pass
393 394
395 - def getTotalNSV(self):
396 return svmc.svm_model_l_get(self.model)
397 398
399 - def getNSV(self):
400 """Returns a list with the number of support vectors per class. 401 """ 402 return [ svmc.int_getitem(svmc.svm_model_nSV_get( self.model ), i) 403 for i in range( self.nr_class ) ]
404 405
406 - def getSV(self):
407 """Returns an array with the all support vectors. 408 409 array( nSV x <nFeatures>) 410 """ 411 return svmc.svm_node_matrix2numpy_array( 412 svmc.svm_model_SV_get(self.model), 413 self.getTotalNSV(), 414 self.prob.maxlen)
415 416
417 - def getSVCoef(self):
418 """Return coefficients for SVs... Needs to be used directly with caution! 419 420 Summary on what is happening in libsvm internals with sv_coef 421 422 svm_model's sv_coef (especially) are "cleverly" packed into a matrix 423 nr_class - 1 x #SVs_total which stores 424 coefficients for 425 nr_class x (nr_class-1) / 2 426 binary classifiers' SV coefficients. 427 428 For classifier i-vs-j 429 General packing rule can be described as: 430 431 i-th row contains sv_coefficients for SVs of class i it took 432 in all i-vs-j or j-vs-i classifiers. 433 434 Another useful excerpt from svm.cpp is 435 436 // classifier (i,j): coefficients with 437 // i are in sv_coef[j-1][nz_start[i]...], 438 // j are in sv_coef[i][nz_start[j]...] 439 440 It can also be described as j-th column lists coefficients for SV # j which 441 belongs to some class C, which it took (if it was an SV, ie != 0) 442 in classifiers i vs C (iff i<C), or C vs i+1 (iff i>C) 443 444 This way no byte of storage is wasted but imho such setup is quite convolved 445 """ 446 return svmc.doubleppcarray2numpy_array( 447 svmc.svm_model_sv_coef_get(self.model), 448 self.nr_class - 1, 449 self.getTotalNSV())
450 451
452 - def getRho(self):
453 """Return constant(s) in decision function(s) (if multi-class)""" 454 return doubleArray2List(svmc.svm_model_rho_get(self.model), 455 self.nr_class * (self.nr_class-1)/2)
456