// file: $isip_ifc/class/pr/RelevanceVectorMachine/rvm_06.cc
// version: $Id: rvm_06.cc 10424 2006-02-10 20:37:45Z raghavan $
//

// isip include files
//
#include "RelevanceVectorMachine.h"
#include <MatrixFloat.h>
#include <LinearAlgebra.h>

// method: train
//
// argument:
//  ALGORITHM mode: (input) mode to train in
//  float alpha_thresh: (input) hyperparameter pruning threshold
//  float min_allowed_weight: (input) weight pruning threshold
//
// return: boolean value indicating status
//
boolean RelevanceVectorMachine::train(RVMTrainData& tdata_a,
				      ALGORITHM mode_a) {
  
  // find the size of the full training set
  //
  long in_class_size = in_class_data_d.length();
  long out_class_size = out_class_data_d.length();
  long full_train_size = in_class_size + out_class_size;
  
  // declare local data
  //
  Vector<VectorFloat> train_data(full_train_size);
  VectorByte train_targets(full_train_size);
  
  // concatenate the data to create the full set
  //
  for (long i = 0; i < in_class_size; i++) {
    train_data(i).assign(in_class_data_d(i));
    train_targets(i).assign((byte)1);
  }
  for (long i = in_class_size, j = 0; i < full_train_size; i++, j++) {
    train_data(i).assign(out_class_data_d(j));
    train_targets(i).assign((byte)0);
  }
  
  // train the RVM
  //
  setData(train_data);
  setTargets(train_targets);
  
  if (algorithm_d == FULL) {
    return trainFull(tdata_a);
  }
  else if (algorithm_d == ITERATIVE_REFINEMENT) {
    return trainIterativeRefinement(tdata_a);
  }
  else if (algorithm_d == TIPPING_CONSTRUCTIVE) {
    return trainTippingConstructive(tdata_a);
  }
  else {
    return Error::handle(name(), L"train", Error::ARG, __FILE__, __LINE__);
  }
}

// method: trainFull
//
// argument:
//  RVMTrainData&: (input) training data object
//  boolean is_init: (input) is the data and object init'd already?
//
// return: boolean value indicating status
//
// this method trains an RVM probabilistic classifier on the input data and
// targets provided. the inputs and targets should have a one-to-one
// correspondence and all targets should be either 0 (out-of-class) or
// 1 (in-class). The training scheme follows that of [1] section 3.
// It is assumed that the class data and targets are already set when
// this method is called.
//
// the training algorithm in [1] for RVMs proceeds in three iterative steps
//
//   1. prune away any weights whose hyperparameters have gone to infinity
//
//   2. estimate most probable weights: in this step we find those weights that
//      maximize equation (24) of [1].  The iteratively reweighted least
//      squares algorithm is used to find w_mp
//
//   3. estimate the covariance of a Gaussian approximation to the posterior
//      distribution (the posterior is what we want to model in the end) over
//      the weights centered at the weights, w_mp.
//
//   4. estimate the hyperparameters that govern the weights. this is done 
//      by evaluating the derivative over the hyperparameters and finding the
//      maximizing hyperparameters.
//
// 1, 2, 3, and 4 are carried out iteratively until a suitable convergence
// criteria is satisfied.
//
boolean RelevanceVectorMachine::trainFull(RVMTrainData& tdata_a,
					  boolean is_init_a) { 

  // debugging information
  //
  if (debug_level_d > Integral::NONE) {
    Console::put(L"RVM training: Full");
    Console::increaseIndention();
  }

  if (debug_level_d > Integral::DETAILED) {
    vectors_d.debug(L"vectors_d");
    targets_d.debug(L"targets_d");
  }

  // 0. initialize data structures for training
  //
  if (!is_init_a) {
    if (!initFull(tdata_a)) {
      return Error::handle(name(), L"train", ERR_INIT, __FILE__, __LINE__);
    }
  }

  // debugging information
  //
  if (debug_level_d > Integral::NONE) {
    Console::put(L"RVM convergence loop");
    Console::increaseIndention();
  }

  // loop until convergence or until a maximum number of iterations has
  // been reached
  //
  long num_rvm_its = 0;
  boolean rvm_converged = false;
  while (!rvm_converged) {

    // store the weights achieved on the last iteration so we can test for
    // convergence later
    //
    tdata_a.last_rvm_weights_d.assign(tdata_a.curr_weights_d);

    // debugging information
    //
    if (debug_level_d > Integral::NONE) {
      String its(L"RVM Iteration: ");
      its.concat(num_rvm_its);
      Console::put(its);
      Console::increaseIndention();
    }
    
    if (debug_level_d > Integral::DETAILED) {
      tdata_a.A_d.debug(L"A");
      tdata_a.phi_d.debug(L"phi");
    }

    // 1. prune only after the first iteration (we use a weight of exactly
    // 0.0 to trigger pruning so pruning on the first iteration would
    // prune all weights
    //
    if (num_rvm_its > 0) {
      pruneAndUpdate(tdata_a);
    }

    // if all weights have been pruned, then there is nothing left to do
    // and the process has converged (albeit to a pretty useless solution)
    //
    if (tdata_a.dimA_d == 0) {
      rvm_converged = true;

      // debugging information
      //
      if (debug_level_d > Integral::NONE) {
	Console::put(L"rvm convergence achieved");
      }
      if (debug_level_d > Integral::NONE) {
	Console::decreaseIndention();
      }

      // conclude training
      //
      break;
    }
    
    // debugging information
    //
    if (debug_level_d > Integral::DETAILED) {
      tdata_a.phi_d.debug(L"phi");
      tdata_a.A_d.debug(L"A");
    }

    // 2. run a pass of IRLS training to estimate w_MP
    //
    irlsTrain(tdata_a);
    
    // 3. estimate the covariance of the Gaussian approximation
    // compute the variance vector. The covariance is the inverse
    // of the Hessian matrix. Only the diagonal elements are needed
    // to update the hyperparameters. From the Cholesky decomposition, we
    // can efficiently find these values. After this function call,
    // the diagonal elements of covar_cholesky will contain the
    // negation of the diagonal elements of the estimated covariance.
    // The other elements of covar_cholesky are not meaningful.
    //
    computeVarianceCholesky(tdata_a);

    // debugging information
    //
    if (debug_level_d > Integral::DETAILED) {
      tdata_a.covar_cholesky_d.debug(L"covar_cholesky");
    }

    // 4. reestimate the hyperparameters ignore first instance of the call
    // the "false" short-circuits it anyways - a work in progress to try to
    // figure out when Tipping's "aggressive" update equation can be used...
    //
    boolean A_changed = false;
    if (false && (num_rvm_its > 0)) {
      updateHyperparameters(A_changed, tdata_a, FULL, false);
    }
    else {
      updateHyperparameters(A_changed, tdata_a, FULL,true);
    }
    
    // store the final weights from this iteration
    //
    long start_index = 0;
    if (tdata_a.num_samples_d < tdata_a.dimA_d) {
      start_index = 1;
      bias_d = tdata_a.curr_weights_d(0);
    }
    weights_d.move(tdata_a.curr_weights_d, tdata_a.num_samples_d,
		   start_index, 0);

    // debugging information
    //
    if (debug_level_d > Integral::DETAILED) {
      tdata_a.curr_weights_d.debug(L"weights after rvm iteration");
      tdata_a.A_d.debug(L"A after rvm iteration");
    }

    // check convergence:
    // converge if the weight updates have stagnated and we have not pruned
    // off a weight
    //
    if (tdata_a.curr_weights_d.almostEqual(tdata_a.last_rvm_weights_d)) {

      tdata_a.last_changed_d++;
      if (!A_changed  ||
	  (tdata_a.last_changed_d > tdata_a.max_update_its_d)) {
	rvm_converged = true;
      }
    }
    else {
      tdata_a.last_changed_d = 0;
    }
    if (num_rvm_its > tdata_a.max_rvm_its_d) {
      rvm_converged = true;
    }
    
    // debugging information
    //
    if (rvm_converged) {
      if (debug_level_d > Integral::NONE) {
	Console::put(L"rvm convergence achieved");
      }
    }
    if (debug_level_d > Integral::NONE) {
      Console::decreaseIndention();
    }

    // next iteration of RVM optimization
    //
    num_rvm_its++;
  }

  if (debug_level_d > Integral::NONE) {
    Console::decreaseIndention();
  }

  // run any final steps in the training algorithm and store the final model
  //
  finalizeTraining(tdata_a, FULL);
  
  // debugging information
  //
  if (debug_level_d > Integral::NONE) {
    Console::decreaseIndention();
  }
  if (debug_level_d > Integral::BRIEF) {
    Console::put(L"End RVM training");
    targets_d.debug(L"final targets");
    vectors_d.debug(L"final relevance vectors");
    weights_d.debug(L"final weights");
    bias_d.debug(L"final bias");
    tdata_a.debug(L"tdata_a");
  }
  
  // exit gracefully
  //
  return true;
}


// method: trainIterativeRefinement
//
// argument: 
//  RVMTrainData&: (input) training data object
//  boolean is_init: (input) is the data and object init'd already?
//
// return: boolean value indicating status
//
// this method uses an iterative refinement technique to search the data set
// for relevance vectors. The full data set is traversed only once. Subsets
// of the data are defined where the size of the subset (and, thus, the size
// of the subproblems) are defined by the user. This method is not
// optimal in the likelihood sense and is somewhat dependent on the subset
// size used (larger the better, but slower). 
//
// the training algorithm proceeds in the following steps
//
//   1. Choose a subset of training vectors as candidate relevance vectors.
//      The size of this subset is chosen by the user
//
//   2. Train an RVM on the full training set, but where only the subset of
//      vectors chosen in 1. are allowed to contribute to the final model.
//
//   3. Combine the relevance vectors found in 2. along with N-#rvs new
//      vectors to create the next subset.
//
//   4. Repeat 2. and 3. until the entire training set has been observed
//
boolean RelevanceVectorMachine::trainIterativeRefinement(RVMTrainData& tdata_a,
							 boolean is_init_a) { 

  // debugging information
  //
  if (debug_level_d > Integral::NONE) {
    Console::put(L"RVM training: Iterative Refinement");
    Console::increaseIndention();
  }

  if (debug_level_d > Integral::DETAILED) {
    vectors_d.debug(L"vectors_d");
    targets_d.debug(L"targets_d");
  }

  // 0. initialize data structures for training
  //
  if (!is_init_a) {
    if (!initIterativeRefinement(tdata_a)) {
      return Error::handle(name(), L"train", ERR_INIT, __FILE__, __LINE__);
    }
  }

  // debugging information
  //
  if (debug_level_d > Integral::NONE) {
    Console::put(L"RVM convergence loop");
    Console::increaseIndention();
  }

  // of the remaining RVs in contention, initialize the subsets
  //
  initializeRVSubsets(tdata_a);
    
  // loop until the entire training set has been observed
  //
  boolean complete = false;
  tdata_a.active_subset_d = 0;  // keep track of our position in the data set
  RelevanceVectorMachine training_rvm;
  RVMTrainData training_tdata;
  
  while (!complete) {

    if (debug_level_d > Integral::NONE) {
      String its(L"remaining data: ");
      its.concat(tdata_a.subsets_d(0).length());
      Console::put(its);
    }

    training_rvm.clear();
    training_tdata.clear();

    // debugging information
    //
    if (debug_level_d > Integral::BRIEF) {
      tdata_a.debug(L"tdata_a before subset init");
    }

    initializeSubsetRVM(training_rvm, training_tdata, tdata_a);

    // determine if this is the last iteration
    //
    if (tdata_a.subsets_d(0).length() <= tdata_a.subset_size_d) {
      complete = true;
    }
    
    // train the RVM
    //
    training_rvm.trainFull(training_tdata, true);

    // determine which RVs remain and update their values
    //
    updateSubsetRVM(tdata_a, training_rvm, training_tdata);

  }

  // copy trained RVM to this RVM
  //
  assign(training_rvm);

  // debugging information
  //
  if (debug_level_d > Integral::NONE) {
    Console::put(L"rvm convergence achieved");
    Console::decreaseIndention();
    Console::decreaseIndention();
  }
  
  // debugging information
  //
  if (debug_level_d > Integral::NONE) {
    Console::put(L"End RVM training");
    targets_d.debug(L"final targets");
    vectors_d.debug(L"final relevance vectors");
    weights_d.debug(L"final weights");
    bias_d.debug(L"final bias");
  }
  
  // exit gracefully
  //
  return true;
}


// method: trainTippingConstructive
//
// argument: 
//  RVMTrainData&: (input) training data object
//  boolean is_init: (input) is the data and object init'd already?
//
// return: boolean value indicating status
//
// this method uses Tipping and Faul's constructive method for training
//
boolean RelevanceVectorMachine::trainTippingConstructive(RVMTrainData& tdata_a,
							 boolean is_init_a) {

  // not implemented yet
  
  // exit gracefully
  //
  return true;
}
