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