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 length = len(x) 184 185 # make two lists, one of indices, one of values 186 # YYY Use isinstance instead of type...is so we could 187 # easily use derived subclasses 188 if isinstance(x, dict): 189 iter_range = list(x).sort() 190 iter_values = N.ndarray(x.values()) 191 elif isinstance(x, N.ndarray): 192 iter_range = range(length) 193 iter_values = x 194 elif operator.isSequenceType(x): 195 iter_range = range(length) 196 iter_values = N.asarray(x) 197 else: 198 raise TypeError, "data must be a mapping or an ndarray or a sequence" 199 200 # allocate c struct 201 data = svmc.svm_node_array(length + 1) 202 # insert markers into the c struct 203 svmc.svm_node_array_set(data, length, -1, 0.0) 204 # pass the list and the ndarray to the c struct 205 svmc.svm_node_array_set(data, iter_range, iter_values) 206 207 return data
208
209 210 211 -class SVMProblem:
212 - def __init__(self, y, x):
213 assert len(y) == len(x) 214 self.prob = prob = svmc.new_svm_problem() 215 self.size = size = len(y) 216 217 self.y_array = y_array = svmc.new_double(size) 218 for i in range(size): 219 svmc.double_setitem(y_array, i, y[i]) 220 221 self.x_matrix = x_matrix = svmc.svm_node_matrix(size) 222 self.data = [] 223 self.maxlen = 0 224 for i in range(size): 225 data = convert2SVMNode(x[i]) 226 self.data.append(data) 227 svmc.svm_node_matrix_set(x_matrix, i, data) 228 if type(x[i]) == dict: 229 if (len(x[i]) > 0): 230 self.maxlen = max(self.maxlen, max(x[i].keys())) 231 else: 232 self.maxlen = max(self.maxlen, len(x[i])) 233 234 svmc.svm_problem_l_set(prob, size) 235 svmc.svm_problem_y_set(prob, y_array) 236 svmc.svm_problem_x_set(prob, x_matrix)
237 238
239 - def __repr__(self):
240 return "<SVMProblem: size = %s>" % (self.size)
241 242
243 - def __del__(self):
244 if __debug__: 245 debug('CLF_', 'Destroying libsvm.SVMProblem %s' % `self`) 246 247 svmc.delete_svm_problem(self.prob) 248 svmc.delete_double(self.y_array) 249 for i in range(self.size): 250 svmc.svm_node_array_destroy(self.data[i]) 251 svmc.svm_node_matrix_destroy(self.x_matrix)
252
253 254 255 -class SVMModel:
256 - def __init__(self, arg1, arg2=None):
257 if arg2 == None: 258 # create model from file 259 filename = arg1 260 self.model = svmc.svm_load_model(filename) 261 else: 262 # create model from problem and parameter 263 prob, param = arg1, arg2 264 self.prob = prob 265 if param.gamma == 0: 266 param.gamma = 1.0/prob.maxlen 267 msg = svmc.svm_check_parameter(prob.prob, param.param) 268 if msg: 269 raise ValueError, msg 270 self.model = svmc.svm_train(prob.prob, param.param) 271 272 #setup some classwide variables 273 self.nr_class = svmc.svm_get_nr_class(self.model) 274 self.svm_type = svmc.svm_get_svm_type(self.model) 275 #create labels(classes) 276 intarr = svmc.new_int(self.nr_class) 277 svmc.svm_get_labels(self.model, intarr) 278 self.labels = intArray2List(intarr, self.nr_class) 279 svmc.delete_int(intarr) 280 #check if valid probability model 281 self.probability = svmc.svm_check_probability_model(self.model)
282 283
284 - def __repr__(self):
285 """ 286 Print string representation of the model or easier comprehension 287 and some statistics 288 """ 289 ret = '<SVMModel:' 290 try: 291 ret += ' type = %s, ' % `self.svm_type` 292 ret += ' number of classes = %d (%s), ' \ 293 % ( self.nr_class, `self.labels` ) 294 except: 295 pass 296 return ret+'>'
297 298
299 - def predict(self, x):
300 data = convert2SVMNode(x) 301 ret = svmc.svm_predict(self.model, data) 302 svmc.svm_node_array_destroy(data) 303 return ret
304 305
306 - def getNRClass(self):
307 return self.nr_class
308 309
310 - def getLabels(self):
311 if self.svm_type == NU_SVR \ 312 or self.svm_type == EPSILON_SVR \ 313 or self.svm_type == ONE_CLASS: 314 raise TypeError, "Unable to get label from a SVR/ONE_CLASS model" 315 return self.labels
316 317 318 #def getParam(self): 319 # return SVMParameter( 320 # svmc_parameter=svmc.svm_model_param_get(self.model)) 321 322
323 - def predictValuesRaw(self, x):
324 #convert x into SVMNode, allocate a double array for return 325 n = self.nr_class*(self.nr_class-1)//2 326 data = convert2SVMNode(x) 327 dblarr = svmc.new_double(n) 328 svmc.svm_predict_values(self.model, data, dblarr) 329 ret = doubleArray2List(dblarr, n) 330 svmc.delete_double(dblarr) 331 svmc.svm_node_array_destroy(data) 332 return ret
333 334
335 - def predictValues(self, x):
336 v = self.predictValuesRaw(x) 337 if self.svm_type == NU_SVR \ 338 or self.svm_type == EPSILON_SVR \ 339 or self.svm_type == ONE_CLASS: 340 return v[0] 341 else: #self.svm_type == C_SVC or self.svm_type == NU_SVC 342 count = 0 343 d = {} 344 for i in range(len(self.labels)): 345 for j in range(i+1, len(self.labels)): 346 d[self.labels[i], self.labels[j]] = v[count] 347 d[self.labels[j], self.labels[i]] = -v[count] 348 count += 1 349 return d
350 351
352 - def predictProbability(self, x):
353 #c code will do nothing on wrong type, so we have to check ourself 354 if self.svm_type == NU_SVR or self.svm_type == EPSILON_SVR: 355 raise TypeError, "call get_svr_probability or get_svr_pdf " \ 356 "for probability output of regression" 357 elif self.svm_type == ONE_CLASS: 358 raise TypeError, "probability not supported yet for one-class " \ 359 "problem" 360 #only C_SVC, NU_SVC goes in 361 if not self.probability: 362 raise TypeError, "model does not support probabiliy estimates" 363 364 #convert x into SVMNode, alloc a double array to receive probabilities 365 data = convert2SVMNode(x) 366 dblarr = svmc.new_double(self.nr_class) 367 pred = svmc.svm_predict_probability(self.model, data, dblarr) 368 pv = doubleArray2List(dblarr, self.nr_class) 369 svmc.delete_double(dblarr) 370 svmc.svm_node_array_destroy(data) 371 p = {} 372 for i in range(len(self.labels)): 373 p[self.labels[i]] = pv[i] 374 return pred, p
375 376
377 - def getSVRProbability(self):
378 #leave the Error checking to svm.cpp code 379 ret = svmc.svm_get_svr_probability(self.model) 380 if ret == 0: 381 raise TypeError, "not a regression model or probability " \ 382 "information not available" 383 return ret
384 385
386 - def getSVRPdf(self):
387 #get_svr_probability will handle error checking 388 sigma = self.getSVRProbability() 389 return lambda z: exp(-fabs(z)/sigma)/(2*sigma)
390 391
392 - def save(self, filename):
393 svmc.svm_save_model(filename, self.model)
394 395
396 - def __del__(self):
397 if __debug__: 398 debug('CLF_', 'Destroying libsvm.SVMModel %s' % (`self`)) 399 400 try: 401 if svmc.__version__ < 300: 402 svmc.svm_destroy_model(self.model) 403 else: 404 svmc.svm_destroy_model_helper(self.model) 405 except: 406 # blind way to overcome problem of already deleted model and 407 # "SVMModel instance has no attribute 'model'" in ignored 408 pass
409 410
411 - def getTotalNSV(self):
412 return svmc.svm_model_l_get(self.model)
413 414
415 - def getNSV(self):
416 """Returns a list with the number of support vectors per class. 417 """ 418 return [ svmc.int_getitem(svmc.svm_model_nSV_get( self.model ), i) 419 for i in range( self.nr_class ) ]
420 421
422 - def getSV(self):
423 """Returns an array with the all support vectors. 424 425 array( nSV x <nFeatures>) 426 """ 427 return svmc.svm_node_matrix2numpy_array( 428 svmc.svm_model_SV_get(self.model), 429 self.getTotalNSV(), 430 self.prob.maxlen)
431 432
433 - def getSVCoef(self):
434 """Return coefficients for SVs... Needs to be used directly with caution! 435 436 Summary on what is happening in libsvm internals with sv_coef 437 438 svm_model's sv_coef (especially) are "cleverly" packed into a matrix 439 nr_class - 1 x #SVs_total which stores 440 coefficients for 441 nr_class x (nr_class-1) / 2 442 binary classifiers' SV coefficients. 443 444 For classifier i-vs-j 445 General packing rule can be described as: 446 447 i-th row contains sv_coefficients for SVs of class i it took 448 in all i-vs-j or j-vs-i classifiers. 449 450 Another useful excerpt from svm.cpp is 451 452 // classifier (i,j): coefficients with 453 // i are in sv_coef[j-1][nz_start[i]...], 454 // j are in sv_coef[i][nz_start[j]...] 455 456 It can also be described as j-th column lists coefficients for SV # j which 457 belongs to some class C, which it took (if it was an SV, ie != 0) 458 in classifiers i vs C (iff i<C), or C vs i+1 (iff i>C) 459 460 This way no byte of storage is wasted but imho such setup is quite convolved 461 """ 462 return svmc.doubleppcarray2numpy_array( 463 svmc.svm_model_sv_coef_get(self.model), 464 self.nr_class - 1, 465 self.getTotalNSV())
466 467
468 - def getRho(self):
469 """Return constant(s) in decision function(s) (if multi-class)""" 470 return doubleArray2List(svmc.svm_model_rho_get(self.model), 471 self.nr_class * (self.nr_class-1)/2)
472