简体   繁体   中英

How to train in Matlab a model, save it to disk, and load in C++ program?

I am using libsvm version 3.16. I have done some training in Matlab, and created a model. Now I would like to save this model to disk and load this model in my C++ program. So far I have found the following alternatives:

  1. This answer explains how to save a model from C++, which is based on this website. Not exactly what I need, but could be adapted. (This requires development time).
  2. I could find the best training parameters (kernel,C) in Matlab and re-train everything in C++. (Will require doing the training in C++ each time I change a parameter. It's not scalable).

Thus, both of these options are not satisfactory,

Does anyone have an idea?

My solution was to retrain in C++ because I couldn't find a nice way to directly save the model. Here's my code. You'll need to adapt it and clean it up a bit. The biggest change you'll have to make it not hard coding the svm_parameter values like I did. You'll also have to replace FilePath with std::string . I'm copying, pasting and making small edits here in SO so the formatting won't e perfect:

Used like this:

    auto targetsPath = FilePath("targets.txt");
    auto observationsPath = FilePath("observations.txt");

    auto targetsMat = MatlabMatrixFileReader::Read(targetsPath, ',');
    auto observationsMat = MatlabMatrixFileReader::Read(observationsPath, ',');
    auto v = MiscVector::ConvertVecOfVecToVec(targetsMat);
    auto model = SupportVectorRegressionModel{ observationsMat, v };

    std::vector<double> observation{ { // 32 feature observation
        0.883575729725847,0.919446119013878,0.95359403450317,
        0.968233630936732,0.91891307107125,0.887897763183844,
        0.937588566544751,0.920582702918882,0.888864454119387,
        0.890066735260163,0.87911085669864,0.903745573664995,
        0.861069296586979,0.838606194934074,0.856376230548304,
        0.863011311537075,0.807688936997926,0.740434984165146,
        0.738498042748759,0.736410940165691,0.697228384912424,
        0.608527698289016,0.632994967880269,0.66935784966765,
        0.647761430696238,0.745961037635717,0.560761134660957,
        0.545498063585615,0.590854855113663,0.486827902942118,
        0.187128866890822,- 0.0746523069562551
    } };

    double prediction = model.Predict(observation);

miscvector.h

    static vector<double> ConvertVecOfVecToVec(const vector<vector<double>> &mat)
    {
        vector<double> targetsVec;
        targetsVec.reserve(mat.size());
        for (size_t i = 0; i < mat.size(); i++)
        {
            targetsVec.push_back(mat[i][0]);
        }
        return targetsVec;
    }

libsvmtargetobjectconvertor.h

#pragma once

#include "machinelearning.h"

struct svm_node;

class LibSvmTargetObservationConvertor
{
public:
    svm_node ** LibSvmTargetObservationConvertor::ConvertObservations(const vector<MlObservation> &observations, size_t numFeatures) const
{
    svm_node **svmObservations = (svm_node **)malloc(sizeof(svm_node *) * observations.size());
    for (size_t rowI = 0; rowI < observations.size(); rowI++)
    {
        svm_node *row = (svm_node *)malloc(sizeof(svm_node) * numFeatures);
        for (size_t colI = 0; colI < numFeatures; colI++)
        {
            row[colI].index = colI;
            row[colI].value = observations[rowI][colI];
        }
        row[numFeatures].index = -1; // apparently needed
        svmObservations[rowI] = row;
    }
    return svmObservations;
}

svm_node* LibSvmTargetObservationConvertor::ConvertMatToSvmNode(const MlObservation &observation) const
{
    size_t numFeatures = observation.size();
    svm_node *obsNode = (svm_node *)malloc(sizeof(svm_node) * numFeatures);
    for (size_t rowI = 0; rowI < numFeatures; rowI++)
    {
        obsNode[rowI].index = rowI;
        obsNode[rowI].value = observation[rowI];
    }
    obsNode[numFeatures].index = -1; // apparently needed
    return obsNode;
}
};

machinelearning.h

#pragma once

#include <vector>
using std::vector;

using MlObservation = vector<double>;
using MlTarget = double;

//machinelearningmodel.h
#pragma once

#include <vector>
#include "machinelearning.h"
class MachineLearningModel
{
public:
    virtual ~MachineLearningModel() {}
    virtual double Predict(const MlObservation &observation) const = 0;
};

matlabmatrixfilereader.h

#pragma once

#include <vector>
using std::vector;

class FilePath;
// Matrix created with command:
// dlmwrite('my_matrix.txt', somematrix, 'delimiter', ',', 'precision', 15);
// In these files, each row is a matrix row. Commas separate elements on a row.
// There is no space at the end of a row. There is a blank line at the bottom of the file.
// File format:
// 0.4,0.7,0.8
// 0.9,0.3,0.5
// etc.
static class MatlabMatrixFileReader
{
public:
    static vector<vector<double>> Read(const FilePath &asciiFilePath, char delimiter)
{

    vector<vector<double>> values;
    vector<double> valueline;
    std::ifstream fin(asciiFilePath.Path());
    string item, line;
    while (getline(fin, line))
    {
        std::istringstream in(line);

        while (getline(in, item, delimiter))
        {
            valueline.push_back(atof(item.c_str()));
        }           
        values.push_back(valueline);
        valueline.clear();
    }
    fin.close();
    return values;
}

};

supportvectorregressionmodel.h

#pragma once

#include <vector>
using std::vector;
#include "machinelearningmodel.h"

#include "svm.h" // libsvm

class FilePath;

class SupportVectorRegressionModel : public MachineLearningModel
{
public:
    SupportVectorRegressionModel::~SupportVectorRegressionModel()
{
    svm_free_model_content(model_);
    svm_destroy_param(&param_);
    svm_free_and_destroy_model(&model_);
}

SupportVectorRegressionModel::SupportVectorRegressionModel(const vector<MlObservation>& observations, const vector<MlTarget>& targets)
{
    // assumes all observations have same number of features
    size_t numFeatures = observations[0].size();

    //setup targets
    //auto v = ConvertVecOfVecToVec(targetsMat);
    double *targetsPtr = const_cast<double *>(&targets[0]); // why aren't the targets const?

    LibSvmTargetObservationConvertor conv;
    svm_node **observationsPtr = conv.ConvertObservations(observations, numFeatures);

    // setup observations
    //svm_node **observations = BuildObservations(observationsMat, numFeatures);

    // setup problem
    svm_problem problem;
    problem.l = targets.size();
    problem.y = targetsPtr;
    problem.x = observationsPtr;

    // specific to out training sets
    // TODO:    This is hard coded. 
    //          Bust out these values for use in constructor
    param_.C = 0.4;                 // cost
    param_.svm_type = 4;            // SVR
    param_.kernel_type = 2;         // radial
    param_.nu = 0.6;                // SVR nu
                                    // These values are the defaults used in the Matlab version
                                    // as found in svm_model_matlab.c
    param_.gamma = 1.0 / (double)numFeatures;
    param_.coef0 = 0;
    param_.cache_size = 100;        // in MB
    param_.shrinking = 1;
    param_.probability = 0;
    param_.degree = 3;
    param_.eps = 1e-3;
    param_.p = 0.1;
    param_.shrinking = 1;
    param_.probability = 0;
    param_.nr_weight = 0;
    param_.weight_label = NULL;
    param_.weight = NULL;

    // suppress command line output
    svm_set_print_string_function([](auto c) {});

    model_ = svm_train(&problem, &param_);
}

double SupportVectorRegressionModel::Predict(const vector<double>& observation) const
{
    LibSvmTargetObservationConvertor conv;
    svm_node *obsNode = conv.ConvertMatToSvmNode(observation);
    double prediction = svm_predict(model_, obsNode);
    return prediction;
}

SupportVectorRegressionModel::SupportVectorRegressionModel(const FilePath & modelFile)
{
    model_ = svm_load_model(modelFile.Path().c_str());
}
private:
    svm_model *model_;
    svm_parameter param_;
};

Option 1 is actually pretty reasonable. If you save the model in libsvm's C format through matlab, then it is straightforward to work with the model in C/C++ using functions provided by libsvm. Trying to work with matlab-formatted data in C++ will probably be much more difficult.

The main function in "svm-predict.c" (located in the root directory of the libsvm package) probably has most of what you need:

if((model=svm_load_model(argv[i+1]))==0)
{
    fprintf(stderr,"can't open model file %s\n",argv[i+1]);
    exit(1);
}

To predict a label for example x using the model, you can run

int predict_label = svm_predict(model,x);

The trickiest part of this will be to transfer your data into the libsvm format (unless your data is in the libsvm text file format, in which case you can just use the predict function in "svm-predict.c").

A libsvm vector, x , is an array of struct svm_node that represents a sparse array of data. Each svm_node has an index and a value, and the vector must be terminated by an index that is set to -1. For instance, to encode the vector [0,1,0,5] , you could do the following:

struct svm_node *x = (struct svm_node *) malloc(3*sizeof(struct svm_node));
x[0].index=2; //NOTE: libsvm indices start at 1
x[0].value=1.0;
x[1].index=4;
x[1].value=5.0;
x[2].index=-1;

For SVM types other than the classifier (C_SVC), look at the predict function in "svm-predict.c".

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM