Initial release of "regularized risk minimization" project
This commit is contained in:
@@ -0,0 +1,23 @@
|
||||
librule(
|
||||
name = "svm",
|
||||
|
||||
sources = ["regmin.cc"],
|
||||
|
||||
headers = ["opt_smo.h", "opt_sgd.h", "opt_md.h", "opt_tgd.h", "regmin.h", "regmin_data.h"],
|
||||
|
||||
deplibs = ["fastlib:fastlib"],
|
||||
|
||||
)
|
||||
|
||||
binrule(
|
||||
# The name of the executable.
|
||||
name = "regmin",
|
||||
|
||||
sources = ["regmin.cc"],
|
||||
|
||||
headers = ["opt_smo.h", "opt_sgd.h", "opt_md.h", "opt_tgd.h", "regmin.h", "regmin_data.h"],
|
||||
|
||||
deplibs = [":svm"]
|
||||
|
||||
)
|
||||
|
||||
@@ -0,0 +1,420 @@
|
||||
/**
|
||||
* @author Hua Ouyang
|
||||
*
|
||||
* @file opt_md.h
|
||||
*
|
||||
* This head file contains functions for performing L1-regularized linear loss optimization, using Mirror Descent
|
||||
*
|
||||
*
|
||||
* @see svm.h
|
||||
*/
|
||||
|
||||
#ifndef U_SVM_OPT_MD_H
|
||||
#define U_SVM_OPT_MD_H
|
||||
|
||||
#include "fastlib/fastlib.h"
|
||||
|
||||
const double MD_ZERO = 1.0e-30;
|
||||
|
||||
template<typename TKernel>
|
||||
class MD {
|
||||
FORBID_ACCIDENTAL_COPIES(MD);
|
||||
|
||||
public:
|
||||
typedef TKernel Kernel;
|
||||
|
||||
private:
|
||||
int learner_typeid_;
|
||||
|
||||
Kernel kernel_;
|
||||
const Dataset_sl *dataset_;
|
||||
index_t n_data_; /* number of data samples */
|
||||
index_t n_features_; /* # of features == # of row - 1, exclude the last row (for labels) */
|
||||
// index_t n_features_bias_; /* # of features + 1 , [x, 1], for the bias term */
|
||||
|
||||
index_t n_sv_; /* number of support vectors */
|
||||
|
||||
index_t w_nnz_;
|
||||
|
||||
double round_thd_;
|
||||
|
||||
ArrayList<int> y_; /* list that stores "labels" */
|
||||
|
||||
ArrayList<NZ_entry> w_; /* the slope of the decision hyperplane, including bias: [w, b] */
|
||||
ArrayList<NZ_entry> w_p_; /* coefficients for positive w_t+ */
|
||||
ArrayList<NZ_entry> w_n_; /* coefficients for negative w_t- */
|
||||
|
||||
double scale_w_; // the scale for w
|
||||
|
||||
// parameters
|
||||
double C_; // \|w\|_1^1 \leq C
|
||||
double lambda_; // regularization parameter. lambda = 1/(C*n_data)
|
||||
index_t n_iter_; // number of iterations
|
||||
index_t n_epochs_; // number of epochs
|
||||
double accuracy_; // accuracy for stopping creterion
|
||||
double eta_; // step length. eta = 1/(lambda*t)
|
||||
double t_;
|
||||
|
||||
//bool is_constant_step_size_; // whether use constant step size (default) or not
|
||||
|
||||
ArrayList<index_t> old_from_new_; // for generating a random sequence of training data
|
||||
|
||||
public:
|
||||
MD() {}
|
||||
~MD() {}
|
||||
|
||||
/**
|
||||
* Initialization for parameters
|
||||
*/
|
||||
void InitPara(int learner_typeid, ArrayList<double> ¶m_) {
|
||||
// init parameters
|
||||
if (learner_typeid == 0) { // SVM_C
|
||||
C_ = param_[0];
|
||||
n_epochs_ = (index_t)param_[2];
|
||||
n_iter_ = (index_t)param_[3];
|
||||
accuracy_ = param_[4];
|
||||
}
|
||||
else if (learner_typeid == 1) { // SVM_R
|
||||
}
|
||||
}
|
||||
|
||||
void Train(int learner_typeid, Dataset_sl &dataset_in);
|
||||
|
||||
Kernel& kernel() {
|
||||
return kernel_;
|
||||
}
|
||||
|
||||
void GetW(ArrayList<NZ_entry> &w_out) {
|
||||
index_t wp_size = w_p_.size();
|
||||
index_t ct_nz = 0;
|
||||
w_out.Init(w_nnz_);
|
||||
for (index_t i=0; i<wp_size; i++) {
|
||||
if (fabs(w_p_[i].value) >= round_thd_ ) {
|
||||
w_out[ct_nz].index = w_p_[i].index;
|
||||
w_out[ct_nz].value = w_p_[i].value;
|
||||
ct_nz ++;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
double ScaleW() const {
|
||||
return scale_w_;
|
||||
}
|
||||
|
||||
//void GetSV(ArrayList<index_t> &dataset_index, ArrayList<double> &coef, ArrayList<bool> &sv_indicator);
|
||||
|
||||
private:
|
||||
/**
|
||||
* Loss functions
|
||||
*/
|
||||
double LossFunction_(int learner_typeid, double yy_hat) {
|
||||
if (learner_typeid_ == 0) { // SVM_C
|
||||
return HingeLoss_(yy_hat);
|
||||
}
|
||||
else if (learner_typeid_ == 1) { // SVM_R
|
||||
return 0.0; // TODO
|
||||
}
|
||||
else
|
||||
return HingeLoss_(yy_hat);
|
||||
}
|
||||
|
||||
/**
|
||||
* Gradient of loss functions
|
||||
*/
|
||||
double LossFunctionGradient_(int learner_typeid, double yy_hat) {
|
||||
if (learner_typeid_ == 0) { // SVM_C
|
||||
return HingeLossGradient_(yy_hat);
|
||||
//return LogisticLossGradient_(yy_hat);
|
||||
}
|
||||
else if (learner_typeid_ == 1) { // SVM_R
|
||||
return 0.0; // TODO
|
||||
}
|
||||
else {
|
||||
if (yy_hat < 1.0)
|
||||
return 1.0;
|
||||
else
|
||||
return 0.0;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Hinge Loss function
|
||||
*/
|
||||
double HingeLoss_(double yy_hat) {
|
||||
if (yy_hat < 1.0)
|
||||
return 1.0 - yy_hat;
|
||||
else
|
||||
return 0.0;
|
||||
}
|
||||
|
||||
/**
|
||||
* Gradient of the Hinge Loss function
|
||||
*/
|
||||
double HingeLossGradient_(double yy_hat) {
|
||||
if (yy_hat < 1.0)
|
||||
return -1.0;
|
||||
else
|
||||
return 0.0;
|
||||
}
|
||||
|
||||
/**
|
||||
* Gradient of the Logistic Loss function
|
||||
*/
|
||||
double LogisticLossGradient_(double yy_hat) {
|
||||
double tmp = exp(-yy_hat);
|
||||
return -tmp/(1+tmp);
|
||||
}
|
||||
|
||||
void LearnersInit_(int learner_typeid);
|
||||
|
||||
int TrainIteration_();
|
||||
|
||||
double GetC_(index_t i) {
|
||||
return C_;
|
||||
}
|
||||
|
||||
};
|
||||
|
||||
|
||||
/**
|
||||
* Initialization according to different SVM learner types
|
||||
*
|
||||
* @param: learner type id
|
||||
*/
|
||||
template<typename TKernel>
|
||||
void MD<TKernel>::LearnersInit_(int learner_typeid) {
|
||||
index_t i;
|
||||
double C_iv_f = C_/(n_features_);
|
||||
double C_iv_2f = C_iv_f/2.0;
|
||||
|
||||
learner_typeid_ = learner_typeid;
|
||||
|
||||
// init w, w+, w-
|
||||
w_.Init(0);
|
||||
w_p_.Init(n_features_);
|
||||
w_n_.Init(n_features_);
|
||||
for (i=0; i<n_features_; i++) {
|
||||
w_p_[i].index = i;
|
||||
w_p_[i].value = C_iv_2f; // TODO
|
||||
w_n_[i].index = i;
|
||||
w_n_[i].value = C_iv_2f; // TODO
|
||||
}
|
||||
|
||||
y_.Init(n_data_);
|
||||
for (i = 0; i < n_data_; i++) {
|
||||
y_[i] = (dataset_->y)[i] > 0 ? 1 : -1;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* L1-regularization training for 2-classes
|
||||
*
|
||||
* @param: input 2-classes data matrix with labels (1,-1) in the last row
|
||||
*/
|
||||
template<typename TKernel>
|
||||
void MD<TKernel>::Train(int learner_typeid, Dataset_sl &dataset_in) {
|
||||
index_t i, j, epo, ct;
|
||||
|
||||
index_t total_n_iter;
|
||||
//double DX, M, x_sq_sup;
|
||||
double cons_step, w_sum = 1.0;
|
||||
|
||||
/* general learner-independent initializations */
|
||||
dataset_ = &dataset_in;
|
||||
n_data_ = dataset_->n_points;
|
||||
n_features_ = dataset_->n_features;
|
||||
|
||||
printf("n_data=%d,n_feature=%d\n", n_data_, n_features_);
|
||||
|
||||
if (n_epochs_ > 0) { // # of epochs provided, use it
|
||||
n_iter_ = n_data_;
|
||||
total_n_iter = n_iter_ * n_epochs_;
|
||||
}
|
||||
else { // # of epochs not provided, use n_iter_ to count iterations
|
||||
n_epochs_ = 1; // not exactly one epoch, just use it for one loop
|
||||
total_n_iter = n_iter_;
|
||||
}
|
||||
|
||||
DEBUG_ASSERT(C_ != 0);
|
||||
lambda_ = 1.0/(C_*n_data_);
|
||||
|
||||
/* learners initialization */
|
||||
LearnersInit_(learner_typeid);
|
||||
old_from_new_.Init(n_data_);
|
||||
|
||||
// determine step sizes
|
||||
double x_sq_sp = 0.0;
|
||||
NZ_entry *xs;
|
||||
xs = (dataset_->x)[0];
|
||||
while (xs->index != -1) {
|
||||
x_sq_sp += math::Sqr(xs->value);
|
||||
++xs;
|
||||
}
|
||||
//eta_ = 1/(4.0*x_sq_sp);
|
||||
t_ = 4.0 * x_sq_sp;
|
||||
|
||||
cons_step = 1;
|
||||
|
||||
index_t work_idx_old = 0;
|
||||
|
||||
/* Begin training iterations */
|
||||
double yt, yt_hat, yy_hat;
|
||||
scale_w_ = 1.0;
|
||||
|
||||
printf("MD training begins...\n");
|
||||
for (epo = 0; epo<n_epochs_; epo++) {
|
||||
/* To mimic the online learning senario, in each epoch,
|
||||
we randomly permutate the training set, indexed by old_from_new_ */
|
||||
for (i=0; i<n_data_; i++) {
|
||||
old_from_new_[i] = i;
|
||||
}
|
||||
for (i=0; i<n_data_; i++) {
|
||||
j = rand() % n_data_;
|
||||
swap(old_from_new_[i], old_from_new_[j]);
|
||||
}
|
||||
|
||||
ct = 0;
|
||||
while (ct <= n_iter_) {
|
||||
work_idx_old = old_from_new_[ct % n_data_];
|
||||
//eta_ = 1.0 /(lambda_ * sqrt(t_));
|
||||
//eta_ = 1.0 / (lambda_ * t_);
|
||||
//eta_ = 1.0 / t_;
|
||||
eta_ = 1.0 /sqrt(t_);
|
||||
|
||||
NZ_entry *xt;
|
||||
xt = (dataset_->x)[work_idx_old];
|
||||
yt = y_[work_idx_old];
|
||||
yt_hat = SparseDot(w_p_, xt) - SparseDot(w_n_, xt);
|
||||
yy_hat = yt * yt_hat;
|
||||
|
||||
double eta_grad_tmp = eta_* yt * LossFunctionGradient_(learner_typeid, yy_hat);
|
||||
//printf("epo:%d, ct:%d, eta_grad:%f\n", epo, ct, eta_grad_tmp);
|
||||
if (eta_grad_tmp != 0) {
|
||||
xt = (dataset_->x)[work_idx_old];
|
||||
index_t ct_w = 0;
|
||||
|
||||
while (ct_w < w_p_.size() && xt->index != -1) {
|
||||
if (w_p_[ct_w].index < xt->index) {
|
||||
++ct_w;
|
||||
}
|
||||
else if (w_p_[ct_w].index == xt->index) {
|
||||
w_p_[ct_w].value = w_p_[ct_w].value * exp(-eta_grad_tmp * xt->value);
|
||||
++ct_w;
|
||||
++xt;
|
||||
}
|
||||
else { // w_p_[ct_w].index > xt->index
|
||||
++xt;
|
||||
}
|
||||
}
|
||||
ct_w = 0;
|
||||
xt = (dataset_->x)[work_idx_old];
|
||||
while (ct_w < w_n_.size() && xt->index != -1) {
|
||||
if (w_n_[ct_w].index < xt->index) {
|
||||
++ct_w;
|
||||
}
|
||||
else if (w_n_[ct_w].index == xt->index) {
|
||||
w_n_[ct_w].value = w_n_[ct_w].value * exp(eta_grad_tmp * xt->value);
|
||||
++ct_w;
|
||||
++xt;
|
||||
}
|
||||
else { // w_n_[ct_w].index > xt->index
|
||||
++xt;
|
||||
}
|
||||
}
|
||||
|
||||
// calc sum_i(w_p_i+w_n_i)
|
||||
w_sum = 0.0;
|
||||
for (i=0; i<w_p_.size(); i++) {
|
||||
w_sum += w_p_[i].value;
|
||||
}
|
||||
for (i=0; i<w_n_.size(); i++) {
|
||||
w_sum += w_n_[i].value;
|
||||
}
|
||||
|
||||
if (w_sum > C_) {
|
||||
// printf("epo:%d,iter:%d, w_sum=%f\n", epo, ct, w_sum);
|
||||
SparseScale(C_/w_sum, w_p_);
|
||||
SparseScale(C_/w_sum, w_n_);
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
//printf("epo:%d, ct:%d-- sacle_w:%f\n", epo, ct, scale_w_);
|
||||
|
||||
/*
|
||||
for (i=0; i<w_p_.size(); i++)
|
||||
printf("w_p[%d]=%f\n", i, w_p_[i].value);
|
||||
for (i=0; i<w_n_.size(); i++)
|
||||
printf("w_n[%d]=%f\n", i, w_n_[i].value);
|
||||
for (i=0; i<w_.size(); i++)
|
||||
printf("w[%d]=%f\n", i, w_[i].value);
|
||||
*/
|
||||
|
||||
t_ += 1.0;
|
||||
ct ++;
|
||||
}
|
||||
}// for epo
|
||||
|
||||
SparseSubOverwrite(w_n_, w_p_); // w_p <= w_p - w_n
|
||||
|
||||
// rounding w
|
||||
index_t wp_size = w_p_.size();
|
||||
round_thd_ = fx_param_double(NULL, "thd", 1.0e-5);
|
||||
w_nnz_ = 0;
|
||||
for (ct=0; ct<wp_size; ct++) {
|
||||
if (fabs(w_p_[ct].value) >= round_thd_) { // TODO: thresholding
|
||||
w_nnz_++;
|
||||
}
|
||||
}
|
||||
|
||||
printf("%d out of %d features are non zero. NZ rate:%f\n", w_nnz_, n_features_, (double)(w_nnz_)/(double)n_features_);
|
||||
|
||||
/*
|
||||
// find max(abs(w_i))
|
||||
double wi_abs_max = -INFINITY;
|
||||
double wi_abs;
|
||||
for (i=0; i<w_.size(); i++) {
|
||||
wi_abs = fabs(w_[i].value);
|
||||
if (wi_abs > wi_abs_max) {
|
||||
wi_abs_max = wi_abs;
|
||||
}
|
||||
}
|
||||
// round small w_i to 0
|
||||
index_t w_ct = 0;
|
||||
double round_factor = fx_param_double(NULL, "round_factor", 1.0e32);
|
||||
double round_thd = wi_abs_max / round_factor;
|
||||
for (i=0; i<w_.size(); i++) {
|
||||
//printf("w[%d]=%f\n", i, w_[i].value);
|
||||
if ( fabs(w_[i].value) > round_thd ) {
|
||||
w_ct ++;
|
||||
//printf("w_dim:%d, w_value:%lf\n", i, w_[i]);
|
||||
}
|
||||
else {
|
||||
w_.Remove(i);
|
||||
}
|
||||
}
|
||||
printf("%d out of %d features are non zero\n", w_ct, n_features_);
|
||||
*/
|
||||
|
||||
// Calculate objective value; default: no calculation to save time
|
||||
int objvalue = fx_param_int(NULL, "objvalue", 0);
|
||||
if (objvalue > 0) {
|
||||
double hinge_loss = 0.0, loss_sum= 0.0;
|
||||
|
||||
// primal objective value
|
||||
for (i=0; i< n_data_; i++) {
|
||||
hinge_loss = 1- y_[i] * SparseDot(w_p_, (dataset_->x)[i]);
|
||||
if (hinge_loss > 0) {
|
||||
loss_sum += hinge_loss;
|
||||
}
|
||||
}
|
||||
|
||||
printf("Primal objective value: %lf\n", loss_sum);
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
#endif
|
||||
|
||||
|
||||
@@ -0,0 +1,564 @@
|
||||
/**
|
||||
* @author Hua Ouyang
|
||||
*
|
||||
* @file opt_sgd.h
|
||||
*
|
||||
* This head file contains functions for performing Stochastic Gradient Descent (SGD) based optimization for SVM
|
||||
*
|
||||
* The algorithms in the following papers are implemented:
|
||||
*
|
||||
* 1. SGD for linear SVM
|
||||
* @ARTICLE{Zhang_SGD,
|
||||
* author = "Tong Zhang",
|
||||
* title = "{Solving Large Scale Linear Prediction Problems Using Stochastic Gradient Descent Algorithms}",
|
||||
* booktitle = "{International Conference on Machine Learning}",
|
||||
* year = 2004,
|
||||
* }
|
||||
*
|
||||
* 2. SGD for nonlinear SVM
|
||||
* @ARTICLE{Kivinen_SGD,
|
||||
* author = "Jyrki Kivinen",
|
||||
* title = "{Online Learning with Kernels}",
|
||||
* booktitle = NIPS,
|
||||
* number = 14,
|
||||
* year = 2001,
|
||||
* }
|
||||
*
|
||||
* @see svm.h
|
||||
*/
|
||||
|
||||
#ifndef U_SVM_OPT_SGD_H
|
||||
#define U_SVM_OPT_SGD_H
|
||||
|
||||
#include "fastlib/fastlib.h"
|
||||
|
||||
// tolerance of sacale_w
|
||||
const double SGD_SCALE_W_TOLERANCE = 1.0e-9;
|
||||
// threshold that determines whether an alpha is a SV or not
|
||||
const double SGD_ALPHA_ZERO = 1.0e-7;
|
||||
|
||||
|
||||
template<typename TKernel>
|
||||
class SGD {
|
||||
FORBID_ACCIDENTAL_COPIES(SGD);
|
||||
|
||||
public:
|
||||
typedef TKernel Kernel;
|
||||
|
||||
private:
|
||||
int learner_typeid_;
|
||||
|
||||
Kernel kernel_;
|
||||
const Dataset_sl *dataset_;
|
||||
index_t n_data_; /* number of data samples */
|
||||
index_t n_features_; /* # of features == # of row - 1, exclude the last row (for labels) */
|
||||
|
||||
Vector coef_; /* alpha*y, to be optimized */
|
||||
index_t n_alpha_; /* number of lagrangian multipliers in the dual */
|
||||
index_t n_sv_; /* number of support vectors */
|
||||
|
||||
index_t i_cache_, j_cache_; /* indices for the most recently cached kernel value */
|
||||
double cached_kernel_value_; /* cache */
|
||||
|
||||
ArrayList<int> y_; /* list that stores "labels" */
|
||||
ArrayList<NZ_entry> w_;
|
||||
double bias_;
|
||||
double scale_w_; // the scale for w
|
||||
|
||||
// parameters
|
||||
double C_; // for SVM_C
|
||||
double epsilon_; // for SVM_R
|
||||
bool b_linear_; // whether it's a linear SVM
|
||||
double lambda_; // regularization parameter. lambda = 1/(C*n_data)
|
||||
index_t n_iter_; // number of iterations
|
||||
index_t n_epochs_; // number of epochs
|
||||
double accuracy_; // accuracy for stopping creterion
|
||||
double eta_; // step length. eta = 1/(lambda*t)
|
||||
double t_;
|
||||
|
||||
ArrayList<index_t> old_from_new_; // for generating a random sequence of training data
|
||||
//ArrayList<index_t> new_from_old_; // for generating a random sequence of training data
|
||||
|
||||
double rho_;// for soft margin nonlinear SGD SVM
|
||||
|
||||
public:
|
||||
SGD() {}
|
||||
~SGD() {}
|
||||
|
||||
/**
|
||||
* Initialization for parameters
|
||||
*/
|
||||
void InitPara(int learner_typeid, ArrayList<double> ¶m_) {
|
||||
// init parameters
|
||||
if (learner_typeid == 0) { // SVM_C
|
||||
C_ = param_[0];
|
||||
b_linear_ = param_[2]>0.0 ? false: true; // whether it's a linear learner
|
||||
n_epochs_ = (index_t)param_[3];
|
||||
n_iter_ = (index_t)param_[4];
|
||||
accuracy_ = param_[5];
|
||||
}
|
||||
else if (learner_typeid == 1) { // SVM_R
|
||||
}
|
||||
}
|
||||
|
||||
void Train(int learner_typeid, const Dataset_sl &dataset_in);
|
||||
|
||||
Kernel& kernel() {
|
||||
return kernel_;
|
||||
}
|
||||
|
||||
double Bias() const {
|
||||
return bias_;
|
||||
}
|
||||
|
||||
void GetW(ArrayList<NZ_entry> &w_out) {
|
||||
w_out.InitCopy(w_, w_.size());
|
||||
}
|
||||
|
||||
double ScaleW() const {
|
||||
return scale_w_;
|
||||
}
|
||||
|
||||
void GetSV(ArrayList<index_t> &dataset_index, ArrayList<double> &coef, ArrayList<bool> &sv_indicator);
|
||||
|
||||
private:
|
||||
|
||||
/**
|
||||
* Loss functions
|
||||
*/
|
||||
double LossFunction_(int learner_typeid, double yy_hat) {
|
||||
if (learner_typeid_ == 0) { // SVM_C
|
||||
return HingeLoss_(yy_hat);
|
||||
}
|
||||
else if (learner_typeid_ == 1) { // SVM_R
|
||||
return 0.0; // TODO
|
||||
}
|
||||
else
|
||||
return HingeLoss_(yy_hat);
|
||||
}
|
||||
|
||||
/**
|
||||
* Gradient of loss functions
|
||||
*/
|
||||
double LossFunctionGradient_(int learner_typeid, double yy_hat) {
|
||||
if (learner_typeid_ == 0) { // SVM_C
|
||||
return HingeLossGradient_(yy_hat);
|
||||
}
|
||||
else if (learner_typeid_ == 1) { // SVM_R
|
||||
return 0.0; // TODO
|
||||
}
|
||||
else {
|
||||
if (yy_hat < 1.0)
|
||||
return 1.0;
|
||||
else
|
||||
return 0.0;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Hinge Loss function
|
||||
*/
|
||||
double HingeLoss_(double yy_hat) {
|
||||
if (yy_hat < 1.0)
|
||||
return 1.0 - yy_hat;
|
||||
else
|
||||
return 0.0;
|
||||
}
|
||||
|
||||
/**
|
||||
* Gradient of the Hinge Loss function
|
||||
*/
|
||||
double HingeLossGradient_(double yy_hat) {
|
||||
if (yy_hat < 1.0)
|
||||
return -1.0;
|
||||
else
|
||||
return 0.0;
|
||||
}
|
||||
|
||||
void LearnersInit_(int learner_typeid);
|
||||
|
||||
int TrainIteration_();
|
||||
|
||||
double GetC_(index_t i) {
|
||||
return C_;
|
||||
}
|
||||
|
||||
/**
|
||||
* Calculate kernel values
|
||||
*/
|
||||
double CalcKernelValue_(index_t i, index_t j) {
|
||||
// for SVM_R where n_alpha_==2*n_data_
|
||||
if (learner_typeid_ == 1) {
|
||||
i = i >= n_data_ ? (i-n_data_) : i;
|
||||
j = j >= n_data_ ? (j-n_data_) : j;
|
||||
}
|
||||
|
||||
// Check cache
|
||||
//if (i == i_cache_ && j == j_cache_) {
|
||||
// return cached_kernel_value_;
|
||||
//}
|
||||
|
||||
// Do Caching. Store the recently caculated kernel values.
|
||||
//i_cache_ = i;
|
||||
//j_cache_ = j;
|
||||
|
||||
cached_kernel_value_ = kernel_.Eval((dataset_->x)[i], (dataset_->x)[j]);
|
||||
|
||||
return cached_kernel_value_;
|
||||
}
|
||||
};
|
||||
|
||||
|
||||
/**
|
||||
* Initialization according to different SVM learner types
|
||||
*
|
||||
* @param: learner type id
|
||||
*/
|
||||
template<typename TKernel>
|
||||
void SGD<TKernel>::LearnersInit_(int learner_typeid) {
|
||||
index_t i;
|
||||
learner_typeid_ = learner_typeid;
|
||||
rho_ = fx_param_double(NULL, "rho", 1.0); // specify the soft margin. default value 1.0: hard margin
|
||||
|
||||
w_.Init(0); // all coefs of w_ are init as 0
|
||||
|
||||
if (learner_typeid_ == 0) { // SVM_C
|
||||
if (b_linear_) { // linear SVM
|
||||
coef_.Init(0); // not used, plain init
|
||||
}
|
||||
else { // nonlinear SVM
|
||||
n_alpha_ = n_data_;
|
||||
coef_.Init(n_alpha_);
|
||||
coef_.SetZero();
|
||||
|
||||
//w_.Init(0); // not used, plain init
|
||||
}
|
||||
|
||||
y_.Init(n_data_);
|
||||
for (i = 0; i < n_data_; i++) {
|
||||
y_[i] = (dataset_->y)[i] > 0 ? 1 : -1;
|
||||
}
|
||||
}
|
||||
else if (learner_typeid_ == 1) { // SVM_R
|
||||
// TODO
|
||||
n_alpha_ = 2 * n_data_;
|
||||
|
||||
coef_.Init(n_alpha_);
|
||||
coef_.SetZero();
|
||||
|
||||
y_.Init(n_alpha_);
|
||||
for (i = 0; i < n_data_; i++) {
|
||||
y_[i] = 1; // -> alpha_i
|
||||
y_[i + n_data_] = -1; // -> alpha_i^*
|
||||
}
|
||||
}
|
||||
else if (learner_typeid_ == 2) { // SVM_DE
|
||||
// TODO
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Steepest descent based SGD training for 2-classes
|
||||
*
|
||||
* @param: input 2-classes data matrix with labels (1,-1) in the last row
|
||||
*/
|
||||
template<typename TKernel>
|
||||
void SGD<TKernel>::Train(int learner_typeid, const Dataset_sl &dataset_in) {
|
||||
index_t i, j, epo, ct;
|
||||
// Load data
|
||||
dataset_ = &dataset_in;
|
||||
n_data_ = dataset_->n_points;
|
||||
n_features_ = dataset_->n_features;
|
||||
|
||||
/* general learner-independent initializations */
|
||||
if (n_epochs_ > 0) { // # of epochs provided, use it
|
||||
n_iter_ = n_data_;
|
||||
}
|
||||
else { // # of epochs not provided, use n_iter_ to count iterations
|
||||
n_epochs_ = 1; // not exactly one epoch, just use it for one loop
|
||||
}
|
||||
|
||||
DEBUG_ASSERT(C_ != 0);
|
||||
lambda_ = 1.0/(C_*n_data_);
|
||||
bias_ = 0.0;
|
||||
|
||||
/* learners initialization */
|
||||
LearnersInit_(learner_typeid);
|
||||
old_from_new_.Init(n_data_);
|
||||
|
||||
index_t work_idx_old = 0;
|
||||
|
||||
/* Begin SGD iterations */
|
||||
if (b_linear_) { // linear SVM, output: w, bias
|
||||
double yt, yt_hat, yy_hat;
|
||||
double sqrt_n = sqrt(n_data_);
|
||||
double eta0 = sqrt_n / max(1.0, LossFunctionGradient_(learner_typeid, -sqrt_n)); // initial step length
|
||||
t_ = 1.0 / (eta0 * lambda_);
|
||||
scale_w_ = 1.0;
|
||||
|
||||
printf("SGD_linear training begins...\n");
|
||||
for (epo = 0; epo<n_epochs_; epo++) {
|
||||
/* To mimic the online learning senario, in each epoch,
|
||||
we randomly permutate the training set, indexed by old_from_new_ */
|
||||
for (i=0; i<n_data_; i++) {
|
||||
old_from_new_[i] = i;
|
||||
}
|
||||
for (i=0; i<n_data_; i++) {
|
||||
j = rand() % n_data_;
|
||||
swap(old_from_new_[i], old_from_new_[j]);
|
||||
}
|
||||
|
||||
ct = 0;
|
||||
while (ct <= n_iter_) {
|
||||
work_idx_old = old_from_new_[ct % n_data_];
|
||||
|
||||
eta_ = 1.0 / (lambda_ * t_); // update step size
|
||||
scale_w_ = scale_w_ - scale_w_ / t_; // update scale of w
|
||||
//la::Scale(scale_w, &w_); // Note: moving w's scaling calculation to the testing session is faster
|
||||
|
||||
if (scale_w_ < SGD_SCALE_W_TOLERANCE) {
|
||||
SparseScale(scale_w_, w_);
|
||||
scale_w_ = 1.0;
|
||||
//printf("epo %d: scale_w tolerance reached.\n", epo);
|
||||
}
|
||||
NZ_entry *xt;
|
||||
xt = (dataset_->x)[work_idx_old];
|
||||
yt = y_[work_idx_old];
|
||||
//yt_hat = la::Dot(w_, xt) * scale_w_ + bias_;
|
||||
yt_hat = SparseDot(w_, xt) * scale_w_ + bias_;
|
||||
yy_hat = yt * yt_hat;
|
||||
|
||||
// update w by Stochastic Gradient Descent: w_{t+1} = w_t - eta_t * grad_t = (1-eta*lambda) * w_t + eta * [yt*xt]^+
|
||||
double eta_grad = eta_ * LossFunctionGradient_(learner_typeid, yy_hat) * yt;
|
||||
if (eta_grad != 0) {
|
||||
// Note: moving w's scaling calculation w_t*(1-1/t) to the testing session is faster
|
||||
SparseAddExpert(-eta_grad / scale_w_, xt, w_);
|
||||
// update bias
|
||||
bias_ -= eta_grad * 0.01;
|
||||
}
|
||||
|
||||
t_ += 1.0;
|
||||
ct ++;
|
||||
}
|
||||
} // for epo
|
||||
|
||||
// Calculate objective value; default: no calculation to save time
|
||||
int objvalue = fx_param_int(NULL, "objvalue", 0);
|
||||
if (objvalue > 0) {
|
||||
double v = 0.0, hinge_loss = 0.0, loss_sum= 0.0;
|
||||
|
||||
// primal objective value
|
||||
for (i=0; i< n_data_; i++) {
|
||||
NZ_entry *xt;
|
||||
xt = (dataset_->x)[work_idx_old];
|
||||
//hinge_loss = 1- y_[i] * (scale_w_ * la::Dot(w_, xt) + bias_);
|
||||
hinge_loss = 1- y_[i] * (scale_w_ * SparseDot(w_, xt) + bias_);
|
||||
if (hinge_loss > 0) {
|
||||
loss_sum += hinge_loss * C_;
|
||||
}
|
||||
}
|
||||
|
||||
for (j=0; j<w_.size(); j++) {
|
||||
v += w_[j].value * w_[j].value;
|
||||
}
|
||||
v = v * scale_w_ * scale_w_ / 2.0 + loss_sum;
|
||||
|
||||
printf("Primal objective value: %lf\n", v);
|
||||
|
||||
|
||||
// find max(abs(w_i))
|
||||
double wi_abs_max = -INFINITY;
|
||||
double wi_abs;
|
||||
for (i=0; i<w_.size(); i++) {
|
||||
wi_abs = fabs(w_[i].value);
|
||||
if (wi_abs > wi_abs_max) {
|
||||
wi_abs_max = wi_abs;
|
||||
}
|
||||
}
|
||||
// round small w_i to 0
|
||||
index_t w_ct = 0;
|
||||
double round_factor = fx_param_double(NULL, "round_factor", 1.0e32);
|
||||
double round_thd = wi_abs_max / round_factor;
|
||||
for (i=0; i<w_.size(); i++) {
|
||||
if ( fabs(w_[i].value) > round_thd ) {
|
||||
w_ct ++;
|
||||
}
|
||||
else {
|
||||
w_.Remove(i);
|
||||
}
|
||||
}
|
||||
printf("%d out of %d features are non zero\n", w_ct, n_features_);
|
||||
}
|
||||
|
||||
}
|
||||
else { // nonlinear SVM, output: coefs(i.e. alpha*y), bias
|
||||
// it's more expensive to calc the accuracy than linear SVM, so we just use n_iter_ as stop criterion
|
||||
double delta;
|
||||
double yt, yt_hat, yy_hat;
|
||||
double one_minus_eta_lambda;
|
||||
index_t n_real_epo, n_data_res;
|
||||
double kernel_value;
|
||||
|
||||
Vector b_calc_kernel;
|
||||
b_calc_kernel.Init(n_data_);
|
||||
b_calc_kernel.SetAll(0);
|
||||
|
||||
Vector coef_long;
|
||||
n_iter_ = n_iter_ * n_epochs_;
|
||||
coef_long.Init(n_iter_);
|
||||
|
||||
// initial step length
|
||||
//double sqrt_n = sqrt(n_data_);
|
||||
//double eta0 = sqrt_n / max(1.0, LossFunctionGradient_(learner_typeid, -sqrt_n)); // initial step length
|
||||
//t_ = 1.0 / (eta0 * lambda_);
|
||||
//double eta0 = 1.0 / (2*lambda_);
|
||||
|
||||
/* To mimic the online learning senario, we randomly permutate the training set, indexed by old_from_new_ */
|
||||
for (i=0; i<n_data_; i++) {
|
||||
old_from_new_[i] = i;
|
||||
}
|
||||
for (i=0; i<n_data_; i++) {
|
||||
j = rand() % n_data_;
|
||||
swap(old_from_new_[i], old_from_new_[j]);
|
||||
}
|
||||
|
||||
t_ = 1.0;
|
||||
ct = 0;
|
||||
|
||||
printf("SGD_kernel training begins...\n");
|
||||
while (ct < n_iter_) {
|
||||
work_idx_old = old_from_new_[ct % n_data_];
|
||||
|
||||
yt = y_[work_idx_old];
|
||||
yt_hat = 0.0;
|
||||
|
||||
n_real_epo = index_t(ceil(ct/n_data_));
|
||||
n_data_res = ct - n_real_epo * n_data_;
|
||||
if (n_real_epo > 0) {
|
||||
for (i=0; i<ct; i++) {
|
||||
if (fabs(coef_long[i]) >= SGD_ALPHA_ZERO) {
|
||||
b_calc_kernel[i%n_data_] = 1;
|
||||
}
|
||||
}
|
||||
for (i=0; i<n_data_res; i++) {
|
||||
if (b_calc_kernel[i]>0) {
|
||||
kernel_value = CalcKernelValue_(old_from_new_[i], work_idx_old);
|
||||
for (j=0; j<n_real_epo+1; j++) {
|
||||
yt_hat += coef_long[j*n_data_+i] * kernel_value;
|
||||
}
|
||||
}
|
||||
}
|
||||
for (i=n_data_res; i<n_data_; i++) {
|
||||
if (b_calc_kernel[i]>0) {
|
||||
kernel_value = CalcKernelValue_(old_from_new_[i], work_idx_old);
|
||||
for (j=0; j<n_real_epo; j++) {
|
||||
yt_hat += coef_long[j*n_data_+i] * kernel_value;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
else { // ct < n_data_
|
||||
for (i=0; i<ct; i++) {
|
||||
if (fabs(coef_long[i]) >= SGD_ALPHA_ZERO) {
|
||||
yt_hat += coef_long[i] * CalcKernelValue_(old_from_new_[i], work_idx_old);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
yt_hat += bias_;
|
||||
yy_hat = yt * yt_hat;
|
||||
|
||||
// update step length
|
||||
eta_ = 1.0 / (lambda_ * t_);
|
||||
//eta_ = eta0 / sqrt(t_);
|
||||
|
||||
if (ct >= 1) {
|
||||
bias_ += coef_long[ct-1];
|
||||
}
|
||||
|
||||
// update old coefs (for i<t)
|
||||
one_minus_eta_lambda = 1.0 - eta_ * lambda_;
|
||||
//printf("%d: %f\n", ct, one_minus_eta_lambda);
|
||||
for (i=0; i<ct; i++) {
|
||||
coef_long[i] = coef_long[i] * one_minus_eta_lambda;
|
||||
}
|
||||
|
||||
// update current coef (for i==t)
|
||||
|
||||
//coef_long[ct] = eta_ * LossFunctionGradient_(learner_typeid, yy_hat) * yt; // Hard margin SVM
|
||||
//printf("%f, %f, %f\n", eta_, LossFunctionGradient_(learner_typeid, yy_hat), yt);
|
||||
|
||||
// soft margin svm
|
||||
if (yy_hat <= rho_) {
|
||||
//printf("%d: %f, %f\n", ct, yy_hat, yt);
|
||||
delta = 1.0;
|
||||
}
|
||||
else {
|
||||
delta = 0.0;
|
||||
}
|
||||
coef_long[ct] = eta_ * delta * yt;
|
||||
|
||||
// update bias
|
||||
//bias_ += coef_long[ct];
|
||||
|
||||
t_ += 1.0;
|
||||
ct ++;
|
||||
}
|
||||
// convert coef_long to coef_
|
||||
for (i=0; i<n_iter_; i++) {
|
||||
work_idx_old = old_from_new_[i % n_data_];
|
||||
coef_[work_idx_old] = coef_[work_idx_old] + coef_long[i];
|
||||
}
|
||||
} // else
|
||||
}
|
||||
|
||||
|
||||
/* Get results for nonlinear SGD: coefficients(alpha*y), number and indecies of SVs
|
||||
*
|
||||
* @param: sample indices of the training (sub)set in the total training set
|
||||
* @param: support vector coefficients: alpha*y
|
||||
* @param: bool indicators FOR THE TRAINING SET: is/isn't a support vector
|
||||
*
|
||||
*/
|
||||
template<typename TKernel>
|
||||
void SGD<TKernel>::GetSV(ArrayList<index_t> &dataset_index, ArrayList<double> &coef, ArrayList<bool> &sv_indicator) {
|
||||
n_sv_ = 0;
|
||||
if (learner_typeid_ == 0) {// SVM_C
|
||||
for (index_t i = 0; i < n_data_; i++) {
|
||||
if (fabs(coef_[i]) >= SGD_ALPHA_ZERO) { // support vectors found
|
||||
coef.PushBack() = coef_[i];
|
||||
sv_indicator[dataset_index[i]] = true;
|
||||
n_sv_++;
|
||||
}
|
||||
else {
|
||||
coef.PushBack() = 0;
|
||||
}
|
||||
}
|
||||
printf("Number of support vectors: %d.\n", n_sv_);
|
||||
}
|
||||
else if (learner_typeid_ == 1) {// SVM_R
|
||||
// TODO
|
||||
/*
|
||||
for (index_t i = 0; i < n_data_; i++) {
|
||||
double alpha_diff = -alpha_[i] + alpha_[i+n_data_]; // alpha_i^* - alpha_i
|
||||
if (fabs(alpha_diff) >= SGD_ALPHA_ZERO) { // support vectors found
|
||||
coef.PushBack() = alpha_diff;
|
||||
sv_indicator[dataset_index[i]] = true;
|
||||
n_sv_++;
|
||||
}
|
||||
else {
|
||||
coef.PushBack() = 0;
|
||||
}
|
||||
}
|
||||
*/
|
||||
}
|
||||
}
|
||||
|
||||
#endif
|
||||
|
||||
// ./regmin --learner_name=svm_c --mode=train_test --train_data=heart_scale --test_data=heart_scale --opt=sgd --kernel=linear --normalize=0 --objvalue=1 --c=0.1 --n_iter=4000
|
||||
|
||||
// ./regmin --learner_name=svm_c --mode=train_test --train_data=heart_scale --test_data=heart_scale --opt=sgd --kernel=gaussian --normalize=0 --objvalue=1 --sigma=1 --c=10 --n_iter=5000
|
||||
|
||||
// ./regmin --learner_name=svm_c --mode=train_test --train_data=real_sim --test_data=real_sim --opt=sgd --kernel=linear --normalize=0 --objvalue=0 --c=1 --n_iter=140000
|
||||
@@ -0,0 +1,929 @@
|
||||
/**
|
||||
* @author Hua Ouyang
|
||||
*
|
||||
* @file opt_smo.h
|
||||
*
|
||||
* This head file contains functions for performing Sequential Minimal Optimization (SMO)
|
||||
*
|
||||
* The algorithms in the following papers are implemented:
|
||||
*
|
||||
* 1. SMO and Working set selecting using 1st order expansion
|
||||
* @ARTICLE{Platt_SMO,
|
||||
* author = "J. C. Platt",
|
||||
* title = "{Fast Training of Support Vector Machines using Sequential Minimal Optimization}",
|
||||
* booktitle = "{Advances in Kernel Methods - Support Vector Learning}",
|
||||
* year = 1999,
|
||||
* publisher = "MIT Press"
|
||||
* }
|
||||
*
|
||||
* 2. Shrinkng and Caching for SMO
|
||||
* @ARTICLE{Joachims_SVMLIGHT,
|
||||
* author = "T. Joachims",
|
||||
* title = "{Making large-Scale SVM Learning Practical}",
|
||||
* booktitle = "{Advances in Kernel Methods - Support Vector Learning}",
|
||||
* year = 1999,
|
||||
* publisher = "MIT Press"
|
||||
* }
|
||||
*
|
||||
* 3. Working set selecting using 2nd order expansion
|
||||
* @ARTICLE{Fan_JMLR,
|
||||
* author = "R. Fan, P. Chen, C. Lin",
|
||||
* title = "{Working Set Selection using Second Order Information for Training Support Vector Machines}",
|
||||
* journal = "{Jornal of Machine Learning Research}",
|
||||
* year = 2005
|
||||
* }
|
||||
*
|
||||
* @see svm.h
|
||||
*/
|
||||
|
||||
#ifndef U_SVM_OPT_SMO_H
|
||||
#define U_SVM_OPT_SMO_H
|
||||
|
||||
#include "fastlib/fastlib.h"
|
||||
#include "regmin_data.h"
|
||||
|
||||
|
||||
// maximum # of interations for SMO training
|
||||
const index_t MAX_NUM_ITER_SMO = 10000000;
|
||||
// after # of iterations to do shrinking
|
||||
const index_t SMO_NUM_FOR_SHRINKING = 1000;
|
||||
// threshold that determines whether need to do unshrinking
|
||||
const double SMO_UNSHRINKING_FACTOR = 10;
|
||||
// threshold that determines whether an alpha is a SV or not
|
||||
const double SMO_ALPHA_ZERO = 1.0e-7;
|
||||
// for indefinite kernels
|
||||
const double TAU = 1e-12;
|
||||
|
||||
const double SMO_ID_LOWER_BOUNDED = -1;
|
||||
const double SMO_ID_UPPER_BOUNDED = 1;
|
||||
const double SMO_ID_FREE = 0;
|
||||
|
||||
template <class T> inline void swap(T& x, T& y) { T t=x; x=y; y=t; }
|
||||
|
||||
template<typename TKernel>
|
||||
class SMO {
|
||||
FORBID_ACCIDENTAL_COPIES(SMO);
|
||||
|
||||
public:
|
||||
typedef TKernel Kernel;
|
||||
|
||||
private:
|
||||
int learner_typeid_;
|
||||
int hinge_; // do L2-SVM or L1-SVM, default: L1
|
||||
|
||||
index_t ct_iter_; /* counter for the number of iterations */
|
||||
index_t ct_shrinking_; /* counter for doing shrinking */
|
||||
bool do_shrinking_; // 1(default): do shrinking after 1000 iterations; 0: don't do shrinking
|
||||
|
||||
Kernel kernel_;
|
||||
index_t n_data_; /* number of data samples */
|
||||
Dataset_sl *dataset_; /* alias for the input dataste */
|
||||
|
||||
Vector alpha_; /* the alphas, to be optimized */
|
||||
Vector alpha_status_; /* ID_LOWER_BOUND (-1), ID_UPPER_BOUND (1), ID_FREE (0) */
|
||||
index_t n_sv_; /* number of support vectors */
|
||||
|
||||
index_t n_alpha_; /* number of variables to be optimized */
|
||||
index_t n_active_; /* number of samples in the active set */
|
||||
ArrayList<index_t> active_set_; /* list that stores the old indices of active alphas followed by inactive alphas. == old_from_new*/
|
||||
bool reconstructed_; /* indicator: where unshrinking has been carried out */
|
||||
index_t i_cache_, j_cache_; /* indices for the most recently cached kernel value */
|
||||
double cached_kernel_value_; /* cache */
|
||||
|
||||
ArrayList<int> y_; /* list that stores "labels" */
|
||||
|
||||
double bias_;
|
||||
|
||||
Vector grad_; /* gradient value */
|
||||
Vector grad_bar_; /* gradient value when treat un-upperbounded variables as 0: grad_bar_i==C\sum_{j:a_j=C} y_i y_j K_ij */
|
||||
|
||||
// parameters
|
||||
int budget_;
|
||||
double Cp_; // C_+, for SVM_C, y==1
|
||||
double Cn_; // C_-, for SVM_C, y==-1
|
||||
double C_;
|
||||
double inv_two_C_; // 1/2C
|
||||
double epsilon_; // for SVM_R
|
||||
int wss_; // working set selection scheme, 1 for 1st order expansion; 2 for 2nd order expansion
|
||||
index_t n_iter_; // number of iterations
|
||||
double accuracy_; // accuracy for stopping creterion
|
||||
double gap_; // for stopping criterion
|
||||
|
||||
public:
|
||||
SMO() {}
|
||||
~SMO() {}
|
||||
|
||||
/**
|
||||
* Initialization for parameters
|
||||
*/
|
||||
void InitPara(int learner_typeid, ArrayList<double> ¶m_) {
|
||||
// init parameters
|
||||
budget_ = (int)param_[0];
|
||||
wss_ = (int) param_[4];
|
||||
hinge_ = (int) param_[3];
|
||||
n_iter_ = (index_t) param_[5];
|
||||
n_iter_ = n_iter_ < MAX_NUM_ITER_SMO ? n_iter_: MAX_NUM_ITER_SMO;
|
||||
accuracy_ = param_[6];
|
||||
if (learner_typeid == 0) { // SVM_C
|
||||
if (hinge_==2) { // L2-SVM: squated hinge loss
|
||||
Cp_ = INFINITY;
|
||||
Cn_ = INFINITY;
|
||||
C_ = param_[1];
|
||||
inv_two_C_ = 1/(2*C_);
|
||||
}
|
||||
else { // L1-SVM
|
||||
Cp_ = param_[1];
|
||||
Cn_ = param_[2];
|
||||
}
|
||||
}
|
||||
else if (learner_typeid == 1) { // SVM_R
|
||||
Cp_ = param_[1];
|
||||
Cn_ = Cp_;
|
||||
epsilon_ = param_[2];
|
||||
}
|
||||
}
|
||||
|
||||
void Train(int learner_typeid, Dataset_sl &dataset_in);
|
||||
|
||||
Kernel& kernel() {
|
||||
return kernel_;
|
||||
}
|
||||
|
||||
double Bias() const {
|
||||
return bias_;
|
||||
}
|
||||
|
||||
void GetSV(ArrayList<index_t> &dataset_index, ArrayList<double> &coef, ArrayList<bool> &sv_indicator);
|
||||
|
||||
private:
|
||||
void LearnersInit_(int learner_typeid);
|
||||
|
||||
int SMOIterations_();
|
||||
|
||||
void ReconstructGradient_();
|
||||
|
||||
bool TestShrink_(index_t i, double y_grad_max, double y_grad_min);
|
||||
|
||||
void Shrinking_();
|
||||
|
||||
bool WorkingSetSelection_(index_t &i, index_t &j);
|
||||
|
||||
void UpdateGradientAlpha_(index_t i, index_t j);
|
||||
|
||||
void CalcBias_();
|
||||
|
||||
/* void GetVector_(index_t i, Vector *v) const {
|
||||
datamatrix_.MakeColumnSubvector(i, 0, datamatrix_.n_rows()-1, v);
|
||||
}
|
||||
*/
|
||||
|
||||
/**
|
||||
* Instead of C, we use C_+ and C_- to handle unbalanced data
|
||||
*/
|
||||
double GetC_(index_t i) {
|
||||
return (y_[i] > 0 ? Cp_ : Cn_);
|
||||
}
|
||||
|
||||
void UpdateAlphaStatus_(index_t i) {
|
||||
if (alpha_[i] >= GetC_(i)) {
|
||||
alpha_status_[i] = SMO_ID_UPPER_BOUNDED;
|
||||
}
|
||||
else if (alpha_[i] <= 0) {
|
||||
alpha_status_[i] = SMO_ID_LOWER_BOUNDED;
|
||||
}
|
||||
else { // 0 < alpha_[i] < C
|
||||
alpha_status_[i] = SMO_ID_FREE;
|
||||
}
|
||||
}
|
||||
|
||||
bool IsUpperBounded(index_t i) {
|
||||
return alpha_status_[i] == SMO_ID_UPPER_BOUNDED;
|
||||
}
|
||||
bool IsLowerBounded(index_t i) {
|
||||
return alpha_status_[i] == SMO_ID_LOWER_BOUNDED;
|
||||
}
|
||||
|
||||
/**
|
||||
* Calculate kernel values
|
||||
*/
|
||||
double CalcKernelValue_(index_t ii, index_t jj) {
|
||||
// the indices have been swaped in the shrinking processes
|
||||
index_t i = active_set_[ii]; // ii/jj: index in the new permuted set
|
||||
index_t j = active_set_[jj]; // i/j: index in the old set
|
||||
|
||||
// for SVM_R where n_alpha_==2*n_data_
|
||||
if (learner_typeid_ == 1) {
|
||||
i = i >= n_data_ ? (i-n_data_) : i;
|
||||
j = j >= n_data_ ? (j-n_data_) : j;
|
||||
}
|
||||
|
||||
// Check cache
|
||||
//if (i == i_cache_ && j == j_cache_) {
|
||||
// return cached_kernel_value_;
|
||||
//}
|
||||
|
||||
cached_kernel_value_ = kernel_.Eval((dataset_->x)[i], (dataset_->x)[j]);
|
||||
|
||||
if (hinge_ == 2) { // for L2-SVM
|
||||
if (i == j) {
|
||||
cached_kernel_value_ = cached_kernel_value_ + inv_two_C_;
|
||||
}
|
||||
}
|
||||
|
||||
return cached_kernel_value_;
|
||||
}
|
||||
};
|
||||
|
||||
|
||||
/**
|
||||
* Reconstruct inactive elements of G from G_bar and free variables
|
||||
*
|
||||
* @param: learner type id
|
||||
*/
|
||||
template<typename TKernel>
|
||||
void SMO<TKernel>::ReconstructGradient_() {
|
||||
index_t i, j;
|
||||
if (n_active_ == n_alpha_)
|
||||
return;
|
||||
if (learner_typeid_ == 0) { // SVM_C
|
||||
for (i=n_active_; i<n_alpha_; i++) {
|
||||
grad_[i] = 1 - grad_bar_[i];
|
||||
}
|
||||
}
|
||||
else if (learner_typeid_ == 1) { // SVM_R
|
||||
for (i=n_active_; i<n_alpha_; i++) {
|
||||
j = i >= n_data_ ? (i-n_data_) : i;
|
||||
//grad_[j] = grad_bar_[j] + datamatrix_.get(datamatrix_.n_rows()-1, active_set_[j]) - epsilon_; // TODO
|
||||
grad_[j] = grad_bar_[j] + (dataset_->y)[j] - epsilon_; // TODO
|
||||
}
|
||||
}
|
||||
|
||||
for (i=0; i<n_active_; i++) {
|
||||
if (alpha_status_[i] == SMO_ID_FREE) {
|
||||
for (j=n_active_; j<n_alpha_; j++) {
|
||||
grad_[j] = grad_[j] - y_[j] * alpha_[i] * y_[i] * CalcKernelValue_(i,j);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
/**
|
||||
* Test whether need to do shrinking for provided index and y_grad_max, y_grad_min
|
||||
*
|
||||
*/
|
||||
template<typename TKernel>
|
||||
bool SMO<TKernel>::TestShrink_(index_t i, double y_grad_max, double y_grad_min) {
|
||||
if (IsUpperBounded(i)) { // alpha_[i] = C
|
||||
if (y_[i] == 1) {
|
||||
return (grad_[i] > y_grad_max);
|
||||
}
|
||||
else { // y_[i] == -1
|
||||
return (grad_[i] + y_grad_min > 0); // -grad_[i]<y_grad_min
|
||||
}
|
||||
}
|
||||
else if (IsLowerBounded(i)) {
|
||||
if (y_[i] == 1) {
|
||||
return (grad_[i] < y_grad_min);
|
||||
}
|
||||
else { // y_[i] == -1
|
||||
return (grad_[i] + y_grad_max < 0); // -grad_[i]>y_grad_max
|
||||
}
|
||||
}
|
||||
else
|
||||
return false;
|
||||
}
|
||||
|
||||
/**
|
||||
* Do Shrinking. Temporarily remove alphas (from the active set) that are
|
||||
* unlikely to be selected in the working set, since they have reached their
|
||||
* lower/upper bound.
|
||||
*
|
||||
*/
|
||||
template<typename TKernel>
|
||||
void SMO<TKernel>::Shrinking_() {
|
||||
index_t t;
|
||||
|
||||
// Find m(a) == y_grad_max(i\in I_up) and M(a) == y_grad_min(j\in I_down)
|
||||
double y_grad_max = -INFINITY;
|
||||
double y_grad_min = INFINITY;
|
||||
for (t=0; t<n_active_; t++) { // find argmax(y*grad), t\in I_up
|
||||
if (y_[t] == 1) {
|
||||
if (!IsUpperBounded(t)) // t\in I_up, y==1: y[t]alpha[t] < C
|
||||
if (grad_[t] > y_grad_max) { // y==1
|
||||
y_grad_max = grad_[t];
|
||||
}
|
||||
}
|
||||
else { // y[t] == -1
|
||||
if (!IsLowerBounded(t)) // t\in I_up, y==-1: y[t]alpha[t] < 0
|
||||
if (grad_[t] + y_grad_max < 0) { // y==-1... <=> -grad_[t] > y_grad_max
|
||||
y_grad_max = -grad_[t];
|
||||
}
|
||||
}
|
||||
}
|
||||
for (t=0; t<n_active_; t++) { // find argmin(y*grad), t\in I_down
|
||||
if (y_[t] == 1) {
|
||||
if (!IsLowerBounded(t)) // t\in I_down, y==1: y[t]alpha[t] > 0
|
||||
if (grad_[t] < y_grad_min) { // y==1
|
||||
y_grad_min = grad_[t];
|
||||
}
|
||||
}
|
||||
else { // y[t] == -1
|
||||
if (!IsUpperBounded(t)) // t\in I_down, y==-1: y[t]alpha[t] > -C
|
||||
if (grad_[t] + y_grad_min > 0) { // y==-1...<=> -grad_[t] < y_grad_min
|
||||
y_grad_min = -grad_[t];
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Find the alpha to be shrunk
|
||||
printf("Shrinking...\n");
|
||||
for (t=0; t<n_active_; t++) {
|
||||
// Shrinking: put inactive alphas behind the active set
|
||||
if (TestShrink_(t, y_grad_max, y_grad_min)) {
|
||||
n_active_ --;
|
||||
while (n_active_ > t) {
|
||||
if (!TestShrink_(n_active_, y_grad_max, y_grad_min)) {
|
||||
swap(active_set_[t], active_set_[n_active_]);
|
||||
swap(alpha_[t], alpha_[n_active_]);
|
||||
swap(alpha_status_[t], alpha_status_[n_active_]);
|
||||
swap(y_[t], y_[n_active_]);
|
||||
swap(grad_[t], grad_[n_active_]);
|
||||
swap(grad_bar_[t], grad_bar_[n_active_]);
|
||||
break;
|
||||
}
|
||||
n_active_ --;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
double gap = y_grad_max - y_grad_min;
|
||||
//printf("%d: gap:%f, n_active:%d\n", ct_iter_, gap, n_active_);
|
||||
// do unshrinking for the first time when y_grad_max - y_grad_min <= SMO_UNSHRINKING_FACTOR * accuracy_
|
||||
if ( reconstructed_==false && gap <= SMO_UNSHRINKING_FACTOR * accuracy_ ) {
|
||||
printf("Unshrinking...\n");
|
||||
// Unshrinking: put shrinked alphas back to active set
|
||||
// 1.recover gradient
|
||||
ReconstructGradient_();
|
||||
// 2.recover active status
|
||||
for (t=n_alpha_-1; t>n_active_; t--) {
|
||||
if (!TestShrink_(t, y_grad_max, y_grad_min)) {
|
||||
while (n_active_ < t) {
|
||||
if (TestShrink_(n_active_, y_grad_max, y_grad_min)) {
|
||||
swap(active_set_[t], active_set_[n_active_]);
|
||||
swap(alpha_[t], alpha_[n_active_]);
|
||||
swap(alpha_status_[t], alpha_status_[n_active_]);
|
||||
swap(y_[t], y_[n_active_]);
|
||||
swap(grad_[t], grad_[n_active_]);
|
||||
swap(grad_bar_[t], grad_bar_[n_active_]);
|
||||
break;
|
||||
}
|
||||
n_active_ ++;
|
||||
}
|
||||
n_active_ ++;
|
||||
}
|
||||
}
|
||||
reconstructed_ = true; // indicator: unshrinking has been carried out in this round
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Initialization according to different SVM learner types
|
||||
*
|
||||
* @param: learner type id
|
||||
*/
|
||||
template<typename TKernel>
|
||||
void SMO<TKernel>::LearnersInit_(int learner_typeid) {
|
||||
index_t i;
|
||||
learner_typeid_ = learner_typeid;
|
||||
|
||||
if (learner_typeid_ == 0) { // SVM_C
|
||||
n_alpha_ = n_data_;
|
||||
|
||||
alpha_.Init(n_alpha_);
|
||||
alpha_.SetZero();
|
||||
|
||||
// initialize gradient
|
||||
grad_.Init(n_alpha_);
|
||||
grad_.SetAll(1.0);
|
||||
|
||||
y_.Init(n_alpha_);
|
||||
for (i = 0; i < n_alpha_; i++) {
|
||||
y_[i] = (dataset_->y)[i] > 0 ? 1 : -1;
|
||||
}
|
||||
}
|
||||
else if (learner_typeid_ == 1) { // SVM_R
|
||||
n_alpha_ = 2 * n_data_;
|
||||
|
||||
alpha_.Init(2 * n_alpha_); // TODO
|
||||
alpha_.SetZero();
|
||||
|
||||
// initialize gradient
|
||||
grad_.Init(n_alpha_);
|
||||
y_.Init(n_alpha_);
|
||||
for (i = 0; i < n_data_; i++) {
|
||||
y_[i] = 1; // -> alpha_i
|
||||
y_[i + n_data_] = -1; // -> alpha_i^*
|
||||
grad_[i] = epsilon_ - (dataset_->y)[i];
|
||||
grad_[i + n_data_] = epsilon_ + (dataset_->y)[i];
|
||||
}
|
||||
}
|
||||
else if (learner_typeid_ == 2) { // SVM_DE
|
||||
// TODO
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* SMO training for 2-classes
|
||||
*
|
||||
* @param: input 2-classes data matrix with labels (1,-1) in the last row
|
||||
*/
|
||||
template<typename TKernel>
|
||||
void SMO<TKernel>::Train(int learner_typeid, Dataset_sl &dataset_in) {
|
||||
index_t i;
|
||||
// Load data
|
||||
dataset_ = &dataset_in;
|
||||
n_data_ = dataset_->n_points;
|
||||
|
||||
// Learners initialization
|
||||
LearnersInit_(learner_typeid);
|
||||
|
||||
// General learner-independent initializations
|
||||
budget_ = min(budget_, n_data_);
|
||||
bias_ = 0.0;
|
||||
n_sv_ = 0;
|
||||
reconstructed_ = false;
|
||||
i_cache_ = -1; j_cache_ = -1;
|
||||
cached_kernel_value_ = INFINITY;
|
||||
|
||||
n_active_ = n_alpha_;
|
||||
active_set_.Init(n_alpha_);
|
||||
for (i=0; i<n_alpha_; i++) {
|
||||
active_set_[i] = i;
|
||||
}
|
||||
|
||||
alpha_status_.Init(n_alpha_);
|
||||
for (i=0; i<n_alpha_; i++)
|
||||
UpdateAlphaStatus_(i);
|
||||
|
||||
|
||||
// initialize gradient (already set to init values)
|
||||
/*
|
||||
for (i=0; i<n_alpha_; i++) {
|
||||
for(j=0; j<n_alpha_; j++) {
|
||||
if (!IsLowerbounded(j)) { // alpha_j >0
|
||||
grad_[i] = grad_[i] - y_[i] * y_[j] * alpha_[j] * CalcKernelValue_(i,j);
|
||||
}
|
||||
}
|
||||
}
|
||||
*/
|
||||
|
||||
// initialize gradient_bar
|
||||
grad_bar_.Init(n_alpha_);
|
||||
grad_bar_.SetZero();
|
||||
|
||||
do_shrinking_ = fx_param_int(NULL, "shrink", 0);
|
||||
ct_shrinking_ = min(n_data_, SMO_NUM_FOR_SHRINKING);
|
||||
/*
|
||||
if (do_shrinking_) {
|
||||
for (i=0; i<n_alpha_; i++) {
|
||||
for(j=0; j<n_alpha_; j++) {
|
||||
if(IsUpperBounded(j)) // alpha_j >= C
|
||||
grad_bar_[i] = grad_bar_[i] + GetC_(j) * y_[j] * CalcKernelValue_(i,j);
|
||||
}
|
||||
grad_bar_[i] = y_[i] * grad_bar_[i];
|
||||
}
|
||||
}
|
||||
*/
|
||||
|
||||
printf("SMO initialization done!\n");
|
||||
|
||||
// Begin SMO iterations
|
||||
ct_iter_ = 0;
|
||||
|
||||
int stop_condition = 0;
|
||||
while (1) {
|
||||
//for(index_t i=0; i<n_alpha_; i++)
|
||||
// printf("%f.\n", y_[i]*alpha_[i]);
|
||||
//printf("\n\n");
|
||||
|
||||
// for every min(n_data_, 1000) iterations, do shrinking
|
||||
if (do_shrinking_) {
|
||||
if ( --ct_shrinking_ == 0) {
|
||||
Shrinking_();
|
||||
ct_shrinking_ = min(n_data_, SMO_NUM_FOR_SHRINKING);
|
||||
}
|
||||
}
|
||||
|
||||
// Find working set, check stopping criterion, update gradient and alphas
|
||||
stop_condition = SMOIterations_();
|
||||
// Termination check, if stop_condition==1 or ==2 => SMO terminates
|
||||
if (stop_condition == 1) {// optimality reached
|
||||
// Calculate the bias term
|
||||
CalcBias_();
|
||||
printf("SMO terminates since the accuracy %f achieved!!! Number of iterations: %d.\n", accuracy_, ct_iter_);
|
||||
break;
|
||||
}
|
||||
else if (stop_condition == 2) {// max num of iterations exceeded
|
||||
// Calculate the bias term
|
||||
CalcBias_();
|
||||
fprintf(stderr, "SMO terminates since the number of iterations %d exceeded !!! Gap: %f.\n", n_iter_, gap_);
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* SMO training iterations
|
||||
*
|
||||
* @return: stopping condition id
|
||||
*/
|
||||
template<typename TKernel>
|
||||
int SMO<TKernel>::SMOIterations_() {
|
||||
ct_iter_ ++;
|
||||
index_t i,j;
|
||||
if (WorkingSetSelection_(i,j) == true) {
|
||||
if (!do_shrinking_) { // no shrinking, optimality reached
|
||||
return 1;
|
||||
}
|
||||
else { // shrinking, need to check whether optimality really reached
|
||||
ReconstructGradient_(); // restore the inactive alphas and reconstruct gradients
|
||||
n_active_ = n_alpha_;
|
||||
if (WorkingSetSelection_(i,j) == true) { // optimality reached
|
||||
return 1;
|
||||
}
|
||||
else {
|
||||
ct_shrinking_ = 1; // do shrinking in the next iteration
|
||||
return 0;
|
||||
}
|
||||
}
|
||||
}
|
||||
else if (ct_iter_ >= n_iter_) { // number of iterations exceeded
|
||||
if (!do_shrinking_) { // no shrinking, optimality reached
|
||||
return 2;
|
||||
}
|
||||
else if ( ct_iter_ >= min(n_data_, SMO_NUM_FOR_SHRINKING) ) { // shrinking has been carried out, need to calculate the true gap
|
||||
ReconstructGradient_(); // restore the inactive alphas and reconstruct gradients
|
||||
n_active_ = n_alpha_;
|
||||
WorkingSetSelection_(i,j);
|
||||
return 2;
|
||||
}
|
||||
else {
|
||||
return 2;
|
||||
}
|
||||
}
|
||||
else{ // update gradient, alphas and bias term, and continue iterations
|
||||
UpdateGradientAlpha_(i, j);
|
||||
return 0;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Try to find a working set (i,j). Both 1st(default) and 2nd order approximations of
|
||||
* the objective function Z(\alpha+\lambda u_ij)-Z(\alpha) are implemented.
|
||||
*
|
||||
* @param: reference to working set (i, j)
|
||||
*
|
||||
* @return: working set (i, j); indicator of whether the optimal solution is reached (true:reached)
|
||||
*/
|
||||
template<typename TKernel>
|
||||
bool SMO<TKernel>::WorkingSetSelection_(index_t &out_i, index_t &out_j) {
|
||||
double y_grad_max = -INFINITY;
|
||||
double y_grad_min = INFINITY;
|
||||
int idx_i = -1;
|
||||
int idx_j = -1;
|
||||
|
||||
// Find i using maximal violating pair scheme
|
||||
index_t t;
|
||||
for (t=0; t<n_active_; t++) { // find argmax(y*grad), t\in I_up
|
||||
if (y_[t] == 1) {
|
||||
if (!IsUpperBounded(t)) // t\in I_up, y==1: y[t]alpha[t] < C
|
||||
if (grad_[t] > y_grad_max) { // y==1
|
||||
y_grad_max = grad_[t];
|
||||
idx_i = t;
|
||||
}
|
||||
}
|
||||
else { // y[t] == -1
|
||||
if (!IsLowerBounded(t)) // t\in I_up, y==-1: y[t]alpha[t] < 0
|
||||
if (grad_[t] + y_grad_max < 0) { // y==-1... <=> -grad_[t] > y_grad_max
|
||||
y_grad_max = -grad_[t];
|
||||
idx_i = t;
|
||||
}
|
||||
}
|
||||
}
|
||||
out_i = idx_i; // i found
|
||||
|
||||
/* Find j using maximal violating pair scheme (1st order approximation) */
|
||||
if (wss_ == 1) {
|
||||
for (t=0; t<n_active_; t++) { // find argmin(y*grad), t\in I_down
|
||||
if (y_[t] == 1) {
|
||||
if (!IsLowerBounded(t)) // t\in I_down, y==1: y[t]alpha[t] > 0
|
||||
if (grad_[t] < y_grad_min) { // y==1
|
||||
y_grad_min = grad_[t];
|
||||
idx_j = t;
|
||||
}
|
||||
}
|
||||
else { // y[t] == -1
|
||||
if (!IsUpperBounded(t)) // t\in I_down, y==-1: y[t]alpha[t] > -C
|
||||
if (grad_[t] + y_grad_min > 0) { // y==-1...<=> -grad_[t] < y_grad_min
|
||||
y_grad_min = -grad_[t];
|
||||
idx_j = t;
|
||||
}
|
||||
}
|
||||
}
|
||||
out_j = idx_j; // j found
|
||||
}
|
||||
/* Find j using 2nd order working set selection scheme; need to calc kernels, but faster convergence */
|
||||
else if (wss_ == 2) {
|
||||
double K_ii = CalcKernelValue_(out_i, out_i);
|
||||
double opt_gain_max = -INFINITY;
|
||||
double grad_diff;
|
||||
double quad_kernel;
|
||||
double opt_gain = -INFINITY;
|
||||
for (t=0; t<n_active_; t++) {
|
||||
double K_it = CalcKernelValue_(out_i, t);
|
||||
double K_tt = CalcKernelValue_(t, t);
|
||||
if (y_[t] == 1) {
|
||||
if (!IsLowerBounded(t)) { // t\in I_down, y==1: y[t]alpha[t] > 0
|
||||
// calculate y_grad_min for Stopping Criterion
|
||||
if (grad_[t] < y_grad_min) // y==1
|
||||
y_grad_min = grad_[t];
|
||||
// find j
|
||||
grad_diff = y_grad_max - grad_[t]; // max(y_i*grad_i) - y_t*grad_t
|
||||
if (grad_diff > 0) {
|
||||
quad_kernel = K_ii + K_tt - 2 * K_it;
|
||||
if (quad_kernel > 0) // for positive definite kernels
|
||||
opt_gain = ( grad_diff * grad_diff ) / quad_kernel; // actually ../2*quad_kernel
|
||||
else // handle non-positive definite kernels
|
||||
opt_gain = ( grad_diff * grad_diff ) / TAU;
|
||||
// find max(opt_gain)
|
||||
if (opt_gain > opt_gain_max) {
|
||||
idx_j = t;
|
||||
opt_gain_max = opt_gain;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
else { // y[t] == -1
|
||||
if (!IsUpperBounded(t)) {// t\in I_down, y==-1: y[t]alpha[t] > -C
|
||||
// calculate y_grad_min for Stopping Criterion
|
||||
if (grad_[t] + y_grad_min > 0) // y==-1, -grad_[t] < y_grad_min
|
||||
y_grad_min = -grad_[t];
|
||||
// find j
|
||||
grad_diff = y_grad_max + grad_[t]; // max(y_i*grad_i) - y_t*grad_t
|
||||
if (grad_diff > 0) {
|
||||
quad_kernel = K_ii + K_tt - 2 * K_it;
|
||||
if (quad_kernel > 0) // for positive definite kernels
|
||||
opt_gain = ( grad_diff * grad_diff ) / quad_kernel; // actually ../2*quad_kernel
|
||||
else // handle non-positive definite kernels
|
||||
opt_gain = ( grad_diff * grad_diff ) / TAU;
|
||||
// find max(opt_gain)
|
||||
if (opt_gain > opt_gain_max) {
|
||||
idx_j = t;
|
||||
opt_gain_max = opt_gain;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
out_j = idx_j; // j found
|
||||
|
||||
//printf("y_i=%d, y_j=%d\n", y_[out_i], y_[out_j]);
|
||||
//printf("a_i=%f, a_j=%f\n", alpha_[out_i], alpha_[out_j]);
|
||||
|
||||
// Stopping Criterion check
|
||||
//printf("ct_iter:%d, accu:%f\n", ct_iter_, y_grad_max - y_grad_min);
|
||||
gap_ = y_grad_max - y_grad_min;
|
||||
//printf("%d: gap=%f\n", ct_iter_, gap_);
|
||||
if (gap_ <= accuracy_) {
|
||||
return true; // optimality reached
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
/**
|
||||
* Search direction; Update gradient, alphas and bias term
|
||||
*
|
||||
* @param: a working set (i,j) found by working set selection
|
||||
*
|
||||
*/
|
||||
template<typename TKernel>
|
||||
void SMO<TKernel>::UpdateGradientAlpha_(index_t i, index_t j) {
|
||||
index_t t;
|
||||
|
||||
double a_i = alpha_[i]; // old alphas
|
||||
double a_j = alpha_[j];
|
||||
int y_i = y_[i];
|
||||
int y_j = y_[j];
|
||||
double C_i = GetC_(i); // can be Cp (for y==1) or Cn (for y==-1)
|
||||
double C_j = GetC_(j);
|
||||
|
||||
// cached kernel values
|
||||
double K_ii, K_ij, K_jj;
|
||||
K_ii = CalcKernelValue_(i, i);
|
||||
K_ij = CalcKernelValue_(i, j);
|
||||
K_jj = CalcKernelValue_(j, j);
|
||||
|
||||
double first_order_diff = y_i * grad_[i] - y_j * grad_[j];
|
||||
double second_order_diff = K_ii + K_jj - 2 * K_ij;
|
||||
if (second_order_diff <= 0) // handle non-positive definite kernels
|
||||
second_order_diff = TAU;
|
||||
double lambda = first_order_diff / second_order_diff; // step size
|
||||
|
||||
//printf("step size=%f\n", lambda);
|
||||
|
||||
// Update alphas
|
||||
alpha_[i] = a_i + y_i * lambda;
|
||||
alpha_[j] = a_j - y_j * lambda;
|
||||
|
||||
// Handle bounds for updated alphas
|
||||
if (y_i != y_j) {
|
||||
double alpha_old_diff = a_i - a_j;
|
||||
if (alpha_old_diff > 0) {
|
||||
if (alpha_[j] < 0) {
|
||||
alpha_[j] = 0;
|
||||
alpha_[i] = alpha_old_diff;
|
||||
}
|
||||
}
|
||||
else { // alpha_old_diff <= 0
|
||||
if (alpha_[i] < 0) {
|
||||
alpha_[i] = 0;
|
||||
alpha_[j] = - alpha_old_diff;
|
||||
}
|
||||
}
|
||||
if (alpha_old_diff > C_i - C_j) {
|
||||
if (alpha_[i] > C_i) {
|
||||
alpha_[i] = C_i;
|
||||
alpha_[j] = C_i - alpha_old_diff;
|
||||
}
|
||||
}
|
||||
else {
|
||||
if (alpha_[j] > C_j) {
|
||||
alpha_[j] = C_j;
|
||||
alpha_[i] = C_j + alpha_old_diff;
|
||||
}
|
||||
}
|
||||
}
|
||||
else { // y_i == y_j
|
||||
double alpha_old_sum = a_i + a_j;
|
||||
if (alpha_old_sum > C_i) {
|
||||
if (alpha_[i] > C_i) {
|
||||
alpha_[i] = C_i;
|
||||
alpha_[j] = alpha_old_sum - C_i;
|
||||
}
|
||||
}
|
||||
else {
|
||||
if (alpha_[j] < 0) {
|
||||
alpha_[j] = 0;
|
||||
alpha_[i] = alpha_old_sum;
|
||||
}
|
||||
}
|
||||
if (alpha_old_sum > C_j) {
|
||||
if (alpha_[j] > C_j) {
|
||||
alpha_[j] = C_j;
|
||||
alpha_[i] = alpha_old_sum - C_j;
|
||||
}
|
||||
}
|
||||
else {
|
||||
if (alpha_[i] < 0) {
|
||||
alpha_[i] = 0;
|
||||
alpha_[j] = alpha_old_sum;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Update gradient
|
||||
double diff_i = alpha_[i] - a_i;
|
||||
double diff_j = alpha_[j] - a_j;
|
||||
for (t=0; t<n_active_; t++) {
|
||||
grad_[t] = grad_[t] - y_[t] * (y_[i] * diff_i * CalcKernelValue_(i, t) + y_[j] * diff_j * CalcKernelValue_(j, t));
|
||||
}
|
||||
|
||||
bool ub_i = IsUpperBounded(i);
|
||||
bool ub_j = IsUpperBounded(j);
|
||||
|
||||
// Update alpha active status
|
||||
UpdateAlphaStatus_(i);
|
||||
UpdateAlphaStatus_(j);
|
||||
|
||||
|
||||
if (do_shrinking_) {
|
||||
// Update gradient_bar
|
||||
if( ub_i != IsUpperBounded(i) ) { // updated_alpha_i >= C
|
||||
if(ub_i) // old_alpha_i >= C, new_alpha_i < C
|
||||
for(t=0; t<n_alpha_; t++)
|
||||
grad_bar_[t] = grad_bar_[t] - C_i * y_[i] * y_[t] * CalcKernelValue_(i, t);
|
||||
else // old_alpha_i < C, new_alpha_i >= C
|
||||
for(t=0; t<n_alpha_; t++)
|
||||
grad_bar_[t] = grad_bar_[t] + C_i * y_[i] * y_[t] * CalcKernelValue_(i, t);
|
||||
}
|
||||
if( ub_j != IsUpperBounded(j) ) {
|
||||
if(ub_j) // old_alpha_j >= C, new_alpha_j < C
|
||||
for(t=0; t<n_alpha_; t++)
|
||||
grad_bar_[t] = grad_bar_[t] - C_j * y_[j] * y_[t] * CalcKernelValue_(j, t);
|
||||
else // old_alpha_j < C, new_alpha_j >= C
|
||||
for(t=0; t<n_alpha_; t++)
|
||||
grad_bar_[t] = grad_bar_[t] + C_j * y_[j] * y_[t] * CalcKernelValue_(j, t);
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
/**
|
||||
* Calcualte bias term
|
||||
*
|
||||
* @return: the bias
|
||||
*
|
||||
*/
|
||||
template<typename TKernel>
|
||||
void SMO<TKernel>::CalcBias_() {
|
||||
double b;
|
||||
index_t n_free_alpha = 0;
|
||||
double ub = INFINITY, lb = -INFINITY, sum_free_yg = 0.0;
|
||||
|
||||
for (index_t i=0; i<n_active_; i++){
|
||||
double yg = y_[i] * grad_[i];
|
||||
|
||||
if (IsUpperBounded(i)) { // bounded: alpha_i >= C
|
||||
if(y_[i] == 1)
|
||||
lb = max(lb, yg);
|
||||
else
|
||||
ub = min(ub, yg);
|
||||
}
|
||||
else if (IsLowerBounded(i)) { // bounded: alpha_i <= 0
|
||||
if(y_[i] == -1)
|
||||
lb = max(lb, yg);
|
||||
else
|
||||
ub = min(ub, yg);
|
||||
}
|
||||
else { // free: 0< alpha_i <C
|
||||
n_free_alpha++;
|
||||
sum_free_yg += yg;
|
||||
}
|
||||
}
|
||||
|
||||
if(n_free_alpha>0)
|
||||
b = sum_free_yg / n_free_alpha;
|
||||
else
|
||||
b = (ub + lb) / 2;
|
||||
|
||||
bias_ = b;
|
||||
}
|
||||
|
||||
/* Get SVM results:coefficients, number and indecies of SVs
|
||||
*
|
||||
* @param: sample indices of the training (sub)set in the total training set
|
||||
* @param: support vector coefficients: alpha*y
|
||||
* @param: bool indicators FOR THE TRAINING SET: is/isn't a support vector
|
||||
*
|
||||
*/
|
||||
template<typename TKernel>
|
||||
void SMO<TKernel>::GetSV(ArrayList<index_t> &dataset_index, ArrayList<double> &coef, ArrayList<bool> &sv_indicator) {
|
||||
ArrayList<index_t> new_from_old; // it's used to retrieve the permuted new index from old index
|
||||
new_from_old.Init(n_alpha_);
|
||||
for (index_t i = 0; i < n_alpha_; i++) {
|
||||
new_from_old[active_set_[i]] = i;
|
||||
}
|
||||
if (learner_typeid_ == 0) {// SVM_C
|
||||
for (index_t ii = 0; ii < n_data_; ii++) {
|
||||
index_t i = new_from_old[ii]; // retrive the index of permuted vector
|
||||
if (alpha_[i] >= SMO_ALPHA_ZERO) { // support vectors found
|
||||
//printf("%f\n", alpha_[i] * y_[i]);
|
||||
coef.PushBack() = alpha_[i] * y_[i];
|
||||
sv_indicator[dataset_index[ii]] = true;
|
||||
n_sv_++;
|
||||
}
|
||||
else {
|
||||
coef.PushBack() = 0;
|
||||
}
|
||||
}
|
||||
printf("Number of SVs: %d\n", n_sv_);
|
||||
}
|
||||
else if (learner_typeid_ == 1) {// SVM_R
|
||||
for (index_t ii = 0; ii < n_data_; ii++) {
|
||||
index_t i = new_from_old[ii]; // retrive the index of permuted vector
|
||||
index_t iplusn = new_from_old[ii+n_data_];
|
||||
double alpha_diff = -alpha_[i] + alpha_[iplusn]; // alpha_i^* - alpha_i
|
||||
if (fabs(alpha_diff) >= SMO_ALPHA_ZERO) { // support vectors found
|
||||
coef.PushBack() = alpha_diff;
|
||||
sv_indicator[dataset_index[ii]] = true;
|
||||
n_sv_++;
|
||||
}
|
||||
else {
|
||||
coef.PushBack() = 0;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#endif
|
||||
|
||||
// ./regmin --learner_name=svm_c --mode=train_test --train_data=heart_scale --test_data=heart_scale --opt=smo --kernel=linear --normalize=0 --objvalue=1 --c=0.1 --wss=2 --shrink=1 --n_iter=300
|
||||
|
||||
// ./regmin --learner_name=svm_c --mode=train_test --train_data=heart_scale --test_data=heart_scale --opt=smo --kernel=gaussian --normalize=0 --objvalue=1 --wss=2 --shrink=1 --c=10 --sigma=1 --n_iter=300
|
||||
@@ -0,0 +1,390 @@
|
||||
/**
|
||||
* @author Hua Ouyang
|
||||
*
|
||||
* @file opt_tgd.h
|
||||
*
|
||||
* This head file contains functions for performing L1-regularized linear loss optimization, using Truncated Gradient Descent
|
||||
*
|
||||
*
|
||||
* @see svm.h
|
||||
*/
|
||||
|
||||
#ifndef U_SVM_OPT_TGD_H
|
||||
#define U_SVM_OPT_TGD_H
|
||||
|
||||
#include "fastlib/fastlib.h"
|
||||
|
||||
const double TGD_ZERO = 1.0e-30;
|
||||
|
||||
template<typename TKernel>
|
||||
class TGD {
|
||||
FORBID_ACCIDENTAL_COPIES(TGD);
|
||||
|
||||
public:
|
||||
typedef TKernel Kernel;
|
||||
|
||||
private:
|
||||
int learner_typeid_;
|
||||
|
||||
Kernel kernel_;
|
||||
const Dataset_sl *dataset_;
|
||||
index_t n_data_; /* number of data samples */
|
||||
index_t n_features_; /* # of features == # of row - 1, exclude the last row (for labels) */
|
||||
// index_t n_features_bias_; /* # of features + 1 , [x, 1], for the bias term */
|
||||
|
||||
index_t n_sv_; /* number of support vectors */
|
||||
|
||||
index_t w_nnz_;
|
||||
|
||||
double round_thd_;
|
||||
|
||||
ArrayList<int> y_; /* list that stores "labels" */
|
||||
|
||||
ArrayList<NZ_entry> w_; /* the slope of the decision hyperplane, including bias: [w, b] */
|
||||
|
||||
double scale_w_; // the scale for w
|
||||
|
||||
// parameters
|
||||
double C_; // \|w\|_1^1 \leq C
|
||||
double g_; // regularization parameter in Langford's paper. g = 1/C
|
||||
index_t n_iter_; // number of iterations
|
||||
index_t n_epochs_; // number of epochs
|
||||
double accuracy_; // accuracy for stopping creterion
|
||||
double eta_; // step length. eta = 1/sqrt(t)
|
||||
double t_;
|
||||
index_t k_; // perform truncation every k iterations
|
||||
|
||||
ArrayList<index_t> old_from_new_; // for generating a random sequence of training data
|
||||
|
||||
public:
|
||||
TGD() {}
|
||||
~TGD() {}
|
||||
|
||||
/**
|
||||
* Initialization for parameters
|
||||
*/
|
||||
void InitPara(int learner_typeid, ArrayList<double> ¶m_) {
|
||||
// init parameters
|
||||
if (learner_typeid == 0) { // SVM_C
|
||||
C_ = param_[0];
|
||||
n_epochs_ = (index_t)param_[2];
|
||||
n_iter_ = (index_t)param_[3];
|
||||
accuracy_ = param_[4];
|
||||
}
|
||||
else if (learner_typeid == 1) { // SVM_R
|
||||
}
|
||||
}
|
||||
|
||||
void Train(int learner_typeid, Dataset_sl &dataset_in);
|
||||
|
||||
Kernel& kernel() {
|
||||
return kernel_;
|
||||
}
|
||||
|
||||
void GetW(ArrayList<NZ_entry> &w_out) {
|
||||
index_t w_size = w_.size();
|
||||
index_t ct_nz = 0;
|
||||
w_out.Init(w_nnz_);
|
||||
for (index_t i=0; i<w_size; i++) {
|
||||
if (fabs(w_[i].value) >= round_thd_ ) {
|
||||
w_out[ct_nz].index = w_[i].index;
|
||||
w_out[ct_nz].value = w_[i].value;
|
||||
ct_nz ++;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
double ScaleW() const {
|
||||
return scale_w_;
|
||||
}
|
||||
|
||||
//void GetSV(ArrayList<index_t> &dataset_index, ArrayList<double> &coef, ArrayList<bool> &sv_indicator);
|
||||
|
||||
private:
|
||||
/**
|
||||
* Loss functions
|
||||
*/
|
||||
double LossFunction_(int learner_typeid, double yy_hat) {
|
||||
if (learner_typeid_ == 0) { // SVM_C
|
||||
return HingeLoss_(yy_hat);
|
||||
}
|
||||
else if (learner_typeid_ == 1) { // SVM_R
|
||||
return 0.0; // TODO
|
||||
}
|
||||
else
|
||||
return HingeLoss_(yy_hat);
|
||||
}
|
||||
|
||||
/**
|
||||
* Gradient of loss functions
|
||||
*/
|
||||
double LossFunctionGradient_(int learner_typeid, double yy_hat) {
|
||||
if (learner_typeid_ == 0) { // SVM_C
|
||||
//return HingeLossGradient_(yy_hat);
|
||||
return LogisticLossGradient_(yy_hat);
|
||||
}
|
||||
else if (learner_typeid_ == 1) { // SVM_R
|
||||
return 0.0; // TODO
|
||||
}
|
||||
else {
|
||||
if (yy_hat < 1.0)
|
||||
return 1.0;
|
||||
else
|
||||
return 0.0;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Hinge Loss function
|
||||
*/
|
||||
double HingeLoss_(double yy_hat) {
|
||||
if (yy_hat < 1.0)
|
||||
return 1.0 - yy_hat;
|
||||
else
|
||||
return 0.0;
|
||||
}
|
||||
|
||||
/**
|
||||
* Gradient of the Hinge Loss function
|
||||
*/
|
||||
double HingeLossGradient_(double yy_hat) {
|
||||
if (yy_hat < 1.0)
|
||||
return -1.0;
|
||||
else
|
||||
return 0.0;
|
||||
}
|
||||
|
||||
/**
|
||||
* Gradient of the Logistic Loss function
|
||||
*/
|
||||
double LogisticLossGradient_(double yy_hat) {
|
||||
double tmp = exp(-yy_hat);
|
||||
return -tmp/(1+tmp);
|
||||
}
|
||||
|
||||
void LearnersInit_(int learner_typeid);
|
||||
|
||||
int TrainIteration_();
|
||||
|
||||
double GetC_(index_t i) {
|
||||
return C_;
|
||||
}
|
||||
|
||||
};
|
||||
|
||||
|
||||
/**
|
||||
* Initialization according to different SVM learner types
|
||||
*
|
||||
* @param: learner type id
|
||||
*/
|
||||
template<typename TKernel>
|
||||
void TGD<TKernel>::LearnersInit_(int learner_typeid) {
|
||||
index_t i;
|
||||
|
||||
learner_typeid_ = learner_typeid;
|
||||
|
||||
// init w, w+, w-
|
||||
w_.Init(n_features_);
|
||||
for (i=0; i<n_features_; i++) {
|
||||
w_[i].index = i;
|
||||
w_[i].value = 0.0; // TODO
|
||||
}
|
||||
|
||||
y_.Init(n_data_);
|
||||
for (i = 0; i < n_data_; i++) {
|
||||
y_[i] = (dataset_->y)[i] > 0 ? 1 : -1;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* L1-regularization training for 2-classes
|
||||
*
|
||||
* @param: input 2-classes data matrix with labels (1,-1) in the last row
|
||||
*/
|
||||
template<typename TKernel>
|
||||
void TGD<TKernel>::Train(int learner_typeid, Dataset_sl &dataset_in) {
|
||||
index_t i, j, epo, ct, ct_k;
|
||||
|
||||
index_t total_n_iter;
|
||||
|
||||
k_ = fx_param_int(NULL, "k", 1);
|
||||
g_ = 1.0 / C_;
|
||||
|
||||
/* general learner-independent initializations */
|
||||
dataset_ = &dataset_in;
|
||||
n_data_ = dataset_->n_points;
|
||||
n_features_ = dataset_->n_features;
|
||||
|
||||
if (n_epochs_ > 0) { // # of epochs provided, use it
|
||||
n_iter_ = n_data_;
|
||||
total_n_iter = n_iter_ * n_epochs_;
|
||||
}
|
||||
else { // # of epochs not provided, use n_iter_ to count iterations
|
||||
n_epochs_ = 1; // not exactly one epoch, just use it for one loop
|
||||
total_n_iter = n_iter_;
|
||||
}
|
||||
|
||||
DEBUG_ASSERT(C_ != 0);
|
||||
|
||||
/* learners initialization */
|
||||
LearnersInit_(learner_typeid);
|
||||
old_from_new_.Init(n_data_);
|
||||
|
||||
index_t work_idx_old = 0;
|
||||
|
||||
/* Begin training iterations */
|
||||
double yt, yt_hat, yy_hat;
|
||||
//double sqrt_n = sqrt(n_data_);
|
||||
//double eta0 = sqrt_n / max(1.0, LossFunctionGradient_(learner_typeid, -sqrt_n)); // initial step length
|
||||
//double eta_grad = 0;
|
||||
//t_ = 1.0 / (eta0 * lambda_);
|
||||
t_ = 1.0;
|
||||
scale_w_ = 1.0;
|
||||
|
||||
ct_k = 0;
|
||||
printf("TGD training begins...\n");
|
||||
for (epo = 0; epo<n_epochs_; epo++) {
|
||||
/* To mimic the online learning senario, in each epoch,
|
||||
we randomly permutate the training set, indexed by old_from_new_ */
|
||||
for (i=0; i<n_data_; i++) {
|
||||
old_from_new_[i] = i;
|
||||
}
|
||||
for (i=0; i<n_data_; i++) {
|
||||
j = rand() % n_data_;
|
||||
swap(old_from_new_[i], old_from_new_[j]);
|
||||
}
|
||||
|
||||
ct = 0;
|
||||
while (ct <= n_iter_) {
|
||||
work_idx_old = old_from_new_[ct % n_data_];
|
||||
|
||||
// eta_ = 1.0 / t_;
|
||||
eta_ = 1.0 / sqrt(t_);
|
||||
|
||||
//scale_w_ = scale_w_ / w_sum;
|
||||
|
||||
NZ_entry *xt;
|
||||
xt = (dataset_->x)[work_idx_old];
|
||||
yt = y_[work_idx_old];
|
||||
yt_hat = SparseDot(w_, xt) * scale_w_;
|
||||
yy_hat = yt * yt_hat;
|
||||
|
||||
|
||||
double grad_tmp = yt * LossFunctionGradient_(learner_typeid, yy_hat);
|
||||
|
||||
//printf("epo:%d, ct:%d, w_size=%d, y_grad:%f\n", epo, ct, w_.size(), grad_tmp);
|
||||
for (i=0; i<n_features_; i++) {
|
||||
if (i < xt->index) {
|
||||
if (w_[i].value >= -g_ && w_[i].value <= g_) {
|
||||
w_[i].value = 0;
|
||||
}
|
||||
// else w_[i].value remains unchanged
|
||||
}
|
||||
else { // i== xt->index
|
||||
double db_tmp = w_[i].value - eta_ * grad_tmp * xt->value;
|
||||
if (db_tmp >= 0.0 && db_tmp <= g_) {
|
||||
if (!(ct_k % k_)) {
|
||||
db_tmp -= eta_ * k_ * g_;
|
||||
}
|
||||
if (db_tmp >=0.0) {
|
||||
w_[i].value = db_tmp;
|
||||
}
|
||||
else {
|
||||
w_[i].value = 0.0;
|
||||
}
|
||||
}
|
||||
else if (db_tmp >= -g_ && db_tmp <= 0.0) {
|
||||
if (!(ct_k % k_)) {
|
||||
db_tmp += eta_ * k_ * g_;
|
||||
}
|
||||
if (db_tmp <=0.0) {
|
||||
w_[i].value = db_tmp;
|
||||
}
|
||||
else {
|
||||
w_[i].value = 0.0;
|
||||
}
|
||||
}
|
||||
else {
|
||||
w_[i].value = db_tmp;
|
||||
}
|
||||
++xt;
|
||||
}
|
||||
}
|
||||
|
||||
/*
|
||||
printf("epo:%d, ct:%d\n", epo, ct);
|
||||
for (i=0; i<w_.size(); i++)
|
||||
printf("w[%d]=%f\n", i, w_[i].value);
|
||||
*/
|
||||
|
||||
t_ += 1.0;
|
||||
ct ++;
|
||||
ct_k ++;
|
||||
}
|
||||
}// for epo
|
||||
|
||||
/*
|
||||
// find max(abs(w_i))
|
||||
double wi_abs_max = -INFINITY;
|
||||
double wi_abs;
|
||||
for (i=0; i<w_.size(); i++) {
|
||||
wi_abs = fabs(w_[i].value);
|
||||
if (wi_abs > wi_abs_max) {
|
||||
wi_abs_max = wi_abs;
|
||||
}
|
||||
}
|
||||
|
||||
// round small w_i to 0
|
||||
index_t w_ct = 0;
|
||||
double round_factor = fx_param_double(NULL, "round_factor", 1.0e32);
|
||||
double round_thd = wi_abs_max / round_factor;
|
||||
for (i=0; i<w_.size(); i++) {
|
||||
if ( fabs(w_[i].value) > round_thd ) {
|
||||
w_ct ++;
|
||||
//printf("w_dim:%d, w_value:%lf\n", i, w_[i]);
|
||||
}
|
||||
else {
|
||||
w_.Remove(i);
|
||||
}
|
||||
}
|
||||
printf("%d out of %d features are non zero\n", w_ct, n_features_);
|
||||
*/
|
||||
|
||||
// rounding w
|
||||
index_t w_size = w_.size();
|
||||
round_thd_ = fx_param_double(NULL, "thd", 1.0e-5);
|
||||
w_nnz_ = 0;
|
||||
for (ct=0; ct<w_size; ct++) {
|
||||
if (fabs(w_[ct].value) >= round_thd_) { // TODO: thresholding
|
||||
w_nnz_++;
|
||||
}
|
||||
}
|
||||
|
||||
printf("%d out of %d features are non zero. NZ rate:%f\n", w_nnz_, n_features_, (double)(w_nnz_)/(double)n_features_);
|
||||
|
||||
// Calculate objective value; default: no calculation to save time
|
||||
int objvalue = fx_param_int(NULL, "objvalue", 0);
|
||||
if (objvalue > 0) {
|
||||
double hinge_loss = 0.0, loss_sum= 0.0, w_sum = 0.0;
|
||||
|
||||
// primal objective value
|
||||
for (i=0; i<n_data_; i++) {
|
||||
hinge_loss = 1- y_[i] * SparseDot(w_, (dataset_->x)[i]);
|
||||
if (hinge_loss > 0) {
|
||||
loss_sum += hinge_loss;
|
||||
}
|
||||
}
|
||||
for (i=0; i<n_features_; i++) {
|
||||
w_sum += fabs(w_[i].value);
|
||||
}
|
||||
double obj_value = loss_sum + g_ * w_sum;
|
||||
|
||||
printf("Primal objective value: %lf\n", obj_value);
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
#endif
|
||||
|
||||
@@ -0,0 +1,341 @@
|
||||
/**
|
||||
* @author Hua Ouyang
|
||||
*
|
||||
* @file regmin.cc
|
||||
*
|
||||
* This file contains main routines for performing Regularized Risk Minimizations.
|
||||
* Sparse matrix vector manipulations are implemented.
|
||||
*
|
||||
* It provides four modes:
|
||||
* "cv": cross validation;
|
||||
* "train": model training
|
||||
* "train_test": training and then online batch testing;
|
||||
* "test": offline batch testing.
|
||||
*
|
||||
* Please refer to README for detail description of usage and examples.
|
||||
*
|
||||
* @see regmin.h
|
||||
* @see opt_smo.h
|
||||
* @see opt_sgd.h
|
||||
*/
|
||||
|
||||
#include <errno.h>
|
||||
#include "regmin.h"
|
||||
|
||||
char *line = NULL;
|
||||
index_t max_line_length; // buffer size for read a line
|
||||
|
||||
Dataset_sl train_set;
|
||||
NZ_entry *train_nz_pool; // a pool for all non-zero entries in the training set
|
||||
Dataset_sl test_set;
|
||||
NZ_entry *test_nz_pool; // a pool for all non-zero entries in the testing set
|
||||
|
||||
|
||||
static char *ReadLine(FILE *fp_in) {
|
||||
index_t length;
|
||||
if ( fgets(line, max_line_length, fp_in)==NULL ) {
|
||||
return NULL;
|
||||
}
|
||||
while ( strrchr(line,'\n')==NULL ) {
|
||||
max_line_length *= 2;
|
||||
line = (char *) realloc(line, max_line_length);
|
||||
length = (index_t) strlen(line);
|
||||
if ( fgets(line+length, max_line_length-length, fp_in)==NULL ) {
|
||||
break;
|
||||
}
|
||||
}
|
||||
return line;
|
||||
}
|
||||
|
||||
int ReadData(Dataset_sl &dataset, struct NZ_entry *nz_pool, FILE *fp) {
|
||||
index_t max_index, inst_max_index, i, j;
|
||||
char *endptr, *label, *index, *value;
|
||||
|
||||
max_index = 0;
|
||||
j = 0;
|
||||
for (i=0; i<dataset.n_points; i++) {
|
||||
inst_max_index = -1;
|
||||
ReadLine(fp);
|
||||
dataset.x[i] = &nz_pool[j];
|
||||
label = strtok(line, " \t");
|
||||
dataset.y[i] = strtod(label, &endptr);
|
||||
if (endptr == label) {
|
||||
printf("No values found for a data point at line %d\n", i+1);
|
||||
return 0;
|
||||
}
|
||||
while (1) {
|
||||
index = strtok(NULL, ":");
|
||||
value = strtok(NULL, " \t");
|
||||
if (value == NULL) {
|
||||
break;
|
||||
}
|
||||
errno = 0;
|
||||
// in svmlight's data format, feature index begins from 1, not 0
|
||||
nz_pool[j].index = (index_t) strtol(index, &endptr, 10) - 1;
|
||||
if (endptr == index || errno !=0 || *endptr !='\0' || nz_pool[j].index<=inst_max_index) {
|
||||
printf("No values found for a data point at line %d\n", i+1);
|
||||
return 0;
|
||||
}
|
||||
else {
|
||||
inst_max_index = nz_pool[j].index;
|
||||
}
|
||||
errno = 0;
|
||||
nz_pool[j].value = strtod(value, &endptr);
|
||||
if ( endptr == value || errno !=0 || (*endptr!='\0' && !isspace(*endptr)) ) {
|
||||
printf("No values found for a data point at line %d\n", i+1);
|
||||
return 0;
|
||||
}
|
||||
j++;
|
||||
}
|
||||
if (inst_max_index > max_index) {
|
||||
max_index = inst_max_index;
|
||||
}
|
||||
nz_pool[j++].index = -1; // an indicator of the end of a data point
|
||||
}
|
||||
dataset.n_features = max_index + 1;
|
||||
return 1;
|
||||
}
|
||||
|
||||
/**
|
||||
* Initialize sparse training dataset from a file
|
||||
*
|
||||
* @param: the training set
|
||||
* @param: the training filename
|
||||
*/
|
||||
int InitTrainsetFromFile(String param) {
|
||||
if (fx_param_exists(NULL, param)) {
|
||||
String train_filename = fx_param_str_req(NULL, param);
|
||||
FILE *fp = fopen(train_filename, "r");
|
||||
if (fp == NULL) {
|
||||
fprintf(stderr, "Cannot open the specified training file!!!\n");
|
||||
return 0;
|
||||
}
|
||||
else {
|
||||
index_t num_nz_entries = 0;
|
||||
train_set.n_points= 0;
|
||||
|
||||
// count # of data points, # of non-zero entries
|
||||
while ( ReadLine(fp)!= NULL ) {
|
||||
// skip the lable
|
||||
char *p = strtok(line, " \t");
|
||||
// count # of features
|
||||
while (1) {
|
||||
p = strtok(NULL, " \t");
|
||||
if (p == NULL || *p == '\n') {
|
||||
break;
|
||||
}
|
||||
num_nz_entries ++;
|
||||
}
|
||||
num_nz_entries ++; // add an indicator for the end of this data point
|
||||
train_set.n_points ++;
|
||||
}
|
||||
rewind(fp);
|
||||
|
||||
train_set.y = Malloc(double, train_set.n_points);
|
||||
train_set.x = Malloc(struct NZ_entry *, train_set.n_points);
|
||||
train_nz_pool = Malloc(struct NZ_entry, num_nz_entries);
|
||||
|
||||
if (ReadData(train_set, train_nz_pool, fp)) {
|
||||
fclose(fp);
|
||||
return 1;
|
||||
}
|
||||
else {
|
||||
free(train_set.y);
|
||||
free(train_set.x);
|
||||
free(train_nz_pool);
|
||||
fclose(fp);
|
||||
fprintf(stderr, "Errors in training file format!!!\n");
|
||||
return 0;
|
||||
}
|
||||
}
|
||||
}
|
||||
else {
|
||||
fprintf(stderr, "No training filename specified !!!\n");
|
||||
return 0;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Initialize sparse testing dataset from a file
|
||||
*
|
||||
* @param: the testing set
|
||||
* @param: the testing filename
|
||||
*/
|
||||
int InitTestsetFromFile(String param) {
|
||||
if (fx_param_exists(NULL, param)) {
|
||||
String test_filename = fx_param_str_req(NULL, param);
|
||||
FILE *fp = fopen(test_filename, "r");
|
||||
if (fp == NULL) {
|
||||
fprintf(stderr, "Cannot open the specified testing file!!!\n");
|
||||
return 0;
|
||||
}
|
||||
else {
|
||||
index_t num_nz_entries = 0;
|
||||
test_set.n_points= 0;
|
||||
|
||||
// count # of data points, # of non-zero entries
|
||||
while ( ReadLine(fp)!= NULL ) {
|
||||
// skip the lable
|
||||
char *p = strtok(line, " \t");
|
||||
// count # of features
|
||||
while (1) {
|
||||
p = strtok(NULL, " \t");
|
||||
if (p == NULL || *p == '\n') {
|
||||
break;
|
||||
}
|
||||
num_nz_entries ++;
|
||||
}
|
||||
num_nz_entries ++; // add an indicator for the end of this data point
|
||||
test_set.n_points ++;
|
||||
}
|
||||
rewind(fp);
|
||||
|
||||
test_set.y = Malloc(double, test_set.n_points);
|
||||
test_set.x = Malloc(struct NZ_entry *, test_set.n_points);
|
||||
test_nz_pool = Malloc(struct NZ_entry, num_nz_entries);
|
||||
|
||||
if (ReadData(test_set, test_nz_pool, fp)) {
|
||||
fclose(fp);
|
||||
return 1;
|
||||
}
|
||||
else {
|
||||
free(test_set.y);
|
||||
free(test_set.x);
|
||||
free(test_nz_pool);
|
||||
fclose(fp);
|
||||
fprintf(stderr, "Errors in testing file format!!!\n");
|
||||
return 0;
|
||||
}
|
||||
}
|
||||
}
|
||||
else {
|
||||
fprintf(stderr, "No testing filename specified!!!\n");
|
||||
return 0;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Multiclass SVM classification/ SVM regression - Main function
|
||||
*
|
||||
* @param: argc
|
||||
* @param: argv
|
||||
*/
|
||||
int main(int argc, char *argv[]) {
|
||||
fx_init(argc, argv, NULL);
|
||||
|
||||
srand(time(NULL));
|
||||
|
||||
String mode = fx_param_str_req(NULL, "mode");
|
||||
String kernel = fx_param_str_req(NULL, "kernel");
|
||||
String learner_name = fx_param_str_req(NULL,"learner_name");
|
||||
int learner_typeid;
|
||||
|
||||
if (learner_name == "svm_c") { // Support Vector Classfication
|
||||
learner_typeid = 0;
|
||||
}
|
||||
else if (learner_name == "svm_r") { // Support Vector Regression
|
||||
learner_typeid = 1;
|
||||
}
|
||||
else if (learner_name == "svm_q") { // Support Vector Quantile Estimation
|
||||
learner_typeid = 2;
|
||||
}
|
||||
else {
|
||||
fprintf(stderr, "Unknown support vector learner name!!!\n");
|
||||
return 0;
|
||||
}
|
||||
|
||||
max_line_length = 1024;
|
||||
line = Malloc(char, max_line_length);
|
||||
|
||||
/* Training Mode, need training data | Training + Testing(online) Mode, need training data + testing data */
|
||||
if (mode=="train" || mode=="train_test"){
|
||||
fprintf(stderr, "SVM Training... \n");
|
||||
|
||||
/* Load training data */
|
||||
if (InitTrainsetFromFile("train_data") == 0) {
|
||||
free(line);
|
||||
exit(1);
|
||||
}
|
||||
|
||||
/* Begin SVM Training | Training and Testing */
|
||||
datanode *svm_module = fx_submodule(fx_root, "svm");
|
||||
if (kernel == "linear") {
|
||||
SVM<SVMLinearKernel> svm;
|
||||
svm.InitTrain(learner_typeid, train_set, svm_module);
|
||||
|
||||
/* training and testing, thus no need to load model from file */
|
||||
if (mode=="train_test"){
|
||||
fprintf(stderr, "SVM Predicting... \n");
|
||||
/* Load testing data */
|
||||
if (InitTestsetFromFile("test_data") == 0) {
|
||||
free(line);
|
||||
exit(1);
|
||||
}
|
||||
svm.BatchPredict(learner_typeid, test_set, "predicted_values");
|
||||
free(test_set.y);
|
||||
free(test_set.x);
|
||||
free(test_nz_pool);
|
||||
}
|
||||
|
||||
free(train_set.y);
|
||||
free(train_set.x);
|
||||
free(train_nz_pool);
|
||||
}
|
||||
else if (kernel == "gaussian") {
|
||||
SVM<SVMRBFKernel> svm;
|
||||
svm.InitTrain(learner_typeid, train_set, svm_module);
|
||||
|
||||
/* training and testing, thus no need to load model from file */
|
||||
if (mode=="train_test"){
|
||||
fprintf(stderr, "SVM Predicting... \n");
|
||||
/* Load testing data */
|
||||
if (InitTestsetFromFile("test_data") == 0) {
|
||||
free(line);
|
||||
exit(1);
|
||||
}
|
||||
svm.BatchPredict(learner_typeid, test_set, "predicted_values");
|
||||
free(test_set.y);
|
||||
free(test_set.x);
|
||||
free(test_nz_pool);
|
||||
}
|
||||
|
||||
free(train_set.y);
|
||||
free(train_set.x);
|
||||
free(train_nz_pool);
|
||||
}
|
||||
}
|
||||
/* Testing(offline) Mode, need loading model file and testing data */
|
||||
else if (mode=="test") {
|
||||
fprintf(stderr, "SVM Predicting... \n");
|
||||
|
||||
/* Load testing data */
|
||||
if (InitTestsetFromFile("test_data") == 0) {
|
||||
free(line);
|
||||
exit(1);
|
||||
}
|
||||
|
||||
/* Begin Prediction */
|
||||
datanode *svm_module = fx_submodule(fx_root, "svm");
|
||||
|
||||
if (kernel == "linear") {
|
||||
SVM<SVMLinearKernel> svm;
|
||||
svm.Init(learner_typeid, test_set, svm_module);
|
||||
svm.LoadModelBatchPredict(learner_typeid, test_set, "svm_model", "predicted_values");
|
||||
free(test_set.y);
|
||||
free(test_set.x);
|
||||
free(test_nz_pool);
|
||||
}
|
||||
else if (kernel == "gaussian") {
|
||||
SVM<SVMRBFKernel> svm;
|
||||
svm.Init(learner_typeid, test_set, svm_module);
|
||||
svm.LoadModelBatchPredict(learner_typeid, test_set, "svm_model", "predicted_values");
|
||||
free(test_set.y);
|
||||
free(test_set.x);
|
||||
free(test_nz_pool);
|
||||
}
|
||||
}
|
||||
free(line);
|
||||
fx_done(NULL);
|
||||
}
|
||||
|
||||
File diff suppressed because it is too large
Load Diff
@@ -0,0 +1,440 @@
|
||||
/**
|
||||
* @author Hua Ouyang
|
||||
*
|
||||
* @file regmin_data.h
|
||||
*
|
||||
* This head file contains sparse data related functions
|
||||
*
|
||||
*/
|
||||
|
||||
#ifndef U_REGMIN_DATA_H
|
||||
#define U_REGMIN_DATA_H
|
||||
|
||||
#include "fastlib/fastlib.h"
|
||||
|
||||
#define Malloc(type,n) (type *)malloc((n)*sizeof(type))
|
||||
|
||||
|
||||
#define ID_LINEAR 0
|
||||
#define ID_GAUSSIAN 1
|
||||
|
||||
/**
|
||||
* An nonzero entry(dimension) of a data point
|
||||
*/
|
||||
struct NZ_entry {
|
||||
index_t index;
|
||||
double value;
|
||||
};
|
||||
|
||||
/**
|
||||
* Sparse labeled dataset
|
||||
*/
|
||||
struct Dataset_sl {
|
||||
index_t n_features;
|
||||
index_t n_points;
|
||||
index_t n_classes;
|
||||
double *y; // labels of data points
|
||||
struct NZ_entry **x; // data points
|
||||
//ArrayList<NZ_entry> *x; // TODO
|
||||
};
|
||||
|
||||
|
||||
/**
|
||||
* Class for Linear Kernel
|
||||
*/
|
||||
class SVMLinearKernel {
|
||||
public:
|
||||
// Init of kernel parameters
|
||||
ArrayList<double> kpara_; // kernel parameters
|
||||
void Init(datanode *node) { //TODO: NULL->node
|
||||
kpara_.Init();
|
||||
}
|
||||
// Kernel name
|
||||
void GetName(String* kname) {
|
||||
kname->Copy("linear");
|
||||
}
|
||||
// Get an type ID for kernel
|
||||
int GetTypeId() {
|
||||
return ID_LINEAR;
|
||||
}
|
||||
// Kernel value evaluation
|
||||
double Eval(NZ_entry *pa, NZ_entry *pb) {
|
||||
// sparse dot product
|
||||
double kv = 0.0;
|
||||
if (pa->index == -1 || pb->index == -1) {
|
||||
return 0.0;
|
||||
}
|
||||
else {
|
||||
while (pa->index !=-1 && pb->index !=-1) {
|
||||
if (pa->index == pb->index) {
|
||||
kv += pa->value * pb->value;
|
||||
++pa;
|
||||
++pb;
|
||||
}
|
||||
else {
|
||||
if (pa->index > pb->index) {
|
||||
++pb;
|
||||
}
|
||||
else {
|
||||
++pa;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
return kv;
|
||||
}
|
||||
// Save kernel parameters to file
|
||||
void SaveParam(FILE* fp) {
|
||||
}
|
||||
};
|
||||
|
||||
/**
|
||||
* Class for Gaussian RBF Kernel
|
||||
*/
|
||||
class SVMRBFKernel {
|
||||
public:
|
||||
// Init of kernel parameters
|
||||
ArrayList<double> kpara_; // kernel parameters
|
||||
void Init(datanode *node) { //TODO: NULL->node
|
||||
kpara_.Init(2);
|
||||
kpara_[0] = fx_param_double_req(NULL, "sigma"); // sigma
|
||||
kpara_[1] = -1.0 / (2 * kpara_[0] * kpara_[0]); // -gamma = -1/(2 sigma^2)
|
||||
}
|
||||
// Kernel name
|
||||
void GetName(String* kname) {
|
||||
kname->Copy("gaussian");
|
||||
}
|
||||
// Get an type ID for kernel
|
||||
int GetTypeId() {
|
||||
return ID_GAUSSIAN;
|
||||
}
|
||||
// Kernel value evaluation
|
||||
double Eval(NZ_entry *pa, NZ_entry *pb) {
|
||||
double kv = 0;
|
||||
while (pa->index !=-1 && pb->index !=-1) {
|
||||
if (pa->index == pb->index) {
|
||||
double tmp = pa->value - pb->value;
|
||||
kv += tmp * tmp;
|
||||
++pa;
|
||||
++pb;
|
||||
}
|
||||
else {
|
||||
if (pa->index > pb->index) {
|
||||
kv += pb->value * pb->value;
|
||||
++pb;
|
||||
}
|
||||
else {
|
||||
kv += pa->value * pa->value;
|
||||
++pa;
|
||||
}
|
||||
}
|
||||
}
|
||||
while (pa->index != -1) {
|
||||
kv += pa->value * pa->value;
|
||||
++pa;
|
||||
}
|
||||
while (pb->index != -1) {
|
||||
kv += pb->value * pb->value;
|
||||
++ pb;
|
||||
}
|
||||
kv = exp( kpara_[1] * kv );
|
||||
return kv;
|
||||
}
|
||||
// Save kernel parameters to file
|
||||
void SaveParam(FILE* fp) {
|
||||
fprintf(fp, "sigma %g\n", kpara_[0]);
|
||||
fprintf(fp, "gamma %g\n", kpara_[1]);
|
||||
}
|
||||
};
|
||||
|
||||
|
||||
/**
|
||||
* Sparse vector scaling: w<= scale *w
|
||||
*/
|
||||
void SparseScale(double scale, ArrayList<NZ_entry> &w) {
|
||||
if (scale == 0) {
|
||||
w.ShrinkTo(0);
|
||||
}
|
||||
else {
|
||||
for (index_t i=0; i<w.size(); i++) {
|
||||
w[i].value = w[i].value * scale;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Sparse dot product: return w^T x
|
||||
*/
|
||||
double SparseDot(ArrayList<NZ_entry> &w, NZ_entry *x) {
|
||||
double dv = 0.0;
|
||||
index_t w_nz = w.size();
|
||||
index_t ct = 0;
|
||||
if (w_nz == 0 || x->index == -1) {
|
||||
return 0.0;
|
||||
}
|
||||
else {
|
||||
while (ct<w_nz && x->index !=-1) {
|
||||
if (w[ct].index == x->index) {
|
||||
dv += w[ct].value * x->value;
|
||||
++ct;
|
||||
++x;
|
||||
}
|
||||
else {
|
||||
if (w[ct].index > x->index) {
|
||||
++x;
|
||||
}
|
||||
else {
|
||||
++ct;
|
||||
}
|
||||
}
|
||||
}
|
||||
return dv;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Sparse vector scaled add: w<= w+ scale * x
|
||||
*/
|
||||
void SparseAddExpert(double scale, NZ_entry *x, ArrayList<NZ_entry> &w) {
|
||||
index_t ct = 0;
|
||||
NZ_entry nz_tmp;
|
||||
|
||||
if (x->index == -1 || scale == 0) { // x: all zeros or scale ==0
|
||||
// w remains unchanged
|
||||
}
|
||||
else if (w.size() == 0) { // w: all zeros
|
||||
while (x->index != -1) {
|
||||
nz_tmp.index = x->index;
|
||||
nz_tmp.value = scale * x->value;
|
||||
w.PushBackCopy(nz_tmp);
|
||||
++x;
|
||||
}
|
||||
}
|
||||
else { // neither w nor x is of all zeros
|
||||
while (ct<w.size() || x->index !=-1) {
|
||||
if (ct == w.size()) { // w reaches end, while x still not
|
||||
nz_tmp.index = x->index;
|
||||
nz_tmp.value = scale * x->value;
|
||||
w.PushBackCopy(nz_tmp);
|
||||
++ct;
|
||||
++x;
|
||||
}
|
||||
else if (x->index == -1) { // x reaches end, while w still not
|
||||
// the succeeding w remain unchanged
|
||||
break;
|
||||
}
|
||||
else { // neither w nor x reaches end
|
||||
if (w[ct].index == x->index) {
|
||||
w[ct].value = w[ct].value + scale * x->value;
|
||||
++ct;
|
||||
++x;
|
||||
}
|
||||
else if (w[ct].index > x->index) {
|
||||
nz_tmp.index = x->index;
|
||||
nz_tmp.value = scale * x->value;
|
||||
w.InsertCopy(ct, nz_tmp); // w's dimension increases by 1
|
||||
++ct;
|
||||
++x;
|
||||
}
|
||||
else { // w[ct].index < x->index
|
||||
// w[ct] remains unchanged
|
||||
++ct;
|
||||
}
|
||||
}
|
||||
}
|
||||
// shrink w
|
||||
for (ct=0; ct<w.size(); ct++) {
|
||||
if (fabs(w[ct].value) < 1.0e-5) { // TODO: thresholding
|
||||
w.Remove(ct);
|
||||
ct--;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
/**
|
||||
* Sparse vector subtraction: z <= y-x
|
||||
*/
|
||||
void SparseSub(ArrayList<NZ_entry> &x,ArrayList<NZ_entry> &y, ArrayList<NZ_entry> &z) {
|
||||
index_t x_size, y_size;
|
||||
index_t i, ct_x, ct_y;
|
||||
x_size = x.size();
|
||||
y_size = y.size();
|
||||
ct_x = 0; ct_y = 0;
|
||||
NZ_entry nz_tmp;
|
||||
|
||||
/*
|
||||
for (index_t i=0; i<y.size(); i++) {
|
||||
printf("y[%d].index=%d,.value=%f\n", i, y[i].index, y[i].value);
|
||||
}
|
||||
for (index_t i=0; i<x.size(); i++) {
|
||||
printf("x[%d].index=%d,.value=%f\n", i, x[i].index, x[i].value);
|
||||
}
|
||||
*/
|
||||
|
||||
z.ShrinkTo(0);
|
||||
|
||||
if (y_size == 0) { // y: all zeros
|
||||
z.GrowTo(x_size);
|
||||
for (i=0; i<x_size; i++) {
|
||||
z[i].index = x[i].index;
|
||||
z[i].value = - x[i].value;
|
||||
}
|
||||
}
|
||||
else if (x_size == 0) { // x: all zeros
|
||||
z.GrowTo(y_size);
|
||||
for (i=0; i<y_size; i++) {
|
||||
z[i].index = y[i].index;
|
||||
z[i].value = y[i].value;
|
||||
}
|
||||
}
|
||||
else { // neither x nor y is of all zeros
|
||||
while ( ct_x < x_size || ct_y < y_size ) {
|
||||
if (ct_x == x_size) { // x reaches end, while y still not
|
||||
nz_tmp.index = y[ct_y].index;
|
||||
nz_tmp.value = y[ct_y].value;
|
||||
z.PushBackCopy(nz_tmp);
|
||||
++ct_y;
|
||||
}
|
||||
else if (ct_y == y_size) { // y reaches end, while x still not
|
||||
nz_tmp.index = x[ct_x].index;
|
||||
nz_tmp.value = - x[ct_x].value;
|
||||
z.PushBackCopy(nz_tmp);
|
||||
++ct_x;
|
||||
}
|
||||
else { // neither x nor y reaches end
|
||||
if (x[ct_x].index == y[ct_y].index) {
|
||||
nz_tmp.index = x[ct_x].index;
|
||||
nz_tmp.value = y[ct_y].value - x[ct_x].value;
|
||||
z.PushBackCopy(nz_tmp);
|
||||
++ct_x;
|
||||
++ct_y;
|
||||
}
|
||||
else if (y[ct_y].index > x[ct_x].index) {
|
||||
nz_tmp.index = x[ct_x].index;
|
||||
nz_tmp.value = - x[ct_x].value;
|
||||
z.InsertCopy(ct_y, nz_tmp); // w's dimension increases by 1
|
||||
++ct_x;
|
||||
}
|
||||
else { // y[ct].index < x[ct_x].index
|
||||
++ct_y;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
/*
|
||||
for (index_t i=0; i<z.size(); i++) {
|
||||
printf("z[%d].index=%d,.value=%f\n", i, z[i].index, z[i].value);
|
||||
}
|
||||
*/
|
||||
|
||||
}
|
||||
|
||||
|
||||
|
||||
|
||||
/**
|
||||
* Sparse vector subtraction: y <= y-x
|
||||
*/
|
||||
void SparseSubOverwrite(ArrayList<NZ_entry> &x,ArrayList<NZ_entry> &y) {
|
||||
index_t x_size;
|
||||
index_t i, ct_x, ct_y;
|
||||
x_size = x.size();
|
||||
ct_x = 0; ct_y = 0;
|
||||
NZ_entry nz_tmp;
|
||||
|
||||
if (y.size() == 0) { // y: all zeros
|
||||
y.GrowTo(x_size);
|
||||
for (i=0; i<x_size; i++) {
|
||||
y[i].index = x[i].index;
|
||||
y[i].value = - x[i].value;
|
||||
}
|
||||
}
|
||||
else if (x_size == 0) { // x: all zeros
|
||||
// y remains unchanged
|
||||
}
|
||||
else { // neither x nor y is of all zeros
|
||||
while (ct_x < x_size || ct_y < y.size() ) {
|
||||
if (ct_x == x_size) { // x reaches end, while y still not
|
||||
break;
|
||||
}
|
||||
else if (ct_y == y.size()) { // y reaches end, while x still not
|
||||
nz_tmp.index = x[ct_x].index;
|
||||
nz_tmp.value = - x[ct_x].value;
|
||||
y.PushBackCopy(nz_tmp);
|
||||
++ct_x;
|
||||
++ct_y;
|
||||
}
|
||||
else { // neither x nor y reaches end
|
||||
if (y[ct_y].index == x[ct_x].index) {
|
||||
y[ct_y].value = y[ct_y].value - x[ct_x].value;
|
||||
++ct_x;
|
||||
++ct_y;
|
||||
}
|
||||
else if (y[ct_y].index > x[ct_x].index) {
|
||||
nz_tmp.index = x[ct_x].index;
|
||||
nz_tmp.value = - x[ct_x].value;
|
||||
y.InsertCopy(ct_y, nz_tmp); // w's dimension increases by 1
|
||||
++ct_x;
|
||||
++ct_y;
|
||||
}
|
||||
else { // y[ct].index < x[ct_x].index
|
||||
++ct_y;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
/**
|
||||
* Sparse element-wise multiplication of vectors: y <= y .* x
|
||||
*/
|
||||
void SparseElementMulOverwrite(ArrayList<NZ_entry> &y, ArrayList<NZ_entry> &x) {
|
||||
index_t ct_x = 0;
|
||||
index_t ct_y = 0;
|
||||
|
||||
if (y.size() == 0) { // y: all zeros
|
||||
// y remains unchanged
|
||||
}
|
||||
else if (x.size() == 0) { // x: all zeros
|
||||
y.ShrinkTo(0);
|
||||
}
|
||||
else { // neither x nor y is of all zeros
|
||||
while (ct_x < x.size() || ct_y < y.size()) {
|
||||
if (ct_x == x.size()) { // x reaches end, while y still not
|
||||
y.Remove(ct_y);
|
||||
//printf("ct_y=%d\n", ct_y);
|
||||
}
|
||||
else if (ct_y == y.size()) { // y reaches end, while x still not
|
||||
break;
|
||||
}
|
||||
else { // neither x nor y reaches end
|
||||
if (x[ct_x].index == y[ct_y].index) {
|
||||
y[ct_y].value = y[ct_y].value * x[ct_x].value;
|
||||
++ct_x;
|
||||
++ct_y;
|
||||
}
|
||||
else if (y[ct_y].index > x[ct_x].index) {
|
||||
++ct_x;
|
||||
//printf("y[%d].index=%d; x[%d].index=%d\n",ct_y, y[ct_y].index, ct_x, x[ct_x].index);
|
||||
}
|
||||
else { // y[ct].index < x[ct_x].index
|
||||
y.Remove(ct_y);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
|
||||
#endif
|
||||
Reference in New Issue
Block a user