1
2
3
4
5
6
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
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
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
48 if x != 'NULL' and x != None:
49 svmc.delete_int(x)
50
53 if x != 'NULL' and x != None:
54 svmc.delete_double(x)
55
58 return map(svmc.int_getitem, [x]*n, range(n))
59
62 return map(svmc.double_getitem, [x]*n, range(n))
63
66 """
67 SVMParameter class safe to be deepcopied.
68 """
69
70 default_parameters = {
71 'svm_type' : C_SVC,
72 'kernel_type' : RBF,
73 'degree' : 3,
74 'gamma' : 0,
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
89 """Internal class to to avoid memory leaks returning away svmc's params"""
90
92 self.param = svmc.new_svm_parameter()
93 for attr, val in params.items():
94
95 if attr == 'weight_label':
96
97 val = intArray(val)
98
99
100 elif attr == 'weight':
101
102 val = doubleArray(val)
103
104
105
106 set_func = getattr(svmc, 'svm_parameter_%s_set' % (attr))
107 set_func(self.param, val)
108
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
118 self._orig_params = kw
119 self.untrain()
120
122 self._params = {}
123 self._params.update(self.default_parameters)
124 self._params.update(**self._orig_params)
125 self.__svmc_params = None
126 self.__svmc_recompute = False
127
130
132 return "SVMParameter: %s" % `self._params`
133
138
143
145 if not self.__svmc_params is None:
146 del self.__svmc_params
147 self.__svmc_params = None
148
149 @property
157
162
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
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()
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
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
227 return "<SVMProblem: size = %s>" % (self.size)
228
229
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
244 if arg2 == None:
245
246 filename = arg1
247 self.model = svmc.svm_load_model(filename)
248 else:
249
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
260 self.nr_class = svmc.svm_get_nr_class(self.model)
261 self.svm_type = svmc.svm_get_svm_type(self.model)
262
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
268 self.probability = svmc.svm_check_probability_model(self.model)
269
270
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
291
292
295
296
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
306
307
308
309
311
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
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:
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
340
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
348 if not self.probability:
349 raise TypeError, "model does not support probabiliy estimates"
350
351
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
365
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
374
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
384 if __debug__:
385 debug('CLF_', 'Destroying libsvm.SVMModel %s' % (`self`))
386
387 try:
388 svmc.svm_destroy_model(self.model)
389 except:
390
391
392 pass
393
394
396 return svmc.svm_model_l_get(self.model)
397
398
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
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
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
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