/*-----------------------------------------------------------------------
* msvmocas.c: Standalone application implementing the OCAS folver for
* training multi-class linear SVM classifiers.
*
* Copyright (C) 2008,2009 Vojtech Franc, xfrancv@cmp.felk.cvut.cz
* Soeren Sonnenburg, soeren.sonnenburg@first.fraunhofer.de
*
* This program is free software; you can redistribute it and/or
* modify it under the terms of the GNU General Public
* License as published by the Free Software Foundation;
*-------------------------------------------------------------------- */
#include <stdio.h>
#include <string.h>
#include <stdint.h>
#include <math.h>
#include <stdlib.h>
#include "lib_svmlight_format.h"
#include "libocas.h"
#include "sparse_mat.h"
#include "ocas_helper.h"
#include "features_double.h"
#include "version.h"
void print_usage(void)
{
printf("MSVMOCAS: Optimized Cutting Plane Algorithm for Support Vector Machines\n"
" Training linear multi-class SVM classifier from examples\n"
" " OCAS_VERSION "\n"
"\n"
" usage: msvmocas [options] example_file model_file\n"
"\n"
"Arguments:\n"
" example_file file with training examples stored in SVM^light format\n"
" model_file text file to store learned linear rule f(x)=W'*x; the model file\n"
" contains M columns and D lines (M is number of classes, D is data\n"
" dimension) corresponding to the elements of the matrix W [D x M].\n"
"General options:\n"
" -h this help\n"
" -v [0,1] verbosity level (default 1).\n"
"Learning options:\n"
" -c float regularization constant C (default 1).\n"
" -n int use only first n examples for training. By default n equals to\n"
" the number of examples in the example_file.\n"
"Optimization options:\n"
" -m [0,1] solver to be used: 0 ... standard cutting plane (BMRM, SVM^perf)\n"
" 1 ... OCAS (default 1).\n"
" -s int cache size for cutting planes (default 2000).\n"
"Stopping conditions:\n"
" -a float absolute tolerance TolAbs: halt if QP-QD <= TolAbs (default 0).\n"
" -r float relative tolerance TolRel: halt if QP-QD <= abs(QP)*TolRel (default 0.01).\n"
" -q float desired objective value QPValue: halt if QP <= QPValue (default 0).\n"
" -t float halts if the solver time (loading time is not counted) exceeds\n"
" the given time in seconds (default inf).\n\n"
"Example:\n"
" Train multi-class SVM classifier from example file ./data/example4_train.light with\n"
" regularization constant set to C = 10, verbosity switched off and save model to\n"
" file ./data/msvmocas.model\n"
" ./msvmocas -c 10 -v 0 ./data/example4_train.light ./data/msvmocas.model\n"
"\n"
" Compute testing error of the classifier stored in ./data/msvmocas.model using testing\n"
" examples from ./data/example4_test.light and save predicted labels to ./data/example4_test.pred\n"
" ./linclassif -e -o ./data/exaple4_test.pred ./data/example4_test.light ./data/msvmocas.model\n"
"\n"
);
}
int main(int argc, char *argv[])
{
double C, TolRel, TolAbs, QPBound, MaxTime;
uint32_t i, j, BufSize;
uint16_t Method;
ocas_return_value_T ocas;
int len;
int recognized;
int exitflag = 1;
int verb;
/* timing variables */
double load_time;
double total_time;
char *model_fname;
char *input_fname;
FILE *fid;
/* start time measuring */
total_time = get_time();
/* init */
data_X = NULL;
data_y = NULL;
W = NULL;
oldW = NULL;
sparse_A.nz_dims = NULL;
sparse_A.index = NULL;
sparse_A.value = NULL;
new_a = NULL;
full_A = NULL;
/* default setting of input arguments*/
C = 1.0;
Method = 1;
TolRel = 0.01;
TolAbs = 0.0;
QPBound = 0.0;
BufSize = 2000;
MaxTime = (double)LIBOCAS_PLUS_INF;
nData = -1;
verb = 1;
/*-----------------------------------------------------------
Process input arguments
------------------------------------------------------------*/
if(argc ==1 || strcmp(argv[1], "-h") == 0)
{
print_usage();
goto clean_up;
}
if(argc < 3)
{
fprintf(stderr,"Not enough input arguments.\n\n");
goto clean_up;
}
for (i = 1; i < argc-2; i++)
{
recognized = 0;
if (strcmp(argv[i], "-h") == 0)
{
print_usage();
goto clean_up;
}
if (strcmp(argv[i], "-c") == 0)
{
if(i+1 >= argc-2)
{
fprintf(stderr,"You have to specify a value after argument -c\n");
goto clean_up;
}
C = atof(argv[i+1]);
if(C <=0)
{
fprintf(stderr,"Parameter C must be geater than zero.\n");
goto clean_up;
}
i++;
recognized = 1;
}
if (strcmp(argv[i], "-n") == 0)
{
if(i+1 >= argc-2)
{
fprintf(stderr,"You have to specify a value after argument -n\n");
goto clean_up;
}
nData = atol(argv[i+1]);
if(nData <=0)
{
fprintf(stderr,"A value after the argument -n must be greater than zero.\n");
goto clean_up;
}
i++;
recognized = 1;
}
if (strcmp(argv[i], "-s") == 0)
{
if(i+1 >= argc-2)
{
fprintf(stderr,"You have to specify a value after argument -s\n");
goto clean_up;
}
BufSize = atol(argv[i+1]);
if(nData <=0)
{
fprintf(stderr,"A value after the argument -s must be greater than zero.\n");
goto clean_up;
}
i++;
recognized = 1;
}
if (strcmp(argv[i], "-m") == 0)
{
if(i+1 >= argc-2)
{
fprintf(stderr,"You have to specify a value after argument -m\n");
goto clean_up;
}
Method = atoi(argv[i+1]);
if(Method != 0 && Method != 1)
{
fprintf(stderr,"A value after the argument -m must be 0 or 1.\n");
goto clean_up;
}
i++;
recognized = 1;
}
if (strcmp(argv[i], "-v") == 0)
{
if(i+1 >= argc-2)
{
fprintf(stderr,"You have to specify a value after argument -v\n");
goto clean_up;
}
verb = atoi(argv[i+1]);
if(verb < 0 || verb > 1)
{
fprintf(stderr,"A value after the argument -v must be either 0 or 1.\n");
goto clean_up;
}
i++;
recognized = 1;
}
if (strcmp(argv[i], "-a") == 0)
{
if(i+1 >= argc-2)
{
fprintf(stderr,"You have to specify a value after argument -a\n");
goto clean_up;
}
TolAbs = atof(argv[i+1]);
if(TolAbs < 0)
{
fprintf(stderr,"A value after the argument -a must be a positive scalar.\n");
goto clean_up;
}
i++;
recognized = 1;
}
if (strcmp(argv[i], "-r") == 0)
{
if(i+1 >= argc-2)
{
fprintf(stderr,"You have to specify a value after argument -r\n");
goto clean_up;
}
TolRel = atof(argv[i+1]);
if(TolRel < 0)
{
fprintf(stderr,"A value after the argument -r must be a positive scalar.\n");
goto clean_up;
}
i++;
recognized = 1;
}
if (strcmp(argv[i], "-q") == 0)
{
if(i+1 >= argc-2)
{
fprintf(stderr,"You have to specify a value after argument -q\n");
goto clean_up;
}
QPBound = atof(argv[i+1]);
i++;
recognized = 1;
}
if (strcmp(argv[i], "-t") == 0)
{
if(i+1 >= argc-2)
{
fprintf(stderr,"You have to specify a value after argument -t\n");
goto clean_up;
}
MaxTime = atof(argv[i+1]);
if(MaxTime <=0)
{
fprintf(stderr,"A value after the argument -t must be a positive scalar.\n");
goto clean_up;
}
i++;
recognized = 1;
}
if(recognized == 0)
{
fprintf(stderr,"Unknown input argument: %s\n", argv[i]);
goto clean_up;
}
}
len = strlen(argv[argc-2]);
input_fname = calloc(len+1,sizeof(char));
strcpy(input_fname, argv[argc-2]);
len = strlen(argv[argc-1]);
model_fname = calloc(len+1,sizeof(char));
strcpy(model_fname, argv[argc-1]);
if(verb)
printf("Input file: %s\n", input_fname);
/*----------------------------------------------------------------
Load input examples
-------------------------------------------------------------------*/
load_time = get_time();
if( load_svmlight_file(input_fname,verb) == -1 || data_X == NULL || data_y == NULL)
goto clean_up;
load_time = get_time() - load_time;
/* get examples' dimension */
nDim = mxGetM(data_X);
/* if not given set number of training examples to be used */
if(nData == -1)
nData = mxGetN(data_X);
else if(nData <= 0 || nData > mxGetN(data_X))
{
fprintf(stderr,"Number of examples in the input file is %d.\n", mxGetN(data_X));
fprintf(stderr,"A value after argument -n must be less or equal to the number of examples.\n");
goto clean_up;
}
/* get number of classes */
for(i=0, nY = 0; i < nData; i++)
nY = LIBOCAS_MAX(nY, (uint32_t)data_y[i]);
/*----------------------------------------------------------------
Print setting
-------------------------------------------------------------------*/
if(verb)
{
printf("Input file statistics:\n"
" # of examples : %d\n"
" # of classes : %d\n"
" dimensionality : %d\n",
mxGetN(data_X), nY, nDim);
if( mxIsSparse(data_X) )
printf(" density : %.2f%% (sparse matrix representation used)\n",
100.0*(double)mxGetNZMAX(data_X)/((double)nDim*(double)(mxGetN(data_X))));
else
printf(" density : 100%% (full matrix representation used)\n");
printf("Setting:\n"
" C : %f\n"
" # of examples : %d\n"
" solver : %d\n"
" cache size : %d\n"
" TolAbs : %f\n"
" TolRel : %f\n"
" QPValue : %f\n"
" MaxTime : %f [s]\n"
" Verbosity : %d\n",
C, nData, Method,BufSize,TolAbs,TolRel, QPBound, MaxTime,verb);
}
/*----------------------------------------------------------------
Allocate memory for working variables and cutting plane cache
-------------------------------------------------------------------*/
/* learned weight vector */
W = (double*)mxCalloc(nDim*nY,sizeof(double));
if(W == NULL)
{
fprintf(stderr,"Not enough memory for matrix W.\n");
goto clean_up;
}
oldW = (double*)mxCalloc(nDim*nY,sizeof(double));
if(oldW == NULL)
{
fprintf(stderr,"Not enough memory for matrix oldW.");
goto clean_up;
}
/* allocate buffer for computing cutting plane */
new_a = (double*)mxCalloc(nDim*nY,sizeof(double));
if(new_a == NULL)
{
fprintf(stderr,"Not enough memory for auxciliary cutting plane buffer new_a.");
goto clean_up;
}
if(mxIsSparse(data_X))
{
/* init cutting plane buffer */
sparse_A.nz_dims = mxCalloc(BufSize,sizeof(uint32_t));
sparse_A.index = mxCalloc(BufSize,sizeof(sparse_A.index[0]));
sparse_A.value = mxCalloc(BufSize,sizeof(sparse_A.value[0]));
if(sparse_A.nz_dims == NULL || sparse_A.index == NULL || sparse_A.value == NULL)
{
fprintf(stderr,"Not enough memory for cutting plane buffer sparse_A.");
goto clean_up;
}
/*----------------------------------------------------------------
Run OCAS run ...
-------------------------------------------------------------------*/
if(verb)
{
printf("Starting optimization:\n");
ocas = msvm_ocas_solver( C, data_y, nY, nData, TolRel, TolAbs, QPBound, MaxTime,BufSize, Method,
&msvm_sparse_compute_W, &msvm_update_W, &msvm_sparse_add_new_cut,
&msvm_sparse_compute_output, &qsort_data, &ocas_print, 0);
}
else
{
ocas = msvm_ocas_solver( C, data_y, nY, nData, TolRel, TolAbs, QPBound, MaxTime,BufSize, Method,
&msvm_sparse_compute_W, &msvm_update_W, &msvm_sparse_add_new_cut,
&msvm_sparse_compute_output, &qsort_data, &ocas_print_null, 0);
}
}
else
{
/* init cutting plane buffer */
full_A = mxCalloc(BufSize*nDim*nY,sizeof(double));
if( full_A == NULL )
{
fprintf(stderr,"Not enough memory for cutting plane buffer full_A.");
goto clean_up;
}
if(verb)
{
printf("Starting optimization:\n");
ocas = msvm_ocas_solver( C, data_y, nY, nData, TolRel, TolAbs, QPBound, MaxTime,BufSize, Method,
&msvm_full_compute_W, &msvm_update_W, &msvm_full_add_new_cut,
&msvm_full_compute_output, &qsort_data, &ocas_print, 0);
}
else
{
ocas = msvm_ocas_solver( C, data_y, nY, nData, TolRel, TolAbs, QPBound, MaxTime,BufSize, Method,
&msvm_full_compute_W, &msvm_update_W, &msvm_full_add_new_cut,
&msvm_full_compute_output, &qsort_data, &ocas_print_null, 0);
}
}
total_time=get_time()-total_time;
if(verb)
{
printf("Stopping condition: ");
switch( ocas.exitflag )
{
case 1: printf("1-Q_D/Q_P <= TolRel(=%f) satisfied.\n", TolRel); break;
case 2: printf("Q_P-Q_D <= TolAbs(=%f) satisfied.\n", TolAbs); break;
case 3: printf("Q_P <= QPBound(=%f) satisfied.\n", QPBound); break;
case 4: printf("Optimization time (=%f) >= MaxTime(=%f).\n", ocas.ocas_time, MaxTime); break;
case -1: printf("Has not converged!\n" ); break;
case -2: printf("Not enough memory for the solver.\n" ); break;
}
printf("Timing statistics:\n"
" load_time : %f[s]\n"
" qp_solver_time : %f[s]\n"
" sort_time : %f[s]\n"
" output_time : %f[s]\n"
" add_time : %f[s]\n"
" w_time : %f[s]\n"
" print_time : %f[s]\n"
" ocas_time : %f[s]\n"
" total_time : %f[s]\n",
load_time, ocas.qp_solver_time, ocas.sort_time, ocas.output_time,
ocas.add_time, ocas.w_time, ocas.print_time, ocas.ocas_time, total_time);
printf("Training error: %.4f%%\n", 100*(double)ocas.trn_err/(double)nData);
}
/* save W to the model file */
fid = fopen(model_fname, "w+");
if(fid == NULL) {
perror("fopen error ");
mexPrintf("Cannot open model file.");
goto clean_up;
}
for(i=0; i < nDim; i++)
{
for(j=0; j < nY; j++)
{
fprintf(fid, "%.20f ", W[LIBOCAS_INDEX(i,j,nDim)]);
}
fprintf(fid, "\n");
}
fclose(fid);
if(verb)
printf("Result saved to model file: %s\n", model_fname);
exitflag = 0;
clean_up:
mxDestroyArray(data_X);
mxFree(data_y);
mxFree(W);
mxFree(oldW);
mxFree(new_a);
mxFree(full_A);
mxFree(sparse_A.nz_dims);
if( sparse_A.index !=NULL)
{
for(i=0; i < BufSize; i++)
if(sparse_A.index[i] != NULL)
mxFree(sparse_A.index[i]);
mxFree(sparse_A.index);
}
if( sparse_A.value != NULL)
{
for(i=0; i < BufSize; i++)
if(sparse_A.value[i] != NULL)
mxFree(sparse_A.value[i]);
mxFree(sparse_A.value);
}
return(exitflag);
}