view pylearn/external/wrap_libsvm.py @ 1492:e7c4d031d333

Fix for Windows paths
author Olivier Delalleau <delallea@iro>
date Tue, 16 Aug 2011 15:44:01 -0400
parents 80123baa4544
children
line wrap: on
line source

"""Run an experiment using libsvm.
"""
import numpy
from ..datasets import make_dataset

# libsvm currently has no python installation instructions/convention.
#
# This module uses a specific convention for libsvm's installation.
# I base this on installing libsvm-2.88.
# To install libsvm's python module, do the following:
# 1. Build libsvm (run make in both the root dir and the python subdir).
# 2. touch a '__init__.py' file in the python subdir
# 3. add a symbolic link to a PYTHONPATH location that looks like this:
#    libsvm -> <your root path>/libsvm-2.88/python/
# 4. modify the svm_model class in python/svm.py to inherit from object
#
# That is the sort of thing that this module expects from 'import libsvm'

import libsvm

class svm_model(libsvm.svm_model):
    """
    This class is a picklable drop-in replacement for libsvm.svm_model.
    """
    def __getstate__(self):
        return PicklableSVM.svm_to_str(self)

    def __setstate__(self, svm_str):
        PicklableSVM.str_to_svm(svm_str, self=self)

    @staticmethod
    def str_to_svm(s, self=None):
        fname = tempfile.mktemp()
        f = open(fname,'w')
        f.write(s)
        f.close()
        rval = self
        try:
            if self:
                self.__init__(fname)
            else:
                rval = libsvm.svm_model(fname)
        finally:
            os.remove(fname)
        return rval

    @staticmethod
    def svm_to_str(svm):
        fname = tempfile.mktemp()
        svm.save(fname)
        rval = open(fname, 'r').read()
        os.remove(fname)
        return rval

    def predict(self, x):
        if type(x) != numpy.ndarray:
            raise TypeError(x)
        if x.ndim != 1:
            raise TypeError(x)
        return libsvm.svm_model.predict(self, numpy.asarray(x, dtype='float64'))

    def predict_probability(self, x):
        if x.ndim != 1:
            raise TypeError(x)
        return libsvm.svm_model.predict_probability(self, numpy.asarray(x, dtype='float64'))

svm_problem = libsvm.svm_problem
svm_parameter = libsvm.svm_parameter
RBF = libsvm.RBF


####################################
# Extra stuff that is less essential 
#
# TODO: Move stuff below to a file 
#       in algorithms
####################################

def score_01(x, y, model):
    assert len(x) == len(y)
    size = len(x)
    errors = 0
    for i in range(size):
        prediction = model.predict(x[i])
        #probability = model.predict_probability
        if (y[i] != prediction):
            errors = errors + 1
    return float(errors)/size

#this is the dbdict experiment interface... if you happen to use dbdict
class State(object):
    #TODO: parametrize to get all the kernel types, not hardcode for RBF
    dataset = 'MNIST_1k'
    C = 10.0
    kernel = 'RBF'
    # rel_gamma is related to the procedure Jerome used. He mentioned why in
    # quadratic_neurons/neuropaper/draft3.pdf.
    rel_gamma = 1.0   

    def __init__(self, **kwargs):
        for k, v in kwargs:
            setattr(self, k, type(getattr(self, k))(v))

def state_run_svm_experiment(state, channel=lambda *args, **kwargs:None):
    """Parameters are described in state, and returned in state.

    :param state: object instance to store parameters and return values
    :param channel: not used

    :returns: None

    This is the kind of function that dbdict-run can use.

    """
    dataset = make_dataset(**state.dataset)


    #libsvm needs stuff in int32 on a 32bit machine
    #TODO: test this on a 64bit machine
    # -> Both int32 and int64 (default) seem to be OK
    train_y = numpy.asarray(dataset.train.y, dtype='int32')
    valid_y = numpy.asarray(dataset.valid.y, dtype='int32')
    test_y = numpy.asarray(dataset.test.y, dtype='int32')
    problem = libsvm.svm_problem(train_y, dataset.train.x);

    gamma0 = 0.5 / numpy.sum(numpy.var(dataset.train.x, axis=0))

    param = libsvm.svm_parameter(C=state['C'],
            kernel_type=getattr(libsvm, state['kernel']),
            gamma=state['rel_gamma'] * gamma0)

    model = libsvm.svm_model(problem, param) #this is the expensive part

    state['train_01'] = score_01(dataset.train.x, train_y, model)
    state['valid_01'] = score_01(dataset.valid.x, valid_y, model)
    state['test_01'] = score_01(dataset.test.x, test_y, model)

    state['n_train'] = len(train_y)
    state['n_valid'] = len(valid_y)
    state['n_test'] = len(test_y)

def run_svm_experiment(**kwargs):
    """Python-friendly interface to dbdict_run_svm_experiment

    Parameters are used to construct a `State` instance, which is returned after running
    `dbdict_run_svm_experiment` on it.

    .. code-block:: python
        results = run_svm_experiment(dataset='MNIST_1k', C=100.0, rel_gamma=0.01)
        print results.n_train
        # 1000
        print results.valid_01, results.test_01
        # 0.14, 0.10  #.. or something...

    """
    state_run_svm_experiment(state=kwargs)
    return kwargs

def train_rbf_model(train_X, train_Y, C, gamma):
    param = libsvm.svm_parameter(C=C, kernel_type=libsvm.RBF, gamma=gamma)
    problem = libsvm.svm_problem(train_Y, train_X)
    model = svm_model(problem, param)

    #save_filename = state.save_filename
    #model.save(save_filename)


def jobman_train_model(state, channel):
    """
    
    According to the given validation set,
    What is the best libsvm parameter setting to train on?
    """
    (train_X, train_Y) = jobman.tools.make(state.train_set)
    (valid_X, valid_Y) = jobman.tools.make(state.valid_set)

    C_grid = [1,2,3]
    gamma_grid = [0.1, 1, 10]

    grid = [dict(
        train_set=None,
        svm_param=dict(kernel='RBF', C=C, gamma=g),
        save_filename='model_RBF_C%f_G%f.libsvm')
            for C in C_grid
            for g in gamma_grid]

    # will return quickly if jobs have already run
    # and the rootpath is populated with results
    grid = jobman.map(
            jobman_train_model_given_all_params, 
            grid,
            path=jobman.rootpath(state)+'/gridmap',
            cleanup=False)

    # evaluate all these sub_state models on our validation_set
    valid_perf = []
    for sub_state in grid:
        # create a file in this state-space called model.tmp
        # with the same contents as the
        # save_filename file in the sub_state
        jobman.link('model.tmp', jobman.rootpath(sub_state)+'/'+sub_state.save_filename)
        model = svm.model('model.tmp')
        valid_perf.append((score_01(valid_X, valid_Y, model), sub_state))
        jobman.unlink('model.tmp')

    # calculate the return value
    valid_perf.sort() #lowest first
    state.lowest_valid_err = valid_perf[0][0]
    state.lowest_valid_svm_param = valid_perf[0][1].svm_param