Packages

An erlang library for SVM classification and regression based on libsvm.

Current section

Files

Jump to
esvm c_src esvm.cc
Raw

c_src/esvm.cc

#include "esvm.h"
#include "esvm_nif.h"
#include "nif_utils.h"
#include "macros.h"
#include "svm.h"
#include <vector>
#include <algorithm>
#include <set>
typedef std::vector<svm_node> FeatureVector;
namespace esvm {
namespace {
const char kFailedToAllocResourceMsg[] = "enif_alloc_resource failed";
struct enif_svm_model
{
svm_model* model_;
svm_problem* prob_;
};
void init_params(svm_parameter* param)
{
param->svm_type = C_SVC;
param->kernel_type = RBF;
param->degree = 3;
param->gamma = 0.5;
param->coef0 = 0;
param->nu = 0.5;
param->cache_size = 100;
param->C = 1;
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;
}
ERL_NIF_TERM parse_params(ErlNifEnv* env, ERL_NIF_TERM list, svm_parameter* params, bool* has_gamma)
{
if(!enif_is_list(env, list))
return false;
ERL_NIF_TERM head;
const ERL_NIF_TERM *items;
int arity;
std::string key;
int int_value;
double double_value;
while(enif_get_list_cell(env, list, &head, &list))
{
if(!enif_get_tuple(env, head, &arity, &items) || arity != 2)
return make_badarg(env);
if(!get_string(env, items[0], &key))
return make_badarg(env);
if(key == "svm_type")
{
if(!enif_get_int(env, items[1], &int_value))
return make_error(env, "invalid 'svm_type' value");
params->svm_type = int_value;
}
else if(key == "kernel_type")
{
if(!enif_get_int(env, items[1], &int_value))
return make_error(env, "invalid 'kernel_type' value");
params->kernel_type = int_value;
}
else if(key == "degree")
{
if(!enif_get_int(env, items[1], &int_value))
return make_error(env, "invalid 'degree' value");
params->degree = int_value;
}
else if(key == "gamma")
{
if(!get_double(env, items[1], &double_value))
return make_error(env, "invalid 'gamma' value");
params->gamma = double_value;
*has_gamma = true;
}
else if(key == "coef0")
{
if(!get_double(env, items[1], &double_value))
return make_error(env, "invalid 'coef0' value");
params->coef0 = double_value;
}
else if(key == "cache_size")
{
if(!get_double(env, items[1], &double_value))
return make_error(env, "invalid 'cache_size' value");
params->cache_size = double_value;
}
else if(key == "eps")
{
if(!get_double(env, items[1], &double_value))
return make_error(env, "invalid 'eps' value");
params->eps = double_value;
}
else if(key == "C")
{
if(!get_double(env, items[1], &double_value))
return make_error(env, "invalid 'C' value");
params->C = double_value;
}
else if(key == "nu")
{
if(!get_double(env, items[1], &double_value))
return make_error(env, "invalid 'nu' value");
params->nu = double_value;
}
else if(key == "p")
{
if(!get_double(env, items[1], &double_value))
return make_error(env, "invalid 'p' value");
params->p = double_value;
}
else if(key == "shrinking")
{
if(!enif_get_int(env, items[1], &int_value))
return make_error(env, "invalid 'shrinking' value");
params->shrinking = int_value;
}
else if(key == "probability")
{
if(!enif_get_int(env, items[1], &int_value))
return make_error(env, "invalid 'probability' value");
params->probability = int_value;
}
}
return ATOMS.atomOk;
}
bool parse_feature_vector(ErlNifEnv* env, ERL_NIF_TERM list, FeatureVector& feature_vector)
{
if(!enif_is_list(env, list))
return false;
ERL_NIF_TERM head;
const ERL_NIF_TERM *items;
int arity;
while(enif_get_list_cell(env, list, &head, &list))
{
if(!enif_get_tuple(env, head, &arity, &items) || arity != 2)
return false;
svm_node node;
if(!enif_get_int(env, items[0], &node.index))
return false;
if(!get_double(env, items[1], &node.value))
return false;
feature_vector.push_back(node);
}
// Sort the feature vector based on index (required by libsvm)
std::sort(feature_vector.begin(), feature_vector.end(), [](const svm_node& a, const svm_node& b) {
return a.index < b.index;
});
// push back end node as required by libsvm
feature_vector.push_back({-1, 0});
return true;
}
}
void nif_svm_model_free(ErlNifEnv* env, void* obj)
{
UNUSED(env);
enif_svm_model* data = static_cast<enif_svm_model*>(obj);
if(data->model_ != nullptr)
{
svm_free_model_content(data->model_);
data->model_ = nullptr;
}
if(data->prob_ != nullptr)
{
// release memory for svm_problem
for (int i = 0; i < data->prob_->l; ++i)
delete[] data->prob_->x[i];
delete[] data->prob_->x;
delete data->prob_;
data->prob_ = nullptr;
}
}
ERL_NIF_TERM nif_svm_model_create(ErlNifEnv* env, int argc, const ERL_NIF_TERM argv[])
{
UNUSED(argc);
ERL_NIF_TERM list = argv[0];
if(!enif_is_list(env, list))
return make_badarg(env);
unsigned int estimated_length;
if(!enif_get_uint(env, argv[1], &estimated_length))
return make_badarg(env);
bool has_gamma_param = false;
svm_parameter param;
init_params(&param);
ERL_NIF_TERM parse_params_result = parse_params(env, argv[2], &param, &has_gamma_param);
if(parse_params_result != ATOMS.atomOk)
return parse_params_result;
// step 1: parse training data
std::vector<double> labels;
std::vector<FeatureVector> features;
labels.reserve(estimated_length);
features.reserve(estimated_length);
ERL_NIF_TERM head;
const ERL_NIF_TERM *items;
int arity;
size_t max_feature_size = 0;
while(enif_get_list_cell(env, list, &head, &list))
{
if(!enif_get_tuple(env, head, &arity, &items) || arity != 2)
return make_badarg(env);
FeatureVector feature;
double label;
if(!get_double(env, items[0], &label))
return make_error(env, "invalid label type");
if(!parse_feature_vector(env, items[1], feature))
return make_error(env, "invalid feature vector format");
if(max_feature_size < feature.size())
max_feature_size = feature.size();
labels.push_back(label);
features.push_back(feature);
}
if(!has_gamma_param && max_feature_size > 2)
param.gamma = 1.0f/static_cast<double>(max_feature_size-1);
// step 2: prepare the problem data for SVM
esvm_data* data = static_cast<esvm_data*>(enif_priv_data(env));
enif_svm_model* enif_obj = static_cast<enif_svm_model*>(enif_alloc_resource(data->resEsvmModel, sizeof(enif_svm_model)));
if(enif_obj == NULL)
return make_error(env, esvm::kFailedToAllocResourceMsg);
svm_problem* prob = new svm_problem;
prob->l = static_cast<int>(features.size());
prob->y = new double[prob->l];
prob->x = new svm_node*[prob->l];
for (int i = 0; i < prob->l; ++i)
{
prob->y[i] = labels[i];
const FeatureVector& feature = features[i];
prob->x[i] = new svm_node[feature.size()];
for (size_t j = 0; j < feature.size(); ++j)
prob->x[i][j] = feature[j];
}
const char* error = svm_check_parameter(prob, &param);
if(error != nullptr)
{
enif_obj->model_ = nullptr;
enif_obj->prob_ = prob;
nif_svm_model_free(env, enif_obj);
enif_release_resource(enif_obj);
return make_error(env, error);
}
// step 3: train svm classifier
enif_obj->model_ = svm_train(prob, &param);
enif_obj->prob_ = prob;
ERL_NIF_TERM term = enif_make_resource(env, enif_obj);
enif_release_resource(enif_obj);
return make_ok_result(env, term);
}
ERL_NIF_TERM nif_svm_model_load(ErlNifEnv* env, int argc, const ERL_NIF_TERM argv[])
{
UNUSED(argc);
std::string filename;
if(!get_string(env, argv[0], &filename))
return make_badarg(env);
svm_model* model = svm_load_model(filename.c_str());
if(model == nullptr)
return make_error(env, "failed to load the model");
esvm_data* data = static_cast<esvm_data*>(enif_priv_data(env));
enif_svm_model* enif_obj = static_cast<enif_svm_model*>(enif_alloc_resource(data->resEsvmModel, sizeof(enif_svm_model)));
if(enif_obj == NULL)
{
svm_free_model_content(model);
return make_error(env, esvm::kFailedToAllocResourceMsg);
}
enif_obj->model_ = model;
enif_obj->prob_ = nullptr;
ERL_NIF_TERM term = enif_make_resource(env, enif_obj);
enif_release_resource(enif_obj);
return make_ok_result(env, term);
}
ERL_NIF_TERM nif_svm_model_save(ErlNifEnv* env, int argc, const ERL_NIF_TERM argv[])
{
UNUSED(argc);
enif_svm_model* enif_obj = NULL;
esvm_data* data = static_cast<esvm_data*>(enif_priv_data(env));
if(!enif_get_resource(env, argv[0], data->resEsvmModel, reinterpret_cast<void**>(&enif_obj)))
return make_badarg(env);
std::string filename;
if(!get_string(env, argv[1], &filename))
return make_badarg(env);
if(svm_save_model(filename.c_str(), enif_obj->model_) == -1)
return ATOMS.atomFalse;
return ATOMS.atomTrue;
}
ERL_NIF_TERM nif_svm_model_predict(ErlNifEnv* env, int argc, const ERL_NIF_TERM argv[])
{
UNUSED(argc);
enif_svm_model* enif_obj = NULL;
esvm_data* data = static_cast<esvm_data*>(enif_priv_data(env));
if(!enif_get_resource(env, argv[0], data->resEsvmModel, reinterpret_cast<void**>(&enif_obj)))
return make_badarg(env);
FeatureVector feature;
if(!parse_feature_vector(env, argv[1], feature))
return make_badarg(env);
return make_ok_result(env, enif_make_double(env, svm_predict(enif_obj->model_, feature.data())));
}
}