/**
 * @file methods/adaboost/adaboost.hpp
 * @author Udit Saxena
 *
 * The AdaBoost class.  AdaBoost is a boosting algorithm, meaning that it
 * combines an ensemble of weak learners to produce a strong learner.  For more
 * information on AdaBoost, see the following paper:
 *
 * @code
 * @article{schapire1999improved,
 *   author = {Schapire, Robert E. and Singer, Yoram},
 *   title = {Improved Boosting Algorithms Using Confidence-rated Predictions},
 *   journal = {Machine Learning},
 *   volume = {37},
 *   number = {3},
 *   month = dec,
 *   year = {1999},
 *   issn = {0885-6125},
 *   pages = {297--336},
 * }
 * @endcode
 *
 * mlpack is free software; you may redistribute it and/or modify it under the
 * terms of the 3-clause BSD license.  You should have received a copy of the
 * 3-clause BSD license along with mlpack.  If not, see
 * http://www.opensource.org/licenses/BSD-3-Clause for more information.
 */
#ifndef MLPACK_METHODS_ADABOOST_ADABOOST_HPP
#define MLPACK_METHODS_ADABOOST_ADABOOST_HPP

#include <mlpack/prereqs.hpp>
#include <mlpack/methods/perceptron/perceptron.hpp>
#include <mlpack/methods/decision_tree/decision_tree.hpp>

namespace mlpack {
namespace adaboost {

/**
 * The AdaBoost class.  AdaBoost is a boosting algorithm, meaning that it
 * combines an ensemble of weak learners to produce a strong learner.  For more
 * information on AdaBoost, see the following paper:
 *
 * @code
 * @article{schapire1999improved,
 *   author = {Schapire, Robert E. and Singer, Yoram},
 *   title = {Improved Boosting Algorithms Using Confidence-rated Predictions},
 *   journal = {Machine Learning},
 *   volume = {37},
 *   number = {3},
 *   month = dec,
 *   year = {1999},
 *   issn = {0885-6125},
 *   pages = {297--336},
 * }
 * @endcode
 *
 * This class is general, and can be used with any type of weak learner, so long
 * as the learner implements the following functions:
 *
 * @code
 * // A boosting constructor, which learns using the training parameters of the
 * // given other WeakLearner, but uses the given instance weights for training.
 * WeakLearner(WeakLearner& other,
 *             const MatType& data,
 *             const arma::Row<size_t>& labels,
 *             const arma::rowvec& weights);
 *
 * // Given the test points, classify them and output predictions into
 * // predictedLabels.
 * void Classify(const MatType& data, arma::Row<size_t>& predictedLabels);
 * @endcode
 *
 * For more information on and examples of weak learners, see
 * perceptron::Perceptron<> and decision_stump::DecisionStump<>.
 *
 * @tparam MatType Data matrix type (i.e. arma::mat or arma::sp_mat).
 * @tparam WeakLearnerType Type of weak learner to use.
 */
template<typename WeakLearnerType = mlpack::perceptron::Perceptron<>,
         typename MatType = arma::mat>
class AdaBoost
{
 public:
  /**
   * Constructor.  This runs the AdaBoost.MH algorithm to provide a trained
   * boosting model.  This constructor takes an already-initialized weak
   * learner; all other weak learners will learn with the same parameters as the
   * given weak learner.
   *
   * @param data Input data.
   * @param labels Corresponding labels.
   * @param numClasses The number of classes.
   * @param iterations Number of boosting rounds.
   * @param tolerance The tolerance for change in values of rt.
   * @param other Weak learner that has already been initialized.
   */
  AdaBoost(const MatType& data,
           const arma::Row<size_t>& labels,
           const size_t numClasses,
           const WeakLearnerType& other,
           const size_t iterations = 100,
           const double tolerance = 1e-6);

  /**
   * Create the AdaBoost object without training.  Be sure to call Train()
   * before calling Classify()!
   */
  AdaBoost(const double tolerance = 1e-6);

  //! Get the tolerance for stopping the optimization during training.
  double Tolerance() const { return tolerance; }
  //! Modify the tolerance for stopping the optimization during training.
  double& Tolerance() { return tolerance; }

  //! Get the number of classes this model is trained on.
  size_t NumClasses() const { return numClasses; }

  //! Get the number of weak learners in the model.
  size_t WeakLearners() const { return alpha.size(); }

  //! Get the weights for the given weak learner.
  double Alpha(const size_t i) const { return alpha[i]; }
  //! Modify the weight for the given weak learner (be careful!).
  double& Alpha(const size_t i) { return alpha[i]; }

  //! Get the given weak learner.
  const WeakLearnerType& WeakLearner(const size_t i) const { return wl[i]; }
  //! Modify the given weak learner (be careful!).
  WeakLearnerType& WeakLearner(const size_t i) { return wl[i]; }

  /**
   * Train AdaBoost on the given dataset.  This method takes an initialized
   * WeakLearnerType; the parameters for this weak learner will be used to train
   * each of the weak learners during AdaBoost training.  Note that this will
   * completely overwrite any model that has already been trained with this
   * object.
   *
   * @param data Dataset to train on.
   * @param labels Labels for each point in the dataset.
   * @param numClasses The number of classes.
   * @param learner Learner to use for training.
   * @param iterations Number of boosting rounds.
   * @param tolerance The tolerance for change in values of rt.
   * @return The upper bound for training error.
   */
  double Train(const MatType& data,
               const arma::Row<size_t>& labels,
               const size_t numClasses,
               const WeakLearnerType& learner,
               const size_t iterations = 100,
               const double tolerance = 1e-6);

  /**
   * Classify the given test points.
   *
   * @param test Testing data.
   * @param predictedLabels Vector in which the predicted labels of the test
   *      set will be stored.
   * @param probabilities matrix to store the predicted class probabilities for
   *      each point in the test set.
   */
  void Classify(const MatType& test,
                arma::Row<size_t>& predictedLabels,
                arma::mat& probabilities);

  /**
   * Classify the given test points.
   *
   * @param test Testing data.
   * @param predictedLabels Vector in which the predicted labels of the test
   *      set will be stored.
   */
  void Classify(const MatType& test,
                arma::Row<size_t>& predictedLabels);

  /**
   * Serialize the AdaBoost model.
   */
  template<typename Archive>
  void serialize(Archive& ar, const unsigned int /* version */);

 private:
  //! The number of classes in the model.
  size_t numClasses;
  // The tolerance for change in rt and when to stop.
  double tolerance;

  //! The vector of weak learners.
  std::vector<WeakLearnerType> wl;
  //! The weights corresponding to each weak learner.
  std::vector<double> alpha;
}; // class AdaBoost

} // namespace adaboost
} // namespace mlpack

//! Set the serialization version of the adaboost class.
namespace boost {
namespace serialization {

template<typename WeakLearnerType, typename MatType>
struct version<mlpack::adaboost::AdaBoost<WeakLearnerType, MatType>>
{
  BOOST_STATIC_CONSTANT(int, value = 1);
};

} // namespace serialization
} // namespace boost

// Include implementation.
#include "adaboost_impl.hpp"

#endif
