267 lines
7.7 KiB
C++
267 lines
7.7 KiB
C++
/**
|
|
* @author Hua Ouyang
|
|
*
|
|
* @file svm_main.cc
|
|
*
|
|
* This file contains main routines for performing multiclass SVM
|
|
* classification. One-vs-One method is employed.
|
|
*
|
|
* It provides four modes:
|
|
* "cv": cross validation;
|
|
* "train": model training
|
|
* "train_test": training and then online batch testing;
|
|
* "test": offline batch testing.
|
|
*
|
|
* @see svm.h
|
|
* @see smo.h
|
|
*/
|
|
|
|
#include "svm.h"
|
|
|
|
/**
|
|
* Data Normalization
|
|
*
|
|
* @param: the dataset to be normalized
|
|
*/
|
|
void DoSvmNormalize(Dataset* dataset) {
|
|
Matrix m;
|
|
Vector sums;
|
|
|
|
m.Init(dataset->n_features()-1, dataset->n_points());
|
|
sums.Init(dataset->n_features() - 1);
|
|
sums.SetZero();
|
|
|
|
for (index_t i = 0; i < dataset->n_points(); i++) {
|
|
Vector s;
|
|
Vector d;
|
|
dataset->matrix().MakeColumnSubvector(i, 0, dataset->n_features()-1, &s);
|
|
m.MakeColumnVector(i, &d);
|
|
d.CopyValues(s);
|
|
la::AddTo(s, &sums);
|
|
}
|
|
|
|
la::Scale(-1.0 / dataset->n_points(), &sums);
|
|
for (index_t i = 0; i < dataset->n_points(); i++) {
|
|
Vector d;
|
|
m.MakeColumnVector(i, &d);
|
|
la::AddTo(sums, &d);
|
|
}
|
|
|
|
Matrix cov;
|
|
|
|
la::MulTransBInit(m, m, &cov);
|
|
|
|
Vector d;
|
|
Matrix u; // eigenvectors
|
|
Matrix ui; // the inverse of eigenvectors
|
|
|
|
PASSED(la::EigenvectorsInit(cov, &d, &u));
|
|
la::TransposeInit(u, &ui);
|
|
|
|
for (index_t i = 0; i < d.length(); i++) {
|
|
d[i] = 1.0 / sqrt(d[i] / (dataset->n_points() - 1));
|
|
}
|
|
|
|
la::ScaleRows(d, &ui);
|
|
|
|
Matrix cov_inv_half;
|
|
la::MulInit(u, ui, &cov_inv_half);
|
|
|
|
Matrix final;
|
|
la::MulInit(cov_inv_half, m, &final);
|
|
|
|
for (index_t i = 0; i < dataset->n_points(); i++) {
|
|
Vector s;
|
|
Vector d;
|
|
dataset->matrix().MakeColumnSubvector(i, 0, dataset->n_features()-1, &d);
|
|
final.MakeColumnVector(i, &s);
|
|
d.CopyValues(s);
|
|
}
|
|
|
|
if (fx_param_bool(NULL, "save", 0)) {
|
|
fx_default_param(NULL, "kfold/save", "1");
|
|
dataset->WriteCsv("m_normalized.csv");
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Generate an artificial data set
|
|
*
|
|
* @param: the dataset to be generated
|
|
*/
|
|
void GenerateArtificialDataset(Dataset* dataset){
|
|
Matrix m;
|
|
index_t n = fx_param_int(NULL, "n", 30);
|
|
double offset = fx_param_double(NULL, "offset", 0.0);
|
|
double range = fx_param_double(NULL, "range", 1.0);
|
|
double slope = fx_param_double(NULL, "slope", 1.0);
|
|
double margin = fx_param_double(NULL, "margin", 1.0);
|
|
double var = fx_param_double(NULL, "var", 1.0);
|
|
double intercept = fx_param_double(NULL, "intercept", 0.0);
|
|
|
|
// 2 dimensional dataset, size n, 3 classes
|
|
m.Init(3, n);
|
|
for (index_t i = 0; i < n; i += 3) {
|
|
double x;
|
|
double y;
|
|
|
|
x = (rand() * range / RAND_MAX) + offset;
|
|
y = margin / 2 + (rand() * var / RAND_MAX);
|
|
m.set(0, i, x);
|
|
m.set(1, i, x*slope + y + intercept);
|
|
m.set(2, i, 0); // labels
|
|
|
|
x = (rand() * range / RAND_MAX) + offset;
|
|
y = margin / 2 + (rand() * var / RAND_MAX);
|
|
m.set(0, i+1, 10*x);
|
|
m.set(1, i+1, x*slope + y + intercept);
|
|
m.set(2, i+1, 1); // labels
|
|
|
|
x = (rand() * range / RAND_MAX) + offset;
|
|
y = margin / 2 + (rand() * var / RAND_MAX);
|
|
m.set(0, i+2, 20*x);
|
|
m.set(1, i+2, x*slope + y + intercept);
|
|
m.set(2, i+2, 2); // labels
|
|
}
|
|
|
|
data::Save("artificialdata.csv", m); // TODO, for training, for testing
|
|
dataset->OwnMatrix(&m);
|
|
}
|
|
|
|
/**
|
|
* Load data set from data file. If data file not exists, generate an
|
|
* artificial data set.
|
|
*
|
|
* @param: the dataset
|
|
* @param: name of the data file to be loaded
|
|
*/
|
|
int LoadData(Dataset* dataset, String datafilename){
|
|
if (fx_param_exists(NULL, datafilename)) {
|
|
// when a data file is specified, use it.
|
|
if ( !PASSED(dataset->InitFromFile( fx_param_str_req(NULL, datafilename) )) ) {
|
|
fprintf(stderr, "Couldn't open the data file.\n");
|
|
return 0;
|
|
}
|
|
}
|
|
else {
|
|
fprintf(stderr, "No data file exist. Generating artificial dataset.\n");
|
|
// otherwise, generate an artificial dataset and save it to "m.csv"
|
|
GenerateArtificialDataset(dataset);
|
|
}
|
|
|
|
if (fx_param_bool(NULL, "normalize", 1)) {
|
|
fprintf(stderr, "Normalizing\n");
|
|
DoSvmNormalize(dataset);
|
|
} else {
|
|
fprintf(stderr, "Skipping normalize\n");
|
|
}
|
|
return 1;
|
|
}
|
|
|
|
/**
|
|
* Multiclass SVM classification- Main function
|
|
*
|
|
* @param: argc
|
|
* @param: argv
|
|
*/
|
|
int main(int argc, char *argv[]) {
|
|
fx_init(argc, argv);
|
|
srand(time(NULL));
|
|
|
|
String mode = fx_param_str_req(NULL, "mode");
|
|
String kernel = fx_param_str_req(NULL, "kernel");
|
|
|
|
// TODO: more kernels to be supported
|
|
|
|
/* Cross Validation Mode, need cross validation data */
|
|
if(mode == "cv") {
|
|
fprintf(stderr, "SVM Cross Validation... \n");
|
|
|
|
/* Load cross validation data */
|
|
Dataset cvset;
|
|
if (LoadData(&cvset, "cv_data") == 0)
|
|
return 1;
|
|
|
|
if (kernel == "linear") {
|
|
SimpleCrossValidator< SVM<SVMLinearKernel> > cross_validator;
|
|
/* Initialize n_folds_, confusion_matrix_; k_cv: number of cross-validation folds, need k_cv>1 */
|
|
cross_validator.Init(&cvset,cvset.n_labels(),fx_param_int_req(NULL,"k_cv"), fx_root, "svm");
|
|
/* k_cv folds cross validation; (true): do training set permutation */
|
|
cross_validator.Run(true);
|
|
cross_validator.confusion_matrix().PrintDebug("confusion matrix");
|
|
}
|
|
else if (kernel == "gaussian") {
|
|
SimpleCrossValidator< SVM<SVMRBFKernel> > cross_validator;
|
|
/* Initialize n_folds_, confusion_matrix_; k_cv: number of cross-validation folds */
|
|
cross_validator.Init(&cvset,cvset.n_labels(),fx_param_int_req(NULL,"k_cv"), fx_root, "svm");
|
|
/* k_cv folds cross validation; (true): do training set permutation */
|
|
cross_validator.Run(true);
|
|
cross_validator.confusion_matrix().PrintDebug("confusion matrix");
|
|
}
|
|
}
|
|
/* Training Mode, need training data | Training + Testing(online) Mode, need training data + testing data */
|
|
else if (mode=="train" || mode=="train_test"){
|
|
fprintf(stderr, "SVM Training... \n");
|
|
|
|
/* Load training data */
|
|
Dataset trainset;
|
|
if (LoadData(&trainset, "train_data") == 0) // TODO:param_req
|
|
return 1;
|
|
|
|
/* Begin SVM Training | Training and Testing */
|
|
datanode *svm_module = fx_submodule(fx_root, NULL, "svm");
|
|
|
|
if (kernel == "linear") {
|
|
SVM<SVMLinearKernel> svm;
|
|
svm.InitTrain(trainset, trainset.n_labels(), svm_module);
|
|
/* training and testing, thus no need to load model from file */
|
|
if (mode=="train_test"){
|
|
fprintf(stderr, "SVM Classifying... \n");
|
|
/* Load testing data */
|
|
Dataset testset;
|
|
if (LoadData(&testset, "test_data") == 0) // TODO:param_req
|
|
return 1;
|
|
svm.BatchClassify(&testset, "testlabels");
|
|
}
|
|
}
|
|
else if (kernel == "gaussian") {
|
|
SVM<SVMRBFKernel> svm;
|
|
svm.InitTrain(trainset, trainset.n_labels(), svm_module);
|
|
/* training and testing, thus no need to load model from file */
|
|
if (mode=="train_test"){
|
|
fprintf(stderr, "SVM Classifying... \n");
|
|
/* Load testing data */
|
|
Dataset testset;
|
|
if (LoadData(&testset, "test_data") == 0) // TODO:param_req
|
|
return 1;
|
|
svm.BatchClassify(&testset, "testlabels"); // TODO:param_req
|
|
}
|
|
}
|
|
}
|
|
/* Testing(offline) Mode, need loading model file and testing data */
|
|
else if (mode=="test") {
|
|
fprintf(stderr, "SVM Classifying... \n");
|
|
|
|
/* Load testing data */
|
|
Dataset testset;
|
|
if (LoadData(&testset, "test_data") == 0) // TODO:param_req
|
|
return 1;
|
|
|
|
/* Begin Classification */
|
|
datanode *svm_module = fx_submodule(fx_root, NULL, "svm");
|
|
|
|
if (kernel == "linear") {
|
|
SVM<SVMLinearKernel> svm;
|
|
svm.Init(testset, testset.n_labels(), svm_module); // TODO:n_labels() -> num_classes_
|
|
svm.LoadModelBatchClassify(&testset, "svm_model", "testlabels"); // TODO:param_req
|
|
}
|
|
else if (kernel == "gaussian") {
|
|
SVM<SVMRBFKernel> svm;
|
|
svm.Init(testset, testset.n_labels(), svm_module); // TODO:n_labels() -> num_classes_
|
|
svm.LoadModelBatchClassify(&testset, "svm_model", "testlabels"); // TODO:param_req
|
|
}
|
|
}
|
|
fx_done();
|
|
}
|
|
|